请选择 进入手机版 | 继续访问电脑版

 找回密码
 立即注册
楼主: 电子D***

触摸电子琴从无到有心得分享——基于STC8H4K64TL的电子琴

[复制链接]

该用户从未签到

538

主题

8730

回帖

1万

积分

管理员

积分
14205
发表于 2023-3-16 20:41:48 | 显示全部楼层
是模仿实际的量产产品:
1, 外壳/亚克力玻璃5mm厚;
2, 经过弹簧;
3, 到实际的PCB线路板的触摸按键

===你总不能洗衣机的电路板露在外面给你按按键吧
===你总不能电饭煲的电路板露在外面给你按按键吧
  • TA的每日心情
    奋斗
    昨天 10:00
  • 签到天数: 93 天

    [LV.6]常住居民II

    10

    主题

    68

    回帖

    866

    积分

    高级会员

    积分
    866
    发表于 2023-3-17 09:55:15 | 显示全部楼层
    本帖最后由 ourstc 于 2023-3-17 09:58 编辑

    楼主真帅,必须顶一下!
    那个触摸弹簧某宝上随便买就可以用的吗?
    除了尺寸其它方面要注意什么不?

    点评

    弹簧随便买就好,搜触摸弹簧,偏心U或者直针等等只要能焊到板子上的弹簧都可以,弹簧的选型主要看你的外壳,和你的外壳能够接触到,弹簧相互之间没有接触就行。因为弹簧装上去之后第一次需要软件稍微校准一下的!所  详情 回复 发表于 2023-3-17 10:17

    该用户从未签到

    9

    主题

    286

    回帖

    1638

    积分

    版主

    积分
    1638
    QQ
    发表于 2023-3-17 10:17:34 | 显示全部楼层
    ourstc 发表于 2023-3-17 09:55
    楼主真帅,必须顶一下!
    那个触摸弹簧某宝上随便买就可以用的吗?
    除了尺寸其它方面要注意什么不? ...

    弹簧随便买就好,搜触摸弹簧,偏心U或者直针等等只要能焊到板子上的弹簧都可以,弹簧的选型主要看你的外壳,和你的外壳能够接触到,弹簧相互之间没有接触就行。因为弹簧装上去之后第一次需要软件稍微校准一下的!所以理论来说任意弹簧都可以
    QQ:1463035472,微信:19952583954
  • TA的每日心情
    开心
    昨天 15:41
  • 签到天数: 68 天

    [LV.6]常住居民II

    31

    主题

    885

    回帖

    6272

    积分

    荣誉版主

    冲哥视频教程和各种开源资料QQ交流群884047237,可群

    积分
    6272
    QQ
     楼主| 发表于 2023-3-17 11:15:09 | 显示全部楼层

    触摸电子琴从无到有心得分享——基于STC8H4K64TL的电子琴
    八、触摸调试

    终于要来到全贴最紧张刺激的时候了,我们要开始触摸部分的调试了,最早看中这颗芯片最主要的就是看中了触摸,因为之前做过好多项目都需要用到触摸,那这里也算是为了以后的项目做个铺垫,那么首先我们先把触摸部分焊接起来。
    1.png

    因为芯片里集成了触摸驱动,板子上触摸相关 的电路非常简单,核心电路就是一颗电容(电容规格参考下图),然后每用到一路触摸多加一个电阻和弹簧,非常的简单。这里的话有两路触摸的引脚我想后面留着做RTC时钟,所以这里我就不焊了。(这里唯一可惜的就是这个板子小了点,导致这个触摸焊盘距离设计的比较近,最后这个弹簧都只能选了个还没满月的小弹簧~)当然这里要注意的就是这个电容,可以看到手册里关于电容有如下描述:

    2.png

    这样子我们触摸电路部分就完成了,接着开始我们的程序调试,首先看看手册(这个芯片我也是第一次用,也得仔细找找资料,从头开始学起)

    3.png

    看了手册最开始的介绍,大概意思就是说芯片里面有自带了个ADC,然后实时读取ADC,触摸按下了这个ADC数值就会变小,松开了ADC就会回到原来的数值,然后程序直接判断当前ADC数值和这个比较的差值(当前ADC大于差值,按键没按下;当前ADC小于差值,按键按下了)就好了,那么问题来了,这个差值我们怎么确定呢,接着往下看:

    4.png


    我们按照目录一个个往下看,当然这个我前面也说过,第一遍不用去精读某个字眼或者句子,直接看到最后一个范例程序,呕吼,这里难道还有现成的程序么,这不马上就可以实测一下了~那我们直接到这一章节去看看,打开这一章节如下截图:

    5.png

    这里看到他居然还有上位机可以配置参数,优秀!那我们按照他的方法去测试一下(这里小小的吐槽一下,看完了这一章节居然没有范例程序!~这有点不像STC的风格,STC的每一个功能下面都会带程序,但是这里居然没写,但是仔细一想上位机都出来了咋可能没程序,那我们就去找么,肯定在官网啥的有,再不行直接call官方技术要)。那我就是按照如下顺序找的程序:

    1.去官网的演示视频里找之前看到的触摸的视频,看看有没有线索

    6.png

    果不其然,在这个视频里看到了这个程序的名字(如下图),但是他没说这个程序在哪里,那咱就接着找

    7.png

    2.去官网的软件工具板块找,果不其然,在这个工具里面找到了~:lol这种找个东西那不分分钟的,毫无压力。然后直接下载。

    8.png

    本来到这里应该结束了,但是万万没想到打脸来的那么快,这个下载的文件夹里打开居然只有如下内容,不死心的我还解压出来看了,只有一个手册里提到的上位机,好家伙,接着找。

    9.png

    3.接着往下找,最后在下载的其他里面又找到了一个,可以看到这个文件,这描述里还写了包含例程,那这次不会再有错了吧!!!在有问题咱就真的只能怒而打电话了。

    10.png

    下载下来打开压缩包一看,果然啥都有了:lol然后解压出来准备测试一下子看看

    11.png




    首先打开他的程序看一眼,乍一看程序好长,咱都懒得一个个看,咱就看看头上的中文,需要串口那我们就找个串口工具,时钟选择22.1184~触摸引脚就是这16个雷打不动!(毕竟硬件摆在那里,触摸的引脚还能跑了不成?)

    12.png

    话不多说,直接测试:

    13.png


    按照如上配置我们的ISP软件,打开我们最新的ISP软件(截止至目前最新版本就是我用的这个6.91M版本),然后选择芯片型号,选择串口(因为要用这个软件调试需要串口,这里直接接串口工具下载好了),选择程序文件,主时钟改成22.1184,然后点击下载,等待下载完成,然后开始下一步测试,按照官网之前我们看过的视频,直接打开上位机就能读到数据了,我们去试试:

    14.png

    按照上图那样选择115200的波特率,选择我们的串口,直接点击打开,然后选择触摸数据的选项卡,再点击一下最后面的读取键值按钮,可以看到下面的框框里会有个折线图开始动起来了~~这不是就要完成了么!

    这里选了和TK00的触摸按钮,我们去我们的原理图上找下这个是哪个按钮!

    15.png

    16.png

    结合硬件的触摸引脚,我们吧所有的触摸焊盘对应的硬件的引脚给他标注出来了,可以看到这里T几就是表示触摸的第几个通道,上面选择的TK00就是这个T0,也就是下面一排的第四个触摸焊盘,这时候我们去按一下这个触摸的弹簧试试


    可以看到这里所有的这个按钮,会有一个肉眼可见的ADC数值变低的反应,那么说明这个触摸按键画的还是可以的,能用~当然这里按下几次过后,我们可以按一下获取差值的按钮,这样ADC的差值数据就出来了,后面程序我们就可以根据这个差值去处理了~那么剩下的几个通道我们都可以这样去测试,然后获取到全部通道的差值

    17.png

    最终所有通道的差值都出来了,具体数值如下图,因为TK6和TK7我们要作为RTC的晶振,所以这里没有接触摸,但是焊盘预留了,需要的小伙伴们可以自行测试

    18.png


    可以看到上图中,14个ADC通道的差值,这个是按下触摸和抬起触摸的ADC数值的一个差值,我们程序可以通过这个值去换算然后作为比较值,所以这个数值的准确性非常的重要,我们可以看到这里差值最小的只有五千多,大的有一万多,当然这里其实差值应该是越大越好,这样才能方便我们去比较~(这个参数一定要记好,后面应该要用到)

    当然这里影响触摸ADC数值的原因大概可以分为以下几种:

    1.触摸的Tcap引脚上的电容和触摸的电阻。

    2.触摸的走线和铺铜,以及板子上其它元器件的干扰

    3.触摸弹簧材质,按压的力度,时间

    4.其它(欢迎小伙伴补充其它内容)


    当然我们这个板子这个参数看下来也差不多能用,不会说存在差值太小,没法区分的情况,说明一方面这个板子布线还可以,另一方面也能说明这个STC的触摸功能用起来非常的方便!!!就上面几步就完成了测试。



  • TA的每日心情
    开心
    昨天 15:41
  • 签到天数: 68 天

    [LV.6]常住居民II

    31

    主题

    885

    回帖

    6272

    积分

    荣誉版主

    冲哥视频教程和各种开源资料QQ交流群884047237,可群

    积分
    6272
    QQ
     楼主| 发表于 2023-3-17 16:54:09 | 显示全部楼层

    触摸电子琴从无到有心得分享——基于STC8H4K64TL的电子琴
    八、触摸程序编写

    上面的章节我们已经测试过触摸没有问题了,并且也找到了一个比较好用的触摸程序,那么现在话不多说先把他的触摸程序移植过来,他的就是我的,我的也还是我的!
    首先打开上个章节用过的那个代码,
    1.png
    前面几行是变量,IO等的初始化,这些都一样,这里先不管他,我们把他最终要的触摸的功能(红色框)复制到我们的代码里,绿色框他是串口的,我们也可以复制过来帮助我们调试,蓝色框他是触摸变量用到的初始,他这个功能比较强大,还加了零点追踪的功能,这里先给大家用最简单的办法,先不考虑这么复杂的功能,先开始移植

    第一步,先开始移植串口
    1. //========================================================================
    2. // 函数: void        UART1_config(u32 clk, u32 brt)
    3. // 描述: UART1初始化函数。
    4. // 参数: clk: 系统主频.
    5. //       brt: 通信波特率.
    6. // 返回: none.
    7. // 版本: VER1.0
    8. // 日期: 2021-7-29
    9. // 备注:
    10. //========================================================================
    11. void        UART1_config(u32 clk, u32 brt)
    12. {
    13.         brt = 65536UL - (clk / 4) / brt;
    14.         TR1 = 0;
    15.         AUXR &= ~0x01;                //S1 BRT Use Timer1;
    16.         AUXR |=  (1<<6);        //Timer1 set as 1T mode
    17.         TMOD &= ~(1<<6);        //Timer1 set As Timer
    18.         TMOD &= ~0x30;                //Timer1_16bitAutoReload;
    19.         TH1 = (u8)(brt >> 8);
    20.         TL1 = (u8)brt;
    21.         ET1 = 0;                        // 禁止Timer1中断
    22.         INTCLKO &= ~0x02;        // Timer1不输出高速时钟
    23.         TR1  = 1;                        // 运行Timer1
    24.         P_SW1 &= 0x3f;
    25.         P_SW1 |= 0x00;      //UART1 switch to, 0x00: P3.0 P3.1, 0x40: P3.6 P3.7, 0x80: P1.6 P1.7, 0xC0: P4.3 P4.4
    26.         SCON = (SCON & 0x3f) | (1<<6);        // 8位数据, 1位起始位, 1位停止位, 无校验
    27. //        PS  = 1;        //高优先级中断
    28.         ES  = 1;        //允许中断
    29.         REN = 1;        //允许接收
    30.         B_TX1_Busy = 0;
    31.         RX1_Cnt = 0;
    32. }
    33. //========================================================================
    34. // 函数: void UART1_TxByte(u8 dat)
    35. // 描述: 串口1发送一个字节函数
    36. // 参数: dat: 要发送的数据.
    37. // 返回: none.
    38. // 版本: VER1.0
    39. // 日期: 2018-4-2
    40. // 备注:
    41. //========================================================================
    42. void UART1_TxByte(u8 dat)
    43. {
    44.         B_TX1_Busy = 1;                        //标志发送忙
    45.         SBUF = dat;                                        //发一个字节
    46.         while(B_TX1_Busy);        //等待发送完成
    47. }
    48. //========================================================================
    49. // 函数: void UART1_PrintString(u8 *puts)
    50. // 描述: 串口1字符串打印函数
    51. // 参数: puts: 字符串指针.
    52. // 返回: none.
    53. // 版本: VER1.0
    54. // 日期: 2018-4-2
    55. // 备注:
    56. //========================================================================
    57. void UART1_PrintString(u8 *puts)
    58. {
    59.         for (; *puts != 0;        puts++)
    60.         {
    61.                 UART1_TxByte(*puts);
    62.         }
    63. }
    64. //========================================================================
    65. // 函数: void UART1_int (void) interrupt UART1_VECTOR
    66. // 描述: 串口1中断函数
    67. // 参数: none.
    68. // 返回: none.
    69. // 版本: VER1.0
    70. // 日期: 2018-4-2
    71. // 备注:
    72. //========================================================================
    73. void UART1_int (void) interrupt UART1_VECTOR
    74. {
    75.         if(RI)
    76.         {
    77.                 RI = 0;
    78.                 if(RX1_Cnt >= CMD_LEN) RX1_Cnt = 0;
    79.                 RX1_Buffer[RX1_Cnt] = SBUF;
    80.                 RX1_Cnt++;
    81.                 RX1_TimeOut = 5;
    82.         }
    83.         if(TI)
    84.         {
    85.                 TI = 0;
    86.                 B_TX1_Busy = 0;
    87.         }
    88. }
    复制代码

    先把这几个串口初始化的代码全都复制到我们的代码里(上面的代码随便找个MCU的串口初始化代码都能用),然后现在main函数里调用初始化函数UART1_config,第一个入口参数是主时钟,第二个波特率,这里可以通过软件自己计算参数,这里我们就直接定死波特率115200好了。最后我们的程序里的串口初始化就是这么一行:
    1. UART1_config(MAIN_Fosc,115200);
    复制代码

    这边串口接收函数我们也暂时不用,所以串口中断里接收的部分我们也给他全都屏蔽掉,如下蓝色部分屏蔽掉,
    1. //========================================================================
    2. // 函数: void UART1_int (void) interrupt UART1_VECTOR
    3. // 描述: 串口1中断函数
    4. // 参数: none.
    5. // 返回: none.
    6. // 版本: VER1.0
    7. // 日期: 2018-4-2
    8. // 备注:
    9. //========================================================================
    10. void UART1_int (void) interrupt UART1_VECTOR
    11. {
    12.         if(RI)
    13.         {
    14.                 RI = 0;
    15. <font color="#00bfff">//                if(RX1_Cnt >= CMD_LEN) RX1_Cnt = 0;
    16. //                RX1_Buffer[RX1_Cnt] = SBUF;
    17. //                RX1_Cnt++;
    18. //                RX1_TimeOut = 5;</font>
    19.         }
    20.         if(TI)
    21.         {
    22.                 TI = 0;
    23.                 B_TX1_Busy = 0;
    24.         }
    25. }
    复制代码

    最后在为了能更加方便的使用我们的串口打印调试信息,这里我们增加一个printf的函数的支持,首先调用头文件
    1. #include        "stdio.h"
    复制代码

    然后重定向一下串口的函数,也就是添加如下的代码
    1. char putchar(char dat)
    2. {
    3.     UART1_TxByte(dat);
    4.     return dat;
    5. }   
    复制代码

    这样我们的工程就可以使用printf函数了,最后我们在main函数初始化串口完成之后加上下面这句,使能一下总中断
    1. EA = 1;
    复制代码
    最后我们在while函数之前编写如下代码就完成了:
    1. printf("基于STC8H4K64TL的触摸电子琴\r\n");
    复制代码

    我们来看一下测试效果:
    1.png
    可以看到这边串口打印出来了我们想要的数据,那这个函数成功了之后,后面我们就可以做很多的功能了。

    第二步触摸初始化代码移植
    1. P1n_pure_input(0xff);        //Touch Key设置为高阻
    2.         P5n_pure_input(0x0f);
    3.         P0n_pure_input(0x0f);
    复制代码
    可以看到我们原来的代码里有这么三行,这个就是为了初始化引脚的,我们上一张内容用来测试的代码写法好像没有这个好,那我们就还在这个基础上就好就好了,我们P1.6-P1.7因为后面要作为RTC时钟的晶振引脚,这里就不配置了,所以我们最后改好的初始化代码是这个样子的
    1. P1n_pure_input(0x3f);        //Touch Key设置为高阻
    2. P5n_pure_input(0x0f);
    3. P0n_pure_input(0x0f);
    复制代码

    然后我们把上节内容里的触摸初始化的代码全都复制过来:放在我们之前的LED驱动代码之后,EA=1;之前。
    1.         TSRT = 0x00;                //没有LED分时扫描
    2.         TSCHEN1 = 0xff;        //TK00~TK07
    3.         TSCHEN2 = 0xff;        //TK08~TK15
    4.         TSCFG1  = (7<<4) + 3;        //开关电容工作频率 = fosc/(2*(TSCFG1[6:4]+1)), 放电时间(系统时钟周期数) 0(125) 1(250) 2(500) 3(1000) 4(2000) 5(2500) 6(5000) 7(7500) 最小3
    5.         TSCFG2  = 2;                                //配置触摸按键控制器的内部参考电压(AVCC的分压比), 0(1/4)  1(1/2)  2(5/8)  3(3/4)
    6.         TSCTRL = 0xA0;                        //开始自动扫描, 无平均, B7: TSGO,  B6: SINGLE,  B5: TSWAIT, B4: TSWUCS, B3: TSDCEN, B2: TSWUEN, B1 B0: TSSAMP
    7. //        TSCTRL = (1<<7) + (1<<6);        //开始单次扫描, 无平均
    8. //        TSCTRL = (1<<7) + (1<<6)+3;        //开始单次扫描, 4次平均
    9. //        TSCTRL = (1<<7) + (1<<6)+1;        //开始单次扫描, 2次平均
    10. //        TSWUTC = 12;                //100ms唤醒一次
    11.         IE2 |= 0x80;                        //使能触摸中断
    复制代码
    来一个个看下这里提到的寄存器。
    1.png
    第一个TSRT寄存器,说这个接了触摸的IO口如果又要接触摸,又要接指示灯可以用这个功能,不过这里我们不需要,那这个寄存器就和他说的这样直接写0就好了。


    2.png

    这里TSCHENn是用来配置触摸是否需要,总共16个触摸,要用的通道对应的位置写1就好,这里的话,我们除了P16,P17别的都选中,所以这里我们要把它写成

    1. TSCHEN1 = 0x3f;        //TK00~TK05
    2. TSCHEN2 = 0xff;        //TK08~TK15
    复制代码
    在下一个寄存器是

    3.png

    4.png

    这里就是一些系统时钟和参考电源的设置,当然我们历程能用,且用的好好地我们就不需要再去修改它了。

    5.png

    6.png

    最后一个就是这里的TSCTRL寄存器了,这个寄存器具体描述可以看手册的这部分描述,这里的话我们需要按键一直检测,所以我们直接把它配置为启动模式,重复扫描,并且完成一轮采集后需要手动清除TSIF的标志位才能继续,这样也能减轻CPU的开销,毕竟不会频繁的进入中断。

    这就是整个触摸检测流程的介绍了,说白了这些配置就是为了帮助我们设置一下触摸的参数,整套触摸的配置流程其实就这下面的这个图而已,手册写的还是很清晰的!

    7.png


    第三触摸参数的读取
    1. //========================================================================
    2. // 函数: void TKSU_Interrupt(void)
    3. // 描述: 触摸按键中断。
    4. // 参数: none.
    5. // 返回: none.
    6. // 版本: VER1.0
    7. // 日期: 2021-02-01
    8. // 备注:
    9. //========================================================================
    10. void TKSU_Interrupt(void) interrupt 13
    11. {
    12.         u8        j;
    13.         j = TSSTA2;
    14.         if(j & 0x40)        //数据溢出, 错误处理(略)
    15.         {
    16.                 TSSTA2 |= 0x40;        //写1清零
    17.         }
    18.         if(j & 0x80)        //扫描完成
    19.         {
    20.                 j &= 0x0f;
    21.                 TK_cnt[j] = TSDAT;        //保存某个通道的读数
    22.                 TSSTA2 |= 0x80;        //写1清零
    23.                 read_cnt++;                //读次数+1, 用于延时或读键计数
    24.                 TK_TimeOut = 0;
    25.         }
    26. }
    复制代码


    可以看到我们之前测试的那个代码里的数据采集全部在中断里实现,首先读取TSSTA2寄存器判断是哪个中断源,是溢出中断不处理,是扫描完成中断的话把数据保存下来。TSSTA2的最高位是扫描完成的中断,次高位是溢出,最低四位是哪个通道,所以判断到最高位是1直接读取这个数据保存到这个通道的数组里就完成了。

    8.png

    9.png

    这个中断采集的函数我们给他稍稍的改写一下,变成这个样子:
    1. //========================================================================
    2. // 函数: void TKSU_Interrupt(void)
    3. // 描述: 触摸按键中断。
    4. // 参数: none.
    5. // 返回: none.
    6. // 版本: VER1.0
    7. // 日期: 2021-02-01
    8. // 备注:
    9. //========================================================================
    10. void TKSU_Interrupt(void) interrupt 13
    11. {
    12.         u8        j;
    13.         j = TSSTA2;
    14.         if(j & 0x40)        //数据溢出, 错误处理(略)
    15.         {
    16.                 TSSTA2 |= 0x40;        //写1清零
    17.         }
    18.         if(j & 0x80)        //扫描完成
    19.         {
    20.                 j &= 0x0f;
    21.                 TSSTA2 |= 0x80;        //写1清零
    22.                 TK_cnt[j] = TSDAT;        //保存某个通道的读数
    23.                 if( j==15 )
    24.                         B_ReadKeyOk=1;
    25.         }
    26. }
    复制代码

    这里增加了一个B_ReadKeyOk标志位,这个标志位是原来的工程里的,表示16个通道全部检测完成了,把这个标志位置1,看起来有用就把他带上了,这样我们就可以判断当前一轮循环结束了没有。其次这里读取完一次一定要记得手动清除TSSTA2 这个里的标志位!!当然这里每个通道的数据最终都会保存到TK_cnt的数组里。数组第几个元素就是第几个通道的ADC,当然这里用到了一个空中断的小技巧,可以看到这里中断向量号是13,至于为什么大家可以去看手册的这一张,里面有详细解读
    2.png


    第四步触摸数据的测试,
    上面一步理论来说我们已经能能够正确的读取到了触摸的数据,但是这个数据对不对我们是不是可以通过串口打印出来看下,上面我们已经调通了串口,调通的adc,那这里我们直接在while函数里把触摸的数据打印出来看看就好了,当然也可以吧之前的那个数码管显示的代码先屏蔽掉,直接册数adc,我们直接编写如下代码:
    1.         if( B_ReadKeyOk )
    2.         {
    3.             B_ReadKeyOk = 0;
    4.             printf("(%u,%u)\r\n",TK_cnt[0],TK_cnt[1]);
    5.         }
    6.         delay_ms(20);
    复制代码
    这里我们就简单的打印两路数据就好了,因为是循环采集的,两路数据没问题那别的应该也没有问题的,上面的代码写入主函数,然后我们开始编译下载:
    3.png
    这里我们就已经可以打印出当触摸通道0和触摸通道1的ADC数据了,但是这样看数据不是特别的直观,我们用绘图软件去看一下, 这里我用的RCOM,大家也可以用自己喜欢的,我用的这个软件的数据个数就是"(通道0数据,通道1数据)"就可以接收到数据了,我们先打开这个软件配置一下数据:
    4.png

    然后监控我们的数值曲线:
    5.png
    可以看到我们这个曲线图里面,红色的是我们通道0的数据,绿色的是通道1的数据,无论通道1或者通道2按下都会有明显的数值变化,数值基本和我们上一节测试的差值差不多,说明我们这一章的代码都是可以的,不过这里能看得出来通道0按下的时候,通道1的数据也会变小一点点。通道1按下的时候通道0的数值也会干扰,这个就是走线的时候,触摸的线和焊盘考的太近了的缘故,这个下一个板子可以修改一下,当然了从数值上分析误差只有几百,我们可以忽略不计,不影响我们对他的整个功能的调试~触摸和串口的驱动到这里就完美的结束了~



  • TA的每日心情
    开心
    2024-3-8 01:37
  • 签到天数: 1 天

    [LV.1]初来乍到

    18

    主题

    41

    回帖

    422

    积分

    中级会员

    积分
    422
    发表于 2023-3-17 20:51:31 | 显示全部楼层
    加油,等待最后成品,演奏一首歌曲:victory::victory:
  • TA的每日心情
    开心
    昨天 15:41
  • 签到天数: 68 天

    [LV.6]常住居民II

    31

    主题

    885

    回帖

    6272

    积分

    荣誉版主

    冲哥视频教程和各种开源资料QQ交流群884047237,可群

    积分
    6272
    QQ
     楼主| 发表于 2023-3-17 22:11:15 | 显示全部楼层

    触摸电子琴从无到有心得分享——基于STC8H4K64TL的电子琴
    九、触摸按键程序检测

    上面我们已经移植好了触摸按键的驱动函数,也已经看到了触摸按键按下的时候有了明显的曲线变化,但是我们还是不能检测出到底是哪一个按键被按下了。这个时候我们就需要编写我们的一个按键检测的一个程序,当然了,这里我们也可以像我们之前视频教程里提到的那种按键检测按下,松开,长按等等,我们这时候就直接把我们之前的几个函数给复制过来先。
    1. u16 Count[8] = {0,0,0,0,0,0,0,0};
    2. u8  LastState = 0;                                                //8位变量         b0=1,代表k0上一次按下过
    3. <blockquote>#define<span style="white-space:pre">        </span>KEY_NOPRESS<span style="white-space:pre">                </span>0<span style="white-space:pre">                </span>//按键未按下
    复制代码
    可以看到我们之前视频教程里的按键检测就是这些函数,当然啦,如果面前的你们对于这些函数不太熟练或者说看不懂是什么意思,可以去看一下我们这个《STC32G单片机视频开发教程》的第13集——建议多任务处理,里面有很详细的讲解,手把手的一步一步教你这个函数是怎么写出来的,这里因为篇幅有限,我就不再过多的赘述了。

    首先移植过来以后,我们需要先把第一个按键的个数给他加到16个。就是说count那个数组,我们要给它增加到16个元素,所以第一行就可以给他改写成这样,这里的话初始值也不需要写了
    1. u16 Count[16] ;
    复制代码
    然后LastState 那个变量也需要给它改成16位。
    1. u16 LastState = 0;
    复制代码
    我们来看一下这个代码里持续按下的检测,也就是下面一串代码中的蓝色部分,
    1. void KEY_Deal(void) //检查所有的按键状态 10ms执行一次
    2. {
    3. u8 i = 0;
    4. for(i=0;i<8;i++) //循环8次 i的取值范围是0-7 代表了P30-P37的状态查询
    5. {
    6. <font color="#00bfff">if( ~KEY & ( 1<<i ) ) //持续按下,变量+1</font>
    7. {
    8. if( Count[i]<60000 )
    9. Count[i] ++; //按键按下,这个计数变量+1
    10. }
    11. else //按键松开了,变量清0
    12. {
    13. if( Count[i]>0 ) //如果这个变量是按下过的
    14. {
    15. LastState |= (1<<i); //这个变量相应的标志位置位
    16. }
    17. else
    18. {
    19. LastState &= ~(1<<i); //这个变量相应的标志位清0
    20. }
    21. Count[i] = 0; //按键按下,这个计数变量清0
    22. }
    23. }
    24. }
    复制代码
    因为之前的按键是检测物理的按键,它只有高低电平,但是我们这个触摸按键需要去检测一个adc的数值,所以这个函数我们应该改写成这个样子。这个函数我们应该改写成
    1. //========================================================================
    2. // 函数名称: KEY_Deal
    3. // 函数功能: 按键状态读取
    4. // 入口参数: 无
    5. // 函数返回: 无
    6. // 当前版本: VER1.0
    7. // 修改日期: 2023 - 1-1
    8. // 当前作者:
    9. // 其他备注:循环读取八个端口的状态,并将按下的时间赋值给 Count 数组,按下的状态赋值给LastState
    10. //========================================================================
    11. void KEY_Deal(void)                        //检查所有的按键状态 10ms执行一次
    12. {
    13.         u8 i = 0;
    14.         for(i=0;i<<font color="#00ffff">16</font>;i++)                                        //循环8次 i的取值范围是0-7  代表了P30-P37的状态查询
    15.         {
    16. <font color="#00bfff">                if( TK_cnt[i]< (TK_zero[i]-T_KeyCmp[i]))                        //持续按下,变量+1  </font>
    17.                 {
    18.                         if( Count[i]<60000 )
    19.                                 Count[i] ++;                        //按键按下,这个计数变量+1
    20.                 }
    21.                 else                                                        //按键松开了,变量清0
    22.                 {
    23.                         if( Count[i]>0 )                        //如果这个变量是按下过的
    24.                         {
    25.                                 LastState |= (1<<i);        //这个变量相应的标志位置位
    26.                         }
    27.                         else
    28.                         {
    29.                                 LastState &= ~(1<<i);        //这个变量相应的标志位清0
    30.                         }
    31.                         Count[i] = 0;                                //按键按下,这个计数变量清0
    32.                 }
    33.         }
    34. }
    复制代码
    其中,TK_cnt这个数组是当前的触摸按键的adc数值,TK_zero是我们触摸按键没有按下时候的初始adc数值,KeyCmp就是我们比较的差值,当前的TK_cnt数值如果小于TK_zero减去KeyCmp,就说明我们的触摸已经被按下了,反之则已经松开了。
    那么这时候我们就需要通过程序去判定我们的TK_zero的数值,另外KeyCmp的数值我们已经通过上一次的那个上位机软件已经计算出来了,也就是我们之前测试的时候测出了14个通道的那个差值的那张图。当然了我,我上一次的图因为最小值都在5000左右,最大值有1万多。所以这里我就偷个懒,所有通道的KeyCmp数值都等于两千好了,所以得到初始化函数中需要编写如下代码
    1. for(i=0;i<16;i++)
    2.     {
    3.         T_KeyCmp[i] = 2000;
    4.     }
    复制代码
    当然因为这个数组没有定义过,所以要在头上对这个函数进行一下定义。
    1. u16 T_KeyCmp[16];
    复制代码



    然后就是我们TK_zero的数组的数值确定了,这个0点的数值的话,一般常见的方法就是直接初始化也就是刚刚上电的时候,把adc的数值直接读出来,写入数组。用这个初始的一个数组来作为我们的一个0点,那么我们可以编写如下的程序。
    1. delay_ms(100);
    复制代码
    这串代码其实看起来也非常的简单,就是说上电以后先延时100个毫秒,等待触摸adc的数值稳定。然后再循环的读取八次当前的adc数据,且每一次读取数值都需要等待adc已经完成全部通道的检测。然后我们再把当前通道的数值直接写入那个0点的数组。因为是八次的数据和,所以我们最后检测完成之后需要计算一下这个数组除以8的数据。但是这里一定要注意一个问题,就是说我们之前检测的时候,这触摸按键的这个adc数值大约都是1万多,我们如果直接累加八次,这个TK_zero的数组一定会超范围,所以我们这里需要注意一下,把这个数组的一个定义的类型给它改成u32。
    1. u32        xdata TK_zero[16];        // 0点读数
    复制代码

    这样改完之后基本就没问题了。我们就可以编写测试程序。把这个数据给他打印出来。我们就可以在while函数里面编写如下测试程序,我们屏蔽之前while函数里的所有程序,只保留如下部分。
    1.         if( B_ReadKeyOk )
    2.         {
    3.             B_ReadKeyOk = 0;
    4.             KEY_Deal();
    5.             for(i=0;i<16;i++)
    6.             {
    7.                 if( KEY_ReadState(i)==KEY_PRESS )
    8.                 {
    9.                     COM0_DAH = T_LED_CODE[i/10]>>8;
    10.                     COM0_DAL = T_LED_CODE[i/10];
    11.                     COM1_DAH = T_LED_CODE[i%10]>>8;
    12.                     COM1_DAL = T_LED_CODE[i%10];        
    13.                   printf("按钮%d按下\r\n",(u16)i);  
    14.                 }
    15.             }
    16.             //printf("(%u,%u)\r\n",TK_cnt[0],TK_cnt[1]);
    17.         }
    18.         delay_ms(20);
    复制代码
    这里的话其实就是很简单的,一方面是通过串口打印出当前是哪一个触摸的通道被按下了,另一方面就是说两位数码管显示出我们当前的一个触摸按键的通道号,那么这里代码已经编写完了,我们直接下载进去测试一下。
    1.png
    可以看到我们这个程序编写完并且下载完以后,我们这个窗口上就可以把我们每个通道的adc的一个0点的数值给他打印了出来,当然了,这个地方要注意的就是我们这个刚刚初始化的时候,手千万不要去触摸这个按键。这个是这个上电自适应0点的唯一的一个缺点,这个后面有时间,我把这个给完善一下,有一个0点追踪的办法可以修掉。或者说大家感兴趣的话,可以去看一下官网上的那个程序,里面其实就是做了这个处理的。

    2.png


    然后我们再来按几个按钮,是不是也能成功的读出这个按钮的序号,并且只有按下的一瞬间才会有效,不会循环的触发,这样就可以帮助我们快速的对后面的程序进行编写。那最后再来放上我们一个实物演示的一个效果的图。


    可以看到这个视频里面,每一个触摸按键按下,数码管都能显示出对应的通道号,不过我的手指头可能有点粗,按的时候因为一只手还端着手机里没有理想的那么好,另外就是手机的像素问题,导致可能数码管显示不是那么的齐全,实际上其实数码管显示非常的稳。


  • TA的每日心情
    开心
    昨天 15:41
  • 签到天数: 68 天

    [LV.6]常住居民II

    31

    主题

    885

    回帖

    6272

    积分

    荣誉版主

    冲哥视频教程和各种开源资料QQ交流群884047237,可群

    积分
    6272
    QQ
     楼主| 发表于 2023-3-18 14:51:14 | 显示全部楼层
    十、PWM程序

    上面的章节已经把触摸和LED驱动调的差不多了,那这一章我们就准备调PWM来模拟声音了,在开始之前,我们需要有一点点的常识,就是频率多少对应的是哪个音
    1.png

    上面一章网图,四个纵向的列分别是低音、中音、高音、超高音对应的频率,横向的就是每个音符对应的频率,比如说低音的Do的音符就是262HZ,占空比一般都是50%。

    再来看一下我们的原理图:

    2.png

    蜂鸣的引脚在P33上面,那就好说了,因为查阅手册发现,我们的这个引脚刚好在PWMA的通道4上面

    3.png

    说明我们可以直接用硬件的PWM去驱动它,产生它对应的一个频率。这种时候我一般都是用到什么功能教一下它对应的硬件,比如说这里需要一个PWM,一般就是用硬件PWM去产生,或者用时钟信号分频输出,或者PCA/CCP/PWMPM,既然这个引角刚刚好有PWM输出的功能,那这绝对就是最优解

    既然这里选择了用硬件PWM,那我们首先打开stc8的一个历程包,这个历程的话,可以去官网进行下载,下载STC8H的开天斧的代码就好了。可以看到开天斧的代码包里面有这么一个例程《10-高级PWM1-PWM2-PWM3-PWM4,驱动P2口呼吸灯实验程序》,这个刚好就是PWMA的通道四输出PWM的例子,那直接打开来看下里面的代码结构:

    4.png

    初始化里关于PWM输出的部分就这些,非常的简单,不过这里需要注意一个事情,我们的PWM的输出引脚在通道4的N通道上,也就是不开启P通道,才能吧正常的PWM输出到N通道上。关于这里PWM的每一个寄存器的描述,可以去看一下这篇帖子:https://www.stcaimcu.com/forum.php?mod=viewthread&tid=885&extra=

    因为篇幅比较多,这里就不在重复描述占篇幅了。这里我们直接把我们改写好的PWM函数给复制过来,最终的到如下的PWM初始化函数:

    1. void PWM_Init(u16 PWM_PERIOD)
    2. {
    3.     PWMA_CCER1 = 0x00; //写 CCMRx 前必须先清零 CCxE 关闭通道
    4.     PWMA_CCER2 = 0x00;
    5. //    PWMA_CCMR1 = 0x68; //通道模式配置
    6. //    PWMA_CCMR2 = 0x68;
    7. //    PWMA_CCMR3 = 0x68;
    8.     PWMA_CCMR4 = 0x68;
    9. //    PWMA_CCER1 = 0x50; //配置通道输出使能和极性
    10.     PWMA_CCER2 = 0x55;
    11.     PWMA_ARRH = (u8)(PWM_PERIOD >> 8); //设置周期时间
    12.     PWMA_ARRL = (u8)PWM_PERIOD;
    13.     PWMA_ENO = 0x00;
    14. //    PWMA_ENO |= ENO1P; //使能输出
    15. //    PWMA_ENO |= ENO1N; //使能输出
    16. //    PWMA_ENO |= ENO2P; //使能输出
    17. //    PWMA_ENO |= ENO2N; //使能输出
    18. //    PWMA_ENO |= ENO3P; //使能输出
    19. //    PWMA_ENO |= ENO3N; //使能输出
    20. //    PWMA_ENO |= ENO4P; //使能输出
    21.     PWMA_ENO |= ENO4N; //使能输出
    22.     PWMA_PS = 0x00;  //高级 PWM 通道输出脚选择位
    23. //    PWMA_PS |= PWM1_2; //选择 PWM1_2 通道
    24. //    PWMA_PS |= PWM2_2; //选择 PWM2_2 通道
    25. //    PWMA_PS |= PWM3_2; //选择 PWM3_2 通道
    26.     PWMA_PS |= PWM4_4; //选择 PWM4_2 通道
    27. <font color="#00ffff">    PWMA_CCR4H = (u8)((PWM_PERIOD+1) >> 8); //设置占空比时间
    28.     PWMA_CCR4L = (u8)(PWM_PERIOD+1);</font>
    29. <font color="#00ff00">//    PWMA_CCR4H = (u8)(((PWM_PERIOD+1)/2) >> 8); //设置占空比时间
    30. //    PWMA_CCR4L = (u8)(((PWM_PERIOD+1)/2));        </font>
    31.         
    32.     PWMA_BKR = 0x80; //使能主输出
    33.     PWMA_CR1 |= 0x81; //ARR预装载,开始计时        
    34. }
    复制代码
    上面的代码,蓝色的部分就是初始化的时候强制关闭PWM输出,绿色的代码是为了测试的时候,初始化直接输出50%占空比的PWM。当然测试的时候我们可以直接选择绿色的代码,输出PWM,这样我们初始化完就可以直接测试波形是不是正常了,话不多说开干,先把这个初始化函数写好,然后主函数添加这么一句

    1. PWM_Init(1023);
    复制代码
    编译提示我们有几个参数缺了,这边把原来工程的几个宏定义也复制过来:

    1. #define PWM1_1      0x00        //P:P1.0  N:P1.1
    2. #define PWM1_2      0x01        //P:P2.0  N:P2.1
    3. #define PWM1_3      0x02        //P:P6.0  N:P6.1
    4. #define PWM2_1      0x00        //P:P1.2/P5.4  N:P1.3
    5. #define PWM2_2      0x04        //P:P2.2  N:P2.3
    6. #define PWM2_3      0x08        //P:P6.2  N:P6.3
    7. #define PWM3_1      0x00        //P:P1.4  N:P1.5
    8. #define PWM3_2      0x10        //P:P2.4  N:P2.5
    9. #define PWM3_3      0x20        //P:P6.4  N:P6.5
    10. #define PWM4_1      0x00        //P:P1.6  N:P1.7
    11. #define PWM4_2      0x40        //P:P2.6  N:P2.7
    12. #define PWM4_3      0x80        //P:P6.6  N:P6.7
    13. #define PWM4_4      0xC0        //P:P3.4  N:P3.3
    14. #define ENO1P       0x01
    15. #define ENO1N       0x02
    16. #define ENO2P       0x04
    17. #define ENO2N       0x08
    18. #define ENO3P       0x10
    19. #define ENO3N       0x20
    20. #define ENO4P       0x40
    21. #define ENO4N       0x80
    复制代码
    在编译就通过了,我们下载进去之后,我们直接把示波器探头接到P33引脚上

    5.png


    可以看到输出的PWM频率是10.8K,因为主频是11.0592M,分频系数是10231,所以最终的理论PWM频率是:
    6.png

    可以看的出来这个频率还是很准的。然后我们后面输出PWM只需要改变这个分频系数就好了,那我们在编写一个函数来控制他输出不同频率的PWM,也就是改变ARR和CCR4寄存器的数值就好了,这里我们编写如下的修改PWM的函数:

    1. void PWM_Set(u16 PWM_PERIOD)
    2. {
    3.         if(( PWM_PERIOD==0 )||( PWM_PERIOD==0xffff ))
    4.         {
    5.                 PWMA_ARRH = (u8)(1024 >> 8); //设置周期时间
    6.                 PWMA_ARRL = (u8)1024;
    7.                
    8.                 PWMA_CCR4H = (u8)(((1024+1)) >> 8); //设置占空比时间
    9.                 PWMA_CCR4L = (u8)(((1024+1)));        
    10.         }
    11.         else
    12.         {
    13.                 PWMA_ARRH = (u8)(PWM_PERIOD >> 8); //设置周期时间
    14.                 PWMA_ARRL = (u8)PWM_PERIOD;
    15.                
    16.                 PWMA_CCR4H = (u8)(((PWM_PERIOD+1)/2) >> 8); //设置占空比时间
    17.                 PWMA_CCR4L = (u8)(((PWM_PERIOD+1)/2));        
    18.         }
    19. }
    复制代码
    这里就是判断PWM是否需要打开,写入0或者最大值我们就关闭PWM,否则打开pwm。

    最后我们通过表格计算出我们想要的PWM频率对应的PWM的分频系数;

    7.png

    首先这个原始的频率数值参考上面的图片,计算的这个PWM的分频系数的公司也在表格里有,这样就可以得到我们的每一个音阶对应的音符的频率了,我们吧这个生成的数据换成C语言的表格写入我们的程序,直接搞个二维数组:

    1. u16 code Sound_Fre[4][7]=          //s声音库
    2. {                           
    3.         0xA4E2        ,0x92F0        ,0x82E8        ,0x7BC8        ,0x6E34        ,0x622E        ,0x5773,
    4.         0x5299        ,0x4998        ,0x418D        ,0x3DE4        ,0x371A        ,0x3117        ,0x2BB9,
    5.         0x2942        ,0x24C4        ,0x2081        ,0x1EEC        ,0x1B8D        ,0x188B        ,0x15D7,
    6.         0x14A3        ,0x1264        ,0x1061        ,0x0F76        ,0x0DC6        ,0x0C45        ,0x0C07,
    7. } ;        
    复制代码
    最后我们在while函数里编写如下程序:
    1.         if( B_ReadKeyOk )
    2.         {
    3.             B_ReadKeyOk = 0;
    4.             KEY_Deal();
    5.             for(i=0;i<16;i++)
    6.             {
    7.                 if( KEY_ReadState(i)==KEY_PRESS )
    8.                 {
    9.                     COM0_DAH = T_LED_CODE[i/10]>>8;
    10.                     COM0_DAL = T_LED_CODE[i/10];
    11.                     COM1_DAH = T_LED_CODE[i%10]>>8;
    12.                     COM1_DAL = T_LED_CODE[i%10];        
    13.                                         printf("按钮%d按下\r\n",(u16)i);
    14.                                        
    15.                                         switch(i)                //音节档位切换
    16.                                         {
    17.                                                 case 15:Sound = 1;break;
    18.                                                 case 10:Sound = 2;break;
    19.                                                 case 11:Sound = 3;break;
    20.                                                 case 00:Sound = 4;break;
    21.                                                 case 01:Sound = 5;break;
    22.                                                 case 04:Sound = 6;break;       
    23.                                                 case 05:Sound = 7;break;
    24.                                                 case 12:
    25.                                                 case 13:
    26.                                                 case 14:                                                       
    27.                                                         Sound = 0;break;                                               
    28.                                         }               
    29.                                         if(Sound>0  )
    30.                                                 PWM_Set(Sound_Fre[0][Sound-1]);
    31.                                         else               
    32.                                                 PWM_Set(0);
    33.                 }
    34.             }
    35.             //printf("(%u,%u)\r\n",TK_cnt[0],TK_cnt[1]);
    36.         }
    37.         delay_ms(20);
    复制代码

    这里就是在上一章 的内容山增加了触摸按键的功能。通道15、10、11、0、1、4、5这七个分别是1-7的音符对应的按键,左边三个这里就简单的给他设置为关闭按钮,按下左边的任意一个就可以关闭PWM输出,这样我们最最最简单的一个发音的功能就完成了,但是这里默认这播放低音,当然7个按钮按下,示波器测量频率完全正确,那么这里最简单的一个小工程就结束了,来简单的看看效果



    虽然暂时还不是特别好听,但是小的驱动基本调的差不多了,电路上还有LED和功放的芯片还没到,还没办法接着往下了,先停更两天,这边先把原理图和当前的程序开源给大家,可以感兴趣的也可以自己找硬件试一下,或者等我最终版调试完在开放最终的源文件。






    电子琴代码.rar

    656.84 KB, 下载次数: 70

    piano.pdf

    635.05 KB, 下载次数: 83

    原理图

  • TA的每日心情
    开心
    昨天 15:41
  • 签到天数: 68 天

    [LV.6]常住居民II

    31

    主题

    885

    回帖

    6272

    积分

    荣誉版主

    冲哥视频教程和各种开源资料QQ交流群884047237,可群

    积分
    6272
    QQ
     楼主| 发表于 2023-3-18 14:56:47 | 显示全部楼层
    1.png




    借一楼单独的插入一下一些小细节

    1.功能暂定如上几个,还有什么好的功能推荐记得留言
    2.电路增加串口的2.54插针,增加P33,P34的测试焊点,修改触摸走线,修改晶振位置,(板子要不要改大一点)

    回复 支持 1 反对 0 送花

    使用道具 举报

  • TA的每日心情
    奋斗
    2024-1-16 15:48
  • 签到天数: 1 天

    [LV.1]初来乍到

    6

    主题

    25

    回帖

    156

    积分

    注册会员

    积分
    156
    发表于 2023-3-22 11:21:03 | 显示全部楼层
    来看看进度,期待更新:lol
    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

    GMT+8, 2024-3-29 06:44 , Processed in 0.110229 second(s), 74 queries .

    Powered by Discuz! X3.5

    © 2001-2024 Discuz! Team.

    快速回复 返回顶部 返回列表