ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32智能婴儿床实战拆解:传感器驱动与多级联动逻辑

STM32智能婴儿床实战拆解:传感器驱动与多级联动逻辑 简介这是一份基于STM32的智能婴儿床毕业设计/课程设计资料包包含完整代码工程与Proteus仿真面向嵌入式初学者、电子类学生及幼儿监护类项目开发者。方案覆盖温度与湿度调节、尿湿检测、哭声识别与安抚、MPU6050撞击预警、LCD显示与按键交互等功能涉及DS18B20、DHT11、ADC检测、I2C、PWM等常见外设能帮助读者理解从传感器采集到执行器联动的完整嵌入式开发流程。压缩包共87个文件体积约659KB以C源文件、H头文件、Keil工程、Hex烧写文件及Proteus仿真文件为主C代码负责逻辑实现仿真文件用于电路验证另含备份与编译中间产物整体目录清晰、便于二次改造。已有531人学习浏览适合课程设计、毕业设计或智能家居方向创新实践直接打开工程即可参考整体架构和移植思路。1. 拆一套 KeilProteus 的 STM32 智能婴儿床麻烦不在传感器在联动逻辑一套基于 STM32 的智能婴儿床工程单看传感器选型没什么新鲜的温度用 DS18B20湿度用 DHT11尿湿检测走 ADC 通道撞击检测靠 I2C 总线上的 MPU6050显示用 LCD1602。直接跑起来也不难Keil 编译生成 hex 后丢进 Proteus 就能看到仿真效果真正花时间的是把这五路输入和风扇、蜂鸣器、摇床电机、音乐模块这些输出按优先级组织起来而不是让温湿度控制和尿湿报警互相抢总线、抢显示资源。这篇基于一个可以完整编译、仿真的 STM32 毕业设计工程拆一遍每一路驱动的代码细节、联动状态机的实现思路以及 Proteus 仿真里最容易翻车的几个地方适合正在做 STM32 课设/毕设、或者想快速搞明白多传感器系统怎么整合的人。2. 系统架构与传感器选型单总线、I2C 和 ADC 通道各管一摊把 rar 包解开之后第一眼的印象是这不是一个玩具 Demo而是一个能直接编译的 Keil 工程加 Proteus 仿真工程。main.c、adc.c、dht.c、lcd.c、iic.c、common.c 分层清楚config.h 集中放引脚定义这种目录结构对课设/毕设来说已经比大多数“教你点亮一颗 LED”的教程要完整得多。拆工程的第一步不是读代码而是先把“哪个文件对应哪个外设”这个映射表建出来后面改阈值、改引脚才不会到处翻。2.1 功能需求到模块映射从摘要里的功能列表往下对应这个工程的外设划分大概是这样的DS18B20 负责婴儿床内温度DHT11 负责湿度ADC 通道接尿湿检测模块的模拟输出MPU6050 通过 I2C 上报加速度数据做撞击判断LCD1602 显示当前温湿度和尿湿状态独立按键用来切换显示页面和手动控制风扇/音乐。执行机构里风扇是一个由 GPIO 控制的直流电机摇床和音乐模块一般通过继电器或三极管开关电路接到 MCU主控只负责给高电平或低电平。功能模块传感器/执行器接口类型工程文件温度检测DS18B20单总线 GPIODS18B20_One.h / DS18B20.h湿度检测DHT11单总线 GPIOdht.c / dht.h尿湿检测尿湿传感器 比较器输出ADC 通道adc.c撞击检测MPU6050I2Ciic.c显示LCD1602并口 GPIOlcd.c / lcd1602.h手动操作独立按键GPIO 扫描key_alone.h风扇/摇床/音乐直流电机 / 继电器GPIO 开关main.c 中联控这张表对应到工程里config.h 里做的就是引脚复用声明。一个常见的坑是工程里有两套单总线器件DS18B20 和 DHT11它们的时序完全不同不能共用同一个驱动函数。2.2 DS18B20 与 DHT11 的分工DS18B20 和 DHT11 虽然都叫“单总线器件”但它们的电气协议并不一样。DS18B20 是 Maxim 的单总线协议有严格的初始化时序、ROM 指令和功能指令读取温度时要先发 Skip ROM0xCC再发 Convert T0x44等待转换完成后发 Read Scratchpad0xBE读 9 个字节。DHT11 则是自定义的单总线时序主机先拉低 18ms 以上作为起始信号然后释放总线从机响应后连发 40 位数据高位在前每位的 0/1 由高电平持续时间区分。之所以把两个传感器分开成独立的 .c 文件就是因为它们的时序差异太大硬塞进一个驱动里容易把读时隙的延时参数搞混。实际在跑仿真的时候如果 DHT11 偶尔读到 0xFF大概率不是传感器坏了而是主机起始信号的拉低时间不够或者上拉电阻没接。2.3 ADC 通道做尿湿检测的电路思路尿湿检测模块的输出本质是一个模拟电压尿湿传感器是两块互不接触的导体干燥时输出高电平有水时导体间导通、输出电压被拉低。有些模块输出的是数字电平可以直接接 GPIO这个工程里用的是 ADC 采集方式意味着模块输出的是连续变化的模拟量需要把 adc.c 里的规则通道配置和实际引脚对上。这里有一个值得注意的细节因为检测的是“电压落没落到阈值以下”所以阈值判断不能只做一次要在主循环里持续采样并且加一个简单的滤波比如连续读到三次超过阈值才判定尿湿。仿真里如果不加这个滤波Proteus 模拟的 ADC 数据偶尔跳变会触发误报警。2.4 MPU6050 的 I2C 接口与撞击检测MPU6050 挂载在 I2C 总线上工程里 iic.c 是软件模拟 I2C 还是硬件 I2C要看代码里有没有操作 I2C 外设寄存器。常见的做法是直接用 GPIO 模拟 I2C 时序这样引脚可以任意映射移植方便。撞击检测的判断逻辑倒不复杂连续读取加速度计的 X/Y/Z 三轴原始值计算合成加速度 sqrt(x² y² z²)当合成值超过预设阈值就认为受到撞击。这里要注意 MPU6050 上电后需要一段稳定时间否则前几十次读出来的数据全是 0 或者漂移很大。3. 驱动层拆解DS18B20 时序、DHT11 握手和 ADC 尿湿检测的代码细节这一章直接落到代码按照“先看时序、再看实现、最后看怎么改参数”的顺序来。工程里这几个驱动文件单独拿出来都能复用等到自己画 PCB 换引脚时只需要改 config.h 里的宏定义和 delay.c 里的延时函数。3.1 DS18B20初始化、读时隙和写时隙DS18B20 的驱动核心是三个函数复位、写一个字节、读一个字节。复位时序最关键主机把总线拉低 480us 以上然后释放等待从机拉低 60~240us 作为存在脉冲如果没有这个脉冲后续所有读写都是白做。// DS18B20 复位返回 0 表示检测到设备返回 1 表示无设备 unsigned char DS18B20_Reset(void) { unsigned char presence 1; DQ_OUT_LOW(); // 拉低总线 Delay_Us(500); // 至少拉低 480us DQ_INPUT_MODE(); // 释放总线切换到输入模式 Delay_Us(60); // 等待 15~60us 后采样 presence DQ_READ(); // 读到 0 说明从机拉低了总线 Delay_Us(400); // 等待剩余复位时隙完成 DQ_OUTPUT_MODE(); return presence; }这里把 DQ 引脚先在输出模式和输入模式之间切换是因为 DS18B20 的总线是开漏的如果一直保持输出模式读引脚电平会读到自己的输出值。实际在改成自己的板子时只要把DQ_OUT_LOW()和DQ_INPUT_MODE()换成对应引脚的 GPIO 操作宏即可。延时参数里 480us 和 60us 这两个值不要随意缩小Proteus 仿真时对延时精度比较宽容但真实硬件上 DS18B20 对时序窗口要求严格得多。读温度的主流程是按照单总线协议发指令再拼接两个字节的温度数据最后右移 4 位得到带符号的整数部分// 读取温度值返回整数部分单位℃ signed char DS18B20_GetTemperature(void) { unsigned char low, high; signed int temp 0; DS18B20_Reset(); DS18B20_WriteByte(0xCC); // 跳过 ROM 匹配只有一个设备 DS18B20_WriteByte(0x44); // 启动温度转换 Delay_Ms(750); // 转换时间典型值 750ms DS18B20_Reset(); DS18B20_WriteByte(0xCC); DS18B20_WriteByte(0xBE); // 读取暂存器 low DS18B20_ReadByte(); high DS18B20_ReadByte(); temp (high 8) | low; return (signed char)(temp 4); // 只取整数部分符号位保留 }参数说明0xCC 是 Skip ROM 指令单点测温时跳过 ROM 巡检直接访问传感器0x44 启动一次温度转换转换期间传感器会占用总线0xBE 读取暂存器连续读 9 个字节但前两个是温度值。如果你要接多个 DS18B20 做多测点这里必须换成搜索 ROM0xF0和匹配 ROM0x55的逻辑。3.2 DHT11起始信号和 40 位数据读取DHT11 的读取流程是主机拉低至少 18ms → 释放 20~40us → 从机响应拉低 80us → 从机拉高 80us → 连续输出 40 位数据。每一位数据以 50us 低电平开始随后的高电平持续 26~28us 表示 0持续 70us 表示 1。代码实现上读取一位的函数依赖一个微秒级延时后采样总线电平。// 读取一位数据高电平时间 40us 判定为 1否则为 0 unsigned char DHT11_ReadBit(void) { unsigned char bit_val 0; unsigned int timeout 0; while (DHT11_PIN_READ() 0); // 等待低电平结束 Delay_Us(40); // 延时 40us 后采样 if (DHT11_PIN_READ() 1) { bit_val 1; } // 等待高电平结束 while (DHT11_PIN_READ() 1) { if (timeout 1000) break; // 防止死循环 } return bit_val; }注意这个读位函数对延时的依赖很敏感40us 是 0 和 1 的分界线延时长过头会把 1 判成 0。Proteus 仿真时如果你的主频设置和代码里 SystemInit 不匹配这个函数会系统性地读错位。工程里 dht.c 通常还会在读完 40 位后做一次校验把前 4 个字节相加取低 8 位和最后一个字节比对校验不通过就丢弃本次数据用上一次的值继续显示。3.3 ADC 尿湿检测采样配置与阈值判定adc.c 这个文件的职责是把尿湿传感器的模拟电压转换成数字量。STM32F103 的 ADC 是 12 位分辨率参考电压 3.3V所以采样值 0~4095 对应 0~3.3V。配置时需要注意采样时间的选取尿湿传感器的输出内阻偏高刚切换到通道后电压需要时间建立。// 配置 ADC1 并读取指定通道的采样值 void ADC_Config(void) { GPIO_InitTypeDef gpio; ADC_InitTypeDef adc; RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE); gpio.GPIO_Mode GPIO_Mode_AIN; // 模拟输入模式 gpio.GPIO_Pin GPIO_Pin_0; GPIO_Init(GPIOA, gpio); adc.ADC_Mode ADC_Mode_Independent; adc.ADC_ScanConvMode DISABLE; // 单通道扫描 adc.ADC_ContinuousConvMode DISABLE; // 单次转换 adc.ADC_ExternalTrigConv ADC_ExternalTrigConv_None; adc.ADC_DataAlign ADC_DataAlign_Right; adc.ADC_NbrOfChannel 1; ADC_Init(ADC1, adc); ADC_Cmd(ADC1, ENABLE); } uint16_t ADC_ReadChannel(uint8_t channel) { ADC_RegularChannelConfig(ADC1, channel, 1, ADC_SampleTime_239Cycles5); ADC_SoftwareStartConvCmd(ADC1, ENABLE); while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) RESET); return ADC_GetConversionValue(ADC1); }参数说明ADC_SampleTime_239Cycles5是 ADC 可配置的最长采样时间尿湿传感器的信号源阻抗高采样时间短的话电容充不满读出来会偏低。使用这个配置后单次转换周期变长主循环里不需要高频读取每隔 200ms 读一次就够。尿湿判定阈值一般取 1.5V 左右也就是采样值约 1861低于这个值说明尿液让传感器导通、输出电压被拉低。实际调试时可以先打印出干燥和湿润两种状态下的采样值再取两个值的中间位置做阈值。3.4 LCD1602 显示驱动的关键点LCD1602 是 4 位还是 8 位数据模式工程里 lcd.c 的开头注释和宏定义会写明。8 位模式占用 11 个 GPIO4 位模式占用 7 个这个工程里为了给风扇、蜂鸣器留足引脚大概率用的是 4 位模式。写命令和写数据之间要严格控制时序void LCD_WriteCommand(uint8_t cmd) { LCD_RS_LEVEL(0); // 命令模式 LCD_RW_LEVEL(0); // 写模式 LCD_DATA_OUT(cmd); // 并行数据送到数据线 LCD_EN_LEVEL(1); Delay_Us(50); // 使能脉冲宽度要求 LCD_EN_LEVEL(0); // 下降沿锁存数据 Delay_Us(50); }LCD1602 对上电时序有要求VCC 稳定后要等 15ms 以上再发初始化命令否则可能一个字都不显示。Proteus 仿真时如果 LCD1602 一直黑屏先检查有没有给 LCD 的对比度脚V0接可调电位器这在实际硬件里是个经典坑仿真模型里通常已经默认调好了但真做板子时忘记接电位器屏幕就是一片黑。4. 联动逻辑与状态机按键菜单、温控滞回和异常事件优先级的实现驱动层解决的问题是“把数据读上来”联动层解决的是“这些数据放在一起怎么决策”。main.c 里如果只有一个 while(1) 大循环那这个工程的核心逻辑一定写在里面。多传感器系统最怕的是温度一超阈值就把风扇打开、同时按键切换页面又要刷新 LCD结果 LCD 刷一半被风扇控制打断显示出现撕裂。解决办法是把所有事件处理拆成轮询加状态标志。4.1 按键扫描与页面切换按键模块 key_alone.h 只做一件事扫描 GPIO 电平变化并返回按键编号。独立按键的扫描要处理两个问题机械抖动和长按误触。uint8_t Key_Scan(void) { static uint8_t key_lock 0; uint8_t key 0; if (KEY1_PIN 0) { Delay_Ms(10); // 消抖延时 if (KEY1_PIN 0) { key KEY1_VALUE; key_lock 1; } } if (KEY1_PIN 1 key_lock) { // 检测松手 key_lock 0; } return key; }消抖延时 10ms 是机械按键的典型值太大的话快速连按会丢按键太小的话抖动没滤干净。这个函数只检测了 KEY1工程里如果要扩展多按键可以用数组遍历或者逐引脚判断。页面切换的逻辑放在主循环里根据按键返回值切换一个 display_page 变量LCD 的刷新函数根据这个变量决定显示温度还是湿度。4.2 温湿度控制的滞回策略温度控制直接写“超过 28℃ 开风扇、低于 26℃ 关风扇”是最容易出问题的写法因为传感器数据会在阈值边缘抖动风扇会频繁启停。正确的做法是加滞回区间hysteresis开和关的阈值错开// 滞回控制风扇开启阈值和关闭阈值相差 2℃ #define TEMP_FAN_ON 28 #define TEMP_FAN_OFF 26 uint8_t fan_state 0; void TempControl(int8_t temp) { if (fan_state 0 temp TEMP_FAN_ON) { fan_state 1; FAN_GPIO_HIGH(); // 打开风扇 } else if (fan_state 1 temp TEMP_FAN_OFF) { fan_state 0; FAN_GPIO_LOW(); // 关闭风扇 } }这个逻辑的好处是温度从 27℃ 开始升温到 28℃ 开风扇降温到 26℃ 才关风扇中间这 1℃ 的区间内风扇状态保持不变。如果你直接把开关阈值都设在 28℃传感器数值在 27.9 和 28.1 之间抖动时风扇会在几秒内来回切换继电器触点寿命很快就耗完了。湿度控制可以复用同一套逻辑只是阈值换成 DHT11 读到的湿度百分数。4.3 啼哭检测与摇床/音乐联动啼哭检测用的是声音传感器常见方案是一个驻极体麦克风加 LM393 比较器输出数字电平或模拟电压。工程里如果没有单独的声音传感器驱动文件那就是把声音模块的数字输出直接接 GPIO。检测到啼哭后的联动顺序很关键先启动摇床电机再播放音乐同时把“啼哭持续次数”累加如果超过 3 次还没被家长解除就点亮 LCD 上的警示标志。// 啼哭检测回调摇床和音乐联动 void CryHandle(uint8_t cry_flag) { static uint8_t cry_count 0; if (cry_flag) { cry_count; if (cry_count 3) { // 连续检测到啼哭 ROCK_BED_ON(); // 启动摇床 MUSIC_ON(); // 播放音乐 LCD_ShowString(2, 1, CRYING!); // 屏幕警示 } } else { cry_count 0; ROCK_BED_OFF(); } }这里的cry_flag是主循环里对声音传感器输出做去抖之后的结果不能直接用原始电平否则风扇的振动或者外界噪音会让摇床频繁启停。实际调的时候还要注意摇床电机启动瞬间电流很大如果和 LCD 共用 5V 电源屏幕会闪仿真里看不到这个问题但实做板子时要在电机供电上加独立电源或者大电容。4.4 异常事件仲裁撞击 尿湿 啼哭当多个异常同时发生时系统必须先处理最紧急的一个。这个工程里的优先级排序一般是撞击检测是安全类事件一旦触发要先报警其次是尿湿影响舒适度但不涉及安全啼哭优先级最低因为啼哭可能只是因为饿了或者要抱抱不需要触发紧急警报。仲裁在 main.c 里是层层判断结构while (1) { key Key_Scan(); if (key) Key_Handle(key); if (task_1s_flag) { temp DS18B20_GetTemperature(); humi DHT11_GetHumidity(); adc_val ADC_ReadChannel(ADC_CHANNEL_0); // 事件仲裁顺序撞击 尿湿 啼哭 if (MPU6050_HitDetect()) { Alarm_Trigger(ALARM_HIT); } else if (adc_val URINE_THRESHOLD) { Alarm_Trigger(ALARM_URINE); } else if (Sound_Detect()) { CryHandle(1); } else { CryHandle(0); Alarm_Disable(); } TempControl(temp); LCD_Refresh(display_page, temp, humi, adc_val); task_1s_flag 0; } }task_1s_flag是一个定时器中断里置 1 的全局标志保证传感器读取和 LCD 刷新以 1 秒为周期执行。这个结构避免了在 while 循环里反复读传感器导致 LCD 刷新卡顿的问题。中断函数在 stm32f10x_it.c 里或者 main.c 末尾取决于工程组织方式把低频事务放主循环、高频事务放中断是这个工程在架构上做得比较成熟的一点。5. Proteus 仿真验证与 KEIL 构建排错从 hex 加载到单总线上拉的几个真坑仿真工程文件仿真.pdsprj要使用 Proteus 8.9 及以上版本打开低版本会直接提示文件格式不支持。打开后如果原理图里的元器件散落、连线看不清不要急着删掉重画先检查左侧工具栏的元器件列表确认 STM32F103 芯片型号和代码里配置的型号一致。这可能是仿真不跑的第一个坑。把 Keil 生成的 hex 文件加载到 Proteus 的 MCU 里步骤是在原理图上双击 STM32 芯片 → Program File 一栏点文件夹图标 → 选择工程目录下生成的 .hex 文件 → 确认 Crystal Frequency 填 8M → 点运行。如果点击运行后没有任何反应先看右下角有没有报错信息再去确认芯片型号到底是 STM32F103C6 还是 STM32F103C8这两款 flash 大小不同代码里如果开了超出芯片容量的调试选项Proteus 会直接罢工。DS18B20 的 4.7kΩ 上拉电阻在 Proteus 里是最容易被忽略的一环。仿真模型虽然比真实芯片宽松但数据线悬空时读到的电平是随机的表现就是温度值偶尔跳成 85℃ 或者 0xFF 对应的无效值。在 DS18B20 的数据引脚和 VCC 之间放一个 4.7kΩ 电阻DHT11 的数据线同样处理。如果加上电阻后读数仍然异常用示波器从头到尾拉一下时序看主机初始化时的低电平有没有达到 480us。ADC 尿湿检测仿真不动的排查思路和实际硬件不同Proteus 里如果 ADC 引脚悬空读到的值是固定的 4095 或者 0不会出现真实芯片那种随机漂移。这时候在 ADC 引脚上接一个电位器手动调整分压比就能模拟尿液导致电压变化的过程。如果电位器调节时读数纹丝不动检查 adc.c 里配置的通道号是不是和原理图上引脚对应的通道一致STM32F103 的 PA0 是 ADC1 通道 0PA1 是通道 1接错通道读到的永远是另一路的电压。启动文件也容易踩坑工程文件列表里的 STARTUP.A51 是 8051 的启动文件和 STM32 无关编译时确保 Keil 里选的是 STM32F10x 对应的启动文件如 startup_stm32f10x_md.s如果工程加载了错误的启动文件编译不一定报错但程序跑起来就是死机。本文还有配套的精品资源点击获取
返回列表