尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

STM32F103 寄存器方式实现流水灯

发布时间:2026/9/24 17:57:29

资讯中心
01
ARTICLE

STM32F103 寄存器方式实现流水灯

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
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。