yang377156216 发表于 2022-7-29 10:06

【AT-START-WB415测评】基于 USB HID 的音乐媒体控制器

本帖最后由 yang377156216 于 2022-7-29 10:09 编辑

#申请原创#@21小跑堂
前言在申请 AT32WB415 蓝牙开发板时,填写的测评计划为:先拿到开发板熟悉基础外设以及通讯外设 USB/CAN的功能,再测评完蓝牙部分能申请到的 profile 功能例程,最后完成应用方案 —— 基于 USB(BLE) HID 的音乐媒体控制器 。收到板子后那就按照这个步骤进行下去吧,接下来我的测评内容整体可分为以下几点:
[*]雅特力蓝牙芯片官方资源体验
[*]着手打造一个音乐媒体控制器
[*]后续精加工

雅特力蓝牙芯片官方资源体验由于本人是第一次使用雅特力的芯片和开发板,之前只是在论坛里和技术群里有接触,所以万事都得从头开始学习和整理,好在雅特力官网都已经把相关的技术资料都开放出来了,极大方便了开发者,我将这颗芯片的资料下载链接附在此处:https://www.arterytek.com/cn/product/AT32WB415.jsp#Resource官方资源非常丰富,从数据手册、参考手册、例程、开发板硬件参考设计到应用笔记、勘误手册以及配套小工具,应有尽有、非常齐全,点赞!下载好资料后我将所有内容分类整理如下,这样看起来非常清爽了:接着,我将下载得到的官方资源逐一浏览了一遍,大体感受是:国产 MCU 厂商中,鲜有能把生态资源做得那么漂亮、完善的了,雅特力生态团队真是用心了,对得起自己的台湾血统(台湾联电集团一员)!就冲每一个小工具、Lib 组件都披上了 RH 外套(revision history)这么个小举动,我会认为他们的流程是非常规范的,也都有站在用户角度去做内容输出。雅特力有针对性地做了非常多的小工具,非常实用,比如说:时钟配置图形化工具可以直接生成 clock init c 语言代码,can 位时间计算工具可以直接生成不同采样点、不同波特率情况的 can 位时间初始化代码,AT Link 及ICP/ISP 生产烧录工具,不同通讯接口的 IAP 烧录工具,jlink 复位芯片工具以及一键添加 j-flash 设备的工具等等。除了芯片本身的突破,软件、配套工具的完善,雅特力还在不遗余力地建设 AT32 MCU 开发生态,前段时间在立创 EDA 平台自建了官方的器件封装库,目前共有 42 种器件型号封装可供用户使用,并持续更新。以下是我试用软件、工具等资源时特意留下的一些截图:此外,在哔站、RT-Thread 等主流技术交流平台和嵌入式技术框架中都能见到雅特力官方身影,足以见得,他们是真心想要建立更加完备的生态开发体系,在国产 MCU 厂商中脱颖而出,点赞+1!然后,将芯片的 KEIL pack 包安装好,开始试用 AT32WB415_Firmware_Library_V2.0.1 来熟悉各个基础外设功能,可以直接通过 at_start_wb415_Example_list.htm 这个静态网页看到官方样例包中涵盖了哪些外设功能 demo ,简直了,直给啊,其他厂家真心没见过呀,点赞+1:整个样例 SDK 包层次非常分明,版本描述、API 帮助文档、Driver BSP 和 Middlewares 、系统功能性demo 、模板示例工程等等,在 SDK 中都有体现,作为软件开发人员可以很快上手并且进行深度移植测评。紧接着就调试、烧录了一些基础外设功能的工程,比如:ADC/GPIO/FLASH/TMR 等等,整体没有遇到什么问题一路都很顺畅,也都熟悉了基础外设的相关寄存器,范例工程的代码写得非常规范,包括注释也都符合 Doxygen 语法,让人看起来就很舒服。不过,还是有发现一个小漏洞,在 .s 启动文件中未能正确开启 Keil 的 ConfigurationWizard 导致无法正常进行图形化显示配置内容,在文件开头落了下面这句代码:///<<< Use Configuration Wizard in Context Menu >>>这点需要官方加以修正,正常添加后,可以显示 stack 和 heap 的图形化配置了:由于此次测评芯片是带蓝牙的,所以把试用重心放到蓝牙功能上来,基础外设的评估在此不做过多探讨,下面聊聊我玩蓝牙的经历。根据官方现有的(鲜有的)一些技术文档和推广素材中的描述,应该可以知道此芯片同样为一颗合封芯片,将 ARM®Cortex®-M4 内核的 MCU 与另外一颗 ARM9 内核的 BLE 5.0 蓝牙芯片合封在了一起,2 者之间是通过 UART 接口进行通讯的, 2 者之间不会实时通讯只在有事件触发时才会有报文交互,做到了完全独立运行、互不干扰。另外,它们 2 者的调试烧录口共用一个 SWD 口,通过板载 DAP-Link 选择不同目标来进行切换,这看起来不会有问题。了解到以上基础信息后,接着我跟着 《AN0077_AT32_BLE_Application_Note_ZH_V2.0.2.pdf》所描述的步骤进行实操。首先我准备好了所有硬件和软件资源,包括手机 APP 我的是 nRF Connect ,然后我打开了 mcu 端的工程和 ble 端的工程,分别编译并且直接在 Keil 中下载好 mcu 端的程序,由于文档中没有介绍是否也能直接在 Keil 中烧录 ble 的程序,所以使用 ICP tool 对蓝牙端的程序进行烧录。开启 ICP tool 后正常连接好硬件板卡,并且按照文中说明设置好 ble 的代码起始地址和长度,此时可以对 ble 的程序区进行读写操作了,先试了一下读取发现其实板子寄来时已经烧录好了出厂程序:由于自己需要熟悉整个流程,所以接着进行烧写测试,将刚刚编译好的烧录 bin 文件导入到工具中,配置好相关的 烧写选项后再烧录:烧录成功后,打开 app 进行搜索连接,发现没有找到板子发出的相应广播,此时我将烧录工具关闭了并且按下 mcu 和 ble 的复位键,重新搜索发现仍然未找到 AT32 的蓝牙。难道我拿到的芯片有问题?这不免让我产生了怀疑。但仍未放弃排查原因,打开了 2 个工程的源程序进行阅览,根据以往使用蓝牙 SOC 的经验找到了问题点:由于 2 颗内核启动存在时序差异,而应用上需要 mcu 先启动初始化完与 ble 通讯的串口并且开启 AT 命令等待后再启动 ble 内核,它正常上电后应该会发送一个握手命令之类的报文给 mcu ,告知蓝牙上线了,后面再进行正常的交互。于是,我先复位 mcu 然后再复位了 ble ,经过此番操作后,我的 APP 已经可以搜到并且连接上 ATK-BLE-Device 了。查看连接状态为 connected,在 UUID 为 0xC101 的服务栏中使用两个可用特性,分别是 read values 和 write values ,正常初始情况下读出开发板 LED2 的状态值为 0x00 ,然后写入 0x01 后可以将 LED2 点亮:接着我就想着用电脑蓝牙连接试试,发现一直转转圈根本无法连上,也不知道是哪里的问题:整个蓝牙功能的体验流程到此已经成功完成了,第一次使用也遇到了一些问题,不过好在都已经理解并且解决了。但是,就目前官方提供的资料和例程来说,我认为还是不足的,对于非专业的 BLE 协议栈和资深蓝牙 profile 应用开发者来说,要想根据文档中关于新增自定义服务特征值的描述就能轻松实现自己应用的迁移,我认为这是非常困难的一件事儿,在这且不说这颗蓝牙 GATT 层代码的晦涩难懂。由于官方给的资源有限,我也不想过多投入钻研这颗蓝牙部分了,期待后续官方持续更新再来深度评测吧。就我所知,国产带蓝牙的 MCU 中,沁恒在例程上做得还是不错的,将自己实现的各种 profile 全部开放给了用户,拿去就可以应用,这点非常容易吸引客户:
着手打造一个音乐媒体控制器由于对 BLE HID 无能为力,所以只能实现 USB HID 接口形式的音乐控制器了,好在官方例程中 USB Device 相关的参考还是挺丰富的。本应用的实现,需要将开发板通过 USB 接口连接到 PC 电脑上,然后枚举成一个 HID 类设备,再借助一个旋转编码器模块和板载的 USER 按键来控制电脑音乐播放器的六种按键状态,分别是:顺时针旋转(音量+)、逆时针旋转(音量-)、快速按下中间按键(暂停/播放)、按住中间按键并且逆时针旋转(上一首)、按住中间按键并且顺时针旋转(下一首),快速按下开发板 USER 按键(MUTE 切换)。与此同时,板载的 LED 可作为 USB 连接状态的指示以及旋转编码器转动状态切换的指示,有条件的话可以用一个线性马达作为切换反馈,使得旋转时有更舒服的体验。下面来细说实现步骤。由于同鼠标一样为 HID 设备,所以我先体验好官方的 mouse 例程,确认使用起来没有问题后再直接在此工程基础上更改 USB 设备描述,需要修改的内容涉及到的文件有mouse_desc.c / mouse_desc.h / mouse_class.c / mouse_class.h 。关于雅特力 USB 协议栈的实现和 USB HID 设备的实现原理和内容在此不做深入研究,只贴出其中关键代码如下:​
#define BUTTON_RELEASE                   0
#define VOLUME_UP                        0x01
#define VOLUME_DOWN                      0x02
#define MUTE_OR_NOT                      0x04
#define PLAY_PAUSE                     0x08
#define NEXT_SONG                        0x10
#define PREVIOUS_SONG                  0x20

/**
* @briefusb device class report function
* @paramudev: to the structure of usbd_core_type
* @paramop: operation
* @retval none
*/
void usb_hid_mouse_send(void *udev, uint8_t op)
{
    usbd_core_type *pudev = (usbd_core_type *)udev;
    mouse_type *pmouse = (mouse_type *)pudev->class_handler->pdata;
    int8_t report = 0;
    switch (op)
    {
    case VOLUME_UP:
      report = 0x01;
      break;

    case VOLUME_DOWN:
      report = 0x2;
      break;
   
    case MUTE_OR_NOT:
      report = 0x4;
      break;
   
    case PLAY_PAUSE:
      report = 0x8;
      break;

    case NEXT_SONG:
      report = 0x10;
      break;

    case PREVIOUS_SONG:
      report = 0x20;
      break;

    default:
      break;
    }
    pmouse->mouse_buffer = report;
//    pmouse->mouse_buffer = 0;
//    pmouse->mouse_buffer = posy;

    usb_mouse_class_send_report(udev, pmouse->mouse_buffer, 1);
}​
/**
* @brief usb bcd number define
*/
#define MOUSE_BCD_NUM                      0x0111

/**
* @brief usb vendor id and product id define
*/
#define USBD_MOUSE_VENDOR_ID             0x2E3C
#define USBD_MOUSE_PRODUCT_ID            0x5710

/**
* @brief usb descriptor size define
*/
#define USBD_MOUSE_CONFIG_DESC_SIZE            34
#define USBD_MOUSE_SIZ_REPORT_DESC             42
#define USBD_MOUSE_SIZ_STRING_LANGID         4
#define USBD_MOUSE_SIZ_STRING_SERIAL         0x1A

/**
* @brief usb string define(vendor, product configuration, interface)
*/
#define USBD_MOUSE_DESC_MANUFACTURER_STRING    "Artery"
#define USBD_MOUSE_DESC_PRODUCT_STRING         "Human interface"
#define USBD_MOUSE_DESC_CONFIGURATION_STRING   "HID Config"
#define USBD_MOUSE_DESC_INTERFACE_STRING       "HID Interface"

/**
* @brief usb hid endpoint interval define
*/
#define MOUSE_BINTERVAL_TIME                0x0A


/**
* @brief usb device standard descriptor
*/
#if defined ( __ICCARM__ ) /* iar compiler */
    #pragma data_alignment=4
#endif
ALIGNED_HEAD static uint8_t g_usbd_descriptor ALIGNED_TAIL =
{
    USB_DEVICE_DESC_LEN,                   /* bLength */
    USB_DESCIPTOR_TYPE_DEVICE,             /* bDescriptorType */
    0x00,                                  /* bcdUSB */
    0x02,
    0x00,                                  /* bDeviceClass */
    0x00,                                  /* bDeviceSubClass */
    0x00,                                  /* bDeviceProtocol */
    USB_MAX_EP0_SIZE,                      /* bMaxPacketSize */
    LBYTE(USBD_MOUSE_VENDOR_ID),         /* idVendor */
    HBYTE(USBD_MOUSE_VENDOR_ID),         /* idVendor */
    LBYTE(USBD_MOUSE_PRODUCT_ID),          /* idProduct */
    HBYTE(USBD_MOUSE_PRODUCT_ID),          /* idProduct */
    0x00,                                  /* bcdDevice rel. 2.00 */
    0x02,
    USB_MFC_STRING,                        /* Index of manufacturer string */
    USB_PRODUCT_STRING,                  /* Index of product string */
    USB_SERIAL_STRING,                     /* Index of serial number string */
    1                                    /* bNumConfigurations */
};

/**
* @brief usb device qualifier standard descriptor
*/
#if defined ( __ICCARM__ ) /* iar compiler */
    #pragma data_alignment=4
#endif
ALIGNED_HEAD static uint8_t g_usbd_qualifier_descriptor ALIGNED_TAIL =
{
    USB_DEVICE_QUALIFIER_DESC_LEN,         /* bLength */
    USB_DESCIPTOR_TYPE_DEVICE_QUALIFIER,   /* bDescriptorType */
    0x00,                                  /* bcdUSB */
    0x02,
    0x00,                                  /* bDeviceClass */
    0x00,                                  /* bDeviceSubClass */
    0x00,                                  /* bDeviceProtocol */
    0x40,
    0x01,
    0x00,
};

/**
* @brief usb configuration standard descriptor
*/
#if defined ( __ICCARM__ ) /* iar compiler */
    #pragma data_alignment=4
#endif
ALIGNED_HEAD static uint8_t g_usbd_configuration ALIGNED_TAIL =
{
    USB_DEVICE_CFG_DESC_LEN,               /* bLength: configuration descriptor size */
    USB_DESCIPTOR_TYPE_CONFIGURATION,      /* bDescriptorType: configuration */
    LBYTE(USBD_MOUSE_CONFIG_DESC_SIZE),    /* wTotalLength: bytes returned */
    HBYTE(USBD_MOUSE_CONFIG_DESC_SIZE),    /* wTotalLength: bytes returned */
    0x01,                                  /* bNumInterfaces: 1 interface */
    0x01,                                  /* bConfigurationValue: configuration value */
    0x00,                                  /* iConfiguration: index of string descriptor describing
                                          the configuration */
    0xC0,                                  /* bmAttributes: Bus powered *//*Bus powered: 7th bit, Self Powered: 6th bit, Remote wakeup: 5th bit, reserved: 4..0 bits *///描述设备特性
    0x32,                                  /* MaxPower 100 mA: this current is used for detecting vbus */

    USB_DEVICE_IF_DESC_LEN,                /* bLength: interface descriptor size */
    USB_DESCIPTOR_TYPE_INTERFACE,          /* bDescriptorType: interface descriptor type */
    0x00,                                  /* bInterfaceNumber: number of interface */
    0x00,                                  /* bAlternateSetting: alternate set */
    0x01,                                  /* bNumEndpoints: number of endpoints */
    USB_CLASS_CODE_HID,                  /* bInterfaceClass: class code hid */
    0x00,                                  /* bInterfaceSubClass: subclass code */
    0x00,                                  /* bInterfaceProtocol: protocol code */
    0x00,                                  /* iInterface: index of string descriptor */

    0x09,                                  /* bLength: size of HID descriptor in bytes */
    HID_CLASS_DESC_HID,                  /* bDescriptorType: HID descriptor type */
    LBYTE(MOUSE_BCD_NUM),
    HBYTE(MOUSE_BCD_NUM),                  /* bcdHID: HID class specification release number */
    0x00,                                  /* bCountryCode: hardware target conutry */
    0x01,                                  /* bNumDescriptors: number of HID class descriptor to follow */
    HID_CLASS_DESC_REPORT,               /* bDescriptorType: report descriptor type */
    LBYTE(sizeof(g_usbd_mouse_report)),
    HBYTE(sizeof(g_usbd_mouse_report)),      /* wDescriptorLength: total length of reprot descriptor */

    USB_DEVICE_EPT_LEN,                  /* bLength: size of endpoint descriptor in bytes */
    USB_DESCIPTOR_TYPE_ENDPOINT,         /* bDescriptorType: endpoint descriptor type */
    USBD_MOUSE_IN_EPT,                     /* bEndpointAddress: the address of endpoint on usb device described by this descriptor */
    USB_EPT_DESC_INTERRUPT,                /* bmAttributes: endpoint attributes */
    LBYTE(USBD_MOUSE_IN_MAXPACKET_SIZE),
    HBYTE(USBD_MOUSE_IN_MAXPACKET_SIZE),   /* wMaxPacketSize: maximum packe size this endpoint */
    MOUSE_BINTERVAL_TIME,                  /* bInterval: interval for polling endpoint for data transfers */
};

/**
* @brief usb mouse report descriptor
*/
#if defined ( __ICCARM__ ) /* iar compiler */
    #pragma data_alignment=4
#endif
ALIGNED_HEAD uint8_t g_usbd_mouse_report ALIGNED_TAIL =
{
    0x05, 0x0c,
    0x09, 0x01,
    0xa1, 0x01,   //开一个集合
    0xa1, 0x00,
    //输入报表
    0x09, 0xe9,   //音量+
    0x09, 0xea,   //音量-
    0x09, 0xe2,   //静音
    0x09, 0xcd,   //暂停/播放
    0x09, 0xb5,   //下一首
    0x09, 0xb6,   //上一首

    0x35, 0x00,   //数组项目的物理最小值
    0x45, 0x01,   //数组项目的物理最大值
    0x15, 0x80,   //定义输入最小
    0x25, 0x7f,   //定义输入最大
    0x75, 0x01,   //定义报表数据项大小
    0x95, 0x06,   //定义报表数据向个数
    //输出报表
    0x81, 0x02,
    0x75, 0x01,   //定义报表数据项大小
    0x95, 0x02,   //定义报表数据向个数
    0x81, 0x01,
    0xc0, 0xc0      //关闭集合
};

/**
* @brief usb hid descriptor
*/
#if defined ( __ICCARM__ ) /* iar compiler */
    #pragma data_alignment=4
#endif
ALIGNED_HEAD uint8_t g_mouse_usb_desc ALIGNED_TAIL=
{
    0x09,                                  /* bLength: size of HID descriptor in bytes */
    HID_CLASS_DESC_HID,                  /* bDescriptorType: HID descriptor type */
    LBYTE(MOUSE_BCD_NUM),
    HBYTE(MOUSE_BCD_NUM),                  /* bcdHID: HID class specification release number */
    0x00,                                  /* bCountryCode: hardware target conutry */
    0x01,                                  /* bNumDescriptors: number of HID class descriptor to follow */
    HID_CLASS_DESC_REPORT,               /* bDescriptorType: report descriptor type */
    LBYTE(sizeof(g_usbd_mouse_report)),
    HBYTE(sizeof(g_usbd_mouse_report)),      /* wDescriptorLength: total length of reprot descriptor */
};


/**
* @brief usb string lang id
*/
#if defined ( __ICCARM__ ) /* iar compiler */
    #pragma data_alignment=4
#endif
ALIGNED_HEAD static uint8_t g_string_lang_id ALIGNED_TAIL =
{
    USBD_MOUSE_SIZ_STRING_LANGID,
    USB_DESCIPTOR_TYPE_STRING,
    0x09,
    0x04,
};

/**
* @brief usb string serial
*/
#if defined ( __ICCARM__ ) /* iar compiler */
    #pragma data_alignment=4
#endif
ALIGNED_HEAD static uint8_t g_string_serial ALIGNED_TAIL =
{
    USBD_MOUSE_SIZ_STRING_SERIAL,
    USB_DESCIPTOR_TYPE_STRING,
};


/* device descriptor */
static usbd_desc_t device_descriptor =
{
    USB_DEVICE_DESC_LEN,
    g_usbd_descriptor
};

/* device qualifier descriptor */
static usbd_desc_t device_qualifier_descriptor =
{
    USB_DEVICE_QUALIFIER_DESC_LEN,
    g_usbd_qualifier_descriptor
};

/* config descriptor */
static usbd_desc_t config_descriptor =
{
    USBD_MOUSE_CONFIG_DESC_SIZE,
    g_usbd_configuration
};

/* langid descriptor */
static usbd_desc_t langid_descriptor =
{
    USBD_MOUSE_SIZ_STRING_LANGID,
    g_string_lang_id
};

/* serial descriptor */
static usbd_desc_t serial_descriptor =
{
    USBD_MOUSE_SIZ_STRING_SERIAL,
    g_string_serial
};

static usbd_desc_t vp_desc;

/**
* @briefstandard usb unicode convert
* @paramstring: source string
* @paramunicode_buf: unicode buffer
* @retval length
*/
static uint16_t usbd_unicode_convert(uint8_t *string, uint8_t *unicode_buf)
{
    uint16_t str_len = 0, id_pos = 2;
    uint8_t *tmp_str = string;

    while (*tmp_str != '\0')
    {
      str_len ++;
      unicode_buf = *tmp_str ++;
      unicode_buf = 0x00;
    }

    str_len = str_len * 2 + 2;
    unicode_buf = str_len;
    unicode_buf = USB_DESCIPTOR_TYPE_STRING;

    return str_len;
}

/**
* @briefusb int convert to unicode
* @paramvalue: int value
* @parampbus: unicode buffer
* @paramlen: length
* @retval none
*/
static void usbd_int_to_unicode(uint32_t value, uint8_t *pbuf, uint8_t len)
{
    uint8_t idx = 0;

    for (idx = 0 ; idx < len ; idx ++)
    {
      if (((value >> 28)) < 0xA)
      {
            pbuf[ 2 * idx] = (value >> 28) + '0';
      }
      else
      {
            pbuf = (value >> 28) + 'A' - 10;
      }

      value = value << 4;

      pbuf = 0;
    }
}

/**
* @briefusb get serial number
* @paramnone
* @retval none
*/
static void get_serial_num(void)
{
    uint32_t serial0, serial1, serial2;

    serial0 = *(uint32_t *)MCU_ID1;
    serial1 = *(uint32_t *)MCU_ID2;
    serial2 = *(uint32_t *)MCU_ID3;

    serial0 += serial2;

    if (serial0 != 0)
    {
      usbd_int_to_unicode(serial0, &g_string_serial, 8);
      usbd_int_to_unicode(serial1, &g_string_serial, 4);
    }
}

/**
* @briefget device descriptor
* @paramnone
* @retval usbd_desc
*/
static usbd_desc_t *get_device_descriptor(void)
{
    return &device_descriptor;
}

/**
* @briefget device qualifier
* @paramnone
* @retval usbd_desc
*/
static usbd_desc_t *get_device_qualifier(void)
{
    return &device_qualifier_descriptor;
}经过上面一系列的修改,正常情况下已经可以枚举成 HID 设备了,接下来需要对 EC11 编码器模块进行处理,本次采用的模块接口定义如下:
引脚丝印功能说明
G与开发板 GND 相连
KEC11 中间按键按下后 K 引脚与 GND 导通,与开发板 PA3 相连
AEC11 A 相输出,与开发板 PA2 相连
BEC11 B 相输出,与开发板 PA4 相连
关于 EC11 的功能说明大家可以参考以下链接,在此不过多叙述:https://blog.csdn.net/lengyuefeng212/article/details/104328393主要实现的功能代码放于 main.c 中,以下为具体核心代码:/**
**************************************************************************
* @file   main.c
* @versionv2.0.1
* @date   2022-05-20
* @brief    main program
**************************************************************************
*                     Copyright notice & Disclaimer
*
* The software Board Support Package (BSP) that is made available to
* download from Artery official website is the copyrighted work of Artery.
* Artery authorizes customers to use, copy, and distribute the BSP
* software and its related documentation for the purpose of design and
* development in conjunction with Artery microcontrollers. Use of the
* software is governed by this copyright notice and the following disclaimer.
*
* THIS SOFTWARE IS PROVIDED ON "AS IS" BASIS WITHOUT WARRANTIES,
* GUARANTEES OR REPRESENTATIONS OF ANY KIND. ARTERY EXPRESSLY DISCLAIMS,
* TO THE FULLEST EXTENT PERMITTED BY LAW, ALL EXPRESS, IMPLIED OR
* STATUTORY OR OTHER WARRANTIES, GUARANTEES OR REPRESENTATIONS,
* INCLUDING BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
*
**************************************************************************
*/

#include "at32wb415_board.h"
#include "at32wb415_clock.h"
#include "usb_conf.h"
#include "usb_core.h"
#include "usbd_int.h"
#include "mouse_class.h"
#include "mouse_desc.h"

/** @addtogroup AT32WB415_periph_examples
* @{
*/

/** @addtogroup 415_USB_device_mouse USB_device_mouse
* @{
*/

/* usb global struct define */
otg_core_type otg_core_struct;
__IO uint8_t press_mouse = 0;
void usb_clock48m_select(usb_clk48_s clk_s);
void keyboard_send_string(uint8_t *string, uint8_t len);
void usb_gpio_config(void);
void usb_low_power_wakeup_config(void);
void system_clock_recover(void);
void button_exint_init(void);

unsigned char flag = 0;
unsigned char b = 0;
unsigned char KUP;
unsigned int cou;

#define Encoder_A_Pin            GPIO_PINS_2
#define Encoder_A_GPIO_Port      GPIOA
#define Encoder_K_Pin            GPIO_PINS_3
#define Encoder_K_GPIO_Port      GPIOA
#define Encoder_B_Pin            GPIO_PINS_4
#define Encoder_B_GPIO_Port      GPIOA

void Encoder_GPIO_Init(void)
{
    gpio_init_type gpio_init_struct;

    /* enable the led clock */
    crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, TRUE);

    /* set default parameter */
    gpio_default_para_init(&gpio_init_struct);

    /* configure button pin as input with pull-up */
    gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
    gpio_init_struct.gpio_out_type= GPIO_OUTPUT_PUSH_PULL;
    gpio_init_struct.gpio_mode = GPIO_MODE_INPUT;
    gpio_init_struct.gpio_pins = Encoder_A_Pin;
    gpio_init_struct.gpio_pull = GPIO_PULL_UP;
    gpio_init(Encoder_A_GPIO_Port, &gpio_init_struct);

    gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
    gpio_init_struct.gpio_out_type= GPIO_OUTPUT_PUSH_PULL;
    gpio_init_struct.gpio_mode = GPIO_MODE_INPUT;
    gpio_init_struct.gpio_pins = Encoder_K_Pin;
    gpio_init_struct.gpio_pull = GPIO_PULL_UP;
    gpio_init(Encoder_K_GPIO_Port, &gpio_init_struct);

    gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
    gpio_init_struct.gpio_out_type= GPIO_OUTPUT_PUSH_PULL;
    gpio_init_struct.gpio_mode = GPIO_MODE_INPUT;
    gpio_init_struct.gpio_pins = Encoder_B_Pin;
    gpio_init_struct.gpio_pull = GPIO_PULL_UP;
    gpio_init(Encoder_B_GPIO_Port, &gpio_init_struct);
}

void LED_Toggle()
{
    unsigned int i;
   
    press_mouse = 1;
    for (i = 0; i < 5; i++)
    {
      at32_led_off(LED2);
      at32_led_off(LED3);
      at32_led_off(LED4);
      delay_ms(10);
      at32_led_on(LED2);
      at32_led_on(LED3);
      at32_led_on(LED4);
      delay_ms(10);
    }
}

unsigned char Encoder_Scan(void)
{
    unsigned char a = 0; //存放按键的值
    unsigned char kt;

    if (gpio_input_data_bit_read(Encoder_A_GPIO_Port, Encoder_A_Pin))KUP = 0; //当L没有接通,标记为锁死
    if (!gpio_input_data_bit_read(Encoder_A_GPIO_Port, Encoder_A_Pin) && KUP == 0) //判断是否旋转旋钮,同时判断是否有旋钮锁死
    {
      cou = 0;
      delay_ms(1);
      kt = gpio_input_data_bit_read(Encoder_B_GPIO_Port, Encoder_B_Pin); //把旋钮另一端电平状态记录
      delay_ms(2);
      if (!gpio_input_data_bit_read(Encoder_A_GPIO_Port, Encoder_A_Pin)) //去抖
      {
            if (kt == 0) //用另一端判断左或右旋转
            {
                a = 1; //右转
            }
            else
            {
                a = 2; //左转
            }
            cou = 0; //初始锁死判断计数器
            while (!gpio_input_data_bit_read(Encoder_A_GPIO_Port, Encoder_A_Pin) && cou < 1200) //等待放开旋钮,同时累加判断锁死
            {
                cou++;
                KUP = 1;
                delay_ms(1);
            }
      }
    }
    if (gpio_input_data_bit_read(Encoder_K_GPIO_Port, Encoder_K_Pin))   flag = 0;
    if (flag != 1)
    {
      if (!gpio_input_data_bit_read(Encoder_K_GPIO_Port, Encoder_K_Pin) && KUP == 0) //判断旋钮是否按下
      {
            delay_ms(20);
            if (!gpio_input_data_bit_read(Encoder_K_GPIO_Port, Encoder_K_Pin)) //去抖动
            {
                delay_ms(200);
                if (gpio_input_data_bit_read(Encoder_K_GPIO_Port, Encoder_K_Pin))
                {
                  a = 3; //在按键按下时加上按键的状态值
                  flag = 0;
                }
                else
                {
                  a |= 0x10;
                  flag = 1;
                }
            }
      }
    }
    else
    {
      a |= 0x10;
    }
    if (a == 0x10)a = 0;
   
    return a;
}

/**
* @briefmain function.
* @paramnone
* @retval none
*/
int main(void)
{
    __IO uint32_t delay_index = 0;

    nvic_priority_group_config(NVIC_PRIORITY_GROUP_4);

    system_clock_config();

    at32_board_init();
    Encoder_GPIO_Init();
   
    /* usb gpio config */
    usb_gpio_config();

#ifdef USB_LOW_POWER_WAKUP
    /* enable pwc and bpr clock */
    crm_periph_clock_enable(CRM_PWC_PERIPH_CLOCK, TRUE);
    button_exint_init();
    usb_low_power_wakeup_config();
#endif

    /* enable otgfs clock */
    crm_periph_clock_enable(OTG_CLOCK, TRUE);

    /* select usb 48m clcok source */
    usb_clock48m_select(USB_CLK_HEXT);

    /* enable otgfs irq */
    nvic_irq_enable(OTG_IRQ, 0, 0);

    /* init usb */
    usbd_init(&otg_core_struct,
            USB_FULL_SPEED_CORE_ID,
            USB_ID,
            &mouse_class_handler,
            &mouse_desc_handler);

    at32_led_on(LED2);
    at32_led_on(LED3);
    at32_led_on(LED4);

    while (1)
    {
      b = Encoder_Scan();
      if (at32_button_press() == USER_BUTTON)
      {
            if (usbd_connect_state_get(&otg_core_struct.dev) == USB_CONN_STATE_CONFIGURED)
            {
                usb_hid_mouse_send(&otg_core_struct.dev, MUTE_OR_NOT);// 静音
                press_mouse = 1;
            }
            
            /* remote wakeup */
            if (usbd_connect_state_get(&otg_core_struct.dev) == USB_CONN_STATE_SUSPENDED
                  && (otg_core_struct.dev.remote_wakup == 1))
            {
                usbd_remote_wakeup(&otg_core_struct.dev);
            }
      }
      else if (press_mouse == 1)
      {
            delay_ms(10);
            usb_hid_mouse_send(&otg_core_struct.dev, BUTTON_RELEASE);
            press_mouse = 0;
      }
      
      if(b != 0)
      {
            if (usbd_connect_state_get(&otg_core_struct.dev) == USB_CONN_STATE_CONFIGURED)
            {
                if (b == 0x01)
                {
                  LED_Toggle();
                  usb_hid_mouse_send(&otg_core_struct.dev, VOLUME_UP);    //音量+
                }
                else if (b == 0x02)
                {
                  LED_Toggle();
                  usb_hid_mouse_send(&otg_core_struct.dev, VOLUME_DOWN);//音量-
                }
                else if (b == 0x03)
                {
                  LED_Toggle();
                  usb_hid_mouse_send(&otg_core_struct.dev, PLAY_PAUSE);//暂停/播放
                }
                else if (b == 0x11)
                {
                  LED_Toggle();
                  usb_hid_mouse_send(&otg_core_struct.dev, NEXT_SONG);//下一曲
                }
                else if (b == 0x12)
                {
                  LED_Toggle();
                  usb_hid_mouse_send(&otg_core_struct.dev, PREVIOUS_SONG);//上一曲
                }
            }
      }
      else
      {
            if (press_mouse == 1)
            {
                delay_ms(10);
                usb_hid_mouse_send(&otg_core_struct.dev, BUTTON_RELEASE);
                press_mouse = 0;
            }
      }            
      
#ifdef USB_LOW_POWER_WAKUP
      /* enter deep sleep */
      if (((mouse_type *)(otg_core_struct.dev.class_handler->pdata))->hid_suspend_flag == 1)
      {
            at32_led_off(LED2);
            at32_led_off(LED3);
            at32_led_off(LED4);
            /* congfig the voltage regulator mode */
            pwc_voltage_regulate_set(PWC_REGULATOR_LOW_POWER);

            /* enter deep sleep mode */
            pwc_deep_sleep_mode_enter(PWC_DEEP_SLEEP_ENTER_WFI);
            /* wait clock stable */
            for (delay_index = 0; delay_index < 600; delay_index++)
            {
                __NOP();
            }
            system_clock_recover();
            ((mouse_type *)(otg_core_struct.dev.class_handler->pdata))->hid_suspend_flag = 0;
            at32_led_on(LED2);
            at32_led_on(LED3);
            at32_led_on(LED4);
      }
#endif
    }
}

/**
* @briefusb 48M clock select
* @paramclk_s:USB_CLK_HICK, USB_CLK_HEXT
* @retval none
*/
void usb_clock48m_select(usb_clk48_s clk_s)
{
    switch (system_core_clock)
    {
    /* 48MHz */
    case 48000000:
      crm_usb_clock_div_set(CRM_USB_DIV_1);
      break;

    /* 72MHz */
    case 72000000:
      crm_usb_clock_div_set(CRM_USB_DIV_1_5);
      break;

    /* 96MHz */
    case 96000000:
      crm_usb_clock_div_set(CRM_USB_DIV_2);
      break;

    /* 120MHz */
    case 120000000:
      crm_usb_clock_div_set(CRM_USB_DIV_2_5);
      break;

    /* 144MHz */
    case 144000000:
      crm_usb_clock_div_set(CRM_USB_DIV_3);
      break;

    default:
      break;

    }
}

/**
* @briefthis function config gpio.
* @paramnone
* @retval none
*/
void usb_gpio_config(void)
{
    gpio_init_type gpio_init_struct;

    crm_periph_clock_enable(OTG_PIN_GPIO_CLOCK, TRUE);
    gpio_default_para_init(&gpio_init_struct);

#ifdef USB_SOF_OUTPUT_ENABLE
    gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
    gpio_init_struct.gpio_out_type= GPIO_OUTPUT_PUSH_PULL;
    gpio_init_struct.gpio_mode = GPIO_MODE_MUX;
    gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
    crm_periph_clock_enable(OTG_PIN_SOF_GPIO_CLOCK, TRUE);
    gpio_init_struct.gpio_pins = OTG_PIN_SOF;
    gpio_init(OTG_PIN_SOF_GPIO, &gpio_init_struct);
#endif

}
#ifdef USB_LOW_POWER_WAKUP
/**
* @briefusb low power wakeup interrupt config
* @paramnone
* @retval none
*/
void usb_low_power_wakeup_config(void)
{
    exint_init_type exint_init_struct;

    exint_default_para_init(&exint_init_struct);

    exint_init_struct.line_enable = TRUE;
    exint_init_struct.line_mode = EXINT_LINE_INTERRUPUT;
    exint_init_struct.line_select = OTG_WKUP_EXINT_LINE;
    exint_init_struct.line_polarity = EXINT_TRIGGER_RISING_EDGE;
    exint_init(&exint_init_struct);

    nvic_irq_enable(OTG_WKUP_IRQ, 0, 0);
}

/**
* @briefsystem clock recover.
* @paramnone
* @retval none
*/
void system_clock_recover(void)
{
    /* enable external high-speed crystal oscillator - hext */
    crm_clock_source_enable(CRM_CLOCK_SOURCE_HEXT, TRUE);

    /* wait till hext is ready */
    while (crm_hext_stable_wait() == ERROR);

    /* enable pll */
    crm_clock_source_enable(CRM_CLOCK_SOURCE_PLL, TRUE);

    /* wait till pll is ready */
    while (crm_flag_get(CRM_PLL_STABLE_FLAG) == RESET);

    /* enable auto step mode */
    crm_auto_step_mode_enable(TRUE);

    /* select pll as system clock source */
    crm_sysclk_switch(CRM_SCLK_PLL);

    /* wait till pll is used as system clock source */
    while (crm_sysclk_switch_status_get() != CRM_SCLK_PLL);
}

/**
* @briefconfigure button exint
* @paramnone
* @retval none
*/
void button_exint_init(void)
{
    exint_init_type exint_init_struct;

    crm_periph_clock_enable(CRM_IOMUX_PERIPH_CLOCK, TRUE);
    gpio_exint_line_config(GPIO_PORT_SOURCE_GPIOA, GPIO_PINS_SOURCE0);

    exint_default_para_init(&exint_init_struct);
    exint_init_struct.line_enable = TRUE;
    exint_init_struct.line_mode = EXINT_LINE_INTERRUPUT;
    exint_init_struct.line_select = EXINT_LINE_0;
    exint_init_struct.line_polarity = EXINT_TRIGGER_RISING_EDGE;
    exint_init(&exint_init_struct);

    nvic_irq_enable(EXINT0_IRQn, 0, 0);
}

/**
* @briefthis function handles otgfs wakup interrupt.
* @paramnone
* @retval none
*/
void OTG_WKUP_HANDLER(void)
{
    exint_flag_clear(OTG_WKUP_EXINT_LINE);
}

/**
* @briefexint0 interrupt handler
* @paramnone
* @retval none
*/
void EXINT0_IRQHandler(void)
{
    exint_flag_clear(EXINT_LINE_0);
}

#endif

/**
* @briefthis function handles otgfs interrupt.
* @paramnone
* @retval none
*/
void OTG_IRQ_HANDLER(void)
{
    usbd_irq_handler(&otg_core_struct);
}

/**
* @briefusb delay millisecond function.
* @paramms: number of millisecond delay
* @retval none
*/
void usb_delay_ms(uint32_t ms)
{
    /* user can define self delay function */
    delay_ms(ms);
}

/**
* @briefusb delay microsecond function.
* @paramus: number of microsecond delay
* @retval none
*/
void usb_delay_us(uint32_t us)
{
    delay_us(us);
}

/**
* @}
*/

/**
* @}
*/经过以上代码完善,已经可以实现整个音乐媒体控制器想要达成的功能了,而且还很顺畅,测试期间我还用 USB Bushond 抓取了 USB Device 从枚举到正常工作的过程,大家可以借鉴此工具来查验自己的 USB 设备是否改造成功:
以下展示了整个控制器的操作过程,还是比较丝滑的:
后续精加工对此应用作品有后续期望,如下:
[*]等待官方提供更加详细的蓝牙例程以及指导文档,这样可以增加蓝牙 HID 功能 ;
[*]使用 AT32WB415 系列芯片自己打板,将 EC11 模块和线性马达、RGB 灯全部整合在一起,实现更加酷炫的效果,并且加上锂电池充电功能,便于移动和携带 ;
[*]使用 3D 打印机制作一个外壳,或者采用公模,这样形成了一个完整的产品了。
就像它一样:此次测评之旅到此结束,感谢雅特力带来了不错的体验,只是以后希望更加大气些,别再让寄来的板子光秃秃的连个包装盒都不带(导致多处排针歪了),也还请官方后续将蓝牙这块的应用做更加丰富的案例参考。本次自己改动的代码已经全部放在本帖中,也就不再加到附件中了。{:lol:}

james03 发表于 2022-8-2 15:10

灰常不错,顶起

qiangtech 发表于 2022-8-3 09:49

不错,还是非常深度的试用。

Jon495323976 发表于 2022-8-5 15:29

蓝牙模块信号不佳是因为AN0077 代码默认的发射功率设置的太小导致
打开BLE的code,搜索‘RPL_POWER_MAX’ 把该宏修改为‘0x0F’即可正常搜索到蓝牙

jimmhu 发表于 2022-12-5 20:50

蓝牙的功能开发,有教程吗
            

wengh2016 发表于 2022-12-5 21:19

这个可以,性能怎么样?            

sdlls 发表于 2022-12-6 13:59

如果在电脑端口实现控制的?            

kevensz 发表于 2022-12-8 10:04

这个挺厉害的。
页: [1]
查看完整版本: 【AT-START-WB415测评】基于 USB HID 的音乐媒体控制器