MiniCH32V203EVB应用案例指南
第一部分、前言
由于作者水平有限,文档和视频中难免有出错和讲得不好的地方,欢迎各位读者和观众善意地提出意见和建议,谢谢!本文档是MiniCH32V203评估板案例的指南,主要是一些相关硬件说明和代码讲解;如有疑问,可发邮件到以下邮箱:
1101495766@qq.com
购买方式:
-
淘宝:https://item.taobao.com/item.htm?ft=t&id=768285052770
-
B站工房:https://mall.bilibili.com/neul-next/index.html?page=mall-up_itemDetail&noTitleBar=1&itemsId=1104871067&from=items_share&msource=items_share
资料下载:资料与评估板配套,购买后向客服索取;
第二部分、硬件概述
2.1 实物图:
从上图可以看出,CH32V203评估板提供了12V,5V,3.3V供电需求,同时板载两个Type-C 接口,同时扩充丰富的接口和功能模块,特别适合开发工程师验证与初学者的学习。
2.1 原理图:
第三部分、USB应用案例
3.1 按键编码器模拟鼠标:
该案例是使用一个五向按键模块和一个旋转编码器模块实现一个模拟鼠标的功能,其中按键模块五向按键实现的是鼠标的XY和左中右按键,而旋转编码器模块实现的是滚轮上下功能;
3.1.1硬件说明:
我们把用到的IO功能表列出来,用以后续软件作为参考;
IO名称 | 复用/映射功能 | 描述 |
---|---|---|
PA0 | TIM2_CH1 | 用作编码器接口输入A |
PA1 | TIM2_CH2 | 用作编码器接口输入B |
PA2 | GPIO上拉输入 | 检测UP按键 |
PA3 | GPIO上拉输入 | 检测DWN按键 |
PA4 | GPIO上拉输入 | 检测LFT按键 |
PA5 | GPIO上拉输入 | 检测RHT按键 |
PA6 | GPIO上拉输入 | 检测MID按键 |
PA7 | GPIO上拉输入 | 检测SET按键 |
PA8 | GPIO上拉输入 | 检测RST按键 |
3.1.2 程序设计:
程序主要分为三部分:一是一般外设驱动,二是USB HID驱动,三是应用代码;
3.1.2.1 外设驱动
按键驱动:Button_Init()主要是配置相关IO作为输入,Button_Handler是传入鼠标buffer把对应的数据解析一下;如上下左右按键就是模拟XY的,SET、RST和MID则是左右中按键;
void Button_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure = { 0 };
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4
| GPIO_Pin_5 | GPIO_Pin_6|GPIO_Pin_7|GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
void Button_Handler(u8 *buf) {
//Y
if ((UP_ButtonSta() == Bit_RESET) && (DWN_ButtonSta() != Bit_RESET)) {
buf[2] = -STEP;
} else if ((UP_ButtonSta() != Bit_RESET)
&& (DWN_ButtonSta() == Bit_RESET)) {
buf[2] = STEP;
} else {
buf[2] = 0;
}
//X
if ((LFT_ButtonSta() == Bit_RESET) && (RHT_ButtonSta() != Bit_RESET)) {
buf[1] = -STEP;
} else if ((LFT_ButtonSta() != Bit_RESET)
&& (RHT_ButtonSta() == Bit_RESET)) {
buf[1] = STEP;
} else {
buf[1] = 0;
}
//中键
if (MID_ButtonSta() == Bit_RESET) {
buf[0] |= 0x04;
} else {
buf[0] &= ~0x04;
}
//左键
if (SET_ButtonSta() == Bit_RESET) {
buf[0] |= 0x01;
} else {
buf[0] &= ~0x01;
}
//右键
if (RST_ButtonSta() == Bit_RESET) {
buf[0] |= 0x02;
} else {
buf[0] &= ~0x02;
}
}
旋转编码器:EncoderInit()配置TIM2为编码器接口模式,map()是把编码器输出的数据限制在8bit以内,EncoderHandler是把编码器数据转出滚轮数据;
void EncoderInit(void) {
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
//使能相应时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //使能AFIO复用功能模块时钟
//GPIO初始化配置 TIM2_CH1(PA0) TIM2_CH2(PA1)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
//定时器初始化配置
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Period = 0xFFFF; //计数器自动重装载值
TIM_TimeBaseStructure.TIM_Prescaler = 1; //预分频器值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0; //重复计数器值
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //初始化结构体
TIM_EncoderInterfaceConfig(TIM2, TIM_EncoderMode_TI12,
TIM_ICPolarity_Rising, TIM_ICPolarity_Rising); //使用编码器模式3
//输入捕获配置
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1 | TIM_Channel_2;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //输入捕获极性设置,可用于配置编码器正反相
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //输入捕获预分频器设置
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //输入捕获通道选择,编码器模式需选用此配置
TIM_ICInitStructure.TIM_ICFilter = 10; //输入捕获滤波器设置
TIM_ICInit(TIM2, &TIM_ICInitStructure);
// TIM_ClearFlag(TIM2, TIM_FLAG_Update); //清除TIM更新标志位
//
// TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //使能开启TIM中断
//Reset counter
TIM_SetCounter(TIM2, 0);
TIM_Cmd(TIM2, ENABLE);
}
/*
在这个例子中,map 函数的参数解释如下:
x: 输入值
in_min 和 in_max: 输入范围的最小值和最大值
out_min 和 out_max: 输出范围的最小值和最大值
*/
s8 map(s16 x, s16 in_min, s16 in_max, s16 out_min, s16 out_max) {
if(x>in_max)
{
return 10;
}else if(x<in_min)
{
return -10;
}else{
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
}
s8 EncoderHandler(void)
{
s8 encout;
s16 enc= TIM_GetCounter(TIM2);
TIM_SetCounter(TIM2,0);
encout=map(enc,-100,100,-127,127);
printf("enc = %d\r\n", enc);
printf("enc1 = %d\r\n", encout);
return encout;
}
3.1.2.2 USB驱动
USB 传输事务的处理:USBHD_IRQHandler是传输事务的处理,这里我们只看标准请求里面的获取描述符请求,其中USB_DESCR_TYP_DEVICE是获取设备描述符;
USB_DESCR_TYP_CONFIG是获取配置描述符集合,该集合有配置描述符、接口描述符、HID描述符、端点描述符;
USB_DESCR_TYP_HID是获取HID描述符;
USB_DESCR_TYP_REPORT是获取报告描述符;
USB_DESCR_TYP_STRING是获取字符串描述符,字符串描述符又分语言ID描述符、厂商字符串描述符、产品字符串描述符、序列号字符串描述符;
case USB_GET_DESCRIPTOR:
switch( (uint8_t)( USBFS_SetupReqValue >> 8 ) )
{
/* get usb device descriptor */
case USB_DESCR_TYP_DEVICE:
pUSBFS_Descr = MyDevDescr;
len = DEF_USBD_DEVICE_DESC_LEN;
break;
/* get usb configuration descriptor */
case USB_DESCR_TYP_CONFIG:
pUSBFS_Descr = MyCfgDescr;
len = DEF_USBD_CONFIG_DESC_LEN;
break;
/* get usb hid descriptor */
case USB_DESCR_TYP_HID:
if( USBFS_SetupReqIndex == 0x00 )
{
pUSBFS_Descr = &MyCfgDescr[ 18 ];
len = 9;
}
else
{
errflag = 0xFF;
}
break;
/* get usb report descriptor */
case USB_DESCR_TYP_REPORT:
if( USBFS_SetupReqIndex == 0x00 )
{
pUSBFS_Descr = MouseRepDesc;
len = DEF_USBD_REPORT_DESC_LEN_MS;
}
else
{
errflag = 0xFF;
}
break;
/* get usb string descriptor */
case USB_DESCR_TYP_STRING:
switch( (uint8_t)( USBFS_SetupReqValue & 0xFF ) )
{
/* Descriptor 0, Language descriptor */
case DEF_STRING_DESC_LANG:
pUSBFS_Descr = MyLangDescr;
len = DEF_USBD_LANG_DESC_LEN;
break;
/* Descriptor 1, Manufacturers String descriptor */
case DEF_STRING_DESC_MANU:
pUSBFS_Descr = MyManuInfo;
len = DEF_USBD_MANU_DESC_LEN;
break;
/* Descriptor 2, Product String descriptor */
case DEF_STRING_DESC_PROD:
pUSBFS_Descr = MyProdInfo;
len = DEF_USBD_PROD_DESC_LEN;
break;
/* Descriptor 3, Serial-number String descriptor */
case DEF_STRING_DESC_SERN:
Get_SerialNum();
pUSBFS_Descr = USBD_StringSerial;
len = USBD_StringSerial[0];
break;
default:
errflag = 0xFF;
break;
}
break;
default :
errflag = 0xFF;
break;
}
/* Copy Descriptors to Endp0 DMA buffer */
if( USBFS_SetupReqLen > len )
{
USBFS_SetupReqLen = len;
}
len = ( USBFS_SetupReqLen >= DEF_USBD_UEP0_SIZE ) ? DEF_USBD_UEP0_SIZE : USBFS_SetupReqLen;
memcpy( USBFS_EP0_Buf, pUSBFS_Descr, len );
pUSBFS_Descr += len;
break;
3.1.2.3 应用代码
这里的应用代码,主要是使用了MultiTimer组件整合了外设驱动和USB HID数据上报
#include "PollSystem.h"
#include "Button.h"
#include "Encoder.h"
#include "ch32v20x_usbfs_device.h"
vu32 uwTick;
MultiTimer timer1;
MultiTimer timer2;
u8 Mouse_Report[4] = { 0 };
void SysTick_Handler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
uint64_t PlatformTicksGetFunc(void) {
return uwTick;
}
u8 buttonSemaphore = 0;
void ButtonTimer1Callback(MultiTimer* timer, void *userData) {
static u8 lastbut=0;
Button_Handler(Mouse_Report);
Mouse_Report[3]=EncoderHandler();
if((Mouse_Report[1]!=0)||(Mouse_Report[2]!=0)||(Mouse_Report[3]!=0)||(Mouse_Report[0]!=lastbut))
{
buttonSemaphore = 1;
lastbut=Mouse_Report[0];
}
MultiTimerStart(timer, 5, ButtonTimer1Callback, userData);
}
void JoystickTimer3Callback(MultiTimer* timer, void *userData) {
if(buttonSemaphore)
{
buttonSemaphore=0;
USBFS_Endp_DataUp( DEF_UEP1, Mouse_Report, sizeof( Mouse_Report ), DEF_UEP_CPY_LOAD );
}
MultiTimerStart(timer, 5, JoystickTimer3Callback, userData);
}
void SYSTICK_Init_Config(u64 ticks)
{
SysTick->SR = 0;
SysTick->CNT = 0;
SysTick->CMP = ticks;
SysTick->CTLR =0xF;
NVIC_SetPriority(SysTicK_IRQn, 15);
NVIC_EnableIRQ(SysTicK_IRQn);
}
void PollSystemInit(void) {
SYSTICK_Init_Config(SystemCoreClock/1000-1);
MultiTimerInstall(PlatformTicksGetFunc);
MultiTimerStart(&timer1, 5, ButtonTimer1Callback, NULL);
MultiTimerStart(&timer2, 5, JoystickTimer3Callback,NULL);
}
void SysTick_Handler(void)
{
SysTick->SR = 0;
uwTick++;
}