物联网项目32代码(2)
172.7物联网项目32代码(2)
/* gizwits_product.c:以下按文件中的位置摘录本节新增或实际讲解的业务代码片段,不补入下一课的串口标志位。 */
#include "adc.h" /* 引入 CubeMX 生成的 ADC1 句柄与初始化接口声明,使本文件能使用 hadc1。 */
uint32_t ad_value; /* 保存 ADC1 单通道转换得到的 12 位原始码,正常范围为 0~4095。 */
/* gizwitsEventProcess() 中机智云生成框架已有的 EVENT_led1 分支,以及本节填入的实体灯与 OLED 操作。 */
case EVENT_led1: /* App 下发可写 led1 数据点时,机智云协议层进入这个事件分支。 */
currentDataPoint.valueled1 = dataPointPtr->valueled1; /* 把本次下发的布尔值保存到设备当前数据点结构体。 */
GIZWITS_LOG("Evt: EVENT_led1 %d \n", currentDataPoint.valueled1); /* 通过调试日志打印本次 led1 事件值。 */
if(0x01 == currentDataPoint.valueled1) /* 判断 App 下发值是否为 1;课程把 1 定义为开灯。 */
{ /* “开灯”分支开始。 */
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET); /* 将 PB5 拉低;课程板载 LED 是低电平点亮。 */
OLED_ShowString(0,6,"LED:ON ",16); /* 在第 4 个 16 像素行显示开灯状态,末尾空格用于覆盖旧字符串残留的 F。 */
//user handle;这是生成框架原有的占位注释,本节没有在此调用 userHandle()。
} /* “开灯”分支结束。 */
else /* 下发值不是 1 时进入关灯分支。 */
{ /* “关灯”分支开始。 */
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET); /* 将 PB5 置高,切断板载 LED 的灌电流回路而熄灯。 */
OLED_ShowString(0,6,"LED:OFF",16); /* 在同一位置显示关灯状态;该字符串比 LED:ON 多一个可见字符。 */
//user handle;保留生成包原有注释,实际实体灯动作已经由上面的 GPIO 写操作完成。
} /* “关灯”分支结束。 */
break; /* 结束 EVENT_led1 分支,防止继续落入下一个事件 case。 */
/* userHandle() 中本节新增的 ADC 采集、烟雾判断及屏幕显示语句。 */
HAL_ADC_Start(&hadc1); /* 软件启动 ADC1 的一次规则通道转换。 */
HAL_ADC_PollForConversion(&hadc1,10); /* 最多轮询等待 10 ms;课程源码没有检查等待是否成功。 */
ad_value=HAL_ADC_GetValue(&hadc1); /* 读取 ADC1 数据寄存器,把 PA1/ADC1_IN1 的转换码保存到 ad_value。 */
currentDataPoint.valuesmoke = HAL_GPIO_ReadPin(SMOKE_GPIO_Port, SMOKE_Pin); /* 把 PA4 上的原始高低电平直接写入 smoke 数据点。 */
currentDataPoint.valuesun =100 - ad_value/4095.0*100; /* 把 0~4095 反向映射到约 100%~0%,浮点结果再写入整数数据点。 */
if(HAL_GPIO_ReadPin(SMOKE_GPIO_Port, SMOKE_Pin)) /* 再读一次 PA4;非零即 GPIO_PIN_SET,课程实测将其解释为“无烟”。 */
{ /* PA4 为高电平的分支开始。 */
OLED_ShowString(0,4,"Smog:N",16); /* 在第 3 个 16 像素行左侧显示 N,表示课程当前模块未触发烟雾阈值。 */
HAL_GPIO_WritePin(BEEP_GPIO_Port, BEEP_Pin, GPIO_PIN_RESET); /* 将 PB8 拉低,关闭高电平驱动的板载蜂鸣器。 */
}else{ /* PA4 为低电平时,进入课程实测的“检测到烟雾”分支。 */
OLED_ShowString(0,4,"Smog:Y",16); /* 在相同位置显示 Y,表示模块比较器已经触发。 */
HAL_GPIO_WritePin(BEEP_GPIO_Port, BEEP_Pin, GPIO_PIN_SET); /* 将 PB8 置高,经三极管驱动板载有源蜂鸣器发声。 */
} /* 烟雾状态判断结束。 */
sprintf(OLED_BUFF,"Sun:%2d%%",currentDataPoint.valuesun); /* 把相对光照百分比写入 OLED 缓冲区;%% 输出字面量百分号。 */
OLED_ShowString(64,4,OLED_BUFF,16); /* 从屏幕右半边 x=64、第 3 行显示光照文本。 */
/* userInit() 中本节新增的一次性 ADC 校准和 LED 初始状态显示。 */
OLED_ShowString(0,6,"LED:OFF",16); /* 上电初始化时先显示 LED 关闭,与 PB5 默认高电平相对应。 */
HAL_ADCEx_Calibration_Start(&hadc1); /* ADC1 初始化完成后执行一次自校准;课程源码没有检查返回状态。 */
源码范围说明:顶部只收录 172 视频实际讲解并写入的应用代码。
case EVENT_led1外层的机智云事件函数、CubeMX 自动生成的 ADC/GPIO 初始化和 HAL 库实现没有整段抄入;171 已经讲过的 DHT11/OLED 标题代码也不重复。课程完成工程后来把烟雾if整段注释掉,但本节视频结尾确实用这段逻辑完成了 OLED 与蜂鸣器测试,因此这里按本节画面保留为可执行语句。最终工程中的led_on_cmd、led_off_cmd等变量属于 173 的 ZigBee 串口交互,本节没有提前补入。
一、物联网项目编程控制
本节补齐 STM32 本地端的三组功能:
烟雾模块 DO ──► PA4 数字输入 ──► OLED 状态 + PB8 蜂鸣器 + 机智云 smoke 数据点
光敏模块 AO ──► PA1/ADC1_IN1 ──► 12 位 ADC ──► 相对百分比 + OLED + sun 数据点
手机 App led1 ──► 机智云 EVENT_led1 ──► PB5 板载 LED + OLED 状态
视频最后验证了本地温湿度、烟雾、相对光照和第一路灯。与 51 单片机之间的 ZigBee/USART3 指令收发尚未编写,留到下一节。
1.1 编程控制温室项目
原标题中的“温室项目”可理解为此处要实现的环境监测与控制部分。本节既有周期采集,也有事件驱动控制,两类代码位置不同:
- DHT11、ADC 和烟雾输入属于设备主动采集,写在循环执行的
userHandle()。 - App 下发的 LED 命令属于机智云事件,写在
gizwitsEventProcess()的EVENT_led1分支,不是写在userHandle()。 - ADC 校准、OLED 初始状态等只需执行一次的动作放在
userInit()。
1.1.1 编程控制温室项目代码分析
1. HAL_GPIO_ReadPin() 的返回值
该函数返回类型是 GPIO_PinState,枚举中包含 GPIO_PIN_RESET 和 GPIO_PIN_SET。课程可把它们理解为逻辑 0 和 1,但更严谨的比较方式仍是直接与枚举常量比较,而不是假设所有平台都用任意整数表达:
if (HAL_GPIO_ReadPin(SMOKE_GPIO_Port, SMOKE_Pin) == GPIO_PIN_SET) /* 明确判断输入引脚当前是否为高电平。 */
顶部保持课程原代码 if(HAL_GPIO_ReadPin(...)),没有擅自改写。该判断只说明引脚是高还是低;“有烟/无烟”的业务含义还取决于模块输出有效电平。
2. 本课烟雾模块的实测逻辑
视频先承认不确定高低电平含义,再通过调节模块电位器跨过比较器阈值进行验证,最终代码采用:
| PA4/SMOKE | 本课 OLED | PB8/BEEP | 本课解释 |
|---|---|---|---|
| 高电平 | Smog:N | 低,蜂鸣器关闭 | 未检测到烟雾 |
| 低电平 | Smog:Y | 高,蜂鸣器发声 | 检测到烟雾/比较器触发 |
这个表是课程当前模块和阈值下的测试结果,不是所有烟雾模块的统一规则。电位器只是在改变比较器阈值,拧到触发状态只是模拟数字输出翻转,并不等于用真实、已知浓度的烟气完成了传感器校准。
还有一个语义细节:源码把 PA4 原始状态直接赋给 currentDataPoint.valuesmoke。按上表,1 实际表示“无烟”、0 表示“触发”。若机智云布尔数据点约定 1=有烟,云端含义就会反;只有平台显示规则也按 1=无烟、0=有烟 配置时才一致。业务代码最好把“原始电平”先转换成含义明确的告警布尔值,但本节没有这样改,所以顶部仍保留原赋值。
3. 板载蜂鸣器为什么是高电平响
课程开发板原理图中,蜂鸣器正端接 3.3 V,负端接 S8050 NPN 三极管 Q3 的集电极;PB8 的 BEEP 信号通过 1 kΩ 电阻 R27 驱动基极,R28(10 kΩ)把基极下拉:
3.3 V ──► 有源蜂鸣器 ──► Q3 集电极
Q3 发射极 ──► GND
PB8/BEEP ── R27 1 kΩ ──► Q3 基极
│
R28 10 kΩ
│
GND
- PB8 为高电平:产生基极电流,Q3 导通,蜂鸣器电流回路闭合,蜂鸣器响。
- PB8 为低电平:Q3 截止,蜂鸣器没有完整回路,蜂鸣器不响。
- 三极管承担蜂鸣器电流,避免由 STM32 GPIO 直接驱动负载;R27 限制基极电流,R28 保证 MCU 复位或引脚悬空时 Q3 倾向于关闭。
因此视频前段的“先随便写一个,错了再换”只是调试过程;有原理图时可以在烧写前确定板载蜂鸣器为高电平开启。CubeMX 最终把 PB8 配成推挽输出,初始写 GPIO_PIN_RESET,所以上电默认静音。
4. 烟雾模块供电不能只看它输出数字量
视频说烟雾模块可优先接 5 V,因为读取的是高低电平。这个理由并不充分:模块供电为 5 V 时,数字输出高电平也可能接近 5 V,是否能直连 3.3 V STM32 必须查模块的输出级和上拉电压。课程仓库没有该烟雾模块的原理图或型号资料,因此精教版不能凭外观断言其 5 V 数字输出一定安全。
安全做法是先测量/查明:
- 模块允许的供电电压和加热器电流;
- DO 是推挽输出还是开集电极输出;
- 高电平由什么电压上拉;
- 接入 PA4 后的高电平是否落在 STM32 允许范围内。
若 DO 可能输出 5 V,应使用合适的分压或电平转换,或在确认模块允许的前提下以 3.3 V 供电。不能用“它只有 0 和 1”代替电压兼容性检查。
1.1.2 物联网项目思维导图分析
本节光敏模块使用 AO 模拟输出,而不是 DO 比较器输出。信号链为:
环境光变化
↓
光敏电阻阻值变化
↓
模块分压点 AO 电压变化
↓
PA1 / ADC1_IN1
↓
12 位 ADC 码 0~4095
↓
反向换算为 0~100 的相对百分比
课程配套的四线光敏模块资料说明:VCC 可用 3.3~5 V,模块同时提供 DO 和 AO;电位器调整的是 LM393 比较器的数字阈值,主要影响 DO。本项目读取的是 AO,因此 ADC 得到连续变化的分压电压。
根据模块电路图,光敏电阻在 AO 分压点与 GND 之间。光越强,光敏电阻阻值越小,AO 越被拉向 GND,所以原始 ADC 码反而越小;视频实测“手机灯照射后数值下降”与这张电路图一致。课程最终使用 100 - 原始比例,让显示值随光增强而增大。
ADC 配置与调用顺序
完成工程的 CubeMX 配置为:
| 项目 | 值 |
|---|---|
| 输入 | PA1 / ADC1_IN1 |
| 分辨率 | 12 bit,正常结果 0~4095 |
| 规则通道数 | 1 |
| 连续转换 | Disable |
| 触发 | Software Start |
| 数据对齐 | Right |
| 采样时间 | 239.5 ADC cycles |
因为连续转换关闭,所以 userHandle() 每次都要依次执行 HAL_ADC_Start()、HAL_ADC_PollForConversion()、HAL_ADC_GetValue()。HAL_ADCEx_Calibration_Start() 只需在 ADC 初始化完成后执行一次;main.c 的实际顺序是 MX_ADC1_Init() 在前、userInit() 在后,满足这个前提。
课程代码忽略了 Start、Poll 和 Calibration 的返回状态。如果轮询超时,随后仍读取数据寄存器,可能使用旧值。可靠程序应只在转换完成时更新数据点,但这属于后续健壮性改进,未添加到顶部原代码。
模拟输入电压安全
STM32F103ZET6 数据手册规定 ADC 转换输入范围为 0 V~VREF+,而本板 VREF+/VDDA 约为 3.3 V。光敏模块若由 5 V 供电,AO 分压点可能进入接近 5 V 的范围,超出正常 ADC 转换范围;因此课程要求在读取 AO 时给模块接 3.3 V 是正确且必要的。
模块说明书写“工作电压 3.3~5 V”,只代表模块本身可在这个范围工作,不代表它的 AO 在任何供电下都能安全接入任意 MCU。模块供电要求与 MCU 输入范围是两项必须同时满足的条件。
1.1.3 温室项目温室部分代码
1. 4095 与 4096 不是完全相同
12 位 ADC 一共有 $2^{12}=4096$ 个离散码,码值从 0 到 4095。若要求最大码 4095 显示为 100%,课程采用:
$ P_{raw}=\frac{ADC}{4095.0}\times100 $
若除以 4096.0,最大值会得到约 99.976%,而不是精确 100%。两者差异很小,但含义不同,不能简单说“都一样”。课程最后选择 4095.0,并用浮点常量避免整数除法先把绝大多数结果截成 0。
由于实物电路强光时 ADC 值变小,课程显示值为:
$ P_{sun}=100-\frac{ADC}{4095.0}\times100 $
valuesun 是 uint32_t,赋值时浮点小数部分会被截去。例如计算结果 63.8 最终保存为 63。这个数只是当前模块、供电和分压关系下的相对比例,不是经过标定的照度 lux,也不能跨不同光敏电阻直接比较绝对光强。
2. OLED 第三行布局
课程把同一行分成左右两块:
x=0 x=64 x=127
┌────────────────────────────────────┬────────────────────────────────────────┐
│ Smog:N / Smog:Y │ Sun:xx% │ y=4
└────────────────────────────────────┴────────────────────────────────────────┘
y=4 是 OLED 页地址,第 3 个 16 像素高显示行;x=64 是屏幕横向中点,不等于把字符串自身居中。
格式串 "Sun:%2d%%" 的 %2d 只是最小宽度 2:0~9 会补一个前导空格,10~99 占两位,100 会占三位。于是 Sun:100% 有 8 个字符,正好占满右侧 64 像素;数值从 100 变回两位数时,新字符串只有 7 个字符,末端可能残留旧像素。课程只处理了 LED 的长短字符串残留,没有处理这里;更稳妥的办法是固定宽度、先清局部区域或追加合适覆盖字符,但这些改法没有写进本节源码。
3. 烟雾状态与蜂鸣器
视频用电位器人为改变比较器阈值,观察到模块指示灯翻转、OLED 从 N/Y 切换并伴随蜂鸣器开关。这验证的是“数字比较器输出→GPIO→显示/蜂鸣器”的链路,不代表已经完成对真实烟雾浓度、响应时间和报警阈值的标定。
HAL_GPIO_ReadPin() 在本函数中被调用两次:一次赋给云端数据点,一次进入 if。如果信号恰在两次读取之间变化,两个结果理论上可能不一致。课程当前轮询场景一般不明显,但工程化实现可先读入一个局部变量,再统一用于上报、显示和报警;本节按源码原样保留,没有擅自重构。
1.1.4 温室项目LED部分代码
1. 控制代码应放在事件分支
led1 在机智云平台被定义为可写数据点,所以协议生成器创建 EVENT_led1。App 下发后,协议层解析数据并调用 gizwitsEventProcess(),本节只需在生成器预留的 case 中补充硬件动作:
App 开关
↓
ESP8266 / 机智云协议
↓
EVENT_led1
↓
currentDataPoint.valueled1
├─ 1:PB5=RESET → 灯亮 → OLED 显示 LED:ON
└─ 0:PB5=SET → 灯灭 → OLED 显示 LED:OFF
原笔记称其位于 userHandle(),不准确。userHandle() 是持续采集入口,EVENT_led1 是收到下发事件时才执行的控制入口;源码中的 //user handle 只是机智云模板留的注释文字,并不表示这里自动调用了同名函数。
2. 为什么 PB5 低电平点亮
开发板原理图中,PB5 连接网络 LED0,LED 和 1 kΩ 限流电阻的另一端接 3.3 V:
3.3 V ──► LED ──► 1 kΩ ──► PB5
PB5 输出低电平时,电流从 3.3 V 经 LED 和电阻流入 PB5,LED 点亮;输出高电平时两端压差不足,LED 熄灭。CubeMX 的用户标签把 PB5 命名为 LED1,所以代码里的 LED1_Pin 实际控制原理图网络名 LED0,不要被两个命名层级混淆。
CubeMX 初始化先把 PB5 写为 GPIO_PIN_SET,使灯上电默认熄灭;userInit() 再显示 LED:OFF,硬件状态与初始界面一致。
3. OLED 字符串残留
LED:OFF 有 7 个字符,LED:ON 只有 6 个。如果先显示 OFF,再在同一位置直接写 ON,最后一个 F 不会自动清除,屏幕可能显示成 LED:ONF。课程源码给较短文本写成 "LED:ON ",用尾部空格覆盖旧字符。这是局部刷新时常用的最小修复。
4. 为什么第二盏灯不能直接照搬
视频发现 led2 没有对应事件,并承认平台读写类型配置错了。它至少有两个独立问题:
- 机智云平台中的
led2是只读数据点,因此生成代码没有EVENT_led2;要接受 App 命令,必须先改为可写并重新生成/合并协议代码。 - 真实 ZE 开发板第二路用户 LED 接
PE5,但当前 CubeMX 工程目标是 LQFP48 的 STM32F103C8Tx,该封装没有 PE5。
所以不能只在 userInit() 中“重新初始化某个 E 口”就算修好。正确做法是按真实 STM32F103ZET6/LQFP144 重建或迁移工程,再配置 PE5,并同步修正机智云 led2 数据点为可写。课程口述的“E2/E8”是记忆不清,原理图给出的确切引脚是 PE5。
1.1.5 功能测试
课程测试结果应分层理解:
| 测试动作 | 观察结果 | 能证明什么 | 不能证明什么 |
|---|---|---|---|
| App 切换客厅灯 | PB5 灯亮灭,OLED 显示 ON/OFF | EVENT_led1、GPIO 和显示链路可工作 | 第二路灯和远端 51 节点也已可控 |
| 手机灯照光敏模块 | 显示与 App 中相对光照值变化 | AO、ADC1 和反向百分比链路可工作 | 数值就是准确 lux |
| 调节烟雾模块电位器 | 指示灯、OLED、蜂鸣器状态变化 | 数字比较器、PA4 和 PB8 链路可工作 | 对真实烟雾浓度已经标定 |
| 查看温湿度 | 本地数值随环境变化 | DHT11 读取链路工作 | 171 中云端 temp1/humi1 字段错位已经修复 |
视频最后说“除与 51 单片机交互外基本完成”,应限定为 STM32 本地功能已能演示。尚未完成的包括:USART3/ZigBee 命令帧、51 端执行与应答、心跳检测、第二路温湿度/灯、窗帘动作,以及 171 已指出的温湿度云端字段映射问题。
参考资料
- 本地课程配套 STM32F103ZE 开发板原理图:核对 PB8 蜂鸣器三极管驱动、PB5/PE5 板载 LED 及其有效电平。
- 本地 光敏电阻传感器模块电路图 与 四线制使用说明书:核对 AO 分压方向、DO 比较器阈值、3.3~5 V 模块供电范围和四线接口。
- 本地 ST 官方 STM32F103ZET6 数据手册:核对 12 位 ADC 的 0~4095 码空间、
0~VREF+转换范围、最高 14 MHz ADC 时钟以及上电后校准建议。 - 本节落地工程中的 gizwits_product.c、adc.c、gpio.c、main.h 与 STM32F103C8x.ioc:核对本节业务语句、引脚标签、ADC 参数和上电默认电平;完成工程含后续课程代码,顶部已按 172 边界剔除。