51开源 USB 三键摸鱼 键盘@STC32G, 或者自己改成STC8H8K64U 成品如上图所示!话不多说开始介绍功能!开源程序源代码见附件 一、功能介绍: 单机版键盘的按键功能如下图所示,一共有3+6个按键,正面的三个按键就是机械键盘的可插拔键轴,侧面六个按键(上三个,下三个)是侧贴的沉板按键.
-
- ┌────键4-───键5-───键6-───┐
- │ ┌───┐ ┌───┐ ┌───┐ │
- │ │键1│ │键2│ │键3│ C <=== 连接数据线的TYPEC口
- │ └───┘ └───┘ └───┘ │
- └────键7-───键8-───键9-───┘
-
-
- 默认按键参数:
- 键4 = 数字1
- 键5 = 数字2
- 键6 = 数字3
- 键7 = 数字4
- 键8 = 数字5
- 键9 = 数字6
- 键1 = CTRL+C
- 键2 = CTRL+V
- 键3 = ALT+TAB
-
复制代码
另外板子上还有三个全彩WS2812的LED,驱动代码已经写好(基于DMA-SPI驱动,有效减少cpu占用),和一个PWM驱动的无缘蜂鸣器!
二、代码讲解:
开始代码前自行预习前面的知识:
基于前面的两个章节,已经可以实现设备插入电脑识别出USB CDC和HID设备了,那么后面就很简单了! (1)CDC数据处理:
- void usb_out_ep3()
- {
- BYTE csr;
- BYTE cnt;
- u8 dat[64];
-
- usb_write_reg(INDEX, 3);
- csr = usb_read_reg(OUTCSR1);
- if (csr & OUTSTSTL)
- {
- usb_write_reg(OUTCSR1, OUTCLRDT);
- }
- if (csr & OUTOPRDY)
- {
- cnt = usb_read_reg(OUTCOUNT1);
- while (cnt)
- {
- CDC_RxBuffer[CDC_RxBuffer_cnt++] = usb_read_reg(FIFO3);
- cnt--;
- }
- CDC_RxBuffer[CDC_RxBuffer_cnt] = '\0';
-
- if(strcmp(CMD_ReadParm,CDC_RxBuffer)==0)
- {
- USBCON = 0x00; //清除USB设置11111111
- USBCLK = 0x00;
- IRC48MCR = 0x00;
-
- Delay20ms();
- IAP_CONTR = 0x60; //触发软件复位,从ISP开始执行
- }
-
- if (RxWptr - RxRptr >= 256 - EP1OUT_SIZE)
- {
- UsbOutBusy = 1;
- }
- else
- {
- usb_write_reg(OUTCSR1, 0);
- }
- }
- }
复制代码
因为CDC占用的端点3,所以只需要在端点3的接收函数里处理下数据,这里将接收到的数据保存在CDC_RxBuffer里,数据个数保存在CDC_RxBuffer_cnt里,当然这里其实还有一个小技巧,为了配合上位机实现不停电下载,这里还加了一段字符串比较函数,如果接受到的字符串是"@STCISP#"就会进入下载模式!!只需要配合ISP软件的这个即可实现不停电下载!!
当然这个CDC最主要的功能当然不是只用一个不停电下载,是为了后面的上位机修改按键键码和自定义灯光特效!(既然都讲到了,那就提前放个上位机的半成品图片预告一下咯,还没做完,界面有点丑)
上面是接收处理,这里由于数据不处理,仅供测试,那么这里直接把接收到的数据直接回传到上位机,这里就可以在主函数里添加如下内容“
- if((CDC_RxBuffer_cnt>0)&&( !UsbInBusy )) //如果CDC有接受到数据
- {
- CDC_SendData(CDC_RxBuffer,CDC_RxBuffer_cnt); //CDC串口发回数据
- CDC_RxBuffer_cnt = 0; //清空数据
-
- }
复制代码
这里判断哪个按键按下之后通过KEYNUM_Read函数读取内部的按键键码数据(因为后面会通过上位机将键码数据保存到内部EEPROM,所以这里提前编写好了一个接口便于后期直接替换),这里的键码数据如下如所示
-
- u8 KEYNUM0[8] = { 0X00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }; //空白
-
- u8 KEYNUM1[8] = { 0X01,0x00,0x06,0x00,0x00,0x00,0x00,0x00 }; //复制
- u8 KEYNUM2[8] = { 0X01,0x00,0x19,0x00,0x00,0x00,0x00,0x00 }; //粘贴
- u8 KEYNUM3[8] = { 0X04,0x00,0x2b,0x00,0x00,0x00,0x00,0x00 }; //界面切换
-
- u8 KEYNUM4[8] = { 0X00,0x00,0x1E,0x00,0x00,0x00,0x00,0x00 }; //数字1
- u8 KEYNUM5[8] = { 0X00,0x00,0x1F,0x00,0x00,0x00,0x00,0x00 }; //数字2
- u8 KEYNUM6[8] = { 0X00,0x00,0x20,0x00,0x00,0x00,0x00,0x00 }; //数字3
- u8 KEYNUM7[8] = { 0X00,0x00,0x21,0x00,0x00,0x00,0x00,0x00 }; //数字4
- u8 KEYNUM8[8] = { 0X00,0x00,0x22,0x00,0x00,0x00,0x00,0x00 }; //数字5
- u8 KEYNUM9[8] = { 0X00,0x00,0x23,0x00,0x00,0x00,0x00,0x00 }; //数字6
复制代码
装填好了八个数据的键码,这里就可以开始准备发送数据了,发送之前
1.判断端口是否空闲
2.如果空闲先给他置忙碌
3.选择对应的USB端点(这里是端点1)
4.8个字节的数据写入该端点的缓存
5.开启发送
具体的代码见上面!
当然这里为什么键码是八个字节的呢!
USB键盘数据包含8个字节,他们所代表的数据分别如下:
所以,这里列举两个例子 1.发送数值1的键码(BYTE2写入0X1E,这个数据的由来见代码包里的HID码表):0X00,0x00,0x1E,0x00,0x00,0x00,0x00,0x00 2.发送CTRL+c的键码(BYTE0写入0x01,BYTE2写入0X1E,这个数据的由来见代码包里的HID码表):0X01,0x00,0x06,0x00,0x00,0x00,0x00,0x00 3.发送1+2+3的键码:0X00,0x00,0x1e,0x1f,0x20,0x00,0x00,0x00
以此类推@
(3)WS2812数据处理:
相信很多小伙伴都有做WS2812的时候,但是很多人还是用的延时,但是延时的时候一旦某个中断响应一下就必死无疑了,这里用一种SPI+DMA的方式实现基本0占用时间的方式 1.配置SPI - void SPI_init(void)
- {
- P_SW1 = (P_SW1 & ~(3<<2)) | (3<<2); //IO口切换. 0: P1.2/P5.4 P1.3 P1.4 P1.5, 1: P2.2 P2.3 P2.4 P2.5, 2: P5.4 P4.0 P4.1 P4.3, 3: P3.5 P3.4 P3.3 P3.2
- SSIG = 1; //忽略 SS 引脚功能,使用 MSTR 确定器件是主机还是从机
- SPEN = 1; //使能 SPI 功能
- DORD = 0; //先发送/接收数据的高位( MSB)
- MSTR = 1; //设置主机模式
- CPOL = 0; //SCLK 空闲时为低电平,SCLK 的前时钟沿为上升沿,后时钟沿为下降沿
- CPHA = 0; //数据 SS 管脚为低电平驱动第一位数据并在 SCLK 的后时钟沿改变数据
- SPCTL = (SPCTL & ~3) | 3; //SPI 时钟频率选择, 0: 4T, 1: 8T, 2: 16T, 3: 2T
-
- P32=P33=P34=0; //端口电平设置为低电平
- HSCLKDIV = 0x01; //高速时钟8分频,默认2分频。开漏模式通过10K电阻上拉到3.3V,电平上升速度慢,需要降低SPI速率才能正常通信。
- }
复制代码
2.配置DMA - void DMA_Config(void)
- {
- DMA_SPI_STA = 0x00;
- DMA_SPI_CFG = 0xE0; //bit7 1:Enable Interrupt
- DMA_SPI_AMT = 0xff; //设置传输总字节数:n+1
-
- DMA_SPI_TXAH = (u8)((u16)&DmaTxBuffer >> 8); //SPI发送数据存储地址
- DMA_SPI_TXAL = (u8)((u16)&DmaTxBuffer);
- DMA_SPI_RXAH = (u8)((u16)&DmaRxBuffer >> 8); //SPI接收数据存储地址
- DMA_SPI_RXAL = (u8)((u16)&DmaRxBuffer);
-
- DMA_SPI_CFG2 = 0x01; //01:P2.2
- DMA_SPI_CR = 0x81; //bit7 1:使能 SPI_DMA, bit6 1:开始 SPI_DMA 主机模式, bit0 1:清除 SPI_DMA FIFO
- }
复制代码
这里其实只需要发送数据,所以接收的不配置也可以。
有了上面的基础,剩下的是需要把颜色数据写入缓存,让DMA自己去刷新就好了!这样24个字节就是一个灯的颜色数据,DMA控制spi发出24个数据就能点亮一个灯,且24色数据就是 绿,红,蓝的数据,这里先调用一个函数将颜色数据写入缓存: - void WS_Write(u8 num,u8 g,u8 r,u8 b)
- {
- u16 i;
- num = num -1;
- color[num] = ((((u32)g)<<16)+(((u32)r)<<8)+(((u32)b)<<0)); //当前的颜色数值写入数组
-
- for(i=0;i<8;i++) //循环八次,先将绿色的数值写入缓存数组
- {
- if( (1<<(7-i))& g ) //如果是1
- DmaTxBuffer[24*num+i] = 0xff; //写入1码电平对应的数据
- else //如果是0
- DmaTxBuffer[24*num+i] = 0xf0; //写入0码电平对应的数据
- }
- for(i=0;i<8;i++) //循环八次,先将红色色的数值写入缓存数组
- {
- if( (1<<(7-i))& r ) //如果是1
- DmaTxBuffer[24*num+i+8] = 0xff; //写入1码电平对应的数据
- else //如果是0
- DmaTxBuffer[24*num+i+8] = 0xf0; //写入0码电平对应的数据
- }
- for(i=0;i<8;i++) //循环八次,先将蓝色的数值写入缓存数组
- {
- if( (1<<(7-i))& b ) //如果是1
- DmaTxBuffer[24*num+i+16] = 0xff; //写入1码电平对应的数据
- else //如果是0
- DmaTxBuffer[24*num+i+16] = 0xf0; //写入0码电平对应的数据
- }
- }
复制代码
然后控制颜色自动切换并刷新就好了 - void WS_task(void)
- {
- u8 i;
- static u16 tim=0; //计时变量
-
- if( tim<2048 ) //如果时间小于2048ms
- {
- for(i=0;i<12;i++) //循环12次
- {
- WS_Write(i+1,(u8)0,0,tim/8); //给12个灯都写入蓝色,蓝色的数值= 时间/8,数值逐渐变大
- }
- }
- else if( tim<4096 ) //如果时间小于2048ms
- {
- for(i=0;i<12;i++) //循环12次
- {
- WS_Write(i+1,(u8)0,0,(4095-tim)/8); //给12个灯都写入蓝色,数值变小
- }
- }
-
- tim++; //计时变量自加
- if( tim>4096 ) //大于4096,自动清0
- tim=0;
-
- SPI_Write_Nbytes(24*12); //刷新颜色
- }
复制代码
当然这里实现的就是一个最简单的蓝色呼吸效果(个人喜欢蓝色,所以就写了这个,当然也可以自己通过调用WS_Write函数写入自己喜欢的颜色,最后通过SPI_Write_Nbytes输出控制灯显示响应的颜色即可)
(4)蜂鸣器处理:
这里的蜂鸣器由于是无源的,所以要用PWM给他提供方波信号! - void PWM_Init(void)
- {
- PWMB_CCER1 = 0x00; //写 CCMRx 前必须先清零 CCxE 关闭通道
- PWMB_CCMR2 = 0x68; //配置PWM模式
- PWMB_CCER1 = 0x30; //配置通道输出使能和极性
-
- PWMB_ARRH = (u8)(PWM_PERIOD >> 8); //设置周期时间
- PWMB_ARRL = (u8)PWM_PERIOD;
-
-
- PWMB_ENO = 0x00;
- PWMB_ENO |= ENO6P; //使能输出
-
- PWMB_PS = 0x00; //高级 PWM 通道输出脚选择位
- PWMB_PS |= PWM6_1; //选择 PWM7_1 通道
-
- PWMB_BKR = 0x80; //使能主输出
- PWMB_CR1 |= 0x81; //使能ARR预装载,开始计时
- }
复制代码
代码比较简单,就不介绍了,看不懂的可以去看视频教程! - void PWM_Set(u16 fre)
- {
- if( fre>0 ) //如果频率大于0
- {
- PWMB_ARRH = (u8)(fre >> 8); //设置周期时间
- PWMB_ARRL = (u8)fre;
- PWMB_CCR6H = (u8)((fre/2) >> 8); //设置占空比时间
- PWMB_CCR6L = (u8)((fre/2));
- PWMB_ENO |= ENO6P; //使能输出
- }
- else
- PWMB_ENO = 0; //关闭输出
- }
复制代码
这里直接控制蜂鸣器开始和关闭
最终主函数的代码如下图所示!! - void main()
- {
- WTST = 0; //设置程序指令延时参数,赋值为0可将CPU执行指令的速度设置为最快
- EAXFR = 1; //扩展寄存器(XFR)访问使能
- CKCON = 0; //提高访问XRAM速度
-
- P5M0 = 0X10;P5M1 = 0X00; //设置为准双向口
- P3M0 = 0X00;P3M1 = 0X00; //设置为准双向口
- P2M0 = 0X00;P2M1 = 0X00; //设置为准双向口
- P1M0 = 0X00;P1M1 = 0X00; //设置为准双向口
-
- uart_init(); //串口初始化(这里备用)
- usb_init(); //usb初始化,作为CDC+HID复合设备
- Timer0_Init(); //定时器0初始化,提供ms时基
- SPI_init(); //SPI初始化,这里驱动WS的灯
- DMA_Config(); //DMA初始化
- WS_Init(); //ws灯的驱动参数初始化
- PWM_Init(); //驱动蜂鸣器的PWM初始化
- PWM_Set(6000); //打开蜂鸣器
- EA = 1; //使能总中断
- Delay50ms(); //延时50ms,测试蜂鸣器
- PWM_Set(0); //关闭蜂鸣器
-
- while (1)
- {
- if (DeviceState != DEVSTATE_CONFIGURED) //等待USB连接完成
- continue;
-
- if( tim_1ms ) //1MS定时到达
- {
- tim_1ms=0;
- CDC_task(); //串口修改数据任务
- HID_task(); //HID发送键码任务
- WS_task(); //灯光控制任务
- }
- }
- }
复制代码
源代码放在附件了,有需要的自行下载! 最后插一嘴:注意:代码里的VID和PID不能随意使用,仅供参考,商用的话请向STC官方申请或自行购买,不然出现任何问题本作者概不负责! |