RISC-V Linux 嵌入式编程技术 · 第三章

本章实验 · 三个实验

实验一 温度扫描边界 · 实验二 滞回决策与时间线 · 实验三 继电器控制风扇(真 GPIO)

本章三个实验一览

第三章的主实验要接真继电器、真风扇,动的是 GPIO。为了让「决策和执行」分开练清楚,本章拆成三个实验:实验一、实验二不需要任何接线或器材,在主机或板端(riscv64 gcc)就能跑;实验三才是原来的整板接线实验。实验一、实验二写出的两段逻辑,正是实验三里要平移到真 GPIO 上的那两段。

实验练什么器材源码目录
实验一
温度扫描边界
让模拟温度在 24 ↔ 30 之间来回扫、不越界(到边界前反向) code/sweep-ramp/
实验二
滞回决策与时间线
fan_decide(>29 开 / <25 关 / 中间保持),看时间线与开关统计 code/fan-timeline/
实验三
继电器控制风扇
接线 + 经 libgpiod 控真继电器,把实验一/二的逻辑接到执行端 继电器模块、风扇、面包板、杜邦线 code/temperature_fan.c
建议:先在板上把实验一、实验二跑通,再进入实验三接线——那时你只剩「GPIO 申请/置位」没写过,风扇控制本身你已经会了。

实验一 · 温度扫描边界(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;能口头说出「到边界是钳住反向,不是越过去再反」。

板上 sweep-ramp-sol 输出 ALL PASS
板端证据(SSH → sweep-ramp):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.cfan_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;能口头解释滞回如何避免抖动。

板上 fan-timeline-sol 输出 SUMMARY turn_ons=9
板端证据(SSH → fan-timeline):SUMMARY … turn_ons=9

实验三 · 继电器控制风扇(真 GPIO)

实验一你写好了「温度在 24↔30 扫描不越界」,实验二写好了「>29 开 / <25 关」的决策。实验三把它们接到真继电器上:把 fan_decide 平移到 temperature_fan.c(函数签名一致),再补上 GPIO 申请与置位,接上风扇负载验收。下面是原实验正文,器材、接线、编译、验收都从这里开始。

说明

本实验要干什么:按表接好信号端与风扇负载;在脚手架里实现三个 C 函数;跑通模拟温度控制;再改阈值观察开/关点。
你能学到什么:经内核申请并改 GPIO;用滞回把「决策」接到真继电器;用改宏证明控制点在软件。

讲义已用 gpioset 跟做过信号链。本实验递进两步:接上风扇负载,并把讲义里的路径写成你自己的 C。

成功时你能演示:你写的代码能 make 通过;终端模拟温度来回;超上限风扇转、低下限风扇停;改阈值后开/关点跟着变。
安全:风扇大电流经继电器触点;GPIO 只接信号端。板子上标着 Fan Power 的白色插座是板载散热风扇用的,与本实验风扇无关。接线时尽量断电。

实验器材

东西用途
荔枝派 4A跑 Linux;排针接线
继电器模块(1 路,5V)信号端受 GPIO 控制,触点通断风扇电源
5V 小风扇负载;红线正极、黑线负极
面包板 + 杜邦线搭接电源轨与信号线;不必每次焊接
电烙铁(约 40W)与焊锡需要固定端子、延长线或补焊时使用;本章首次用到

本章就这几样。真传感器第四章再接。

参考资料:继电器模块说明见 chapters/ch03/manuals/0505单片机继电器模块说明.pdfHW-221 产品说明书.pdf);排针插针方位见 lpi4a-wiki-assets/io_map.png。旧版 wiki 摘录已移入 manuals/archive/,不作为当前依据。

面包板怎么用

面包板用内部金属簧片把若干插孔连在一起,方便用杜邦线临时搭电路。本课用它把荔枝派排针上的电源、地和信号线接到继电器模块。

说明书用语与商品详情一致,见 chapters/ch03/manuals/面包板_MB102摘录.md 与同目录结构图。

面包板:电源轨、连接孔、隔离凹槽
图 4 电源轨(+ / − 横向连通);连接孔(同一列纵向连通);隔离凹槽(上下互不通)
名称怎么通本课怎么用
电源轨「+」与「−」各自沿线横向连通正极侧接排针 5V,负极侧接 GND;再从轨上分线到模块
连接孔同一列 a–e(或 f–j)五个孔纵向连通;相邻列不通模块某一脚与杜邦线插在同一列即相连
隔离凹槽正中沟上下互不通跨沟必须另用跳线,不要默认已连通
连接要求:电源轨上的「+ / −」只是印刷标记,本身不带电;必须由排针 5V / GND 经杜邦线接入后,轨上才有电压。

电烙铁与安全(本章首次)

本章以面包板与杜邦线为主。若课程提供的线材需要补焊端子、延长线,或继电器螺丝端子需要焊环/焊片,会用到电烙铁。下图为常见的约 40W 直热式烙铁外形。

电烙铁外形:手柄、发热芯、烙铁头与电源线
图 5 电烙铁(示例约 40W):使用前确认额定功率与电源规格符合实验室要求
步骤要求
通电前烙铁头安装牢固;电源线无破损;工作台干燥、无易燃物;备好烙铁架与湿海绵/黄铜清洁丝
加热中烙铁始终放在架子上,尖头朝外;勿触碰金属杆与烙铁头;手握隔热手柄
焊接时先加热焊点再送锡,勿甩锡;通风或侧向排烟;避免长时间烫同一焊盘
结束后断电,待完全冷却后再收纳;清洗烙铁头并镀一层薄锡防氧化
安全注意事项:烙铁头温度可达数百摄氏度,会造成烫伤并引燃桌面杂物。禁止用手试温;禁止把烙铁放在书本、塑料盒或杜邦线上;实验室内勿靠近易燃溶剂。皮肤接触焊锡烟气应尽量减少——保持通风。未成年人须在指导教师在场时使用。
与本实验的关系:能用杜邦线插接完成的连接,不必焊接。只有课程明确要求补焊或端子固定时,才启用烙铁,并在焊接前再次确认开发板已断电。

荔枝派排针怎么认

板子侧面那排竖针是 20 针排针。信号脚高电平约 1.8V;本课光耦继电器可直连。

排针上的字本课用途
5V继电器线圈;经继电器给风扇供电
GND共地
IO1_5继电器信号脚(默认)
IO1_6备用(第四章 DHT22)
荔枝派 4A 排针接到面包板与外设
图 1 排针接到面包板与外设(本图含第四章传感器与电平转换;本章实验三先接继电器与风扇即可)
插针方位图:仓库 chapters/ch03/manuals/lpi4a-wiki-assets/io_map.png。以文档图为准;早期板丝印可能不准。认脚认丝印;(chip, line) 用运行时核对。

接线总表

先认丝印,再接线。丝印与表不完全一致时,以实物丝印为准,逻辑按下表角色对应。

A. 继电器(信号 + 供电)

说明
排针 IO1_5继电器信号脚 IN / X1GPIO 直连
排针 5V继电器 V+ / VCC线圈要 5V
排针 GND继电器 V- / GND共地,必接

跳线帽拨到 H(高电平有效)。

继电器模块接线,电源指示灯亮
图 2 继电器与风扇:信号端(V+ / V− / X1)接 GPIO 与 5V/GND;触点侧接风扇电源;电源指示灯亮

B. 风扇(走继电器触点)

接法说明
排针 5V → 继电器 NO(常开)平时断开
继电器 COM → 风扇红线吸合后红线才有电
风扇黑线 → 排针 GND负极回地
排针 5V ──► [继电器 NO]──(吸合时接通)──[COM]──► 风扇红线
排针 GND ──────────────────────────────────────► 风扇黑线
排针 IO1_5 ──► 继电器 IN / X1(直连)
排针 5V  ──► 继电器 V+      排针 GND ──► 继电器 V-

步骤 1:接线

  1. 接线前断开开发板电源。
  2. 按总表:先 GND(共地),再 5V,再信号线 IO1_5 → IN,最后接风扇。
  3. 确认跳线帽在 H;没碰到 Fan Power 白色插座。
  4. 再上电。

本步成功判据:继电器电源指示灯亮;未触发时风扇不转;能指着线说出「地 / 信号 / 风扇回路」。

步骤 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);

写的时候要想清楚

libgpiod v2 线索:讲义 3.2 的片段已展示 settings → line_config → request_config → gpiod_chip_request_linesgpiod_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 的运行记录。

板上 sudo temperature_fan-sol:模拟温度扫过阈值风扇开,Ctrl+C 清理
图 3 板端证据(SSH → make sol):模拟温度过 29°C 风扇开;Ctrl+C 后清理

步骤 4:模拟温度控制验收

停程序:SSH 里 Ctrl+C

退出与悬浮:只释放 GPIO 时脚可能悬浮;本继电器输入悬浮可能飘高 → 风扇一直转。脚手架在退出前调用 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 deniedsudo ./temperature_fan
busy / 打不开 gpiochip残留 gpiosetsudo pkill -x gpioset
程序在跑,风扇完全不动共地?继电器 5V?跳帽 H?NO/COM?信号是否 IO1_5?
置 1 没反应排针用 gpiochip5sudo 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(改后)
风扇转时继电器工作灯
实测哪根脚拉不高(若有)
完成标志:验收表该勾的都勾了;记录表填完。带着「自己写的 GPIO 输出能驱动执行器」进入第四章读传感器。