STC15F2K60S2单片机作为蓝桥杯单片机竞赛的指定芯片,是一款高性能8051内核微控制器。这款芯片最吸引我的地方在于它内置了60K Flash和2K RAM,完全能满足省赛项目的存储需求。记得第一次拿到开发板时,最直观的感受是它的封装紧凑,引脚功能复用性强,特别是P0-P3这4组I/O口,通过74HC138译码器扩展后能同时驱动数码管、LED和按键矩阵。
在实际搭建硬件环境时,有几个关键点需要注意:首先是电源部分,虽然芯片支持3.3V-5V宽电压,但实测发现使用5V供电时ADC采集更稳定;其次是时钟配置,内部RC振荡器默认是11.0592MHz,但通过ISP工具可以调整到12MHz,这个细节直接影响定时器中断的精度。我曾在调试时因为没注意时钟源设置,导致频率测量结果偏差了8%,后来才发现是时钟配置问题。
开发环境推荐使用Keil C51,配合STC-ISP下载工具。有个小技巧:在工程配置中要把Memory Model设为Large,否则变量多了容易报错。硬件连接上,PCF8591模块的I2C引脚要接P2.0和P2.1,数码管段选接P0口,位选通过74HC138控制,这些在原理图上都要反复核对。
PCF8591这颗8位ADC芯片虽然精度不高,但对于比赛场景完全够用。代码中read_pcf8591_rb2()函数实现了通道2的电压采集,这里有个关键细节:采集到的原始值V是0-255的数字量,需要转换成实际电压。我采用的转换公式是V_smg=V*100*(5.0/255),这样得到的是放大100倍的电压值,方便数码管显示小数位。
实际调试中发现,ADC读数会有波动。我的解决方案是软件滤波:连续采样5次取中间值。更高级的做法可以加上硬件RC滤波,在PCF8591的AIN2引脚对地接个104电容效果就很明显。输出控制部分,read_pcf8591_da()函数实现了DAC输出,注意写入的值需要换算,比如要输出2.5V就写入128(255*2.5/5)。
频率测量用的是定时器T0的计数模式,配置为模式1(16位自动重装)。这里有个精妙设计:T0设置为计数外部脉冲(P3.4引脚),而T1作为50ms定时中断。每20次中断(即1秒)读取T0的计数值mai_c,就是频率值hz。
调试这个功能时我踩过坑:最初没注意到T0的GATE位要置零,导致计数不启动。另一个常见问题是脉冲宽度,STC15F2K60S2最高能计数1/24晶振频率的脉冲,12MHz时就是500kHz。如果被测信号频率过高,可以考虑分频处理。
数码管显示采用经典的动态扫描方式,smg_show()函数中通过74HC138依次选通位选。这里有两个优化点:一是消影处理,在段选数据变化前先关闭位选(HC138(6,0xff));二是显示滤波,当hz为0时不显示,避免上电乱码。
对于电压显示,我特别设计了带小数点的格式。查表方式很巧妙:smg_dot[]数组中存储的是带小数点的段码,比如显示"2.5"就是smg_dot[2]和smg_nodot[5]的组合。这种方式比实时计算段码节省大量CPU时间。
四个独立按键(S4-S7)实现模式切换功能。scankeys()函数中有几个关键设计:首先是消抖处理,检测到按键按下后延时10ms再次判断;其次是状态锁定,通过while循环保持显示刷新直到按键释放。
模式切换逻辑值得关注:S4切换频率/电压显示模式,S5切换ADC/DAC工作模式,S6控制LED开关,S7控制数码管开关。每个功能键都有明确的状态变量(mode、mode_Vda等),这种设计扩展性很好,后期要新增功能只需增加状态变量即可。
定时器T1中断服务程序T1_ZD()是系统的节拍器。这里有个重要细节:重装初值要分高低字节操作,我见过有人直接写TH1=TL1=(65535-50000)/256,这是错误的。正确的写法应该是:
c复制TH1 = (65535-50000)/256;
TL1 = (65535-50000)%256;
中断服务程序中要尽量精简代码,我把计数值换算和状态判断都放在主循环中处理。实测发现,如果中断服务程序执行时间超过50μs,就会影响其他功能的实时性。
I2C通信是最容易出问题的部分,我的调试心得是:首先用示波器看波形,确认START信号(SDA下降沿时SCL为高)和STOP信号(SDA上升沿时SCL为高)是否规范;其次检查ACK响应,如果从机无应答,可能是地址错误或硬件连接问题。
在代码层面,iic.c中的延时函数I2C_Delay()需要根据主频调整。12MHz时DELAY_TIME设为5比较合适,如果主频变化,这个值要相应调整。有个常见错误是忘记释放SDA线,在接收完数据后要执行sda=1。
STC15F2K60S2的资源有限,需要合理分配:定时器T0用于频率计数,T1用于系统时基,PCA模块可以保留给扩展功能。RAM使用方面,我把频繁访问的变量如V_smg、hz定义为全局变量,而临时变量尽量用局部变量节省栈空间。
显示刷新是个耗时的操作,我的优化方案是:只有当数据变化时才更新显示。比如在smg_show()函数中,只有mode或hz/V_smg变化时才重新输出数码管数据,平时保持原状态。这种方式能降低50%以上的CPU占用率。
虽然省赛题目要求的功能已经实现,但还可以进一步扩展:比如增加串口通信,把测量数据上传到PC;或者利用PCA模块实现PWM输出,构建闭环控制系统。更复杂的可以加上EEPROM存储校准参数,提升ADC测量精度。
在准备比赛时,我建议多积累些通用模块代码,比如DS18B20驱动、LCD12864显示等。这些模块虽然本次没用到,但其他赛题可能会需要。把每个模块都封装成独立的.c/.h文件,使用时直接包含就能快速搭建系统。