一个最简的 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即可实现轮 ...
随机推荐
- NOPI导出Excel 自定义列名
NOPI 做Excel 导出确实很方便 ,但是一直在用没好好研究. 在网上没找到自定义Columns的方法 ,于是乎自己就在原来的方法上简单地改改. 想用的童鞋们可以直接拿去用! /// 数据大于65 ...
- 无法连接远程mysql问题
按照别人的博客操作之后,重启了服务: 前几天,windows上的mysql无法远程连接,在网上搜了一下,都是一个说法,神马改表.加权限.刷新等等,尝试之后都没有用,后来同事告诉了我一个方法,就解决了那 ...
- linux grep 指定字符串的正则表达式
cat all_uuid_log | grep "[a-z0-9]\{32\}"
- 基于jQuery带标题的图片3D切换焦点图
今天给大家分享一款基于jQuery带标题的图片3D切换焦点图.这款焦点图适用浏览器:IE8.360.FireFox.Chrome.Safari.Opera.傲游.搜狗.世界之窗. 实现的代码. htm ...
- NetWare
本地网络连接属性中就有Netware客户端服务项 概括的说,Netware是NOVELL公司推出的网络操作系统,Netware最重要的特征是基于基本模块设计思想的开放式系统结构. Netware是一个 ...
- codeforces 617E. XOR and Favorite Number 莫队
题目链接 给n个数, m个询问, 每次询问问你[l, r]区间内有多少对(i, j), 使得a[i]^a[i+1]^......^a[j]结果为k. 维护一个前缀异或值就可以了. 要注意的是 区间[l ...
- 简单介绍一下ODI的几个基本概念
简单介绍一下ODI的几个基本概念 ODI的几个基本概念是本文我们主要要介绍的内容,接下来我们就开始介绍这一过程,一起来看看吧! 什么是资料库 ODI资料库可安装在任何支持ANSIISO89的数据库 ...
- 转: html5 history api详解~很好的文章
从Ajax翻页的问题说起 请想象你正在看一个视频下面的评论,在翻到十几页的时候,你发现一个写得稍长,但非常有趣的评论.正当你想要停下滚轮细看的时候,手残按到了F5.然后,页面刷新了,评论又回到了第一页 ...
- Y5V贴片电容(MLCC)容量范围速查表
Y5V贴片电容简述 Y5V贴片电容属于EIA规定的Class 2类材料的电容.它的电容量受温度.电压.时间变化影响大. Y5V贴片电容特性 具有较差的电容量稳定性,在-25℃-85℃工作温度范围内,温 ...
- 把WinXP装进内存 性能飚升秒杀固态硬盘
现在用户新配置的电脑,内存很少有小于2GB的,配置4GB内存的朋友也有不少.容量如此大的内存,我们在使用电脑的日常操作中绝对用不完.而目前制约系统性能最大的瓶颈就是硬盘的传输速度,所以,这里教你怎么把 ...