- #define SW1 GPIO_ReadInputBit(GPIOA,GPIO_PIN_0)
- #define SW2 GPIO_ReadInputBit(GPIOB,GPIO_PIN_9)
- #define SW3 GPIO_ReadInputBit(GPIOB,GPIO_PIN_8)
- #define SW4 GPIO_ReadInputBit(GPIOB,GPIO_PIN_7)
- #define SW5 GPIO_ReadInputBit(GPIOB,GPIO_PIN_6)
- #define SW6 GPIO_ReadInputBit(GPIOA,GPIO_PIN_15)
- #define SW7 GPIO_ReadInputBit(GPIOB,GPIO_PIN_3)
- #define SW8 GPIO_ReadInputBit(GPIOB,GPIO_PIN_4)
- #define SW9 GPIO_ReadInputBit(GPIOB,GPIO_PIN_5)

我们只要配置8个GPIO作为输入去检测按键信号;
3.1.2 软件设计首先关于新建工程,我们直接使用官方的Examples下面的GPIO例子,将输出改成输入模式,初始化代码如下:
- /*!
- * [url=home.php?mod=space&uid=247401]@brief[/url] Board_KeyGPIOInit
- *
- * @param None
- *
- * @retval None
- */
- void Board_KeyGPIOInit(void)
- {
- GPIO_Config_T gpioConfigStruct;
-
-
- RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_GPIOA|RCM_APB2_PERIPH_GPIOB|RCM_APB2_PERIPH_AFIO);
- //PB3复位后是JTDO功能,这里需要禁用JTAG以实现PB3作为上拉输入模式 ,并且AFIO时钟也要使能
- GPIO_ConfigPinRemap(GPIO_REMAP_SWJ_JTAGDISABLE);
-
- gpioConfigStruct.mode = GPIO_MODE_IN_PU;
- gpioConfigStruct.pin = GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6
- |GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9;
- gpioConfigStruct.speed = GPIO_SPEED_50MHz;
-
- GPIO_Config(GPIOB, &gpioConfigStruct);
-
- gpioConfigStruct.pin = GPIO_PIN_15;
- GPIO_Config(GPIOA, &gpioConfigStruct);
-
- gpioConfigStruct.mode = GPIO_MODE_IN_PD;
- gpioConfigStruct.pin = GPIO_PIN_0;
- GPIO_Config(GPIOA, &gpioConfigStruct);
- }
需要留意的是PB3复位后是JTDO功能(这点参考规格书可以确认),这里需要禁用JTAG以实现PB3作为上拉输入模式;由于Up主一开始也没有留意,直接配置,然后发现PB7一直处于按下,低电平状态,后来才通过查看规格书中引脚图得知PB3复位后是JTDO功能;这个故事告诉我们一个道理:数据手册要经常看;
另外我们发现RCM_APB2_PERIPH_AFIO时钟也需要使能,GPIO_ConfigPinRemap这个函数我们也是只禁用了JTAG;
初始化完成之后我们需要写一个测试程序以测试按键是否按下;
- void Board_ButtonScan(void)
- {
- if(SW1==BIT_SET)
- {
- printf("SW1 Down\r\n");
- }
- if(SW2==BIT_RESET)
- {
- printf("SW2 Down\r\n");
- }
- if(SW3==BIT_RESET)
- {
- printf("SW3 Down\r\n");
- }
- if(SW4==BIT_RESET)
- {
- printf("SW4 Down\r\n");
- }
- if(SW5==BIT_RESET)
- {
- printf("SW5 Down\r\n");
- }
- if(SW6==BIT_RESET)
- {
- printf("SW6 Down\r\n");
- }
- if(SW7==BIT_RESET)
- {
- printf("SW7 Down\r\n");
- }
- if(SW8==BIT_RESET)
- {
- printf("SW8 Down\r\n");
- }
- if(SW9==BIT_RESET)
- {
- printf("SW9 Down\r\n");
- }
- }
最后在main函数中的while循环里调用并延迟一会;
- /*!
- * [url=home.php?mod=space&uid=247401]@brief[/url] Main program
- *
- * @param None
- *
- * @retval None
- *
- */
- int main(void)
- {
- Board_KeyGPIOInit();
- Board_UartPrintInit();
- while (1)
- {
- Board_ButtonScan();
- Delay();
-
- }
- }
-
- /*!
- * @brief Main program
- *
- * @param None
- *
- * @retval None
- *
- */
- void Delay(void)
- {
- volatile uint32_t delay = 0xfffff;
-
- while(delay--);
- }
3.1.3 下载验证我们通过我们自有的仿真器模块WCH-link(DAP模式)把程序下载进去即可,仿真器需要选择CMSIS-DAP Debugger;
这里用到的wch-link,我们是在这里购买的:https://item.taobao.com/item.htm?id=671288574690
wch-link支持DAP(ARM)和RV(wch RISC-V)模式,并且支持Usb转TTL串口;
我们把我们评估板上的H1的GND和TX分别接到wch-link的GND和RX;打开串口调试助手可以看到如下现象:
3.1.4 入门视频本节的入门视频链接如下:
【MINIGPA103 USBHID评估板】重新出发基于APM32F103CxT6实现评估板KEY按键输入测试 摇杆鼠标游戏手柄键盘设备哔哩哔哩bilibili
3.2 实例Eg2_JtickAdc本节我们将通过配置ADC DMA模式去对摇杆电位器的电压进行采样;
3.2.1硬件设计 如下图是我们评估板的原理图 ,可以看到VRX1和VRY1分别有以下对应PA1和PA2,这是ADC的ch1和ch2;

3.2.2 软件设计在上一节的基础上,我们初始ADC和DMA,初始化代码如下:
- /*!
- * @brief Board_KeyGPIOInit
- *
- * @param None
- *
- * @retval None
- */
- void Board_JoystickADCInit(void)
- {
- GPIO_Config_T configStruct;
- ADC_Config_T ADC_configStruct;
- DMA_Config_T DMA_ConfigStruct;
-
-
- RCM_ConfigADCCLK(RCM_PCLK2_DIV_6); /* 6分频 72/6=12MHZ ADCCLK不能超过14MHZ*/
- RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_GPIOA); /* 使能GPIO时钟 */
- RCM_EnableAHBPeriphClock(RCM_AHB_PERIPH_DMA1); /* 使能DMA1时钟 */
- RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_ADC1); /* 使能ADC1时钟 */
-
- NVIC_EnableIRQRequest(ADC1_2_IRQn, 0, 0);
-
- configStruct.pin = GPIO_PIN_1|GPIO_PIN_2;
- configStruct.mode = GPIO_MODE_ANALOG;
- GPIO_Config(GPIOA, &configStruct);
-
- DMA_Reset(DMA1_Channel1); /* 复位DMA1通道1 */
-
- DMA_ConfigStruct.peripheralBaseAddr = ADC1_DR_Address; /* DMA通道外设基地址 */
- DMA_ConfigStruct.memoryBaseAddr = (uint32_t)dma_buffer; /* DMA通道ADC数据存储器 */
- DMA_ConfigStruct.dir = DMA_DIR_PERIPHERAL_SRC; /* 指定外设为源地址 */
- DMA_ConfigStruct.bufferSize = 2; /* DMA缓冲区大小(根据ADC采集通道数量修改) */
- DMA_ConfigStruct.peripheralInc = DMA_PERIPHERAL_INC_DISABLE; /* 当前外设寄存器地址不变(即不自增) */
- DMA_ConfigStruct.memoryInc = DMA_MEMORY_INC_ENABLE; /* 当前存储器地址:Disable不变,Enable递增(用于多通道采集) */
- DMA_ConfigStruct.peripheralDataSize = DMA_PERIPHERAL_DATA_SIZE_HALFWORD; /* 外设数据宽度16位 */
- DMA_ConfigStruct.memoryDataSize = DMA_MEMORY_DATA_SIZE_HALFWORD; /* 存储器数据宽度16位 */
- DMA_ConfigStruct.loopMode = DMA_MODE_CIRCULAR; /* DMA通道操作模式位环形缓冲模式 */
- DMA_ConfigStruct.priority = DMA_PRIORITY_HIGH; /* DMA通道优先级高 */
- DMA_ConfigStruct.M2M = DMA_M2MEN_DISABLE; /* 禁止DMA通道存储器到存储器传输 */
- DMA_Config(DMA1_Channel1, &DMA_ConfigStruct);
-
- //DMA_EnableInterrupt(DMA1_Channel1, DMA_INT_TC);
- DMA_Enable(DMA1_Channel1);
-
-
-
- ADC_Reset(ADC1); /* 复位ADC1 */
- /** ADC1 Configuration */
- ADC_configStruct.mode = ADC_MODE_INDEPENDENT; /* ADC1工作在独立模式 */
- ADC_configStruct.scanConvMode = ENABLE; /* 使能扫描 */
- ADC_configStruct.continuosConvMode = ENABLE; /* 使能ADC连续转换模式 轮询方式使用*/
- // ADC_configStruct.continuosConvMode = DISABLE; /* 不使能ADC连续转换模式 中断方式使用*/
- ADC_configStruct.externalTrigConv = ADC_EXT_TRIG_CONV_None; /* 软件控制转换 */
- ADC_configStruct.dataAlign = ADC_DATA_ALIGN_RIGHT; /* 转换数据右对齐 */
- ADC_configStruct.nbrOfChannel = 2; /* 顺序进行规则转换的ADC通道的数目 */
- ADC_Config(ADC1, &ADC_configStruct); /* 初始化ADC1寄存器 */
-
- /* 设置指定ADC的规则组通道,设置它们的转化顺序和采样时间 */
- ADC_ConfigRegularChannel(ADC1, ADC_CHANNEL_1, 1, ADC_SAMPLETIME_13CYCLES5); /* ADC1选择通道10 采样顺序1 采样时间13.5个周期 */
- ADC_ConfigRegularChannel(ADC1, ADC_CHANNEL_2, 2, ADC_SAMPLETIME_13CYCLES5); /* ADC1选择通道11 采样顺序2 采样时间13.5个周期 */
-
-
- // ADC_EnableInterrupt(ADC1, ADC_INT_EOC); /* 使能ADC转换完成中断 */
- ADC_EnableDMA(ADC1); /* 使能ADC的DMA支持 */
- ADC_Enable(ADC1); /* 使能ADC1 */
-
- ADC_ResetCalibration(ADC1); /* 复位ADC1的校准寄存器 */
- while(ADC_ReadResetCalibrationStatus(ADC1)); /* 等待ADC1复位校准完成 */
- ADC_StartCalibration(ADC1); /* 开始ADC1校准 */
- while(ADC_ReadCalibrationStartFlag(ADC1)); /* 等待ADC1校准完成 */
-
- ADC_EnableSoftwareStartConv(ADC1); /* 启动ADC1转换 */
-
-
-
- }
配置完成之后我们需要写一个测试程序以测试ADC DMA的采样;
- /*!
- * @brief Main program
- *
- * @param None
- *
- * @retval None
- *
- */
- int main(void)
- {
- Board_Init();
- while (1)
- {
- /* 以下采用轮询方式等待转换完成 */
- while(!ADC_ReadStatusFlag(ADC1, ADC_FLAG_EOC)); /* 使用此行代码必须使能连续转换模式 */
- printf("ADC1采样数据:\r\n");
- for (uint8_t i = 0; i < 2; i++) {
- printf("ADC_CHANNEL_%d:%d\r\n", i, dma_buffer);
- }
- printf("\r\n");
- Delay();
-
- }
最后在main函数中的while循环里调用并延迟一会;
- /*!
- * @brief Main program
- *
- * @param None
- *
- * @retval None
- *
- */
- int main(void)
- {
- Board_KeyGPIOInit();
- Board_UartPrintInit();
- while (1)
- {
- Board_ButtonScan();
- Delay();
-
- }
- }
-
- /*!
- * @brief Main program
- *
- * @param None
- *
- * @retval None
- *
- */
- void Delay(void)
- {
- volatile uint32_t delay = 0xfffff;
-
- while(delay--);
- }
3.2.3 下载验证请参考视频;
3.2.4 入门视频本节的入门视频链接如下:
【MINIGPA103 USBHID评估板】基于APM32F103CxT6实现评估板ADC DMA对摇杆电位器进行采样测试 摇杆鼠标游戏手柄键盘设备哔哩哔哩bilibili
3.3 实例Eg3_USB_HID_Joystick前两节我们把基本的外设以及调试OK,现在我们开始USB的学习,本节需要具备一定的USB设备开发知识;关于Usb的学习,这里推荐两个学习视频和一个学习网站:
USB技术应用与开发:
https://www.bilibili.com/video/BV1sy4y1n7d9/?spm_id_from=333.33.header_right.fav_list.click&vd_source=2bbde87de845d5220b1d8ba075c12fb0
CherryUSB设备协议栈教程:
https://www.bilibili.com/video/BV1Ef4y1t73d/?spm_id_from=333.33.header_right.fav_list.click&vd_source=2bbde87de845d5220b1d8ba075c12fb0
USB中文网:
https://www.usbzh.com/
我们主要做USB HID开发,一般我们需要了解一些标准请求,还有HID类的请求;其中标准请求主要是主机获取设备描述符、配置描述符、接口描述符、端点描述符、字符串描述符的过程,如果是HID,还有HID描述符的过程 ,以及报表描述符的过程;
3.2.1硬件设计 请参考原理图;
3.2.2 软件设计这一节,主要是USB的代码;主要对USBD_InitParam_T这个USB初始化参数结构体的初始化
- /*!
- * @brief HID mouse init
- *
- * @param None
- *
- * @retval None
- */
- void HidMouse_Init(void)
- {
- USBD_InitParam_T usbParam;
-
- Get_SerialNum();
-
- USBD_InitParamStructInit(&usbParam);
-
- usbParam.classReqHandler = USBD_ClassHandler;
- usbParam.stdReqExceptionHandler = HidMouse_ReportDescriptor;
-
- usbParam.resetHandler = HidMouse_Reset;
- usbParam.inEpHandler = HidMouse_EPHandler;
- usbParam.pDeviceDesc = (USBD_Descriptor_T *)&g_deviceDescriptor;
- usbParam.pConfigurationDesc = (USBD_Descriptor_T *)&g_configDescriptor;
-
-
- usbParam.pStringDesc = (USBD_Descriptor_T *)g_stringDescriptor;
- usbParam.pStdReqCallback = &s_stdCallback;
-
- USBD_Init(&usbParam);
- }
首先是USBD_ClassHandler,我们不做任何修改
- /*!
- * @brief USB HID Class request handler
- *
- * @param reqData : point to USBD_DevReqData_T structure
- *
- * @retval None
- */
- void USBD_ClassHandler(USBD_DevReqData_T* reqData)
- {
- switch (reqData->byte.bRequest)
- {
- case HID_CLASS_REQ_SET_IDLE:
- s_hidIdleState = reqData->byte.wValue[1];
- USBD_CtrlInData(NULL, 0);
- break;
-
- case HID_CLASS_REQ_GET_IDLE:
- USBD_CtrlInData(&s_hidIdleState, 1);
- break;
-
- case HID_CLASS_REQ_SET_PROTOCOL:
- s_hidProtocol = reqData->byte.wValue[0];
- USBD_CtrlInData(NULL, 0);
- break;
-
- case HID_CLASS_REQ_GET_PROTOCOL:
- USBD_CtrlInData(&s_hidProtocol, 1);
- break;
-
- default:
- break;
- }
- }
-
接着是HidMouse_ReportDescriptor,主要是对获取HID描述符与报表描述符
- /*!
- * @brief Standard request Report HID Descriptor
- *
- * @param reqData: Standard request data
- *
- * @retval None
- */
- void HidMouse_ReportDescriptor(USBD_DevReqData_T *reqData)
- {
- uint8_t len;
-
- if((reqData->byte.bRequest == USBD_GET_DESCRIPTOR) &&
- (reqData->byte.bmRequestType.bit.recipient == USBD_RECIPIENT_INTERFACE) &&
- (reqData->byte.bmRequestType.bit.type == USBD_REQ_TYPE_STANDARD))
- {
- if(reqData->byte.wValue[1] == 0x21)
- {
- len = USB_MIN(reqData->byte.wLength[0], 9);
- USBD_CtrlInData((uint8_t *)&g_configDescriptor.pDesc[0x12], len);
- }
- else if(reqData->byte.wValue[1] == 0x22)
- {
- len = USB_MIN(reqData->byte.wLength[0], g_ReportDescriptor.size);
- USBD_CtrlInData((uint8_t *)g_ReportDescriptor.pDesc, len);
- }
- }
- else
- {
- USBD_SetEPTxRxStatus(USBD_EP_0, USBD_EP_STATUS_STALL, USBD_EP_STATUS_STALL);
- }
- }
再有,HidMouse_Reset和HidMouse_EPHandler,前者是配置打开端点1,后者是清除USB缓存;
- /*!
- * @brief Reset
- *
- * @param None
- *
- * @retval None
- */
- void HidMouse_Reset(void)
- {
- USBD_EPConfig_T epConfig;
-
- s_usbConfigStatus = 0;
-
- /* Endpoint 1 IN */
- epConfig.epNum = USBD_EP_1;
- epConfig.epType = USBD_EP_TYPE_INTERRUPT;
- epConfig.epBufAddr = USB_EP1_TX_ADDR;
- epConfig.maxPackSize = 4;
- epConfig.epStatus = USBD_EP_STATUS_NAK;
- USBD_OpenInEP(&epConfig);
-
- USBD_SetEPRxStatus(USBD_EP_1, USBD_EP_STATUS_DISABLE);
- }
- /*!
- * @brief Endpoint handler
- *
- * @param ep: Endpoint number
- *
- * @param dir: Direction.0: Out; 1: In
- *
- * @retval None
- */
- void HidMouse_EPHandler(uint8_t ep)
- {
- s_statusEP = 1;
- }
还有最关键的g_deviceDescriptor与g_configDescriptor,以及g_stringDescriptor,是获取设备描述符,配置描述符以及字符串描述符;
- /* Device descriptor */
- USBD_Descriptor_T g_deviceDescriptor = {s_hidMouseDeviceDescriptor, HID_MOUSE_DEVICE_DESCRIPTOR_SIZE};
- /* Config descriptor */
- USBD_Descriptor_T g_configDescriptor = {s_hidMouseConfigDescriptor, USB_CUSTOM_HID_CONFIG_DESC_SIZ};
- /* String descriptor */
- USBD_Descriptor_T g_stringDescriptor[SRTING_DESC_NUM] =
- {
- {s_hidMouseLandIDString, HID_MOUSE_LANGID_STRING_SIZE},
- {s_hidMouseVendorString, HID_MOUSE_VENDOR_STRING_SIZE},
- {s_hidMouseProductString, HID_MOUSE_PRODUCT_STRING_SIZE},
- {s_hidMouseSerialString, HID_MOUSE_SERIAL_STRING_SIZE}
- };
这些描述符的获取比较重要,亲直接参考我们的视频讲解;
最后是s_stdCallback,是USB初始完成的标志;
- /*!
- * @brief Standard request set configuration call back
- *
- * @param None
- *
- * @retval None
- */
- void HidMouse_SetConfigCallBack(void)
- {
- s_usbConfigStatus = 1;
- }
- /** @defgroup USB_HID_Mouse_Variables Variables
- @{
- */
-
- USBD_StdReqCallback_T s_stdCallback =
- {
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- HidMouse_SetConfigCallBack,
- NULL,
- NULL,
- NULL,
- NULL,
- };
3.2.3 下载验证我们通过我们自有的仿真器模块WCH-link(DAP模式)把程序下载进去即可,可以得到一个Joystick;
3.2.4 入门视频本节的入门视频链接如下: