第四十二章:IO拓展实验

本文对应原书第1088页起,第五篇「实战篇」。

本章在讲什么?(先看这个)

开发板板载的按键和 LED 数量有限,实际项目经常需要更多外设。本章使用正点原子 IO 扩展板,一口气学习三种经典外设的驱动:

  1. 4x4 矩阵键盘(16 个按键):学习"行列扫描"这一经典省引脚技巧;
  2. 4 位八段数码管:学习静态显示与段码译码;
  3. 8 位拨码开关 + 8 个 LED:最简单的状态直接映射。

实验任务:矩阵键盘按下的键值显示在数码管上,拨码开关的状态实时反映到 LED 上。这些模块看似简单,但矩阵键盘扫描状态机和数码管驱动是所有嵌入式工程师的基本功。

核心概念拆解

IO 扩展板硬件一览

扩展板通过 20x2 排针插接到开发板扩展口,上面集成的外设:

  • 8 个 LED共阴接法(阳极接 FPGA IO,阴极公共接地,IO 输出高电平点亮);
  • 8 位拨码开关:断开时由于另一端上拉呈现高电平,闭合时接地变低电平——所以读到的开关状态是"闭合=0";
  • 4 个八段数码管:共阴,位选段选信号都引出到扩展口;
  • 4x4 矩阵键盘:4 根行线接 3.3V 上拉,4 根列线由 FPGA 驱动,行列 8 个端口全部接 FPGA。

图42.1.1 IO扩展板实物图

八段数码管与静态显示

八段数码管 = 7 段(a~g)+ 1 个小数点(dp),每段是一个 LED。共阴数码管段亮需要段选线输出高电平。显示 0~F 需要段码表

数字 段码 数字 段码
0 8'h3f 8 8'h7f
1 8'h06 9 8'h6f
2 8'h5b a 8'h77
... ... f 8'h71

静态显示的原理:每位数码管的段选线独立连接,只要段选不变就一直显示。优点是程序简单,缺点是引脚占用多——本章 4 位数码管用静态方式足够,但如果位数很多就要用动态扫描(下章交通灯实验会讲)。

图42.2.3 四位数码管等效电路图

矩阵键盘扫描:key_4x4 模块(6 状态机)

矩阵键盘把 16 个按键排成 4 行 4 列,行线被上拉为高电平。检测原理:FPGA 在列线上输出低电平,若某行某列交叉点的按键被按下,对应行线就被拉低——由此定位按键坐标。

但 4 根列线同时输出低会有问题(多键同按时分不清),所以用逐列扫描:每次只让一列为低。书上设计了一个 6 状态的状态机

  • 状态 0:所有列线全输出低(粗扫,快速检测有无按键);
  • 状态 1~4:依次输出 4'b1110 → 4'b1101 → 4'b1011 → 4'b0111(逐列扫低,精确定位是哪一列);
  • 状态 5:锁存键值,等待按键释放。

图42.4.5 矩阵键盘扫描模块状态图

消抖设计:delay_cnt 初值 1000000(50MHz 下即 20ms),检测到按键有效后计数消抖;"计数到 1"即认为按键稳定有效(而不是计到 0,注意这个细节)。

键值译码:按下后行线值 key_row_reg 和列线值 key_col_reg 拼成 8 位:

key_value = {key_col_reg, key_row_reg};   // 行列拼接
case ({key_col_reg, key_row_reg})
    8'b1110_1110 : key_value_t = 6'd0;    // 第0列第0行 → 键值0
    8'b1110_1101 : key_value_t = 6'd1;
    // ……共16种组合对应键值0~15
    8'b0111_0111 : key_value_t = 6'd15;
endcase

16 种组合逐一 case 译码,得到 0~15 的键值。

数码管驱动:seg_led 模块

本模块显示单个键值(0~15 对应 0~F)。注意扩展板数码管虽是共阴,但硬件上可能经过了驱动电路反相,代码里做了取反处理:

assign sel_t     = ~sel;          // 位选取反
assign seg_led_t = ~seg_led;      // 段选取反

段码用组合逻辑 case 译码(0=8'h3f ... f=8'h71),位选在本实验静态显示下固定使能一位。

拨码开关:swi_led 模块

最简单的一个模块,核心就一行:

assign led = swi;    // 开关状态直接映射到LED

开关断开(读 1)→ LED 亮?不对——开关"闭合=0",所以拨下开关 LED 熄灭或点亮取决于硬件极性,上板一试便知。这个模块存在的意义是让你体会"输入直接映射输出"的最小系统。

顶层模块 top_matrix_keyboard

例化 key_4x4(键值扫描)、seg_led(键值显示)、swi_led(开关→LED),把键值接到数码管显示端口。

图42.4.2 顶层系统框图

图片对照

  • 硬件:图 42.1.1(扩展板实物图)、图 42.2.1(扩展板原理图)、图 42.2.2(八段数码管结构图)、图 42.2.3(四位数码管等效电路)。
  • 矩阵键盘:图 42.4.3(扫描模块框图)、图 42.4.4(端口波形)、图 42.4.5(状态图)、图 42.4.6(扫描波形)——状态机六个状态怎么跳转全在这里。
  • 数码管:图 42.4.7(模块框图)、图 42.4.8/42.4.9(波形图)。
  • 拨码开关:图 42.4.10(框图)、图 42.4.11/42.4.12(波形)。
  • 顶层:图 42.4.13(顶层信号连接)、图 42.4.14(顶层仿真波形)。
  • 上板:图 42.5.1(硬件连接图)、图 42.5.2(最终效果图,数码管显示键值)。

初学者容易踩的坑

  1. 列线同时全部拉低后直接译码:全列扫低只能判断"有键按下",无法区分列。必须进入状态 1~4 逐列扫描才能定位。
  2. 消抖判断条件写成"计数到 0":书上 delay_cnt 计到 1 判定有效(初值 1000000),改代码时容易把边界条件弄错,导致按键偶发失灵。
  3. 上拉没接:行线靠 3.3V 上拉保持高电平,若 FPGA 内部上拉没使能且外部也没上拉,行线悬空,扫描结果随机跳变。
  4. 段码表背错极性:共阴是"1"点亮,如果照抄共阳的段码表(0=8'hc0)就全反了。本模块代码中的取反处理正是为了适配硬件。
  5. 拨码开关读到的是反逻辑:闭合=低电平(因为接地),想判断"开关闭合"应判断 0 而不是 1。
  6. 16 种键值组合漏写:case 译码要写全 16 项,漏写某项时按对应按键数码管毫无反应,排查时先数 case 分支数量。

小结 & 下一步

本章的三大件——矩阵键盘扫描状态机、段码译码、开关直连——是数字系统人机交互的基础件。矩阵键盘的"全扫粗判→逐列精扫→锁存释锁"三段式流程尤其值得背下来,以后做任何小键盘输入都能复用。

下一章把这些外设用起来做一个综合项目:交通灯。4 位数码管动态扫描倒计时、红绿灯状态机、黄灯闪烁,一个实验串起前面所有知识点。