学习所有用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 的硬件资源。

相关推荐
handler011 小时前
算法:Trie树(字典树)
c语言·数据结构·c++·笔记·算法·深度优先
普中科技1 小时前
【普中 51-Ai8051 开发攻略】-- 第 3 章 Ai8051U 介绍
单片机·嵌入式硬件·开发板·普中科技·ai8051u·aicube
元契1 小时前
英语基础语法学习3
学习
元契2 小时前
英语基础语法学习2
学习
坚定学代码2 小时前
qt c++ 局域网聊天小工具
c++·qt·个人开发
Book思议-2 小时前
【数据结构考研真题】链表题
c语言·数据结构·算法·链表·408·计算机考研
盐水冰2 小时前
【烘焙坊项目】后端搭建(14) - 工作台&导出数据报表
java·后端·学习
隔壁大炮2 小时前
PID控制结构&角度环实现直立
stm32·嵌入式·硬件·pid·平衡车·江协科技
羽获飞2 小时前
从零开始学嵌入式之STM32——29.通用定时器-输入捕获模式测量信号的周期和频率
stm32·单片机·嵌入式硬件