数码之家

 找回密码
 立即注册
搜索
查看: 1820|回复: 21

[C51] 触摸电子琴从无到有心得分享——基于STC8H4K64TL(1)

[复制链接]
头像被屏蔽
发表于 2023-3-21 11:19:02 | 显示全部楼层 |阅读模式

爱科技、爱创意、爱折腾、爱极致,我们都是技术控

您需要 登录 才可以下载或查看,没有账号?立即注册

x

序言
最近看到STC多了一款LED+触摸的一款,想起了之前看到的单片机电子琴(下图为网图,如有侵权,请联系作者删除),心血来潮,当然以前我也做过几个,什么按键的啊,激光的等等,决定给以前做的来个升级板,结合这个芯片现有的资源,做一款有趣的电子琴,一方面也是为了学习新知识,方便日后用它做项目,另一方面也是为了回馈广大网友。
3.21-1.png
一、需求分析:
3.21-2.png
可以看到STC这个芯片的内部资源如上图所示:主要看中了他触摸驱动和数码管直驱功能,看了眼官网具体的功能介绍,居然还看到了他有现成的触摸演示
3.21-3.png
其次还在官网看到了现成的原理图,天助我也都有现成的最小系统和测试原理图了,这都不用就是暴遣天物哈哈。
3.21-4.png
他提供的原理图如下:
3.21-5.png
可以看到他的主控就是我们的芯片,刚刚好一模一样,那么我就不客气了,这个TYPEC,断电电路,最小系统图我都不客气了,直接白嫖了。然后他还有16路触摸,8*4位的数码管(内心:我的天,这这这为什么一会儿共阴一会儿共阳的,这个画板子的人怕不是有毒,乱画。仔细一想,不对,姚总天天在宣传的图,这要是有问题那还得了)然后去看了一眼手册,然后见到了这么一句:
3.21-6.png
好吧,确实是我格局小了,真的没有我做不到,只有我想不到。第一次看到又能共阴共阳驱动的LED驱动,那种岂不是更美了哈哈,悠闲地引脚创造无限的可能,结合他现有的资源,我暂定给他做如下的功能:(我真是一个合格的产品经理哈哈!)
1.板子尺寸10*10以内(白嫖免费打样~)
2.5v供电,使用typec口并能使用typec直接下载程序(写着可以模拟USB下载,我得试试~物尽其用!)
3.使用7个触摸按键作为1-7的音符,3个触摸键作为档位切换(低音,中音,高音),4个触摸键作为模式/设置/切换/选择按键,还有两个作为备用(他们还是RTC时钟的晶振引脚,先预留,到时候不焊了)
4.一个PWM输出口驱动蜂鸣器,(最廉价的用蜂鸣器驱动它
5.三个4位数码管(一个显示时间,一个显示音符和档位,一个显示模式啥的,三个四位数码管绰绰有余,虽然有点不甘心还有那么多数码管位没放上去,但是想着放上去板子也放不下,就释怀了哈哈)
6.7*4个LED显示当前音阶,16个LED指示当前触摸按键(既然这个LED驱动能带数码管,那带LED不也分分钟的~毕竟数码管就是LED做出来的)
7.RTC时钟电路(显示个时间不也很好)
8.有空间的话加一个pwm转DAC电路+功放和W25Q128的芯片(追求更好的音质和更大的存储空间)
二、原理图设计
好了需求也有了,这不就可以开始折腾原理图了~
综合上述需求,原理图暂时就先这样子!!

3.21-7.png
电源下载/蜂鸣/断电电路/MCU都是抄的官方的(内心OS:抄官方的坑定不会有问题了吧,这要是有问题我得去他们那里让出这图的人请我吃饭赔罪!!!)然后数码管什么的也是官方的图上改编过来的,加个pwm转dac和功放,当然这个PWM转DAC也是直接抄的手册的嘿嘿,既然有现成的,不用白不用,省的自己动脑子了哈哈。看起来很简单清爽,这要换别的MCU还得一大堆的触摸芯片或者一大堆的ADC模拟触摸按键,太麻烦了我才不干。
好了,废话不多说,发别人画板子去咯~(毕竟高端的工程师能简单点就简单点,有现成的不用白不用~)顺便问我的销售要了两个样品嘿嘿,销售一如既往的支持,二话不说就给我安排上了样品,再次特别感谢我的销售经理吴某某和聂某哈哈,也感谢STC和姚总的支持。

三、PCB绘制
经过了漫长的等待,PCB总算画完了发回来了,PCB整个板子外形如下:
3.21-8.png
乍一看是不是还不错~我也觉得不错。下面一排触摸作为音符选择,左边作为下载口和音阶选择,右边是模式按键,上面两个备用。
最顶上是一排数码管,每个触摸边上都有LED,这样可以直接指示触摸的状态,每个触摸都可以焊一个触摸弹簧,
最上面再盖上一块亚克力板子,简直不要太好。
3.21-9.png
走线也还算清爽,当然了也有一些美中不足,像触摸的走线,铺铜(这个就不放出来了,有点不雅观)等等还能优化,电源那一块还能优化。算了,能用就行,我先打一版出来先调程序。(这里很多人会想着一步到位,一次画完。但是以我多年项目经验下来这个基本不可能,尤其是新开的项目坑定会改的,所以第一版可以先做下去,程序先调起来,然后硬件在慢慢的优化整改),这里板子也画完了,那么下面就可以发出去打样咯(期待),可惜我的免费打样用完了,还是出了20块钱的打样费。




头像被屏蔽
 楼主| 发表于 2023-3-21 11:43:20 | 显示全部楼层
四、焊接测试
经过漫长的等待,板子终于回来了。看着也还挺好看,不过就是稍稍小了点,不过影响哈哈。当然板子回了,就可以开始焊接测试了。这时候很多人一拿到板子就会直接开始焊接,然后一口气全部焊完,当然我一般都是遵循,电源->最小系统->从左到右的方式开始焊接。
先焊接电源部分,焊接好了测试通电电源没有问题了,在焊接单片机的最小系统,当然不出意外这里果然还是出问题了,如下两部分焊接完成,测试VCC居然没电压,且LED灯也不亮,好家伙,最后通过万用表测量SB1204的mos边上的电压,发现电不能通过这个mos,和屠龙刀一比对,封装的引脚错了~然后经过引脚一对比,线路板就改成了这个样子~
这里直接给MOS翻了个身,这样输入引脚就怼上了,然后输出和控制脚直接灵魂飞线,考虑到这个电流也不大,那就直接裸露,散热片也先不加了嘿嘿,这样改完一通电,果然LED亮起来了,测量单片机的电源脚,电源也正确了,那么就可以先下载下程序开始测试了
3.21-11.png
五、程序下载

刚刚已经焊接好了最小系统,那就可以直接下载程序了,这里我一般会先下载完程序在焊接别的。为什么呢,这时候要是焊完了,最后下载不进去都不知道是不是后级干扰了还是怎么滴了,建议刚开始就用最小系统去下载程序,然后边焊接边编写测试的程序,这样可以把出现问题的可能性降到最低,会更方便查找问题。
记得这个型号可以直接USB模拟下载程序,我们去手册看下资料(stc所有你能想到的资料,手册都有,那就第一时间去看手册)
首先是手册这里有电路图,简单看了下和我的一模一样:

3.21-12.png
接着往下看,有操作方法:
3.21-13.png
这里随便找了个程序,单击下载!
3.21-14.png
下载成功!









回复 支持 反对

使用道具 举报

发表于 2023-3-21 11:54:19 | 显示全部楼层
这按键布局能弹的怕是有点对自己残忍了,仅仅是为了白嫖人家pcb打样
回复 支持 反对

使用道具 举报

发表于 2023-3-22 09:15:06 | 显示全部楼层
STC也带触摸功能了,真的是越来越强大了!支持国产,支持STC
回复 支持 反对

使用道具 举报

发表于 2023-3-22 09:20:36 | 显示全部楼层
顶,来学习了,期待成品期待开源
回复 支持 反对

使用道具 举报

头像被屏蔽
 楼主| 发表于 2023-3-24 09:03:20 | 显示全部楼层
六、数码管调试
紧接着上面,我们已经把下载部分都测试完成了,那么今天我们开始下一个部分的测试,首先我们先把硬件电路焊起来(由于一点点小失误,数码管只买了个共阳的,共阴的还在购物车里躺着,很尴尬,已经加急买下去了),最后数码管焊完之后成品如下(新焊接的部分如图红圈部分所示,包含原理图上的限流电阻和数码管部分,都是手册推荐的规格):

硬件部分焊完了,然后找个驱动代码测试一下,看看能不能点亮先。既然之前直接down的官网的原理图,那就再去把官网的那个配套的程序也下下来用着:

因为考虑到我们的板子和官方的板子引脚也用的差不多,那直接历程下载进去应该就能直接用,话不多说,直接冲,反正下载程序下错了既不会死机,也不会炸机,下就完了,下载完之后,数码管显示了这:


可以看到我们的程序部分如上,既然工程名称叫“16个触摸按键16个灯-8个共阴-8个共阳数码管显示”,那么推测程序就是一半灯驱动,一半触摸驱动,秉承着这个思路,我们开始分析程序,就看main开始的部分。
void main(void)
{
        u8        i;
        u16        j;


        P_SW2 |= 0x80;        //允许访问XSFR(扩展特殊功能寄存器)


//        XOSCCR = 0xc0;           //启动外部晶振
//        while (!(XOSCCR & 1));   //等待时钟稳定
//        CLKDIV = 0x00;           //时钟不分频
//        CKSEL = 0x01;            //选择外部晶振


        P0M0 = 0x00;
        P0M1 = 0x00;


<font color="#ff0000">        P2n_push_pull(0xff);        //COM0~COM7线设置为推挽输出
        P4n_push_pull(0xff);        //SEG0~SEG7(P4.0~P4.7)线设置为推挽输出
        P3n_push_pull(0xf0);        //SEG12~SEG15(P3.4~P3.7)线设置为推挽输出


        P2DR = 0x00;        //COM0~COM7线设置为强电流驱动
        P4DR = 0x00;        //SEG0~SEG7线设置为强电流驱动
        P3DR = 0x0f;        //SEG12~SEG15线设置为强电流驱动


        COMEN  = 0xff;        //允许8个COM
        SEGENL = 0xff;        //允许8个SEG 低字节
        SEGENH = 0xf0;        //允许8个SEG 高字节
        LEDCTRL = 0x80 + (2<<4) + 0;        //B7=1: ON,  B5 B4: 0共阴, 1共阳, 2共阴共阳, 3保留, B2 B1 B0: 0占空比100%, 1~7占空比7/8~1/8
        LEDCKS = 8;                //数码管COM扫描显示的主频时钟数 = 160*9*LEDCKS, 每个COM显示时间=1440*LEDCKS/fosc(MHz) us, COM扫描频率=fosc/(1440*LEDCKS *COM数).
                                        //例如 fosc = 11.0592MHz, 8个COM, LEDCKS = 8, 则每个COM显示时间=1440*8/11.0592 = 1042 us, COM扫描频率=11059200/(1440*8*8)=120Hz.
                                        //已知扫描时间t(us)计算 LEDCKS = t*fosc(MHz)/1440, 比如fosc = 24MHz, 要求1个COM扫描1ms, 则 LEDCKS = 1000*24/1440 = 16.7, 取整数17.


        for(i=0; i<8; i++)
        {
                LoadLED_A(i,i);                //共阳,显示01234567
                LoadLED_B(i,i+8);        //共阳,显示89ABCDEF
        }
        COM4_DAH_tmp = 0;
        COM5_DAH_tmp = 0;
        COM6_DAH_tmp = 0;
        COM7_DAH_tmp = 0;</font>




<font color="#9acd32">        P1n_pure_input(0xff);        //Touch Key设置为高阻
        P5n_pure_input(0x0f);
        P0n_pure_input(0x0f);


//        TSCHEN = 0xffff;        //TK0~TK15
        TSCHEN1 = 0xff;                //TK0~TK7
        TSCHEN2 = 0xff;                //TK8~TK15
        TSCFG1  = (7<<4) + 6;        //开关电容工作频率 = fosc/(2*(TSCFG1[6:4]+1)), 放电时间(系统时钟周期数) 0(125) 1(250) 2(500) 3(1000) 4(2000) 5(2500) 6(5000) 7(7500) 最小3
        TSCFG2  = 1;                //配置触摸按键控制器的内部参考电压(AVCC的分压比), 0(1/4)  1(1/2)  2(5/8)  3(3/4)
//        TSCTRL = (1<<7) + (1<<6) +3;        //开始扫描, B7: TSGO,  B6: SINGLE,  B5: TSWAIT, B4: TSWUCS, B3: TSDCEN, B2: TSWUEN, B1 B0: TSSAMP
//        TSRT = 0x00;                //没有LED分时扫描
        IE2 |= 0x80;                //允许触摸按键中断
        EA = 1;


        delay_ms(50);
//        B_TK_Lowpass = 0;        //禁止低通滤波
        B_TK_Lowpass = 1;        //允许低通滤波
        for(read_cnt=0; read_cnt<40; read_cnt++)                //读40次键, 将此值作为未触摸时的0点, 要求上电时不要触摸按键
        {
        //        TSCTRL = (1<<7) + (1<<6) +3;        //开始扫描, 4次平均, 读数大约为无平均的一半
                TSCTRL = (1<<7) + (1<<6) +1;        //开始扫描, 2次平均, 读数大约为无平均的一半
        //        TSCTRL = (1<<7) + (1<<6);                //开始扫描, 只转换1次, 无平均
                B_ReadKeyOk = 0;
                for(i=0; i<100; i++)
                {
                        if(B_ReadKeyOk)        break;
                        delay_ms(1);
                }
        }
        for(i=0; i<16; i++)                TK_zero = TK_cnt;        //保存0点
        
        B_TK_Lowpass = 1;        //允许低通
        KeyState = 0;
        read_cnt = 0;</font>
        
<font color="#9acd32">        B_ReadKeyOk = 0;
        KeyValue = 10;
        KeyCode = 0;
        ShowKey();</font>
        
        while (1)
        {
<font color="#dda0dd">                delay_ms(1);
               
                if(++TrigLimit >= 100)        //触发转换
                {
                        TrigLimit = 0;
                //        TSCTRL = (1<<7) + (1<<6) +3;        //开始扫描, 4次平均, 读数大约为无平均的一半
                        TSCTRL = (1<<7) + (1<<6) +1;        //开始扫描, 2次平均, 读数大约为无平均的一半
                //        TSCTRL = (1<<7) + (1<<6);                //开始扫描, 只转换1次, 无平均
                }
               
                if(B_ReadKeyOk)                        // 16个键都转换完毕
                {
                        B_ReadKeyOk = 0;
                        TrigLimit = 100;
                        ShowValue(KeyValue);        //显示读数


                        j = KeyState;                //读入上一次键状态
                        for(i=0; i<16; i++)
                        {
                                if(TK_zero > TK_cnt)        //计算与0点的差值
                                {
                                        TK_zero--;        //缓慢0点跟随
                                                 if((TK_zero - TK_cnt) >= T_KeyPress/2)        KeyState |=  T_KeyState;        // 大于按键读数变量的1/2就是按下

                                        else if((TK_zero - TK_cnt) <= T_KeyPress/3)        KeyState &= ~T_KeyState;        // 小于按键读数变量的1/3就是释放
                                }
                                else
                                {
                                        KeyState &= ~T_KeyState;
                                        if((TK_cnt - TK_zero) > 100)        TK_zero += 50;        //差别很大, 则快速回0点
                                        else                                                                TK_zero += 10;        //差别不大, 则慢速回0点
                                }
                        }
                        j = (j ^ KeyState) & KeyState;        //检测键是否按下
                        if(j != 0)
                        {
                                KeyCode = CheckKey(j);        //计算键码 1~16
                                i = (u8)j;
                                COM4_DAH_tmp ^= (i << 4);
                                COM5_DAH_tmp ^= (i & 0xf0);
                                i = (u8)(j >> 8);
                                COM6_DAH_tmp ^= (i << 4);
                                COM7_DAH_tmp ^= (i & 0xf0);
                                
                                COM4_DAH = COM4_DAH_tmp;
                                COM5_DAH = COM5_DAH_tmp;
                                COM6_DAH = COM6_DAH_tmp;
                                COM7_DAH = COM7_DAH_tmp;
                                
                                ShowKey();
                                if(KeyCode == 15)
                                {
                                        KeyValue--;                        //显示数值的键号-1
                                        KeyValue &= 0x0f;
                                        ShowValue(KeyValue);        //显示读数
                                }
                                if(KeyCode == 16)
                                {
                                        KeyValue++;                        //显示数值的键号+1
                                        KeyValue &= 0x0f;
                                        ShowValue(KeyValue);        //显示读数
                                }
                        }
                }</font>
        }

}

我们通过注释和函数的名称,变量名称等,我们把程序分为如上三块,红色的部分就是数码管(LED)驱动,绿色的部分就是触摸的驱动,最后while里面那个紫不紫,粉不粉(暂且叫他粉紫色,或者有谁知道这个具体叫啥的下边留言告诉我下)的就是数码管和触摸的组合的用户功能,那我们现在只测数码管,先把上面绿色和粉紫色的代码屏蔽掉,在下载进去看下效果如下:

这时候可以看到数码管显示贼稳了,说明这个代码基本可以用,给官方点个赞undefined剩下的小细节我们就可以慢慢的通过测试对比然后修改了。

回复 支持 反对

使用道具 举报

发表于 2023-3-27 08:45:35 | 显示全部楼层
妥妥的干货啊,膜拜学习!
回复 支持 反对

使用道具 举报

发表于 2023-3-29 09:51:32 来自手机浏览器 | 显示全部楼层
疯狂的兔子来了 发表于 2023-3-21 11:43
四、焊接测试
经过漫长的等待,板子终于回来了。看着也还挺好看,不过就是稍稍小了点,不过影响哈哈。当然 ...

冲哥好猛呀
回复 支持 反对

使用道具 举报

头像被屏蔽
 楼主| 发表于 2023-3-30 08:50:41 | 显示全部楼层
本帖最后由 疯狂的兔子来了 于 2023-3-30 13:50 编辑

触摸电子琴从无到有心得分享——基于STC8H4K64TL的电子琴
视频太大,上传不了,有兴趣的可以官网下载查看:
image.png


回复 支持 反对

使用道具 举报

发表于 2023-3-30 20:25:00 | 显示全部楼层


冲哥这个开源的项目,完美的展示了 1T 8051, STC8H4K64TL-45MHz-LQFP48的:
1,STC-MCU自带大电流LED数码管自动刷新,最多 32个 8段数码管
2,STC-MCU最多支持16个触摸按键
3,STC-MCU自带 RTC 时钟,年月日时分秒
4,8051的PWM唱歌
image.png
回复 支持 反对

使用道具 举报

头像被屏蔽
 楼主| 发表于 2023-3-31 09:00:26 | 显示全部楼层
本帖最后由 疯狂的兔子来了 于 2023-3-31 09:01 编辑

七、数码管的显示调试

  • u8 code T_LED_CODE[]={                                                //标准字库
  • //代码            0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0A 0x0B 0x0C 0x0D 0x0E 0x0F
  • //显示字符   0    1    2    3    4    5    6    7    8    9    A    B    C    D    E    F
  •             0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,
  • //代码            0x10 0x11 0x12 0x13 0x14 0x15 0x16 0x17 0x18 0x19 0x1A 0x1B 0x1C 0x1D 0x1E 0x1F
  • //显示字符   0.   1.   2.   3.   4.   5.   6.   7.   8.   9.   空   -
  •             0xBF,0x86,0xDB,0xCF,0xE6,0xED,0xFD,0x87,0xFF,0xEF,0x00,0x40};

[color=rgb(51, 102, 153) !important]复制代码

可以看到原来的代码里有这么一串代码,明显就是一个调用数组可以再数码管上显示一些字符的码表,但是我们的引脚SEG排列和他不一样,那么我们得重新设置我们的排列的码表,这里我们沿用上一个的表格,直接在线生成:

                               
登录/注册后可看大图

利用EXCEL我们很方便你的就弄好了这个码表,然后我们把它复制到程序里,并最终整合整和为了这个数组来代替远来的:

  • u16 code T_LED_CODE[]=          //标准字库
  • {
  • //代码            0x00    0x01    0x02    0x03    0x04    0x05    0x06    0x07    0x08    0x09    0x0A    0x0B    0x0C    0x0D    0x0E    0x0F
  • //显示字符   0       1       2       3       4       5       6       7       8       9       A       B       C       D       E       F
  •             0xE034, 0x4004,0xE050,  0xE044, 0x4064, 0xA064, 0xA074, 0x6004, 0xE074, 0xE064, 0x6074, 0x8074, 0x8050, 0xC054, 0xA070, 0x2070
  • //代码            0x10    0x11    0x12    0x13    0x14    0x15    0x16    0x17    0x18    0x19    0x1A    0x1B
  • //显示字符   0.      1.      2.      3.      4.      5.      6.      7.      8.      9.      空       -
  •             0xE0B4, 0x4084, 0xE0D0, 0xE0C4, 0x40E4, 0xA0E4, 0xA0F4, 0x6084, 0xE0F4, 0xE0E4, 0x0000, 0x0040
  • }


[color=rgb(51, 102, 153) !important]复制代码

顺便把这个数组放到工程前面去,我喜欢把数组放前面,方便查看和修改。最后变成了这样


                               
登录/注册后可看大图

这个数组一共有26个元素,那为了验证一下准确性,我们在while里编写一个简单的测试,让这个26个字符循环显示一下看看,直接在while函数编写如下代码:

  • COM0_DAH = T_LED_CODE>>8;
  •         COM0_DAL = T_LED_CODE;
  •         delay_ms(250);delay_ms(250);delay_ms(250);delay_ms(250);
  •         i++;
  •         if(i>25)
  •             i=0;


[color=rgb(51, 102, 153) !important]复制代码

上面的代码非常的简单,第一行先把高八位的数据DA_H传送到COM0_DAH 这个寄存器里,第二行先把低八位的数据DA_L传送到COM0_DAL这个寄存器里,第三行简单的4个delay_ms(250);总共延时一秒钟,第5-7行就是让他这个变量从0-25循环自加。是不是非常的简单~(当然如果对这个数码管的显示取模不太懂的话,可以去看官方的STC32教学视频第九集,里面讲了怎么取模,方法一致的哈~)最后我们来看一下实物的演示效果:
image.png
这样是不是就很nice了,后面想显示啥就显示啥,很nice~附件就是我们用到的计算数码管码表的表格,内含公式等待,有兴趣可以下载下来看看

回复 支持 反对

使用道具 举报

头像被屏蔽
 楼主| 发表于 2023-3-31 09:45:05 | 显示全部楼层
触摸电子琴从无到有心得分享——基于STC8H4K64TL的电子琴
八、触摸调试
终于要来到全贴最紧张刺激的时候了,我们要开始触摸部分的调试了,最早看中这颗芯片最主要的就是看中了触摸,因为之前做过好多项目都需要用到触摸,那这里也算是为了以后的项目做个铺垫,那么首先我们先把触摸部分焊接起来。

                               
登录/注册后可看大图

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


                               
登录/注册后可看大图

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


                               
登录/注册后可看大图

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


                               
登录/注册后可看大图


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


                               
登录/注册后可看大图

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

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


                               
登录/注册后可看大图

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


                               
登录/注册后可看大图

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


                               
登录/注册后可看大图

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


                               
登录/注册后可看大图

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


                               
登录/注册后可看大图

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


                               
登录/注册后可看大图




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


                               
登录/注册后可看大图

话不多说,直接测试:


                               
登录/注册后可看大图


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


                               
登录/注册后可看大图

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

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


                               
登录/注册后可看大图


                               
登录/注册后可看大图

结合硬件的触摸引脚,我们吧所有的触摸焊盘对应的硬件的引脚给他标注出来了,可以看到这里T几就是表示触摸的第几个通道,上面选择的TK00就是这个T0,也就是下面一排的第四个触摸焊盘,这时候我们去按一下这个触摸的弹簧试试。可以看到这里所有的这个按钮,会有一个肉眼可见的ADC数值变低的反应,那么说明这个触摸按键画的还是可以的,能用~当然这里按下几次过后,我们可以按一下获取差值的按钮,这样ADC的差值数据就出来了,后面程序我们就可以根据这个差值去处理了~那么剩下的几个通道我们都可以这样去测试,然后获取到全部通道的差值


                               
登录/注册后可看大图

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


                               
登录/注册后可看大图


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

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

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

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

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

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


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







回复 支持 反对

使用道具 举报

头像被屏蔽
 楼主| 发表于 2023-3-31 09:47:35 | 显示全部楼层
触摸电子琴从无到有心得分享——基于STC8H4K64TL的电子琴
八、触摸程序编写

上面的章节我们已经测试过触摸没有问题了,并且也找到了一个比较好用的触摸程序,那么现在话不多说先把他的触摸程序移植过来,他的就是我的,我的也还是我的!
首先打开上个章节用过的那个代码,

                               
登录/注册后可看大图

前面几行是变量,IO等的初始化,这些都一样,这里先不管他,我们把他最终要的触摸的功能(红色框)复制到我们的代码里,绿色框他是串口的,我们也可以复制过来帮助我们调试,蓝色框他是触摸变量用到的初始,他这个功能比较强大,还加了零点追踪的功能,这里先给大家用最简单的办法,先不考虑这么复杂的功能,先开始移植

第一步,先开始移植串口
  • //========================================================================
  • // 函数: void        UART1_config(u32 clk, u32 brt)
  • // 描述: UART1初始化函数。
  • // 参数: clk: 系统主频.
  • //       brt: 通信波特率.
  • // 返回: none.
  • // 版本: VER1.0
  • // 日期: 2021-7-29
  • // 备注:
  • //========================================================================
  • void        UART1_config(u32 clk, u32 brt)
  • {
  •         brt = 65536UL - (clk / 4) / brt;
  •         TR1 = 0;
  •         AUXR &= ~0x01;                //S1 BRT Use Timer1;
  •         AUXR |=  (1<<6);        //Timer1 set as 1T mode
  •         TMOD &= ~(1<<6);        //Timer1 set As Timer
  •         TMOD &= ~0x30;                //Timer1_16bitAutoReload;
  •         TH1 = (u8)(brt >> 8);
  •         TL1 = (u8)brt;
  •         ET1 = 0;                        // 禁止Timer1中断
  •         INTCLKO &= ~0x02;        // Timer1不输出高速时钟
  •         TR1  = 1;                        // 运行Timer1
  •         P_SW1 &= 0x3f;
  •         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
  •         SCON = (SCON & 0x3f) | (1<<6);        // 8位数据, 1位起始位, 1位停止位, 无校验
  • //        PS  = 1;        //高优先级中断
  •         ES  = 1;        //允许中断
  •         REN = 1;        //允许接收
  •         B_TX1_Busy = 0;
  •         RX1_Cnt = 0;
  • }
  • //========================================================================
  • // 函数: void UART1_TxByte(u8 dat)
  • // 描述: 串口1发送一个字节函数
  • // 参数: dat: 要发送的数据.
  • // 返回: none.
  • // 版本: VER1.0
  • // 日期: 2018-4-2
  • // 备注:
  • //========================================================================
  • void UART1_TxByte(u8 dat)
  • {
  •         B_TX1_Busy = 1;                        //标志发送忙
  •         SBUF = dat;                                        //发一个字节
  •         while(B_TX1_Busy);        //等待发送完成
  • }
  • //========================================================================
  • // 函数: void UART1_PrintString(u8 *puts)
  • // 描述: 串口1字符串打印函数
  • // 参数: puts: 字符串指针.
  • // 返回: none.
  • // 版本: VER1.0
  • // 日期: 2018-4-2
  • // 备注:
  • //========================================================================
  • void UART1_PrintString(u8 *puts)
  • {
  •         for (; *puts != 0;        puts++)
  •         {
  •                 UART1_TxByte(*puts);
  •         }
  • }
  • //========================================================================
  • // 函数: void UART1_int (void) interrupt UART1_VECTOR
  • // 描述: 串口1中断函数
  • // 参数: none.
  • // 返回: none.
  • // 版本: VER1.0
  • // 日期: 2018-4-2
  • // 备注:
  • //========================================================================
  • void UART1_int (void) interrupt UART1_VECTOR
  • {
  •         if(RI)
  •         {
  •                 RI = 0;
  •                 if(RX1_Cnt >= CMD_LEN) RX1_Cnt = 0;
  •                 RX1_Buffer[RX1_Cnt] = SBUF;
  •                 RX1_Cnt++;
  •                 RX1_TimeOut = 5;
  •         }
  •         if(TI)
  •         {
  •                 TI = 0;
  •                 B_TX1_Busy = 0;
  •         }
  • }

[color=rgb(51, 102, 153) !important]复制代码


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

[color=rgb(51, 102, 153) !important]复制代码


这边串口接收函数我们也暂时不用,所以串口中断里接收的部分我们也给他全都屏蔽掉,如下蓝色部分屏蔽掉,
  • //========================================================================
  • // 函数: void UART1_int (void) interrupt UART1_VECTOR
  • // 描述: 串口1中断函数
  • // 参数: none.
  • // 返回: none.
  • // 版本: VER1.0
  • // 日期: 2018-4-2
  • // 备注:
  • //========================================================================
  • void UART1_int (void) interrupt UART1_VECTOR
  • {
  •         if(RI)
  •         {
  •                 RI = 0;
  • <font color="#00bfff">//                if(RX1_Cnt >= CMD_LEN) RX1_Cnt = 0;
  • //                RX1_Buffer[RX1_Cnt] = SBUF;
  • //                RX1_Cnt++;
  • //                RX1_TimeOut = 5;</font>
  •         }
  •         if(TI)
  •         {
  •                 TI = 0;
  •                 B_TX1_Busy = 0;
  •         }
  • }

[color=rgb(51, 102, 153) !important]复制代码


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

[color=rgb(51, 102, 153) !important]复制代码


然后重定向一下串口的函数,也就是添加如下的代码
  • char putchar(char dat)
  • {
  •     UART1_TxByte(dat);
  •     return dat;
  • }


[color=rgb(51, 102, 153) !important]复制代码


这样我们的工程就可以使用printf函数了,最后我们在main函数初始化串口完成之后加上下面这句,使能一下总中断
  • EA = 1;

[color=rgb(51, 102, 153) !important]复制代码

最后我们在while函数之前编写如下代码就完成了:
  • printf("基于STC8H4K64TL的触摸电子琴\r\n");

[color=rgb(51, 102, 153) !important]复制代码


我们来看一下测试效果:

                               
登录/注册后可看大图
可以看到这边串口打印出来了我们想要的数据,那这个函数成功了之后,后面我们就可以做很多的功能了。

第二步触摸初始化代码移植
  • P1n_pure_input(0xff);        //Touch Key设置为高阻
  •         P5n_pure_input(0x0f);
  •         P0n_pure_input(0x0f);

[color=rgb(51, 102, 153) !important]复制代码

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

[color=rgb(51, 102, 153) !important]复制代码


然后我们把上节内容里的触摸初始化的代码全都复制过来:放在我们之前的LED驱动代码之后,EA=1;之前。
  •         TSRT = 0x00;                //没有LED分时扫描
  •         TSCHEN1 = 0xff;        //TK00~TK07
  •         TSCHEN2 = 0xff;        //TK08~TK15
  •         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
  •         TSCFG2  = 2;                                //配置触摸按键控制器的内部参考电压(AVCC的分压比), 0(1/4)  1(1/2)  2(5/8)  3(3/4)
  •         TSCTRL = 0xA0;                        //开始自动扫描, 无平均, B7: TSGO,  B6: SINGLE,  B5: TSWAIT, B4: TSWUCS, B3: TSDCEN, B2: TSWUEN, B1 B0: TSSAMP
  • //        TSCTRL = (1<<7) + (1<<6);        //开始单次扫描, 无平均
  • //        TSCTRL = (1<<7) + (1<<6)+3;        //开始单次扫描, 4次平均
  • //        TSCTRL = (1<<7) + (1<<6)+1;        //开始单次扫描, 2次平均
  • //        TSWUTC = 12;                //100ms唤醒一次
  •         IE2 |= 0x80;                        //使能触摸中断

[color=rgb(51, 102, 153) !important]复制代码

来一个个看下这里提到的寄存器。

                               
登录/注册后可看大图

第一个TSRT寄存器,说这个接了触摸的IO口如果又要接触摸,又要接指示灯可以用这个功能,不过这里我们不需要,那这个寄存器就和他说的这样直接写0就好了。



                               
登录/注册后可看大图

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

  • TSCHEN1 = 0x3f;        //TK00~TK05
  • TSCHEN2 = 0xff;        //TK08~TK15

[color=rgb(51, 102, 153) !important]复制代码

在下一个寄存器是


                               
登录/注册后可看大图


                               
登录/注册后可看大图

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


                               
登录/注册后可看大图


                               
登录/注册后可看大图

最后一个就是这里的TSCTRL寄存器了,这个寄存器具体描述可以看手册的这部分描述,这里的话我们需要按键一直检测,所以我们直接把它配置为启动模式,重复扫描,并且完成一轮采集后需要手动清除TSIF的标志位才能继续,这样也能减轻CPU的开销,毕竟不会频繁的进入中断。
这就是整个触摸检测流程的介绍了,说白了这些配置就是为了帮助我们设置一下触摸的参数,整套触摸的配置流程其实就这下面的这个图而已,手册写的还是很清晰的!


                               
登录/注册后可看大图


第三触摸参数的读取
  • //========================================================================
  • // 函数: void TKSU_Interrupt(void)
  • // 描述: 触摸按键中断。
  • // 参数: none.
  • // 返回: none.
  • // 版本: VER1.0
  • // 日期: 2021-02-01
  • // 备注:
  • //========================================================================
  • void TKSU_Interrupt(void) interrupt 13
  • {
  •         u8        j;
  •         j = TSSTA2;
  •         if(j & 0x40)        //数据溢出, 错误处理(略)
  •         {
  •                 TSSTA2 |= 0x40;        //写1清零
  •         }
  •         if(j & 0x80)        //扫描完成
  •         {
  •                 j &= 0x0f;
  •                 TK_cnt[j] = TSDAT;        //保存某个通道的读数
  •                 TSSTA2 |= 0x80;        //写1清零
  •                 read_cnt++;                //读次数+1, 用于延时或读键计数
  •                 TK_TimeOut = 0;
  •         }
  • }

[color=rgb(51, 102, 153) !important]复制代码



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


                               
登录/注册后可看大图


                               
登录/注册后可看大图

这个中断采集的函数我们给他稍稍的改写一下,变成这个样子:
  • //========================================================================
  • // 函数: void TKSU_Interrupt(void)
  • // 描述: 触摸按键中断。
  • // 参数: none.
  • // 返回: none.
  • // 版本: VER1.0
  • // 日期: 2021-02-01
  • // 备注:
  • //========================================================================
  • void TKSU_Interrupt(void) interrupt 13
  • {
  •         u8        j;
  •         j = TSSTA2;
  •         if(j & 0x40)        //数据溢出, 错误处理(略)
  •         {
  •                 TSSTA2 |= 0x40;        //写1清零
  •         }
  •         if(j & 0x80)        //扫描完成
  •         {
  •                 j &= 0x0f;
  •                 TSSTA2 |= 0x80;        //写1清零
  •                 TK_cnt[j] = TSDAT;        //保存某个通道的读数
  •                 if( j==15 )
  •                         B_ReadKeyOk=1;
  •         }
  • }

[color=rgb(51, 102, 153) !important]复制代码


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

                               
登录/注册后可看大图


第四步触摸数据的测试,
上面一步理论来说我们已经能能够正确的读取到了触摸的数据,但是这个数据对不对我们是不是可以通过串口打印出来看下,上面我们已经调通了串口,调通的adc,那这里我们直接在while函数里把触摸的数据打印出来看看就好了,当然也可以吧之前的那个数码管显示的代码先屏蔽掉,直接册数adc,我们直接编写如下代码:
  •         if( B_ReadKeyOk )
  •         {
  •             B_ReadKeyOk = 0;
  •             printf("(%u,%u)\r\n",TK_cnt[0],TK_cnt[1]);
  •         }
  •         delay_ms(20);

[color=rgb(51, 102, 153) !important]复制代码

这里我们就简单的打印两路数据就好了,因为是循环采集的,两路数据没问题那别的应该也没有问题的,上面的代码写入主函数,然后我们开始编译下载:

                               
登录/注册后可看大图
这里我们就已经可以打印出当触摸通道0和触摸通道1的ADC数据了,但是这样看数据不是特别的直观,我们用绘图软件去看一下, 这里我用的RCOM,大家也可以用自己喜欢的,我用的这个软件的数据个数就是"(通道0数据,通道1数据)"就可以接收到数据了,我们先打开这个软件配置一下数据:

                               
登录/注册后可看大图

然后监控我们的数值曲线:

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



回复 支持 反对

使用道具 举报

头像被屏蔽
 楼主| 发表于 2023-3-31 09:48:39 | 显示全部楼层
触摸电子琴从无到有心得分享——基于STC8H4K64TL的电子琴
九、触摸按键程序检测

上面我们已经移植好了触摸按键的驱动函数,也已经看到了触摸按键按下的时候有了明显的曲线变化,但是我们还是不能检测出到底是哪一个按键被按下了。这个时候我们就需要编写我们的一个按键检测的一个程序,当然了,这里我们也可以像我们之前视频教程里提到的那种按键检测按下,松开,长按等等,我们这时候就直接把我们之前的几个函数给复制过来先。
  • u16 Count[8] = {0,0,0,0,0,0,0,0};
  • u8  LastState = 0;                                                //8位变量         b0=1,代表k0上一次按下过
  • <blockquote>#define<span style="white-space:pre">        </span>KEY_NOPRESS<span style="white-space:pre">                </span>0<span style="white-space:pre">                </span>//按键未按下

[color=rgb(51, 102, 153) !important]复制代码

可以看到我们之前视频教程里的按键检测就是这些函数,当然啦,如果面前的你们对于这些函数不太熟练或者说看不懂是什么意思,可以去看一下我们这个《STC32G单片机视频开发教程》的第13集——建议多任务处理,里面有很详细的讲解,手把手的一步一步教你这个函数是怎么写出来的,这里因为篇幅有限,我就不再过多的赘述了。

首先移植过来以后,我们需要先把第一个按键的个数给他加到16个。就是说count那个数组,我们要给它增加到16个元素,所以第一行就可以给他改写成这样,这里的话初始值也不需要写了
  • u16 Count[16] ;

[color=rgb(51, 102, 153) !important]复制代码

然后LastState 那个变量也需要给它改成16位。
  • u16 LastState = 0;

[color=rgb(51, 102, 153) !important]复制代码

我们来看一下这个代码里持续按下的检测,也就是下面一串代码中的蓝色部分,
  • void KEY_Deal(void) //检查所有的按键状态 10ms执行一次
  • {
  • u8 i = 0;
  • for(i=0;i<8;i++) //循环8次 i的取值范围是0-7 代表了P30-P37的状态查询
  • {
  • <font color="#00bfff">if( ~KEY & ( 1<<i ) ) //持续按下,变量+1</font>
  • {
  • if( Count<60000 )
  • Count ++; //按键按下,这个计数变量+1
  • }
  • else //按键松开了,变量清0
  • {
  • if( Count>0 ) //如果这个变量是按下过的
  • {
  • LastState |= (1<<i); //这个变量相应的标志位置位
  • }
  • else
  • {
  • LastState &= ~(1<<i); //这个变量相应的标志位清0
  • }
  • Count = 0; //按键按下,这个计数变量清0
  • }
  • }
  • }

[color=rgb(51, 102, 153) !important]复制代码

因为之前的按键是检测物理的按键,它只有高低电平,但是我们这个触摸按键需要去检测一个adc的数值,所以这个函数我们应该改写成这个样子。这个函数我们应该改写成
  • //========================================================================
  • // 函数名称: KEY_Deal
  • // 函数功能: 按键状态读取
  • // 入口参数: 无
  • // 函数返回: 无
  • // 当前版本: VER1.0
  • // 修改日期: 2023 - 1-1
  • // 当前作者:
  • // 其他备注:循环读取八个端口的状态,并将按下的时间赋值给 Count 数组,按下的状态赋值给LastState
  • //========================================================================
  • void KEY_Deal(void)                        //检查所有的按键状态 10ms执行一次
  • {
  •         u8 i = 0;
  •         for(i=0;i<<font color="#00ffff">16</font>;i++)                                        //循环8次 i的取值范围是0-7  代表了P30-P37的状态查询
  •         {
  • <font color="#00bfff">                if( TK_cnt< (TK_zero-T_KeyCmp))                        //持续按下,变量+1  </font>
  •                 {
  •                         if( Count<60000 )
  •                                 Count ++;                        //按键按下,这个计数变量+1
  •                 }
  •                 else                                                        //按键松开了,变量清0
  •                 {
  •                         if( Count>0 )                        //如果这个变量是按下过的
  •                         {
  •                                 LastState |= (1<<i);        //这个变量相应的标志位置位
  •                         }
  •                         else
  •                         {
  •                                 LastState &= ~(1<<i);        //这个变量相应的标志位清0
  •                         }
  •                         Count = 0;                                //按键按下,这个计数变量清0
  •                 }
  •         }
  • }

[color=rgb(51, 102, 153) !important]复制代码

其中,TK_cnt这个数组是当前的触摸按键的adc数值,TK_zero是我们触摸按键没有按下时候的初始adc数值,KeyCmp就是我们比较的差值,当前的TK_cnt数值如果小于TK_zero减去KeyCmp,就说明我们的触摸已经被按下了,反之则已经松开了。
那么这时候我们就需要通过程序去判定我们的TK_zero的数值,另外KeyCmp的数值我们已经通过上一次的那个上位机软件已经计算出来了,也就是我们之前测试的时候测出了14个通道的那个差值的那张图。当然了我,我上一次的图因为最小值都在5000左右,最大值有1万多。所以这里我就偷个懒,所有通道的KeyCmp数值都等于两千好了,所以得到初始化函数中需要编写如下代码
  • for(i=0;i<16;i++)
  •     {
  •         T_KeyCmp = 2000;
  •     }

[color=rgb(51, 102, 153) !important]复制代码

当然因为这个数组没有定义过,所以要在头上对这个函数进行一下定义。
  • u16 T_KeyCmp[16];

[color=rgb(51, 102, 153) !important]复制代码




然后就是我们TK_zero的数组的数值确定了,这个0点的数值的话,一般常见的方法就是直接初始化也就是刚刚上电的时候,把adc的数值直接读出来,写入数组。用这个初始的一个数组来作为我们的一个0点,那么我们可以编写如下的程序。
  • delay_ms(100);

[color=rgb(51, 102, 153) !important]复制代码

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

[color=rgb(51, 102, 153) !important]复制代码


这样改完之后基本就没问题了。我们就可以编写测试程序。把这个数据给他打印出来。我们就可以在while函数里面编写如下测试程序,我们屏蔽之前while函数里的所有程序,只保留如下部分。
  •         if( B_ReadKeyOk )
  •         {
  •             B_ReadKeyOk = 0;
  •             KEY_Deal();
  •             for(i=0;i<16;i++)
  •             {
  •                 if( KEY_ReadState(i)==KEY_PRESS )
  •                 {
  •                     COM0_DAH = T_LED_CODE[i/10]>>8;
  •                     COM0_DAL = T_LED_CODE[i/10];
  •                     COM1_DAH = T_LED_CODE[i%10]>>8;
  •                     COM1_DAL = T_LED_CODE[i%10];
  •                   printf("按钮%d按下\r\n",(u16)i);
  •                 }
  •             }
  •             //printf("(%u,%u)\r\n",TK_cnt[0],TK_cnt[1]);
  •         }
  •         delay_ms(20);

[color=rgb(51, 102, 153) !important]复制代码

这里的话其实就是很简单的,一方面是通过串口打印出当前是哪一个触摸的通道被按下了,另一方面就是说两位数码管显示出我们当前的一个触摸按键的通道号,那么这里代码已经编写完了,我们直接下载进去测试一下。

                               
登录/注册后可看大图

可以看到我们这个程序编写完并且下载完以后,我们这个窗口上就可以把我们每个通道的adc的一个0点的数值给他打印了出来,当然了,这个地方要注意的就是我们这个刚刚初始化的时候,手千万不要去触摸这个按键。这个是这个上电自适应0点的唯一的一个缺点,这个后面有时间,我把这个给完善一下,有一个0点追踪的办法可以修掉。或者说大家感兴趣的话,可以去看一下官网上的那个程序,里面其实就是做了这个处理的。


                               
登录/注册后可看大图


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


回复 支持 反对

使用道具 举报

头像被屏蔽
 楼主| 发表于 2023-3-31 09:51:43 | 显示全部楼层
十、PWM程序

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

                               
登录/注册后可看大图

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

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


                               
登录/注册后可看大图

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


                               
登录/注册后可看大图

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

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


                               
登录/注册后可看大图

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

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

  • void PWM_Init(u16 PWM_PERIOD)
  • {
  •     PWMA_CCER1 = 0x00; //写 CCMRx 前必须先清零 CCxE 关闭通道
  •     PWMA_CCER2 = 0x00;
  • //    PWMA_CCMR1 = 0x68; //通道模式配置
  • //    PWMA_CCMR2 = 0x68;
  • //    PWMA_CCMR3 = 0x68;
  •     PWMA_CCMR4 = 0x68;
  • //    PWMA_CCER1 = 0x50; //配置通道输出使能和极性
  •     PWMA_CCER2 = 0x55;
  •     PWMA_ARRH = (u8)(PWM_PERIOD >> 8); //设置周期时间
  •     PWMA_ARRL = (u8)PWM_PERIOD;
    •    PWMA_ENO = 0x00;
    • //    PWMA_ENO |= ENO1P; //使能输出
    • //    PWMA_ENO |= ENO1N; //使能输出
    • //    PWMA_ENO |= ENO2P; //使能输出
    • //    PWMA_ENO |= ENO2N; //使能输出
    • //    PWMA_ENO |= ENO3P; //使能输出
    • //    PWMA_ENO |= ENO3N; //使能输出
    • //    PWMA_ENO |= ENO4P; //使能输出
    •     PWMA_ENO |= ENO4N; //使能输出
    •     PWMA_PS = 0x00;  //高级 PWM 通道输出脚选择位
    • //    PWMA_PS |= PWM1_2; //选择 PWM1_2 通道
    • //    PWMA_PS |= PWM2_2; //选择 PWM2_2 通道
    • //    PWMA_PS |= PWM3_2; //选择 PWM3_2 通道
    •     PWMA_PS |= PWM4_4; //选择 PWM4_2 通道
    • <font color="#00ffff">    PWMA_CCR4H = (u8)((PWM_PERIOD+1) >> 8); //设置占空比时间
    •     PWMA_CCR4L = (u8)(PWM_PERIOD+1);</font>
    • <font color="#00ff00">//    PWMA_CCR4H = (u8)(((PWM_PERIOD+1)/2) >> 8); //设置占空比时间
    • //    PWMA_CCR4L = (u8)(((PWM_PERIOD+1)/2));        </font>
    •     PWMA_BKR = 0x80; //使能主输出
    •     PWMA_CR1 |= 0x81; //ARR预装载,开始计时
    • }

    [color=rgb(51, 102, 153) !important]复制代码

    上面的代码,蓝色的部分就是初始化的时候强制关闭PWM输出,绿色的代码是为了测试的时候,初始化直接输出50%占空比的PWM。当然测试的时候我们可以直接选择绿色的代码,输出PWM,这样我们初始化完就可以直接测试波形是不是正常了,话不多说开干,先把这个初始化函数写好,然后主函数添加这么一句

    • PWM_Init(1023);

    [color=rgb(51, 102, 153) !important]复制代码

    编译提示我们有几个参数缺了,这边把原来工程的几个宏定义也复制过来:

    • #define PWM1_1      0x00        //P:P1.0  N:P1.1
    • #define PWM1_2      0x01        //P:P2.0  N:P2.1
    • #define PWM1_3      0x02        //P:P6.0  N:P6.1
    • #define PWM2_1      0x00        //P:P1.2/P5.4  N:P1.3
    • #define PWM2_2      0x04        //P:P2.2  N:P2.3
    • #define PWM2_3      0x08        //P:P6.2  N:P6.3
    • #define PWM3_1      0x00        //P:P1.4  N:P1.5
    • #define PWM3_2      0x10        //P:P2.4  N:P2.5
    • #define PWM3_3      0x20        //P:P6.4  N:P6.5
    • #define PWM4_1      0x00        //P:P1.6  N:P1.7
    • #define PWM4_2      0x40        //P:P2.6  N:P2.7
    • #define PWM4_3      0x80        //P:P6.6  N:P6.7
    • #define PWM4_4      0xC0        //P:P3.4  N:P3.3
    • #define ENO1P       0x01
    • #define ENO1N       0x02
    • #define ENO2P       0x04
    • #define ENO2N       0x08
    • #define ENO3P       0x10
    • #define ENO3N       0x20
    • #define ENO4P       0x40
    • #define ENO4N       0x80

    [color=rgb(51, 102, 153) !important]复制代码

    在编译就通过了,我们下载进去之后,我们直接把示波器探头接到P33引脚上


                                   
    登录/注册后可看大图


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

                                   
    登录/注册后可看大图

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

    • void PWM_Set(u16 PWM_PERIOD)
    • {
    •         if(( PWM_PERIOD==0 )||( PWM_PERIOD==0xffff ))
    •         {
    •                 PWMA_ARRH = (u8)(1024 >> 8); //设置周期时间
    •                 PWMA_ARRL = (u8)1024;
    •                 PWMA_CCR4H = (u8)(((1024+1)) >> 8); //设置占空比时间
    •                 PWMA_CCR4L = (u8)(((1024+1)));
    •         }
    •         else
    •         {
    •                 PWMA_ARRH = (u8)(PWM_PERIOD >> 8); //设置周期时间
    •                 PWMA_ARRL = (u8)PWM_PERIOD;
    •                 PWMA_CCR4H = (u8)(((PWM_PERIOD+1)/2) >> 8); //设置占空比时间
    •                 PWMA_CCR4L = (u8)(((PWM_PERIOD+1)/2));
    •         }
    • }

    [color=rgb(51, 102, 153) !important]复制代码

    这里就是判断PWM是否需要打开,写入0或者最大值我们就关闭PWM,否则打开pwm。

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


                                   
    登录/注册后可看大图

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

    • u16 code Sound_Fre[4][7]=          //s声音库
    • {
    •         0xA4E2        ,0x92F0        ,0x82E8        ,0x7BC8        ,0x6E34        ,0x622E        ,0x5773,
    •         0x5299        ,0x4998        ,0x418D        ,0x3DE4        ,0x371A        ,0x3117        ,0x2BB9,
    •         0x2942        ,0x24C4        ,0x2081        ,0x1EEC        ,0x1B8D        ,0x188B        ,0x15D7,
    •         0x14A3        ,0x1264        ,0x1061        ,0x0F76        ,0x0DC6        ,0x0C45        ,0x0C07,
    • } ;


    [color=rgb(51, 102, 153) !important]复制代码

    最后我们在while函数里编写如下程序:
    •         if( B_ReadKeyOk )
    •         {
    •             B_ReadKeyOk = 0;
    •             KEY_Deal();
    •             for(i=0;i<16;i++)
    •             {
    •                 if( KEY_ReadState(i)==KEY_PRESS )
    •                 {
    •                     COM0_DAH = T_LED_CODE[i/10]>>8;
    •                     COM0_DAL = T_LED_CODE[i/10];
    •                     COM1_DAH = T_LED_CODE[i%10]>>8;
    •                     COM1_DAL = T_LED_CODE[i%10];
    •                                         printf("按钮%d按下\r\n",(u16)i);
    •                                         switch(i)                //音节档位切换
    •                                         {
    •                                                 case 15:Sound = 1;break;
    •                                                 case 10:Sound = 2;break;
    •                                                 case 11:Sound = 3;break;
    •                                                 case 00:Sound = 4;break;
    •                                                 case 01:Sound = 5;break;
    •                                                 case 04:Sound = 6;break;
    •                                                 case 05:Sound = 7;break;
    •                                                 case 12:
    •                                                 case 13:
    •                                                 case 14:
    •                                                         Sound = 0;break;
    •                                         }
    •                                         if(Sound>0  )
    •                                                 PWM_Set(Sound_Fre[0][Sound-1]);
    •                                         else
    •                                                 PWM_Set(0);
    •                 }
    •             }
    •             //printf("(%u,%u)\r\n",TK_cnt[0],TK_cnt[1]);
    •         }
    •         delay_ms(20);

    [color=rgb(51, 102, 153) !important]复制代码


    这里就是在上一章 的内容山增加了触摸按键的功能。通道15、10、11、0、1、4、5这七个分别是1-7的音符对应的按键,左边三个这里就简单的给他设置为关闭按钮,按下左边的任意一个就可以关闭PWM输出,这样我们最最最简单的一个发音的功能就完成了




回复 支持 反对

使用道具 举报

发表于 2023-3-31 13:04:11 | 显示全部楼层
这个支持大电流LED数码管自动刷新的STC8H4K64TL-45MHz-LQF4P是典范
省I/O, 可以动态切换到支持共阴或共阳LED数码管, 最多支持 32个 8段大电流LED自动刷新,还有触摸功能的1T 8051
回复 支持 反对

使用道具 举报

头像被屏蔽
 楼主| 发表于 2023-4-1 09:42:30 | 显示全部楼层
官方其实也开源了:

16个8段LED数码管大电流自动涮新,16个触摸按键,STC8H4K64TL
===STC8H4K64TL-45MHz-LQFP48 实际可以驱动 32个8段LED数码管自动涮新
大电流自动涮新 16个8段LED数码管,简化了硬件设计,减轻了CPU的压力,简化了程序设计

4.1 -触摸.png


回复 支持 反对

使用道具 举报

头像被屏蔽
 楼主| 发表于 2023-4-15 09:13:27 | 显示全部楼层
触摸按键电子琴完整方案 !STC8H4K64TL-45MHz-LQFP48/LQFP32/TSSOP20 实现
===1,触摸按键;2,大电流LED数码管自动刷新;3,PWM发声 !完整解决方案,冲哥奉献 !!!

最终版的板子的工程和程序哈,有需要的可以自己下载文件打板子买材料焊接测试和体验。
最近比较忙程序草草的写完了,注释写的不多,月底的时候补上注释,暂时先参考看看哈。
另外我也在应小伙伴们的要求买材料准备做几个套件包,成品或者散件都可以自由下单,预计2-3周可以上架,尽请期待。

47d07b4e387bd5aabc00e69397488b3e_165744xhqefxm82eqhmzhm.png
47d07b4e387bd5aabc00e69397488b3e_165744xhqefxm82eqhmzhm.png

电子琴(原理图 程序).rar

5.07 MB, 下载次数: 1, 下载积分: 家元 -55

回复 支持 反对

使用道具 举报

发表于 2023-4-17 15:56:37 | 显示全部楼层
厉害了,大神
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

APP|手机版|小黑屋|关于我们|联系我们|法律条款|技术知识分享平台

闽公网安备35020502000485号

闽ICP备2021002735号-2

GMT+8, 2024-3-28 19:33 , Processed in 0.156000 second(s), 12 queries , Redis On.

Powered by Discuz!

© 2006-2023 smzj.net

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