打印
[Cortex-M0技术交流]

一个最简的 USB HID(Keyboard) 示例

[复制链接]
9470|33
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
john_lee|  楼主 | 2013-5-19 03:15 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 john_lee 于 2013-5-19 11:54 编辑

使用 USB 协议栈框架做的最简洁的 HID 示例,仍然是基于新唐 M0。
ukey.zip (2 KB) 附件中是生成的 hex 文件,可以运行在菜农助学板上,烧写时需要配置 Config0 中的时钟选择为 external 12Mhz。两个按键,一个启动“浏览器”,一个启动“我的电脑”。
代码空间开销:
   text    data     bss     dec     hex filename
   1452     196      40    1688     698 .\default\ukey.elf
flash 占用 1.6K。上程序:
#include <usb/hal/numicro>                                  // USB HAL(Hardware Abstract Layer),新唐 Numicro
#include <usb/hid>                                          // USB HID Class
#include <numicro/nuc/sfr/gpio>                             // GPIO Register
using namespace usb;                                        // 使用 usb 名空间
using namespace hid;                                        // 使用 hid 名空间

template<typename PARENT, uint32_t PARAM>                   // 定式
class ep_t : public in::ep_impl_t<                          // 定义 Endpoint 类
            ep_t, PARENT, PARAM,                            // 定式
            10                                              // 指定 bInterval
        > { };

template<typename PARENT, uint32_t PARAM>                   // 定式
class if_t : public if_impl_t<                              // 定义 Interface 类
            if_t, PARENT, PARAM,                            // 定式
            0,                                              // 指定 bInterfaceSubClass
            1,                                              // 指定 bInterfaceProtocol
            0,                                              // 指定 String ID
            0x110,                                          // 指定 bcdHID
            0,                                              // 指定 bCountryCode
            optional_t<                                     // 定义 hid optional descriptor
                report_t<                                   // 定义 hid report descriptor
                    usage_page_t<CONSUMER_DEVICE>,          // 0x05, 0x0C,      //  Usage Page (Consumer Devices),
                    usage_t<1>,                             // 0x09, 0x06,      //  Usage (Consumer Control),
                    collection_t<APPLICATION,               // 0xA1, 0x01,      //  Collection (Application),
                        logical_extremum_t<0, 1>,           // 0x15, 0x00,      //      Logical Minimum (0),
                                                            // 0x25, 0x65,      //      Logical Maximum (101),
                        report_size_t<1>,                   // 0x75, 0x08,      //      Report Size (1),
                        report_count_t<2>,                  // 0x95, 0x01,      //      Report Count (2),
                        usage_t<0x23, 2>,                   // 0x0A, 0x23, 0x02,//      USAGE (0x223),      // WWW Browser
                        usage_t<0x94, 1>,                   // 0x0A, 0x94, 0x01,//      USAGE (0x194),      // My Computer
                        input_t<DATA, VARIABLE, ABSOLUTE>,  // 0x81, 0x02,      //      Input (Data, Variable, Absolute),
                        report_count_t<6>,                  // 0x95, 0x07,      //      Report Count (6),
                        input_t<CONSTANT>                   // 0x81, 0x01,      //      Input (Constant)
                    >                                       // 0xC0         //  End Collection
                >
            >,
            ep_t                                            // 指定本 Interface 包含的 Endpoint
        > {
public:
    __INLINE void config()          // Interface 初始化,当 Set Configuration 时被调用
    {
        if_t::if_impl_t::config();  // 使用默认的 config 处理
        using namespace sfr::gpio;
        *reinterpret_cast<volatile uint32_t*>(0xe000e100) = (1 << EINT0_IRQn) | (1 << EINT1_IRQn);  // NVIC 使能 EINT0、EINT1 中断
        GPIOB.IEN()
            .IF_EN14(1)             // 使能 EINT0 下降沿中断
            .IR_EN14(1)             // 使能 EINT0 上升沿中断
            .IF_EN15(1)             // 使能 EINT1 下降沿中断
            .IR_EN15(1);            // 使能 EINT1 上升沿中断
    }
};

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
        10_mA,                  // bMaxPower
        0,                      // iConfiguration
        if_t> {                 // 指定 usb 包含的 Interface,可连续加入多个 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'渭', u'K', u'e', u'y'> 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 eint0_isr()            // EINT0_IRQn 中断向量 handler
{
    static uint8_t code;
    using namespace sfr::gpio;
    auto pin = GPIOB.PIN();
    auto isrc = GPIOB.ISRC();
    GPIOB.ISRC = isrc;
    if (isrc.ISRC14)
        code = pin.PIN14 == 0 ? 1 : 0;      // WWW Browser
    else
        code = pin.PIN15 == 0 ? 2 : 0;      // My Computer
    get_if<0>(usbd).write(&code, sizeof(code));     // 使用 usbd 的 interface 0 发送数据
}

void eint1_isr()            // EINT1_IRQn 中断向量 handler
{
    eint0_isr();
}

int main()
{
    usbd.open(true);        // 初始化 usb 对象
    while (true);
}
评分
参与人数 1威望 +8 收起 理由
xyz549040622 + 8

相关帖子

沙发
john_lee|  楼主 | 2013-5-19 03:16 | 只看该作者
抢沙发,看我快还是9G快!!!

使用特权

评论回复
板凳
缥缈九哥| | 2013-5-19 03:23 | 只看该作者
我都 没有上来。所以 给你抢着了。

使用特权

评论回复
地板
缥缈九哥| | 2013-5-19 11:56 | 只看该作者
我再顶。

使用特权

评论回复
5
john_lee|  楼主 | 2013-5-19 12:05 | 只看该作者
这个示例中的用户代码(函数部分)不超过 30 行,真的非常少。

使用特权

评论回复
6
乡村男孩| | 2013-5-19 12:13 | 只看该作者
必须顶起呀....

使用特权

评论回复
7
dong_abc| | 2013-5-21 00:09 | 只看该作者
get_if<0>(usbd).write(&code, sizeof(code));     // 使用 usbd 的 interface 0 发送数据

HID键盘不是向端点发数据吗? 怎么用interface发数据? get_if<0>神马意思啊?

使用特权

评论回复
8
john_lee|  楼主 | 2013-5-21 00:53 | 只看该作者
本帖最后由 john_lee 于 2013-5-21 00:55 编辑
dong_abc 发表于 2013-5-21 00:09
get_if(usbd).write(&code, sizeof(code));     // 使用 usbd 的 interface 0 发送数据

HID键盘不是向端 ...


不错,这些回复里,就属你的贴子有技术含量,他们都是打酱油的。

USB 收发数据,物理上是用的 Endpoint 没错,但逻辑功能却都集中在 Interface 或 Function 里,所以,usbd 对象外部的程序逻辑,要访问 usbd 对象,访问的层次应该是 Interface,而不是 Endpoint,虽然最终数据是从 Endpoint 走的。这个协议栈的层次关系是非常明确的,就像其他很多有层次的协议栈一样,比如 TCP/IP。

get_if是个模板函数,尖括号里的 0 是模板参数,圆括号里的 usbd 是函数实参,整个 get_if<0>(usbd) 的意思,就是取得 usbd 对象中的 第 0 号 Interface 的引用。不知我说明白没?

使用特权

评论回复
9
缥缈九哥| | 2013-5-21 13:07 | 只看该作者
我看你说的明白。可是实质是不明白。

使用特权

评论回复
10
dong_abc| | 2013-5-21 19:02 | 只看该作者
缥缈九哥 发表于 2013-5-21 13:07
我看你说的明白。可是实质是不明白。

是的,只看见表象了,实质的东西根本不清楚。

使用特权

评论回复
11
hotpower| | 2013-5-22 12:11 | 只看该作者

使用特权

评论回复
12
abin0415| | 2013-5-22 21:30 | 只看该作者
顶起

使用特权

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

顶!强烈支持

使用特权

评论回复
14
zifan| | 2013-5-29 11:15 | 只看该作者
刚好要做这方面。记录,后续细看。

使用特权

评论回复
15
chaled| | 2013-6-3 10:55 | 只看该作者
收藏,准备使用这个芯片。

使用特权

评论回复
16
liumingxing| | 2013-6-8 10:01 | 只看该作者
最近才知道LOOK-RTOS,就是不会用。要是有一本手册,介绍LOOK-RTOS如何使用,或者有一本书能系统地介绍LOOK,就太好了。

使用特权

评论回复
17
江枫渔火| | 2013-6-8 10:06 | 只看该作者
马克~顶,好贴~俺也是打酱油的

使用特权

评论回复
18
Ryanhsiung| | 2013-6-8 17:58 | 只看该作者
没有项目么。。。

使用特权

评论回复
19
jstele| | 2013-6-14 07:33 | 只看该作者
usb傳輸都是以endpoint去做in或out的工作,當你不是主機而是設備時;usb   hid在transection過程中會在主機與設備間建立一個通道就是所謂的pipe。所以此時usb主機和設備都會知道對方的端點號(usb2.0有1~15組in和out的端點號所以共有30個,端點號0是給usb在init時與主機聯絡的起始端點)所以我想版主所說的interface應該是設備在與主機連線上後所建立完成pipe後所確定的主機和設備的in和out端點號才是。

使用特权

评论回复
20
hesun| | 2013-6-28 13:45 | 只看该作者
向高手学习。

使用特权

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

本版积分规则

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

33

主题

1466

帖子

21

粉丝