一个最简的 USB Audio 示例
经过了两三个月的痛苦,USB 协议栈的 Audio Device Class 框架已具雏形了,用了两三天时间,使用这个框架实战了一个基于新唐 M0 的最简单的 USB Audio 程序,可以作为 USB 声卡。
adc.zip (2.2 KB, 下载次数: 36) 附件中是生成的 hex 文件,可以运行在菜农助学板上,烧写时需要配置 Config0 中的时钟选择为 external 12Mhz。
代码空间开销:
text data bss dec hex filename
7c8 .\default\adc.elf
整个程序占用 flash 大约 1.8K,除去中断向量表、系统启动、硬件初始化和 I2S功能的空间开销,框架在这个示例程序中所占空间大约 1.2K 左右,随着功能多少的变化,框架的开销也会有变化,但对比目前市面上的各种 USB 协议栈驱动,我想它应该是开销最小的框架了。
先贴出应用程序,恭请各位提提意见,我好改进框架,谢谢。
#include <usb/hal/numicro> // USB HAL(Hardware Abstract Layer),新唐 Numicro
#include <usb/adc> // USB Audio Device Class
#include <numicro/nuc/sfr/i2s> // I2S Register
void i2s_start();
using namespace usb; // 使用 usb 名空间
using namespace adc; // 使用 usb adc(audio device class)名空间
template<typename PARENT, uint8_t ID> // 定式
struct it_t : it_impl_t< // 定义 Input Terminal 类模板
it_t, PARENT, ID, // 定式
// 指定 String ID,为 0 表示不定义
> {
using input_t = typename it_t::it_impl_t; // 为本类指定一个“别名”,方便在定义 Entities 集合时标识
};
template<typename PARENT, uint8_t ID>
struct ot_t : ot_impl_t< // 定义 Output Terminal 类模板
ot_t, PARENT, ID,
> {
using output_t = typename ot_t::ot_impl_t;
};
template<typename PARENT> // 定式
struct tuple_t : entity_tuple_t< // 定义 Entities 集合类,其中可以加入以上定义的各个 Entity 类模板
PARENT, // 定式
it_t, // 已定义的类模板
ot_t // 已定义的类模板
> {
using typename tuple_t::entity_tuple_t::input_t; // 导入各个 Terminal 的别名
using typename tuple_t::entity_tuple_t::output_t; // ...
struct __attribute__((packed)) entities_descriptor_t // 定义 Entities 集合的描述符
: td_t< // 定义 Terminal 描述符
input_t, // 使用已导入的别名,用于确定本 Entity ID,
USB_STREAMING, // 指定 Terminal 类型
cluster_t< // 定义 Channel(声道) Cluster,其中可以加入任意声道
, // 指定 String ID
cluster_channel_t::LEFT_FRONT, // 指定 Left Front
cluster_channel_t::RIGHT_FRONT // 指定 Right Front
>
>,
td_t< // 定义 Terminal 描述符
output_t, // 使用已导入的别名,用于确定本 Entity ID,
SPEAKER, // 指定 Terminal 类型
input_t // 指定本输出 Terminal 的数据来源
> { };
};
template<typename PARENT, uint32_t PARAM> // 定式
class ep_t : public streaming::data::out::ep_impl_t< // 定义 Audio Data Endpoint(数据端点)类
ep_t, PARENT, PARAM, // 定式
core::transfer::iso::sync_t::SYNC, // 指定端点的 sync 类型
false, // 指定 bmAttributes 的 sampling frequency,参见 USB Audio 规范
false, // 指定 bmAttributes 的 pitch,参见 USB Audio 规范
false, // 指定 bmAttributes 的 MaxPacketsOnly, 参见 USB Audio 规范
streaming::UNDEFINED, // 指定 bLockDelayUnits, 参见 USB Audio 规范
// 指定 wLockDelay, 参见 USB Audio 规范
> { };
#define SAMPLE_RATE 16000
struct { // 定义 Audio Data 缓冲区
union {
struct {
uint8_t wrpos; // 写缓冲指针
uint8_t rdpos; // 读缓冲指针
};
uint16_t pos;
};
uint32_t data[ * / sizeof(uint32_t)]; // 缓冲数据区,包含两个 packet
} buffer;
template<typename PARENT, uint32_t PARAM> // 定式
class ifc_t : public control::if_impl_t< // 定义 AudioControl Interface(控制接口)类
ifc_t, PARENT, PARAM, // 定式
// 指定 String ID
> { };
template<typename PARENT, uint32_t PARAM> // 定式
class ifs_t : public streaming::if_impl_t< // 定义 AudioStream Interface(流接口)类
ifs_t, PARENT, PARAM, // 定式
, // 指定 String ID
typename tuple_t<PARENT>::input_t, // 指定连接到的 terminal
ep_t // 指定本接口包含的数据端点(暂不支持同步端点)
> {
public:
template<uint8_t N> using ep_t = typename core::elem_t<N, typename ifs_t::tuple_t>::type;
struct __attribute__((packed)) descriptor_t // 定义流接口描述符
: core::descriptor_t<core::descriptor::std_if_t<ifs_t, , , >>, // Alternative 0
core::descriptor_t<core::descriptor::std_if_t<ifs_t, , , >, // Alternative 1
streaming::pcm_descriptor_t< // 定义 PCM 描述符
ifs_t, // 定式
, // bDelay
, // bNrChannels
, // bSubframeSize
, // bBitResolution
true, // 指定固定采样
SAMPLE_RATE // 采样率
>,
ep_t<> // 指定 Alternative 1 所包含的 Endpoint
> { };
__INLINE bool set_interface(uint_fast8_t alternative) // Set Interface 处理
{
if (alternative) {
first = true;
); // 读 packet
buffer.pos = ;
}
return true;
}
__INLINE void read_complete(uint_fast16_t length) // 读 packet 完成
{
uint_fast8_t pos = buffer.wrpos;
pos ^= / sizeof(uint32_t);
buffer.wrpos = pos;
); // 读 packet
if (first) {
first = ;
i2s_start(); // 启动 I2S
}
}
private:
uint8_t first;
};
template<typename PARENT, uint32_t PARAM> // 定式
class fn_t : public adc::fn_impl_t< // 定义 Function
fn_t, PARENT, PARAM, // 定式
, // 指定 String ID
0x100, // 指定 bcdADC,Release Number
ifc_t, // 已定义的 AudioControl Interface
tuple_t<fn_t<PARENT, PARAM>>, // 已定义的 Entiies 集合
ifs_t // 已定义的 AudioStream Interface,可连续加入多个
> { };
class usbd_t : public core::usbd_impl_t< // 定义 USB 类
usbd_t, // 定式
0x110, // bcdUSB
, // bDeviceClass
, // bDeviceSubClass
, // bDeviceProtocol
0x0416, // idVendor
0x5011, // idProduct
0x100, // bcdDevice
, // iManufacture
, // iProduct
, // iSerialNumber
true, // bmAttributes, Bus Powered
false, // bmAttributes, Self Powered
false, // bmAttributes, Remote Wakeup
50_mA, // bMaxPower
, // iConfiguration
fn_t> { // 已定义的 Function,可连续加入多个 Function 和 Interface
public:
__INLINE usbd_t() { }
#if 1
__INLINE bool data_out(uint_fast8_t type, uint_fast8_t request, uint_fast16_t value, uint_fast16_t index, uint_fast16_t length)
{
out();
return true;
}
__INLINE bool data_in(uint_fast8_t type, uint_fast8_t request, uint_fast16_t value, uint_fast16_t index, uint_fast16_t length)
{
in();
return true;
}
#endif
__INLINE const uint8_t* get_string_descriptor(uint_fast8_t index, uint_fast16_t lang_id) // GetDescriptor(String) 处理
{
static const string_langid_t<langid_t::English_UnitedStates> desc0;
static const string_t<u'j', u'.', u'y', u'.', u'l', u'e', u'e', u'@', u'y', u'e', u'a', u'h', u'.', u'n', u'e', u't'> desc1;
static const string_t<u'U', u'S', u'B', u' ', u'A', u'u', u'd', u'i', u'o'> desc2;
'> desc3;
static const uint8_t* const descriptor[] {
reinterpret_cast<const uint8_t*>(&desc0),
reinterpret_cast<const uint8_t*>(&desc1),
reinterpret_cast<const uint8_t*>(&desc2),
reinterpret_cast<const uint8_t*>(&desc3)
};
]) ? descriptor[index] : nullptr;
}
};
usbd_t usbd; // 定义 USB 类对象
void usbd_isr() // USBD_IRQn 中断向量 handler
{
usbd.isr(); // 调用 USB 类的中断处理
}
void i2s_isr() // I2S_IRQn 中断向量 handler
{
using namespace sfr::i2s;
if (I2S.STATUS().TXUDF) {
I2S.STATUS().TXUDF();
I2S.CON();
I2S.IE();
return;
}
if (buffer.pos == 0x400 || buffer.pos == 0x1014) {
I2S.IE().TXUDFIE();
return;
}
// 从 Audio Data 缓冲填充数据到 I2S 发送 FIFO
uint_fast8_t rdpos{ buffer.rdpos };
do {
I2S.TXFIFO = buffer.data[rdpos++];
)
rdpos = ;
} );
buffer.rdpos = rdpos;
// 调整 I2S 速率
uint_fast16_t pos{ buffer.pos };
if (pos == 0xc00 || pos == 0x1c10) { // 加快
I2S.CLKDIV().BCLK_DIV();
} || pos == 0x1010) { // 减慢
I2S.CLKDIV().BCLK_DIV();
} else
I2S.CLKDIV().BCLK_DIV(); // 恢复正常
}
void i2s_start() // I2S 启动
{
using namespace sfr::i2s;
do {
I2S.TXFIFO = ;
} );
I2S.CON()
.I2SEN()
.TXEN()
.WORDWIDTH()
.FORMAT()
.TXTH();
I2S.IE().TXTHIE();
}
int main()
{
using namespace sfr::i2s;
I2S.CLKDIV().BCLK_DIV(); // 设置 I2S 默认速率
*reinterpret_cast< << I2S_IRQn; // NVIC:允许 I2S 中断
usbd.open(true); // 初始化 usb 对象
while (true);
}
程序中所有的类和模板的层次结构:
usbd_t USB device 的封装
|
+--fn_t<...> Interface 的容器
|
+--ifc_t<...> Audio Control Interface 的模板封装
|
+--tuple_t<...> Terminal 和 Unit 的容器
| |
| +--it_t<...> Input Terminal 的模板封装
| |
| +--ot_t<...> Output Terminal 的模板封装
|
+--ifs_t<...> Audio Stream Interface 的模板封装
|
+--ep_t<...> Audio Data Endpoint 的模板封装
一个最简的 USB Audio 示例的更多相关文章
- RELabel : 一个极简的正则表达式匹配和展示框架
html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,bi ...
- USB Audio设计与实现
1 前言 本文将基于STM32F4 Discovery板,从零开始设计并实现一个USB Audio的例子. 2 设计构思 所谓的USB AUDIO就是制作一个盒子,这个盒子可以通过USB连接到PC,P ...
- USB AUDIO Device CLASS Requests
写在前面 本文翻译自 USB Device Class Definition for Audio Devices 1998年版.主要是鄙人个人使用,所以只挑对我有用的翻译.有些我认为不是很重要的可能就 ...
- 借助腾讯云的云函数实现一个极简的API网关
借助腾讯云的云函数实现一个极简的API网关 Intro 微信小程序的域名需要备案,但是没有大陆的服务器,而且觉得备案有些繁琐,起初做的小程序都有点想要放弃了,后来了解到腾讯云的云函数,于是利用腾讯云的 ...
- Spring Boot(5)一个极简且完整的后台框架
https://blog.csdn.net/daleiwang/article/details/75007588 Spring Boot(5)一个极简且完整的后台框架 2017年07月12日 11:3 ...
- 如何在Android平台上使用USB Audio设备
http://blog.csdn.net/kevinx_xu/article/details/12951131 需求:USB Headset插上去后,声音要从本地CODEC切换到USB Headset ...
- 一个最简的Thinkphp3.2 操作Mongodb的例子
看到Thinkphp网站上没有调用Mongodb的例子,就写一个一个最简的Thinkphp操作Mongodb的例子.欢迎讨论[前提]Thinkphp对Mongdb的支持依赖于PHP对Mongodb的支 ...
- JavaScript一个页面中有多个audio标签,其中一个播放结束后自动播放下一个,audio连续播放
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- 用vue写一个仿简书的轮播图
原文地址:用vue写一个仿简书的轮播图 先展示最终效果: Vue的理念是以数据驱动视图,所以拒绝通过改变元素的margin-top来实现滚动效果.写好css样式,只需改变每张图片的class即可实现轮 ...
随机推荐
- js高程笔记--创建对象
1.工厂模式 ex: function createPerson( name, age, job) { var o = new Object() ; o.name = name; o.job = jo ...
- c#操作word文档之简历导出
前言 1.写这个功能之前,我得说说微软的这个类库,用着真苦逼!是他让我有程序猿,攻城尸的感觉了.首先这个类库,从没接触过,方法与属性都不懂,还没有提示.神啊,我做这功能真是一步一卡,很潇洒啊. 2.这 ...
- Android开源资料大集合_架构&UI
1. Anroid开源框架架 https://github.com/kymjs/KJFrameForAndroidhttp://www.oschina.net/p/thinkandroid http: ...
- jQuery获取Select选择的Text(非表单元素)和 Value(表单元素)(转)
jQuery获取Select选择的Text和Value: 语法解释: . $("#select_id").change(function(){//code...}); //为Sel ...
- jquery $.post 返回json数据
$(function () { $("#prompt").hide(); $("#searchIpt").keyup(function () { var key ...
- T4模板之基础篇
一.回顾 上一篇文章 ——T4模板之菜菜鸟篇,我们囫囵吞枣的创建了与“T4模板”有关的文件.在创建各个文件的这一个过程中,我们对于T4模板有了那么丁点的认识.现在就带着之前的那些问题,正式的迈入对“T ...
- 关于win7系统的Oracle安装时的[INS-30131]问题的解决方案
我是今天晚上安装的Oracle,结果在第二步遇到了这个问题,前后折腾了两个小时,百度了很多解决方案,终于解决了这个问题; 由于我的电脑系统还是win7的系统,其他的我没试过,不过也差不多都这么解决; ...
- codeforces 547B. Mike and Feet 单调栈
题目链接 用单调栈计算出一个数字, 左边第一个比他小的数字的位置, 右边比第一个他小的数字的位置, 然后len = r[i] - l[i] +1. ans[len] = max(ans[len], a ...
- windows理论基础(一)
windows体系结构 一. 用户模式和内核模式 (user mode &kernel mode) Intel x86 处理器的体系结构定义了四种特权级,或特为四个环.来保护系统代码不会被低 ...
- 用WebBrowser实现HTML界面的应用和交互 good
这一篇将继续讨论在使用delphi进行普通应用程序开发的时候,WebBrowser的具体应用,主要是针对使用其进行HTML界面开发的,这也是一篇我在网上找到的资料,大家如要转载,请尊重原作者的知识产权 ...