简述
主要记录一些常用的配置和选项,如果发现错误,欢迎在评论区讨论。
STM32CubeMX生成F1的工程中造成 下载器无法下载 问题的解决方案
Cubemx的RCC时钟源配置
在用cube配置时钟时,有下面两个选项:
- BYPASS Clock Source(旁路时钟源)
指无需使用外部晶体时所需的芯片内部时钟驱动组件,直接从外界导入时钟信号。犹如芯片内部的驱动组件被旁路了。 - Crystal/Ceramic Resonator(晶体/陶瓷晶振)(外部晶振)
该时钟源是由外部无源晶体与MCU内部时钟驱动电路共同配合形成,有一定的启动时间,精度较高。
其中HSE是硬件原理图上的OSC_IN和OSC_OUT
接的晶振
LSE是硬件原理图上的OSC32_IN和OSC32_OUT
接的晶振
配置时钟树
时钟树要参考硬件是采用内部时钟还是外部晶振,然后根据芯片数据手册或参考手册配置想要的频率,不可以超过手册上要求的最大频率。
上图为Robomaster官方C版例程时钟树
Cubemx配置GPIO引脚
-
GPIO output level : 默认输出电平
-
GPIO mode:
Output push pull 开漏
Ootput open drain 推挽
理解开漏和推挽输出 -
GPIO Pull-up/Pull-down:上下拉电阻,需要根据硬件配置。
-
Maximum output speed:
GPIO_SPEED_LOW; (2) 2MHz
GPIO_SPEED_MEDIUM; (25) 12.5MHz ~ 50MHz
GPIO_SPEED_FAST; (50) 25MHz ~ 100 MHz
GPIO_SPEED_HIGH; (100) 50MHz ~ 200MHz
UART串口配置
- Mode:
Asynchronous 异步通信
Synchronous 同步通信
其他模式一般用不到
UART串口中断
需要重定义中断回调函数
UART串口DMA模式
-
Mode:
Normal:普通模式,需要不断使能发送和接收
Circular:循环模式 -
Data Width:DMA一次传输的数据
-
Increment Address:DMA传输完一个Data Width的数据后地址是否增加
串口DMA使能宏定义:
__HAL_DMA_ENABLE,__HAL_DMA_ENABLE_IT。
串口DMA中断回调函数:
串口收发同时使用DMA
串口收发可以同时用DMA功能,但是需要两个不同的DMA通道,串口和DMA通道之间有对应关系,查看参考手册。
DMA通道
DMA有多个通道,虽然每个通道可以接收多个外设的请求,但是同一时间只能接收一个,不能同时接收多个。
cubemx配置DMA会自动使能DMA全局中断,但最好还是自己用_HAL_DMA_ENABLE_IT 再使能一次。
如果在循环模式下每次接受相同长度的数据,可以设置DMA_SxNDTR寄存器。保险起见,也可以每次重新设置。可以用来手动检查一帧数据传输有没有数据长度错误。设置前记得失能DMA
串口空闲接收+DMA双缓冲区
- 串口接收完一帧数据(一帧数据由多个字节组成,自己设置一次接收多少数据),
IDLE
位会置1。 - 双缓冲区会在接收完一帧数据后(DMA 传输结束时)切换目标存储区。这样如果在处理上一帧数据时,这时候接收新的一帧数据就不会覆盖原来的数据。
- 这里的数据都是定长数据。
- DMA的MTM模式(内存到内存)不支持双缓冲区。双缓冲一定要设置成循环模式。
-
设置双缓冲
方法一:
使用HAL库函数HAL_DMAEx_MultiBufferStart_IT()
,然后在中断函数中判断当前缓冲区。(可能有些芯片不支持这个HAL库函数,比如f103)。仍然需要使能DMA。方法二:
寄存器设置双缓冲区模式,然后在中断函数中判断当前缓冲区。这里以USART3为例,记得修改DMA流和uart寄存器。//enable the DMA transfer for the receiver request //使能DMA串口接收 SET_BIT(huart3.Instance->CR3, USART_CR3_DMAR); //enalbe idle interrupt //使能空闲中断 __HAL_UART_ENABLE_IT(&huart3, UART_IT_IDLE); //disable DMA //失效DMA __HAL_DMA_DISABLE(&hdma_usart3_rx); while(hdma_usart3_rx.Instance->CR & DMA_SxCR_EN) { __HAL_DMA_DISABLE(&hdma_usart3_rx); } //外设寄存器 hdma_usart3_rx.Instance->PAR = (uint32_t) & (USART3->DR); //memory buffer 1 //内存缓冲区1 hdma_usart3_rx.Instance->M0AR = (uint32_t)(rx1_buf); //memory buffer 2 //内存缓冲区2 hdma_usart3_rx.Instance->M1AR = (uint32_t)(rx2_buf); //data length //一帧的数据长度 hdma_usart3_rx.Instance->NDTR = dma_buf_num; //enable double memory buffer //使能双缓冲区 SET_BIT(hdma_usart3_rx.Instance->CR, DMA_SxCR_DBM); //enable DMA //使能DMA __HAL_DMA_ENABLE(&hdma_usart3_rx);
上面两种方法可以加在
HAL_UART_MspInit()中的用户代码处
-
串口中断中判断
CT
位
先判断现在的缓存区是Memory0还是Memory1,如果是0则处理1中的数据,如果是1则处理0中的信息,处理时不会影响另一个缓冲区进行数据的接收:
//串口中断
void USART3_IRQHandler(void)
{
if(huart3.Instance->SR & UART_FLAG_RXNE)//接收到数据
{
__HAL_UART_CLEAR_PEFLAG(&huart3);//清除校验错误中断位,如果使能对应中断会进入
}
else if(USART3->SR & UART_FLAG_IDLE)//检测到总线空闲
{
static uint16_t this_time_rx_len = 0;
__HAL_UART_CLEAR_PEFLAG(&huart3);//清除校验错误中断位,如果使能对应中断会进入
if ((hdma_usart3_rx.Instance->CR & DMA_SxCR_CT) == RESET)//DMA_SxCR寄存器的CT位为0,处理缓冲区1数据
{
/* Current memory buffer used is Memory 0 */
//disable DMA
//失效DMA
__HAL_DMA_DISABLE(&hdma_usart3_rx);
//get receive data length, length = set_data_length - remain_length
//获取接收数据长度,长度 = 设定长度 - 剩余长度
this_time_rx_len = SBUS_RX_BUF_NUM - hdma_usart3_rx.Instance->NDTR;
//reset set_data_lenght
//重新设定数据长度
hdma_usart3_rx.Instance->NDTR = SBUS_RX_BUF_NUM;
//set memory buffer 1
//设定缓冲区1
hdma_usart3_rx.Instance->CR |= DMA_SxCR_CT;
//enable DMA
//使能DMA
__HAL_DMA_ENABLE(&hdma_usart3_rx);
if(this_time_rx_len == RC_FRAME_LENGTH)
{
sbus_to_rc(sbus_rx_buf[1], &rc_ctrl);
}
}
else //当前缓冲区为1,处理缓冲区,0数据
{
/* Current memory buffer used is Memory 1 */
//disable DMA
//失效DMA
__HAL_DMA_DISABLE(&hdma_usart3_rx);
//get receive data length, length = set_data_length - remain_length
//获取接收数据长度,长度 = 设定长度 - 剩余长度
this_time_rx_len = SBUS_RX_BUF_NUM - hdma_usart3_rx.Instance->NDTR;
//reset set_data_lenght
//重新设定数据长度
hdma_usart3_rx.Instance->NDTR = SBUS_RX_BUF_NUM;
//set memory buffer 0
//设定缓冲区0
hdma_usart1_rx.Instance->CR &= ~(DMA_SxCR_CT);
// 或者改为 DMA1_Stream1->CR &= ~(DMA_SxCR_CT);
//如果不是usart3,记得修改DMA流
//enable DMA
//使能DMA
__HAL_DMA_ENABLE(&hdma_usart3_rx);
if(this_time_rx_len == RC_FRAME_LENGTH)
{
//处理遥控器数据
sbus_to_rc(sbus_rx_buf[0], &rc_ctrl);
}
}
}
}
这里使用了空闲中断,需要提前使能空闲中断。
可以参考HAL库双缓冲区
经过试验,使用 之前说错了,使能双缓冲区bit确实会自动切换缓冲区,因为C板遥控器例程有点bug,所以判断错误。HAL_DMAEx_MultiBufferStart_IT()
函数仍然需要在中断中更换缓冲区。
IIC
半双工
常用HAL库函数:
HAL_I2C_Mem_Read ()和HAL_I2C_Mem_Write ()等,参考HAL库帮助文档
SPI
全双工
常用HAL库函数:
HAL_SPI_Transmit ()
IIC和SPI主要用于数据量不大的通信,实际驱动不同的外设可能还要进一步封装。
CAN
TIM
- 主从模式,很少用
-
Internal Clock:内部时钟,即时钟源
-
ETR2 外部触发输入(ETR)(仅适用TIM2,3,4)
-
PSC:预分频器,时钟源经该预分频器才是定时器时钟。定时器时钟频率等于 时钟源频率/(psc+1)
-
Counter Mode:向上、下、三种中心对齐计数。基本定时器只能向上计数。
-
ARR:自动重装载值,定时器周期 T = (ARR+1) * (1/定时器时钟频率) = (ARR+1)*(PSC+1) / 72M
-
Internal clock division : 时钟分频因子 ,配置死区时间时需要用到。
-
auto-reload-preload:自动重装载)使能
-
TRGO Parameters 触发输出 (TRGO)
要使定时器开始工作,调用HAL_TIM_Base_Start()库函数。
要使用定时中断,调用 HAL_TIM_Base_Start_IT()库函数。
PSC和ARR都要先减1,因为根据参考手册,寄存器的值是设置的值加1
TIM_PWM
- Pulse:设置占空比,设置CCR寄存器的值。占空比P = CCR/(ARR+1)
TIM_ENCODER
只有TIM1和TIM2有编码器模式
- Encoder mode : T1 and T2 //编码器在TI1 TI2上升沿捕获,TI1上升沿根据TI2电平高低加减计数器,TI2上升沿根据TI1电平高低加减计数器
Polarity : Rising
//TIM_EncoderMode参数是模式,单相计数(只能反映速度)还是两相计数(速度和方向)
//TI1和TI2边沿处均计数,至于是向上还是向下取决于正转还是反转
//捕获发生在IC1和IC2的上升沿
编码器模式具体看手册
ADC/DAC
- ADC_mode: 只使用一个ADC配置为独立模式,使用多个ADC(ADC1/2/3)配置为
- Data Alignment: 数据左/右对齐
- Scan Conversion Mode: 扫描模式,使用多个通道时要开启
- Continuous Conversion Mode: 连续转换/单次转换,配置为单次转换时只转换一次数据就停止,要重新触发
- Enable Regular Conversions: 常规转换,
- Enable Injected Conversions: 注入转换
ADC开始前要使能:
HAL_ADC_Start 或 HAL_ADC_Start_IT