用Cubemx和HAL库开发stm32的一些注意事项

简述

主要记录一些常用的配置和选项,如果发现错误,欢迎在评论区讨论。

STM32CubeMX生成F1的工程中造成 下载器无法下载 问题的解决方案

Mculover666的博客

Cubemx的RCC时钟源配置

在用cube配置时钟时,有下面两个选项:
在这里插入图片描述

  • BYPASS Clock Source(旁路时钟源)
    指无需使用外部晶体时所需的芯片内部时钟驱动组件,直接从外界导入时钟信号。犹如芯片内部的驱动组件被旁路了。
  • Crystal/Ceramic Resonator(晶体/陶瓷晶振)(外部晶振)
    该时钟源是由外部无源晶体与MCU内部时钟驱动电路共同配合形成,有一定的启动时间,精度较高。

其中HSE是硬件原理图上的OSC_IN和OSC_OUT接的晶振
在这里插入图片描述
LSE是硬件原理图上的OSC32_IN和OSC32_OUT接的晶振
在这里插入图片描述

配置时钟树

时钟树要参考硬件是采用内部时钟还是外部晶振,然后根据芯片数据手册或参考手册配置想要的频率,不可以超过手册上要求的最大频率。
RM C板时钟树
上图为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模式(内存到内存)不支持双缓冲区。双缓冲一定要设置成循环模式。
  1. 设置双缓冲
    方法一:
    使用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()中的用户代码处

  2. 串口中断中判断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库双缓冲区

经过试验,使用HAL_DMAEx_MultiBufferStart_IT()函数仍然需要在中断中更换缓冲区。 之前说错了,使能双缓冲区bit确实会自动切换缓冲区,因为C板遥控器例程有点bug,所以判断错误。

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

猜你喜欢

转载自blog.csdn.net/lqysgdb/article/details/112966705