本章实验 · 三个实验
本章三个实验一览
第三章的主实验要接真继电器、真风扇,动的是 GPIO。为了让「决策和执行」分开练清楚,本章拆成三个实验:实验一、实验二不需要任何接线或器材,在主机或板端(riscv64 gcc)就能跑;实验三才是原来的整板接线实验。实验一、实验二写出的两段逻辑,正是实验三里要平移到真 GPIO 上的那两段。
| 实验 | 练什么 | 器材 | 源码目录 |
|---|---|---|---|
| 实验一 温度扫描边界 |
让模拟温度在 24 ↔ 30 之间来回扫、不越界(到边界前反向) | 无 | code/sweep-ramp/ |
| 实验二 滞回决策与时间线 |
写 fan_decide(>29 开 / <25 关 / 中间保持),看时间线与开关统计 |
无 | code/fan-timeline/ |
| 实验三 继电器控制风扇 |
接线 + 经 libgpiod 控真继电器,把实验一/二的逻辑接到执行端 | 继电器模块、风扇、面包板、杜邦线 | code/temperature_fan.c |
实验一 · 温度扫描边界(sweep-ramp)
实验三主程序里,模拟温度要 24 → 30 → 24 来回扫。最容易写错的是温度越界:曾有一个版本扫到 30.5 / 23.5 才回头,被审阅专门指出。本实验把这小段逻辑单独抽出来,用断言直接卡死越界。源码 chapters/ch03/code/sweep-ramp/,只实现 ramp_next。
步骤
# 主机,或先 scp 到板子再进目录 $ cd chapters/ch03/code/sweep-ramp # 板上:cd ~/sweep-ramp $ make $ ./build/sweep-ramp # 未实现前:温度卡在 24 不动,到不了 30 → FAIL
打开 src/ramp.c,按头文件契约写 ramp_next:
/* 从 t、方向 dir 走一步(RAMP_STEP=0.5),温度保持在 [lo, hi] 内。
* dir>0 且 t+0.5 > hi → 返回 hi、方向改 -1; (到上限前就钳住反向)
* dir<0 且 t-0.5 < lo → 返回 lo、方向改 +1;
* 否则平推一步,方向不变。 */
实现后重新 make 再跑:
$ ./build/sweep-ramp # 期望: # ok 200 步都在 [24, 30] 内 # ok 到达过上限 30 # ok 到达过下限 24 # ALL PASS # 对照参考:make sol && ./build/sweep-ramp-sol(同样 ALL PASS)
本实验成功判据:./build/sweep-ramp 打印 ALL PASS;能口头说出「到边界是钳住反向,不是越过去再反」。
ALL PASS实验二 · 滞回决策与时间线(fan-timeline)
实验二写这一章的决策函数 fan_decide,并跑一整天时间线看它的表现。注意:它的签名和实验三 temperature_fan 里要你写的 fan_decide 一模一样——本实验写对,实验三直接平移即可。源码 chapters/ch03/code/fan-timeline/,只实现 decide.c。
步骤
# 主机,或先 scp 到板子再进目录 $ cd chapters/ch03/code/fan-timeline # 板上:cd ~/fan-timeline $ make $ ./build/fan-timeline # 未实现前:风扇一直 OFF,SUMMARY 三数全错 → 预期
打开 src/decide.c 写 fan_decide(阈值 T_HIGH=29 / T_LOW=25,已在 fan.h):
/* temp_c > T_HIGH → *fan_on = 1
* temp_c < T_LOW → *fan_on = 0
* 中间区间 → 保持 *fan_on(滞回!不要每步都跟着温度上下抖) */
驱动会扫 240 步温度并打印时间线(每行 [步数] t=温度 fan=ON/OFF),最后一行是统计:
$ ./build/fan-timeline | tail -1 # 参考(make sol 一致):SUMMARY steps=240 on_steps=117 turn_ons=9
其中 on_steps 是风扇 ON 的总步数,turn_ons 是「关→开」的次数。如果写成「高于 29 开、一降到 29 以下就关」的抖法,turn_ons 会大得多——滞回的价值就在这:一次降温要跌破 25 才关,风扇不会在 28/29 之间反复开关。
本实验成功判据:自己写的 ./build/fan-timeline 末行 steps=240 on_steps=117 turn_ons=9;能口头解释滞回如何避免抖动。
SUMMARY … turn_ons=9实验三 · 继电器控制风扇(真 GPIO)
实验一你写好了「温度在 24↔30 扫描不越界」,实验二写好了「>29 开 / <25 关」的决策。实验三把它们接到真继电器上:把 fan_decide 平移到 temperature_fan.c(函数签名一致),再补上 GPIO 申请与置位,接上风扇负载验收。下面是原实验正文,器材、接线、编译、验收都从这里开始。
说明
你能学到什么:经内核申请并改 GPIO;用滞回把「决策」接到真继电器;用改宏证明控制点在软件。
讲义已用 gpioset 跟做过信号链。本实验递进两步:接上风扇负载,并把讲义里的路径写成你自己的 C。
- 工程:
chapters/ch03/code/(板上:~/ruyi-riscv-embedded/ch03-lab/) - 学生文件:
temperature_fan.c(默认make) - 参考实现:
temperature_fan-sol.c(make sol;先自己写,过关后再对照) - 脚:
gpiochip5line 5 / 丝印 IO1_5 - 输入:程序内部模拟温度(本章不接 DHT22)
make 通过;终端模拟温度来回;超上限风扇转、低下限风扇停;改阈值后开/关点跟着变。
实验器材
| 东西 | 用途 |
|---|---|
| 荔枝派 4A | 跑 Linux;排针接线 |
| 继电器模块(1 路,5V) | 信号端受 GPIO 控制,触点通断风扇电源 |
| 5V 小风扇 | 负载;红线正极、黑线负极 |
| 面包板 + 杜邦线 | 搭接电源轨与信号线;不必每次焊接 |
| 电烙铁(约 40W)与焊锡 | 需要固定端子、延长线或补焊时使用;本章首次用到 |
本章就这几样。真传感器第四章再接。
chapters/ch03/manuals/(0505单片机继电器模块说明.pdf、HW-221 产品说明书.pdf);排针插针方位见 lpi4a-wiki-assets/io_map.png。旧版 wiki 摘录已移入 manuals/archive/,不作为当前依据。
面包板怎么用
面包板用内部金属簧片把若干插孔连在一起,方便用杜邦线临时搭电路。本课用它把荔枝派排针上的电源、地和信号线接到继电器模块。
说明书用语与商品详情一致,见 chapters/ch03/manuals/面包板_MB102摘录.md 与同目录结构图。
| 名称 | 怎么通 | 本课怎么用 |
|---|---|---|
| 电源轨 | 「+」与「−」各自沿线横向连通 | 正极侧接排针 5V,负极侧接 GND;再从轨上分线到模块 |
| 连接孔 | 同一列 a–e(或 f–j)五个孔纵向连通;相邻列不通 | 模块某一脚与杜邦线插在同一列即相连 |
| 隔离凹槽 | 正中沟上下互不通 | 跨沟必须另用跳线,不要默认已连通 |
- 接线前断开开发板电源;插拔杜邦线时捏线帽,避免扯断芯线。
- 正极与负极不要在轨上短接;5V 与 GND 插反可能导致模块异常或损坏。
- 同一信号只占一列;乱插多列会造成意外并联。
电烙铁与安全(本章首次)
本章以面包板与杜邦线为主。若课程提供的线材需要补焊端子、延长线,或继电器螺丝端子需要焊环/焊片,会用到电烙铁。下图为常见的约 40W 直热式烙铁外形。
| 步骤 | 要求 |
|---|---|
| 通电前 | 烙铁头安装牢固;电源线无破损;工作台干燥、无易燃物;备好烙铁架与湿海绵/黄铜清洁丝 |
| 加热中 | 烙铁始终放在架子上,尖头朝外;勿触碰金属杆与烙铁头;手握隔热手柄 |
| 焊接时 | 先加热焊点再送锡,勿甩锡;通风或侧向排烟;避免长时间烫同一焊盘 |
| 结束后 | 断电,待完全冷却后再收纳;清洗烙铁头并镀一层薄锡防氧化 |
荔枝派排针怎么认
板子侧面那排竖针是 20 针排针。信号脚高电平约 1.8V;本课光耦继电器可直连。
| 排针上的字 | 本课用途 |
|---|---|
| 5V | 继电器线圈;经继电器给风扇供电 |
| GND | 共地 |
| IO1_5 | 继电器信号脚(默认) |
| IO1_6 | 备用(第四章 DHT22) |
chapters/ch03/manuals/lpi4a-wiki-assets/io_map.png。以文档图为准;早期板丝印可能不准。认脚认丝印;(chip, line) 用运行时核对。
接线总表
先认丝印,再接线。丝印与表不完全一致时,以实物丝印为准,逻辑按下表角色对应。
A. 继电器(信号 + 供电)
| 从 | 到 | 说明 |
|---|---|---|
| 排针 IO1_5 | 继电器信号脚 IN / X1 | GPIO 直连 |
| 排针 5V | 继电器 V+ / VCC | 线圈要 5V |
| 排针 GND | 继电器 V- / GND | 共地,必接 |
跳线帽拨到 H(高电平有效)。
B. 风扇(走继电器触点)
| 接法 | 说明 |
|---|---|
| 排针 5V → 继电器 NO(常开) | 平时断开 |
| 继电器 COM → 风扇红线 | 吸合后红线才有电 |
| 风扇黑线 → 排针 GND | 负极回地 |
排针 5V ──► [继电器 NO]──(吸合时接通)──[COM]──► 风扇红线 排针 GND ──────────────────────────────────────► 风扇黑线 排针 IO1_5 ──► 继电器 IN / X1(直连) 排针 5V ──► 继电器 V+ 排针 GND ──► 继电器 V-
步骤 1:接线
- 接线前断开开发板电源。
- 按总表:先 GND(共地),再 5V,再信号线 IO1_5 → IN,最后接风扇。
- 确认跳线帽在 H;没碰到 Fan Power 白色插座。
- 再上电。
本步成功判据:继电器电源指示灯亮;未触发时风扇不转;能指着线说出「地 / 信号 / 风扇回路」。
步骤 2:实现三个函数
打开 temperature_fan.c。脚手架已给出宏、main 调用顺序、信号收尾;只改标了 STUDENT TODO 的区块——三个函数的原型与实现都写在 main 上方。
先直接 make:应看到类似 implicit declaration of function 'line_request'——说明 main 在等你补函数。这是预期起点。
你要写的契约
/* 申请一根脚;direction 为 OUTPUT 时立刻置 val(0 或 1) * 成功返回 request 指针;失败返回 NULL。 * 全局变量 chip 已由 main 打开,可直接用。 */ struct gpiod_line_request *line_request(unsigned int offset, int direction, int val); /* 写继电器信号端:on 非 0 → 吸合;on 为 0 → 释放。 * 用全局 fan_req 与宏 FAN_LINE;成功时打印 [INFO] fan ON/OFF。 */ void fan_set(int on); /* 滞回决策:只改 *fan_on,不要在这里调 gpiod。 * temp_c > T_HIGH → *fan_on = 1 * temp_c < T_LOW → *fan_on = 0 * 中间区间 → 保持 *fan_on 原值 * fan_on 为空指针时直接返回。 */ void fan_decide(float temp_c, int *fan_on);
写的时候要想清楚
line_request:为什么申请输出时要把初值设成 0,而不是先申请再随便写?fan_set与fan_decide为什么拆开?决策函数里直接set_value会怎样?- 中间区间「保持」时,
main为什么用prev比较后再调用fan_set? - 退出前脚手架已调用
fan_set(0):若你的fan_set写错,Ctrl+C 后风扇可能一直转——现象会指回你的实现。
gpiod_chip_request_lines → gpiod_line_request_set_value。consumer 名字建议 "temperature_fan",方便 gpioinfo 认人。
temperature_fan-sol.c。建议过关后再打开;卡住超过一刻钟再对照某一函数即可。
步骤 3:编译运行
主路径:在 x86 Linux 上激活第一章的 venv-gnu-ruyisdk,交叉编译后再拷到荔枝派运行。同一份 Makefile 在荔枝派上会自动改用板载 gcc(若主机交叉因缺少 libgpiod 失败,可直接在荔枝派上 make)。编译的是你自己写的 temperature_fan.c。
# —— 在 x86 Linux 上 —— $ . ~/venv-gnu-ruyisdk/bin/ruyi-activate $ cd chapters/ch03/code $ make clean && make $ file temperature_fan # 期望含 RISC-V;若报找不到 gpiod.h,改到荔枝派上编译(见下) # —— 在荔枝派上(源码目录)—— $ cd ~/ruyi-riscv-embedded/ch03-lab $ make clean && make $ sudo ./temperature_fan
若讲义跟做留下的 gpioset 仍占着 line 5,先 sudo pkill -x gpioset。
sudo?/dev/gpiochip* 默认归 root。
对照参考实现(助教验收路径):make sol && sudo ./temperature_fan-sol。图 3 即 make sol 的运行记录。
make sol):模拟温度过 29°C 风扇开;Ctrl+C 后清理步骤 4:模拟温度控制验收
- 启动打印
[INFO] 模拟温度控制: T_HIGH=29.0 T_LOW=25.0,并说明继电器在 IO1_5。 - 周期性打印
[INFO] temp=…C fan=OFF|ON,模拟温度在 24~30 之间来回。 - 升过 29°C →
[INFO] fan ON,继电器吸合,风扇转。 - 降到 25°C 以下 →
[INFO] fan OFF,风扇停。 - 中间区间(25~29)保持上一状态。
停程序:SSH 里 Ctrl+C。
fan_set(0) 再释放。要退出后也更稳,可在 X1 到 GND 加 10kΩ 下拉。
本步成功判据:能演示超温开 / 降温关;Ctrl+C 后风扇停转(或靠下拉稳定关断)。
步骤 5:改阈值
改 temperature_fan.c 顶部 T_HIGH / T_LOW(默认 29 / 25;例如改成 27 / 23),重新 make 再跑,观察开/关温度点变化。也可加大 STEP_DELTA 让扫温更快。把改后的数填进记录表即可;教材截图仍用默认 29 / 25 的图 3,不另拍改阈值终端。
本步要证明的:阈值是你程序里的常量;硬件只跟着高低电平走。改阈值前后的终端对照图见第四章实验的 set temp。
常见故障
| 现象 | 先查什么 |
|---|---|
implicit declaration / 编译不过 | 三个函数是否写在 main 上方?签名是否与契约一致? |
程序报 Permission denied | sudo ./temperature_fan |
| busy / 打不开 gpiochip | 残留 gpioset?sudo pkill -x gpioset |
| 程序在跑,风扇完全不动 | 共地?继电器 5V?跳帽 H?NO/COM?信号是否 IO1_5? |
| 置 1 没反应 | 排针用 gpiochip5;sudo cat /sys/kernel/debug/gpio 看是否 out hi |
| Ctrl+C 后风扇还在转 | fan_set(0) 是否真的写了低电平? |
| 继电器灯亮,风扇不转 | 风扇 5V 回路:NO–COM–红线–黑线回 GND |
| 中间区间乱抖 | fan_decide 是否在中间误改了 *fan_on? |
| file 显示 x86-64 | 检查 CROSS_COMPILE / PATH;或板端原生编译 |
取证顺序:先看编译与你的三函数 → 再 chip/占用 → 再接线。
验收表
| 验收项 | 通过条件 | □ |
|---|---|---|
| 接线 | IO1_5 直连继电器 IN;继电器 5V/GND;风扇走 NO–COM;跳帽 H;共地 | |
| 自己写的函数 | line_request / fan_set / fan_decide 在学生文件中实现;make 通过 | |
| 运行 | sudo ./temperature_fan 打印模拟温度 | |
| 控制 | 超 29°C 开、低于 25°C 关,风扇启动或停止(与图 3 / 学生版默认常量一致) | |
| 改阈值 | 改过 T_HIGH/T_LOW(或 STEP_DELTA)并观察到开/关点变化。课程不另附第三章改阈值终端图;截图证据见第四章 set temp |
记录表(自填)
| 项 | 你的结果 |
|---|---|
| 继电器脚(chip + line / 丝印) | gpiochip5 line 5 / IO1_5 |
| T_HIGH / T_LOW(改后) | |
| 风扇转时继电器工作灯 | |
| 实测哪根脚拉不高(若有) |