news 2026/7/2 1:09:33

STM32寄存器开发练习(一):GPIO-从最原始的代码到规范写法

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32寄存器开发练习(一):GPIO-从最原始的代码到规范写法

前言

关于STM32的教程,大部分一上来就让我们用HAL库或者标准外设库,调用几个函数就搞定了。

但这样的话,其实不知道底层发生了什么。所以我最近跟着B站尚硅谷老师重新开始学习原始的编程方式——直接操作寄存器,这样才能真正理解MCU的工作原理。

这一篇,我准备点亮开发板上的LED——但我不打算直接给你"完美代码",而是想分享一下我写代码时的优化过程。

从最原始、最粗暴的写法,一步步演进到规范、易读的版本。

这个过程,我觉得比最终结果更有价值。


我的开发板情况

我用的是STM32F103ZET6开发板,上面有两个LED:

  • LED1:接在PB5(低电平点亮)
  • LED2:接在PE5(低电平点亮)

"低电平点亮"的意思是:

  • PB5输出低电平(0V)→ LED亮
  • PB5输出高电平(3.3V)→ LED灭

要点亮这两个LED,需要完成三步:

  1. 开启GPIOB和GPIOE的时钟
  2. 配置PB5和PE5为推挽输出
  3. 控制PB5和PE5输出低电平

下面,我就按我实际写代码的过程,一步步来。


第一步:最原始的方式

最开始,我是这么写的——直接用指针操作寄存器地址:

c

int main(void) { // 两个LED灯分别连在PB5和PE5上 // 1.时钟配置:开启GPIOB和GPIOE的时钟 *(uint32_t *)(0x40021000 + 0x18) = 0x48; // 2.GPIO工作模式配置 *(uint32_t *)(0X40010C00) = 0x300000; *(uint32_t *)(0x40011800) = 0x300000; // 3.PB5输出低电平 *(uint32_t *)(0X40010C00 + 0x0C) = 0xFFDF; *(uint32_t *)(0x40011800 + 0x0C) = 0xFFDF; // 用一个死循环保持状态 while(1) { } }

这种写法,优点是最原始、最直观,能帮你理解寄存器编程的本质。

但缺点也很明显:

  • 0x40021000 + 0x18这种地址,过几天我自己都忘了是什么
  • 地址算错一位,程序就崩了
  • 如果要改配置,得重新算十六进制值

所以,我决定优化一下。


第二步:用stm32f10x.h

我注意到,官方提供的stm32f10x.h头文件,已经帮我定义好了所有寄存器和基地址。

于是,我的代码变成了这样:

c

#include "stm32f10x.h" int main(void) { // 两个LED灯分别连在PB5和PE5上 // 1.时钟配置:开启GPIOB和GPIOE的时钟 RCC->APB2ENR = 0x48; // 2.GPIO工作模式配置 GPIOB->CRL = 0x300000; GPIOE->CRL = 0x300000; // 3.PB5输出低电平 GPIOB->ODR = 0xFFDF; GPIOE->ODR = 0xFFDF; // 用一个死循环保持状态 while(1) { } }

这一版,代码可读性提高了——RCC->APB2ENR*(uint32_t *)(0x40021000 + 0x18)直观多了。

但是,我很快发现一个问题……


第三步:用位操作(我踩的坑)

我发现,RCC->APB2ENR = 0x48这种直接赋值的方式,其实有问题。

假设我之前已经开启了GPIOA的时钟(第2位 = 1),执行RCC->APB2ENR = 0x48,会把第2位清零,导致GPIOA时钟被关闭。

正确的做法,应该是用位操作,只修改需要的位,不影响其他位:

c

#include "stm32f10x.h" int main(void) { // 两个LED灯分别连在PB5和PE5上 // 1.时钟配置:用位操作开启GPIOB和GPIOE的时钟 RCC->APB2ENR |= (1 << 3); // 开启GPIOB时钟(第3位设为1) RCC->APB2ENR |= (1 << 6); // 开启GPIOE时钟(第6位设为1) // 2.GPIO工作模式配置:配置PB5为推挽输出、50MHz GPIOB->CRL &= ~(0x3 << 20); // 先把第20~23位清零 GPIOB->CRL |= (0x3 << 20); // 再设置为推挽输出、50MHz GPIOE->CRL &= ~(0x3 << 20); // 先把第20~23位清零 GPIOE->CRL |= (0x3 << 20); // 再设置为推挽输出、50MHz // 3.PB5输出低电平(点亮LED) GPIOB->ODR &= ~(1 << 5); // PB5输出低电平(第5位清零) GPIOE->ODR &= ~(1 << 5); // PE5输出低电平(第5位清零) // 用一个死循环保持状态 while(1) { } }

这一版,用|=&=进行位操作,只修改特定位,不影响其他位,安全多了。

但是,(1 << 3)(0x3 << 20)这些,还是需要查手册才能知道是哪一位。

所以,我又优化了一版。


第四步:用stm32f10x.h中现成的宏定义(最终版)

我在看stm32f10x.h时,发现里面已经定义了很多宏,比如:

c

// RCC时钟使能位定义 #define RCC_APB2ENR_IOPBEN ((uint32_t)0x00000008) // GPIOB时钟使能 #define RCC_APB2ENR_IOPEEN ((uint32_t)0x00000040) // GPIOE时钟使能 // GPIO_CRL寄存器位定义 #define GPIO_CRL_CNF5 ((uint32_t)0x00C00000) // CNF5位掩码 #define GPIO_CRL_MODE5 ((uint32_t)0x00030000) // MODE5位掩码 // GPIO_ODR寄存器位定义 #define GPIO_ODR_ODR5 ((uint16_t)0x0020) // ODR5位掩码

于是,我的代码变成了这样:

c

#include "stm32f10x.h" int main(void) { // 两个LED灯分别连在PB5和PE5上 // 1.时钟配置:开启GPIOB和GPIOE的时钟 RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; // 开启GPIOB时钟 RCC->APB2ENR |= RCC_APB2ENR_IOPEEN; // 开启GPIOE时钟 // 2.GPIO工作模式配置:配置PB5和PE5为推挽输出、50MHz GPIOB->CRL &= ~GPIO_CRL_CNF5; // 清除PB5的CNF位 GPIOB->CRL |= GPIO_CRL_MODE5; // 设置PB5为推挽输出、50MHz GPIOE->CRL &= ~GPIO_CRL_CNF5; // 清除PE5的CNF位 GPIOE->CRL |= GPIO_CRL_MODE5; // 设置PE5为推挽输出、50MHz // 3.PB5输出低电平(点亮LED) GPIOB->ODR &= ~GPIO_ODR_ODR5; // PB5输出低电平 GPIOE->ODR &= ~GPIO_ODR_ODR5; // PE5输出低电平 // 用一个死循环保持状态 while(1) { } }

这一版,代码几乎不需要注释,因为宏名字就是最好的注释

比如RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;,一看就知道是在开启GPIOB的时钟。


我的优化过程总结

回顾一下,我的代码是这样一步步优化的:

版本写法我的感受
第一步直接操作地址*(uint32_t *)(0x40021000 + 0x18) = 0x48;最原始,但能理解本质
第二步用stm32f10x.h(直接赋值)RCC->APB2ENR = 0x48;可读性提高,但有坑
第三步用位操作RCC->APB2ENR |= (1 << 3);安全了,但还要查手册
第四步用stm32f10x.h中现成的宏定义RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;GPIOB->CRL &= ~GPIO_CRL_CNF5;GPIOB->CRL |= GPIO_CRL_MODE5;代码自解释,可读性极高

我的建议是:

  • 学习阶段:从第一步开始,一步步理解每个优化的意义
  • 实际项目:直接用第四步,用stm32f10x.h中现成的宏定义

补充:如何查看stm32f10x.h中有哪些宏定义

有朋友可能会问:我怎么知道stm32f10x.h中有哪些宏定义?

我一般用这三种方法:

方法1:用Keil的代码补全功能

  • 输入RCC->,会自动提示所有寄存器名
  • 输入RCC_APB2ENR_,会自动提示所有相关的位定义宏

方法2:直接查看stm32f10x.h文件

  • 在Keil中,右键点击#include "stm32f10x.h"
  • 选择"Open Document 'stm32f10x.h'"
  • 然后搜索RCC_APB2ENRGPIO_CRL,就能找到所有相关的宏定义

方法3:查参考手册

  • 打开STM32参考手册(RM0008)
  • 找到对应寄存器的描述,对照着看stm32f10x.h中的宏定义

完整代码(最终版)

我把最终版的完整代码整理一下,你可以直接复制到Keil中编译。

c

#include "stm32f10x.h" int main(void) { // 两个LED灯分别连在PB5和PE5上 // 1.时钟配置:开启GPIOB和GPIOE的时钟 RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; // 开启GPIOB时钟 RCC->APB2ENR |= RCC_APB2ENR_IOPEEN; // 开启GPIOE时钟 // 2.GPIO工作模式配置:配置PB5和PE5为推挽输出、50MHz GPIOB->CRL &= ~GPIO_CRL_CNF5; // 清除PB5的CNF位 GPIOB->CRL |= GPIO_CRL_MODE5; // 设置PB5为推挽输出、50MHz GPIOE->CRL &= ~GPIO_CRL_CNF5; // 清除PE5的CNF位 GPIOE->CRL |= GPIO_CRL_MODE5; // 设置PE5为推挽输出、50MHz // 3.PB5输出低电平(点亮LED) GPIOB->ODR &= ~GPIO_ODR_ODR5; // PB5输出低电平 GPIOE->ODR &= ~GPIO_ODR_ODR5; // PE5输出低电平 // 用一个死循环保持状态 while(1) { } }

总结

这篇文章,我没有直接给你"完美代码",而是分享了我写代码时的优化过程:

  1. 最原始的方式(直接操作地址)
  2. 用stm32f10x.h(但直接赋值,有坑)
  3. 用位操作(安全,但仍需查手册)
  4. 用stm32f10x.h中现成的宏定义(最终版,推荐)

我的感受是:

  • 寄存器编程不是"一把梭",而是可以一步步优化的
  • 每一步优化,都是为了解决前一步的问题
  • 不需要自己手写宏定义,stm32f10x.h里都有!
  • 最终版的代码,用官方头文件中的宏定义,代码自解释,可读性极高
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/2 1:08:03

从推荐系统到大模型:算法工程师的转型实战指南

1. 转型背景与行业趋势观察2019年之前&#xff0c;推荐算法工程师还是互联网行业的热门岗位。当时我在某电商平台负责商品推荐系统&#xff0c;主要用协同过滤和矩阵分解这些传统方法。但到了2020年&#xff0c;明显感觉到行业风向在变——头部公司开始把更多资源投向预训练大模…

作者头像 李华
网站建设 2026/7/2 1:06:44

机械设计公差与配合实战指南:从核心原理到图纸标注

如果你是一名机械工程师、产品设计师&#xff0c;或者正在学习机械制图&#xff0c;那么“公差与配合”这个词组一定让你又爱又恨。爱的是&#xff0c;它是保证零件能够顺利装配、产品能够可靠运行的核心规则&#xff1b;恨的是&#xff0c;它概念抽象、符号繁多、计算复杂&…

作者头像 李华
网站建设 2026/7/2 1:05:14

零代码设计小米穿戴表盘:Mi-Create让创意触手可及

零代码设计小米穿戴表盘&#xff1a;Mi-Create让创意触手可及 【免费下载链接】Mi-Create Unofficial watchface creator for Xiaomi wearables ~2021 and above 项目地址: https://gitcode.com/gh_mirrors/mi/Mi-Create 厌倦了智能手表上千篇一律的默认表盘&#xff1f…

作者头像 李华
网站建设 2026/7/2 1:01:02

为什么说APAxpo已然成为各大品牌新品首发的核心阵地?

如果你留意过近年来的汽车改装圈&#xff0c;会发现一个明显的趋势&#xff1a;越来越多的国际一线品牌、国内头部厂商&#xff0c;选择把他们的重磅新品、全球首发车型&#xff0c;放在同一个展会上亮相。这个展会&#xff0c;就是APAxpo佛山改装展。为什么&#xff1f;因为这…

作者头像 李华
网站建设 2026/7/2 0:47:01

Redis Bitmap 实现北极星日淘用户签到与活跃度统计(极致省内存)

摘要&#xff1a;北极星日淘需要统计用户每日签到、月度活跃度、连续签到天数等运营数据&#xff0c;传统数据库存储每条签到记录数据冗余大、内存占用高、查询效率低。本文基于Redis Bitmap位图结构&#xff0c;实现百万级用户签到数据的极致轻量化存储&#xff0c;单月百万用…

作者头像 李华
网站建设 2026/7/2 0:46:04

2026大二寸证件照制作工具指南:手机App、免费无水印小程序操作教程

2026 年各类学历档案、职业资格考试、部分境外签证材料都会用到大二寸证件照&#xff0c;不少人会纠结使用什么软件处理&#xff0c;既需要适配标准大二寸尺寸&#xff0c;又想找到导出无水印、操作门槛低的工具。本文将分手机 App、微信小程序两大主流使用渠道&#xff0c;拆解…

作者头像 李华