本帖最后由 lilijin1995 于 2022-6-7 16:29 编辑
#申请原创# @21小跑堂 @21小跑堂 @21小跑堂
基于RISC-V CH32V103的鼠标键盘摇杆手柄Joystick学习开发
资料还在持续更新中,评估学习板已经上架,欢迎入手体验,
链接:https://pan.baidu.com/s/1418gLVJf8eAh52CYGSNUaQ?pwd=fkr6 提取码:fkr6 入手链接:https://item.taobao.com/item.htm?ft=t&id=632428000614
在线文档:https://www.cnblogs.com/Li-Share/p/15898572.html
B站视频教程:https://www.bilibili.com/video/BV17r4y1z758/ 第一部分、硬件概述
1.1 实物概图如上图所示,配置了8个6*6轻触按键,一个摇杆(Joystick),搭载一颗WS2812B灯珠; 并将UART1串口,编程接口(SWD),外接Joystick接口,microUSB接口引出; 左边是RKJXV1224005摇杆电位器,右边和下方是8颗66的轻触按键,右上方是5050封装的WS2812B灯珠,中间是microusb母座, H3是SWD烧录接口,烧录程序接口,H2是串口,H1是外接摇杆模块的接口;
1.2 Gamepad原理图
Gamepad原理图如图1.2所示,如看不清可打开Doc目录下的PDF文档查阅 第二部分、软件工具2.1 软件概述
在 /Software 目录下是常用的工具软件: 1. Dt2_4:配置USB设备Report描述符的工具;
2. USBHID调试助手/呀呀USB: USB调试工具,相当于串口调试助手功能;
3. BUSHound:总线调试工具;
4. USBlyzer:一款专业的USB协议分析软件
5. MounRiver: 编译器;2.2 MounRiver软件入门大家访问以下链接: http://mounriver.com/help
第三部分、实战训练3.1
实例Eg1_GamePad本节我们目标是实现GamePad的功能,枚举成XY轴的平面坐标和8个按键的USB HID类设备。 3.1.1硬件设计如上图是Joystick原理图,其中VRX1与VRY1是摇杆的电位器输出的电压信号(ADC检测); SW1则是按键,右侧H1是外接的Joystick口,供接joystick模块使用; 如上图是KEY原理图,我们只要配置8个GPIO作为输入去检测按键信号;
3.1.2 软件设计
首先是工程树,我们打开工程,可以看到Project Explorer下Gamepad目录如下图 其中 Binaries: 二进制文件; Includes: 包含的头文件; Core:内核文件,存放core_riscv内核文件; Debug: 存放串口打印和延迟函数相关的文件; myBSP: 我们自己编写的驱动文件; obj: 编译的生成的obj文件; Peripheral: 这是MCU厂商提供外设相关驱动; Startup: ch32v103的启动文件; User: ch32v103的配置文件,中断相关文件,main函数等;
工程目录这里只做一次介绍,后面的样例目录大同小异。我们先打开startup_ch32v10x.S启动文件,我们看到如下代码 jal SystemInit
la t0, main定位到SystemInit - <font face="仿宋, 仿宋_GB2312">void SystemInit (void)
- {
- RCC->CTLR |= (uint32_t)0x00000001;
- RCC->CFGR0 &= (uint32_t)0xF8FF0000;
- RCC->CTLR &= (uint32_t)0xFEF6FFFF;
- RCC->CTLR &= (uint32_t)0xFFFBFFFF;
- RCC->CFGR0 &= (uint32_t)0xFF80FFFF;
- RCC->INTR = 0x009F0000;
- SetSysClock();
- }</font>
关于RCC寄存器的配置,请各位自行查阅用户手册;我们接着打开SetSysClock函数 - static void SetSysClock(void)
- {
- #ifdef SYSCLK_FREQ_HSE
- SetSysClockToHSE();
- #elif defined SYSCLK_FREQ_24MHz
- SetSysClockTo24();
- #elif defined SYSCLK_FREQ_48MHz
- SetSysClockTo48();
- #elif defined SYSCLK_FREQ_56MHz
- SetSysClockTo56();
- #elif defined SYSCLK_FREQ_72MHz
- SetSysClockTo72();
- #endif
- /* If none of the define above is enabled, the HSI is used as System clock
- * source (default after reset)
- */
- }</font>
由于我们定义了SYSCLK_FREQ_72MHz,SetSysClockTo72这个函数设置了系统时钟为72M; 接下来我们来看看main函数,如下 - int main(void)
- {
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
- Delay_Init();
- USART_Printf_Init(115200);
-
- printf("SystemClk:%d\r\n",SystemCoreClock);
- printf("USBHD Device Test\r\n");
-
- pEP0_RAM_Addr = EP0_Databuf;
- pEP1_RAM_Addr = EP1_Databuf;
- pEP2_RAM_Addr = EP2_Databuf;
-
- USBHD_ClockCmd(RCC_USBCLKSource_PLLCLK_1Div5,ENABLE);
- USB_DeviceInit();
- NVIC_EnableIRQ( USBHD_IRQn );
- ADC_DMA_CONF();
- KEY_INIT();
-
- while(1)
- {
- printf("X=%d,Y=%d\r\n",ADC_ConvertedValue[0],ADC_ConvertedValue[1]);
- if(Ready)
- {
- Gp_SendReport();
- }
- }
- }</font>
NVIC_PriorityGroupConfig是配置优先级分组的,Delay_Init初始化延迟函数;USART2_Printf_Init初始化串口打印, pEP0_RAM_Addr = EP0_Databuf; pEP1_RAM_Addr = EP1_Databuf; pEP2_RAM_Addr = EP2_Databuf; 主要配置端点0~2的缓存Ram; USBHD_ClockCmd(RCC_USBCLKSource_PLLCLK_1Div5,ENABLE);配置系统时钟1.5分频,即48M; USB_DeviceInit(),是对usb设备进行初始化; ADC_DMA_CONF主要对ADC DMA进行配置; KEY_INIT是对按键所对应的GPIO进行初始化; 接着是Gp_SendReport,主要是讲处理并上报坐标和按键数据; - void Gp_SendReport(void)
- {
- memset(Joystick_Buf,0,3);
- Ytemp=ADC_ConvertedValue[0];
- Xtemp=ADC_ConvertedValue[1];
- if(Xtemp>Xmax)
- Xtemp=Xmax;
- if(Xtemp<Xmin)
- Xtemp=Xmin;
- if(Ytemp>=Ymax)
- Ytemp=Ymax;
- if(Ytemp<=Ymin)
- Ytemp=Ymin;
- printf("Xmax=%x,Xcen=%x,Xmin=%x\r\n",Xmax,Xtemp,Xmin);
- printf("Ymax=%x,Ycen=%x,Ymin=%x\r\n",Ymax,Ytemp,Ymin);
- //根据坐标极点确定坐标(两点直线方程)
- X=((Xtemp-Xmin)*255)/(Xmax-Xmin);
- Y=((Ytemp-Ymin)*255)/(Ymax-Ymin);
- Joystick_Buf[0]=X;
- Joystick_Buf[1]=Y;
- Joystick_Buf[2]=Key_Scan();
- Delay_Ms(10);
- while( Endp1Busy )//如果忙(上一包数据没有传上去),则等待。
- { ; }
- Endp1Busy = 1; //设置为忙状态
- memcpy(pEP1_IN_DataBuf, Joystick_Buf, 3);
- DevEP1_IN_Deal(3);
- } </font>
最后我们再来看看USBHD_IRQHandler,我们在这个函数值调用了USB_DevTransProcess。 3.1.3 下载验证
我们把固件程序下载进去可以,打开“设备与打印机”可以看到USB设备枚举成了一个Gamepad,如下图。 右键打开游戏控制器后,点击属性得到下图所示界面 我们可以摇Joystick和按按键可以发现上图游戏控制器界面也跟着响应。
3.2 实例Eg2_Mouse
本节我们目标是实现模拟鼠标的功能,枚举一个具有XY,左右中键以及滚轮上下的功能; 3.2.1硬件设计
同上一章节
3.2.2 软件设计
在上一章节的基础上,我们在USB_DevTransProcess中找到报告描述符的获取,并修改为如下内容 - case USB_DESCR_TYP_REPORT:
- if(((pSetupReqPak->wIndex)&0xff) == 0) //接口0报表描述符
- {
- pDescr = MouseRepDesc; //数据准备上传
- len = sizeof(MouseRepDesc);
- Ready = 1; //如果有更多接口,该标准位应该在最后一个接口配置完成后有效
- }
- else len = 0xff; //本程序只有2个接口,这句话正常不可能执行
- break;</font>
另外鼠标的报告描述符MouseRepDesc如下 - const UINT8 MouseRepDesc[]=
- {
- 0x05, 0x01, // USAGE_PAGE (Generic Desktop)
- 0x09, 0x02, // USAGE (Mouse)
- 0xa1, 0x01, // COLLECTION (Application)
- 0x09, 0x01, // USAGE (Pointer)
- 0xa1, 0x00, // COLLECTION (Physical)
- 0x05, 0x09, // USAGE_PAGE (Button)
- 0x19, 0x01, // USAGE_MINIMUM (Button 1)
- 0x29, 0x03, // USAGE_MAXIMUM (Button 3)
- 0x15, 0x00, // LOGICAL_MINIMUM (0)
- 0x25, 0x01, // LOGICAL_MAXIMUM (1)
- 0x95, 0x03, // REPORT_COUNT (3)
- 0x75, 0x01, // REPORT_SIZE (1)
- 0x81, 0x02, // INPUT (Data,Var,Abs)
- 0x95, 0x01, // REPORT_COUNT (1)
- 0x75, 0x05, // REPORT_SIZE (5)
- 0x81, 0x03, // INPUT (Cnst,Var,Abs)
- 0x05, 0x01, // USAGE_PAGE (Generic Desktop)
- 0x09, 0x30, // USAGE (X)
- 0x09, 0x31, // USAGE (Y)
- 0x09, 0x38, // USAGE (Wheel)
- 0x15, 0x81, // LOGICAL_MINIMUM (-127)
- 0x25, 0x7f, // LOGICAL_MAXIMUM (127)
- 0x75, 0x08, // REPORT_SIZE (8)
- 0x95, 0x03, // REPORT_COUNT (3)
- 0x81, 0x06, // INPUT (Data,Var,Rel)
- 0xc0, // END_COLLECTION
- 0xc0 // END_COLLECTION
- };</font>
然后是报文数据的处理如下: - //处理并上报数据
- void Gp_SendReport(void)
- {
- memset(Joystick_Buf,0,4);
-
-
- Ytemp=ADC_ConvertedValue[0];
- Xtemp=ADC_ConvertedValue[1];
-
- if(Xtemp>Xmax)
- Xtemp=Xmax;
- if(Xtemp<Xmin)
- Xtemp=Xmin;
-
- if(Ytemp>=Ymax)
- Ytemp=Ymax;
- if(Ytemp<=Ymin)
- Ytemp=Ymin;
-
- printf("Xmax=%x,Xcen=%x,Xmin=%x\r\n",Xmax,Xtemp,Xmin);
- printf("Ymax=%x,Ycen=%x,Ymin=%x\r\n",Ymax,Ytemp,Ymin);
- //根据坐标极点确定坐标(两点直线方程)
- X=((Xtemp-Xmin)*255)/(Xmax-Xmin);
- Y=((Ytemp-Ymin)*255)/(Ymax-Ymin);
-
- if(X>(X_BASE+20))
- {
- Joystick_Buf[1]=((X-X_BASE)>>DIV)+1;
- }
- if(X<(X_BASE-20))
- {
- Joystick_Buf[1]=(u8)-(((X_BASE-X)>>DIV)+1);
- }
- if(Y>(Y_BASE+20))
- {
- Joystick_Buf[2]=((Y-Y_BASE)>>DIV)+1;;;
- }
- if(Y<(Y_BASE-20))
- {
- Joystick_Buf[2]=(u8)-(((Y_BASE-Y)>>DIV)+1);
- }
- Key_Handle(Joystick_Buf);
-
- Delay_Ms(5);
-
- while( Endp1Busy )
- {
- ; //如果忙(上一包数据没有传上去),则等待。
- }
- Endp1Busy = 1; //设置为忙状态
- memcpy(pEP1_IN_DataBuf, Joystick_Buf, 4);
- DevEP1_IN_Deal(4);
-
- } </font>
其中X_BASE为摇杆中点, if(X>(X_BASE+20))就是摇杆左摇动;故而((X-X_BASE)>>DIV)+1计算赋值给我们我们X+坐标;其他方向同理,另外Key_Handle的代码如下,主要是为了处理按键与滚轮值 - void Key_Handle(uint8_t* kv)
- {
- if((LFKEY)==Bit_RESET)
- {
- kv[0]|=0x01;
- }
- if((RGKEY)==Bit_RESET)
- {
- kv[0]|=0x02;
- }
- if(SW1!=Bit_RESET)
- {
- kv[0]|=0x04;
- }
- if((UPKEY)==Bit_RESET)
- {
- if(c_tick++>5)
- {
- kv[3]=1;
- c_tick=0;
- }
- }
- if((DNKEY)==Bit_RESET)
- {
- if(c_tick++>5)
- {
- kv[3]=(u8)-1;
- c_tick=0;
- }
- }
- }</font>
3.2.3 下载验证我们把固件程序下载进去可以,打开“设备与打印机”可以看到USB设备枚举成了一个“LD Mouse”,如下图。 我们打开一个网页,摇动摇杆鼠标指针跟着动;右边上下左右键,左右代表鼠标左右键,上下代表滚轮;然后摇杆中键代表鼠标中键;
3.3 实例Eg3_KeyBoard
本节我们目标是实现模拟键盘的功能,枚举一个具有Shift键+1~8键的模拟键盘功能; 3.3.1硬件设计同第一章节
3.3.2 软件设计在上一章节的基础上,我们在USB_DevTransProcess中找到报告描述符的获取,并修改为如下内容 - case USB_DESCR_TYP_REPORT:
- if(((pSetupReqPak->wIndex)&0xff) == 0) //接口0报表描述符
- {
- pDescr = KeyboardRepDesc; //数据准备上传
- len = sizeof(KeyboardRepDesc);
- Ready = 1; //如果有更多接口,该标准位应该在最后一个接口配置完成后有效
- }
- else len = 0xff; //本程序只有2个接口,这句话正常不可能执行
- break;</font>
另外Keyboard的报告描述符KeyboardRepDesc如下 - const UINT8 MouseRepDesc[]=
- {
- 0x05, 0x01, // USAGE_PAGE (Generic Desktop)
- 0x09, 0x06, // USAGE (Keyboard)
- 0xa1, 0x01, // COLLECTION (Application)
- 0x05, 0x07, // USAGE_PAGE (Keyboard)
- 0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl)
- 0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI)
- 0x15, 0x00, // LOGICAL_MINIMUM (0)
- 0x25, 0x01, // LOGICAL_MAXIMUM (1)
- 0x75, 0x01, // REPORT_SIZE (1)
- 0x95, 0x08, // REPORT_COUNT (8)
- 0x81, 0x02, // INPUT (Data,Var,Abs)
- 0x95, 0x01, // REPORT_COUNT (1)
- 0x75, 0x08, // REPORT_SIZE (8)
- 0x81, 0x03, // INPUT (Cnst,Var,Abs)
- 0x95, 0x05, // REPORT_COUNT (5)
- 0x75, 0x01, // REPORT_SIZE (1)
- 0x05, 0x08, // USAGE_PAGE (LEDs)
- 0x19, 0x01, // USAGE_MINIMUM (Num Lock)
- 0x29, 0x05, // USAGE_MAXIMUM (Kana)
- 0x91, 0x02, // OUTPUT (Data,Var,Abs)
- 0x95, 0x01, // REPORT_COUNT (1)
- 0x75, 0x03, // REPORT_SIZE (3)
- 0x91, 0x03, // OUTPUT (Cnst,Var,Abs)
- 0x95, 0x06, // REPORT_COUNT (6)
- 0x75, 0x08, // REPORT_SIZE (8)
- 0x15, 0x00, // LOGICAL_MINIMUM (0)
- 0x25, 0xFF, // LOGICAL_MAXIMUM (255)
- 0x05, 0x07, // USAGE_PAGE (Keyboard)
- 0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated))
- 0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application)
- 0x81, 0x00, // INPUT (Data,Ary,Abs)
- 0xC0 // END_COLLECTION
- };</font>
然后是报文数据的处理如下:
最后是main函数,只改了while中的Keyboard_Handle(); - /*******************************************************************************
- * Function Name : main
- * Description : Main program.
- * Input : None
- * Return : None
- *******************************************************************************/
- int main(void)
- {
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
- Delay_Init();
- USART_Printf_Init(115200);
-
- printf("SystemClk:%d\r\n",SystemCoreClock);
- printf("USBHD Device Test\r\n");
-
- pEP0_RAM_Addr = EP0_Databuf;
- pEP1_RAM_Addr = EP1_Databuf;
- pEP2_RAM_Addr = EP2_Databuf;
-
- USBHD_ClockCmd(RCC_USBCLKSource_PLLCLK_1Div5,ENABLE);
- USB_DeviceInit();
- NVIC_EnableIRQ( USBHD_IRQn );
- ADC_DMA_CONF();
- KEY_INIT();
-
- while(1)
- {
- printf("X=%d,Y=%d\r\n",ADC_ConvertedValue[0],ADC_ConvertedValue[1]);
- if(Ready)
- {
- Keyboard_Handle();
- }
- }
- }
3.3.3 下载验证我们把固件程序下载进去可以,打开“设备与打印机”可以看到USB设备枚举成了一个“LD Keyboard”,如下图。 我们打开一个键盘测试网页,地址如下: https://keyboard.bmcx.com/ 按摇杆按键SW1即为shift键按下,其他键分别对应主键盘的1~8;shift+1-8键也可以组合;
|