【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中断回调函数:
![](https://img-blog.csdnimg.cn/direct/6e1151e863574b2cb299321b4126dc83.png)
串口收发同时使用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