最近调完一个恒温碗的案子主控用的中微SC8F6790显示部分是一块很常见的12864LCD主控芯片是ST7920。这颗屏价格不贵但要把中文、数字、温度刷新和恒温逻辑串在一起还是有不少细节容易踩坑。这篇文章我把完整方案从头拆一遍包括为什么选这颗MCU、LCD接口怎么接、底层驱动代码怎么组织、上电白屏花屏怎么查代码也统一贴出来。如果你正在做小家电、温控类产品或学生竞赛作品想用国产8位机驱动一块带中文显示的屏这篇可以直接照着做。1. 项目拆解与方案选型1.1 恒温碗的显示需求到底有哪些恒温碗这类产品的核心功能听起来简单碗放在底座上底座负责加热和保温用户设定一个目标温度比如45度系统检测温度低于目标就加热到了就停。显示部分要告诉用户三件事当前温度是多少、目标温度是多少、现在是在加热还是保温。这三项信息如果只有数码管只能显示数字“加热”“保温”这种状态就要靠指示灯或者雕刻图标来完成体验比较生硬。如果换成LCD就能直接显示“恒温碗”“加热”“保温”这样的中文人机交互清楚很多。所以这个项目的显示需求可以拆成几个明确指标支持GB2312中文显示、能显示数字和小数点、状态切换响应及时、在低光照环境下有背光可读。市面上很多小家电用的段码LCD其实也能达到类似效果但段码屏需要开模一套模具下来费用不低适合大批量产品。对于功能验证阶段、小批量试产或者DIY方案点阵LCD才是更合适的起点。从显示内容量来说一屏显示四行文字、每行十几个字符就够用了不需要大屏。恒温碗整体是个圆形桌面设备屏幕尺寸一般在2.2到3寸之间12864点阵在这个尺寸下观感刚好字大、清楚不太费眼。这也是我最终没有选择1.8寸TFT的原因小屏显示两行还能看四行信息挤在一起近距离看久了确实累。1.2 屏选型断码屏、点阵屏、TFT怎么选很多第一次做这类项目的朋友会卡在选屏这一步。我把三种主流方案放在一起对比你一眼就能看出差别。方案成本开发难度中文支持适用阶段段码LCD低中等需要定制只能显示固定字符无法任意中文大批量量产12864点阵LCDST7920中等低串口三根线内置中文字库直接发GB码验证、试产、DIY1.8寸TFT128x160高一些高刷屏逻辑复杂中文需要字库或图片取模要求彩屏效果的场合段码LCD的显示内容是“画”在玻璃上的比如温度数字、百分比符号、水滴图标厂商标定好之后就不能改了。如果产品定义很固定、量很大段码屏是成本最优解。但开发前期经常要改显示内容段码屏每改一次就要动模具周期长、费用多不适合折腾。TFT彩屏显示效果好可玩性高但MCU负担也大。ST7920这类不带显存的屏MCU只需要按文本模式发字节内部自己会处理点阵刷新对8位MCU非常友好。所以对于恒温碗这个场景12864点阵屏是性价比非常折中的选择支持中文省引脚开发快后期切段码屏也方便。如果你的产品以后要量产逻辑代码可以从点阵屏原封不动迁移到段码屏方案只是把LCD驱动层替换掉上层显示逻辑基本不用改。1.3 SC8F6790的资源够不够用SC8F6790是中微半导体面向小家电市场的一款8位Flash MCU国产芯片价格不高供货也稳定。这类芯片最擅长的工作就是读几个按键、采集一路NTC温度、控制一路加热、驱动一块小屏。我这次用下来资源上没有任何紧张感。驱动12864的时候如果走串行模式只需要三根IO时钟、数据、片选。ST7920本身自带控制器MCU不用额外挂外部RAM也不用像驱动OLED那样频繁往显存里写数据。温度采集用内部ADC就行加热控制用一个IO口拉高拉低按键占用两个IO蜂鸣器一个IO整颗芯片的引脚资源还剩不少。很多朋友会有个顾虑觉得8位机太老、太简单跑不了复杂逻辑。实际上恒温碗这种产品显示刷新频率不需要太高温度采样的响应也不需要到毫秒级。ST7920的串口时钟跑几百千赫兹就非常稳定了8位机IO翻转速度完全够用。而且8位机开发简单写错寄存器最多就是屏不亮不会像Linux驱动那样出现内核崩溃的问题。再加上国产芯片的勘误手册和例程都在遇到问题有地方查。2. LCD接口设计与时序基础2.1 为什么我选了串行接口ST7920本身支持三种接法8位并口、4位并口和串行。很多初学者容易走入一个误区觉得并口速度快就无脑用并口。速度不是这个项目的核心矛盾接线的复杂度和引脚占用才是。8位并口需要至少11根线8根数据线、RS、RW、E再加上片选和背光。这一下就把MCU的IO吃掉一大半。如果主控引脚富余用并口确实更“正统”初始化的时候还不用考虑串口同步的问题。但SC8F6790这种小封装芯片引脚本身就不多给LCD留太多引脚留给传感器和按键的资源就少了。而且并口模式下数据总线上的电平容易互相干扰布线一乱就花屏排查起来非常痛苦。串行模式只要三根线CS、SCLK、SID。每次发数据MCU把IO翻转成规定的时序ST7920自己内部再把串行数据转成并口信号。速度上有损失但12864这类屏刷新一屏文本本来就不需要多快温度变化一秒刷五次都嫌多。实测下来串口跑起来很稳唯一要注意的就是时序里每个字节之后必须留一点延时让控制器内部处理完不然连续发命令容易丢字节。2.2 ST7920串行时序到底怎么发ST7920的串行协议初看有点绕其实拆开就三层结构先是同步脉冲再是标志位最后才是数据本身。发送一个字节之前CS必须拉低告诉LCD控制器“我要开始通信了”。接着SID线上先发5个连续的1这5个1是同步位相当于在告诉控制器“注意后面是有效数据”。紧随其后的是RW位写操作时固定为0。再往下是RS位RS为0表示后面跟的是命令RS为1表示后面跟的是显示数据。最后还有一个0是协议里规定的填充位。位段数值含义11111同步位唤醒控制器RW0写操作RS0或10命令1数据0填充位协议固定高4位数据高半字节与D4~D7对应低4位数据低半字节与D0~D3对应但这里还有一层实际发送的时候不是一次把一个字节发完。ST7920的串口规定8位数据要先拆成高4位和低4位高4位补4个0拼成一个字节发出去再把低4位左移到高位补4个0发出去。所以你在代码里会看到写一条命令实际要调用三次send_byte第一次发标志位第二次发高半字节第三次发低半字节。对应到代码上写命令的标志字节是0xF8也就是11111000二进制看就是5个1加RW0、RS0、最后一位0。写数据的标志字节是0xFA对应11111010区别只在RS位变成了1。知道这两个数字的来源后面看代码就不会觉得神秘了。2.3 接线清单与IO分配我这次的硬件接线很简单LCD是5V供电的版本SC8F6790也是5V工作电平直接匹配不需要额外转换。LCD引脚连接到SC8F6790功能VCC5V电源GNDGND地CSP1.2片选SCLKP1.0串行时钟SIDP1.1串行数据PSBGND串行模式选择BLP1.3背光控制PSB引脚是并串选择接GND就进入串行模式这个很容易漏。有些模块把PSB默认接在VCC上那你不管怎么写时序都不会亮必须先把PSB拉低。除了屏恒温碗还需要一路NTC热敏电阻采样接在ADC通道0上一个继电器或者可控硅控制加热丝接在P2.0两个按键一个加一个减接在P2.1和P2.2。蜂鸣器可以再接一个IOP2.3。这个分配留了很多余量后续如果要加定时、蓝牙模块引脚还够。3. 完整代码实现3.1 工程准备和引脚定义中微SC8F6790的开发环境我用的Keil C51装好中微的器件包之后新建工程然后选对应型号就能开始编译。代码兼容标准C51语法如果你手里的型号是其他引脚封装只需要改头部的sbit引脚定义驱动逻辑完全不用动。#include SC8F6790.h #include intrins.h sbit LCD_SCLK P1^0; sbit LCD_SID P1^1; sbit LCD_CS P1^2; sbit LCD_BL P1^3; sbit HEAT P2^0; sbit KEY_UP P2^1; sbit KEY_DOWN P2^2;第一次上电我建议先在main里把背光IO拉高确认背光能亮。背光不亮的话后面所有显示代码都是空中楼阁。电源部分也要注意ST7920对电源纹波比较敏感VCC和GND之间最好放一个100uF电解电容加一个104瓷片电容离屏的电源引脚越近越好。我调试过程中遇到过一次白屏排查了半天最后发现是电源线太细背光一亮电压就被拉低换粗线加电容之后问题消失。3.2 底层驱动代码底层驱动主要是四个函数延时、发送一个字节到LCD、发命令、发数据。这四个函数是所有显示功能的基础。void delay_us(unsigned int n) { while(n--) { _nop_(); } } void delay_ms(unsigned int n) { unsigned int i, j; for(i 0; i n; i) for(j 0; j 120; j); } static void lcd_send_byte(unsigned char dat) { unsigned char i; for(i 0; i 8; i) { LCD_SCLK 0; if(dat 0x80) LCD_SID 1; else LCD_SID 0; LCD_SCLK 1; dat 1; } } void lcd_write_cmd(unsigned char cmd) { LCD_CS 0; lcd_send_byte(0xF8); lcd_send_byte(cmd 0xF0); lcd_send_byte((cmd 0x0F) 4); LCD_CS 1; delay_us(50); } void lcd_write_data(unsigned char dat) { LCD_CS 0; lcd_send_byte(0xFA); lcd_send_byte(dat 0xF0); lcd_send_byte((dat 0x0F) 4); LCD_CS 1; delay_us(50); }delay_ms函数里的循环次数是按12MHz主频估的不同主频需要微调。lcd_send_byte是标准的MSB先行移位发送每一bit先在SCLK低电平时把SID设好再把SCLK拉高这样数据线上的电平在时钟上升沿是稳定的。lcd_write_cmd和lcd_write_data的差别只有第一字节是0xF8还是0xFA。0xF8拆开看是11111000代表同步位加RW0加RS00xFA是11111010RS位变成1。后面的两次send_byte第一次发高四位第二次发低四位。命令和数据的延时我用的是50us。ST7920内部处理命令需要时间连续高速发命令很可能丢数据。如果你的程序里有频繁刷屏这个延时要保留不要图快把它删掉。初始化序列是ST7920比较固定的一套流程上电先延时让内部电源稳定然后发几次0x30进入基本指令集模式最后设置显示参数。void lcd_init(void) { delay_ms(100); lcd_write_cmd(0x30); delay_ms(10); lcd_write_cmd(0x30); delay_ms(10); lcd_write_cmd(0x30); delay_ms(10); lcd_write_cmd(0x02); lcd_write_cmd(0x0C); lcd_write_cmd(0x01); delay_ms(2); lcd_write_cmd(0x06); lcd_write_cmd(0x80); }0x30这条命令在串行模式下也要发它的作用是让控制器进入8位接口的基本指令集。0x02把DDRAM地址归零同时让屏幕处于4位模式的内部状态。0x0C是显示开、光标关、光标闪烁关。0x01是清屏之后必须延时因为清屏要逐点刷新整个屏幕需要一点时间。0x06设置写数据后光标自动右移。有个容易踩坑的点部分ST7920模块对0x02这条命令比较敏感如果你的屏初始化之后出现花屏或者半屏亮半屏不亮可以试试把0x02去掉或者把0x02改成0x20。同一个控制器不同厂家的模块行为会有细微差别初始化命令序列在手头模块上验证过才算数。3.3 中文、数字和状态显示ST7920内置GB2312中文字库这是它比很多裸点阵LCD好用很多的地方。显示中文的时候只需要连续发送两个字节例如“恒”这个字的GB码是0xBA 0xE3连续发两个数据字节屏上就会出现“恒”字。英文和数字则直接发送ASCII码一个字节对应一个字符。void lcd_set_cursor(unsigned char x, unsigned char y) { unsigned char addr; switch(y) { case 0: addr 0x80; break; case 1: addr 0x90; break; case 2: addr 0xA0; break; case 3: addr 0xB0; break; default: addr 0x80; break; } lcd_write_cmd(addr x); } void lcd_show_string(unsigned char x, unsigned char y, unsigned char code *str) { lcd_set_cursor(x, y); while(*str) { lcd_write_data(*str); } }lcd_set_cursor里的x是列偏移但要注意这个列是以ASCII字符为单位的一个中文占两个ASCII字符位。比如你从第0列开始显示“温度”两个字实际占用了DDRAM的0、1、2、3四个地址下一个英文冒号从第4列开始冒号后面的数字就要从第5列开始。我在代码里统一约定温度数字从第5列开始显示就是因为前面“温度:”正好占了5个ASCII位置。如果你的编译器环境不是GBK编码直接在代码里写中文可能存成别的编码格式那就在代码外查好GB码用字节数组写死。下面显示“恒温碗”的效果和直接写字符串是一样的。unsigned char code str_title[] {0xBA, 0xE3, 0xCE, 0xC2, 0xCD, 0xEB, 0x00}; // 恒温碗温度数字的显示比字符串稍微麻烦一点因为温度可能是一位数也可能是两位数而且还要带小数。我写了一个固定宽度输出函数保证每次刷新都输出同样的长度这样就不会出现上一次的数字比这次多一位、残留字符在屏上的问题。void lcd_show_temp_value(unsigned char x, unsigned char y, unsigned int value) { unsigned char int_part, frac_part; int_part value / 10; frac_part value % 10; lcd_set_cursor(x, y); if(int_part 100) lcd_write_data(0 int_part / 100); lcd_write_data(0 (int_part / 10) % 10); lcd_write_data(0 int_part % 10); lcd_write_data(.); lcd_write_data(0 frac_part); lcd_write_data(C); lcd_write_data( ); }函数内部约定的温度单位是0.1度所以450代表45.0度255代表25.5度。整数部分除以10得到个位和十位取余得到小数位。每次输出六个字符最后补一个空格这样从25.5C变成45.0C的时候多出来的字符会被空格清掉不会留尾巴。3.4 主循环把屏和恒温逻辑串起来主循环的逻辑不复杂读温度比较目标温度决定加热还是不加热刷新显示处理按键。显示刷新放到最后一步每次循环统一更新一次避免加热控制和显示互相干扰。void main(void) { unsigned int temp_val 255; unsigned int target_temp 450; unsigned char heat 0; LCD_BL 1; lcd_init(); lcd_show_string(0, 0, 恒温碗); lcd_show_string(0, 1, 温度:); lcd_show_string(0, 2, 设定:); lcd_show_string(0, 3, 状态:); while(1) { // adc_val ADC_GetValue(NTC_ADC_CH); // temp_val adc_to_temp(adc_val); 实际接入NTC后打开这行 temp_val 255; // 测试用固定值 if(temp_val target_temp) { heat 1; HEAT 1; } else { heat 0; HEAT 0; } if(KEY_UP 0) { delay_ms(20); if(KEY_UP 0) { target_temp 5; if(target_temp 600) target_temp 600; while(KEY_UP 0); } } if(KEY_DOWN 0) { delay_ms(20); if(KEY_DOWN 0) { if(target_temp 5) target_temp - 5; else target_temp 0; while(KEY_DOWN 0); } } lcd_show_temp_value(5, 1, temp_val); lcd_show_temp_value(5, 2, target_temp); lcd_show_status(5, 3, heat); delay_ms(200); } }状态显示函数用来在“加热”和“保温”之间切换。因为这两个词都是两个汉字切换的时候理论上长度一样实际上只要刷新一次就覆盖了但我还是习惯在后面补两个空格防止之前显示过更长内容时残留。void lcd_show_status(unsigned char x, unsigned char y, unsigned char heat) { lcd_set_cursor(x, y); if(heat) { lcd_show_string(x, y, 加热); } else { lcd_show_string(x, y, 保温); } lcd_show_string(x 4, y, ); }按键处理用了最简单的消抖方式读一次延时20ms再读一次确认按下才生效。加号一次加0.5度减号一次减0.5度温度范围限制在0到60度之间。这个设定对恒温碗比较合理婴儿辅食一般设定在40到50度之间成人使用的汤类可以适当高一点。ADC_GetValue和adc_to_temp这两个函数需要按SC8F6790的手册补全。ADC部分其实也不复杂就是配置通道、启动转换、读结果。NTC热敏电阻的温度换算推荐用查表法把分压值和温度一一对应写到数组里插值计算比用一个纯线性公式准得多。表可以根据你们选定的NTC型号和分压电阻计算出来工具网上很多生成一个1024点的表也就几KB空间8位机的Flash完全放得下。4. 调试方法、常见问题与避坑指南4.1 验证顺序从点亮背光到显示数字LCD驱动这类的调试最怕的是上来就往完整工程里塞代码出了问题根本不知道是哪里错了。我的做法是分四步走。第一步先单独把背光点亮。背光不亮问题基本在供电或者硬件连接先解决这个再写软件。第二步只调用lcd_init初始化然后发一个清屏命令0x01。屏幕应该变成全白。如果全白都看不到说明初始化没有完成检查PSB是否接地、CS时序是否正确。第三步在屏幕固定位置写一个ASCII字符“A”。能显示字母说明底层write_data链路是通的。这时候可以放心去调中文显示。第四步显示中文“温度”再显示数字“25.5C”。如果这一步正常说明DDRAM地址和字库编码都没问题剩下的就是业务逻辑的事了。如果你没有实物也可以先用单片机仿真软件搭一个环境把代码跑一遍。Proteus里有ST7920的模型用来验证时序和命令序列是可以的。不过仿真模型和实物还是有一些差别特别是时序响应时间仿真通过不代表实物一定没问题最终还是要靠板子说话。4.2 常见问题速查表调试中遇到的白屏、花屏、乱码大部分原因集中在几个点上。我把常见的现象和排查方向整理成了一张表方便你对照着查。现象可能原因解决办法完全白屏供电不足、PSB接错检查电源电压确认PSB接地上电无显示初始化序列不对检查0x30是否连续发够三次显示乱码中文编码错误确认字符串是GB2312编码缺笔画时序延时不够增大write_cmd和write_data后的延时屏幕闪刷新频率太高主循环加上200ms延时对比度太浅屏供电偏低、对比度电阻不对调整对比度可调电阻检查供电半屏亮半屏不亮初始化失败去掉0x02命令重新上电花屏和乱码是新手最头痛的问题。乱码八成是编码问题比如你在代码里写“温”字但IDE实际保存的是UTF-8编码发送给ST7920的字节就不是GB码屏自然显示不了。乱码还有一个隐藏来源显示数字后没有清掉旧内容导致新旧字符叠在一起。我在温度显示函数里固定输出宽度、尾部补空格就是为了避免这个问题。4.3 开发调试工具与驱动心得开发这类MCU项目一个稳定的串口调试通道非常有用。SC8F6790如果有空闲UART我强烈建议留一路出来经过CH340芯片转USB接电脑把ADC采样值、温度换算结果、按键扫描结果都通过串口打印出来。这不是留给用户的功能是给自己调试用的窗口。比如NTC温度换算不准确光看屏幕上的数字根本没法判断是ADC读错了还是换算公式错了有串口就能把原始值打出来一眼定位。CH340这类USB转串口芯片的驱动问题也很常见。插上USB模块后先把设备管理器打开看有没有出现COM口。如果出现黄色感叹号或者完全没有反应先把驱动装好然后换一根数据线试试有些线只能充电不能传数据。调试器和下载器也是一样的逻辑ST-Link、J-Link这些调试器驱动没装好之前IDE里根本看不到芯片一切都要从头检查很浪费心情。烧录器和调试器不一定非要买很贵的中微的开发板配套的下载器就能满足需求。如果是量产考虑用脱机烧录器现场编程不用接电脑效率会高很多。芯片批次不同烧录器固件可能需要升级建议拿到烧录器之后先更新到最新固件再开始用。4.4 背光发热、低功耗和量产优化恒温碗是通电持续工作的设备背光一直亮着虽然显得好用但也带来两个问题耗电和发热。背光LED经过限流电阻后功耗不大一晚上下来也不至于出问题但长期高温环境对LCD本身不友好。如果产品有低功耗需求比如电池供电的便携保温碗背光就不能常亮。SC8F6790自带PWM输出可以把背光控制引脚从普通IO切到PWM通道通过调整占空比来调节背光亮度。白天环境光充足的时候把亮度降到30%晚上或者暗光环境再自动提亮这样既不刺眼也省电。不需要背光的时候直接关闭主控进入低功耗模式前先把LCD的背光关掉能省下很大一部分电流。液晶本身在静态显示时功耗很低最大的电流消耗就在背光上。量产阶段还有几个细节值得注意。第一ST7920模块的对比度电阻值在不同批次之间可能有差异建议在板子上预留一个可调电阻位或者用固定电阻但要在供应链确认批次一致性。第二LCD排线不要走得太长SID和SCLK两条线尽量短且平行走线减少信号畸变。第三如果周围有强电设备比如加热丝的开关瞬间会产生浪涌LCD的电源引脚附近一定要加TVS管或者至少加电容否则容易重启甚至损坏屏。5. 从样机到量产的一点个人心得这套方案我完整跑过一轮从最初的裸屏点亮到整机恒温控制前后大概用了一个星期。最花费时间的其实不是LCD驱动而是温度采集的标定。NTC的阻值曲线是非线性的直接用线性公式换算温度在低温段误差能到四五度对恒温碗这种产品来说这个误差太大了。我是先把ADC值分段每一段用对应的倍率换算再配合软件插值误差才控制在0.5度以内。如果你手头有恒温槽或者精密温度计建议做标定的时候把数据采集得密一点后面软件就会轻松很多。还有一个经验是按键的响应和显示刷新最好分开。按键按下时如果正好赶上主循环在刷新LCD会出现按键“抢时间”的感觉。把按键扫描放到定时中断里显示和加热逻辑放在主循环这样界面刷新再频繁按键也不会丢。我现在这个版本没有拆中断因为恒温碗的刷新频率不高200ms一次影响不大但如果你的界面要做动画菜单这个建议越早改越好。最后再说说这套方案的扩展性。ST7920这块屏去掉文本模式之外还有一个绘图模式可以用LCD Image Converter之类的小工具把图片转成字模在屏幕上画个温度计图标或者加热火焰图标观感会比纯文字好很多。再往后如果觉得黑白屏不够用想换成1.8寸TFT彩屏MCU的引脚也够只是驱动层要重写上层的显示抽象函数还可以继续复用。我个人觉得做这类小家电项目最怕的不是芯片性能不够而是方向没想清楚就开始写代码。先把需求拆清楚把屏选对把IO规划好然后再动键盘写驱动效率会高得多。SC8F6790配12864这套组合对恒温碗、小型温控器、咖啡保温垫这一类产品都是很稳的起点。