news 2026/10/10 8:44:58

33_实验三十二_GPIO寄存器与LED硬件

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
33_实验三十二_GPIO寄存器与LED硬件

实验三十二 GPIO 寄存器与 LED 硬件——一切外设操作的起点

对应课件:《第8章 GPIO端口》8.1~8.2 节 + 8.3 节前半,Slide 2-26

系列说明:本系列基于华清远见 FS-MP1A(STM32MP157A)开发板,对应课件《第8章 GPIO端口》。toychar 是"内核里的一块数据",本篇开始碰真硬件:GPIO。它是所有外设操作的基础——GPIO 的每种行为(输入/输出/复用/模拟)都由寄存器决定,而"操作寄存器"就是"读写特定地址的内存",这个等式成立之后,一切芯片手册都能读懂了。本篇先立概念与寄存器地图,下一篇(实验三十三)写 LED 驱动两个版本(寄存器直控版 + 自动创建设备文件版),点亮板上三颗绿灯。前置:实验三十一(驱动三件套写法)、实验二十四(设备文件与主次设备号)。

一、GPIO 是什么,为什么说它是一切硬件操作的基础

GPIO(General Purpose I/O ports,通用输入/输出端口)是芯片引脚,通过它们输入或输出高低电平。STM32MP157 有176 个 GPIO 引脚,分 A~K、Z 共 12 组(每组称一个 Port,记 GPIOx),每组 16 个引脚(GPIOK、GPIOZ 各 8 个),引脚记Pxy——如 PB2 = B 组 2 号。

每个引脚有四种工作模式:输入、输出、复用(给片上外设用)、模拟——工作在哪种模式、各模式下的参数,全部由寄存器决定。课件 8.1 的推论值得抄在墙上:

GPIO 的模式靠写寄存器、数据靠读写寄存器——对 GPIO 的操作就是对寄存器的读写;其他外设同理;每个寄存器分配了一个内存地址——于是"控制外设"最终等价于"读写特定地址的内存单元"。芯片手册的一切寄存器描述(地址、位域、复位值),说的都是这件事。

二、七个寄存器:GPIO 的控制面板(8.2)

每组 GPIO(A~K、Z)有自己的一套寄存器,各组长得一样——4 个配置、2 个数据、1 个置位/复位,外加杂项:

寄存器全名干什么
GPIOx_MODERport mode register引脚功能:00 输入 / 01 输出 / 10 复用 / 11 模拟
GPIOx_OTYPERoutput type register输出类型:0 推挽 / 1 开漏
GPIOx_OSPEEDRoutput speed register输出速度上限:低/中/高/极高
GPIOx_PUPDRpull-up/pull-down register内部上拉/下拉:00 无 / 01 上拉 / 10 下拉
GPIOx_IDRinput data register输入数据(只读)
GPIOx_ODRoutput data register输出数据(读写)
GPIOx_BSRRbit set/reset register原子置位/复位 ODR(写 1 生效)

每个引脚在寄存器里占位的方式有规律:MODER/OSPEEDR/PUPDR 是每引脚 2 位(4 种状态,引脚 y 占 bit 2y+1:2y),OTYPER/IDR/ODR/BSRR 是每引脚 1 位——所以配置 PZ5 时动的是 bit11:10(2 位组),控制 PZ5 电平时动的是 bit5(1 位组)。记住这个换算,下面看每只寄存器的芯片手册截图:

图:课件 Slide 6——GPIOx_MODER(Address offset 0x00,Reset 0xFFFF FFFF):MODER15[1:0]~MODER0[1:0] 每引脚两位,00 Input / 01 General purpose output / 10 Alternate function / 11 Analog。

图:课件 Slide 7——GPIOx_OTYPER(offset 0x04):bit15~0 每引脚一位,0 Output push-pull(推挽,复位值)/ 1 Output open-drain(开漏)。

推挽与开漏(OTYPER 的两个选项,电路图看一眼就懂):

图:课件 Slide 8——推挽输出:两只互补 MOS 管串联(上管 P 接 VDD、下管 N 接地),输入高时上管导通输出高、输入低时下管导通输出低——两个方向都"推得动",驱动能力强,是我们点亮 LED 的选择。

图:课件 Slide 8——开漏输出:片内只有一只漏极开路的 N-MOS 管接地,输出高需要片外上拉电阻接 Vcc——“只能拉低、放开靠上拉”。多设备共线(I2C 总线)就靠它实现线与;F-2 改 U-Boot 的 SD 卡 CD 引脚时GPIO_PULL_UP的上拉、F-1 电源节点里的开漏概念,在这里见到电路本体。

图:课件 Slide 9——GPIOx_OSPEEDR(offset 0x08):00 Low / 01 Medium / 10 High / 11 Very high——"上限速度"选型与 VDD 范围、外部负载有关(点 LED 随便选,抓高速信号才讲究)。

图:课件 Slide 10——GPIOx_PUPDR(offset 0x0C):00 无 / 01 上拉 / 10 下拉 / 11 保留——F-2 里 SD 卡 CD 引脚"上拉 + 低有效"的软件写法,对应的就是这里的硬件位。

一张全景图把七个寄存器的关系连起来:

图:课件 Slide 11——芯片手册 Figure 119《I/O 端口位的基本结构》:左侧 BSRR/ODR/IDR 三个寄存器与复用功能输入/输出通路;右侧输入驱动(施密特触发器)与输出驱动(Output control + P-MOS/N-MOS 对管,推挽/开漏/禁用);引脚带保护二极管与可开关的上拉/下拉电阻。一张图看完"配置怎么起作用"。

图:课件 Slide 12——GPIOx_IDR(offset 0x10,Reset 0x0000 XXXX——引脚当前电平不定)。

图:课件 Slide 12——IDR 位表:bit15~0 只读,存放对应引脚的输入值;输出模式下读它得到引脚当前电平。

图:课件 Slide 13——GPIOx_ODR(offset 0x14):bit15~0 读写;注释说明原子置位/复位请走 BSRR/BRR——这正是下面 BSRR 的存在意义。

图:课件 Slide 14——BSRR 位说明:bit31~16 为 BR15~0(写 1 复位对应 ODR 位),bit15~0 为 BS15~0(写 1 置位);只写寄存器(读返回 0);BSx 与 BRx 同时写 1 时 BSx 优先。

图:课件 Slide 14——GPIOx_BSRR(offset 0x18):低 16 位 BS(置位)、高 16 位 BR(复位),全部 write-only。

BSRR 的妙处:改一个引脚不需要"读-改-写"三步(ODR 方式要val=readl(); val|=bit; writel(val);——中断一来就可能被别的代码搅了),直接写一次就生效(写 0 的位无动作)。实验三十三的 led_switch 会直接用它。

三、LED 硬件与"程序控制设备四步"(8.3 前半)

看原理图:FS-MP1A 的三颗 LED 接在GPIOZ上——

图:课件 Slide 15——LED 连接原理图(左半):网络标号 PZ5(引脚 H4)、PZ6(G3)、PZ7(H3)分别接 LED1、LED2、LED3 网络。

图:课件 Slide 15——原理图(右半):三路 LED 各经 470Ω 限流电阻(R114/R115/R117)接绿色 LED(D35/D36/D38),负极共同接地——GPIO 输出高电平 = LED 亮,输出低 = 灭(正极逻辑,与很多"低电平点亮"的板子相反,写驱动前必看原理图)。

程序控制设备的四步流程(一切外设通吃,与实验三十"核心三步"合流):

流程驱动里的落点
(1) 使能设备(开时钟)注册驱动时做(led.c 里这段被注释,原因见下)
(2) 配置设备(模式/参数)注册驱动时做(MODER/OTYPER/OSPEEDR/PUPDR)
(3) 操作设备file_operations 的操作函数(BSRR 置位复位)
(4) 用毕关闭(复位/断电)注销驱动时做

操作寄存器要先过两道坎:

第一道:虚拟地址。Linux 开着虚拟内存,程序里的地址都是虚拟地址,芯片手册给的寄存器地址是物理地址——用ioremap把物理地址映射成虚拟地址(用完iounmap解除):

void__iomem*ioremap(resource_size_tres_cookie,size_tsize);// 物理首址、长度 → 虚拟首址voidiounmap(volatilevoid__iomem*addr);// 用完必须解除

第二道:有序访问。寄存器读写不要用裸指针——编译器可能乱序优化;要用内核提供的读写函数:

图:课件 Slide 19——读操作函数:u8 readb(addr)、u16 readw(addr)、u32 readl(addr)——8/16/32 位读,返回读到的数据。

图:课件 Slide 19——写操作函数:void writeb(u8 value, addr)、void writew(u16 value, addr)、void writel(u32 value, addr)——8/16/32 位写。

图:课件 Slide 20——内核文档原文:“The read and write functions are defined to be ordered. That is the compiler is not permitted to reorder the I/O sequence.”(读写函数被定义为有序,编译器不得重排 I/O 访问顺序)——这就是"别用裸指针"的官方理由。

寄存器物理地址怎么算:芯片手册查 GPIOZ基址 0x54004000,加上 8.2 节各寄存器的偏移(MODER 0x00、OTYPER 0x04、OSPEEDR 0x08、PUPDR 0x0C、BSRR 0x18)即得。基址的出处:

图:课件 Slide 21——芯片手册 Register boundary addresses 表(AHB5 段):0x54004000–0x540043FF(1 KB)GPIOZ;同表还能看到 RNG1(0x54003000)、HASH1(0x54002000)等邻居。

时钟使能——一段注定被注释掉的代码(本节最有意思的知识点):GPIOZ 的时钟由 RCC(Reset and Clock Control)模块管,使能位在RCC_MP_AHB5ENSETR(RCC 基址 0x50000000 + offset 0x210)的 bit0(GPIOZEN),写 1 使能:

图:课件 Slide 24——RCC_MP_AHB5ENSETR(offset 0x210,Reset 0x0001 0000):写 ‘1’ 使能外设时钟、写 ‘0’ 无效果;位域含 bit0 GPIOZEN、bit6 RNG1EN、bit5 HASH1EN、bit4 CRYPT1EN、bit8 BKPSRAMEN 等。

图:课件 Slide 24——GPIOZEN 位说明:Set by software——写 ‘1’ 使能(读 ‘1’ = 已使能),写 ‘0’ 无效果(读 ‘0’ = 时钟关闭)。

但课件明说:LED 驱动里这段代码注释掉了,两个原因:

图:课件 Slide 26——TrustZone Environment 框图:Normal World(PL0 应用 / PL1 普通 OS / PL2 Hypervisor)与 Secure World(PL0 可信应用 / PL1 可信 OS),底部 Secure Monitor 横跨两界——我们跑的 Linux 是"正常世界",TF-A(第 3 章收官时接任 FSBL 的那位)是"安全世界"的住民。

  1. 出厂 TF-A 已经使能了 GPIOZ 时钟——第 3 章烧进板的 TF-A 顺手办了;
  2. RCC_MP_AHB5ENSETR 属于"安全世界"(AHB5 总线上的设备都是),非安全世界(我们的 Linux)写不动它——写了也无效。

驱动源码里保留注释版代码,是为了给"完整流程"留教材——读到#if 0段时知道它为什么躺平即可。

四、实验环境(实际)

项目实际值
板子第 7 章收官形态;三颗绿色 LED 在板上(PZ5/PZ6/PZ7,高电平点亮)
Ubuntugcc-arm-9.2 + 内核源码树 + drivers-dev.zip 已解(01-toychar 用过)
本篇材料drivers-dev.zip的02-led/(led.c + ledApp.c + Makefile)——不重复复制:这个包已在第 7 章目录(05_字符设备驱动/)复制并解压过,直接取里面的02-led/子目录(实验三十三编译)

开工自检(10 秒):本篇是认知篇(读图与源码预读)。确认02-led/led.c里 6 个ioremap、#if 0时钟段、BSRR 的1<<5/1<<21都能对上本篇讲过的寄存器——对上了,实验三十三的代码就没有一行是新的。

五、课件 ↔ 步骤对应表

课件 Slide内容对应本篇
2~3GPIO 概念、12 组 176 脚、四种模式、“一切皆寄存器读写”第一节
4~14七个寄存器逐个 + 推挽/开漏电路 + I/O 位结构图第二节
15~16LED 原理图(PZ5~7 高电平点亮)第三节
17程序控制设备四步流程第三节
18~20ioremap/iounmap、readl/writel、有序访问第三节
21~22GPIOZ 基址 0x54004000第三节
23~26RCC 时钟使能、安全世界与 TF-A(代码注释的原因)第三节

本篇概念 → 后面谁用 → 现在含糊的后果

本篇概念后面哪一篇用现在含糊的后果
MODER/OTYPER/OSPEEDR/PUPDR/BSRR 位段实验三十三 led.c 的六步初始化逐行对照代码里val &= ~(0x3 << 10)不知道在配哪个脚
BSRR 只写、原子置位复位led_switch 的"直接写、不读改写"画蛇添足先读后写,读到全 0 还以为寄存器坏了
ioremap + readl/writel实验三十三全部寄存器操作;第 9 章 dtsled 的 of_iomap裸指针访问寄存器,偶发跑飞查不到
高电平点亮 + PZ5~7实验三十三测试现象的预期灯不亮不知道是驱动错还是极性记反
安全世界/RCC 注释段读第 9 章设备树版驱动时的时钟处理以为漏抄了使能代码

本篇不需要新动手的材料。

六、自测一览

自测问题达标答案在哪一节
STM32MP157 的 GPIO 怎么编号?12 组 A~K、Z,每组 16 脚(K/Z 各 8),引脚 Pxy 如 PB2、PZ5第一节
四种工作模式由哪个寄存器选?每脚占几位?MODER;2 位(00 输入/01 输出/10 复用/11 模拟)第二节
推挽与开漏的区别?LED 用哪个?推挽双向驱动;开漏只能拉低、靠外部上拉——LED 用推挽第二节
改一个引脚电平为什么推荐 BSRR 而不是 ODR?BSRR 只写、写 1 生效、原子操作——免"读改写"竞态第二节
访问寄存器为什么不能直接用指针?编译器可能乱序;用 readl/writel(有序),且先 ioremap 把物理地址映射为虚拟地址第三节
GPIOZ 时钟使能代码为什么被注释?TF-A 已使能 + RCC_MP_AHB5ENSETR 属安全世界,Linux 写不动第三节
LED 亮灭的电平极性?高电平点亮(三只绿灯负极接地,经 470Ω 限流)第三节

七、下一步:LED 驱动两个版本

下一篇(实验三十三)把本篇的寄存器地图变成能上板的代码:led.c 寄存器直控版(主 201、手动 mknod,ledApp 写 1 开灯写 0 关灯),mdevled.c 自动创建设备文件版(alloc_chrdev_region→cdev→class_create→device_create 五函数与注销逆序)——mdev 与 class 机制联动后,/dev/led会自己出现。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/9 5:38:55

ponytail 是什么?轻量收口插件与 skill 实战指南

1. 从“ponytail”这个热词说起&#xff1a;它到底指什么第一次看到“ponytail”被当成一个技术词条来搜&#xff0c;我其实也愣了一下。字面意思就是马尾辫&#xff0c;一个再日常不过的发型词&#xff0c;怎么会跟“skill”“插件”“如何使用”这些词绑在一起冲上热搜&#…

作者头像 李华
网站建设 2026/10/9 5:38:53

『项目管理精要』第 8 章 相关方管理与向上沟通:破除“技术孤岛”的非权力影响力

许多开发者在成为技术主管(TL)后,最不适应的事情是“每天需要花费大量时间与不同的人沟通”。在弱矩阵和平衡矩阵组织中,TL 既要对接项目经理(PM)、产品经理(PO),又要向上汇报给职能主管或高管,还要应对外部业务方。如果只懂埋头写代码,很容易陷入“技术孤岛”。TL …

作者头像 李华
网站建设 2026/10/9 5:38:53

Docker 入门:镜像、容器与 Dockerfile 一次讲透

个人主页&#xff1a;> 我不会起名字322 < &#xff08;欢迎各位大佬莅临&#x1f60a;&#xff09; 其他栏目&#xff1a;> 技术栈学习笔记 < 其他栏目&#xff1a;> 力扣Hot100题目解析 < 其他栏目&#xff1a;> Go项目学习笔记 < 其他栏目&#xff…

作者头像 李华
网站建设 2026/10/9 5:38:20

ThreadLocal系列(四):父子线程信息传递与TTL

父子线程信息传递与TTL前面介绍了ThreadLocal使用与内存泄漏防范&#xff0c;还从引用队列角度思考如何防范内存泄漏。 这篇文章是自己在实际中用到了RAG检索与回答用自定义线程池而不是tomcat线程池&#xff0c;防止tomcat线程池线程被占用导致无法处理其他请求。 其中用到了跨…

作者头像 李华