尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

STM32F103C8温度测量显示系统:LM75A与TM1640驱动拆解

STM32F103C8温度测量显示系统:LM75A与TM1640驱动拆解 简介基于STM32F103C8单片机的温度传感器数码管显示程序是一套面向STM32初学者的完整KEIL工程源码。项目以DS18B20/LM35等集成温度传感器为对象通过STM32内部ADC采集信号驱动数码管实时显示温度覆盖GPIO配置、ADC采样、数据处理、显示驱动及定时器/中断等嵌入式开发典型环节。压缩包共203个文件以C源文件、H头文件以及uvproj/uvopt工程配置为主同时包含O、CRF等编译中间文件并附带Hex烧录文件、Map映射表和链接脚本整体大小5.19MB可直接在KEIL μVision中打开编译便于逐模块学习和调试。当前已有171人学习下载从学习角度来看该工程既适合理解STM32F103C8的硬件接口与寄存器配置也可利用KEIL断点、变量观察等调试功能掌握基于C语言的嵌入式软件工程组织方法。工程目录结构完整、函数模块划分清晰对于正在学习STM32开发、希望从理论走向实践的读者是一份值得参考的工程样板。1. 别把温度传感器工程当成一个ADC例程拿到这份“基于STM32F103C8单片机温度传感器数码管显示”的KEIL工程很多人第一反应是“ADC采集NTC然后查表转温度”但打开文件列表会发现完全不是这么回事工程里同时出现了 lm75a.c、tm1640.c、key.c、encoder.c、usart.c、flash.c。LM75A是I2C接口的数字温度传感器TM1640是两线串行数码管驱动芯片按键、编码器、串口、Flash存储也全都占了一个模块。这说明它不是一个单点温度检测而是一个带人机交互和掉电保存的完整小系统。对刚入门STM32的开发者来说这类工程比点灯例程多一口气比完整产品少很多业务逻辑最适合拿来搞明白“外设驱动怎么拼到一个while循环里”。下面按从底层外设到应用逻辑的顺序拆这份源码并给出能直接验证的关键函数和参数。2. STM32F103C8的KEIL工程骨架与模块划分2.1 从启动文件到外设驱动工程里各文件在干嘛先看整体。工程文件列表里有 stm32f10x_it.c、delay.c、flash.c、usart.c、key.c、tm1640.c、lm75a.c、encoder.c、buzzer.c这些文件已经能组成一个完整的人机交互层。常见课程设计只写一个 main.c而这个工程把外设拆成了独立 .c/.h编译时还会出现 touch_key.__i 这类中间文件。这里的 .i 是 KEIL 在预处理阶段生成的文件touch_key.__i 展开后能看到所有宏替换结果。如果你怀疑某个引脚定义宏没生效直接打开 .i 文件搜宏名比在源码里来回跳转准很多。文件职责说明main.c主流程外设初始化循环调用各模块stm32f10x_it.c中断服务SysTick、EXTI、串口中断入口lm75a.cI2C温度采集读取LM75A温度寄存器tm1640.c数码管显示驱动把显示缓存写入TM1640key.c按键扫描检测独立按键调节显示模式/阈值encoder.c旋转编码器输入查询或外部中断方式读取增量usart.c串口打印重定向printf输出调试信息buzzer.c蜂鸣器控制温度超限报警flash.cFlash读写保存阈值、校准偏置从这张表能看出工程的数据流方向LM75A把温度读进内存TM1640把温度显示出去按键和编码器负责改参数Flash保存参数USART负责把中间过程吐给电脑。开发时按这个思路去定位问题比一行行读源码快得多。STM32F103C8是64KB Flash的中容量产品启动文件必须选 stm32f10x_md.smedium density而不是大容量的 ld.s。如果你从别的板子复制工程最常见报错是Error: L6218E: Undefined symbol SystemInit。这个错通常不是代码问题而是启动文件没匹配上或者 stm32f10x.h 里的器件宏写成了 STM32F10X_HD。检查顺序先看启动文件再看宏定义最后看 KEIL 的 Device 型号是不是 STM32F103C8。2.2 main.c里的循环框架不要在while里写死一套流水账标准外设库SPL工程的 main.c 结构一般是这样先 SystemInit再初始化各外设然后 while(1) 循环。这套工程稍微进阶一点从文件分区能看出来它采用了“模块状态 轮询主循环”的写法按键和数码管刷新放在主循环I2C采集和编码器扫描用定时器节拍控制。下面是一个符合这套结构的 main.c 骨架#include stm32f10x.h #include lm75a.h #include tm1640.h #include key.h #include usart.h #include delay.h int main(void) { uint16_t temp 0; // 原始温度寄存器值 int16_t temp_c 0; // 换算后的温度单位0.1℃ uint8_t show_buf[8] {0}; // 数码管显示缓存 SystemInit(); delay_init(72); // 配置SysTick72MHz主频 USART1_Init(115200); // 串口调试 LM75A_Init(); // 初始化I2C引脚 TM1640_Init(); // 初始化数码管驱动 KEY_Init(); // 按键GPIO输入 Encoder_Init(); // 编码器GPIO输入 while (1) { temp LM75A_ReadTemp(); // 轮询读LM75A temp_c LM75A_Convert(temp); // 换算成0.1℃ TM1640_ClearBuf(show_buf); TM1640_FormatTemp(show_buf, temp_c); TM1640_Update(show_buf); // 一次性写入TM1640 KEY_Scan(); // 按键检测 Encoder_Scan(); // 编码器增量读取 delay_ms(50); // 刷新率约20Hz } }这段代码按顺序做了三件事读取温度、刷新显示、处理输入。每个外设都只暴露一个精简接口main.c 不关心 I2C 时序怎么折腾。注意delay_ms(50)不是随手加的它同时限制了数码管刷新频率和按键消抖时间如果把主循环改成 5ms 一次TM1640 亮度会提高但 CPU 占用上升按键消抖逻辑也要重新设计。常见做法的刷新节拍在 1020Hz 之间。这套工程真正的价值在于实时性要求不高的输入输出全部放进轮询循环而不是开一堆中断。新手模仿时最容易犯的错是把 I2C 读温度放进定时器中断里实际上 I2C 在中断里容易卡住总线因为中途不能被其他中断抢占。这个工程没有把 lm75a 采集放进中断本身就是在告诉你这类慢速外设更适合放在主循环里用时间片调度。3. LM75A温度传感器I2C读取与数据处理3.1 LM75A的寄存器约定与地址计算LM75A 是一个 I2C 接口的数字温度传感器量程 -55℃125℃典型精度 ±2℃。和 NTC 热敏电阻方案比它不需要查表或多项式拟合寄存器里直接给出二进制温度值。工程里选择它做数码管显示是合理的省掉了 ADC 和线性度校正代价是多写一个 I2C 驱动。NTC 方案虽然成本低但在宽温度范围下需要标定和查表像这种课程设计演示程序用集成传感器反而更干净。从硬件连接说LM75A 的 7 位从机地址默认是 0b10010000x48由 A0A2 引脚决定。7 位地址左移一位分别得到 8 位写地址 0x90 和读地址 0x91。如果你的板子上 A0/A1/A2 有跳线地址会变成 0x490x4F对应写地址 0x920x9E。工程里 lm75a.h 最好用宏定义地址换板子时只改宏不用改时序代码。内部寄存器最常用这几个寄存器地址功能默认值温度寄存器0x00存储最近一次温度转换结果只读配置寄存器0x01设定模式、故障队列、极性0x00迟滞阈值0x02低于该值解除报警75℃过温阈值0x03超过该值触发OS输出80℃LM75A 温度数据是 16 位寄存器实际只有高 11 位有效每一位代表 0.125℃负温度用二进制补码表示。转换时把 16 位右移 7 位得到一个 16 位有符号整数再乘 0.125 就是摄氏度。这个格式比 DS18B20 的 12 位格式更容易处理因为不需要考虑小数位拼接和符号扩展的边界条件。3.2 I2C读温度模拟总线代码与误差处理标准固件库提供硬件 I2C但硬件 I2C 在频繁中断时容易卡住总线很多老工程师宁可用 GPIO 模拟。这套工程从 lm75a.c 独立模块看不出具体实现但模拟 I2C 在单一传感器场景下更稳下面给出一套可替换到工程的读取流程// lm75a.c 模拟I2C读取温度寄存器 #define LM75A_ADDR_W 0x90 // 7位地址0x48左移一位 static void I2C_Start(void) { GPIO_SetBits(GPIOB, GPIO_Pin_6); // SCL拉高 GPIO_SetBits(GPIOB, GPIO_Pin_7); // SDA拉高 GPIO_ResetBits(GPIOB, GPIO_Pin_7); // SCL高时SDA拉低产生起始条件 } uint16_t LM75A_ReadTemp(void) { uint8_t buf[2]; uint16_t raw; I2C_Start(); I2C_SendByte(LM75A_ADDR_W); // 写地址写操作等待ACK I2C_SendByte(0x00); // 指向温度寄存器 I2C_Start(); // 重启I2C切换读方向 I2C_SendByte(LM75A_ADDR_W | 1); // 写地址读操作 buf[0] I2C_RecvByte(1); // 读高字节主机回ACK buf[1] I2C_RecvByte(0); // 读低字节主机回NACK I2C_Stop(); raw ((uint16_t)buf[0] 8) | buf[1]; return raw; } int16_t LM75A_Convert(uint16_t raw) { int16_t temp (int16_t)raw; return (int16_t)(temp 7); // 右移7位得到0.125℃为单位的有符号值 }LM75A_ReadTemp里两次 Start 是 I2C 读指定寄存器的标准做法第一次先写从机地址和寄存器地址第二次重新产生起始条件然后改成读方向。第二次发送的地址LM75A_ADDR_W | 1就是 0x91末尾的 1 表示读。如果地址不对SDA 上收不到 ACKI2C_SendByte里应该在第九个时钟周期检查 SDA 状态并返回错误码而不是闷头继续发。LM75A_Convert返回值的单位是 0.125℃也就是 200 代表 25.0℃。显示一位小数时可以先用整数运算转换成 0.1℃ 单位temp_10x (LM75A_Convert(raw) * 125) / 1000;这样全程不用浮点在 STM32F103C8 上也能减少基准库开销。负温度时temp 7因为有符号数的算术右移结果仍然是负数不需要额外处理符号位。关于 ACK/NACK模拟 I2C 接收最后一个字节前要发 NACK通知从机传输结束。很多新手在读低字节时仍发 ACK导致 LM75A 继续输出数据下一次读时序就错位。代码里I2C_RecvByte(0)的 0 就是 NACK这个细节最容易丢。排查时拿逻辑分析仪抓 SCL/SDA重点看第九个时钟 SDA 是否被拉低ACK 只在最后一个字节之前出现。如果你想在原工程基础上换 DS18B20 或者 NTC改动点只集中在 lm75a.c 的接口实现里main.c 不用变。这也说明把外设封装成“读原始值转换”两个函数的接口比直接把 I2C 逻辑写在 main 里可维护得多。4. TM1640数码管驱动与显示刷新逻辑4.1 两线串行协议不是I2C别混用TM1640 是 8×8 LED 矩阵驱动芯片用在数码管上就是一颗芯片扫 8 位数码管。它只用了 DIN 和 CLK 两根线时序像 SPI但协议不是标准 I2C写操作由起始条件CLK 高时 DIN 拉低开始随后发送命令字节和数据字节每字节低位在前。很多初学者因为没有看数据手册直接拿模拟 I2C 代码改结果数码管乱码。TM1640 的完整写流程是三条命令TM1640_WriteCmd(0x40); // 数据命令固定地址模式写显示寄存器 TM1640_WriteCmd(0xC0); // 显示地址起始从第一个寄存器开始 for (i 0; i 8; i) TM1640_WriteData(display_buf[i]); // 连续写入8个段码 TM1640_WriteCmd(0x8F); // 显示控制开显示亮度最高命令 0x40 表示写显示寄存器0xC0 是第一个显示寄存器地址之后每写一个数据字节地址自动加 1。最后一个 0x8F 是显示开 亮度等级低 4 位可以设为 0x8C0x8F亮度逐级变化。如果把 0x8F 换成 0x88显示会变暗很多适合夜间环境。注意 TM1640 不像 GPIO 动态扫描那样需要手动位选它内部自己扫 8 位数码管这大大简化了软件也释放了至少 12 个 GPIO。相比六位数码管动态扫描驱动的经典方案TM1640 把扫描移到了芯片内部程序里只需要维护一块显示缓存。4.2 段码表、负号和一位小数显示数码管显示的基础是段码映射。常规共阴数码管段码dp,g,f,e,d,c,b,a取值如下字符0123456789-段码0x3F0x060x5B0x4F0x660x6D0x7D0x070x7F0x6F0x40如果板子上的数码管用共阳接法段码表要整体取反例如 0x3F 变 0xC0。工程里 tm1640.c 的段码表若和上面不同优先检查原理图不要硬改表。小数点段是最高位 0x80在 TM1640 的位映射里一般对应 DP 引脚。下面是把温度值写入显示缓存并刷新的函数包含负号和一位小数// tm1640.c 温度显示转换 void TM1640_FormatTemp(uint8_t *buf, int16_t temp_10x) { uint8_t i; uint8_t digit[4] {0}; uint8_t neg 0; if (temp_10x 0) { // 负温度记录符号转绝对值 neg 1; temp_10x -temp_10x; } // temp_10x 是0.1℃单位比如253表示25.3℃ digit[0] temp_10x / 1000; // 十位 digit[1] (temp_10x / 100) % 10; // 个位 digit[2] (temp_10x / 10) % 10; // 十分位 for (i 0; i 8; i) buf[i] 0x00; // 清空缓存 buf[0] digit[0] ? SegCode(digit[0]) : 0x00; // 十位为0熄灭 buf[1] SegCode(digit[1]) | 0x80; // 个位加点 buf[2] SegCode(digit[2]); // 十分位 if (neg) buf[0] 0x40; // 负号显示在十位位置 }SegCode是从段码表里查字符的函数temp_10x是第 3 章转换后的 0.1℃ 整数。这段代码把 25.3℃ 拆成十位 2、个位 5、十分位 3分别放入缓存的前三个位置。buf[1]用| 0x80点亮小数点这样显示效果是 25.3。十位为 0 时熄灭而非显示 0是为了让显示更清爽如果温度超过 100℃digit 数组要扩到 5 位否则显示会截断。TM1640 的刷新和动态扫描最大的区别是芯片内部锁存了每个显示寄存器的数据所以TM1640_Update只需在显示内容变化时调用一次不需要 1ms 一次连续刷。常见误用是在 while 循环里疯狂写 TM1640除了让 GPIO 翻转变频繁外没有任何好处还可能造成亮度不均和两线总线冲突。更好的做法是只在检测到温度值变化或按键按下时更新一次主循环里用一个display_dirty标记判断是否需要调用TM1640_Update。5. USART调试、Flash掉电保存与编码器联调5.1 printf重定向看温度比看数码管快调试阶段数码管只能展示最终结果看不到 I2C 波形里的 ACK 状态。把 USART1 接出来用串口打印原始寄存器值能少走一半弯路。工程里的 usart.c 几乎肯定做了 printf 重定向核心是重写fputcint fputc(int ch, FILE *f) { while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET) ; USART_SendData(USART1, (uint8_t)ch); return ch; }使用前提是 usart.c 里已经把 PA9 配置成 USART1_TX 并启用了串口时钟。调用printf(raw%04X temp%d.%d\n, raw, temp/10, temp%10);时能看到原始寄存器值和换算后的整数温度。如果打印出来 raw 一直是 0xFFFF多半是 SCL/SDA 引脚没有配置成开漏输出并外接 4.7k 上拉这是 I2C 最常见的问题比代码逻辑更值得先排查。5.2 把阈值和校准值写进Flash重启不丢这个工程里有 flash.c典型用途是保存用户设定的过温报警阈值和温度校准偏置。STM32F103C8 的内部 Flash 一般是 64KB常见的做法是把自定义参数放在最后一页的末尾加一个魔法数做有效性标记typedef struct { uint32_t magic; // 0xA55A0001标识参数有效 int16_t alarm_th; // 报警阈值0.1℃单位 int16_t cal_offset; // 校准偏置 } SysParam; void SaveParam(SysParam *p) { FLASH_Unlock(); FLASH_ErasePage(0x0800FC00); // 64KB芯片最后一页 FLASH_ProgramWord(0x0800FC00, p-magic); FLASH_ProgramWord(0x0800FC04, (uint32_t)p-alarm_th); FLASH_ProgramWord(0x0800FC08, (uint32_t)p-cal_offset); FLASH_Lock(); }参数写完后要调用FLASH_Lock否则后面任何误操作都可能把程序区擦掉。读回来时先检查 magic不是预期值就用默认参数。编码器在这里的作用是快速调节阈值一圈按 0.5℃ 步进比按键快很多配合按键把旋钮定义成“设置模式”下的增量调节能在一个 while 循环里完成阈值修改。验证方法很直接把手放在 LM75A 上串口温度上升约 1℃ 后编码器旋到阈值以下让蜂鸣器停止这个过程同时验证 I2C 读取、显示刷新、Flash 存取、报警比较四段逻辑。这几条链路全部走通后再上逻辑分析仪抓 TM1640 的 DIN/CLK正常应该能看到以 0x40、0xC0 开头的数据帧这是最后一道硬件时序的确认。本文还有配套的精品资源点击获取
返回列表