STM32寄存器开发心得与经验浅谈
以STM32H750VB系列MCU使用6个SPI进行读写操作为例
开发背景
此次开发是利用STM32H750VB系列读取4个AD4020的数据,以及对5个DAC8832进行写操作。所有操作均需要利用利用SPI的通信方式,查阅数据手册可知,此系列只有6个SPI接口。由于要求必须全部接口都用到,因此需要对每个接口进行配置与操作。
寄存器开发
1. 前言
在之前本科阶段所有的开发都是利用已有的库函数模板进行一些I/O口的模式配置,基本就可以实现我们的常规操作了。但是这总操作于我个人而言,总会有一种雾里看花的感觉,不明朗。因此这次开发也是一次机会,下定决心从寄存器的角度进行开发。
2. 开发模式=参考手册+数据手册+寄存器位操作
经过这次开发下来,最深刻的就是对于接口也好、I/O口也好、RCC时钟也好还是管脚复用配置,这些操作全部都可以从参考手册与数据手册中得到答案。特别是参考手册,以SPI接口为例,如下图: 从图中左侧可以看到参考手册对于SPI接口的相关寄存器目录,点击即可跳转到对应寄存器的详细功能描述。而右侧框选的就是对应寄存器的相关描述,有寄存器的复位值、偏移值、以及各个位的功能、读或写操作的允许与否、以及位值对应的配置功能。至于那些寄存器需要重新配置,需要根据我们实际的功能需求,比如对于SPI而言典型需要对其进行约束的有:CPOL、CPHA、数据包中的帧数、一帧数据的位数、时钟源的分频系数等。这些特征参数都可以在对应的寄存器通过位操作进行初始化设置即可。但一般在设置时要先将SPI进行禁止,才能配置其相关的寄存器,待到配置完后,再进行是能开启。还有个需要对SPI进行配置地方是其外设的内核时钟源,之所以要加粗是因为他的配置位并不在SPI的寄存器中,而是在RCC的域的内核时钟寄存器中,具体的SPI1~6属于哪个域需要进一步进行查看,这个我在接下来的一个小节会具体阐述。总之在这一节中,需要明白的是我们利用寄存器进行开发的基本思路查阅寄存器参考手册然后对相关寄存器的相关位进行配置,这个配置一般就是在我们工程中对应的C文件接口初始化函数中的,使其能够满足我们的最终需求。
3. 时钟树:代码运行的心脏,核心的核心
话不多说先附上我的主函数的main()函数的内容:
int main(void)
{
Stm32_Clock_Init(160,5,2,4);
delay_init(400);
uart_init(100,115200);
MPU_Memory_Protection();
TIM3_Int_Init(35-1,2-1);
AD4020_Init();
DAC8832_Init();
}
可以看到第一行就是一个对系统时钟的初始化配置,这个配置关乎我们STM32运行的系统时钟、APB、AHB等总线时钟以及PLL时钟。我个人的目前理解是选择时钟源、设置对应的分频、倍频系数,最终得到各个始终,不同于51单片机仅仅利用一个外部的时钟就可以作为系统时钟,也没有什么关于时钟的配置。由于STM32时钟系统十分强大,因此也就生发出了时钟树的概念,附图如下: 上图是我去STM32CubeMx中找的一个,可以从左往右看这幅图,大体都是分为两个步骤:一是对时钟源的选择,一个是对倍频分频系数选择。
- 对于时钟源从图中可以大致看出分为两类:外部的时钟源LSE(外部低速时钟源)常为32.768kHz、HSE外部高速时钟源常为25MHz,这两个实际根据所用的开发板或者自己设计来决定;内部的话有LSI RC内部低速时钟源32KHz、HSI RC内部高速时钟源64MHz、CSI RC低功耗内部时钟4MHz、RC48内部高精度时钟源48MHz.基于不同的设计,可以对System Clock Mux、PLL Source Mux以及相关外设的内核时钟源如SPI123、USART1,6等进行选择,这些时钟源的选择都在RCC对应的寄存器中可以进行配置。其中较为重要的是System Clock与PLL Source的配置,这两个一般就在系统初始化函数中需要提前进行配置好。然后具体需要使用到哪些外设时,在可以对其对应的时钟源进行选择。
- 对于分频系数、倍频系数的配置,这个可以根据我们实际需要的时钟大小,合理配置各个分频寄存器与倍频寄存器的值实现。当然这些值都会有对应的大小限制以及一些时钟大小最大值的限制。以APB1 Peripheral Clocks(APB1外设时钟)为例,其最大可以达到100MHz,这一点已经在图中或者查阅数据手册有明确的限制,因此其前面的一系列分频系数与倍频系数的配置必须保证,最终APB1的外设时钟要小于100MHz.
4、关于外设的配置:以SPI为例
一般的步骤我个人的习惯总结如下:1)时钟使能;2)I/O口功能初始化;3)外设内核时钟源选择;4)外设功能初始化;5)外设使能.
1. 时钟使能 时钟使能一般包含搭载外设所在I/O口的总线时钟使能、与外设所搭载的总线时钟使能。外设一般都是需要一具体的MCU引脚(即我们常说的I/O口)作为载体,因此使用外设前必须对其所在I/O口总线时钟进行时能。同时要实现外设功能,要对相关寄存器进行操作时,也需要对外设的总线时钟进行使能。
2. I/O功能初始化 I/O功能初始化就是对控制I/O口功能的寄存器进行配置,常见的如输入(上拉、下拉、浮空等)与输出模式(开漏还是推挽)、I/O的输出速度、以及复用功能等。这个复用功能在I/O口被用作外设时,都是需要进行复用设置的,但具体需要复用成功能(0~16)哪一个,这个可以查阅数据手册中的I/O的选择功能表,那里面会有对应的需要配置成功能的复用值。
3. 外设内核时钟源选择 这个的选择其实一般默认选择复位值,够用也就无须要重新配置时钟源了。之所以需要重新配置时钟源,是因为我在开发时需要SPI5提供100MHz的外设内核时钟,但是它默认是挂载在APB1的外设时钟总线上的,只能提供100MHz的时钟源,加上SPI最少都需要进行2分频,因此无法满足要求,因此我查阅SPI5的时钟源选择寄存器重新选择了PLL2_q作为时钟源,重新在系统初始化函数中配置它的分频与倍频系数(之前所提到的),使其时钟输出200MHz,这样再经过2分频后,可以实现100MHz的始终输出了。(这个是我理论上导这么配置的,还没有实际利用板子烧录去测试是否正确)
4. 外设功能初始化 这部分就是对SPI的相关寄存器进行配置,就如同我们之前所说的那些SPI功能的相关参数,按照我们实际所需进行配置好,一般就差不多了。这里我想提个我个人的疑惑点或者是不确定点吧:因为我需要读取一个20位的ADC的数据,为了准确读取,判断接受标志位,我设置了一个数据包中有2帧数据、一帧数据有10位,我个人认为这样操作的结果是使最终的数据包接收到20位有效数据后,才会触发接收完成的标志。不知道这样操作是否正确。
5. 外设使能 在完成了之前的外设功能配置后,最后就可以对其功能进行使能即可,这样基本上就完成了对一个SPI外设的初始化配置。
总结
以上是我个人对于利用寄存器进行开发的一点点心得与感想,希望其中有诸多不足与错误,望各位朋友可以批评指正。如果能够对大家理解寄存器开发有一定的帮助,我也感到荣幸。
|