STM32F103 寄存器方式实现流水灯
STM32F103 寄存器方式实现流水灯文章目录STM32F103 寄存器方式实现流水灯一、GPIO端口初始化1. 时钟配置2. 设置端口模式二、程序实现1. 流水灯原理2. C语言实现3. 编译和下载时注意的点三、运行效果四、总结五、参考一、GPIO端口初始化本次实验我没有用 HAL 库而是直接对着寄存器写硬件用的是 STM32F103C8T6 最小系统板再加上面包板和 3 个外接 LED板上自带的 PC13 LED 也一起用上。四个灯的顺序是PA0 - PA3 - PB0 - PC13每个灯亮 1 秒然后换下一个灯最后一直循环我一开始觉得直接写寄存器会很麻烦后来把手册里的地址和每一位是什么意思弄清楚以后其实主要就是“先把时钟打开再把引脚设成输出最后控制高低电平”这三步1. 时钟配置STM32 的外设默认不是全都打开时钟的GPIOA、GPIOB、GPIOC 都属于 APB2 总线。用之前要先把对应时钟打开不然寄存器就算写了引脚也不会正常工作先把地址找到根据《STM32中文参考手册》第 28 页的存储器映射RCC 起始地址0x40021000GPIOA 起始地址0x40010800GPIOB 起始地址0x40010C00GPIOC 起始地址0x40011000APB2 外设时钟使能寄存器的名字是RCC_APB2ENR它的偏移地址是0x18所以实际地址就是0x40021000 0x18 0x40021018手册里 RCC、GPIOA、GPIOB、GPIOC 的起始地址截图如下红框里是我这次用到的内容图1RCC 的地址范围截图自《STM32中文参考手册》第 28 页。图2APB2 下 GPIOA、GPIOB、GPIOC 的地址范围截图自《STM32中文参考手册》第 28 页。然后在手册第 77 页找到RCC_APB2ENR。这次要用到下面三个位1第 2 位IOPAEN打开 GPIOA 时钟2第 3 位IOPBEN打开 GPIOB 时钟3第 4 位IOPCEN打开 GPIOC 时钟图3RCC_APB2ENR 中的 GPIOA/B/C 时钟使能位截图自《STM32中文参考手册》第 77 页。代码里直接把这三位写 1RCC-APB2ENRRCC-APB2ENR|(1U2);/* GPIOA 时钟 */RCC-APB2ENRRCC-APB2ENR|(1U3);/* GPIOB 时钟 */RCC-APB2ENRRCC-APB2ENR|(1U4);/* GPIOC 时钟 */这里其实也可以一行写RCC-APB2ENR|(1U2)|(1U3)|(1U4);2. 设置端口模式时钟打开后就要把用到的引脚设成输出。GPIO 的配置寄存器主要有两个GPIOx_CRL配置第 0 到 7 号引脚偏移地址是0x00GPIOx_CRH配置第 8 到 15 号引脚偏移地址是0x04每个引脚占 4 位分别是CNFy[1:0]和MODEy[1:0]。我这次用到的引脚是 PA0、PA3、PB0 和 PC13所以PA0、PA3、PB0 都看CRLPC13 要看CRH根据手册第 113 页GPIOx_CRL的偏移地址是0x00复位值是0x4444 4444。图4GPIOx_CRL 的引脚配置位截图自《STM32中文参考手册》第 113 页。根据手册第 114 页GPIOx_CRH的偏移地址是0x04。PC13 对应CNF13[1:0]和MODE13[1:0]也就是CRH的第 20 到 23 位。图5GPIOx_CRH 中 PC13 对应的配置位截图自《STM32中文参考手册》第 114 页。我们这里写的是推挽输出0x3表示 50MHz 推挽输出也就是MODE11、CNF000x2表示 2MHz 推挽输出也就是MODE10、CNF00PA0、PA3、PB0 我设置成 50MHz 推挽输出。PC13 比较特殊STM32F103 的 PC13 输出速度不能开太高所以这里设置成 2MHz 推挽输出稳一点。引脚对应的移位如下引脚寄存器占用位PA0GPIOA_CRL3:0PA3GPIOA_CRL15:12PB0GPIOB_CRL3:0PC13GPIOC_CRH23:20配置代码就是先清零再把对应的值写进去/* PA0CRL bit 0~3 */GPIOA-CRLGPIOA-CRL~(0xFU0);GPIOA-CRLGPIOA-CRL|(GPIO_OUTPUT_50MHZ_PP0);/* PA3CRL bit 12~15 */GPIOA-CRLGPIOA-CRL~(0xFU12);GPIOA-CRLGPIOA-CRL|(GPIO_OUTPUT_50MHZ_PP12);/* PB0CRL bit 0~3 */GPIOB-CRLGPIOB-CRL~(0xFU0);GPIOB-CRLGPIOB-CRL|(GPIO_OUTPUT_50MHZ_PP0);/* PC13CRH bit 20~23 */GPIOC-CRHGPIOC-CRH~(0xFU20);GPIOC-CRHGPIOC-CRH|(GPIO_OUTPUT_2MHZ_PP20);这里我踩过的坑是 PA3 的移位。PA3 不是往左移 3 位而是每个引脚占 4 位所以要用3 * 4 12。PC13 也不能直接写在CRL里它要到CRH的20位开始写。GPIO 各个寄存器的偏移地址在第 129 页的寄存器映射表里也能查到我把它也截了下来。图6GPIOx 的寄存器偏移地址截图自《STM32中文参考手册》第 129 页。常见的几个偏移地址如下寄存器偏移地址作用GPIOx_CRL0x00配置 0 到 7 号引脚GPIOx_CRH0x04配置 8 到 15 号引脚GPIOx_ODR0x0C端口输出数据GPIOx_BSRR0x10置位或复位输出位GPIOx_BRR0x14复位输出位控制输出时我没有直接改ODR而是用了BSRR和BRR。因为直接写ODR会影响整个端口容易把别的引脚状态也改掉BSRR和BRR只针对指定引脚更安全。手册第 115 页对这两个寄存器说明得比较清楚。图7GPIOx_BSRR、GPIOx_BRR 的置位和复位说明截图自《STM32中文参考手册》第 115 页。二、程序实现1. 流水灯原理四个灯的状态是这样的步骤PA0PA3PB0PC13第 1 步点亮熄灭熄灭熄灭第 2 步熄灭点亮熄灭熄灭第 3 步熄灭熄灭点亮熄灭第 4 步熄灭熄灭熄灭点亮每一步后面延时 1 秒然后进入下一步。这里还有一个 LED 正负极的问题。我的外接 LED 是按“GPIO - LED 阳极LED 阴极 - 电阻 - GND”接的所以 PA0、PA3、PB0 是高电平点亮。PC13 是板载 LED它是低电平点亮。两种灯的极性不一样所以代码里设置了单独的宏没有全部写死成同一种。如果自己接线和我不同只需要改下面这四个宏#defineLED_PA0_ACTIVE_LOW0U#defineLED_PA3_ACTIVE_LOW0U#defineLED_PB0_ACTIVE_LOW0U#defineLED_PC13_ACTIVE_LOW1U1U表示低电平点亮0U表示高电平点亮。2. C语言实现完整代码如下#includestm32f10x.h/* LED 顺序PA0 - PA3 - PB0 - PC13 */#defineLED_PA0_PIN(1U0)#defineLED_PA3_PIN(1U3)#defineLED_PB0_PIN(1U0)#defineLED_PC13_PIN(1U13)/* * LED 极性设置 * 1U低电平点亮 * 0U高电平点亮 * * 默认适用于 * PA0、PA3、PB0LED 阳极接 GPIO阴极接电阻到 GND高电平点亮 * PC13STM32 蓝板板载 LED低电平点亮 * * 如果你的 PA0、PA3、PB0 也是低电平点亮将对应宏改成 1U。 */#defineLED_PA0_ACTIVE_LOW0U#defineLED_PA3_ACTIVE_LOW0U#defineLED_PB0_ACTIVE_LOW0U#defineLED_PC13_ACTIVE_LOW1U/* GPIO 输出模式配置位 */#defineGPIO_OUTPUT_50MHZ_PP0x3U#defineGPIO_OUTPUT_2MHZ_PP0x2U/* 每个 LED 点亮时间单位ms */#defineLED_DELAY_MS1000U/* * 软件延时STM32F103 主频 72MHz 时约等于 1ms 的整数倍。 * volatile 用于防止编译器优化掉延时循环。 */staticvoiddelay_ms(uint32_tms){volatileuint32_ti;while(ms0U){for(i0U;i7200U;i){/* 空循环 */}ms--;}}/* * 控制单个 LED。 * gpio_portGPIOA、GPIOB 或 GPIOC * pin对应引脚位 * active_low1 表示低电平点亮0 表示高电平点亮 * on1 表示点亮0 表示熄灭 */staticvoidled_write(GPIO_TypeDef*gpio_port,uint16_tpin,uint8_tactive_low,uint8_ton){if(on!0U){if(active_low!0U){gpio_port-BRRpin;/* 输出低电平点亮 */}else{gpio_port-BSRRpin;/* 输出高电平点亮 */}}else{if(active_low!0U){gpio_port-BSRRpin;/* 输出高电平熄灭 */}else{gpio_port-BRRpin;/* 输出低电平熄灭 */}}}/* 关闭全部 LED */staticvoidled_all_off(void){led_write(GPIOA,LED_PA0_PIN,LED_PA0_ACTIVE_LOW,0U);led_write(GPIOA,LED_PA3_PIN,LED_PA3_ACTIVE_LOW,0U);led_write(GPIOB,LED_PB0_PIN,LED_PB0_ACTIVE_LOW,0U);led_write(GPIOC,LED_PC13_PIN,LED_PC13_ACTIVE_LOW,0U);}intmain(void){/* 1. 打开 GPIOA、GPIOB、GPIOC 时钟 */RCC-APB2ENRRCC-APB2ENR|(1U2);/* GPIOA */RCC-APB2ENRRCC-APB2ENR|(1U3);/* GPIOB */RCC-APB2ENRRCC-APB2ENR|(1U4);/* GPIOC *//* * 2. 配置 LED 引脚为推挽输出 * PA0、PA3、PB0 使用 CRL 寄存器配置为 50MHz 推挽输出。 * PC13 使用 CRH 寄存器按芯片限制配置为 2MHz 推挽输出。 *//* PA0CRL bit 0~3 */GPIOA-CRLGPIOA-CRL~(0xFU0);GPIOA-CRLGPIOA-CRL|(GPIO_OUTPUT_50MHZ_PP0);/* PA3CRL bit 12~15 */GPIOA-CRLGPIOA-CRL~(0xFU12);GPIOA-CRLGPIOA-CRL|(GPIO_OUTPUT_50MHZ_PP12);/* PB0CRL bit 0~3 */GPIOB-CRLGPIOB-CRL~(0xFU0);GPIOB-CRLGPIOB-CRL|(GPIO_OUTPUT_50MHZ_PP0);/* PC13CRH bit 20~23 */GPIOC-CRHGPIOC-CRH~(0xFU20);GPIOC-CRHGPIOC-CRH|(GPIO_OUTPUT_2MHZ_PP20);/* 3. 初始化后关闭全部 LED */led_all_off();/* 4. 按 PA0 - PA3 - PB0 - PC13 顺序循环 */while(1){/* 第一个点亮 PA0 */led_all_off();led_write(GPIOA,LED_PA0_PIN,LED_PA0_ACTIVE_LOW,1U);delay_ms(LED_DELAY_MS);/* 第二个点亮 PA3 */led_all_off();led_write(GPIOA,LED_PA3_PIN,LED_PA3_ACTIVE_LOW,1U);delay_ms(LED_DELAY_MS);/* 第三个点亮 PB0 */led_all_off();led_write(GPIOB,LED_PB0_PIN,LED_PB0_ACTIVE_LOW,1U);delay_ms(LED_DELAY_MS);/* 第四个点亮 PC13 */led_all_off();led_write(GPIOC,LED_PC13_PIN,LED_PC13_ACTIVE_LOW,1U);delay_ms(LED_DELAY_MS);}}代码里我写了一个led_write()函数它就是根据引脚极性决定该写BSRR还是BRR。这样主循环看起来不会太乱也能避免 PA0、PA3、PB0 和 PC13 极性不同导致显示反了。延时函数用的是软件循环for(i0U;i7200U;i)在 72MHz 主频下大概延时 1ms再乘上LED_DELAY_MS就是每个灯的点亮时间。它不是特别精确的定时器延时不过对于流水灯实验够用了。变量用volatile是为了防止编译器把空循环优化掉。3. 编译和下载时注意的点代码要放到 Keil 工程真正编译的main.c里面不能只改一个.txt文件。改完以后要点Rebuild再重新下载到开发板。板子下载时BOOT0接 GND正常从 Flash 启动。如果代码编译没报错但灯还是不变可以先检查是不是修改的文件没有加入工程或者只是点下载但没有重新编译。三、运行效果下载成功后LED 会按照PA0 - PA3 - PB0 - PC13的顺序循环点亮每个灯亮大约 1 秒。四、总结我这次印象比较深的有三点GPIOA、GPIOB、GPIOC 的时钟要先在RCC_APB2ENR里面打开不然配置了也不工作。CRL和CRH是按引脚分组的PA3 对应的位是 12 到 15PC13 在CRH的 20 到 23 位。控制指定引脚时用BSRR和BRR比直接改ODR方便也不会影响同一个端口的其他引脚。另外LED 高电平点亮还是低电平点亮一定要先确认。PC13 板载 LED 和外接 LED 的接法可能不一样如果代码没错但效果反了一般就是极性设置的问题。这次代码虽然不长但把手册、地址、位操作和实际硬件连起来以后对 GPIO 的理解比只看库函数清楚多了。五、参考STMicroelectronics《STM32中文参考手册_V10》CSDN博客《stm32寄存器实现流水灯》https://blog.csdn.net/qq_47281915/article/details/120812867

相关新闻

AI测试工程师面试问什么:我用一万多字总结了13道高频面试题

AI测试工程师面试问什么:我用一万多字总结了13道高频面试题

AI测试这两年炒得很火热,各大公司纷纷宣布引入AI提升测试效率,招聘岗位也开始出现"AI测试工程师"这个新头衔。但真到了面试环节,很多候选人却发现:网上的AI测试资料要么停留在概念层面,要么就是工具教程&…

2026/9/24 17:57:47 阅读更多 →
MemOS 获取消息接口(POST /product/get/message)完全指南:原始对话历史拉取、参数详解与实战场景

MemOS 获取消息接口(POST /product/get/message)完全指南:原始对话历史拉取、参数详解与实战场景

人工智能大模型Agent 记忆AI AgentRAG知识图谱dsh-plugin 【免费下载链接】MemOS Self-evolving memory OS for LLM & AI Agents: ultra-persistent memory, hybrid-retrieval, and cross-task skill reuse, with 35.24% token savings and DeepSeek Harness support. 项目…

2026/9/24 17:57:47 阅读更多 →
Python装饰器自学全解

Python装饰器自学全解

1. 关于装饰器这一概念的基本知识, 其中的第一小节内容探讨了这样一个问题, 即究竟什么是装饰器?装饰器属于一种设计模式, 这种设计模式允许在不修改原始函数代码的前提情况下, 往函数内部添加全新的功能内容, 它属于语法糖的一种范畴, 是基于闭包机制以及高阶函数来实现相关效…

2026/9/24 17:57:47 阅读更多 →

最新新闻

OpenClaw 部署安全指南:从会话锁到提示注入的完整防护

OpenClaw 部署安全指南:从会话锁到提示注入的完整防护

本来我想先夸一夸 OpenClaw 这玩意儿本地跑起来有多爽,但这篇文章还是直接从风险讲起吧。你把它部署好、接到 Teams 或飞书上、再配上千问之类的模型,它就从一个普通程序变成了一个常驻代理:能读文件、能发消息、能调 API、还能替你执行命令。…

2026/9/25 4:49:42 阅读更多 →
宏集DC-Pi工业控制器:融合PLC、HMI与边缘AI的工程实践

宏集DC-Pi工业控制器:融合PLC、HMI与边缘AI的工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/25 4:49:42 阅读更多 →
Windows 10强制关机脚本:原理、权限与企业级落地

Windows 10强制关机脚本:原理、权限与企业级落地

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/25 4:49:42 阅读更多 →
油猴脚本防暂停原理与实战:浏览器自动化技术解析

油猴脚本防暂停原理与实战:浏览器自动化技术解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/25 4:49:42 阅读更多 →
微信公众号历史文章列表页获取实战指南

微信公众号历史文章列表页获取实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/25 4:49:41 阅读更多 →
Ubuntu 22.04 Server 安装与初始化配置全攻略

Ubuntu 22.04 Server 安装与初始化配置全攻略

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/25 4:48:41 阅读更多 →

日新闻

AI元人文:从工具使用到思维重构的深度探索

AI元人文:从工具使用到思维重构的深度探索

最近半年我一直在琢磨一件事:AI元人文到底是什么?说白了,就是“用元视角重新审视人与AI的关系”,也在“探索AI如何反向逼着我们发现自己的思考边界”。标题里的“元探索”,在我看就是一层套一层的追问——当你用AI解决…

2026/9/25 0:00:41 阅读更多 →
Python+CNN车牌识别实战:从数据预处理到模型训练与部署

Python+CNN车牌识别实战:从数据预处理到模型训练与部署

简介:基于Python与卷积神经网络的车牌识别项目,面向计算机视觉初学者及智能交通开发者,目标是帮助用户掌握从数据预处理、模型构建到实际部署的完整流程。压缩包共25个文件,包含jpg/png图像样本、py训练脚本、md说明文档、dat数据…

2026/9/25 0:00:41 阅读更多 →
Vim基础操作全攻略:保存退出、模式切换与高频命令实战

Vim基础操作全攻略:保存退出、模式切换与高频命令实战

1. 项目概述1.1 核心需求解析今天聊聊Vim。写这个题目的原因是:几乎每个后端开发者、运维人员、数据工程师某天都会遇到一个场景——深夜加班,服务器登录界面只有黑底白字,编辑器只有vi/vim,你必须在五分钟内完成一次配置修改并保…

2026/9/25 0:00:41 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/24 14:34:13 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/24 9:10:42 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/24 14:33:56 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/24 12:50:34 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/24 14:33:48 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/24 12:49:17 阅读更多 →