有人使用STM32H7芯片做些事情,发现基于ST公司的HAL库开发UART1的DMA收发时可以轻松实现,而当使用ST的LL库组织代码时,却没法实现UART的DMA传输。
感觉上就是使用HAL库编写代码功能正常而基于LL库则不行。真是这样吗?
使用STM32CubeMx进行图形化配置,并生成基于HAL库的初始代码,要实现UART收发功能的DMA传输的话,除了安排好的收发缓冲内存外,再就只需调用下面两个HAL库的API函数即可进行功能验证。
从功能实现上讲,使用HAL库及相应API还是很方便、很简单的。每个API函数就像个黑盒子,对于里面的内容,如果你不点进去阅读是不会知晓的。
不过,建议尽可能地多点进去瞧瞧,那里往往别有洞天。
如果基于LL库来组织代码的话,先使用STM32CubeMx进行配置并生成基于LL库的初始化代码。
基于CubeMx配置完毕后生成初始化工程,准备收发缓冲内存,然后添加用户代码。
刚开始用户代码是这样编写安排的。见下面代码截图。
上图中A处代码的作用就是开启两个DMA stream的功能,即对相关DMA Stream的控制寄存器的使能位进行使能置1。
编译无错后运行代码,可是根本没有数据的收发动作发生。看来,跟反馈者的症状一样。
没办法,硬着头皮核查代码。除了核查我添加的用户代码外,还核查UART及DMA的初始化代码。看来看去,似乎该有的都有了,该写的都写了。
后来,根据代码里涉及到的寄存器去跟STM32H7手册里寄存器做比较阅读。
在查看DMA各个stream配置控制寄存器【DMA_SxCR】内容时,突然发现并想起了点什么。
其实也是之前在别的DMA应用场合也碰到过的类似问题。
下面为该寄存器的内容布局截图。
隐约记得,该DMA Stream或Channel的控制使能位为0时才能做DMA相关其它参数的配置的。
我们可以在手册里找到针对该位的明确描述:
这里的意思是说,要想让某DMA stream工作,必须令该EN位为1。
不过,当该EN位为1时时,是不允许对DMA及相应FIFO寄存器做配置的。
换言之,若要针对某Stream做DMA相关配置,得先让该控制寄存器的EN位保持为0状态。
而在我的用户代码里,对EN位写1操作则放在了对DMA做各种配置的前面,即上面代码截图的A处。
既然这样,我们把对DMA控制寄存器EN位的置1操作放在其它DMA相关配置之后就应该可以了,即从上面代码截图中的A处拉到B处。
然后,进行测试,结果果真正常了。
其实就是一个配置代码顺序问题,卡了半天。
如果不是用LL库而是用HAL库可能就不太容易碰到这个问题。前面说过了,基于HAL库的API函数像个黑盒子,它帮我们处理了很多细节性、判断性的东西。
基于LL库组织的代码,相比HAL库组织的代码,代码精简、流程清晰、运行高效。不过,使用LL库做开发需要开发者对芯片各个模块的工作原理,操作流程有更清晰、更精准的了解,同时往往还需要开发者对应用相关的寄存器有更细致、深入的把握。
而HAL库往往事先帮我们充分考虑到了基于硬件需求的操作流程、时序,基于软件层面的诸多事件及状态的互斥管理,以及不同STM32系列的代码兼容性,并做了很好、很全面的封装。
所以我们在利用HAL库来实现相应功能时,往往无须对操作流程、时序以及寄存器本身做过多的了解就可以完成。
从开发角度讲,利用HAL库往往比利用LL库能更快地完成任务,同时基于HAL库的代码也有更好的移植性,代价就是代码相对LL库要庞大些。
对应STM32开发者而言,即使基于HAL库开发了一些STM32项目,对于芯片的诸多功能细节以及寄存器的了解往往可能比较有限。当然,这点因人而异吧,不可说死。
对于HAL库和LL库的选用,我们每个人可以根据自身情况来。比方,对芯片软硬件不熟悉时、任务紧急时先使用HAL库,等对芯片及库函数熟悉、任务不紧急时可以切换到LL库。
或者说,只是做些功能性验证确认,使用HAL库组织代码也是非常快捷方便的。
当然,一个工程里HAL库、LL库是可以同时并存的。另外,当我们对芯片寄存器、内核指令系统足够熟悉时,甚至可以尝试使用汇编语言做MCU编程开发。
作为开发人员,基于HAL库组织代码和基于汇编指令组织代码实现相同功能时,对我们自身的挑战及相应的收获是不可同日而语的。