学习所有用c语言定义stm32的语句

在 STM32 开发中,使用 C 语言定义外设寄存器是底层编程的基础。通常通过结构体将外设的多个寄存器组织在一起,再通过指针指向其基地址,从而实现对寄存器的读写操作。下面将穷举(详细列举)常见的定义方式,包括寄存器地址映射、结构体封装、位操作宏以及实际使用示例。


1. 定义外设基地址

每个外设都有一段连续的地址空间,首先需要定义其起始地址。

c 复制代码
// 外设总线基地址(以 STM32F1 系列为例)
#define PERIPH_BASE           ((uint32_t)0x40000000)   // 外设总线基地址
#define APB1PERIPH_BASE       PERIPH_BASE              // APB1 总线
#define APB2PERIPH_BASE       (PERIPH_BASE + 0x10000)  // APB2 总线
#define AHBPERIPH_BASE        (PERIPH_BASE + 0x20000)  // AHB 总线

// GPIO 外设基地址(位于 APB2 上)
#define GPIOA_BASE            (APB2PERIPH_BASE + 0x0800)
#define GPIOB_BASE            (APB2PERIPH_BASE + 0x0C00)
// ... 其他 GPIO

// USART 外设基地址(位于 APB1 上)
#define USART1_BASE           (APB2PERIPH_BASE + 0x3800)  // USART1 在 APB2
#define USART2_BASE           (APB1PERIPH_BASE + 0x4400)
#define USART3_BASE           (APB1PERIPH_BASE + 0x4800)

2. 定义寄存器结构体

将外设的所有寄存器按照数据手册中的偏移顺序定义为结构体成员。

c 复制代码
// GPIO 寄存器结构体
typedef struct {
    volatile uint32_t CRL;    // 端口配置低寄存器,偏移 0x00
    volatile uint32_t CRH;    // 端口配置高寄存器,偏移 0x04
    volatile uint32_t IDR;    // 输入数据寄存器,偏移 0x08
    volatile uint32_t ODR;    // 输出数据寄存器,偏移 0x0C
    volatile uint32_t BSRR;   // 位设置/清除寄存器,偏移 0x10
    volatile uint32_t BRR;    // 位清除寄存器,偏移 0x14
    volatile uint32_t LCKR;   // 配置锁定寄存器,偏移 0x18
} GPIO_TypeDef;

// USART 寄存器结构体
typedef struct {
    volatile uint32_t SR;     // 状态寄存器,偏移 0x00
    volatile uint32_t DR;     // 数据寄存器,偏移 0x04
    volatile uint32_t BRR;    // 波特率寄存器,偏移 0x08
    volatile uint32_t CR1;    // 控制寄存器1,偏移 0x0C
    volatile uint32_t CR2;    // 控制寄存器2,偏移 0x10
    volatile uint32_t CR3;    // 控制寄存器3,偏移 0x14
    volatile uint32_t GTPR;   // 保护时间和预分频寄存器,偏移 0x18
} USART_TypeDef;

3. 将基地址转换为结构体指针

通过强制类型转换,使指针指向外设的基地址,之后就可以通过指针访问寄存器。

c 复制代码
#define GPIOA               ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB               ((GPIO_TypeDef *) GPIOB_BASE)
#define USART1              ((USART_TypeDef *) USART1_BASE)
#define USART2              ((USART_TypeDef *) USART2_BASE)

4. 定义位操作宏

为了方便设置/清除寄存器的某一位,常定义位掩码或位号。

c 复制代码
// GPIO 引脚位定义
#define GPIO_Pin_0          ((uint16_t)0x0001)
#define GPIO_Pin_1          ((uint16_t)0x0002)
// ...
#define GPIO_Pin_15         ((uint16_t)0x8000)

// 寄存器位定义(例如 USART SR 寄存器)
#define USART_SR_TXE        ((uint16_t)0x0080)  // 发送数据寄存器空
#define USART_SR_TC         ((uint16_t)0x0040)  // 发送完成
#define USART_SR_RXNE       ((uint16_t)0x0020)  // 读数据寄存器非空

5. 定义操作寄存器的宏或函数

常用操作如设置位、清除位、读取位等,可以用宏封装。

c 复制代码
// 设置寄存器的某些位
#define SET_BIT(REG, BIT)     ((REG) |= (BIT))

// 清除寄存器的某些位
#define CLEAR_BIT(REG, BIT)   ((REG) &= ~(BIT))

// 读取寄存器的某些位
#define READ_BIT(REG, BIT)    ((REG) & (BIT))

// 写入寄存器的值
#define WRITE_REG(REG, VAL)   ((REG) = (VAL))

// 读取寄存器的值
#define READ_REG(REG)         ((REG))

6. 实际使用示例

6.1 GPIO 初始化与操作

c 复制代码
// 使能 GPIOA 时钟(假设 RCC 寄存器已定义)
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;

// 配置 PA0 为推挽输出,最大速度 50MHz
GPIOA->CRL &= ~(GPIO_CRL_CNF0 | GPIO_CRL_MODE0);  // 先清除原有配置
GPIOA->CRL |= GPIO_CRL_MODE0_1 | GPIO_CRL_MODE0_0; // 设置模式为 50MHz 输出
// CNF 保持 00 表示通用推挽输出

// 设置 PA0 输出高电平
GPIOA->BSRR = GPIO_Pin_0;

// 设置 PA0 输出低电平
GPIOA->BRR = GPIO_Pin_0;

// 读取 PA1 输入电平
uint8_t pin_state = (GPIOA->IDR & GPIO_Pin_1) ? 1 : 0;

6.2 USART 发送数据

c 复制代码
// 使能 USART1 时钟
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;

// 配置波特率、数据位等(略)

// 发送一个字节
void USART_SendChar(USART_TypeDef* USARTx, uint8_t ch) {
    // 等待发送数据寄存器空
    while (!(USARTx->SR & USART_SR_TXE));
    USARTx->DR = ch;
}

// 使用
USART_SendChar(USART1, 'A');

7. 更高级的定义方式

7.1 使用位域结构体(不推荐,因为位域的顺序和端序可能因编译器而异,但有时用于精确控制)

c 复制代码
typedef struct {
    volatile uint32_t MODER  : 2;  // 模式
    volatile uint32_t OTYPER : 1;  // 输出类型
    volatile uint32_t OSPEEDR: 2;  // 输出速度
    volatile uint32_t PUPD   : 2;  // 上拉/下拉
    volatile uint32_t IDR    : 1;  // 输入数据
    volatile uint32_t ODR    : 1;  // 输出数据
    // ... 其他位
} GPIO_Pin_TypeDef;

但这种方式不常用,因为 STM32 标准库和 HAL 库都采用宏和结构体方式。

7.2 定义中断向量表

c 复制代码
typedef void (*pFunction)(void);  // 函数指针类型

typedef struct {
    uint32_t     stack_top;       // 栈顶地址
    pFunction    Reset_Handler;   // 复位处理函数
    pFunction    NMI_Handler;     // NMI 处理函数
    pFunction    HardFault_Handler; // 硬错误处理函数
    // ... 其他异常和中断向量
} VectorTable;

// 在链接脚本中分配向量表到起始地址
extern VectorTable __Vectors;

8. 常见的 C 语言语句在 STM32 编程中的应用

除了上述定义外,实际编程中会大量使用以下 C 语言语句:

  • 变量定义uint32_t timeout;
  • 循环while(!(USART1->SR & USART_SR_RXNE));
  • 条件判断if(GPIOA->IDR & GPIO_Pin_0) { ... }
  • 函数定义void delay(volatile uint32_t count) { while(count--); }
  • 宏定义#define LED_ON() GPIOA->BSRR = GPIO_Pin_5
  • 指针操作uint32_t *p = (uint32_t *)0x20000000;
  • 结构体访问GPIOA->CRL = 0x44444444;
  • 位运算TIM2->CR1 |= TIM_CR1_CEN;

总结

在 STM32 的 C 语言编程中,定义外设的核心步骤是:

  1. #define 定义外设的基地址。
  2. typedef struct 定义外设的寄存器映射。
  3. #define 将基地址转换为结构体指针,得到外设访问句柄。
  4. #define 定义各寄存器中的位掩码或功能常量。
  5. 使用上述定义进行寄存器读写和位操作。

这些定义通常放在芯片型号对应的头文件中(如 stm32f10x.h),开发者直接包含即可使用。通过这种方式,C 语言能够简洁且高效地操作 STM32 的硬件资源。

相关推荐
计算机安禾25 分钟前
【数据结构与算法】第36篇:排序大总结:稳定性、时间复杂度与适用场景
c语言·数据结构·c++·算法·链表·线性回归·visual studio
杨云龙UP32 分钟前
从0到1快速学会Linux操作系统(基础),这一篇就够了!
linux·运维·服务器·学习·ubuntu·centos·ssh
MaCa .BaKa38 分钟前
47-心里健康咨询平台/心理咨询系统
java·spring boot·mysql·tomcat·maven·intellij-idea·个人开发
计算机安禾1 小时前
【数据结构与算法】第35篇:归并排序与基数排序
c语言·数据结构·vscode·算法·排序算法·哈希算法·visual studio
头疼的程序员1 小时前
计算机网络:自顶向下方法(第七版)第八章 学习分享(三)
网络·学习·计算机网络
_李小白2 小时前
【OSG学习笔记】Day 37: NodeVisitor(顶点访问器)
笔记·学习
程序员雷欧2 小时前
大模型应用开发学习第八天
大数据·人工智能·学习
晓晓hh3 小时前
JavaSE学习——set集合和Map映射
学习
4caf13 小时前
作业2:6位数码管静态显示
嵌入式硬件·51单片机
不做无法实现的梦~3 小时前
STM32解析PPM协议
stm32·单片机·嵌入式硬件