在STM32F103C8T6中通过操作地址的方式进行端口写入,就是直接操作GPIO的寄存器地址。这是最底层、最高效的控制方式。
一.预先准备
1. 首先找到GPIO端口的基地址
#define GPIOA_BASE 0x40010800
#define GPIOB_BASE 0x40010C00
#define GPIOC_BASE 0x40011000
#define GPIOD_BASE 0x40011400
2. GPIO相关寄存器偏移地址
// 每个GPIO端口的寄存器偏移量
#define GPIO_CRL_OFFSET 0x00 // 端口配置低寄存器
#define GPIO_CRH_OFFSET 0x04 // 端口配置高寄存器
#define GPIO_IDR_OFFSET 0x08 // 端口输入数据寄存器
#define GPIO_ODR_OFFSET 0x0C // 端口输出数据寄存器
#define GPIO_BSRR_OFFSET 0x10 // 端口位设置/清除寄存器
#define GPIO_BRR_OFFSET 0x14 // 端口位清除寄存器
二. 通过ODR寄存器写入1
方法1:直接操作ODR寄存器
// 定义GPIOA的ODR寄存器地址
#define GPIOA_ODR (*(volatile uint32_t *)(GPIOA_BASE + GPIO_ODR_OFFSET))
// 向PA0写入1
void Write_PA0_High(void)
{
GPIOA_ODR |= (1 << 0); // 将第0位置1,其他位保持不变
}
// 向PA5写入1
void Write_PA5_High(void)
{
GPIOA_ODR |= (1 << 5); // 将第5位置1
}
方法2:使用BSRR寄存器
// 定义GPIOA的BSRR寄存器地址
#define GPIOA_BSRR (*(volatile uint32_t *)(GPIOA_BASE + GPIO_BSRR_OFFSET))
// 向PA0写入1(设置位)
void Set_PA0_High(void)
{
GPIOA_BSRR = (1 << 0); // 设置第0位为1
}
// 向PA5写入1
void Set_PA5_High(void)
{
GPIOA_BSRR = (1 << 5); // 设置第5位为1
}
三. 完整的使用示例
1.方法1
#include "stm32f10x.h"
// 定义寄存器地址
#define GPIOA_BASE 0x40010800
#define GPIOA_CRL (*(volatile uint32_t *)(GPIOA_BASE + 0x00))
#define GPIOA_ODR (*(volatile uint32_t *)(GPIOA_BASE + 0x0C))
#define GPIOA_BSRR (*(volatile uint32_t *)(GPIOA_BASE + 0x10))
// 开启时钟的地址
#define RCC_BASE 0x40021000
#define RCC_APB2ENR (*(volatile uint32_t *)(RCC_BASE + 0x18))
void GPIO_Init_By_Address(void)
{
// 1. 开启GPIOA时钟
RCC_APB2ENR |= (1 << 2); // 开启IOPA时钟
// 2. 配置PA5为推挽输出,最大速度50MHz
// CRL寄存器每4位控制一个引脚(0-7),PA5在[23:20]位
GPIOA_CRL &= ~(0xF << 20); // 先清零配置位
GPIOA_CRL |= (0x3 << 20); // 输出模式,最大速度50MHz
GPIOA_CRL |= (0x0 << 22); // 推挽输出模式
}
// 向PA5写入1
void PA5_Set_High(void)
{
GPIOA_BSRR = (1 << 5); // 使用BSRR设置位
}
// 向PA5写入0
void PA5_Set_Low(void)
{
GPIOA_BSRR = (1 << (5 + 16)); // 使用BSRR清除位(位16-31是清除位)
}
// 翻转PA5电平
void PA5_Toggle(void)
{
if (GPIOA_ODR & (1 << 5)) {
PA5_Set_Low(); // 当前为高,设为低
} else {
PA5_Set_High(); // 当前为低,设为高
}
}
这样子就可以写入了。
2.方法2
// 同时设置多个引脚为1
void Set_Multiple_Pins_High(void)
{
// 同时设置PA0、PA5、PA10为1
GPIOA_BSRR = (1 << 0) | (1 << 5) | (1 << 10);
}
// 使用ODR寄存器同时设置多个引脚(会影响到其他位)
void Set_Multiple_Pins_ODR(void)
{
// 设置PA0、PA5为1,其他位保持不变
GPIOA_ODR |= (1 << 0) | (1 << 5);
}
3.LED闪烁
// 假设LED连接在PC13上
#define GPIOC_BASE 0x40011000
#define GPIOC_CRH (*(volatile uint32_t *)(GPIOC_BASE + 0x04))
#define GPIOC_BSRR (*(volatile uint32_t *)(GPIOC_BASE + 0x10))
#define RCC_APB2ENR (*(volatile uint32_t *)(0x40021000 + 0x18))
void LED_Init(void)
{
// 开启GPIOC时钟
RCC_APB2ENR |= (1 << 4); // 开启IOPC时钟
// 配置PC13为推挽输出
// PC13在CRH寄存器[23:20]位
GPIOC_CRH &= ~(0xF << 20); // 清零配置位
GPIOC_CRH |= (0x3 << 20); // 输出模式,最大速度50MHz
GPIOC_CRH |= (0x0 << 22); // 推挽输出模式
}
void LED_On(void)
{
GPIOC_BSRR = (1 << 13); // PC13置1,LED亮
}
void LED_Off(void)
{
GPIOC_BSRR = (1 << (13 + 16)); // PC13清0,LED灭
}
void LED_Toggle(void)
{
// 简单的延时函数
volatile uint32_t i;
for(i = 0; i < 1000000; i++);
// 翻转LED
if (GPIOC_ODR & (1 << 13)) {
LED_Off();
} else {
LED_On();
}
}
int main(void)
{
LED_Init();
while(1)
{
LED_Toggle(); // LED闪烁
}
}
四. 关键要点总结
-
BSRR vs ODR:
-
BSRR:原子操作,只影响指定的位,推荐使用
-
ODR:需要读-修改-写操作,可能影响其他位
-
-
BSRR寄存器特点:
-
位0-15:设置位(写1对应引脚输出高电平)
-
位16-31:清除位(写1对应引脚输出低电平)
-
写0的位没有任何效果
-
-
操作步骤:
-
开启对应GPIO端口的时钟
-
配置GPIO为输出模式
-
通过BSRR或ODR寄存器控制输出电平
-
最后我们来看一看数据手册







这也是详细的介绍过程。