STM32寄存器开发全面指南
1. 寄存器开发基础
1.1 寄存器与HAL库的关系
STM32微控制器可以通过直接操作寄存器或使用HAL库进行开发。寄存器操作是最底层的编程方式,而HAL库则是ST公司提供的硬件抽象层,封装了寄存器操作,提供了更友好的API。
1.2 寄存器操作基本原理
STM32的寄存器通常是32位的,每个位或一组位控制特定功能。寄存器操作主要包括:
- 读取寄存器值
- 设置特定位(置1)
- 清除特定位(置0)
- 修改特定位(先清除再设置)
位操作基础
在STM32中,大多数寄存器配置都是通过位操作来完成的。主要有以下几种操作:
1. 清除位 : &= ~(value)
2. 设置位 : |= (value)
3. 翻转位 : ^= (value)
位操作解析
&= ~(3 << (0 * 2))
这行代码用于 清除 特定位置的位。
3 在二进制中是 11 ,表示要清除两个连续的位
<< (0 * 2) 表示左移0位,即从第0位开始0 * 2 是在计算位的起始位置
- 在STM32的GPIO MODER寄存器中,每个引脚占用2个位
- 第0个引脚(PA0)对应的位是第0位和第1位
- 第1个引脚(PA1)对应的位是第2位和第3位
- 所以对于引脚n,其在MODER寄存器中的起始位置是 n * 2
~ 是按位取反操作
&= 是按位与赋值操作
所以 &= ~(3 << (0 * 2)) 的作用是将第0位和第1位清零,而保持其他位不变。
基本操作示例:
// 读取寄存器
uint32_t value = REGISTER;
// 设置位
REGISTER |= (1 << BIT_POSITION);
// 清除位
REGISTER &= ~(1 << BIT_POSITION);
// 修改位
REGISTER = (REGISTER & ~(MASK << BIT_POSITION)) | (VALUE << BIT_POSITION);
2. 常用外设寄存器详解
2.1 GPIO寄存器详解
寄存器名称 | 功能描述 | 对应的 HAL 库函数 | 详细配置说明 |
---|---|---|---|
GPIOx_MODER | 配置 IO 口模式 | HAL_GPIO_Init() | 每个引脚占 2 位,00 = 输入,01 = 输出,10 = 复用,11 = 模拟 |
GPIOx_OTYPER | 配置输出类型 | HAL_GPIO_Init() | 0 = 推挽输出,1 = 开漏输出 |
GPIOx_OSPEEDR | 配置输出速度 | HAL_GPIO_Init() | 00 = 低速,01 = 中速,10 = 高速,11 = 超高速 |
GPIOx_PUPDR | 配置上拉 / 下拉 | HAL_GPIO_Init() | 00 = 无上下拉,01 = 上拉,10 = 下拉 |
GPIOx_IDR | 读取输入数据 | HAL_GPIO_ReadPin() | 只读寄存器,读取引脚电平状态 |
GPIOx_ODR | 写输出数据 | HAL_GPIO_WritePin() | 控制引脚输出高低电平 |
GPIOx_BSRR | 原子位设置 / 复位 | HAL_GPIO_WritePin() | 低 16 位置 1 设置对应引脚,高 16 位置 1 复位对应引脚 |
GPIOx_LCKR | 锁定配置 | HAL_GPIO_LockPin() | 锁定 GPIO 配置,防止意外修改 |
GPIOx_AFRL/AFRH | 复用功能配置 | HAL_GPIO_Init() | 选择引脚的复用功能,每个引脚占 4 位 |
GPIO寄存器操作详细示例:
// 初始化GPIO配置
void GPIO_Init(void) {
// 使能GPIOA时钟
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
// 配置PA0为输出模式
GPIOA->MODER &= ~(3 << (0 * 2)); // 清除原有设置
GPIOA->MODER |= (1 << (0 * 2)); // 设置为输出模式(01)
// 配置为推挽输出
GPIOA->OTYPER &= ~(1 << 0);
// 配置为高速输出
GPIOA->OSPEEDR &= ~(3 << (0 * 2));
GPIOA->OSPEEDR |= (2 << (0 * 2));
// 配置无上下拉
GPIOA->PUPDR &= ~(3 << (0 * 2));
// 配置PA1为输入模式(带上拉)
GPIOA->MODER &= ~(3 << (1 * 2)); // 输入模式(00)
GPIOA->PUPDR &= ~(3 << (1 * 2));
GPIOA->PUPDR |= (1 << (1 * 2)); // 上拉(01)
// 配置PA2为复用功能(USART2_TX)
GPIOA->MODER &= ~(3 << (2 * 2));
GPIOA->MODER |= (2 << (2 * 2)); // 复用功能(10)
GPIOA->AFR[0] &= ~(0xF << (2 * 4));
GPIOA->AFR[0] |= (7 << (2 * 4)); // AF7(USART2)
}
// GPIO输出操作
void GPIO_Output_Example(void) {
// 方法1:使用ODR寄存器
GPIOA->ODR |= (1 << 0); // PA0输出高电平
GPIOA->ODR &= ~(1 << 0); // PA0输出低电平
// 方法2:使用BSRR寄存器(原子操作,更安全)
GPIOA->BSRR = (1 << 0); // PA0输出高电平
GPIOA->BSRR = (1 << (0 + 16)); // PA0输出低电平
// 翻转引脚状态
GPIOA->ODR ^= (1 << 0);
}
// GPIO输入操作
uint8_t GPIO_Input_Example(void) {
// 读取PA1输入状态
if(GPIOA->IDR & (1 << 1)) {
return 1; // 高电平
} else {
return 0; // 低电平
}
}
2.2 RCC寄存器详解
寄存器名称 | 功能描述 | 对应的 HAL 库函数 | 详细配置说明 |
---|---|---|---|
RCC_CR | 时钟控制 | HAL_RCC_OscConfig() | 控制各种时钟源的开关和状态 |
RCC_PLLCFGR | PLL 配置 | HAL_RCC_OscConfig() | 配置 PLL 的倍频、分频参数 |
RCC_CFGR | 时钟配置 | HAL_RCC_ClockConfig() | 选择系统时钟源和配置各总线分频 |
RCC_CIR | 时钟中断 | HAL_RCC_NMI_IRQHandler() | 时钟就绪中断和标志 |
RCC_AHB1ENR | AHB1 外设时钟使能 | __HAL_RCC_GPIOx_CLK_ENABLE() | 控制 AHB1 总线上外设的时钟 |
RCC_AHB2ENR | AHB2 外设时钟使能 | __HAL_RCC_xxx_CLK_ENABLE() | 控制 AHB2 总线上外设的时钟 |
RCC_APB1ENR | APB1 外设时钟使能 | __HAL_RCC_xxx_CLK_ENABLE() | 控制 APB1 总线上外设的时钟 |
RCC_APB2ENR | APB2 外设时钟使能 | __HAL_RCC_xxx_CLK_ENABLE() | 控制 APB2 总线上外设的时钟 |
RCC_AHB1RSTR | AHB1 外设复位 | __HAL_RCC_xxx_FORCE_RESET() | 复位 AHB1 总线上的外设 |
RCC_APB1RSTR | APB1 外设复位 | __HAL_RCC_xxx_FORCE_RESET() | 复位 APB1 总线上的外设 |
RCC_APB2RSTR | APB2 外设复位 | __HAL_RCC_xxx_FORCE_RESET() | 复位 APB2 总线上的外设 |
寄存器 | 功能 | HAL库对应函数 | 详细说明 |
---|---|---|---|
RCC寄存器操作详细示例:
// 系统时钟配置(以STM32F4为例,配置为168MHz)
void SystemClock_Config(void) {
// 使能HSE
RCC->CR |= RCC_CR_HSEON;
while(!(RCC->CR & RCC_CR_HSERDY)); // 等待HSE稳定
// 配置PLL
RCC->PLLCFGR = 0; // 复位PLL配置
RCC->PLLCFGR |= (8 << 0); // PLLM=8
RCC->PLLCFGR |= (336 << 6); // PLLN=336
RCC->PLLCFGR |= (0 << 16); // PLLP=2(00)
RCC->PLLCFGR |= (7 << 24); // PLLQ=7
RCC->PLLCFGR |= RCC_PLLCFGR_PLLSRC_HSE; // PLL源为HSE
// 使能PLL
RCC->CR |= RCC_CR_PLLON;
while(!(RCC->CR & RCC_CR_PLLRDY)); // 等待PLL锁定
// 配置Flash等待周期和ART加速器
FLASH->ACR = FLASH_ACR_PRFTEN | FLASH_ACR_ICEN | FLASH_ACR_DCEN | FLASH_ACR_LATENCY_5WS;
// 配置总线分频
RCC->CFGR &= ~RCC_CFGR_HPRE; // AHB不分频
RCC->CFGR |= RCC_CFGR_PPRE1_DIV4; // APB1 4分频(42MHz)
RCC->CFGR |= RCC_CFGR_PPRE2_DIV2; // APB2 2分频(84MHz)
// 选择PLL作为系统时钟
RCC->CFGR &= ~RCC_CFGR_SW;
RCC->CFGR |= RCC_CFGR_SW_PLL;
while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL); // 等待PLL成为系统时钟
}
// 外设时钟使能
void Peripherals_Clock_Enable(void) {
// 使能GPIO时钟
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; // GPIOA
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN; // GPIOB
// 使能USART1时钟(APB2外设)
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
// 使能SPI1时钟(APB2外设)
RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
// 使能I2C1时钟(APB1外设)
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;
// 使能TIM2时钟(APB1外设)
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
// 使能DMA1时钟
RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN;
}
// 外设复位
void Peripheral_Reset(void) {
// 复位USART1
RCC->APB2RSTR |= RCC_APB2RSTR_USART1RST;
RCC->APB2RSTR &= ~RCC_APB2RSTR_USART1RST;
}
2.3 USART寄存器详解
寄存器名称 | 功能描述 | 对应的 HAL 库函数 | 详细配置说明 |
---|---|---|---|
USART_CR1 | 控制寄存器 1 | HAL_UART_Init() | 控制 USART 的基本功能,如使能、字长、奇偶校验等 |
USART_CR2 | 控制寄存器 2 | HAL_UART_Init() | 控制停止位、时钟等 |
USART_CR3 | 控制寄存器 3 | HAL_UART_Init() | 控制硬件流控、DMA 等 |
USART_BRR | 波特率寄存器 | HAL_UART_Init() | 设置波特率 |
USART_GTPR | 保护时间和预分频 | HAL_UART_Init() | 用于智能卡模式 |
USART_RTOR | 接收超时 | HAL_UART_Init() | 设置接收超时时间 |
USART_RQR | 请求寄存器 | HAL_UART_AbortReceive() | 请求特定操作 |
USART_ISR | 中断和状态 | HAL_UART_GetState() | 状态标志 (F7/L4 系列) |
USART_ICR | 中断清除 | HAL_UART_IRQHandler() | 清除中断标志 (F7/L4 系列) |
USART_RDR | 接收数据 | HAL_UART_Receive() | 接收数据寄存器 (F7/L4 系列) |
USART_TDR | 发送数据 | HAL_UART_Transmit() | 发送数据寄存器 (F7/L4 系列) |
USART_SR | 状态寄存器 | HAL_UART_GetState() | 状态标志 (F4/F1 系列) |
USART_DR | 数据寄存器 | HAL_UART_Transmit()/HAL_UART_Receive() | 收发数据 (F4/F1 系列) |
寄存器 | 功能 | HAL库对应函数 | 详细说明 |
---|---|---|---|
USART寄存器操作详细示例:
// USART初始化(以STM32F4为例,USART1配置为115200 8N1)
void USART1_Init(void) {
// 使能USART1和GPIOA时钟
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
// 配置PA9(TX)和PA10(RX)为复用功能
GPIOA->MODER &= ~(GPIO_MODER_MODER9_Msk | GPIO_MODER_MODER10_Msk);
GPIOA->MODER |= (GPIO_MODER_MODER9_1 | GPIO_MODER_MODER10_1); // 复用功能
// 配置为AF7(USART1)
GPIOA->AFR[1] &= ~(0xF << ((9-8)*4) | 0xF << ((10-8)*4));
GPIOA->AFR[1] |= (7 << ((9-8)*4)) | (7 << ((10-8)*4));
// 配置为高速
GPIOA->OSPEEDR |= (GPIO_OSPEEDER_OSPEEDR9_1 | GPIO_OSPEEDER_OSPEEDR10_1);
// 配置为推挽输出和上拉输入
GPIOA->OTYPER &= ~(GPIO_OTYPER_OT_9);
GPIOA->PUPDR &= ~(GPIO_PUPDR_PUPDR9_Msk | GPIO_PUPDR_PUPDR10_Msk);
GPIOA->PUPDR |= (GPIO_PUPDR_PUPDR9_0 | GPIO_PUPDR_PUPDR10_0); // 上拉
// 复位USART1
RCC->APB2RSTR |= RCC_APB2RSTR_USART1RST;
RCC->APB2RSTR &= ~RCC_APB2RSTR_USART1RST;
// 配置波特率(假设APB2时钟为84MHz)
// BRR = fCK / baud rate = 84000000/115200 = 729.16
USART1->BRR = 729;
// 配置USART1参数:8位数据,1位停止位,无校验,使能发送和接收
USART1->CR1 = 0; // 复位CR1
USART1->CR1 |= USART_CR1_TE | USART_CR1_RE; // 使能发送和接收
// 配置CR2和CR3
USART1->CR2 = 0; // 1个停止位
USART1->CR3 = 0; // 无硬件流控
// 使能USART1
USART1->CR1 |= USART_CR1_UE;
// 可选:使能接收中断
// USART1->CR1 |= USART_CR1_RXNEIE;
// NVIC_EnableIRQ(USART1_IRQn);
}
// USART发送一个字节
void USART1_SendByte(uint8_t data) {
// 等待发送缓冲区为空
while(!(USART1->SR & USART_SR_TXE));
// 发送数据
USART1->DR = data;
// 等待发送完成
while(!(USART1->SR & USART_SR_TC));
}
// USART发送字符串
void USART1_SendString(char *str) {
while(*str) {
USART1_SendByte(*str++);
}
}
// USART接收一个字节(阻塞方式)
uint8_t USART1_ReceiveByte(void) {
// 等待接收到数据
while(!(USART1->SR & USART_SR_RXNE));
// 读取并返回数据
return (uint8_t)USART1->DR;
}
// USART中断处理函数
void USART1_IRQHandler(void) {
if(USART1->SR & USART_SR_RXNE) {
// 接收到数据
uint8_t data = (uint8_t)USART1->DR;
// 处理接收到的数据...
// 回显
USART1_SendByte(data);
}
}
2.4 TIM定时器寄存器详解
寄存器名称 | 功能描述 | 对应的 HAL 库函数 | 详细配置说明 |
---|---|---|---|
TIM_CR1 | 控制寄存器 1 | HAL_TIM_Base_Init() | 控制计数器的基本功能 |
TIM_CR2 | 控制寄存器 2 | HAL_TIM_Base_Init() | 控制主从模式等 |
TIM_SMCR | 从模式控制 | HAL_TIM_SlaveConfigSynchro() | 配置从模式和触发源 |
TIM_DIER | 中断使能 | HAL_TIM_Base_Start_IT() | 使能各种中断和 DMA 请求 |
TIM_SR | 状态寄存器 | HAL_TIM_IRQHandler() | 各种状态标志 |
TIM_EGR | 事件生成 | HAL_TIM_GenerateEvent() | 软件生成事件 |
TIM_CCMR1 | 捕获 / 比较模式 1 | HAL_TIM_PWM_Init() | 配置通道 1 和 2 的模式 |
TIM_CCMR2 | 捕获 / 比较模式 2 | HAL_TIM_PWM_Init() | 配置通道 3 和 4 的模式 |
TIM_CCER | 捕获 / 比较使能 | HAL_TIM_PWM_Start() | 使能捕获 / 比较通道 |
TIM_CNT | 计数器 | __HAL_TIM_GET_COUNTER() | 当前计数值 |
TIM_PSC | 预分频器 | HAL_TIM_Base_Init() | 设置预分频值 |
TIM_ARR | 自动重装载 | HAL_TIM_Base_Init() | 设置计数器周期 |
TIM_CCR1 - 4 | 捕获 / 比较寄存器 | __HAL_TIM_SET_COMPARE() | 设置比较值或捕获值 |
TIM_DCR | DMA 控制 | HAL_TIM_DMABurst_WriteStart() | 配置 DMA 突发传输 |
TIM_DMAR | DMA 地址 | HAL_TIM_DMABurst_WriteStart() | DMA 访问的寄存器地址 |
寄存器 | 功能 | HAL库对应函数 | 详细说明 |
---|---|---|---|
TIM定时器寄存器操作详细示例:
// 基本定时器初始化(以TIM2为例,配置为1ms中断)
void TIM2_Init(void) {
// 使能TIM2时钟
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
// 配置TIM2
TIM2->PSC = 84-1; // 预分频,84MHz/84=1MHz
TIM2->ARR = 1000-1; // 自动重装载值,1MHz/1000=1KHz(1ms)
TIM2->CR1 = 0; // 复位CR1
TIM2->CR1 |= TIM_CR1_ARPE; // 使能ARR预装载
// 使能更新中断
TIM2->DIER |= TIM_DIER_UIE;
// 清除更新中断标志
TIM2->SR &= ~TIM_SR_UIF;
// 使能TIM2
TIM2->CR1 |= TIM_CR1_CEN;
// 使能TIM2中断
NVIC_EnableIRQ(TIM2_IRQn);
NVIC_SetPriority(TIM2_IRQn, 1);
}
// PWM输出初始化(以TIM3通道1为例)
void TIM3_PWM_Init(void) {
// 使能TIM3和GPIOA时钟
RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
// 配置PA6为TIM3_CH1
GPIOA->MODER &= ~GPIO_MODER_MODER6_Msk;
GPIOA->MODER |= GPIO_MODER_MODER6_1; // 复用功能
GPIOA->AFR[0] &= ~(0xF << (6 * 4));
GPIOA->AFR[0] |= (2 << (6 * 4)); // AF2(TIM3)
// 配置TIM3基本参数
TIM3->PSC = 84-1; // 预分频,84MHz/84=1MHz
TIM3->ARR = 1000-1; // PWM周期1ms
// 配置通道1为PWM模式1
TIM3->CCMR1 &= ~TIM_CCMR1_OC1M_Msk;
TIM3->CCMR1 |= (6 << TIM_CCMR1_OC1M_Pos); // PWM模式1
TIM3->CCMR1 |= TIM_CCMR1_OC1PE; // 使能预装载
// 设置PWM占空比(50%)
TIM3->CCR1 = 500;
// 使能通道1输出
TIM3->CCER |= TIM_CCER_CC1E;
// 使能TIM3
TIM3->CR1 |= TIM_CR1_ARPE; // 使能ARR预装载
TIM3->CR1 |= TIM_CR1_CEN; // 使能计数器
}
// 输入捕获初始化(以TIM4通道1为例)
void TIM4_Capture_Init(void) {
// 使能TIM4和GPIOB时钟
RCC->APB1ENR |= RCC_APB1ENR_TIM4EN;
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN;
// 配置PB6为TIM4_CH1
GPIOB->MODER &= ~GPIO_MODER_MODER6_Msk;
GPIOB->MODER |= GPIO_MODER_MODER6_1; // 复用功能
GPIOB->AFR[0] &= ~(0xF << (6 * 4));
GPIOB->AFR[0] |= (2 << (6 * 4)); // AF2(TIM4)
// 配置TIM4基本参数
TIM4->PSC = 84-1; // 预分频,84MHz/84=1MHz
TIM4->ARR = 0xFFFF; // 最大计数值
// 配置通道1为输入捕获
TIM4->CCMR1 &= ~TIM_CCMR1_CC1S_Msk;
TIM4->CCMR1 |= (1 << TIM_CCMR1_CC1S_Pos); // 输入捕获,映射到IC1
// 配置输入滤波和分频
TIM4->CCMR1 &= ~(TIM_CCMR1_IC1F_Msk | TIM_CCMR1_IC1PSC_Msk);
TIM4->CCMR1 |= (3 << TIM_CCMR1_IC1F_Pos); // 8个采样点滤波
// 配置上升沿触发
TIM4->CCER &= ~(TIM_CCER_CC1P | TIM_CCER_CC1NP);
// 使能通道1捕获
TIM4->CCER |= TIM_CCER_CC1E;
// 使能捕获中断
TIM4->DIER |= TIM_DIER_CC1IE;
// 使能TIM4
TIM4->CR1 |= TIM_CR1_CEN;
// 使能TIM4中断
NVIC_EnableIRQ(TIM4_IRQn);
NVIC_SetPriority(TIM4_IRQn, 2);
}
// TIM2中断处理函数
void TIM2_IRQHandler(void) {
if(TIM2->SR & TIM_SR_UIF) {
// 清除更新中断标志
TIM2->SR &= ~TIM_SR_UIF;
// 1ms定时中断处理...
// 例如:LED翻转
GPIOA->ODR ^= (1 << 0);
}
}
// TIM4中断处理函数(输入捕获)
void TIM4_IRQHandler(void) {
static uint16_t rising_edge = 0;
static uint16_t falling_edge = 0;
if(TIM4->SR & TIM_SR_CC1IF) {
if(TIM4->CCER & TIM_CCER_CC1P) {
// 下降沿捕获
falling_edge = TIM4->CCR1;
// 计算脉宽
uint16_t pulse_width;
if(falling_edge >= rising_edge) {
pulse_width = falling_edge - rising_edge;
} else {
pulse_width = (0xFFFF - rising_edge) + falling_edge + 1;
}
// 处理捕获结果...
// 切换为上升沿捕获
TIM4->CCER &= ~(TIM_CCER_CC1P | TIM_CCER_CC1NP);
} else {
// 上升沿捕获
rising_edge = TIM4->CCR1;
// 切换为下降沿捕获
TIM4->CCER |= TIM_CCER_CC1P;
TIM4->CCER &= ~TIM_CCER_CC1NP;
}
}
}
2.5 ADC寄存器详解
寄存器名称 | 功能 | HAL 库对应函数 | 详细说明 |
---|---|---|---|
ADC_SR | 状态寄存器 | HAL_ADC_PollForConversion() | 转换状态标志 |
ADC_CR1 | 控制寄存器 1 | HAL_ADC_Init() | 配置 ADC 工作模式、分辨率等 |
ADC_CR2 | 控制寄存器 2 | HAL_ADC_Init() | 配置触发源、数据对齐等 |
ADC_SMPR1/2 | 采样时间 | HAL_ADC_ConfigChannel() | 配置各通道采样时间 |
ADC_SQR1/2/3 | 规则序列 | HAL_ADC_ConfigChannel() | 配置规则转换序列 |
ADC_JSQR | 注入序列 | HAL_ADCEx_InjectedConfigChannel() | 配置注入转换序列 |
ADC_JDR1 - 4 | 注入数据 | HAL_ADCEx_InjectedGetValue() | 注入通道转换结果 |
ADC_DR | 数据寄存器 | HAL_ADC_GetValue() | 规则通道转换结果 |
ADC寄存器操作详细示例:
// ADC初始化(以ADC1为例)
void ADC1_Init(void) {
// 使能ADC1和GPIOA时钟
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
// 配置PA0为模拟输入
GPIOA->MODER |= GPIO_MODER_MODER0; // 模拟模式(11)
// 复位ADC1
RCC->APB2RSTR |= RCC_APB2RSTR_ADCRST;
RCC->APB2RSTR &= ~RCC_APB2RSTR_ADCRST;
// 配置ADC1
ADC1->CR1 = 0; // 复位CR1
ADC1->CR2 = 0; // 复位CR2
// 配置分辨率、扫描模式等
ADC1->CR1 &= ~ADC_CR1_RES; // 12位分辨率
ADC1->CR1 &= ~ADC_CR1_SCAN; // 禁用扫描模式
// 配置数据对齐、EOC等
ADC1->CR2 &= ~ADC_CR2_ALIGN; // 右对齐
ADC1->CR2 |= ADC_CR2_EOCS; // 每次转换结束产生EOC
// 配置通道0采样时间(通道0对应PA0)
ADC1->SMPR2 &= ~ADC_SMPR2_SMP0;
ADC1->SMPR2 |= (7 << ADC_SMPR2_SMP0_Pos); // 480周期(最长采样时间)
// 配置规则序列
ADC1->SQR1 &= ~ADC_SQR1_L; // 1个转换
ADC1->SQR3 &= ~ADC_SQR3_SQ1;
ADC1->SQR3 |= (0 << ADC_SQR3_SQ1_Pos); // 第1个转换为通道0
// 使能ADC1
ADC1->CR2 |= ADC_CR2_ADON;
// 等待ADC稳定
for(volatile uint32_t i = 0; i < 10000; i++);
}
// 单次ADC转换
uint16_t ADC1_GetValue(void) {
// 启动转换
ADC1->CR2 |= ADC_CR2_SWSTART;
// 等待转换完成
while(!(ADC1->SR & ADC_SR_EOC));
// 读取转换结果
return (uint16_t)ADC1->DR;
}
// 多通道ADC扫描模式示例
void ADC1_MultiChannel_Init(void) {
// 使能ADC1和GPIOA/GPIOB时钟
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN | RCC_AHB1ENR_GPIOBEN;
// 配置PA0, PA1, PB0为模拟输入
GPIOA->MODER |= GPIO_MODER_MODER0 | GPIO_MODER_MODER1;
GPIOB->MODER |= GPIO_MODER_MODER0;
// 配置ADC1
ADC1->CR1 = 0;
ADC1->CR2 = 0;
// 使能扫描模式
ADC1->CR1 |= ADC_CR1_SCAN;
// 配置DMA传输
ADC1->CR2 |= ADC_CR2_DMA;
ADC1->CR2 |= ADC_CR2_DDS; // DMA请求直到DMA禁用
// 配置通道采样时间
// 通道0(PA0)
ADC1->SMPR2 &= ~ADC_SMPR2_SMP0;
ADC1->SMPR2 |= (7 << ADC_SMPR2_SMP0_Pos);
// 通道1(PA1)
ADC1->SMPR2 &= ~ADC_SMPR2_SMP1;
ADC1->SMPR2 |= (7 << ADC_SMPR2_SMP1_Pos);
// 通道8(PB0)
ADC1->SMPR2 &= ~ADC_SMPR2_SMP8;
ADC1->SMPR2 |= (7 << ADC_SMPR2_SMP8_Pos);
// 配置规则序列(3个转换)
ADC1->SQR1 &= ~ADC_SQR1_L;
ADC1->SQR1 |= (2 << ADC_SQR1_L_Pos); // 3个转换(值为转换数-1)
// 配置转换顺序
ADC1->SQR3 &= ~(ADC_SQR3_SQ1 | ADC_SQR3_SQ2 | ADC_SQR3_SQ3);
ADC1->SQR3 |= (0 << ADC_SQR3_SQ1_Pos); // 第1个转换为通道0
ADC1->SQR3 |= (1 << ADC_SQR3_SQ2_Pos); // 第2个转换为通道1
ADC1->SQR3 |= (8 << ADC_SQR3_SQ3_Pos); // 第3个转换为通道8
// 使能ADC1
ADC1->CR2 |= ADC_CR2_ADON;
// 等待ADC稳定
for(volatile uint32_t i = 0; i < 10000; i++);
}
// 配置DMA传输ADC结果
void ADC1_DMA_Config(uint16_t *buffer, uint32_t length) {
// 使能DMA2时钟(ADC1使用DMA2 Stream0 Channel0)
RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN;
// 确保DMA2 Stream0禁用
DMA2_Stream0->CR &= ~DMA_SxCR_EN;
while(DMA2_Stream0->CR & DMA_SxCR_EN);
// 清除所有标志
DMA2->LIFCR = 0x0F7D0F7D;
// 配置DMA2 Stream0
DMA2_Stream0->PAR = (uint32_t)&ADC1->DR; // 外设地址
DMA2_Stream0->M0AR = (uint32_t)buffer; // 存储器地址
DMA2_Stream0->NDTR = length; // 数据项数
// 配置DMA传输参数
DMA2_Stream0->CR = 0;
DMA2_Stream0->CR |= (0 << DMA_SxCR_CHSEL_Pos); // 通道0
DMA2_Stream0->CR |= DMA_SxCR_PL_1; // 高优先级
DMA2_Stream0->CR |= DMA_SxCR_MSIZE_0; // 存储器数据宽度16位
DMA2_Stream0->CR |= DMA_SxCR_PSIZE_0; // 外设数据宽度16位
DMA2_Stream0->CR |= DMA_SxCR_MINC; // 存储器地址递增
DMA2_Stream0->CR |= DMA_SxCR_CIRC; // 循环模式
// 使能DMA2 Stream0
DMA2_Stream0->CR |= DMA_SxCR_EN;
}
// 启动ADC连续转换
void ADC1_Start_Conversion(void) {
// 配置为连续转换模式
ADC1->CR2 |= ADC_CR2_CONT;
// 启动转换
ADC1->CR2 |= ADC_CR2_SWSTART;
}
2.6 I2C寄存器详解
寄存器名称 | 功能 | HAL 库对应函数 | 详细说明 |
---|---|---|---|
I2C_CR1 | 控制寄存器 1 | HAL_I2C_Init() | 控制 I2C 的基本功能 |
I2C_CR2 | 控制寄存器 2 | HAL_I2C_Init() | 配置时钟和中断 |
I2C_OAR1 | 自身地址 1 | HAL_I2C_Init() | 设置自身地址 (7 位或 10 位) |
I2C_OAR2 | 自身地址 2 | HAL_I2C_Init() | 设置第二个地址 (7 位) |
I2C_DR | 数据寄存器 | HAL_I2C_Master_Transmit()/HAL_I2C_Master_Receive() | 发送 / 接收数据 |
I2C_SR1 | 状态寄存器 1 | HAL_I2C_GetState() | 状态标志 |
I2C_SR2 | 状态寄存器 2 | HAL_I2C_GetState() | 附加状态标志 |
I2C_CCR | 时钟控制 | HAL_I2C_Init() | 配置 SCL 时钟频率 |
I2C_TRISE | 上升时间 | HAL_I2C_Init() | 配置 SCL 上升时间 |
I2C寄存器操作详细示例:
// I2C初始化(以I2C1为例,配置为主模式,100KHz)
void I2C1_Init(void) {
// 使能I2C1和GPIOB时钟
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN;
// 配置PB6(SCL)和PB7(SDA)为复用功能
GPIOB->MODER &= ~(GPIO_MODER_MODER6_Msk | GPIO_MODER_MODER7_Msk);
GPIOB->MODER |= (GPIO_MODER_MODER6_1 | GPIO_MODER_MODER7_1); // 复用功能
// 配置为开漏输出
GPIOB->OTYPER |= (GPIO_OTYPER_OT_6 | GPIO_OTYPER_OT_7);
// 配置为上拉
GPIOB->PUPDR &= ~(GPIO_PUPDR_PUPDR6_Msk | GPIO_PUPDR_PUPDR7_Msk);
GPIOB->PUPDR |= (GPIO_PUPDR_PUPDR6_0 | GPIO_PUPDR_PUPDR7_0);
// 配置为高速
GPIOB->OSPEEDR |= (GPIO_OSPEEDER_OSPEEDR6_1 | GPIO_OSPEEDER_OSPEEDR7_1);
// 配置为AF4(I2C1)
GPIOB->AFR[0] &= ~(0xF << (6 * 4) | 0xF << (7 * 4));
GPIOB->AFR[0] |= (4 << (6 * 4)) | (4 << (7 * 4));
// 复位I2C1
RCC->APB1RSTR |= RCC_APB1RSTR_I2C1RST;
RCC->APB1RSTR &= ~RCC_APB1RSTR_I2C1RST;
// 禁用I2C1
I2C1->CR1 &= ~I2C_CR1_PE;
// 配置I2C时钟
I2C1->CR2 &= ~I2C_CR2_FREQ;
I2C1->CR2 |= 42; // APB1时钟频率(MHz),假设为42MHz
// 配置CCR(时钟控制)
I2C1->CCR = 0;
I2C1->CCR &= ~I2C_CCR_FS; // 标准模式(100KHz)
// CCR = Fpclk / (2 * Fi2c) = 42MHz / (2 * 100KHz) = 210
I2C1->CCR |= 210;
// 配置TRISE(上升时间)
// TRISE = (Tr / Tpclk) + 1 = (1000ns / (1/42MHz)) + 1 = 43
I2C1->TRISE = 43;
// 使能I2C1
I2C1->CR1 |= I2C_CR1_PE;
}
// I2C发送数据(主模式)
uint8_t I2C1_Write(uint8_t device_addr, uint8_t reg_addr, uint8_t *data, uint16_t len) {
// 等待I2C空闲
uint32_t timeout = 10000;
while(I2C1->SR2 & I2C_SR2_BUSY) {
if(--timeout == 0) return 1; // 超时错误
}
// 发送起始位
I2C1->CR1 |= I2C_CR1_START;
// 等待起始位发送完成
timeout = 10000;
while(!(I2C1->SR1 & I2C_SR1_SB)) {
if(--timeout == 0) return 2; // 超时错误
}
// 发送设备地址(写)
I2C1->DR = device_addr << 1;
// 等待地址发送完成
timeout = 10000;
while(!(I2C1->SR1 & I2C_SR1_ADDR)) {
if(--timeout == 0) return 3; // 超时错误
}
// 清除ADDR标志
uint32_t temp = I2C1->SR1;
temp = I2C1->SR2;
(void)temp;
// 发送寄存器地址
I2C1->DR = reg_addr;
// 等待数据发送完成
timeout = 10000;
while(!(I2C1->SR1 & I2C_SR1_TXE)) {
if(--timeout == 0) return 4; // 超时错误
}
// 发送数据
for(uint16_t i = 0; i < len; i++) {
I2C1->DR = data[i];
// 等待数据发送完成
timeout = 10000;
while(!(I2C1->SR1 & I2C_SR1_TXE)) {
if(--timeout == 0) return 5; // 超时错误
}
}
// 发送停止位
I2C1->CR1 |= I2C_CR1_STOP;
return 0; // 成功
}
// I2C接收数据(主模式)
uint8_t I2C1_Read(uint8_t device_addr, uint8_t reg_addr, uint8_t *data, uint16_t len) {
// 等待I2C空闲
uint32_t timeout = 10000;
while(I2C1->SR2 & I2C_SR2_BUSY) {
if(--timeout == 0) return 1; // 超时错误
}
// 发送起始位
I2C1->CR1 |= I2C_CR1_START;
// 等待起始位发送完成
timeout = 10000;
while(!(I2C1->SR1 & I2C_SR1_SB)) {
if(--timeout == 0) return 2; // 超时错误
}
// 发送设备地址(写)
I2C1->DR = device_addr << 1;
// 等待地址发送完成
timeout = 10000;
while(!(I2C1->SR1 & I2C_SR1_ADDR)) {
if(--timeout == 0) return 3; // 超时错误
}
// 清除ADDR标志
uint32_t temp = I2C1->SR1;
temp = I2C1->SR2;
(void)temp;
// 发送寄存器地址
I2C1->DR = reg_addr;
// 等待数据发送完成
timeout = 10000;
while(!(I2C1->SR1 & I2C_SR1_TXE)) {
if(--timeout == 0) return 4; // 超时错误
}
// 发送重复起始位
I2C1->CR1 |= I2C_CR1_START;
// 等待起始位发送完成
timeout = 10000;
while(!(I2C1->SR1 & I2C_SR1_SB)) {
if(--timeout == 0) return 5; // 超时错误
}
// 发送设备地址(读)
I2C1->DR = (device_addr << 1) | 0x01;
// 等待地址发送完成
timeout = 10000;
while(!(I2C1->SR1 & I2C_SR1_ADDR)) {
if(--timeout == 0) return 6; // 超时错误
}
// 根据接收数据长度配置ACK
if(len == 1) {
// 单字节接收,禁用ACK
I2C1->CR1 &= ~I2C_CR1_ACK;
} else {
// 多字节接收,使能ACK
I2C1->CR1 |= I2C_CR1_ACK;
}
// 清除ADDR标志
temp = I2C1->SR1;
temp = I2C1->SR2;
(void)temp;
// 接收数据
for(uint16_t i = 0; i < len; i++) {
if(i == len - 1) {
// 最后一个字节,禁用ACK
I2C1->CR1 &= ~I2C_CR1_ACK;
}
// 等待接收数据
timeout = 10000;
while(!(I2C1->SR1 & I2C_SR1_RXNE)) {
if(--timeout == 0) return 7; // 超时错误
}
// 读取数据
data[i] = I2C1->DR;
}
// 发送停止位
I2C1->CR1 |= I2C_CR1_STOP;
// 重新使能ACK
I2C1->CR1 |= I2C_CR1_ACK;
return 0; // 成功
}
2.7 SPI寄存器详解
寄存器 | 功能 | HAL 库对应函数 | 详细说明 |
---|---|---|---|
SPI_CR1 | 控制寄存器 1 | HAL_SPI_Init() | 控制 SPI 的基本功能 |
SPI_CR2 | 控制寄存器 2 | HAL_SPI_Init() | 控制中断和 DMA |
SPI_SR | 状态寄存器 | HAL_SPI_GetState() | 状态标志 |
SPI_DR | 数据寄存器 | HAL_SPI_Transmit()/HAL_SPI_Receive() | 发送 / 接收数据 |
SPI_CRCPR | CRC 多项式 | HAL_SPI_Init() | 设置 CRC 多项式 |
SPI_RXCRCR | 接收 CRC | HAL_SPI_GetCRCState() | 接收 CRC 值 |
SPI_TXCRCR | 发送 CRC | HAL_SPI_GetCRCState() | 发送 CRC 值 |
SPI_I2SCFGR | I2S 配置 | HAL_I2S_Init() | 配置 I2S 模式 |
SPI_I2SPR | I2S 预分频 | HAL_I2S_Init() | 配置 I2S 时钟 |
SPI寄存器操作详细示例:
// SPI初始化(以SPI1为例,配置为主模式)
void SPI1_Init(void) {
// 使能SPI1和GPIOA时钟
RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
// 配置PA5(SCK), PA6(MISO), PA7(MOSI)为复用功能
GPIOA->MODER &= ~(GPIO_MODER_MODER5_Msk | GPIO_MODER_MODER6_Msk | GPIO_MODER_MODER7_Msk);
GPIOA->MODER |= (GPIO_MODER_MODER5_1 | GPIO_MODER_MODER6_1 | GPIO_MODER_MODER7_1);
// 配置为AF5(SPI1)
GPIOA->AFR[0] &= ~(0xF << (5 * 4) | 0xF << (6 * 4) | 0xF << (7 * 4));
GPIOA->AFR[0] |= (5 << (5 * 4)) | (5 << (6 * 4)) | (5 << (7 * 4));
// 配置为高速
GPIOA->OSPEEDR |= (GPIO_OSPEEDER_OSPEEDR5_1 | GPIO_OSPEEDER_OSPEEDR6_1 | GPIO_OSPEEDER_OSPEEDR7_1);
// 配置PA5(SCK)和PA7(MOSI)为推挽输出,PA6(MISO)为上拉输入
GPIOA->OTYPER &= ~(GPIO_OTYPER_OT_5 | GPIO_OTYPER_OT_7);
GPIOA->PUPDR &= ~(GPIO_PUPDR_PUPDR5_Msk | GPIO_PUPDR_PUPDR6_Msk | GPIO_PUPDR_PUPDR7_Msk);
GPIOA->PUPDR |= GPIO_PUPDR_PUPDR6_0; // PA6上拉
// 配置PA4为推挽输出(软件NSS)
GPIOA->MODER &= ~GPIO_MODER_MODER4_Msk;
GPIOA->MODER |= GPIO_MODER_MODER4_0; // 输出模式
GPIOA->OTYPER &= ~GPIO_OTYPER_OT_4; // 推挽输出
GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR4_1; // 高速
GPIOA->BSRR = GPIO_BSRR_BS_4; // NSS高电平(不选中)
// 复位SPI1
RCC->APB2RSTR |= RCC_APB2RSTR_SPI1RST;
RCC->APB2RSTR &= ~RCC_APB2RSTR_SPI1RST;
// 配置SPI1
SPI1->CR1 = 0; // 复位CR1
// 配置为主模式,时钟极性和相位,波特率等
SPI1->CR1 |= SPI_CR1_MSTR; // 主模式
SPI1->CR1 &= ~SPI_CR1_CPOL; // 时钟极性CPOL=0
SPI1->CR1 &= ~SPI_CR1_CPHA; // 时钟相位CPHA=0
// 配置波特率为fPCLK/16 (84MHz/16=5.25MHz)
SPI1->CR1 |= (3 << SPI_CR1_BR_Pos);
// 配置为软件NSS管理
SPI1->CR1 |= SPI_CR1_SSM | SPI_CR1_SSI;
// 配置数据格式
SPI1->CR1 &= ~SPI_CR1_DFF; // 8位数据格式
SPI1->CR1 &= ~SPI_CR1_LSBFIRST; // MSB先传
// 使能SPI1
SPI1->CR1 |= SPI_CR1_SPE;
}
// SPI发送接收一个字节
uint8_t SPI1_TransmitReceive(uint8_t data) {
// 等待发送缓冲区为空
while(!(SPI1->SR & SPI_SR_TXE));
// 发送数据
SPI1->DR = data;
// 等待接收缓冲区非空
while(!(SPI1->SR & SPI_SR_RXNE));
// 读取接收到的数据
return SPI1->DR;
}
// SPI发送数据
void SPI1_Transmit(uint8_t *data, uint16_t size) {
// 选中从设备(NSS低电平)
GPIOA->BSRR = GPIO_BSRR_BR_4;
for(uint16_t i = 0; i < size; i++) {
// 等待发送缓冲区为空
while(!(SPI1->SR & SPI_SR_TXE));
// 发送数据
SPI1->DR = data[i];
// 等待传输完成
while(!(SPI1->SR & SPI_SR_RXNE));
// 读取接收到的数据(清除RXNE标志)
uint8_t temp = SPI1->DR;
(void)temp;
}
// 等待SPI不忙
while(SPI1->SR & SPI_SR_BSY);
// 取消选中从设备(NSS高电平)
GPIOA->BSRR = GPIO_BSRR_BS_4;
}
// SPI接收数据
void SPI1_Receive(uint8_t *data, uint16_t size) {
// 选中从设备(NSS低电平)
GPIOA->BSRR = GPIO_BSRR_BR_4;
for(uint16_t i = 0; i < size; i++) {
// 发送一个哑数据以产生时钟
SPI1->DR = 0xFF;
// 等待接收缓冲区非空
while(!(SPI1->SR & SPI_SR_RXNE));
// 读取接收到的数据
data[i] = SPI1->DR;
}
// 等待SPI不忙
while(SPI1->SR & SPI_SR_BSY);
// 取消选中从设备(NSS高电平)
GPIOA->BSRR = GPIO_BSRR_BS_4;
}
// SPI DMA传输配置
void SPI1_DMA_Config(void) {
// 使能DMA2时钟(SPI1使用DMA2)
RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN;
// 配置DMA2 Stream3(SPI1_TX)
DMA2_Stream3->CR = 0; // 复位CR
while(DMA2_Stream3->CR & DMA_SxCR_EN); // 等待DMA禁用
DMA2_Stream3->CR |= (3 << DMA_SxCR_CHSEL_Pos); // 通道3
DMA2_Stream3->CR |= DMA_SxCR_PL_1; // 高优先级
DMA2_Stream3->CR |= DMA_SxCR_MINC; // 存储器地址递增
DMA2_Stream3->CR &= ~DMA_SxCR_PSIZE; // 外设数据宽度8位
DMA2_Stream3->CR &= ~DMA_SxCR_MSIZE; // 存储器数据宽度8位
DMA2_Stream3->CR |= DMA_SxCR_DIR_0; // 存储器到外设
// 配置DMA2 Stream2(SPI1_RX)
DMA2_Stream2->CR = 0; // 复位CR
while(DMA2_Stream2->CR & DMA_SxCR_EN); // 等待DMA禁用
DMA2_Stream2->CR |= (3 << DMA_SxCR_CHSEL_Pos); // 通道3
DMA2_Stream2->CR |= DMA_SxCR_PL_0; // 中优先级
DMA2_Stream2->CR |= DMA_SxCR_MINC; // 存储器地址递增
DMA2_Stream2->CR &= ~DMA_SxCR_PSIZE; // 外设数据宽度8位
DMA2_Stream2->CR &= ~DMA_SxCR_MSIZE; // 存储器数据宽度8位
DMA2_Stream2->CR &= ~DMA_SxCR_DIR; // 外设到存储器
// 设置外设地址
DMA2_Stream3->PAR = (uint32_t)&SPI1->DR;
DMA2_Stream2->PAR = (uint32_t)&SPI1->DR;
// 使能SPI1的DMA请求
SPI1->CR2 |= SPI_CR2_TXDMAEN | SPI_CR2_RXDMAEN;
}
// SPI DMA传输
void SPI1_TransmitReceive_DMA(uint8_t *tx_data, uint8_t *rx_data, uint16_t size) {
// 设置DMA存储器地址和数据量
DMA2_Stream3->M0AR = (uint32_t)tx_data;
DMA2_Stream3->NDTR = size;
DMA2_Stream2->M0AR = (uint32_t)rx_data;
DMA2_Stream2->NDTR = size;
// 清除DMA标志
DMA2->LIFCR = DMA_LIFCR_CTCIF2 | DMA_LIFCR_CHTIF2 | DMA_LIFCR_CTEIF2 | DMA_LIFCR_CDMEIF2 | DMA_LIFCR_CFEIF2;
DMA2->LIFCR = DMA_LIFCR_CTCIF3 | DMA_LIFCR_CHTIF3 | DMA_LIFCR_CTEIF3 | DMA_LIFCR_CDMEIF3 | DMA_LIFCR_CFEIF3;
// 选中从设备(NSS低电平)
GPIOA->BSRR = GPIO_BSRR_BR_4;
// 使能DMA
DMA2_Stream2->CR |= DMA_SxCR_EN; // 先使能RX
DMA2_Stream3->CR |= DMA_SxCR_EN; // 再使能TX
// 等待传输完成
while(!(DMA2->LISR & DMA_LISR_TCIF2));
// 等待SPI不忙
while(SPI1->SR & SPI_SR_BSY);
// 取消选中从设备(NSS高电平)
GPIOA->BSRR = GPIO_BSRR_BS_4;
// 禁用DMA
DMA2_Stream3->CR &= ~DMA_SxCR_EN;
DMA2_Stream2->CR &= ~DMA_SxCR_EN;
}
2.8 DMA寄存器详解
寄存器 | 功能 | HAL 库对应函数 | 详细说明 |
---|---|---|---|
DMA_SxCR | 配置寄存器 | HAL_DMA_Init() | 配置 DMA 传输参数 |
DMA_SxNDTR | 数据量寄存器 | HAL_DMA_Start() | 设置传输数据项数 |
DMA_SxPAR | 外设地址 | HAL_DMA_Init() | 设置外设地址 |
DMA_SxM0AR | 存储器 0 地址 | HAL_DMA_Init() | 设置存储器地址 |
DMA_SxM1AR | 存储器 1 地址 | HAL_DMAEx_MultiBufferStart() | 双缓冲模式的第二个地址 |
DMA_SxFCR | FIFO 控制 | HAL_DMA_Init() | 配置 FIFO 阈值和直接模式 |
DMA_LISR/HISR | 低 / 高中断状态 | HAL_DMA_IRQHandler() | 中断状态标志 |
DMA_LIFCR/HIFCR | 低 / 高中断标志清除 | HAL_DMA_IRQHandler() | 清除中断标志 |
DMA寄存器操作详细示例:
// DMA内存到内存传输示例
void DMA_MemToMem_Transfer(uint32_t *src, uint32_t *dst, uint32_t size) {
// 使能DMA2时钟
RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN;
// 配置DMA2 Stream0
DMA2_Stream0->CR = 0; // 复位CR
while(DMA2_Stream0->CR & DMA_SxCR_EN); // 等待DMA禁用
// 配置为内存到内存模式
DMA2_Stream0->CR |= DMA_SxCR_DIR_0; // 方向:存储器到存储器
DMA2_Stream0->CR |= DMA_SxCR_MINC; // 存储器地址递增
DMA2_Stream0->CR |= DMA_SxCR_PINC; // 外设(这里是源内存)地址递增
DMA2_Stream0->CR |= DMA_SxCR_MSIZE_1; // 存储器数据宽度32位
DMA2_Stream0->CR |= DMA_SxCR_PSIZE_1; // 外设数据宽度32位
DMA2_Stream0->CR |= DMA_SxCR_PL_1; // 高优先级
DMA2_Stream0->CR |= DMA_SxCR_MEM2MEM; // 内存到内存模式
// 设置源地址、目标地址和数据量
DMA2_Stream0->PAR = (uint32_t)src; // 源地址
DMA2_Stream0->M0AR = (uint32_t)dst; // 目标地址
DMA2_Stream0->NDTR = size; // 数据项数
// 禁用FIFO模式(使用直接模式)
DMA2_Stream0->FCR &= ~DMA_SxFCR_DMDIS;
// 清除所有中断标志
DMA2->LIFCR = 0x0F7D0F7D;
// 使能DMA
DMA2_Stream0->CR |= DMA_SxCR_EN;
// 等待传输完成
while(!(DMA2->LISR & DMA_LISR_TCIF0));
// 清除传输完成标志
DMA2->LIFCR = DMA_LIFCR_CTCIF0;
// 禁用DMA
DMA2_Stream0->CR &= ~DMA_SxCR_EN;
}
// DMA双缓冲模式示例(以ADC1为例)
void DMA_DoubleBuffer_Config(uint16_t *buffer0, uint16_t *buffer1, uint16_t size) {
// 使能DMA2时钟
RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN;
// 配置DMA2 Stream0(ADC1)
DMA2_Stream0->CR = 0; // 复位CR
while(DMA2_Stream0->CR & DMA_SxCR_EN); // 等待DMA禁用
// 配置DMA参数
DMA2_Stream0->CR |= (0 << DMA_SxCR_CHSEL_Pos); // 通道0
DMA2_Stream0->CR &= ~DMA_SxCR_DIR; // 外设到存储器
DMA2_Stream0->CR |= DMA_SxCR_MINC; // 存储器地址递增
DMA2_Stream0->CR |= DMA_SxCR_PSIZE_0; // 外设数据宽度16位
DMA2_Stream0->CR |= DMA_SxCR_MSIZE_0; // 存储器数据宽度16位
DMA2_Stream0->CR |= DMA_SxCR_CIRC; // 循环模式
DMA2_Stream0->CR |= DMA_SxCR_PL_1; // 高优先级
// 使能双缓冲模式
DMA2_Stream0->CR |= DMA_SxCR_DBM;
// 设置外设地址和数据量
DMA2_Stream0->PAR = (uint32_t)&ADC1->DR;
DMA2_Stream0->NDTR = size;
// 设置两个存储器地址
DMA2_Stream0->M0AR = (uint32_t)buffer0;
DMA2_Stream0->M1AR = (uint32_t)buffer1;
// 清除所有中断标志
DMA2->LIFCR = 0x0F7D0F7D;
// 使能DMA传输完成中断
DMA2_Stream0->CR |= DMA_SxCR_TCIE;
// 使能DMA中断
NVIC_EnableIRQ(DMA2_Stream0_IRQn);
NVIC_SetPriority(DMA2_Stream0_IRQn, 2);
// 使能DMA
DMA2_Stream0->CR |= DMA_SxCR_EN;
}
// DMA中断处理函数
void DMA2_Stream0_IRQHandler(void) {
if(DMA2->LISR & DMA_LISR_TCIF0) {
// 清除传输完成标志
DMA2->LIFCR = DMA_LIFCR_CTCIF0;
// 检查当前使用的缓冲区
if(DMA2_Stream0->CR & DMA_SxCR_CT) {
// 当前使用的是存储器1,可以处理存储器0的数据
// 处理buffer0的数据...
} else {
// 当前使用的是存储器0,可以处理存储器1的数据
// 处理buffer1的数据...
}
}
}
2.9 EXTI寄存器详解
寄存器 | 功能 | HAL 库对应函数 | 详细说明 |
---|---|---|---|
EXTI_IMR | 中断屏蔽 | HAL_GPIO_Init() | 使能中断线 |
EXTI_EMR | 事件屏蔽 | HAL_GPIO_Init() | 使能事件线 |
EXTI_RTSR | 上升沿触发 | HAL_GPIO_Init() | 配置上升沿触发 |
EXTI_FTSR | 下降沿触发 | HAL_GPIO_Init() | 配置下降沿触发 |
EXTI_SWIER | 软件中断 | HAL_GPIO_EXTI_IRQHandler() | 软件触发中断 |
EXTI_PR | 挂起寄存器 | HAL_GPIO_EXTI_IRQHandler() | 中断挂起标志 |
EXTI寄存器操作详细示例:
// 外部中断初始化(以PA0为例,配置为下降沿触发)
void EXTI0_Init(void) {
// 使能GPIOA和SYSCFG时钟
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;
// 配置PA0为输入模式
GPIOA->MODER &= ~GPIO_MODER_MODER0;
// 配置为上拉输入
GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR0;
GPIOA->PUPDR |= GPIO_PUPDR_PUPDR0_0;
// 配置EXTI线路映射
SYSCFG->EXTICR[0] &= ~SYSCFG_EXTICR1_EXTI0; // 清除EXTI0设置
SYSCFG->EXTICR[0] |= SYSCFG_EXTICR1_EXTI0_PA; // 选择PA0作为EXTI0
// 配置EXTI线0
EXTI->IMR |= EXTI_IMR_MR0; // 使能中断
EXTI->EMR &= ~EXTI_EMR_MR0; // 禁用事件
EXTI->RTSR &= ~EXTI_RTSR_TR0; // 禁用上升沿触发
EXTI->FTSR |= EXTI_FTSR_TR0; // 使能下降沿触发
// 清除挂起标志
EXTI->PR = EXTI_PR_PR0;
// 配置NVIC
NVIC_SetPriority(EXTI0_IRQn, 3);
NVIC_EnableIRQ(EXTI0_IRQn);
}
// 外部中断处理函数
void EXTI0_IRQHandler(void) {
// 检查是否是EXTI0中断
if(EXTI->PR & EXTI_PR_PR0) {
// 清除挂起标志
EXTI->PR = EXTI_PR_PR0;
// 中断处理...
// 例如:LED翻转
GPIOC->ODR ^= GPIO_ODR_OD13;
// 延时消抖
for(volatile uint32_t i = 0; i < 100000; i++);
}
}
// 配置多个外部中断
void EXTI_MultiLine_Init(void) {
// 使能GPIO和SYSCFG时钟
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN | RCC_AHB1ENR_GPIOBEN;
RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;
// 配置PA1和PB2为输入模式
GPIOA->MODER &= ~GPIO_MODER_MODER1;
GPIOB->MODER &= ~GPIO_MODER_MODER2;
// 配置为上拉输入
GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR1;
GPIOA->PUPDR |= GPIO_PUPDR_PUPDR1_0;
GPIOB->PUPDR &= ~GPIO_PUPDR_PUPDR2;
GPIOB->PUPDR |= GPIO_PUPDR_PUPDR2_0;
// 配置EXTI线路映射
// PA1 -> EXTI1
SYSCFG->EXTICR[0] &= ~SYSCFG_EXTICR1_EXTI1;
SYSCFG->EXTICR[0] |= SYSCFG_EXTICR1_EXTI1_PA;
// PB2 -> EXTI2
SYSCFG->EXTICR[0] &= ~SYSCFG_EXTICR1_EXTI2;
SYSCFG->EXTICR[0] |= SYSCFG_EXTICR1_EXTI2_PB;
// 配置EXTI线1和线2
// EXTI1: 上升沿触发
EXTI->IMR |= EXTI_IMR_MR1;
EXTI->RTSR |= EXTI_RTSR_TR1;
EXTI->FTSR &= ~EXTI_FTSR_TR1;
// EXTI2: 下降沿触发
EXTI->IMR |= EXTI_IMR_MR2;
EXTI->RTSR &= ~EXTI_RTSR_TR2;
EXTI->FTSR |= EXTI_FTSR_TR2;
// 清除挂起标志
EXTI->PR = EXTI_PR_PR1 | EXTI_PR_PR2;
// 配置NVIC
NVIC_SetPriority(EXTI1_IRQn, 3);
NVIC_EnableIRQ(EXTI1_IRQn);
NVIC_SetPriority(EXTI2_IRQn, 3);
NVIC_EnableIRQ(EXTI2_IRQn);
}
// EXTI1中断处理函数
void EXTI1_IRQHandler(void) {
if(EXTI->PR & EXTI_PR_PR1) {
EXTI->PR = EXTI_PR_PR1; // 清除挂起标志
// 处理EXTI1中断...
}
}
// EXTI2中断处理函数
void EXTI2_IRQHandler(void) {
if(EXTI->PR & EXTI_PR_PR2) {
EXTI->PR = EXTI_PR_PR2; // 清除挂起标志
// 处理EXTI2中断...
}
}
3. 寄存器开发进阶技巧
3.1 位带操作
STM32的位带操作是一种高效的位操作技术,可以将位操作转换为地址操作,提高效率。
// 位带操作宏定义
#define PERIPH_BASE 0x40000000
#define PERIPH_BB_BASE 0x42000000
#define SRAM_BASE 0x20000000
#define SRAM_BB_BASE 0x22000000
// 外设位带操作
#define PERIPH_BB(addr, bit) \
(*(volatile uint32_t *)(PERIPH_BB_BASE + (((uint32_t)(addr) - PERIPH_BASE) * 32) + ((bit) * 4)))
// SRAM位带操作
#define SRAM_BB(addr, bit) \
(*(volatile uint32_t *)(SRAM_BB_BASE + (((uint32_t)(addr) - SRAM_BASE) * 32) + ((bit) * 4)))
// 使用示例
void BitBand_Example(void) {
// 使用位带操作设置GPIOC的第13位(PC13)
PERIPH_BB(&GPIOC->ODR, 13) = 1; // 等价于 GPIOC->ODR |= (1 << 13);
PERIPH_BB(&GPIOC->ODR, 13) = 0; // 等价于 GPIOC->ODR &= ~(1 << 13);
// 使用位带操作读取GPIOA的第0位(PA0)
uint32_t pin_state = PERIPH_BB(&GPIOA->IDR, 0); // 等价于 (GPIOA->IDR & (1 << 0)) ? 1 : 0;
// SRAM位带操作示例
uint32_t flag = 0;
SRAM_BB(&flag, 0) = 1; // 设置flag的第0位
SRAM_BB(&flag, 1) = 1; // 设置flag的第1位
}
3.2 原子操作
在多线程或中断环境中,原子操作可以避免竞态条件。
// 使用LDREX和STREX指令实现原子操作
uint32_t Atomic_Increment(uint32_t *addr) {
uint32_t tmp;
uint32_t status;
do {
// 加载独占
tmp = __LDREXW(addr);
// 尝试存储独占
status = __STREXW(tmp + 1, addr);
} while(status != 0); // 如果存储失败,重试
return tmp + 1;
}
// 使用位带实现原子位操作
void Atomic_BitSet(volatile uint32_t *addr, uint32_t bit) {
PERIPH_BB(addr, bit) = 1;
}
void Atomic_BitClear(volatile uint32_t *addr, uint32_t bit) {
PERIPH_BB(addr, bit) = 0;
}
3.3 寄存器操作优化技巧
// 使用临时变量减少寄存器访问次数
void Register_Optimization(void) {
// 不优化的方式
GPIOA->MODER &= ~(3 << (0 * 2));
GPIOA->MODER |= (1 << (0 * 2));
GPIOA->OTYPER &= ~(1 << 0);
GPIOA->OSPEEDR &= ~(3 << (0 * 2));
GPIOA->OSPEEDR |= (2 << (0 * 2));
// 优化后的方式
uint32_t temp_moder = GPIOA->MODER;
uint32_t temp_ospeedr = GPIOA->OSPEEDR;
temp_moder &= ~(3 << (0 * 2));
temp_moder |= (1 << (0 * 2));
temp_ospeedr &= ~(3 << (0 * 2));
temp_ospeedr |= (2 << (0 * 2));
GPIOA->MODER = temp_moder;
GPIOA->OTYPER &= ~(1 << 0);
GPIOA->OSPEEDR = temp_ospeedr;
}
// 使用位掩码简化位操作
#define GPIO_PIN_0 ((uint16_t)0x0001)
#define GPIO_PIN_SET 1
#define GPIO_PIN_RESET 0
void GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, uint8_t PinState) {
if(PinState != GPIO_PIN_RESET) {
GPIOx->BSRR = GPIO_Pin;
} else {
GPIOx->BSRR = (uint32_t)GPIO_Pin << 16;
}
}
5. 实用寄存器开发模板
5.1 系统初始化模板
// 系统时钟配置(以STM32F4为例,配置为168MHz)
void SystemClock_Config(void) {
// 使能HSE
RCC->CR |= RCC_CR_HSEON;
while(!(RCC->CR & RCC_CR_HSERDY));
// 配置电源控制
RCC->APB1ENR |= RCC_APB1ENR_PWREN;
PWR->CR |= PWR_CR_VOS; // 电压调节器输出选择Scale1模式
// 配置Flash预取、指令缓存、数据缓存和等待周期
FLASH->ACR = FLASH_ACR_ICEN | FLASH_ACR_DCEN | FLASH_ACR_PRFTEN | FLASH_ACR_LATENCY_5WS;
// 配置PLL
RCC->PLLCFGR = RCC_PLLCFGR_PLLSRC_HSE | // PLL源为HSE
(8 << RCC_PLLCFGR_PLLM_Pos) | // PLLM=8
(336 << RCC_PLLCFGR_PLLN_Pos) | // PLLN=336
(0 << RCC_PLLCFGR_PLLP_Pos) | // PLLP=2
(7 << RCC_PLLCFGR_PLLQ_Pos); // PLLQ=7
// 使能PLL
RCC->CR |= RCC_CR_PLLON;
while(!(RCC->CR & RCC_CR_PLLRDY));
// 配置总线分频
RCC->CFGR = (0 << RCC_CFGR_HPRE_Pos) | // AHB不分频
(5 << RCC_CFGR_PPRE1_Pos) | // APB1 4分频(42MHz)
(4 << RCC_CFGR_PPRE2_Pos); // APB2 2分频(84MHz)
// 选择PLL作为系统时钟
RCC->CFGR |= RCC_CFGR_SW_PLL;
while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL);
// 更新系统时钟变量
SystemCoreClock = 168000000;
}
// 系统初始化
void System_Init(void) {
// 配置系统时钟
SystemClock_Config();
// 使能所需外设时钟
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN | RCC_AHB1ENR_GPIOBEN |
RCC_AHB1ENR_GPIOCEN | RCC_AHB1ENR_GPIODEN;
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN | RCC_APB1ENR_TIM3EN |
RCC_APB1ENR_I2C1EN;
RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN | RCC_APB2ENR_SPI1EN |
RCC_APB2ENR_USART1EN | RCC_APB2ENR_ADC1EN;
// 配置SysTick
SysTick_Config(SystemCoreClock / 1000); // 1ms中断
// 配置NVIC优先级分组
NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
}
5.2 GPIO通用模板
// GPIO初始化
void GPIO_Init(void) {
// LED初始化(PC13)
GPIOC->MODER &= ~GPIO_MODER_MODER13_Msk;
GPIOC->MODER |= GPIO_MODER_MODER13_0; // 输出模式
GPIOC->OTYPER &= ~GPIO_OTYPER_OT_13; // 推挽输出
GPIOC->OSPEEDR &= ~GPIO_OSPEEDER_OSPEEDR13_Msk;
GPIOC->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR13_0; // 中速
// 按键初始化(PA0)
GPIOA->MODER &= ~GPIO_MODER_MODER0_Msk; // 输入模式
GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR0_Msk;
GPIOA->PUPDR |= GPIO_PUPDR_PUPDR0_1; // 下拉
// USART1引脚初始化(PA9/PA10)
GPIOA->MODER &= ~(GPIO_MODER_MODER9_Msk | GPIO_MODER_MODER10_Msk);
GPIOA->MODER |= (GPIO_MODER_MODER9_1 | GPIO_MODER_MODER10_1); // 复用功能
GPIOA->AFR[1] &= ~(0xF << ((9-8)*4) | 0xF << ((10-8)*4));
GPIOA->AFR[1] |= (7 << ((9-8)*4)) | (7 << ((10-8)*4)); // AF7(USART1)
GPIOA->OSPEEDR |= (GPIO_OSPEEDER_OSPEEDR9_1 | GPIO_OSPEEDER_OSPEEDR10_1); // 高速
GPIOA->PUPDR &= ~(GPIO_PUPDR_PUPDR9_Msk | GPIO_PUPDR_PUPDR10_Msk);
GPIOA->PUPDR |= (GPIO_PUPDR_PUPDR9_0 | GPIO_PUPDR_PUPDR10_0); // 上拉
}
// GPIO操作宏定义
#define LED_ON() GPIOC->BSRR = GPIO_BSRR_BR_13
#define LED_OFF() GPIOC->BSRR = GPIO_BSRR_BS_13
#define LED_TOGGLE() GPIOC->ODR ^= GPIO_ODR_OD13
#define KEY_STATE() ((GPIOA->IDR & GPIO_IDR_ID0) ? 1 : 0)
5.3 USART通用模板
// USART1初始化(115200 8N1)
void USART1_Init(void) {
// 使能USART1时钟
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
// 配置波特率(假设APB2时钟为84MHz)
// BRR = fCK / baud rate = 84000000/115200 = 729.16
USART1->BRR = 729;
// 配置USART1参数:8位数据,1位停止位,无校验,使能发送和接收
USART1->CR1 = 0; // 复位CR1
USART1->CR1 |= USART_CR1_TE | USART_CR1_RE; // 使能发送和接收
// 配置CR2和CR3
USART1->CR2 = 0; // 1个停止位
USART1->CR3 = 0; // 无硬件流控
// 使能USART1
USART1->CR1 |= USART_CR1_UE;
// 可选:使能接收中断
// USART1->CR1 |= USART_CR1_RXNEIE;
// NVIC_EnableIRQ(USART1_IRQn);
}
// USART1发送一个字节
void USART1_SendByte(uint8_t data) {
// 等待发送缓冲区为空
while(!(USART1->SR & USART_SR_TXE));
// 发送数据
USART1->DR = data;
}
// USART1发送字符串
void USART1_SendString(char *str) {
while(*str) {
USART1_SendByte(*str++);
}
}
// USART1发送数据(带长度)
void USART1_SendData(uint8_t *data, uint16_t len) {
for(uint16_t i = 0; i < len; i++) {
USART1_SendByte(data[i]);
}
}
// USART1接收一个字节(阻塞方式)
uint8_t USART1_ReceiveByte(void) {
// 等待接收到数据
while(!(USART1->SR & USART_SR_RXNE));
// 读取并返回数据
return (uint8_t)USART1->DR;
}
// USART1中断处理函数
void USART1_IRQHandler(void) {
if(USART1->SR & USART_SR_RXNE) {
// 接收到数据
uint8_t data = (uint8_t)USART1->DR;
// 处理接收到的数据...
// 回显
USART1_SendByte(data);
}
}
5.4 定时器通用模板
// 基本定时器初始化(TIM2, 1ms中断)
void TIM2_Init(void) {
// 使能TIM2时钟
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
// 配置TIM2
TIM2->PSC = 84-1; // 预分频,84MHz/84=1MHz
TIM2->ARR = 1000-1; // 自动重装载值,1MHz/1000=1KHz(1ms)
TIM2->CR1 = 0; // 复位CR1
TIM2->CR1 |= TIM_CR1_ARPE; // 使能ARR预装载
// 使能更新中断
TIM2->DIER |= TIM_DIER_UIE;
// 清除更新中断标志
TIM2->SR &= ~TIM_SR_UIF;
// 使能TIM2
TIM2->CR1 |= TIM_CR1_CEN;
// 使能TIM2中断
NVIC_EnableIRQ(TIM2_IRQn);
NVIC_SetPriority(TIM2_IRQn, 1);
}
// PWM输出初始化(TIM3通道1, PA6)
void TIM3_PWM_Init(void) {
// 使能TIM3和GPIOA时钟
RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
// 配置PA6为TIM3_CH1
GPIOA->MODER &= ~GPIO_MODER_MODER6_Msk;
GPIOA->MODER |= GPIO_MODER_MODER6_1; // 复用功能
GPIOA->AFR[0] &= ~(0xF << (6 * 4));
GPIOA->AFR[0] |= (2 << (6 * 4)); // AF2(TIM3)
// 配置TIM3基本参数
TIM3->PSC = 84-1; // 预分频,84MHz/84=1MHz
TIM3->ARR = 1000-1; // PWM周期1ms
// 配置通道1为PWM模式1
TIM3->CCMR1 &= ~TIM_CCMR1_OC1M_Msk;
TIM3->CCMR1 |= (6 << TIM_CCMR1_OC1M_Pos); // PWM模式1
TIM3->CCMR1 |= TIM_CCMR1_OC1PE; // 使能预装载
// 设置PWM占空比(50%)
TIM3->CCR1 = 500;
// 使能通道1输出
TIM3->CCER |= TIM_CCER_CC1E;
// 使能TIM3
TIM3->CR1 |= TIM_CR1_ARPE; // 使能ARR预装载
TIM3->CR1 |= TIM_CR1_CEN; // 使能计数器
}
// 设置PWM占空比(0-100%)
void TIM3_SetPWM_DutyCycle(uint8_t channel, uint8_t duty) {
uint32_t pulse = ((uint32_t)duty * (TIM3->ARR + 1)) / 100;
switch(channel) {
case 1:
TIM3->CCR1 = pulse;
break;
case 2:
TIM3->CCR2 = pulse;
break;
case 3:
TIM3->CCR3 = pulse;
break;
case 4:
TIM3->CCR4 = pulse;
break;
}
}
// TIM2中断处理函数
void TIM2_IRQHandler(void) {
if(TIM2->SR & TIM_SR_UIF) {
// 清除更新中断标志
TIM2->SR &= ~TIM_SR_UIF;
// 1ms定时中断处理...
// 例如:LED翻转
static uint16_t counter = 0;
counter++;
if(counter >= 500) { // 500ms
counter = 0;
LED_TOGGLE();
}
}
}
5.5 ADC通用模板
// ADC1初始化(单通道模式)
void ADC1_Init(void) {
// 使能ADC1和GPIOA时钟
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
// 配置PA1为模拟输入(ADC1_IN1)
GPIOA->MODER |= GPIO_MODER_MODER1; // 模拟模式(11)
// 复位ADC1
RCC->APB2RSTR |= RCC_APB2RSTR_ADCRST;
RCC->APB2RSTR &= ~RCC_APB2RSTR_ADCRST;
// 配置ADC1
ADC1->CR1 = 0; // 复位CR1
ADC1->CR2 = 0; // 复位CR2
// 配置分辨率、扫描模式等
ADC1->CR1 &= ~ADC_CR1_RES; // 12位分辨率
ADC1->CR1 &= ~ADC_CR1_SCAN; // 禁用扫描模式
// 配置数据对齐、EOC等
ADC1->CR2 &= ~ADC_CR2_ALIGN; // 右对齐
ADC1->CR2 |= ADC_CR2_EOCS; // 每次转换结束产生EOC
// 配置通道1采样时间(通道1对应PA1)
ADC1->SMPR2 &= ~ADC_SMPR2_SMP1;
ADC1->SMPR2 |= (7 << ADC_SMPR2_SMP1_Pos); // 480周期(最长采样时间)
// 配置规则序列
ADC1->SQR1 &= ~ADC_SQR1_L; // 1个转换
ADC1->SQR3 &= ~ADC_SQR3_SQ1;
ADC1->SQR3 |= (1 << ADC_SQR3_SQ1_Pos); // 第1个转换为通道1
// 使能ADC1
ADC1->CR2 |= ADC_CR2_ADON;
// 等待ADC稳定
for(volatile uint32_t i = 0; i < 10000; i++);
}
// 获取ADC转换结果
uint16_t ADC1_GetValue(void) {
// 启动转换
ADC1->CR2 |= ADC_CR2_SWSTART;
// 等待转换完成
while(!(ADC1->SR & ADC_SR_EOC));
// 读取转换结果
return (uint16_t)ADC1->DR;
}
// 获取多次ADC采样的平均值
uint16_t ADC1_GetAverageValue(uint8_t times) {
uint32_t sum = 0;
for(uint8_t i = 0; i < times; i++) {
sum += ADC1_GetValue();
}
return sum / times;
}
5.6 I2C通用模板
// I2C1初始化(主模式,100KHz)
void I2C1_Init(void) {
// 使能I2C1和GPIOB时钟
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN;
// 配置PB6(SCL)和PB7(SDA)为复用功能
GPIOB->MODER &= ~(GPIO_MODER_MODER6_Msk | GPIO_MODER_MODER7_Msk);
GPIOB->MODER |= (GPIO_MODER_MODER6_1 | GPIO_MODER_MODER7_1); // 复用功能
// 配置为开漏输出
GPIOB->OTYPER |= (GPIO_OTYPER_OT_6 | GPIO_OTYPER_OT_7);
// 配置为上拉
GPIOB->PUPDR &= ~(GPIO_PUPDR_PUPDR6_Msk | GPIO_PUPDR_PUPDR7_Msk);
GPIOB->PUPDR |= (GPIO_PUPDR_PUPDR6_0 | GPIO_PUPDR_PUPDR7_0);
// 配置为高速
GPIOB->OSPEEDR |= (GPIO_OSPEEDER_OSPEEDR6_1 | GPIO_OSPEEDER_OSPEEDR7_1);
// 配置为AF4(I2C1)
GPIOB->AFR[0] &= ~(0xF << (6 * 4) | 0xF << (7 * 4));
GPIOB->AFR[0] |= (4 << (6 * 4)) | (4 << (7 * 4));
// 复位I2C1
RCC->APB1RSTR |= RCC_APB1RSTR_I2C1RST;
RCC->APB1RSTR &= ~RCC_APB1RSTR_I2C1RST;
// 禁用I2C1
I2C1->CR1 &= ~I2C_CR1_PE;
// 配置I2C时钟
I2C1->CR2 &= ~I2C_CR2_FREQ;
I2C1->CR2 |= 42; // APB1时钟频率(MHz),假设为42MHz
// 配置CCR(时钟控制)
I2C1->CCR = 0;
I2C1->CCR &= ~I2C_CCR_FS; // 标准模式(100KHz)
// CCR = Fpclk / (2 * Fi2c) = 42MHz / (2 * 100KHz) = 210
I2C1->CCR |= 210;
// 配置TRISE(上升时间)
// TRISE = (Tr / Tpclk) + 1 = (1000ns / (1/42MHz)) + 1 = 43
I2C1->TRISE = 43;
// 使能I2C1
I2C1->CR1 |= I2C_CR1_PE;
}
// I2C写入数据
uint8_t I2C1_Write(uint8_t device_addr, uint8_t reg_addr, uint8_t *data, uint16_t len) {
// 等待I2C空闲
uint32_t timeout = 10000;
while(I2C1->SR2 & I2C_SR2_BUSY) {
if(--timeout == 0) return 1; // 超时错误
}
// 发送起始位
I2C1->CR1 |= I2C_CR1_START;
// 等待起始位发送完成
timeout = 10000;
while(!(I2C1->SR1 & I2C_SR1_SB)) {
if(--timeout == 0) return 2; // 超时错误
}
// 发送设备地址(写)
I2C1->DR = device_addr << 1;
// 等待地址发送完成
timeout = 10000;
while(!(I2C1->SR1 & I2C_SR1_ADDR)) {
if(--timeout == 0) return 3; // 超时错误
}
// 清除ADDR标志
uint32_t temp = I2C1->SR1;
temp = I2C1->SR2;
(void)temp;
// 发送寄存器地址
I2C1->DR = reg_addr;
// 等待数据发送完成
timeout = 10000;
while(!(I2C1->SR1 & I2C_SR1_TXE)) {
if(--timeout == 0) return 4; // 超时错误
}
// 发送数据
for(uint16_t i = 0; i < len; i++) {
I2C1->DR = data[i];
// 等待数据发送完成
timeout = 10000;
while(!(I2C1->SR1 & I2C_SR1_TXE)) {
if(--timeout == 0) return 5; // 超时错误
}
}
// 发送停止位
I2C1->CR1 |= I2C_CR1_STOP;
return 0; // 成功
}
// I2C读取数据
uint8_t I2C1_Read(uint8_t device_addr, uint8_t reg_addr, uint8_t *data, uint16_t len) {
// 等待I2C空闲
uint32_t timeout = 10000;
while(I2C1->SR2 & I2C_SR2_BUSY) {
if(--timeout == 0) return 1; // 超时错误
}
// 发送起始位
I2C1->CR1 |= I2C_CR1_START;
// 等待起始位发送完成
timeout = 10000;
while(!(I2C1->SR1 & I2C_SR1_SB)) {
if(--timeout == 0) return 2; // 超时错误
}
// 发送设备地址(写)
I2C1->DR = device_addr << 1;
// 等待地址发送完成
timeout = 10000;
while(!(I2C1->SR1 & I2C_SR1_ADDR)) {
if(--timeout == 0) return 3; // 超时错误
}
// 清除ADDR标志
uint32_t temp = I2C1->SR1;
temp = I2C1->SR2;
(void)temp;
// 发送寄存器地址
I2C1->DR = reg_addr;
// 等待数据发送完成
timeout = 10000;
while(!(I2C1->SR1 & I2C_SR1_TXE)) {
if(--timeout == 0) return 4; // 超时错误
}
// 发送重复起始位
I2C1->CR1 |= I2C_CR1_START;
// 等待起始位发送完成
timeout = 10000;
while(!(I2C1->SR1 & I2C_SR1_SB)) {
if(--timeout == 0) return 5; // 超时错误
}
// 发送设备地址(读)
I2C1->DR = (device_addr << 1) | 0x01;
// 等待地址发送完成
timeout = 10000;
while(!(I2C1->SR1 & I2C_SR1_ADDR)) {
if(--timeout == 0) return 6; // 超时错误
}
// 根据接收数据长度配置ACK
if(len == 1) {
// 单字节接收,禁用ACK
I2C1->CR1 &= ~I2C_CR1_ACK;
} else {
// 多字节接收,使能ACK
I2C1->CR1 |= I2C_CR1_ACK;
}
// 清除ADDR标志
temp = I2C1->SR1;
temp = I2C1->SR2;
(void)temp;
// 接收数据
for(uint16_t i = 0; i < len; i++) {
if(i == len - 1) {
// 最后一个字节,禁用ACK
I2C1->CR1 &= ~I2C_CR1_ACK;
}
// 等待接收数据
timeout = 10000;
while(!(I2C1->SR1 & I2C_SR1_RXNE)) {
if(--timeout == 0) return 7; // 超时错误
}
// 读取数据
data[i] = I2C1->DR;
}
// 发送停止位
I2C1->CR1 |= I2C_CR1_STOP;
// 重新使能ACK
I2C1->CR1 |= I2C_CR1_ACK;
return 0; // 成功
}