简介:面向STM32单片机学习者与电子设计备赛者,这套环境监测系统资料包含Proteus仿真图、完整源代码及讲解演示视频,覆盖有毒气体检测、温湿度采集、LCD1602实时显示与超限声光报警等功能,能够直接支撑课程设计、毕业设计或竞赛方案的快速搭建与二次开发。资源包共221个文件,压缩包约19.22MB,以.h头文件、.c源码、STM32工程文件(uvprojx/uvoptx)、Proteus仿真工程(pdsprj)、可烧录hex、avi演示视频等为主,另有bat辅助脚本、汇编文件及文档说明,目录结构清晰,便于按源码、仿真、视频分段查阅。已有432人学习下载。借助附带讲解演示视频和详细说明,可以直观理解信号采集、LCD显示、报警阈值设置等关键环节;配合源码与仿真图,读者还能看到STM32的ADC、定时器等外设配置方法,即使初学者也能完成从环境监测功能验证到实际调试的全过程。
1. 从宿舍到实验室:一件STM32环境监测盒子要拆成哪些模块
花三四十块买一个电子温湿度计,放桌上能看个数字,可数据拿不出来、告警逻辑锁死在固件里、连不上自己的上位机,这是成品设备最大的问题。标题里说的这套STM32环境监测系统,是把整条链路拆开摆在你面前:传感器采集、数据处理、显示告警、Proteus仿真、源代码、讲解视频一次配齐,你可以从零开始复现,也可以拿它当课设底子往里加模块。做毕业设计的人需要它把“原理图+代码+演示”三个得分点凑齐;刚入行的工程师需要它理解单总线时序、ADC多通道采集和串口调试这几件基本功。全文按“选型→驱动→仿真→校准”推进,代码以标准外设库为核心,仿真是Proteus 8环境,最后落到用串口和两点标定把仿真数据变成真实量程。
2. 硬件选型:STM32环境监测系统的传感器配置与电路设计要点
2.1 为什么是STM32F103,传感器选型表
环境监测系统的主控选择,常见的做法是STM32F103C8T6,而不是F407或H7。原因很直接:这个场景全是低速信号——温湿度采样周期秒级,烟雾浓度变化分钟级,光照缓变,对主频和浮点运算几乎没有压力。F103C8T6有64KB Flash、20KB RAM,跑标准外设库或HAL库都足够,Proteus里的仿真型号STM32F103R6也和它兼容。换成F103RCT6也完全可以,只是引脚多出来的部分用不到罢了。
传感器选型是这套系统最值得先定下来的表,我一般按下面的逻辑选,而不是看哪个模块便宜就买哪个。
| 监测对象 | 传感器型号 | 输出类型 | 接口方式 | 典型供电 |
|---|---|---|---|---|
| 温湿度 | DHT11 | 单总线数字 | 1-Wire GPIO | 3.3V~5V |
| 烟雾/可燃气体 | MQ-2 | 模拟电压 | ADC | 5V(加热) |
| 光照 | BH1750 | I2C数字 | I2C | 3.3V~5V |
| 雨滴/浸水 | 雨滴传感器模块 | 模拟电压 | ADC | 3.3V~5V |
| 显示 | OLED 0.96寸或LCD1602 | I2C/并口 | I2C/GPIO | 3.3V~5V |
DHT11精度不高(±2℃、±5%RH),但胜在时序简单,是学习单总线协议的最佳入门器件。如果想要更高精度,同一套代码里把DHT11换成DHT22,只需改一下数据位解析的位数和延时参数,硬件不用动。MQ-2最容易被忽略的是它需要预热,上电前几分钟输出漂移很大,代码里要做上电延时再进入正常采样。BH1750走I2C,地址固定0x23,驱动代码可以复用ST官方例程里的软件I2C实现。
2.2 传感器连接与引脚分配
引脚分配的原则是“模拟归模拟、数字归数字”。ADC引脚和GPIO数字引脚不要混用,I2C的SCL和SDA要确认是否带内部上拉,如果没有,外部要加4.7kΩ上拉电阻。我习惯把引脚规划到一张表里,画原理图和写代码都按这张表来,否则代码写一半发现引脚冲突就麻烦了。
| 外设 | 引脚 | STM32映射 | 说明 |
|---|---|---|---|
| DHT11数据 | PA0 | GPIO_AIN | 注意:这里如果接ADC会产生冲突 |
| MQ-2模拟输出 | PA1 | ADC1_IN1 | 5V供电时输出范围0~5V,需注意分压 |
| BH1750 SCL | PB6 | I2C1_SCL | 外部上拉4.7kΩ |
| BH1750 SDA | PB7 | I2C1_SDA | 外部上拉4.7kΩ |
| 蜂鸣器 | PB0 | GPIO_OUT_PP | 低电平触发,加三极管驱动 |
| OLED SCL | PB8 | I2C1_SCL(软件I2C) | 与BH1750共用总线需要地址区分 |
| OLED SDA | PB9 | I2C1_SDA(软件I2C) | 软件I2C可自定义 |
| 串口TX/RX | PA9/PA10 | USART1_TX/RX | 接USB转TTL模块 |
DHT11接PA0有个小坑:如果后续想读它的原始波形,要把它配置成推挽输出和浮空输入两种模式的切换,不要在代码初始化时一口气配成开漏。开漏虽然接上拉也能用,但高电平驱动能力弱,信号沿变缓,时序判断容易出错。MQ-2的输出电压在5V供电时最高能到4V以上,STM32引脚耐压是3.3V,所以必须加分压电阻,常见做法是10kΩ和6.8kΩ分压后进ADC。
2.3 电源、晶振与复位电路,VDDA别漏
这套系统的电源方案很简单:USB的5V进来,用AMS1117-3.3稳压给STM32和传感器供电。但要注意,MQ-2的加热丝需要5V直接供电,不要经过稳压器,否则发热量会让3.3V稳压制温度升高,输出电压漂移,ADC参考电压跟着改变,采集到的数据就全偏了。我一般把5V和3.3V在原理图上分成两个电源域,模拟地和数字地在STM32下方单点相连。
晶振电路很多人直接抄参考设计,但如果你自己计算电容,公式是CL=((C1*C2)/(C1+C2))+Cstray,8MHz晶振配18pF~22pF负载电容都可以,关键是PCB布局时晶振和负载电容要紧挨着MCU的OSC_IN/OSC_OUT引脚,走线不要太长。复位电路用10kΩ上拉加100nF对地电容就够,不需要外部复位芯片。
还要专门提醒一下VDDA引脚。F103的VDDA必须接3.3V,同时加一个1uF和100nF的去耦电容到地,否则ADC采样会莫名其妙地跳动。这个引脚很多新手容易漏接,仿真里不接没事,实物上不接ADC数据没法看。
2.4 点亮一颗LED验证新板子,再谈传感器
拿到新板子先别急着调传感器,第一步永远是点亮一个LED,确认时钟、GPIO和烧录链路都正常。最简化的点灯工程只要三部分:开启GPIOB时钟、配置PB0为推挽输出、循环翻转。以下代码基于标准外设库,在stm32f10x_conf.h里开启GPIO模块即可。
/* main.c 点灯验证,PB0接LED阳极,阴极串联330R到GND */ #include "stm32f10x.h" void Delay_MS(uint32_t ms) { uint32_t i; while (ms--) { for (i = 0; i < 7200; i++); /* 72MHz主频下约1ms */ } } int main(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); while (1) { GPIO_SetBits(GPIOB, GPIO_Pin_0); Delay_MS(500); GPIO_ResetBits(GPIOB, GPIO_Pin_0); Delay_MS(500); } }GPIO_Mode_Out_PP是推挽输出,能输出强高电平和强低电平,驱动单个LED正好。GPIO_Speed_50MHz对点灯来说没有必要,但后面的DHT11时序需要快速翻转,先统一配成50MHz省得后面改。延时函数用循环空转,只在验证阶段用,正式环境监测系统里后面会换成SysTick定时器。
3. 代码实现:STM32环境监测系统的传感器驱动与数据滤波
3.1 开发环境与芯片包安装,从零把工程建起来
在写任何传感器驱动之前,先把STM32的开发环境装对。Keil MDK装完之后有一个最常见的坑:新建工程时器件列表里找不到STM32F103C8,这是因为没有安装对应的芯片支持包,也就是STMicroelectronics的DFP(Device Family Pack)。打开Keil的Pack Installer,在搜索框输入STM32F1,安装STM32F1xx_DFP即可。
还有一类问题是电脑上之前装过C51版本的Keil,再装MDK时路径冲突。C51的安装路径是Keil\C51,MDK要用Keil\MDK,不要把两个版本装进同一个目录,否则编译器会被覆盖。装完之后可以在Project Targets里确认编译器版本是ARM Compiler V5还是V6,标准外设库工程建议用V5编译,V6对旧库的语法检查更严格,报错多,不是代码错误而是警告级别被抬高了。
工程架构上我习惯按下面四个文件夹管理源码:
project/ ├── USER/ // main.c、stm32f10x_it.c、系统时钟配置 ├── HARDWARE/ // dht11.c、mq2.c、bh1750.c、oled.c ├── CORE/ // 启动文件、core_cm3.c └── SYSTEM/ // delay.c、sys.c、usart.c新建工程时,startup_stm32f10x_md.s这个启动文件必须选对。F103C8是中等密度器件(Medium Density),对应md.s。如果选成hd.s,程序也能编译过,但中断向量表长度不一样,实际运行起来中断完全错位。仿真里经常出现“程序跑飞但没有语法错误”的情况,查启动文件是第一步。
3.2 DHT11单总线时序,把40位数据拆开读
DHT11用一根数据线完成主机发命令和从机回数据的全部过程。完整的时序是这样的:主机先把数据线拉低至少18ms,DHT11检测到起始信号后回应一个80us低电平加80us高电平,随后连续输出40位数据。每一位的起始都是一个50us低电平,接着高电平的时间决定这一位是0还是1:26us~28us是0,70us是1。
/* dht11.c 标准的DHT11单总线读取,PA0 */ #include "dht11.h" uint8_t DHT11_ReadByte(void) { uint8_t i, byte = 0; for (i = 0; i < 8; i++) { while (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == RESET); // 等待50us低电平结束 Delay_us(40); // 跳过前40us,落在电平中段 if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == SET) { byte |= (uint8_t)(0x80 >> i); // 高位在前 } while (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == SET); // 等待高电平结束 } return byte; } uint8_t DHT11_ReadData(uint8_t *humidity, uint8_t *temperature) { GPIO_InitTypeDef GPIO_InitStructure; uint8_t buf[5] = {0}; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 主机拉低 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_ResetBits(GPIOA, GPIO_Pin_0); Delay_ms(20); // 拉低至少18ms GPIO_SetBits(GPIOA, GPIO_Pin_0); Delay_us(30); // 拉高20~40us GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 释放总线,切换为输入 GPIO_Init(GPIOA, &GPIO_InitStructure); if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == RESET) { // 应答信号 while (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == RESET); while (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == SET); // 跳过80us高电平 buf[0] = DHT11_ReadByte(); // 湿度整数 buf[1] = DHT11_ReadByte(); // 湿度小数 buf[2] = DHT11_ReadByte(); // 温度整数 buf[3] = DHT11_ReadByte(); // 温度小数 buf[4] = DHT11_ReadByte(); // 校验和 if ((buf[0] + buf[1] + buf[2] + buf[3]) == buf[4]) { *humidity = buf[0]; *temperature = buf[2]; return 1; } } return 0; // 读取失败,主程序里重新拉低触发下一次 }两个关键参数:Delay_us(40)决定了采样点落在数据位高电平的中段,太短会误读边沿噪声,太长会把74us的“1”误判成“0”。主机拉低20ms这个值必须在18ms以上,但也不要超过30ms,否则部分DHT11模块会误判为异常命令而不返回数据。校验和那一行,把前四个字节相加和第五个字节比较,这是单总线通信里最便宜的错误检测方式。
3.3 多通道ADC读取MQ-2,用DMA别用查询
MQ-2的输出是模拟电压,ADC采集有两种实现方式:查询模式和DMA模式。查询模式在主循环里调用ADC_GetConversionValue,每采一次CPU就停一次;如果只采一个通道无所谓,但环境监测系统里MQ-2和雨滴传感器都要采集,连续采样加DMA是更稳的做法。
/* mq2.c 使用ADC1的IN1(PA1),DMA1通道1搬运数据 */ #include "mq2.h" #define ADC_BUF_SIZE 16 uint16_t adc_buf[ADC_BUF_SIZE]; void ADC1_DMA_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; ADC_InitTypeDef ADC_InitStructure; DMA_InitTypeDef DMA_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE); RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; // 模拟输入,不能配置成GPIO_Mode_IN_FLOATING GPIO_Init(GPIOA, &GPIO_InitStructure); DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)adc_buf; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize = ADC_BUF_SIZE; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA1_Channel1, &DMA_InitStructure); ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode = DISABLE; ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel = 1; ADC_Init(ADC1, &ADC_InitStructure); ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_55Cycles5); ADC_DMACmd(ADC1, ENABLE); ADC_Cmd(ADC1, ENABLE); ADC_ResetCalibration(ADC1); while (ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while (ADC_GetCalibrationStatus(ADC1)); ADC_SoftwareStartConvCmd(ADC1, ENABLE); DMA_Cmd(DMA1_Channel1, ENABLE); }DMA_Mode_Circular让DMA环形填充缓冲区,ADC连续转换,CPU完全不需要介入。采样时间配成55.5周期,在72MHz下约0.77us每次,16个样本填满缓冲区只要几十微秒,对烟雾这种慢变信号来说足够。读取烟雾浓度时,对adc_buf里16个数取平均值,比单次转换值稳定得多。ADC_SampleTime的值越长,采样电容充电越充分,输入阻抗高的信号源必须用长采样时间,这也是MQ-2分压电路输出阻抗偏高时的必要设置。
3.4 滑动平均滤波与阈值告警,参数怎么定
DHT11和MQ-2的原始数据都不能直接用。DHT11本身输出就带量化误差,相邻两次读数差1是正常现象;MQ-2的波形在加热电压波动时会叠加噪声。这里我用滑动平均,每个新样本进来,丢掉最旧的一个,窗口保持固定长度。
/* filter.c 环形缓冲滑动平均,窗口长度可配置 */ #define FILTER_SIZE 8 uint16_t filter_buf[FILTER_SIZE]; uint8_t filter_index = 0; uint32_t filter_sum = 0; uint16_t SlidingAverage(uint16_t new_value) { filter_sum -= filter_buf[filter_index]; filter_buf[filter_index] = new_value; filter_sum += new_value; filter_index = (filter_index + 1) % FILTER_SIZE; return (uint16_t)(filter_sum / FILTER_SIZE); }窗口长度怎么选:温度窗口5,烟雾窗口8~16。窗口大了数据平滑但响应慢,MQ-2用来做烟雾告警,响应太慢会错过瞬时浓度峰值;窗口小了毛刺压不住,蜂鸣器会频繁误报。告警阈值我一般先在串口里打印24小时原始数据,统计出正常范围的上限,再在正常值上乘以1.5到2作为告警线。烟雾浓度直接用ADC值比用换算后的ppm做阈值更可靠,因为MQ-2的阻值-浓度曲线不是线性的,强行线性换算会引入更大误差。
3.5 串口打印,printf重定向到USART1
调试这套系统最快的路径是串口。将printf重定向到USART1的关键是重写fputc函数,Keil MDK环境下方法如下。
/* usart.c 重定向printf到USART1 */ #include <stdio.h> int fputc(int ch, FILE *f) { while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); USART_SendData(USART1, (uint8_t)ch); return ch; }调用printf(“T:%d H:%d ADC:%d\n”, temp, humi, adc_value)即可在串口调试助手上持续观察数据。波特率建议115200,8位数据,1位停止位,无校验。看到的数据如果跳变幅度大,先确认地线是不是和USB转TTL模块共地,再检查ADC采样时间是否太短。串口是这套系统的“眼睛”,看完数据再优化算法才有依据。
4. Proteus仿真:让STM32环境监测系统在电脑上先跑起来
4.1 仿真电路搭建步骤与器件清单
Proteus仿真图是这套系统最容易让人卡住的部分,不是因为连线难,而是器件找不到。Proteus 8的元件库里,STM32系列支持的是F103R6、F103V6等早期型号,和F103C8T6引脚不完全一样但内核相同,仿真工程里选STM32F103R6,代码不经过任何修改就能跑。需要的器件清单如下。
| 器件 | 库内搜索名 | 数量 | 用途 |
|---|---|---|---|
| STM32主控 | STM32F103R6 | 1 | 主控 |
| 温湿度传感器 | DHT11 | 1 | 温湿度采集 |
| 电位器 | POT-HG | 1 | 模拟MQ-2输出 |
| 晶振 | CRYSTAL 8MHz | 1 | 时钟 |
| 电容 | CAP | 3 | 22pF x2、100nF复位 |
| 电阻 | RES | 4 | 上拉/分压/LED限流 |
| LED | LED-RED | 2 | 电源指示/告警指示 |
| LCD | LM044L | 1 | 字符屏代替OLED |
搭建顺序是:先放STM32F103R6,再放8MHz晶振和两个22pF电容到OSC_IN和OSC_OUT,复位电路用10kΩ电阻上拉到3.3V、100nF电容到地。然后是DHT11,数据脚接PA0。MQ-2没有现成模型,用POT-HG电位器中间抽头接PA1,拧电位器就能改变ADC输入电压,仿真效果等效于烟雾浓度变化。
4.2 将Keil编译的hex文件加载到仿真芯片
Keil里编译完能生成hex文件才能让Proteus仿真跑起来,默认情况下Keil只生成axf文件,要在Options for Target的Output选项卡里,把Create HEX File勾上。Proteus里双击STM32F103R6芯片,在Program File一栏选到生成的.hex路径,Crystal Frequency填8MHz。启动仿真前要在Design菜单,确认供电电压是3.3V还是5V——如果仿真里只用3.3V给DHT11供电,部分模型会显示读取错误。
启动仿真后,LCD上能依次显示温湿度和ADC转换值,此时旋转POT-HG,ADC值应随之变化,告警阈值触发时LED点亮。仿真运行不起来的排查顺序:STOP按钮是否被点击、hex文件路径是否是英文、芯片是否设置了固件文件。仿真和真实板子的最大优势是可以随时暂停,卡住的时候看引脚电平颜色,蓝色是低电平,红色是高电平,DHT11数据线上如果长时间没有跳变,说明时序代码卡死在while循环里。
4.3 仿真和真实电路的三处差异,提前打预防针
仿真图只能证明逻辑正确,不能证明时序精确,这里的差异要提前说清楚。
第一,Proteus的DHT11模型对时序容限很宽,20us的延时误差也能正确应答,但真实DHT11对主机起始信号有严格要求,延时函数的准确性直接决定读取成败。仿真里能跑的代码,烧到真实板子上DHT11返回全是255,多半是延时不对。第二,仿真里的电位器输出是理想电压,没有噪声和温漂,真实MQ-2的输出随着加热丝温度变化会缓慢漂移,不能在仿真里确定最终阈值。第三,Proteus不仿真ADC的采样保持电容和输入阻抗,真实电路里MQ-2分压输出阻抗偏大时,采样时间55.5周期可能不够,要改成239.5周期。
5. 串口校准:把仿真数据映射成真实量程的最后一步
5.1 用串口日志定位传感器原始值异常
仿真跑通只是第一步,真实板子上电后,第一步永远是打开串口看原始值。DHT11读取失败时返回0xFF,这个值出现在串口上说明时序问题而非硬件损坏;MQ-2的ADC值如果一直是0,先量分压点电压,再查GPIO_Mode_AIN有没有写成输入浮空模式。
# 基于OS的串口日志,Windows下用串口助手即可 open COM3:115200,8,n,1 T:25 H:60 ADC:128 T:25 H:61 ADC:130模拟量传感器的硬件差异导致每个模块的ADC值和真实物理量之间的关系都不同,这块板子的MQ-2在干净空气中读出ADC 350,换一块可能读出420。不在代码里做校准,显示出来的数值没有实际意义。
5.2 两点标定与参数表实现
温度传感器好标定,常压冰水混合物是0℃,沸水是100℃,把DHT11探头放进去记录串口读数。MQ-2做浓度标定需要标准气源,一般环境不具备,退而求其次的做法是标定“干净空气基准点”——开机在干净空气里等10分钟,记录当前ADC值作为基准,之后用这个基准去偏移判断浓度变化。
| 标定点 | 真实值 | 传感器读数 | 计算系数 |
|---|---|---|---|
| 低点 | 20℃ | 20 | slope=1.0 |
| 高点 | 60℃ | 61 | offset=0 |
| MQ-2基准 | 400ppm(相对) | 350 | base=350 |
代码里做一个校准参数结构体,把两点标定的斜率和偏移量存下来,后续显示和告警都走校准值。
/* calibrate.c 线性校准参数表 */ typedef struct { int16_t slope_x100; // 斜率乘以100,避免浮点 int16_t offset; // 截距 } CalibParam; CalibParam temp_calib = {100, 0}; // 默认1:1 CalibParam smoke_calib = {100, 50}; // 实际标定后回写 int16_t Calibrate(int16_t raw, CalibParam *p) { return (int16_t)((raw * p->slope_x100) / 100 + p->offset); }为什么用slope_x100整数而不是float?因为STM32F103没有硬件浮点单元,用float会引入约十几微秒的额外计算时间,环境监测采样周期是秒级,时间上无所谓,但整数运算在固件升级、数据存储和串口打印时更不容易出格式问题。斜率存储为100倍是嵌入式系统里很常见的定点小数做法。标定参数保存到片上Flash的最后一个扇区,每次上电先读Flash再进入主循环,这样就不必在每次烧录后重新标定。校准完成之后,串口打印的就是和实物对应的量程数据,这套系统才算真正跑通。
本文还有配套的精品资源,点击获取