资讯动态

STM32F103C8T6温室监测:传感器驱动、数据滤波与系统调度实战

发布时间:2026/9/16 22:22:53 来源:尧图企业网站定制
简介这是一套以STM32F103C8T6为核心的温室环境监测系统完整设计资料面向电子信息、计算机等专业学生适用于毕业设计、期末大作业及嵌入式项目实践。系统完成空气温湿度、土壤湿度和光照强度的采集、处理与显示结构涵盖传感器模块、主控电路与数据显示单元。压缩包共280个文件大小9.12MB核心包含C/H源码、Keil工程文件、hex固件、markdown设计文档及若干图片另含较多编译中间与备份文件可满足源码研读、程序烧录与论文撰写需求。现有56人学习下载资料内涉及定时器、ADC、I2C、USART等标准外设驱动附带的详细文档阐述了系统架构、数据处理逻辑与模块功能能帮助理解ARM Cortex-M3内核在数据采集监控中的实际应用。项目经学术审核完整度高、可操作性强适合作为课程设计和毕业设计的参考范本。1. 小封装的引脚账本为什么温室监测选 C8T6 而不是大板子温室环境监测在课程设计和毕设里出镜率很高但拆开看硬件链路并不复杂环境量经传感器变成电信号MCU 采集、换算、显示必要时送串口上位机。这套基于 STM32F103C8T6 的工程把温湿度、光照、土壤湿度三路传感器和 OLED 显示集成到一颗 LQFP48 封装的小芯片上并配套完整源码和设计文档。C8T6 只有 20KB RAM、64KB Flash单看资源表寒酸但单总线、I2C、ADC、USART 这四类外设恰好能错开不用为加外设换大板。资源适合两类人一类是第一次做多传感器汇总的嵌入式课设学生另一类是准备答辩前想补足“为什么引脚这么分”细节的应届生。2. GPIO/ADC/单总线/共享I2CC8T6 外设资源分配与初始化2.1 先数引脚再决定外设能不能共存STM32F103C8T6 是 48 脚封装除去电源、地、BOOT、SWD 调试所需的 PA13/PA14 和 NRST实际可用的 GPIO 有三四十个。这个资源里需要占用的接口是DHT11 用一根 GPIO 做单总线BH1750 和 OLED 共用一条 I2C 总线的两根线土壤湿度探头用 ADC 的模拟输入再加上 USART1 的 TX/RX 用于向串口助手打印数据合计只用 7 个引脚。这正是 C8T6 比 ZET6 这类 144 脚大芯片更适合课程设计的原因PCB 布线简单、板子便宜、出问题也好查。常见做法是最小系统板加传感器模块直连本工程的标准外设库代码也是按这套接线写的。值得注意的是资源包里的stm32f10x_*.c属于标准外设库SPL不是 HAL 库初始化和事件判断方式与CubeMX生成的代码有明显差异下文所有代码都按标准外设库展开。2.2 引脚分配和库函数初始化我一般按下面的分配表接线这套分配的原则是单总线独占一个 IOI2C 两根线固定给 PB6/PB7ADC 通道避开 I2C 引脚串口留给 PA9/PA10。外设引脚模式说明DHT11PB0开漏输出 / 上拉输入单总线空闲必须为高电平BH1750 SCLPB6复用开漏I2C1 时钟BH1750 SDAPB7复用开漏I2C1 数据OLED SCL/SDAPB6/PB7复用开漏与 BH1750 共用 I2C1土壤湿度探头PA0模拟输入ADC1_IN0备用土壤探头PA1模拟输入ADC1_IN1USART1 TX/RXPA9/PA10复用推挽 / 浮空输入打印调试信息SWDPA13/PA14默认不要初始化为普通 IO初始化代码首先要处理 GPIO 模式这一步比主频配置更容易翻车。DHT11 数据脚先配置为开漏输出并拉高进入读时序时再切输入I2C 引脚必须用GPIO_Mode_AF_OD不能照搬点灯的推挽输出。void Gpio_Init(void) { GPIO_InitTypeDef gpio; // DHT11 数据脚PB0先置高开漏输出 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_StructInit(gpio); gpio.GPIO_Pin GPIO_Pin_0; gpio.GPIO_Mode GPIO_Mode_Out_OD; gpio.GPIO_Speed GPIO_Speed_2MHz; GPIO_Init(GPIOB, gpio); GPIO_SetBits(GPIOB, GPIO_Pin_0); }开漏模式保证总线空闲时被外部上拉电阻拉到 3.3V也方便后面把同一根线切到输入。GPIO_Speed_2MHz对单总线足够DHT11 时序里最短的高电平也有 26us2MHz 摆率不会把边沿弄缓。I2C1 的初始化要同时配 GPIO 和 I2C 外设关键参数是时钟速率和应答模式void I2C1_Init(void) { GPIO_InitTypeDef gpio; I2C_InitTypeDef i2c; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); gpio.GPIO_Pin GPIO_Pin_6 | GPIO_Pin_7; gpio.GPIO_Mode GPIO_Mode_AF_OD; // I2C 必须复用开漏 gpio.GPIO_Speed GPIO_Speed_2MHz; GPIO_Init(GPIOB, gpio); I2C_StructInit(i2c); i2c.I2C_ClockSpeed 100000; // 100kHz标准模式 i2c.I2C_Ack I2C_Ack_Enable; i2c.I2C_AcknowledgedAddress I2C_AcknowledgedAddress_7bit; I2C_Init(I2C1, i2c); I2C_Cmd(I2C1, ENABLE); }这里有个容易忽略的点OLED 模块和 BH1750 模块放在同一条 I2C 总线上时每个模块上通常都自带一组上拉电阻。多组上拉并联后等效阻值变小总线负载变大上升沿变缓。稳妥做法是只保留一组约 4.7k 的上拉到 3.3V其余模块的上拉焊掉总线速率保持 100kHz 不要上 400kHz。土壤湿度探头接 ADC初始化时要把 PA0/PA1 配成模拟输入。标准外设库的校准流程也要走一遍void ADC1_Init_Soil(void) { GPIO_InitTypeDef gpio; ADC_InitTypeDef adc; RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE); RCC_ADCCLKConfig(RCC_PCLK2_Div6); // ADC 时钟 12MHz gpio.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1; gpio.GPIO_Mode GPIO_Mode_AIN; // 模拟输入 GPIO_Init(GPIOA, gpio); ADC_StructInit(adc); adc.ADC_Mode ADC_Mode_Independent; adc.ADC_ExternalTrigConv ADC_ExternalTrigConv_None; // 软件触发 ADC_Init(ADC1, adc); ADC_Cmd(ADC1, ENABLE); ADC_ResetCalibration(ADC1); while (ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while (ADC_GetCalibrationStatus(ADC1)); }RCC_PCLK2_Div6把 72MHz 的 PCLK2 分频到 12MHz这是该芯片 ADC 的推荐上限。ADC_ExternalTrigConv_None表示每次读取由软件启动转换。校准两次 while 是标准外设库的固定流程漏掉后首次采样可能偏差较大但不会完全读不出来。单次采样代码里采样时间要选长一点uint16_t ADC_ReadOnce(uint8_t ch) { ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5); ADC_SoftwareStartConvCmd(ADC1, ENABLE); while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) RESET); return ADC_GetConversionValue(ADC1); }土壤湿度探头是典型的高内阻源采样时间太短会导致内部采样电容没充满读数跳动剧烈。用ADC_SampleTime_239Cycles5239.5 周期是实打实的经验值配 12MHz ADC 时钟即约 20us 采样窗口。2.3 引脚冲突排查JTAG 占用和 BOOT 配置C8T6 上 PA13、PA14 同时连在 SWD 和 JTAG 上代码里如果把这两个引脚初始化为普通 GPIO下一次就无法仿真下载只能按住复位键抢时间下载或者用串口 ISP 擦除。这个资源没用到 JTAG只留 SWD标准外设库中如果需要释放 PA15、PB3、PB4 做普通 IO可以调用GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE)但要在程序能正常烧录的前提下改否则一次误配置就可能进死循环。另一个容易误判的问题是 BOOT0。BOOT0 必须通过 10k 电阻下拉到 GND否则芯片上电后停在系统存储器里的 ISP 引导程序里主程序不跑OLED 全黑DHT11 无应答串口发出 0x7F。这类问题在毕设联调时经常被误判成传感器损坏排查顺序应该是供电、复位、BOOT、时钟、外设初始化最后再看传感器接线。3. 传感器驱动拆解DHT11 单总线与 BH1750 的 I2C 时序3.1 DHT11 单总线40 个数据位的时序边界DHT11 一次完整通信包括主机起始信号、从机应答和 40 位数据8 位湿度整数、8 位湿度小数、8 位温度整数、8 位温度小数、8 位校验和。时序上主机先把总线拉低至少 18ms再释放传感器随后把总线拉低 80us 应答再拉高 80us之后每一位数据都以 50us 低电平开始。阶段电平时间说明主机起始低20ms必须大于 18ms主机释放高20~40us把总线交还给传感器从机应答低80us随后拉高 80us数据位 0低 50us 高 26~28us约 78us高电平宽度小于 50us数据位 1低 50us 高 70us约 120us高电平宽度大于 50us判断 0 和 1 的关键不是测量绝对时间而是在 50us 低电平结束后用延时采样高电平宽度。下面的读取函数先等每个位的低电平结束延时 40us 后再读引脚uint8_t DHT11_ReadByte(void) { uint8_t byte 0; for (uint8_t i 0; i 8; i) { while (DHT11_PIN_IN() RESET); // 跳过 50us 低电平 DelayUs(40); // 在高电平中间采样 byte 1; if (DHT11_PIN_IN() ! RESET) { // 高电平 40us 视为 1 byte | 0x01; while (DHT11_PIN_IN() ! RESET); // 等本数据位结束 } } return byte; }40us 阈值选在 26us 和 70us 中间留了足够裕量。注意DelayUs必须用 SysTick 或定时器实现不能靠空循环嵌套不同编译优化等级下空循环延时长度差别很大。我在调试中见过代码开-O0一切正常开到-O2后 DHT11 全部读到 0xFF就是延时被编译器优化掉了。完整读取还要处理应答和校验和uint8_t DHT11_Read(float *humi, float *temp) { uint8_t data[5] {0}; DHT11_PIN_OUT_1(); DelayUs(30); DHT11_PIN_OUT_0(); // 起始信号 DelayMs(20); DHT11_PIN_OUT_1(); // 释放总线 DelayUs(40); if (DHT11_PIN_IN() RESET) { // 传感器应答低脉冲 uint8_t timeout 100; while (DHT11_PIN_IN() RESET --timeout); if (timeout 0) return 0; timeout 100; while (DHT11_PIN_IN() ! RESET --timeout); // 跳过应答高电平 if (timeout 0) return 0; for (uint8_t i 0; i 5; i) data[i] DHT11_ReadByte(); // 校验前四个字节相加低 8 位等于校验位 uint8_t sum data[0] data[1] data[2] data[3]; if ((sum 0xFF) data[4]) { *humi data[0] data[1] * 0.1f; *temp data[2] data[3] * 0.1f; return 1; } } return 0; }注意DHT11 两次读取间隔必须大于 1 秒否则传感器内部测量未完成返回结果不稳定。超时处理在这里很关键。单总线协议没有时钟线如果传感器不应答while会一直卡住所以每个等待点都要加 timeout 计数。这套驱动里用递减计数简单可靠如果项目里多个地方都要等待可以统一封装成Wait_Timeout(condition, count)。3.2 BH1750共享 I2C 总线上的一次长读取BH1750 的 I2C 地址固定为 0x46写/0x47读命令寄存器按操作码区分。这个资源里推荐用连续 H-分辨率模式0x10分辨率 1lx测量时间约 120ms适合温室里从日出前到正午的动态范围。很多移植示例漏了一个比例因子读到的 16 位原始值除以 1.2 才是真正的照度 lx。命令值作用Power Down0x00进入待机Power On0x01上电唤醒Continuous H-Resolution0x101lx 分辨率连续测量One Time H-Resolution0x20单次测量后自动断电Reset0x07复位内部寄存器用标准外设库操作 I2C1 时事件链必须严格按 START、地址、数据、STOP 的顺序走uint8_t BH1750_ReadLux(float *lux) { uint8_t buf[2]; uint16_t raw; I2C_GenerateSTART(I2C1, ENABLE); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2C1, 0x46, I2C_Direction_Transmitter); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); I2C_SendData(I2C1, 0x10); // 连续 H 分辨率模式 while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); I2C_GenerateSTOP(I2C1, ENABLE); DelayMs(180); // 等转换完成 I2C_GenerateSTART(I2C1, ENABLE); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2C1, 0x47, I2C_Direction_Receiver); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); for (uint8_t i 0; i 2; i) { if (i 0) { while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)); buf[0] I2C_ReceiveData(I2C1); } else { I2C_AcknowledgeConfig(I2C1, DISABLE); // 最后一字节回 NACK while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)); buf[1] I2C_ReceiveData(I2C1); I2C_GenerateSTOP(I2C1, ENABLE); } } I2C_AcknowledgeConfig(I2C1, ENABLE); raw ((uint16_t)buf[0] 8) | buf[1]; *lux (float)raw / 1.2f; return 1; }这段代码有两个细节。第一连续模式下每次读取都要重新发一次测量命令否则读到的是上一次结果第二接收最后一个字节前要把 ACK 关掉从机收到 NACK 后才知道主机不要更多数据了。STOP 之后再恢复 ACK不然下一次通信的事件状态会被残留标志污染。OLED 也用同一路 I2C 时设备地址不同SSD1306 通常是 0x78 写地址总线仲裁没问题。但两个模块共用时如果 OLED 初始化时拉低总线时间过长BH1750 的测量命令可能被干扰。我一般会先初始化 OLED再初始化 BH1750两条 I2C 事务之间至少间隔 50ms。3.3 长线传输对 I2C 时序的影响BH1750 模块引线超过 30cm 后SCL/SDA 的上升沿会明显变缓。100kHz 标准模式下位周期 10us如果上升沿超过 1us器件采样点容易错。常见做法是把外部上拉从 4.7k 换成 2.2k并把延时适当拉长不要试图把总线提到 400kHz除非后端接的是示波器验证过的短线。OLED 与 BH1750 同总线时OLED 的初始化报文很长初始化完成后加 50ms 延时再开始读光照可以避免连续 I2C 操作导致总线忙。4. 滑动滤波与分段标定把 ADC 裸值变成可读的温室数据4.1 去毛刺中位值平均滤波土壤湿度探头在湿润环境下等效阻抗低干燥时阻抗高ADC 输入信号本身带工频干扰。直接读原始值同一个泥盆连续采样可能波动 ±200 LSB。单纯取平均值能压低噪声但对突发的开关电源尖峰效果不好。我常使用中位值平均滤波连续采样 8 次去掉一个最大值和一个最小值剩下的 6 个取平均。#define FILTER_N 8 uint16_t ADC_Filter(uint8_t ch) { uint16_t buf[FILTER_N]; uint32_t sum 0; uint16_t min, max; for (uint8_t i 0; i FILTER_N; i) { buf[i] ADC_ReadOnce(ch); DelayMs(2); // 采样间隔大于转换时间即可 } min buf[0]; max buf[0]; for (uint8_t i 0; i FILTER_N; i) { if (buf[i] min) min buf[i]; if (buf[i] max) max buf[i]; sum buf[i]; } return (uint16_t)((sum - min - max) / (FILTER_N - 2)); }8 次采样里去掉极值剩下的算术平均能同时压制白噪声和脉冲毛刺。2ms 的采样间隔对应约 500Hz 采样率对土壤湿度这种秒级变化完全够用。如果原始信号漂移严重不要只加大 N响应滞后会更明显先检查探头的供电是否稳定、分压电阻是否匹配再考虑滤波强度。4.2 土壤湿度标定两点定标比公式更重要土壤湿度模块的输出电压随含水率变化换算不能只套线性公式必须做两点标定。常见做法是把探头完全暴露在空气中测一个 ADC 值记为dry_adc再插入水中测一个wet_adc。换算公式根据输出电压的方向决定电压随湿度升高而降低的模块使用下面这段float Soil_GetPercent(uint16_t adc_value) { if (adc_value dry_adc) return 0.0f; // 比空气还干按 0% 处理 if (adc_value wet_adc) return 100.0f; // 完全浸水按饱和处理 return (float)(dry_adc - adc_value) * 100.0f / (float)(dry_adc - wet_adc); }标定有两点必须注意。第一干湿两端要等探头通电预热 30 秒再采样否则探头表面初始极化状态不稳定读出的两个标定点没意义。第二dry_adc和wet_adc不是定死的 4095 和 0不同厂家模块的分压电阻不同同一个模块浸水深度不同结果差异也很大。把标定点定义成宏换探头时只改两行。如果用的是电容式土壤湿度模块输出随湿度升高而升高公式里的减号要反过来。这里还要提醒一个系统性误差STM32F103 的 VREF 一般直接接 VDDA如果板上的 3.3V 由 USB 5V 经 LDO 降压得到LDO 的输出误差会直接影响 ADC 满量程。所以标定要在实际供电电压下进行不要在实验室可调电源下调完再拿到 USB 供电上跑否则标定结果全部偏移。4.3 OLED 显示与串口打印数据出口的统一现场看数据用 OLED记录数据走串口。SSD1306 的驱动代码这里不展开重点看数据格式化方式。OLED 两行分别显示温湿度和光照/土壤串口每一帧打印所有关键量char line[32]; sprintf(line, T:%.1fC H:%.0f%%, temp, humi); OLED_ShowString(0, 0, line); sprintf(line, Lux:%.0f Soil:%.0f%%, lux, soilPercent); OLED_ShowString(0, 2, line); printf(T%.1f C H%.0f%% Lux%.0f Soil%.0f%%\r\n, temp, humi, lux, soilPercent);sprintf处理浮点会在 Keil MDK 下引入较大的浮点库支持代码C8T6 虽然 64KB Flash但标准外设库加 OLED 字库后空间并不宽裕。如果编译报 Flash 不足把%.1f换成整数运算比如temp的整数部分和小数部分分别取int16_t t_int (int16_t)temp; uint8_t t_dec (uint8_t)((temp - t_int) * 10); sprintf(line, T:%d.%dC H:%d%%, t_int, t_dec, (int)humi);用这种方式能省下几 KB。串口重定向在 MDK 里需要重写fputc并关闭半主机模式更简单的做法是直接用USART_SendData封装自己的打印函数输出长度可控也不依赖微库选项。5. 前后台调度、看门狗与 CRC16让监测系统连续跑 72 小时5.1 主循环里的三级任务调度很多初版代码会把三个传感器读一遍、OLED 刷一遍DHT11 卡 20msBH1750 等待 180msADC 滤波循环又是十几 ms一个循环周期超过 200ms。这个系统没有外部中断需求用前后台调度最合适SysTick 产生 1ms 心跳主循环里用“到时间就执行”的非阻塞标志驱动任务。volatile uint32_t g_tick 0; void SysTick_Handler(void) { g_tick; } uint8_t Timeout_Reached(uint32_t *last, uint32_t period) { if (g_tick - *last period) { *last g_tick; return 1; } return 0; } int main(void) { // 时钟、GPIO、I2C、ADC、OLED、串口初始化... uint32_t t_dht 0, t_lux 0, t_soil 0, t_ui 0; while (1) { if (Timeout_Reached(t_dht, 2000)) DHT11_Read(humi, temp); if (Timeout_Reached(t_lux, 2000)) BH1750_ReadLux(lux); if (Timeout_Reached(t_soil, 1000)) soil ADC_Filter(ADC_CH_SOIL); if (Timeout_Reached(t_ui, 500)) OLED_Update(humi, temp, lux, soil); } }g_tick是 32 位无符号数g_tick - *last在计数器溢出时按无符号回绕计算依然正确不需要额外处理 49 天清零问题。各任务周期独立DHT11 读失败时下一轮 2 秒后再试不会阻塞光照和土壤数据的更新。任务周期最大执行时间失败策略DHT11 读取2000ms约 25ms下一周期重试显示--.--BH1750 读取2000ms约 180ms 等待保留旧值最多容忍 2 个周期土壤 ADC 滤波1000ms约 20ms用上次滤波结果OLED 刷新500ms约 10ms不阻塞主循环周期拆开还有一个好处DHT11 需要满足“两次读取间隔大于 1 秒”主循环调度天然保证 2 秒间隔不用在驱动代码里额外做时间判断。如果后面要加按键或报警只需再挂一路Timeout_Reached不会影响已有任务。5.2 独立看门狗死循环和时序卡死的兜底温室监测往往要长时间上电程序在某个while等待事件时卡死是常见故障。STM32F103 的 IWDG 由独立的 40kHz LSI 时钟驱动一旦启动无法由软件停止非常适合这种无人值守场景void IWDG_Init(void) { IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable); IWDG_SetPrescaler(IWDG_Prescaler_64); // LSI 40kHz / 64 625Hz IWDG_SetReload(1250); // 1250 / 625 2 秒 IWDG_ReloadCounter(); IWDG_Enable(); }喂狗点放在主循环末尾不要放在传感器读取函数内部。如果 DHT11 的while等待把所在任务卡住而看门狗喂狗恰好也在同一调用链里看门狗就失效了。放在主循环末尾可以保证即使某个传感器故障只要调度器还活着系统就不会复位故障状态的现场也能保留。验证看门狗是否生效有个危险但有效的方法把 DHT11 数据线拔掉让读取函数超时返回程序应继续运行如果人为进入一个死循环2 秒后芯片自动复位OLED 重新初始化串口打印复位标志RCC_GetFlagStatus(RCC_FLAG_IWDGRST)可以确认复位源来自看门狗。5.3 串口数据帧的 CRC16上位机接收前加一道保险这套系统如果只做本地显示CRC 可有可无一旦要接上位机或 4G 模块建议在串口帧末尾加 CRC16。温室环境数据会被拿去控制风机、卷帘一个错字节可能导致执行机构误动作。CRC16-MODBUS 多项式 0x8005初值 0xFFFF代码量小适合在 MCU 上跑uint16_t CRC16_Modbus(uint8_t *buf, uint16_t len) { uint16_t crc 0xFFFF; for (uint16_t i 0; i len; i) { crc ^ buf[i]; for (uint8_t j 0; j 8; j) { if (crc 0x0001) { crc (crc 1) ^ 0xA001; // 0x8005 的反射多项式 } else { crc 1; } } } return crc; }0xA001是0x8005按位反转后的值这是 Modbus 协议对 CRC 的低字节优先处理方式。发送时先发送 CRC 低字节再发高字节这个字节序和很多上位机示例不一致是联调失败的高发区。接收端收到整帧后重新计算 CRC与帧尾比对不一致就丢弃整帧不做部分处理。6. 用逻辑分析仪和发布脚本把交付物做“干净”DHT11 这类单总线器件时序是否对不能只看读数。用 24MHz 采样率的逻辑分析仪抓 PB0 波形按“20ms 低电平 → 40us 高 → 80us 低 → 80us 高 → 数据位”的顺序核对。波形验证步骤1. 逻辑分析仪 CH0 接 PB0GND 与板子共地 2. 采样率设 2MHz触发方式选下降沿预触发 10% 3. 上电后触发一次测量第一段低电平宽度应约 20ms 4. 展开第一个数据位高电平 26~28us 为 070us 左右为 12MHz 采样率对应 0.5us 分辨率足够区分 26us 和 70us 的高电平。如果分析仪显示所有高电平均在 30us 左右说明数据被判定为全 0需要查DelayUs实现如果起始低电平只有 10msDHT11 大概率不应答。BH1750 则抓 I2C 的 SCL/SDA重点看 STOP 后是否有残留 SCL 脉冲。工程发布前资源里自带的keilkilll.bat这类脚本值得保留。它负责删除Listings、Objects里的中间编译文件以及.uvguix.*界面布局文件。给别人发源码压缩包前执行一次工程体积能从几十 MB 压到几百 KB.axf和.hex按需保留。文件/目录是否保留理由Template.uvprojx保留Keil 工程入口Template.uvguix.*删除本机窗口布局换机器会重建Listings/、Objects/删除编译中间产物用户.c/.h保留核心驱动源码.axf/.hex保留 hexaxf 是调试产物不需要留给别人移植到另一块最小系统板时检查三个点BOOT0 是否下拉到 GNDPA13/PA14 有没有被 GPIO 初始化占用I2C 总线上是否只剩一组上拉。这套程序的核心是驱动文件之间耦合很小从蓝丸板换到自制板主要改stm32f10x_conf.h中的外设使能和引脚宏。第一次移植建议保留原引脚的传感器接线只改时钟和外设初始化等数据通路恢复正常后再动管脚映射这样能把接线问题和代码改动量分开定位出现异常时不用同时怀疑两个变量。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价