基于ARM的裸机程序设计和开发(四):硬件编程原理与GPIO控制思路

文章目录

    • 概要
    • 一、前言
    • 二、什么是硬件编程
    • 三、为什么要学习硬件编程
    • [四、GPIO 为什么适合作为入门外设](#四、GPIO 为什么适合作为入门外设)
    • [五、典型 GPIO 结构](#五、典型 GPIO 结构)
    • [六、GPIO 往往不是"一个引脚",而是一组引脚](#六、GPIO 往往不是“一个引脚”,而是一组引脚)
    • [七、增强型 GPIO 结构](#七、增强型 GPIO 结构)
    • [八、Zynq7000 的 GPIO 结构特点](#八、Zynq7000 的 GPIO 结构特点)
    • [九、GPIO 控制的基本编程思路](#九、GPIO 控制的基本编程思路)
    • [十、输出型 GPIO 场景:使用 GPIO 控制 LED](#十、输出型 GPIO 场景:使用 GPIO 控制 LED)
    • [十一、LED 控制场景下的初始化思路](#十一、LED 控制场景下的初始化思路)

概要

在 Zynq 裸机开发中,真正实现对 LED、按键、串口、I2C、SPI 等外设的控制,本质上都离不开对硬件寄存器的读写操作。这种面向外设控制器寄存器的程序设计方式,就是硬件编程。本文结合 Zynq 平台的 GPIO 外设,介绍什么是硬件编程、为什么要学习硬件编程、GPIO 的典型结构与增强结构,以及基于 GPIO 控制 LED 的基本编程思路和方法,为后续外设驱动开发打下基础。

关键词

Zynq;裸机开发;硬件编程;GPIO;寄存器;LED;MIO

一、前言

在学习 Zynq 裸机开发时,很多初学者一开始会觉得"写程序就是写 C 代码",但真正进入外设控制之后就会发现,裸机程序和普通应用程序有一个很大的不同:

bash 复制代码
裸机程序不是只和变量、函数、流程打交道,而是直接和硬件打交道。

比如:

  • 想让 LED 亮,就要控制对应 GPIO 输出高低电平;
  • 想读取按键状态,就要读取对应输入寄存器的值;
  • 想让串口发送数据,就要向 UART 发送寄存器写入数据;
  • 想和传感器通信,就要配置 I2C 或 SPI 控制器的寄存器。

也就是说,CPU 并不是直接"懂"LED、按键和传感器,而是通过访问各种外设控制器的寄存器,实现对外部世界的控制和感知。这个过程,就是所谓的硬件编程。

在前面的内容中,我们已经接触了 BSP、寄存器读写、地址访问等基础知识。本文就在此基础上,进一步讲清楚 Zynq 裸机开发中的硬件编程原理,并以 GPIO 为例,建立最基本的外设控制思路。

二、什么是硬件编程

所谓硬件编程,简单来说,就是:

bash 复制代码
通过对硬件外设控制器寄存器进行读写,来配置其工作模式并完成数据交互。

在 Zynq 开发中,这里的"硬件"通常包括两大类:

  • PS 端自带的外设控制器,例如 GPIO、UART、I2C、SPI 等;
  • PL 端自定义或扩展的外设控制器 IP。

CPU 要想控制这些硬件,或者从这些硬件获得状态信息,就必须通过对应的控制器寄存器来完成。也就是说,硬件编程并不是"直接操作外设本体",而是"通过寄存器去控制外设控制器,再由控制器去操作外部设备"。

例如:

  • 控制 LCD 显示字符;
  • 控制继电器吸合或断开;
  • 通过串口打印调试信息;
  • 通过 I2C 读取温湿度传感器数据;
  • 通过 SPI 控制 ADC 进行采样;
  • 读取按键状态;

这些场景,本质上都属于硬件编程。

三、为什么要学习硬件编程

对于嵌入式开发来说,学习硬件编程不是"可选技能",而是"基础能力"。

因为不管你最终做的是:

  • LED/按键基础实验,
  • 串口打印调试,
  • 传感器采集,
  • 屏幕显示,
  • 工业控制,
  • 还是 FPGA 与 ARM 协同系统设计,

都离不开对底层外设的控制。

换句话说,如果不会硬件编程,你就很难真正理解:

  • 程序为什么能控制硬件;
  • 某个外设为什么这样初始化;
  • 某一位寄存器为什么要写 1;
  • 为什么有的功能要先配置方向,再配置数据;
  • 为什么有的场景要开中断,有的场景不用。

当然,不同的外设功能不同,寄存器设计也不同,很难存在一个通用的程序模板去控制所有外设。因此只能遵循一个原则:

bash 复制代码
具体硬件具体分析,具体功能具体编程。

而在所有外设中,GPIO 可以说是最基础、最简单、最适合入门的一类,因此也非常适合作为硬件编程的第一站。

四、GPIO 为什么适合作为入门外设

GPIO 是 General Purpose Input/Output 的缩写,也就是通用输入输出接口。

几乎所有基于 CPU 的芯片里都会有 GPIO,它是处理器和外部世界进行最直接电平交互的一类基础外设。比如:

  • 点亮一个 LED;
  • 检测一个按键是否按下;
  • 控制一个继电器吸合;
  • 读取一个简单开关状态;

这些最基本的功能,通常都可以通过 GPIO 来完成。

之所以把 GPIO 作为硬件编程的入门对象,是因为它结构相对简单,但又足够典型。通过学会 GPIO 的控制方法,后面再去学习 UART、I2C、SPI、定时器等外设时,思路会顺畅很多。

五、典型 GPIO 结构

要理解 GPIO 的编程方法,首先要理解它的结构。

从功能上看,我们通常希望 GPIO 做两类事:

输出功能:

比如控制 LED、继电器,只需要让 GPIO 输出高电平或低电平。

输入功能:

比如读取按键状态,只需要检测当前引脚电平是高还是低。

为了支持这两类功能,一个最基础的 GPIO 一般至少需要三类寄存器:

  1. 方向控制寄存器

用于设置 GPIO 当前是输入模式还是输出模式。

因为一个引脚不可能同时既当输入又当输出,所以必须先配置方向。

  1. 输出数据寄存器

用于存储 CPU 希望输出到引脚上的电平值。

如果 GPIO 处于输出模式,那么这个寄存器里的值就会影响引脚输出状态。

  1. 输入数据寄存器

用于反映外部引脚当前实际电平状态。

如果 GPIO 处于输入模式,那么 CPU 就可以通过读取这个寄存器获知外部信号状态。

从抽象上看,可以把它理解成三部分:

  • R1:方向/输出使能控制寄存器
  • R2:输出状态/数据寄存器
  • R3:输入状态/数据寄存器

这就是最典型的 GPIO 结构。

六、GPIO 往往不是"一个引脚",而是一组引脚

严格来说,我们平时说"一个 GPIO",很多时候其实说的是一组引脚资源。

例如一个 8 位 GPIO 端口,可能有:

bash 复制代码
8 位方向控制
8 位输出值
8 位输入值

那么对应的寄存器通常也会是按位管理的:

bash 复制代码
OE[7:0]:每一位控制对应引脚是否输出
WDATA[7:0]:每一位控制对应引脚输出什么值
RDATA[7:0]:每一位反映对应引脚当前输入状态

这样 CPU 只要通过读写这些寄存器,就能统一管理一组 GPIO 引脚。

这也是为什么在寄存器编程中,经常会遇到"改某一位不影响其他位"的问题,因为多个引脚常常共用同一个 32 位寄存器。

七、增强型 GPIO 结构

在实际芯片中,GPIO 通常不只是"方向 + 输入 + 输出"这么简单。为了提高使用效率和扩展功能,很多芯片厂家会在基础结构上增加一些增强功能。

  1. 置位/清零寄存器

假设一个 8 位输出寄存器中,只想把 bit2 置为 1,而不影响其他位,那么最基本的做法通常是:

  • 先读出整个寄存器值;
  • 修改目标位;
  • 再把修改后的值写回去。

例如:

c 复制代码
value = value | (1 << 2);

同样,如果想把 bit3 清零,也要进行读-改-写操作。

这种方法虽然通用,但缺点也明显:

  • 写法繁琐;
  • 编译后指令更多;
  • 执行效率较低;
  • 高频翻转场景下不够高效。

因此很多 MCU 或 SoC 都会额外设计专门的 SET 寄存器 和 CLR 寄存器。

只需要向对应位写 1,就能实现置位或清零,而不会影响其他位。

这本质上是:

bash 复制代码
用更复杂一点的硬件设计,换取更简单、更高效的软件控制。
  1. 中断屏蔽寄存器

在一些输入场景中,CPU 需要对外部电平变化快速响应。比如按键触发、中断检测、边沿检测等。

这时就需要 GPIO 具备触发中断的能力。

但并不是 GPIO 的每一位都需要开中断,因此通常会设计一个 中断屏蔽寄存器,用来决定哪些位允许产生中断请求,哪些位禁止产生中断。

这也是后续学习 GPIO 中断编程时的重要基础。

八、Zynq7000 的 GPIO 结构特点

Zynq7000 作为一款 SoC FPGA,其 GPIO 结构比最基础的 GPIO 模型更完整、更实用。资料中提到,可以在 Zynq 官方技术参考手册 UG585 中查看其 GPIO 功能框图和相关寄存器说明。

对学习者来说,当前阶段不必一开始就把所有 GPIO 寄存器全部背下来,更重要的是先建立这样的认识:

  • Zynq 的 GPIO 仍然符合"方向控制 + 数据控制 + 输入读取"的基本思想;
  • 同时它还提供了中断、掩码写入等更高效的控制机制;
  • 真正编程时,需要结合数据手册和头文件来确定寄存器地址、偏移和功能。

九、GPIO 控制的基本编程思路

无论是控制 LED,还是读取按键,GPIO 编程大体都可以归纳成两部分:

  • 初始化
    初始化阶段要根据场景完成对应设置,例如:
  • 设置方向;
  • 设置输出使能;
  • 是否开启中断;
  • 中断类型如何配置;
  • 是否需要关闭无关中断。
  1. 工作阶段

初始化完成后,GPIO 进入实际工作阶段:

如果是输入型应用:

  • 不开中断时,程序轮询读取输入寄存器;
  • 开中断时,编写中断处理函数响应输入变化。
    如果是输出型应用:
  • 通过数据寄存器或快速置位/清零寄存器修改输出值。

可以看到,不管外设多复杂,思路其实都很统一:

bash 复制代码
先初始化,再运行控制。

十、输出型 GPIO 场景:使用 GPIO 控制 LED

资料中给出的典型例子是:在 ACZ702 开发板上,PS 端 LED 连接到 Zynq 的 MIO7 引脚。

当 MIO7 输出高电平时,LED 点亮;当 MIO7 输出低电平时,LED 熄灭。

这是一个非常典型的单输出型应用场景。

在这个场景下,我们只需要控制 GPIO 输出高低电平即可,不需要用到输入检测和中断触发,因此它是最适合入门的例子。

十一、LED 控制场景下的初始化思路

  1. 关闭中断
    因为本实验只要求 LED 亮灭控制,并不需要 GPIO 产生中断,所以在初始化时应该先关闭对应 GPIO 位的中断功能。

资料中提到,对于 Zynq GPIO,可以通过 INT_DIS 寄存器对某一位写 1 的方式,禁止该位产生中断请求。对于 MIO7,对应的控制思路就是让 bit7 的中断处于关闭状态。

这一步的意义在于:

把当前场景下"不需要的功能"先关掉,避免系统出现多余行为。

  1. 设置方向

LED 是被 CPU 驱动的,所以 GPIO 必须工作在输出模式。

这意味着方向寄存器中对应 bit7 必须配置为输出。

  1. 设置输出使能

仅有方向为输出还不够,还需要让输出驱动真正被使能。

在 Zynq GPIO 中,输出相关控制通常还涉及 OUTEN 寄存器,因此初始化时需要让 bit7 的输出使能打开。

也就是说,点亮 LED 的最基础初始化逻辑其实就是:

  • 关闭中断;
  • 设置方向为输出;
  • 打开输出使能。
相关推荐
奋斗tree21 小时前
EulerOS 2.0 等保三级版(ARM 架构)是什么?
arm开发·架构
senijusene1 天前
i.MX6ULL 裸机 ECSPI 驱动开发详解:
arm开发·驱动开发·嵌入式硬件
Arenaschi2 天前
国产麒麟X86结构和arm架构的区别
arm开发
EnglishJun2 天前
ARM嵌入式学习(二十一)--- Platform总线结合dts、gpio子系统、中断和错误处理
arm开发·学习
AI服务老曹2 天前
异构计算时代的安防底座:基于 x86/ARM 双架构与多芯片适配的 AI 视频云平台架构解析
arm开发·人工智能·架构
落樱弥城2 天前
Arm Mali GPU架构
arm开发·架构
The Mr.Nobody2 天前
基于STM32F407的 TFTP Server
arm开发·stm32·嵌入式硬件
飞凌嵌入式2 天前
如何用JishuShell在RK3588核心板上快速部署OpenClaw?
arm开发·人工智能·嵌入式硬件·openclaw
ai产品老杨2 天前
异构计算时代的安防底座:基于 x86/ARM 双架构与多芯片适配的 AI 视频云平台架构解析
arm开发·人工智能·架构
ai产品老杨3 天前
异构计算时代的视频底座:基于 ZLMediaKit 与 Spring Boot 的 X86/ARM 跨平台架构解析
arm开发·spring boot·音视频