电子DIY小家 发表于 2023-10-11 09:45:41

51开源 USB 三键摸鱼 键盘@STC32G, 或者自己改成STC8H8K64U

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驱动的无缘蜂鸣器!

二、代码讲解:
开始代码前自行预习前面的知识:1.USB基础知识:https://www.stcaimcu.com/forum.php?mod=viewthread&tid=38992.USB复合设备(CDC+HID)分析:https://www.stcaimcu.com/forum.php?mod=viewthread&tid=4080

基于前面的两个章节,已经可以实现设备插入电脑识别出USB CDC和HID设备了,那么后面就很简单了!(1)CDC数据处理:
void usb_out_ep3()
{
    BYTE csr;
      BYTE cnt;
      u8 dat;
      
    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 =usb_read_reg(FIFO3);
                        cnt--;
      }
                CDC_RxBuffer= '\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 = { 0X00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 };                //空白

u8 KEYNUM1 = { 0X01,0x00,0x06,0x00,0x00,0x00,0x00,0x00 };                //复制
u8 KEYNUM2 = { 0X01,0x00,0x19,0x00,0x00,0x00,0x00,0x00 };                //粘贴
u8 KEYNUM3 = { 0X04,0x00,0x2b,0x00,0x00,0x00,0x00,0x00 };                //界面切换

u8 KEYNUM4 = { 0X00,0x00,0x1E,0x00,0x00,0x00,0x00,0x00 };                //数字1
u8 KEYNUM5 = { 0X00,0x00,0x1F,0x00,0x00,0x00,0x00,0x00 };                //数字2
u8 KEYNUM6 = { 0X00,0x00,0x20,0x00,0x00,0x00,0x00,0x00 };                //数字3
u8 KEYNUM7 = { 0X00,0x00,0x21,0x00,0x00,0x00,0x00,0x00 };                //数字4
u8 KEYNUM8 = { 0X00,0x00,0x22,0x00,0x00,0x00,0x00,0x00 };                //数字5
u8 KEYNUM9 = { 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,0x002.发送CTRL+c的键码(BYTE0写入0x01,BYTE2写入0X1E,这个数据的由来见代码包里的HID码表):0X01,0x00,0x06,0x00,0x00,0x00,0x00,0x003.发送1+2+3的键码:0X00,0x00,0x1e,0x1f,0x20,0x00,0x00,0x00
以此类推@

(3)WS2812数据处理:
相信很多小伙伴都有做WS2812的时候,但是很多人还是用的延时,但是延时的时候一旦某个中断响应一下就必死无疑了,这里用一种SPI+DMA的方式实现基本0占用时间的方式1.配置SPIvoid 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.配置DMAvoid 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
}
这里其实只需要发送数据,所以接收的不配置也可以。
实际测试在24MHZ的情况下,这个SPI发出的数据为F0就是0码的电平,FF就是1码的电平!关于这个电平和WS2812驱动原理的描述可以看帖子:https://www.stcaimcu.com/forum.php?mod=viewthread&tid=292
有了上面的基础,剩下的是需要把颜色数据写入缓存,让DMA自己去刷新就好了!这样24个字节就是一个灯的颜色数据,DMA控制spi发出24个数据就能点亮一个灯,且24色数据就是 绿,红,蓝的数据,这里先调用一个函数将颜色数据写入缓存:void WS_Write(u8 num,u8 g,u8 r,u8 b)
{
      u16 i;
      num = num -1;
      color = ((((u32)g)<<16)+(((u32)r)<<8)+(((u32)b)<<0));                //当前的颜色数值写入数组
      
      for(i=0;i<8;i++)                                                                                                //循环八次,先将绿色的数值写入缓存数组
      {
                if( (1<<(7-i))& g )                                                                                        //如果是1
                        DmaTxBuffer = 0xff;                                                      //写入1码电平对应的数据
                else                                                                                                                //如果是0
                        DmaTxBuffer = 0xf0;                                                      //写入0码电平对应的数据
      }
      for(i=0;i<8;i++)                                                                                                //循环八次,先将红色色的数值写入缓存数组
      {
                if( (1<<(7-i))& r )                                                                                        //如果是1
                        DmaTxBuffer = 0xff;                                                      //写入1码电平对应的数据
                else                                                                                                                //如果是0
                        DmaTxBuffer = 0xf0;                                                      //写入0码电平对应的数据
      }      
      for(i=0;i<8;i++)                                                                                                //循环八次,先将蓝色的数值写入缓存数组
      {
                if( (1<<(7-i))& b )                                                                                        //如果是1
                        DmaTxBuffer = 0xff;                                                //写入1码电平对应的数据
                else                                                                                                                //如果是0
                        DmaTxBuffer = 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();                                                                                                //灯光控制任务
                }         
    }
}
源代码放在附件了,有需要的自行下载!有需要成品的也可以直接在这链接购买:https://item.taobao.com/item.htm ... ECf&id=742811211185
最后插一嘴:注意:代码里的VID和PID不能随意使用,仅供参考,商用的话请向STC官方申请或自行购买,不然出现任何问题本作者概不负责!

神农鼎 发表于 2023-10-11 11:15:23

要看懂这个【USB摸鱼】的程序,可能还得来上课
=============================================
【USB 原理及实战,16课时】,视频教学已完美完成 ,大学标准课程 !
【10月/9号,10月/11号】USB基本原理教学视频, 已上传
【10月/16号的 USB-HID 通信 实战】教学视频超级完美, 已上传
             是对着协议和代码一行一行的讲解,认真听的都说会了
【10月/18号下午的 USB-CDC虚拟串口 实战】教学视频, 已上传
             USB-CDC虚拟串口 / 就是最简单最强大的串口
             是对着协议和代码一行一行的讲解,认真听的都说会了
请帮忙转发给可能需要:从0开始了解 USB 的 同学/同事/老师/研发人员
https://www.stcaimcu.com/forum.php?mod=viewthread&tid=4526&extra=&page=1
=========================================
【CAN 原理及实战,8课时】,教学视频,制作中,后续直接看视频回放

gentleman 发表于 2023-10-11 11:25:56

{:4_250:}

32位8051-AI 发表于 2023-10-11 11:53:54

本帖最后由 32位8051-STCAI 于 2023-10-11 11:58 编辑

原理图:




电子DIY小家 发表于 2023-10-17 15:56:25

本帖最后由 电子DIY小家 于 2023-10-17 15:58 编辑

有小伙伴再问这个要怎么下载代码:
一、USB-CDC不停电下载(上一次下载的程序里需要包含一段不停电下载的代码才可以使用):
USB线连接之后,进行如下设置


上述设置完成之后,打开代码,点击下载等待完成即可

二、LINK1D下载:
LINK1D连接电脑和小键盘之后,按照如下设计即可:



如果下载窗口出现等待USB-HID,把这两个勾勾去掉就可以了,


马永锋 发表于 2023-10-17 18:36:33

冲哥开源作品继续玩,想怎么定义怎么定义。
attach://24436.mp4

神农鼎 发表于 2023-10-23 13:31:17

网友问:这个 USB-HID键盘 扩展成支持 :
控制电脑:音量+ / 音量- 及【播放|暂停】功能

摸鱼键盘 自定义快捷键 HID免驱 STC主控 MCU代码开源 DIY键盘-淘宝网 (taobao.com)
51开源 USB 三键摸鱼 键盘@STC32G, 或者自己改成STC8H8K64U



电子DIY小家 发表于 2023-11-1 16:01:12



单机V1.1版本预告:
把小键盘的单个HID通道的Consumer Control和Keyboard功能结合起来了,可以同时控制音量加减和标准键盘用了,驱动代码已调通,区别于隔壁某恒还是用的两个端点做的,这个单点方案的网上开源资料太少,这个版本的整理完直接在这里开源。

神农鼎 发表于 2023-11-1 16:13:03

{:4_196:}冲哥威武

电子DIY小家 发表于 2023-11-2 13:51:47

电子DIY小家 发表于 2023-11-1 16:01
单机V1.1版本预告:
把小键盘的单个HID通道的Consumer Control和Keyboard功能结合起来了,可以同时控制音 ...
内测版程序群文件自取哈
页: [1] 2
查看完整版本: 51开源 USB 三键摸鱼 键盘@STC32G, 或者自己改成STC8H8K64U