初识STM32
2026年04月22日 创建
STM32的字面意义
ST——意法半导体
M——Microcontroller的缩写,微控制器
32——32bit,32 位内核架构
内部集成了外设协议
串口——UART
内部集成电路——I2C
串行通信接口——SPI
SDIO、FSMC接口、I2S、SAI、ADC、GPIO
2的32次方=4GB,所以32位的芯片,最大的内存是4GB
寄存器映射就是对芯片里面的某一个具有特殊功能的内存单元取一个别名。
C语言对于寄存器的封装:
#define GPIOF_BASE 0x40010C00
#define GOIOF_MODER *( unsigned int *)(GPIOF_BASE+0x00 )
typedef struct
{
uint32_t MODER;
uint32_t OTYPER;
uint32_t OSPEEDR;
uint32_t PUPDR;
uint32_t IDR;
uint32_t ODR;
uint32_t BSRR;
uint32_t LCKR;
uint16_t AFRL;
uint16_t AFRH;
}GPIO_TypeDef;
#define GPIOA_BASE (0x40020000)
#define GPIOB_BASE (0x40020400)
#define GPIOC_BASE (0x40020800)
#define GPIOD_BASE (0x40020C00)
#define GPIOE_BASE (0x40021000)
#define GPIOF_BASE (0x40021400)
#define GPIOA (GPIO_TypeDef *) GPIOA_BASE
#define GPIOB (GPIO_TypeDef *) GPIOB_BASE
#define GPIOC (GPIO_TypeDef *) GPIOC_BASE
#define GPIOD (GPIO_TypeDef *) GPIOD_BASE
#define GPIOE (GPIO_TypeDef *) GPIOE_BASE
#define GPIOF (GPIO_TypeDef *) GPIOF_BASE
GPIOA->ODR = 0x00000001;GPIO_TypeDef 结构体:1:1 复刻 GPIO 所有寄存器的内存布局
GPIOx_BASE 宏:每组 GPIO 硬件寄存器的起始基地址
GPIOx 宏:把基地址强制转为结构体指针
最终用法 GPIOA->MODER = 直接读写 GPIOA 硬件的 MODER 寄存器
通过寄存器点亮PF6LED灯
#include "stm32f4xx.h"
int main(void)
{
//通过RCC的外设时钟使能寄存器的第5位置1,开启GPIOF的端口的时钟
* (unsigned int *)(0x40023800+0x30) |= 1 << 5;
//控制GPIOF的方向
//清空PF6的模式位(12、13位清0)
* (unsigned int *)(0x40021400+0x00) &= ~(3 << (2*6));
//设置为01,通用输出
* (unsigned int *)(0x40021400+0x00) |= 1 << (2*6);
//控制GPIO的数据输出寄存器
//PF6输出高电平
* (unsigned int *)(0x40021400+0x14) |= 0x01 << 6;
//PF6输出低电平
* (unsigned int *)(0x40021400+0x14) &= ~(1 << 6);
} void SystemInit(void)
{
}点亮一个霸天虎v2的led
首先看原理图,了解到RED灯是PF6,也就是GPIOF的第六个输出引脚
然后通过手册,GPIO挂载到的是AHB1,了解到RCC的外设时钟使能寄存器, 也就是AHB1 外设时钟使能寄存器 (RCC_AHB1ENR)的第5位是控制GPIOF时钟的打开,1是使能
然后在GPIOF的模式位,GPIOF_MODER,也就是GPIO6的12、13位清0,再设置为01,设置为通用输出,
然后在GPIOF的第六位的数据输出寄存器GPIOF_ODR,置1,输出高电平,置0,输出低电平
关于把控制寄存器的某一个位置,
核心原则:操作寄存器不能 “覆盖”,只能 “修改”
单片机的寄存器里,每一位都管着不同的功能(比如有的管串口,有的管定时器)。
绝对不能直接给寄存器写一个固定值,那样会把其他位的功能搞乱。所以我们必须做到:
只想把某几位置 1 → 其他位保持原样
只想把某几位清 0 → 其他位保持原样
想把某一位置1,按位或
寄存器 |= (1 << 位号);
想把某一位置0,先取反再按位与
寄存器 &= ~(1 << 位号);
// 先清空这两位
寄存器 &= ~(3 << 起始位号);
// 再设置这两位的值
寄存器 |= (1 << 起始位号);
//==================== 必须的类型定义 ====================
typedef unsigned int uint32_t;
typedef unsigned short uint16_t;
typedef unsigned char uint8_t;
// 表示“可读写”寄存器
#define __IO volatile
//==================== GPIO 寄存器结构体 (完整标准) ====================
typedef struct
{
__IO uint32_t MODER; /*!< GPIO port mode register, Address offset: 0x00 */
__IO uint32_t OTYPER; /*!< GPIO port output type register, Address offset: 0x04 */
__IO uint32_t OSPEEDR; /*!< GPIO port output speed register, Address offset: 0x08 */
__IO uint32_t PUPDR; /*!< GPIO port pull-up/pull-down register, Address offset: 0x0C */
__IO uint32_t IDR; /*!< GPIO port input data register, Address offset: 0x10 */
__IO uint32_t ODR; /*!< GPIO port output data register, Address offset: 0x14 */
__IO uint32_t BSRR; /*!< GPIO port bit set/reset register, Address offset: 0x18 */
__IO uint32_t LCKR; /*!< GPIO port configuration lock register, Address offset: 0x1C */
__IO uint32_t AFR[2]; /*!< GPIO alternate function registers, Address offset: 0x20-0x24 */
} GPIO_TypeDef;
//==================== RCC 寄存器结构体 (完整标准) ====================
typedef struct
{
__IO uint32_t CR; /*!< RCC clock control register, Address offset: 0x00 */
__IO uint32_t PLLCFGR; /*!< RCC PLL configuration register, Address offset: 0x04 */
__IO uint32_t CFGR; /*!< RCC clock configuration register, Address offset: 0x08 */
__IO uint32_t CIR; /*!< RCC clock interrupt register, */
// 必须填充 0x10, 0x14, 0x18, 0x1C, 0x20, 0x24, 0x28, 0x2C 这 8 个位置
uint32_t RESERVED_PAD[8];
__IO uint32_t AHB1ENR; /*!< RCC AHB1 peripheral clock enable register, Address offset: 0x30 */
__IO uint32_t AHB2ENR; /*!< RCC AHB2 peripheral clock enable register, Address offset: 0x34 */
__IO uint32_t AHB3ENR; /*!< RCC AHB3 peripheral clock enable register, Address offset: 0x38 */
uint32_t RESERVED0;
__IO uint32_t APB1ENR; /*!< RCC APB1 peripheral clock enable register, Address offset: 0x40 */
__IO uint32_t APB2ENR; /*!< RCC APB2 peripheral clock enable register, Address offset: 0x44 */
} RCC_TypeDef;
//基地址
#define RCC_BASE 0x40023800
#define GPIOF_BASE 0x40021400
//位定义
#define GPIO_EN (1 << 5) //使能GPIOF时钟
#define PIN7_OUT (1 << (2*7)) //PF7配置为输出
#define PIN8_OUT (1 << (2*8)) //PF8配置为输出
#define PF7_PIN (1 << 7) //PF7 引脚
#define PF8_PIN (1 << 8) //PF8 引脚
//寄存器指针封装
#define RCC ((RCC_TypeDef *)RCC_BASE)
#define GPIOF ((GPIO_TypeDef *)GPIOF_BASE)#include "stm32f4xx.h"
// 微秒延时函数(STM32通用)
void usleep(unsigned int us)
{
volatile unsigned int i, j;
for(i = 0; i < us; i++)
for(j = 0; j < 70; j++); // 72MHz 内核下大约 1us
}
int main(void)
{
/*
//通过RCC的外设时钟使能寄存器的第5位置1,开启GPIOF的端口的时钟
* (unsigned int *)(0x40023800+0x30) |= 1 << 5;
//控制GPIOF的方向
//清空PF7的模式位(13、14位清0)
* (unsigned int *)(0x40021400+0x00) &= ~(3 << (2*7));
//设置为01,通用输出
* (unsigned int *)(0x40021400+0x00) |= 1 << (2*7);
//控制GPIO的数据输出寄存器
//PF7输出高电平
* (unsigned int *)(0x40021400+0x14) |= 0x01 << 7;
//PF7输出低电平
* (unsigned int *)(0x40021400+0x14) &= ~(1 << 7);
*/
//通过RCC的外设时钟使能寄存器的第5位置1,开启GPIOF的端口的时钟
RCC->AHB1ENR |= (1 << 5);
//控制GPIOF的方向
//清空PF7的模式位(13、14位清0)
GPIOF->MODER &= ~(3 << (2*7));
//设置为01,通用输出
GPIOF->MODER |= 1 << (2*7);
//清空PF8的模式位(13、14位清0)
GPIOF->MODER &= ~(3 << (2*8));
//设置为01,通用输出
GPIOF->MODER |= 1 << (2*8);
//清空PF6的模式位(13、14位清0)
GPIOF->MODER &= ~(3 << (2*6));
//设置为01,通用输出
GPIOF->MODER |= 1 << (2*6);
GPIOF->ODR |= (1<<6) | (1<<7) | (1<<8);
while(1)
{
//控制GPIO的数据输出寄存器
//PF6输出低电平
GPIOF->ODR &= ~(1 << 6);
usleep(50000);
//PF6输出高电平
GPIOF->ODR |= (1 << 6);
usleep(50000);
//PF7输出低电平
GPIOF->ODR &= ~(1 << 7);
usleep(50000);
//PF7输出高电平
GPIOF->ODR |= (1 << 7);
usleep(50000);
//PF8输出低电平
GPIOF->ODR &= ~(1 << 8);
usleep(50000);
//PF8输出高电平
GPIOF->ODR |= (1 << 8);
usleep(50000);
}
}
void SystemInit(void)
{
}结构体封装完,灯却点不亮了,结果发现是RCC定义的结构体有问题,漏掉了RESERVERD,导致内存地址没对齐
GPIOF->MODER,是直接访问结构体的地址的值,而不是地址
为什么软件的延时函数失效了,发现是延时函数的unsigned int 参数没有加volatile
三种必须要加volatile的情况:
1.硬件寄存器的地址
2.中断函数与主程序共享的全局变量,多线程/多任务共享变量
3.编写“空等待”延时函数,函数没产生任何结果
不能加volatile的情况:普通的局部变量,数学运算,性能敏感的代码