目录
4.2 HAL_UART_Transmit_IT发送的数据与预期不相符
1 内容说明
本文主要讲述作者开发过程中的简单经验,遇到的问题及解决办法。
2 从何处开始
由指定的STM32数据手册开始(Data Sheet),里面描述了芯片的全貌和其他参考手册的信息。
3 参考官方样例代码
STM32CubeMX会自动帮助我们生成大量代码,同时它还下载了大量的官方样例代码供我们参考,通过以下方式找到这些代码,
STM32CubeMX菜单栏->Help->Update Settings->Repository Folder即为存放代码的路径。
4 串口收发数据
4.1 处理串口接收时会改变发送缓冲区的内容
8位数据为,有奇偶检验位,1位停止位时会出现串口接收时会改变发送缓冲区的内容,主要是HAL_UART_Receive接口中出现16位数据的处理,造成处理最后一个接收字节时多处理了一个字节(即改变了其他内存值)。解决办法即在此情况下接收缓冲区要多开一个字节的大小。CubuMx软件的列子中也可以看出接收缓冲区多开了一个字节。只接收一个字节(即缓冲区长度为1)的时候也要注意这个问题。
笔者使用UART4,未开串口中断,只用普通的发送接收功能,在发送完4个字节之后再阻塞接收4个字节。
此时接收缓冲区大小为5,解决“串口接收时会改变发送缓冲区的内容”的问题。
4.2 HAL_UART_Transmit_IT发送的数据与预期不相符
使用HAL_UART_Transmit_IT函数发送多字节时,要注意数据缓冲区的生命周期,防止传送的数据缓冲区变量在数据被传送完之前就被意外改变。笔者遇到过该函数的第二个参数为局部变量,接收端只有第一个字节为预期的数据,之后的数据全为0x0,将第二个参数改为全局变量解决了这个问题。
4.3 使用串口中断发送数据时进入死循环
使用中断发送时需要实现中断处理函数,如果在STM32CubeMX中使能了串口中断则会在stm32f1xx_it.c(stm32f1xx为芯片型号,依实际情况而变)自动生成中断处理函数,如果在STM32CubeMX中没有使能串口中断,则需要在代码中手动打开中断并设置优先级,同时还要实现中断处理函数,否则会出现使用HAL_UART_Transmit_IT函数发送数据时出现死循环的情况,使用Keil uVision查看函数调用栈会发现程序跳转到了”B .”指令处。
5 函数调用过程中局部变量的意外改变(调节栈的大小)
在函数调用过程中,上一级的局部变量作为被调用函数的参数,调试过程中发现作为参数的变量的内容会意料之外地改变,此时需要考虑是否是栈空间太小导致栈溢出引发问题。
函数调用时需要栈空间,使用STM32CubeMX生成代码时默认大小为0x400,可以通过更改设置或直接改变startup_stm32xxx.s(xxx为芯片型号)改变默认的大小。更改设置:STM32CubeMX->Project->settings->Project->Linker Settings->Mininum Stack Size,
此时打开startup_stm32xxx.s文件可以看到”Stack_Size EQU 0x800”,0x800为设置的大小。
6 ADC
6.1 ADC DMA传输只进行1次的问题
使用DMA传输数据时碰到DMA只进行一次的情况,此时需要查看DMA的传输模式,要将模式设置为”Circular”才行,该选项其实是更改了ADC初始化结构体的模式字段,见下图。
6.2 ADC频率太低的问题
使用ADC DMA传输多通道(6通道)转换数据时碰到1秒钟只能传输300次的情况,理论上DMA的传输基本耗时接近0,型如STM32F103VET6的ADC最大速度可以达到1秒钟100万次,此时需要查看ADC转换的配置,如下,第一张图为1秒300次转换的配置,更改为第二张图的配置之后可以达到1秒29000次的转换。
6.3 ADC中断处理函数过长导致主循环不执行
在高频率ADC时,ADC的中断每隔几微秒就会产生一次,如果ADC中断函数过长(代码过多、大量的乘法和除法运算),导致ADC中断一直占有CPU,此时就会出现main函数中的主循环不会执行。
7 IAP升级
7.1 参考文档
ST官方文档:AN2557 《STM32F10x in-application programming using the USART》Page14
7.2 存储器布局
以STM32F103VET6为例,
存储总大小512K,256P(P:Page) | ||||
32K(0x8000),16P IAP代码 | 200K(0x32000),100P 用户程序代码,正常情况下设备就是运行该段代码 | 200K(0x32000),100P 用户升级程序代码 | 10K(0x2800),5P 参数区,1页用于保存升级标记和次数等,另外4页用于保存设备参数 | 70K(11800),35P 其他用途,如保存日志等 |
以STM32L152RCT6为例,
存储总大小256K,1024P(P:Page) | ||||
32K(0x8000),128P IAP代码 | 100K(0x19000),400P 用户程序代码,正常情况下设备就是运行该段代码 | 100K(0x19000),400P 用户升级程序代码 | 10K(0x2800),40P 参数区,1页用于保存升级标记和次数等,另外4页用于保存设备参数 | 14K(3800),56P 其他用途,如保存日志等 |
7.3 IAP代码实现和配置
参考STM32CubeMX库文件STM32Cube_FW_F1_V1.6.1\Projects\STM3210C_EVAL\Applications\IAP\IAP_Main\Src\main.c,加入适当的参数,如升级标记和次数等。
使用默认的如下图的配置即可,
7.4 用户程序代码和用户升级程序代码配置
用户程序代码和用户升级程序代码的配置相比IAP代码的配置有两点不同,第一点是烧录地址偏移不一样,用户程序代码和用户升级程序代码的配置如下图示例,
第二点是中断向量偏移不一样,IAP使用默认偏移(0x00)即可,用户程序代码和用户升级程序代码的中断向量偏移需要根据烧录地址偏移作出调整,具体为修改system_stm32f1xx.c文件里面的VECT_TAB_OFFSET值,如上图所示则VECT_TAB_OFFSET=0x8000
7.5 使用ST-Link烧录代码
可以使用STM32 ST-Link Utility或者Keil烧录这些代码,使用Keil直接烧录即可,使用Keil还可以分别调试各部分代码(按照上述步骤配置好,其他无须变动)。使用STM32 ST-Link Utility烧录bin文件则需要注意偏移地址,也可以烧录hex文件且不用在意偏移地址。
7.6 因为写保护导致升级失败
升级时要置位升级标志,重启时根据该标志判断,如果保存升级标志的区有写保护则写失败,导致升级失败,通过擦除选项字节解决。
8 RTC
8.1 RTC时间异常改变
以下是笔者在开发过程中遇到的导致RTC时间异常改变的情况:
1:switch分支中没有加break
2:memmove操作的大小单位不是一个字节,而是一个结构体的大小
3:STM32L152RCT6设置RTC时间时其他结构体成员未赋值(参考官方sample)
8.2 读写RTC需要等待周期
笔者在开发STM32L152RCT6时,业务中要求单片机收到平台下发的墙上时钟之后立即进行时间同步并传递自身信息给平台,在传递的信息中包含有单片机的时间。发现第一次传递信息时单片机的时间并不是平台下发的墙上时间,就好像时间没有同步到一样,从第二次开始传递的信息中的时间又正常。
参考官方文档,读写RTC都要有几个RTCCLK clock cycles才能完成,如果这几个等待周期和程序运行是异步的就会导致上述问题,从问题的表现来看,等待周期和程序运行是异步的。
8.3 RTC时间与本地墙上时间
笔者本想让单片机不引入时区的概念,所有在调用localtime()时以为返回的就是UTC时间的结构体,但localtime()实际返回的是电脑的本地时间(如果是北京时间则多了8个小时)。应该这么理解,单片机的RTC确实没有时区的概念,但是keil软件的localtime()函数会引用时区的概念,导致了预期和结果不相符。