资讯详情

资讯详情

51单片机坐姿矫正系统设计:STC89C52多传感器采集与调试

简介这份文档围绕基于STC89C51/52单片机的坐姿矫正系统展开面向电子信息、自动化等专业学生及51单片机初学者也可作为毕业设计选题、课程设计和单片机综合实践的参考。系统集成自动与手动模式利用光敏电阻和模数转换检测环境光线结合人体感应、测距模块、LCD1602显示与蜂鸣器提醒可在距离过近、光线不足或学习时长超限时提示纠正坐姿、注意休息。资源包仅含1个doc文件约295KB正文覆盖摘要、总体方案、单片机最小电路、显示模块、光线监测、测距电路、系统功能与测试总结等章节便于读者梳理软硬件模块划分、器件选型、联调思路及文档结构。目前已有793人学习下载适合需要搭建51单片机课程设计或撰写毕业设计文档的读者参考。1. 一块 51 板子能管住坐姿吗这个毕业设计到底在解决什么前段时间帮人看一份 51 单片机毕业设计的实物题目是坐姿矫正系统板子焊好之后学生自己都说不清那几根线各干什么用。这类题目看着简单实际把光敏电阻、模数转换、超声波测距、液晶显示、蜂鸣器和按键揉在一块板子上串起来的问题比单个模块多得多。这个项目用 STC89C52 做主控LCD1602 做显示光敏电阻配 ADC0832 采环境光超声波模块测人离桌面的距离超时或者太近就蜂鸣器响本质是一个「三路传感器 一个执行器 一个计时器」的提醒装置。适合正在做 51 单片机毕业设计、需要一份能跑起来的完整参考的人也适合想拿个小系统练手硬件时序的开发者。2. 方案选型为什么最后定的是 STC89C52 LCD1602 ADC0832这套组合不是拍脑袋定的原文里对主控和显示都做了方案对比只是写成了论文体。把对比过程还原成工程视角看其实是在三个约束下做取舍一是本科阶段要能在两周内把电路焊完并调通二是所有器件在本地电子市场能一次买齐三是代码量要控制在自己能逐行看懂的范围。这三条约束一摆很多「性能更好」的方案就自动出局了。2.1 主控为什么没上 STM32方案里比较过 AVR 和 STM32F103最后落回 STC89C52。原因不是性能是摩擦成本。STM32 要配仿真器、装支持包、建工程模板光是让一个 GPIO 翻转起来环境就得折腾小半天而这个系统真正要用到的外设只有定时器、几个普通 IO 和一个外部中断8 位机的资源绰绰有余。STC89C52 的具体账是这样的8K Flash 装这个程序最多用掉一半512 字节 RAM原文写的 256 是老型号说法STC89C52 实际是 512里全局变量加缓冲区不到 100 字节32 个 IO 口把 LCD 的 8 位数据线加 3 根控制线一占还剩二十来个三个定时器一个做秒基准一个做超声波计时正好够。12MHz 晶振下机器周期 1 微秒超声波换算距离时不用再做除法补偿这是实打实的便利。要提醒的是选 51 就意味着放弃硬件浮点和硬件乘除凡是涉及小数的运算都得自己拆成整数。这个项目里所有「计算」最后都被我改成了整数比较光线不分 0100 的百分比只分「暗/正常/亮」三档距离不做小数直接取厘米整数。新手容易在这上面栽跟头写个float lux ...上去编译能过跑起来 Flash 直接爆。2.2 显示为什么放弃数码管选 LCD1602数码管和点阵的方案都被否了理由很实在数码管显示不了汉字和英文字符而这个系统要显示「LIGHT」「DIST」「TIME」这类标签纯数字根本说不清哪个是哪个点阵屏倒是能显示但驱动芯片和扫描逻辑一上来代码量翻倍对 8 位机的 IO 也是个负担。LCD1602 的好处是内置了字符发生器和控制芯片两行十六字符一根 8 位数据线加 RS/RW/EN 三根控制线就能驱动初始化序列固定网上现成的驱动改改就能用。它的缺点也得提前知道并行接口占 IO 多刷新靠软件延时没有字库所以显示不了中文对比度引脚接不好就是一片蓝屏。这些在第 5 章会逐条展开。2.3 光线通道的两条路外置 ADC0832 还是用内部比较器光敏电阻输出的是连续电压51 单片机没有内置 ADC所以要外挂。常见的两条路一条是 ADC0832 这类串行 ADC8 位分辨率两线CLK DIO就能读成本几块钱另一条是拿 LM393 比较器搭个阈值电路只输出「亮/暗」两个电平电路更简单但只能定一个死阈值调节要拧电位器。这个项目选了 ADC0832因为要「按环境光分档提示」而分档就得有数值。ADC0832 是 8 位、双通道、串行输出转换时间约 32 微秒DI 和 DO 可以复用一根线因为读和写不同时进行这样只占 3 个 IO。代价是时序必须自己用软件模拟CLK 高低电平的间隔、起始位的发送顺序错一步读回来的就是 0 或者 255。第 3 章会给出完整的读时序代码。2.4 引脚分配表把引脚先定死后面写代码和焊板子都以这张表为准能省掉大量「代码里是 P1^2、板子上接的是 P1^3」的返工。功能引脚方向备注LCD1602 数据口 D0-D7P0.0-P0.7输出必须外接 10K 排阻上拉LCD1602 RSP2.0输出高电平选数据寄存器LCD1602 RWP2.1输出本方案固定拉低只写不读LCD1602 ENP2.2输出下降沿锁存ADC0832 CSP1.0输出低电平片选有效ADC0832 CLKP1.1输出软件模拟时钟ADC0832 DIOP1.2双向读之前要先释放为输入超声波 TRIGP1.3输出10 微秒以上高脉冲触发超声波 ECHOP1.4输入高电平宽度即回波时间蜂鸣器P1.5输出经三极管驱动低电平响模式键P3.2输入外部中断 0顺手可用加/减键P3.3 / P3.4输入调阈值、调时间提示P0 口内部是开漏结构做输出时必须外接上拉电阻这一条几乎每个用 LCD1602 的新手都会漏。3. 硬件链路拆解从光敏电阻上的电压到蜂鸣器那一声响整块板子的信号流其实只有三条光敏分压 → ADC0832 → 单片机超声波发射 → 回波 → 单片机单片机 → 三极管 → 蜂鸣器。三条链路各自独立出问题时可以分开验证不用整板怀疑。这一章按信号顺序把每条链路的关键电路和它对应的驱动代码过一遍。3.1 最小系统晶振、复位、电源三件事最小系统是所有问题的根。STC89C52 的最小系统就三个部分晶振电路、复位电路、电源去耦。晶振用 12MHz两只 22pF 瓷片电容接地走线尽量短别从 LCD 数据线底下穿过去否则显示会莫名其妙地闪。复位用的是 10 微法电解电容加 10K 电阻的经典 RC 电路上电瞬间 RST 保持高电平约 1 毫秒完成复位。电源这块是新手最容易省的地方。USB 供电或者 5V 电源适配器进来之后板子上至少要有两个 0.1 微法瓷片电容分别放在单片机 VCC 脚和 LCD 背光附近再加一个 100 微法电解电容做储能特别是蜂鸣器响的瞬间电流会有一个尖峰没有这个大电容顶着单片机会直接复位——这个坑在第 5 章还会细说。/* 最小系统的软件侧体现时钟与复位之后第一件事是关中断、清状态 */ #include reg52.h void MCU_Init(void) { EA 0; /* 先关总中断避免初始化期间被打断 */ TMOD 0x11; /* T0 方式1 做秒基准T1 方式1 做超声波计时 */ TH0 0x3C; /* 12MHz 下 50ms 定时初值 65536-500000x3CB0 */ TL0 0xB0; TH1 0x00; TL1 0x00; IT0 1; /* 外部中断0 下降沿触发接模式按键 */ EX0 1; EA 1; }这段初始化看着平淡但顺序有讲究。TMOD一次性写0x11是因为两个定时器都要用方式 116 位分开写容易出现「改了 T0 把 T1 的高四位顺手清零」的低级错误。EA放在最后开是为了让前面的赋值全部落地之后再允许中断进来。实际调试时我习惯在MCU_Init之后加一句点亮某个 LED确认程序真的跑到了这里再往下进行——比盯着屏幕猜强。3.2 光敏电阻加 ADC0832 的模拟前端光敏电阻和一只 10K 固定电阻串联分压中间点接 ADC0832 的 CH0。光照强光敏阻值小分压点电压低光照弱阻值大分压点电压高。这个方向性很容易搞反接反了就会出现「开灯反而提示光线暗」的滑稽现象调试时先拿手电筒照一下看数值往哪个方向走比翻资料快。ADC0832 的读时序是纯粹的软件模拟核心是三步拉低 CS 片选串行送出起始位和通道选择位然后把 DIO 释放成输入逐位读回 8 位数据高位在前。sbit ADC_CS P1^0; sbit ADC_CLK P1^1; sbit ADC_DIO P1^2; /* 读 CH0 单端输入返回 0~255数值越大表示环境越暗 */ unsigned char ADC0832_Read(void) { unsigned char i; unsigned char dat 0; ADC_CS 0; /* 片选有效 */ ADC_CLK 0; ADC_DIO 1; /* 起始位 */ ADC_CLK 1; ADC_CLK 0; ADC_DIO 1; /* SGL/DIF 1单端输入 */ ADC_CLK 1; ADC_CLK 0; ADC_DIO 0; /* ODD/SIGN 0选 CH0 */ ADC_CLK 1; ADC_CLK 0; ADC_DIO 1; /* 释放总线交给芯片驱动准备读 */ for (i 0; i 8; i) { ADC_CLK 1; /* 上升沿之后数据有效 */ ADC_CLK 0; dat 1; if (ADC_DIO) { dat | 0x01; } } ADC_CS 1; /* 释放片选 */ return dat; }ADC_DIO 1这一句在读取之前是必须的。51 的准双向口写 1 之后才真正变成弱上拉输入如果不写 1 直接读读回来永远是上一次输出的电平表现出来就是「数值纹丝不动」。这是 ADC0832 调试里出现频率最高的原因没有之一。采样之后不建议直接拿单次结果做判断环境光有工频闪烁读数会在小范围内抖。我的做法是连采 5 次去掉最高最低各一个剩下 3 个求平均用一个长度为 5 的unsigned char数组滚存中位值滤波在 51 上跑起来也就几十微秒的事。3.3 LCD1602 的八位并口接线与时序LCD1602 走 8 位并行数据口接 P0控制线 RS、RW、EN 分别接 P2.0 到 P2.2。RW 直接接地是最省事的做法——只写不读不看忙标志位靠固定延时保证每条指令执行完毕。这个取舍在正式产品里不合适但在这种刷新频率极低的系统里完全够用代价只是每条命令多等 1 到 2 毫秒。#define LCD_DATA P0 sbit LCD_RS P2^0; sbit LCD_RW P2^1; sbit LCD_EN P2^2; void DelayMs(unsigned int ms) { unsigned int i, j; for (i 0; i ms; i) for (j 0; j 120; j); /* 12MHz 下约 1ms按需微调 */ } void LCD_WriteCmd(unsigned char cmd) { LCD_RS 0; /* 指令寄存器 */ LCD_RW 0; /* 写 */ LCD_DATA cmd; DelayMs(1); LCD_EN 1; DelayMs(1); LCD_EN 0; /* 下降沿锁存 */ } void LCD_WriteData(unsigned char dat) { LCD_RS 1; /* 数据寄存器 */ LCD_RW 0; LCD_DATA dat; DelayMs(1); LCD_EN 1; DelayMs(1); LCD_EN 0; } void LCD_Init(void) { DelayMs(15); /* 上电后必须等小于 15ms 会初始化失败 */ LCD_WriteCmd(0x38); /* 8 位数据两行显示5x7 点阵 */ LCD_WriteCmd(0x0C); /* 开显示关光标关闪烁 */ LCD_WriteCmd(0x06); /* 写入后地址自动加 1 */ LCD_WriteCmd(0x01); /* 清屏 */ DelayMs(5); }DelayMs(15)这一句千万别删。LCD1602 内部的控制芯片上电之后需要一段内部复位时间手册上写的是大于 15 毫秒实测在电源缓慢上升的情况下还要更久。少了这一步屏幕大概率是只有背光、一片蓝什么都不显示。写字符串的函数封装一下把「第几行第几列」折算成 DDRAM 地址后面界面逻辑就只用关心内容/* x: 0~15 列y: 0 或 1 行 */ void LCD_ShowStr(unsigned char x, unsigned char y, unsigned char *str) { LCD_WriteCmd((y ? 0xC0 : 0x80) x); while (*str ! \0) { LCD_WriteData(*str); } }3.4 超声波模块的距离换算超声波模块HC-SR04 这类的用法是TRIG 脚给一个大于 10 微秒的高脉冲模块自动发 8 个 40kHz 方波然后把 ECHO 拉高高电平持续的宽度就是从发射到收到回波的时间。声速在常温下约 340 米每秒也就是每微秒 0.034 厘米往返所以除以 2最终得到经验公式距离厘米 高电平时间微秒÷ 58。12MHz 晶振下机器周期正好 1 微秒定时器每计一个数就是 1 微秒换算直接是整数除法不需要任何补偿。这也是当初坚持用 12MHz 而不是 11.0592MHz 的原因——后者对串口波特率友好但这个项目不用串口。量程方面模块标称 3 厘米到 90 厘米近端盲区在 3 厘米以内测不到远端误差会涨到 1 到 2 厘米。做坐姿提示够用了因为真正要判断的是「人离桌面小于 30 厘米」这个区间正好在模块的舒适区。3.5 蜂鸣器驱动为什么必须加一只三极管蜂鸣器分有源和无源两类这个项目用的是无源压电式需要单片机输出方波才能发声。无源蜂鸣器的工作电流在 20 到 30 毫安而 51 的单个 IO 口灌电流能力有限直接驱动会拉低整个电源电压表现就是「一响就复位」。正确做法是加一只 NPN 三极管做开关基极串 1K 电阻限流发射极接地集电极接蜂鸣器负极蜂鸣器正极接 5V。单片机输出高电平时三极管导通、蜂鸣器得电。有些电路写成低电平响那就在软件里取反别改硬件。sbit BEEP P1^5; void BEEP_On(void) { BEEP 1; /* 高电平导通 NPN 三极管 */ } void BEEP_Off(void) { BEEP 0; } /* 短鸣 n 次用于距离过近提示 */ void BEEP_Beep(unsigned char n) { unsigned char i; for (i 0; i n; i) { BEEP_On(); DelayMs(80); BEEP_Off(); DelayMs(120); } }用DelayMs做鸣叫会阻塞主循环蜂鸣期间不能刷新屏幕也不能测距。短促的提示音可以接受如果要做「连续滴滴滴直到人坐正」就得改成由定时器中断翻转电平主循环只负责置标志位。第 6 章改造时会用到这个思路。4. 软件结构一个主循环加两个定时器怎么把四件事串起来软件部分最怕写成流水账读光线、读距离、刷屏、判超时四段代码平铺在main里每一段都带死等延时结果就是屏幕刷新卡顿、超声波时不时丢一次、按键按下去没反应。这个项目的结构是「主循环轮询 定时器 0 出秒基准 定时器 1 专供超声波计时」四件事各归各位互不阻塞。4.1 主循环状态机与模式切换主循环不做具体计算只做三件事采样、判断、刷新。采样部分把三路传感器的原始值读进全局变量判断部分根据当前模式决定报警标志刷新部分按 200 毫秒节流更新屏幕。模式分自动和手动两种。自动模式下光线暗自动提示护眼、距离近自动提示坐正手动模式下这两项提示关掉只保留久坐计时提醒适合在光线本来就暗或者需要近距离看书的场景下用。unsigned char g_mode 1; /* 1自动0手动 */ unsigned char g_light 0; /* 光线原始值 0~255 */ unsigned int g_dist 0; /* 距离单位 cm */ unsigned int g_sec 0; /* 累计秒数 */ unsigned int g_limit 45 * 60; /* 久坐阈值默认 45 分钟 */ bit g_alarm_dist 0; bit g_alarm_light 0; bit g_alarm_time 0; void main(void) { unsigned int refresh 0; MCU_Init(); LCD_Init(); while (1) { /* 1. 采样 */ g_light ADC0832_Read(); /* 每 10 次主循环测一次距离避免超声波发得太密互相干扰 */ if (refresh 10) { refresh 0; g_dist Ultrasonic_Measure(); LCD_Refresh(); } /* 2. 判断 */ if (g_mode) { g_alarm_dist (g_dist 0 g_dist 30) ? 1 : 0; g_alarm_light (g_light 180) ? 1 : 0; /* 数值越大越暗 */ } else { g_alarm_dist 0; g_alarm_light 0; } g_alarm_time (g_sec g_limit) ? 1 : 0; /* 3. 执行 */ if (g_alarm_dist || g_alarm_light || g_alarm_time) { BEEP_Beep(1); } Key_Scan(); } }g_light 180这个阈值不是拍脑袋来的是标定出来的拿手机照度计放旁边暗到需要开灯的临界点把 ADC 值记下来再留 20 的余量。g_dist 30里的 30 厘米也是量出来的——让被测的人按正常坐姿坐好读几次取平均再往下压 5 厘米作为触发线免得稍微动一下就报警。4.2 LCD1602 驱动层按位置刷字符串LCD_Refresh是界面层唯一的出口把全局变量格式化成两行显示。第一行显示光线档位和距离第二行显示已用时间和设定时间。数字转字符串在 51 上不能用sprintf太占 Flash 和栈自己写个两位整数转字符串的小函数。/* 把 0~99 的整数转成两位 ASCII写入 buf末尾不带结束符 */ void UInt2Str(unsigned char val, unsigned char *buf) { buf[0] 0 (val / 10) % 10; buf[1] 0 val % 10; } void LCD_Refresh(void) { unsigned char buf[6]; /* 第一行L:xxx D:xxCM */ LCD_ShowStr(0, 0, L); UInt2Str(g_light / 10, buf); /* 0~255 压缩到两位显示 */ LCD_ShowStr(2, 0, buf); LCD_ShowStr(6, 0, D); UInt2Str(g_dist 99 ? 99 : g_dist, buf); LCD_ShowStr(8, 0, buf); /* 第二行T:xx/45 */ LCD_ShowStr(0, 1, T); UInt2Str(g_sec / 60, buf); LCD_ShowStr(2, 1, buf); LCD_ShowStr(4, 1, /45); }刷新频率控制在 200 毫秒一次。太快了屏幕会闪因为每次都是全清重写太慢了数值跳变明显。想彻底不闪可以在写新内容前先比较旧内容只更新变化的字符位代价是多占用十几字节 RAM。4.3 光线分档与提示策略光线值不直接显示原始 ADC 值那样用户看不懂。分三档0 到 90 是「亮」90 到 180 是「正常」180 以上是「暗」。只有「暗」档才触发报警前两档只在屏幕上有区分。这里有个经验人眼对亮度的感知是对数的ADC 值是线性的所以单纯按数值分档会感觉「灯明明还挺亮却已经报警了」。补偿办法是把分段点往前挪宁可多报几次也别漏报因为漏报的后果是用眼健康多报只是烦一下。4.4 定时器 0 的秒基准与久坐提醒秒基准靠定时器 0 每 50 毫秒中断一次中断里数 20 次得到 1 秒。这个中断服务程序必须极其短只做计数和置标志不做任何延时和显示否则会把主循环的时间片吃掉。void Timer0_ISR(void) interrupt 1 { static unsigned char tick 0; TH0 0x3C; /* 重装初值方式1不会自动重装 */ TL0 0xB0; if (tick 20) { /* 20 × 50ms 1s */ tick 0; if (g_sec 60000) { g_sec; } if (g_sec g_limit) { g_alarm_time 1; } } }方式 1 是 16 位定时器溢出后不会自动重装初值必须在中断里手动写TH0和TL0而且要在中断开头写缩短从溢出到重装之间的误差累积。写晚了每次中断会多吃几十个机器周期一天下来时间会慢出好几秒。阈值g_limit默认 45 分钟可以通过按键在 15 到 90 分钟之间以 5 分钟为步进调整。调整时直接改这个变量不需要重启定时器因为比较发生在每秒的判断里。4.5 超声波测距的非阻塞写法最早的写法是while(ECHO 0);死等结果人把手拿开、模块没收到回波的时候程序就永远卡在那一行屏幕不再刷新按键也没反应。这种现象就是典型的黑匣子——外人看来板子「死机」了其实是程序困在一个循环里。改进思路有两个一是用定时器 1 计时并靠溢出标志判断超时二是设一个软件计数上限。前者更准因为有 16 位硬件计数后者更简单但误差大。unsigned int Ultrasonic_Measure(void) { unsigned int t; TRIG 0; DelayMs(1); TRIG 1; DelayMs(1); /* 约 1ms远大于要求的 10us稳妥 */ TRIG 0; TH1 0; TL1 0; TF1 0; TR1 1; /* 开始计时 */ while (ECHO 0) { /* 等回波起始超时则放弃本次测量 */ if (TF1) { TR1 0; TF1 0; return 999; } } TH1 0; /* 从回波真正拉高开始重新计 */ TL1 0; while (ECHO 1) { if (TF1) { TR1 0; TF1 0; return 999; } } TR1 0; t (TH1 8) | TL1; /* 单位微秒12MHz */ if (t 580) { return 999; /* 小于 10cm落在盲区认为无效 */ } return t / 58; /* 换算成厘米 */ }返回 999 表示「本次测量无效」主循环拿到这个值之后跳过距离判断而不是当成一个真实的 999 厘米。这一点很容易写错如果把无效值直接参与比较g_dist 30永远不成立报警就永远不触发看起来像传感器坏了。提示连续测距之间至少留 60 毫秒模块的余波没散干净就发下一次读数会乱跳。5. 常见问题排查调这套板子翻车最多的五个地方硬件系统调试有个规律九成的问题出在电源、地线和一个被忽略的上拉电阻上真正坏芯片的概率极低。下面这五条是按我踩坑的顺序排的每条都写清楚现象、原因和解决办法照着排查能省掉大量换器件的时间。5.1 屏幕只有背光一行字符都不显示现象上电后背光亮着屏幕一片蓝或者顶端一排黑方块程序烧录正常蜂鸣器也能响。原因三种可能按概率排。第一是对比度引脚 VEE第 3 脚悬空或者接错这个脚需要接一个 10K 电位器分压接地对比度最高接 5V 最低悬空则没有偏压液晶分子不翻转看起来就是一片蓝。第二是初始化时没等那 15 毫秒控制芯片还没准备好就收到了指令全部被丢弃。第三是 RW 接高电平却没实现读忙检测总线一直在冲突。解决先把电位器拧到中间位置试多数情况下立刻就能看到字符。如果不行拿万用表量 VEE 脚对地电压应该在 0 到 1V 之间可调。确认电压正常再看初始化代码把上电延时加长到 50 毫秒再试。RW 一律接地用固定延时替代忙检测简单可靠。5.2 光线值纹丝不动永远是 0 或者 255现象用手电筒照光敏电阻、用手捂住屏幕上那个数值都不变。原因ADC0832 的 DIO 脚在读数据前没有写成 1。51 的准双向口内部有弱上拉写 0 之后是强下拉写 1 之后才是弱上拉输入。如果上一次操作把它写成了 0后面直接读引脚读回来的永远是 0凑满 8 位就是 0x00。反之如果外部上拉很强而芯片没输出读回来就是 0xFF。解决在for循环读 8 位之前加一句ADC_DIO 1;把口线释放出来。另外检查片选 CS 是否真的拉低过——有的模块把 CS 直接焊死在低电平代码里再去控制一个并不存在的引脚时序就全乱了。用示波器看 CLK 脚应该有 11 个脉冲1 个起始位 2 个通道位 8 个数据位数量不对就是时序写错了。5.3 人坐着不动距离读数在 20 到 200 之间乱跳现象屏幕上的距离数字一秒一个值完全没法用来判断。原因超声波模块对供电噪声和余波特别敏感。最常见的是两次测距间隔太短上一次的余波还在空气里荡被这一次当成回波收回来了算出来的时间变短距离就偏小。第二个原因是模块的 Trig 和 Echo 走线和其他信号线捆在一起串扰导致误触发。第三个原因是供电纹波大模块内部比较器误判。解决把测距间隔拉到 60 毫秒以上Trig 和 Echo 用双绞或者远离 LCD 数据线模块 VCC 脚就近加一个 0.1 微法电容。软件上再做一层中位值滤波连续测 5 次排序取中间那个跳变立刻收敛。判断距离过近时用「连续 3 次都小于阈值」才报警单次触发不算数。5.4 蜂鸣器一响单片机就复位或者屏幕花一下现象静置一切正常只要蜂鸣器开始响屏幕字符乱掉程序从头开始跑。原因蜂鸣器启动瞬间的浪涌电流把 5V 电源拉下去了。无源蜂鸣器工作电流 20 到 30 毫安如果三极管基极没串限流电阻或者蜂鸣器直接挂在 IO 口上这个电流会瞬间超过整板电源的供应能力单片机电压跌破复位阈值直接重启。解决确认三极管基极串了 1K 电阻蜂鸣器两端反向并一只 IN4148 续流二极管电源入口加 100 微法电解电容。如果板子是从电脑 USB 取电USB 口的限流也可能触发换一个 5V/1A 的适配器试试。软件上避免蜂鸣器长时间连续鸣叫用间断短鸣代替长鸣发热和电流都会小很多。5.5 计时不准久坐提醒提前或者推迟触发现象实际坐了 40 分钟屏幕上显示才 32 分钟或者刚坐下十分钟就报警了。原因定时器 0 的中断服务程序里干了不该干的事。比如在里面调用了LCD_Refresh或者DelayMs一次中断耗时几毫秒20 次中断才凑一秒这期间如果被更长的任务打断秒基准就会被拉长。另一种原因是重装初值时TH0和TL0分开写中间被高优先级中断插了一脚计数值错位。解决中断服务程序只留计数和置标志所有显示和判断都挪到主循环。重装初值时先关TR0写完两个寄存器再开或者干脆换成定时器 2 的自动重装模式STC89C52 有 T2。核对实际时间可以拿手机秒表跑 10 分钟对比误差在 1 秒以内就算合格超标就说明中断里还有耗时操作。5.6 上电自检清单每次改完硬件重新上电按这个顺序过一遍能把问题定位到具体模块不用整板怀疑。顺序检查项正常表现1电源指示灯常亮不闪2单片机复位脚电压上电高电平稳定后接近 0V3LCD 背光与字符背光亮清屏后无残留4光线通道遮挡光敏电阻时数值明显变化5超声波通道手在 10 到 40cm 移动读数跟随变化6蜂鸣器手动触发短鸣屏幕不花、不复位7按键每次按下有明确响应不连击6. 二次开发把「会响的报警器」改成「能记账的坐姿记录仪」这套代码跑通之后最值得动的不是硬件是数据。现在的系统只会「响一下」响完就忘用户第二天完全不知道自己昨天坐姿有多糟。加一颗 24C02I2C 接口的 EEPROM8 字节一页成本很低把阈值和每天的累计超标分钟数存进去整套东西的性质就从「玩具」变成「有反馈的工具」。改造的关键是把所有标定参数从代码里挪出来。现在g_limit和距离阈值都写死在main里用户想改成 30 分钟就得重新烧一次固件这是血泪教训。加两个读写函数上电时读进内存按键调整时写回程序升级也不会丢配置。/* 24C02 简化读写SCL/SDA 分别接 P3^6 和 P3^7 */ #define EE_ADDR_LIMIT 0x00 /* 久坐阈值2 字节 */ #define EE_ADDR_DIST 0x02 /* 距离阈值1 字节 */ void EE_SaveConfig(unsigned int limit, unsigned char dist) { EE_WriteByte(EE_ADDR_LIMIT, (unsigned char)(limit 8)); DelayMs(5); /* 页写间隔不能省 */ EE_WriteByte(EE_ADDR_LIMIT 1, (unsigned char)(limit 0xFF)); DelayMs(5); EE_WriteByte(EE_ADDR_DIST, dist); DelayMs(5); } void EE_LoadConfig(unsigned int *limit, unsigned char *dist) { unsigned char hi EE_ReadByte(EE_ADDR_LIMIT); unsigned char lo EE_ReadByte(EE_ADDR_LIMIT 1); *limit ((unsigned int)hi 8) | lo; *dist EE_ReadByte(EE_ADDR_DIST); /* 首次上电 EEPROM 全是 0xFF兜底成默认值 */ if (*limit 300 || *limit 5400) { *limit 45 * 60; } if (*dist 10 || *dist 60) { *dist 30; } }那个「兜底」判断比读写本身更重要。全新芯片里全是 0xFF读出来是 65535如果直接拿去比较用户一坐下就报警还找不到原因。限定一个合理区间越界就回落到默认值这类防御性代码在长期运行的系统里必须写。再往前一步是统计。在定时器中断的每秒递增里顺手记一个「本分钟是否超标」的标志每满一分钟累加到当天的总超标分钟数存进 EEPROM 的另一个地址。屏幕第二行从「T:32/45」改成「T:32 S:18」前面是本次已坐时间后面是今天累计不良坐姿分钟数。用户看到这个数字每天在涨比听十次蜂鸣器都管用。想再加通信也不是不行把串口用起来按固定格式比如$LIGHT,120,DIST,35,TIME,1830*每 10 秒往外发一帧电脑端用串口助手就能存成 CSV 画曲线。但要注意 12MHz 晶振下串口波特率误差偏大9600 勉强可用115200 完全不行要么换 11.0592MHz 晶振要么接受偶尔丢包。我一开始图省事没换抓了一晚上数据发现有百分之几的帧是坏的后来老老实实换了晶振。从那以后我每次做这类小系统第一版就把阈值、标定值、状态统计全部走 EEPROM哪怕只是调个参数也要能掉电保存——硬件焊死在板子上改一次参数就要拆一次壳、插一次下载线这种后悔药吃一次就够了。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →