打印
[Cortex-M0技术交流]

一个最简的 USB Audio 示例

[复制链接]
11700|49
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
john_lee|  楼主 | 2013-5-18 02:24 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
IO, USB, ST, TE, TI
本帖最后由 john_lee 于 2013-5-18 14:34 编辑

经过了两三个月的痛苦,USB 协议栈的 Audio Device Class 框架已具雏形了,用了两三天时间,使用这个框架实战了一个基于新唐 M0 的最简单的 USB Audio 程序,可以作为 USB 声卡。
adc.zip (2.2 KB) 附件中是生成的 hex 文件,可以运行在菜农助学板上,烧写时需要配置 Config0 中的时钟选择为 external 12Mhz。
代码空间开销:
   text    data     bss     dec     hex filename
   1580     244     168    1992     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,                               // 定式
            0                                               // 指定 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,
            0
        > {
    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,其中可以加入任意声道
                    0,                                      // 指定 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 规范
            0                                               // 指定 wLockDelay, 参见 USB Audio 规范
        > { };

#define SAMPLE_RATE     16000

struct {                                                    // 定义 Audio Data 缓冲区
    union {
        struct {
            uint8_t wrpos;                                  // 写缓冲指针
            uint8_t rdpos;                                  // 读缓冲指针
        };
        uint16_t pos;
    };
    uint32_t data[64 * 2 / 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,                           // 定式
            0                                               // 指定 String ID
        > { };

template<typename PARENT, uint32_t PARAM>                   // 定式
class ifs_t : public streaming::if_impl_t<                  // 定义 AudioStream Interface(流接口)类
            ifs_t, PARENT, PARAM,                           // 定式
            0,                                              // 指定 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, 0, 0, 0>>,     // Alternative 0
                core::descriptor_t<core::descriptor::std_if_t<ifs_t, 1, 0, 0>,      // Alternative 1
                    streaming::pcm_descriptor_t<                                    // 定义 PCM 描述符
                        ifs_t,              // 定式
                        1,                  // bDelay
                        2,                  // bNrChannels
                        2,                  // bSubframeSize
                        16,                 // bBitResolution
                        true,               // 指定固定采样
                        SAMPLE_RATE         // 采样率
                    >,
                    ep_t<0>                 // 指定 Alternative 1 所包含的 Endpoint
                > { };

    __INLINE bool set_interface(uint_fast8_t alternative)   // Set Interface 处理
    {
        if (alternative) {
            first = true;
            this->read(buffer.data, 64);                    // 读 packet
            buffer.pos = 0;
        }
        return true;
    }
    __INLINE void read_complete(uint_fast16_t length)       // 读 packet 完成
    {
        uint_fast8_t pos = buffer.wrpos;
        pos ^= 64 / sizeof(uint32_t);
        buffer.wrpos = pos;
        this->read(&buffer.data[pos], 64);                  // 读 packet
        if (first) {
            first = 0;
            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,                // 定式
            0,                                  // 指定 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
        0,                      // bDeviceClass
        0,                      // bDeviceSubClass
        0,                      // bDeviceProtocol
        0x0416,                 // idVendor
        0x5011,                 // idProduct
        0x100,                  // bcdDevice
        1,                      // iManufacture
        2,                      // iProduct
        3,                      // iSerialNumber
        true,                   // bmAttributes, Bus Powered
        false,                  // bmAttributes, Self Powered
        false,                  // bmAttributes, Remote Wakeup
        50_mA,                  // bMaxPower
        0,                      // 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;
        static const string_t<u'0', u'0', u'0', u'0'> 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)
        };
        return index < sizeof(descriptor) / sizeof(descriptor[0]) ? 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(0).TXUDF(1);
        I2S.CON(0);
        I2S.IE(0);
        return;
    }
    if (buffer.pos == 0x400 || buffer.pos == 0x1014) {
        I2S.IE(0).TXUDFIE(1);
        return;
    }
    // 从 Audio Data 缓冲填充数据到 I2S 发送 FIFO
    uint_fast8_t rdpos{ buffer.rdpos };
    do {
        I2S.TXFIFO = buffer.data[rdpos++];
        if (rdpos == sizeof(buffer.data) / 4)
            rdpos = 0;
    } while (I2S.STATUS().TXFULL == 0);
    buffer.rdpos = rdpos;
    // 调整 I2S 速率
    uint_fast16_t pos{ buffer.pos };
    if (pos == 0xc00 || pos == 0x1c10) {        // 加快
        I2S.CLKDIV(0).BCLK_DIV(45);
    } else if (pos == 0 || pos == 0x1010) {     // 减慢
        I2S.CLKDIV(0).BCLK_DIV(47);
    } else
        I2S.CLKDIV(0).BCLK_DIV(46);             // 恢复正常
}

void i2s_start()                                // I2S 启动
{
    using namespace sfr::i2s;
    do {
        I2S.TXFIFO = 0;
    } while (I2S.STATUS().TXFULL == 0);
    I2S.CON(0)
        .I2SEN(1)
        .TXEN(1)
        .WORDWIDTH(1)
        .FORMAT(1)
        .TXTH(4);
    I2S.IE(0).TXTHIE(1);
}

int main()
{
    using namespace sfr::i2s;
    I2S.CLKDIV(0).BCLK_DIV(46);                 // 设置 I2S 默认速率
    *reinterpret_cast<volatile uint32_t*>(0xe000e100) = 1 << 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 的模板封装
评分
参与人数 1威望 +8 收起 理由
xyz549040622 + 8

相关帖子

沙发
缥缈九哥| | 2013-5-18 02:26 | 只看该作者
顶起。。。

使用特权

评论回复
板凳
john_lee|  楼主 | 2013-5-18 02:28 | 只看该作者
占沙发,:D

使用特权

评论回复
地板
缥缈九哥| | 2013-5-18 02:36 | 只看该作者
john_lee <j.y.lee@yeah.net>  2:32:12
擦,动作也太快了吧!
居然把沙发给我抢走了
缥缈九哥(14131338)  2:34:48
哈哈

使用特权

评论回复
5
xukaiming| | 2013-5-18 09:26 | 只看该作者
地板

使用特权

评论回复
6
乡村男孩| | 2013-5-18 09:28 | 只看该作者
强人 看得我

使用特权

评论回复
7
lxyppc| | 2013-5-18 10:24 | 只看该作者
标记,这是个好东西

使用特权

评论回复
8
呆板书生| | 2013-5-18 10:40 | 只看该作者
标记一下

使用特权

评论回复
9
mcu5i51| | 2013-5-18 13:51 | 只看该作者
好东西要顶

使用特权

评论回复
10
nomefat| | 2013-5-18 14:58 | 只看该作者
路过

使用特权

评论回复
11
dirtwillfly| | 2013-5-18 21:35 | 只看该作者
顶李老师

使用特权

评论回复
12
缥缈九哥| | 2013-5-19 03:31 | 只看该作者
可是功力不够 。一看就有走火入魔的感觉 ,赶紧退出了。

使用特权

评论回复
13
dong_abc| | 2013-5-23 01:12 | 只看该作者
码功 太薄弱,看不懂。

使用特权

评论回复
14
john_lee|  楼主 | 2013-5-23 02:08 | 只看该作者
楼上在不在九哥的群 18748628 里?不在可以加进来聊聊。:D

使用特权

评论回复
15
john_lee|  楼主 | 2013-5-23 02:10 | 只看该作者
dong_abc 发表于 2013-5-23 01:12
码功 太薄弱,看不懂。

在不在九哥的群 18748628 里?不在可以加进来聊聊。

使用特权

评论回复
16
sunbingbing| | 2013-5-23 07:08 | 只看该作者
[em:2:

使用特权

评论回复
17
dong_abc| | 2013-5-23 12:27 | 只看该作者
john_lee 发表于 2013-5-23 02:10
在不在九哥的群 18748628 里?不在可以加进来聊聊。

我在菜农群的QQ号被腾讯收回去了, 下班回来再加. 近两年没折腾单片机了.

使用特权

评论回复
18
jstele| | 2013-5-24 07:58 | 只看该作者

顶!强烈支持

使用特权

评论回复
19
dong_abc| | 2013-5-24 22:01 | 只看该作者
缥缈九哥 发表于 2013-5-19 03:31
可是功力不够 。一看就有走火入魔的感觉 ,赶紧退出了。

9G,你这个群,我怎么加了3天都没加上啊,18748628 。

使用特权

评论回复
20
john_lee|  楼主 | 2013-5-24 22:21 | 只看该作者
dong_abc 发表于 2013-5-24 22:01
9G,你这个群,我怎么加了3天都没加上啊,18748628 。

群号没错啊,我问问9g。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:坚持使用 GCC 一百年不动摇!

33

主题

1466

帖子

21

粉丝