【STM32】STM32F429 HAL库开发注意事项

【STM32】STM32F429 HAL库开发注意事项

STM32的HAL库有哪些坑?

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

====================================

【STM32】STM32F429 HAL库开发注意事项

  https://www.cnblogs.com/ZYQS/p/14783426.html

正点原子F429开发板,HAL库

注意

        1、如果将全部库文件都引入,会出现error: l6200e: symbol hal_mspdeinit multiply defined,此处是stm32f1xx_hal_msp_template.c和stm32f1xx_hal_msp.c重复定义了HAL_MspDeInit和HAL_MspInit 函数,此时要么不引入,要么把其中一个的注释掉

        2、编译后报warning,stm32f4xx_ll_usb.c(785): warning:  #3108-d,点Option for target  /  C/C++ ,将C99 Mode和GNU extensions取消勾选即可解决

        3、用CubeMX生成模板,首先注意CubeMX版本和固件版本,如果CubeMX版本比固件版本新太多可能会编译报错,所以建议直接最新。。。生成以后编译通过了,Download到板子上,却发现程序并没有运行,查了半天错,翻腾版本,排除各方原因后才发现终端没有弹出application running ...,CubeMX生成的Keil模板似乎没有默认将Target Option-Debug-Settings-Flash Download的Download Function(即Download键的功能)中Reset and Run勾上。。。所以其实要么勾上这个,要么重启板子,就可以看到运行结果

        4、按键下降沿触发外部中断,记得设置GPIO上拉。。。

        5、当需要引用.lib文件时,注意勾选Option/Target/Use MicroLIB,否则将找不到.lib文件

        6、针对正点原子的sys.c,在将代码移植到非keil的ide,如IAR或者基于arm-gcc-none-eabi交叉编译器的环境时,第九十行开始会报错

 

//THUMB指令不支持汇编内联
//采用如下方法实现执行汇编指令WFI  
__asm void WFI_SET(void)
{
    WFI;          
}
//关闭所有中断(但是不包括fault和NMI中断)
__asm void INTX_DISABLE(void)
{
    CPSID   I
    BX      LR      
}
//开启所有中断
__asm void INTX_ENABLE(void)
{
    CPSIE   I
    BX      LR  
}
//设置栈顶地址
//addr:栈顶地址
__asm void MSR_MSP(u32 addr)
{
    MSR MSP, r0             //set Main Stack value
    BX r14
}

               这是针对于keil不支持汇编内联做出的修改,但这么写在其他地方会报错,改为如下代码即可

//THUMB指令不支持汇编内联
//采用如下方法实现执行汇编指令WFI  
void WFI_SET(void)
{
    asm("WFI");          
}
//关闭所有中断(但是不包括fault和NMI中断)
void INTX_DISABLE(void)
{
    asm("CPSID   I");
    asm("BX      LR");      
}
//开启所有中断
void INTX_ENABLE(void)
{
    asm("CPSIE   I");
    asm("BX      LR");  
}
//设置栈顶地址
//addr:栈顶地址
void MSR_MSP(u32 addr)
{
    asm("MSR MSP, r0");             //set Main Stack value
    asm("BX r14");
}

        7、使用CubeMX生成TIM的PWM Channel时注意,先指定IO,再配置通道,且生成后MspInit里GPIO特性需要按需修改

        8、切记切记,使用DMA方式的ADC时,注意要先调用DMA的Init,后调用ADC的Init!!!否则程序将卡死


STM32的HAL库有哪些坑?

  https://zhuanlan.zhihu.com/p/631405842

STM32是一款广泛使用的嵌入式微控制器,HAL库是STMicroelectronics官方提供的一种开发工具,用于编写STM32的应用程序。虽然HAL库功能全面,但是使用过程中也存在一些坑点,下面将介绍其中的一些。

一、版本兼容问题

HAL库的版本与开发环境的兼容性非常重要。在升级或降级HAL库时,需要保持兼容性。一旦版本不兼容,就会出现编译出错,调试失败的情况。

二、HAL库的启动时钟配置问题

HAL库的时钟源默认为内部时钟,如果开发者需要使用外部时钟,就需要重新配置时钟源。但是在进行时钟源配置时,开发者需要了解时钟的配置方法,否则将出现时钟频率不准确等问题。

三、中断优先级问题

在使用HAL库时,中断优先级的配置非常重要。需要注意每个中断的优先级设置,以避免优先级覆盖的问题。当多个中断同时触发时,会按照优先级排序,优先级较低的中断将无法执行。

四、DMA传输问题

HAL库支持DMA传输,能够提高数据传输的效率。但是,在使用DMA进行数据传输时,需要注意传输缓冲区的大小和DMA传输的模式设置。如果缓冲区大小设置过小,就会导致数据溢出;如果DMA模式设置错误,就会出现数据传输失败的情况。

五、外设配置问题

HAL库封装了很多外设的配置函数,但是需要开发者了解每个外设的配置方法。例如,使用串口时,开发者需要配置波特率、数据位、校验位、停止位等参数,如果参数设置错误,就会导致串口通信失败。

六、代码的可读性和可维护性问题

在使用HAL库时,开发者需要编写一定量的代码。代码的可读性和可维护性对于代码的质量至关重要。因此,开发者需要遵循代码编写规范,注释清楚每个函数的用途和参数,同时,代码的模块化和可重用性也是很重要的。

以上就是STM32的HAL库存在的一些坑,希望这些问题能帮助开发者更好地使用HAL库。

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

  https://blog.csdn.net/lqysgdb/article/details/112966705

简述

主要记录一些常用的配置和选项,如果发现错误,欢迎在评论区讨论。
STM32CubeMX生成F1的工程中造成 下载器无法下载 问题的解决方案

Mculover666的博客
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 USART1_IRQHandler(void)
{
    HAL_UART_IRQHandler(&huart1);
    if(huart1.Instance->SR & UART_FLAG_RXNE)//接收到数据
    {
        __HAL_UART_CLEAR_PEFLAG(&huart1);
    }
    else if(USART1->SR & UART_FLAG_IDLE)
    {
        static uint16_t this_time_rx_len = 0;

        __HAL_UART_CLEAR_PEFLAG(&huart1);

        if ((hdma_usart1_rx.Instance->CR & DMA_SxCR_CT) == RESET)
        {
            /* Current memory buffer used is Memory 0 */
    
            //disable DMA
            //失效DMA
            __HAL_DMA_DISABLE(&hdma_usart1_rx);

            //get receive data length, length = set_data_length - remain_length
            //获取接收数据长度,长度 = 设定长度 - 剩余长度
            this_time_rx_len = RX_BUF_NUM - hdma_usart1_rx.Instance->NDTR;

            //reset set_data_lenght
            //重新设定数据长度
            hdma_usart1_rx.Instance->NDTR = RX_BUF_NUM;

            //set memory buffer 1
            //设定缓冲区1
            hdma_usart1_rx.Instance->CR |= DMA_SxCR_CT;
            
            //enable DMA
            //使能DMA
            __HAL_DMA_ENABLE(&hdma_usart1_rx);

//            if(this_time_rx_len == RC_FRAME_LENGTH)
//            {
//                sbus_to_rc(rx_buf[0], &rc_ctrl);
//            }
        }
        else
        {
            /* Current memory buffer used is Memory 1 */
            //disable DMA
            //失效DMA
            __HAL_DMA_DISABLE(&hdma_usart1_rx);

            //get receive data length, length = set_data_length - remain_length
            //获取接收数据长度,长度 = 设定长度 - 剩余长度
            this_time_rx_len = RX_BUF_NUM - hdma_usart1_rx.Instance->NDTR;

            //reset set_data_lenght
            //重新设定数据长度
            hdma_usart1_rx.Instance->NDTR = RX_BUF_NUM;

            //set memory buffer 0
            //设定缓冲区0
            DMA1_Stream1->CR &= ~(DMA_SxCR_CT);
            
            //enable DMA
            //使能DMA
            __HAL_DMA_ENABLE(&hdma_usart1_rx);

//            if(this_time_rx_len == RC_FRAME_LENGTH)
//            {
//                //处理遥控器数据
//                sbus_to_rc(rx_buf[1], &rc_ctrl);
//            }
        }
    }
}

如果接受数据填满缓冲区,会自动切换缓冲区。这里设置了双倍缓冲区肯定填不满,所以需要手动切换缓冲区。

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

只有CH1和CH2有编码器模式

    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

————————————————
版权声明:本文为CSDN博主「Enterprise0」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/lqysgdb/article/details/112966705

一、C语言开发及编译文件

程序开发的目的一般是生成一个可以烧写到MCU内部Flash的文件,CPU上电后,CPU的硬件取指系统会自动的读取并执行Flash中的指令序列。程序一般是用C进行编辑。

C编程的基本策略是,用程序把源代码文件转化为可执行文件(其中包含可直接执行的机器语言代码)。典型的C实现通过编译和链接两个步骤来完成。源文件(汇编源文件.s和C源文件.c\.h)经过编译器编译后生成中间目标文件(.obj),每个源文件会对应一个同名的obj文件,这些obj文件中的程序地址及变量地址是未定位的。之后,由链接器负责将这些.obj文件链接为一个完整的目标文件(如.axf文件),这个.axf文件包含CPU可执行的二进制代码和一些必要的调试信息。最后借助于IDE将axf文件转化为可直接烧录的hex文件。

C语言使用这种分而治之的方法方便对程序进行模块化,可以独立编译单独的模块,稍后再用链接器合并已编译的模块,另外,链接器还将你编写的程序和预编译的库代码合并。

 

二、局部变量、全局变量、堆、堆栈、静态和全局

一个由C/C++编译的程序占用的内存分为以下几个部分
(1)栈区(stack)— 由编译器自动分配释放,存放函数的参数值,局部变量的值等,其操作方式类似于数据结构中的栈。
(2)堆区(heap) — 由程序员分配和释放,若程序员不释放,程序结束时可能由操作系统回收。分配方式类似于数据结构中的链表。
(3)全局区(静态区)(static)—全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量、静态变量在相邻的另一块区域。程序结束后由系统自动释放。
(4)文字常量区 — 常量字符串就是放在这里的。
(5)程序代码区 — 存放函数体的二进制代码。

注: 堆一般是由低地址往上(高地址)增长,栈一般是由高地址向下(低地址)增长。都是连续的。C语言不提供内存保护机制类似的功能,如果堆一直增长,栈一直申请,然后就会导致栈溢出,程序崩溃。


堆栈(stack)是内存中的一个连续的块。一个叫堆栈指针的寄存器(SP)指向堆栈的栈顶。堆栈的底部是一个固定地址。堆栈有一个特点就是,后进先出。也就是说,后放入的数据第一个取出。
堆(heap)是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。

三、设置堆栈空间的大小

在使用STM32编程时,一般情况下我们不会关注堆栈空间的大小,因为在STM32的启动文件中,已经帮我们预先设置好了堆栈空间的大小。如下图所示的启动代码中,Stack栈的大小为:0x00000400(1024Byte),Heap堆的大小为:0x00000200(512Byte)。

 堆栈的空间均匀分配在RAM中,可在编译的map文件中查看RAM资源的占用情况

map文件的内容可分为如下几部分:

    1、节区的跨文件引用(Section Cross References)
    2、删除无用节区(Removing Unused input sections from the image)
    3、符号映像表(Image Symbol Table (Local Symbols Global Symbols)
    4、存储器映像索引(Memory Map of the image)
    5、映像组件大小(Image component sizes)

我们大多数关注的是印象组件大小,本次例程内容如下,

全部编译完输出显示

Code 代表执行的代码,程序中所有的函数都位于此处,表示程序所占用FLASH的大小。

RO-data 代表只读数据,程序中所定义的全局常量数据和字符串都位于此处,表示程序定义的常量。

RW-data 代表已初始化的读写数据,程序中定义并且初始化的全局变量和静态变量位于此处。

ZI-data 代表未初始化的读写数据,程序中定义了但没有初始化的全局变量和静态变量位于此处

简单的说就是在烧写的时候是FLASH中的被占用的空间为:Code+ RO Data + RW Data = ROM

程序运行的时候,芯片内部RAM使用的空间为:              RW Data + ZI Data = RAM

 
————————————————
版权声明:本文为CSDN博主「Taqingjie」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Taqingjie/article/details/112793607

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值