一、STM32芯片的基本架构
1、STM32内部组成
STM32芯片,其实就是一台功能阉割的电脑,属于片上SOC的一种。里面包含有Cortex-M、RAM、FLASH等组件。图中所示为STM32F1芯片的内部架构。
以日常生存中使用的电脑为例:
(1)内核Cortex-M:相当于X86电脑的CPU;
(2)总线矩阵:内核和外部装备进行交互的时候需要总线矩阵进行通信,这个总线矩阵就像是台式机上的南 北桥,实现 数据交换的作用。
(3)FLASH:相当于电脑的硬盘;
(4)外设:相当于电脑外接的鼠标,网口,键盘,摄像头等;
(5)RAM:相当于电脑的内存条;
(6)DMA:相当于U盘向电脑拷入数据时,DMA可以直接进行数据搬运,此时不需要CPU参与。
如果对电脑不够了解的话,也可将STM32F芯片看作自己的身体。
(1)内核Cortex-M:相当于我们的大脑,思维、决议和协调各种运动;
(2)总线矩阵:相当于我们的神经体系,类似于神经体系连接身体的各个部分,传递和处置惩罚信息;
(3)FLASH:相当于我们的长期记忆;
(4)RAM:相当于我们的短期期记忆;
(5)外设:相当于我们的五官和四肢;
(6)DMA:想当与我们的血液循环,无需额外的介入而自主的运行。
2、STM32的工作流程
学过C语言的同学都知道,在实行C语言程序的时候,我们所使用的电脑是无法直接的理解C语言。我们能看懂C语言(也可能有的同学看不懂dog);但电脑看不懂。电脑只能读懂0和1。因此需要编译将C语言翻译成机械码。
一般可分为以下几个步骤:
(1)预处置惩罚:预处置惩罚器处置惩罚源代码中的预处置惩罚指令(如#include、#define),生成预处置惩罚后的代码;
(2)编译:编译器将预处置惩罚后的代码转换为汇编语言代码,进行语法查抄和优化;
(3)汇编:汇编器将汇编语言代码转换为呆板语言指令,生成目的文件(.o);
(4)链接:链接器将目的文件与库文件链接,解决符号引用,生成可实行文件(.exe);
(5)加载:操作体系将可实行文件加载到内存中;
(6)实行:处置惩罚器按照指令次序实行内存中的呆板语言代码,完成程序运行。
当我们在IDE(VScode 、Clion、IAR、Keil)上写完STM32程序后,可颠末IDE自带的编译器将所写的程序编译成STM32所能识别的机械语言也就是.hex文件。之后,通过USB-TTL模块或者STLINK下载器将.hex文件烧录到STM32中去。
此中,STM32中RAM储存的是存储变量、堆栈和其他暂时数据;FLASH存储器用于存储程序代码、常量数据以及部分需要在断电后保留的数据。
其实,在STM32运行main()函数之前会做一些工作,来确保main()可以或许正常的运行,就像我们在做数学试卷的时候,在写答案之前会预备好以下几样。
(1)预备好草稿纸:在内存中在开辟堆栈;初始化堆栈指针 SP=_initial_sp;
(2)预备好试卷:初始化程序计数器指针;初始化 PC 指针=Reset_Handler;
(3)正式启动:可通过拉高或者拉低外设引脚的电平来选择启动方式,一般默认为从FLASH启动。
图片里的主闪存存储器就是FLASH。
二、单片机的FLASH和RAM
一般来说单片机的内存指的是FLASH和RAM,当在程序中定义了全局变量、局部变量、只读变量等参数时都是会存放到对应的FLASH或者是RAM中。详细对单片机FLASH和RAM的先容之后再写,这里只对单片机内存分配,对堆和栈以及变量的存储做一个梳理和记录。
图中分别为M3内核、RAM、FLASH
1、FLASH(0x0800 0000)
FLASH紧张是存放程序代码、全局变量(不为零)及常量的。下面是将 FLASH内部进行细分之后的一张图。
STM32的FLASH是从地点0x0800 0000开始的,是向上增长的。FLASH又可以细分为这么几个部分:
(1)文本段 (Text),此中文本段中又包含可实行代码 (Executable Code)和常量 (Literal Value);比方:const int B=1中的B的地点以及我们自己写的程序代码的地点均储存在FLASH的文本段中;
(2)只读数据区域 (Read Only Data);
(3)数据复制段 (Copy of Data Section),这个段充当的作用是存放程序中初始化为非0值的全局变量的初始值;
数据复制段是一个特别的情况,就是初始值不为零的全局变量,他储存的地点是在FlASH中,但开始运行程序时,这个定义的全局变量就会被拷贝到RAM中,此时若要查询变量的地点,发现是在RAM中;可通过查察ST-LINK Utility查察全局变量的地点。
2、RAM(0x2000 0000)
相对与FLASH来说,RAM紧张就是用来存储数据了,在RAM中值得关注的是堆和栈的空间,堆是向上增长的而栈是向下生长的,如果一个函数运行的时候有大量的局部变量(栈向下增长),同时程序在整个过程中malloc申请了大量的堆空间而没有开释(堆向上增长),造成堆和栈空间的辩论,一旦堆栈辩论,体系就崩溃了。
如下是STM32中RAM的分区:
RAM中包含了如下几个部分:
(1)data:存放初始化为非0值的全局变量;
(2)bss:存放未初始化或者是初始化为0的全局变量;
(3)堆(Heap) : 由malloc申请,由free开释;堆是程序在运行的时候用malloc或new申请任意巨细的内存,程序员自己负责在适当的时候用free或delete开释内存。可以根据需要请求一段连续的内存空间,并在程序实行过程中随着数据的变化而增长或减小。
(4)栈(Stack) : 存放局部变量、函数调用时的返回地点以及制止入口等;函数实行竣事时这些存储单元主动被开释,其巨细紧张看函数调用的深度。
这是一个特别的内存区域,紧张用于存储程序实行过程中的局部变量、函数参数和函数调用的返回地点。它是按照后进先出的原则工作的,类似于生存中的堆叠物品。
- 总的来说,FLASH和RAM可分为以下几个部分:
Code为程序代码部分
RO_data 表示程序定义的常量(如:const temp等);
RW_data 表示已初始化的全局变量
ZI_data 表示未初始化的全局变量,以及初始化为0的变量
FLASH=Code+RO_data+RW_data
RAM=RW_data+ ZI_data
初始化时RW_data从flash拷贝到RAM
堆和栈的空间可以由我们来自由设定,可在在STM32的启动文件(.s)中,刚开头就有对堆(Head_Size)和栈(Stack_Size)空间的定义形貌。
三、固件库的本质
ST公司官方提供了三种库,分别为标准库,HAL库,LL库。三种库各有优劣。但这些库本质上就是对STM32中的各种寄存器进行操作。
1、寄存器理解
以SM32F1的GPIOC引脚口为例。若想将设置GPIOC的引脚为高电平,起首是要找到 GPIOC引脚口的端口位设置/扫除寄存器BSRR的地点。其在芯片内部的映射地点为0x40011010
BSRR寄存器是32bit,高低16bit有效,对应着16个外部IO,基本功能如下:
(1)在低16位地点(0-15)写1对应的IO则输出高电平,;
(2)在高16位地点(16-31)写1对应的IO则输出低电平;
(3)若高低16位地点都写1,高16位地点起作用。
以PC13地点对应的的第12位和第28位为例:
12位28位引脚电平01低10高11低00对数据输出寄存器不产生影响 若如今我们通过C语言指针的操作方式,让GPIOC16个IO 都输出高电平。其代码如下:
- // GPIOC 端口全部输出高电平
- *(unsigned int*)(0x4001 1010) = 0xFFFF;
复制代码 0x4001100C在我们看来是GPIOC端口BSRR的地点,但是在编译器看来,这只是一个普通的变量,是一个立即数,要想让编译器也认为是指针,我们得进行强制范例转换,把它转换成指针,即(unsigned int *)0x4001 1010,然后再对这个指针进行 * 操作。 刚刚我们说了,通过绝对地点访问内存单元不好记忆且容易出错,我们可以通过寄存器的方式来操作,详细见代码:
- // GPIOC端口全部输出高电平
- #define GPIOC_BSRR (unsigned int*)(GPIOC_BASE+0x10)
- * GPIOC_BSRR = 0xFFFF;
复制代码 那么这个地点0x40011010是怎么来的呢,
可以看到0x4001100C=0x40010000(基地点)+ 0x00001010(偏移地点),之所以采用基地点+偏移地点的方式,是因为这么多的引脚,有的引脚是高速引脚,有的引脚是低速引脚。
STM32F103C8T6的外设寄存器通常在两个桥上,分别为APB1和APB2。APB1操作速率限于36MHz,APB2操作于全速(最高72MHz)。通过本文的第一张图可以看到,GPIOC地点的寄存器在APB2(0x40010000)上。
2、寄存器地点理解
在拿到了GPIOC这个外设的地点0x40011000,就可以操作GPIOC寄存器了。那么起首看看,控制GPIOC有那些寄存器。根据芯片手册,可以得到:
名称简称寄存器地点偏移地点端口配置低寄存器GPIOC_CLR0x400110000x00端口配置高寄存器GPIOC_CHR0x400110040x04端口输入数据寄存器GPIOC_IDR0x400110080x08端口输出数据寄存器GPIOC_ODR0x4001100C0x0C端口位设置/扫除寄存器GPIOC_BSRR0x400110100x10端口位扫除寄存器GPIOC_BRR0x400110140x14端口配置锁定寄存器GPIOC_LCKR0x400110180x18 所以通过(unsignedint*)(0x40011000)这个指针就可以操作这些寄存器,比如要操作GPIOC_ODR这个寄存器,则(unsignedint*)(0x40011000+0x0C),就可以找到GPIOC_ODR的地点,再对其进行操作, * ((unsignedint*)(0x40011000
+0x0C),就可以修改/读取GPIOC_ODR寄存器里面的内容。
GPIOC_CLRGPIOC_CHRGPIOC_IDRGPIOC_ODRGPIOC_BSRRGPIOC_BRRGPIOC_LCKR⬆*(unsignedint*)(0x40011000) 3、HAL库封装
可STM32有很多外设,每个外设都对应着多种的寄存器。若要写一个功能。要一一查寄存器的功能即地点,然后进行配置。显然,太浪费时间了。于是,ST厂家就对这些寄存器地点进行封装。根据封装的规则不同,于是就形成了标准库,HAL库,LL库。还是以GPIOC的HAL库为例。
(1)起首,对GPIOC地点的外设基地点进行封装。
- /* 外设基地址:整个外设区的基地址 */
- #define PERIPH_BASE 0x40000000UL /*!< Peripheral base address in the alias region */
复制代码 (2)之后,对GPIOC地点的外设总线地点进行封装。
- #define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000UL)
复制代码 (3)根据GPIOC在总线中的偏移地点0x1000, 可以计算出GPIOC 外设基地点
- #define GPIOC_BASE (APB2PERIPH_BASE + 0x00001000UL)
复制代码 (4)封装GPIOC_BSRR寄存器地点,GPIOC_BSRR寄存器地点为GPIOC_BASE+0x10
- #define GPIOC_BSRR (GPIOC_BASE + 0x00000010UL)
复制代码 (5)让GPIOC的16个引脚全部输出高电平
- //1 // GPIOC 端口全部输出 高电平
- *(unsigned int*) GPIOC_BSRR=0xFFFF;
复制代码 到这一步可以发现,我们只需要操作GPIOB_ODR而不再是0x4001 0C0C这个地点,会更加直观。但GPIOA-GPIOE 都各有一组功能雷同的BSRR寄存器,它们只是地点不一样,同样的,但却要为每个寄存器都定义它的地点。为了更方便地访问寄存器,我们引入 C 语言中的布局体语法对寄存器进行封装:
- /**
- * @brief General Purpose I/O
- */
- typedef struct
- {
- __IO uint32_t CRL;
- __IO uint32_t CRH;
- __IO uint32_t IDR;
- __IO uint32_t ODR;
- __IO uint32_t BSRR;
- __IO uint32_t BRR;
- __IO uint32_t LCKR;
- } GPIO_TypeDef;
复制代码 这段代码用 typedef 关键字声明了名为 GPIO_TypeDef 的布局体范例,布局体内有7个成员变量,变量名对应寄存器的名字。C 语言的语法规定,布局体内变量的存储空间是连续的,此中32位的变量占用4个字节。
所以只要给布局体设置好首地点,就能把布局体内成员的地点确定下来,然后就能以布局体的形式访问寄存器。使用宏定义GPIO_TypeDef 范例的指针指向各个 GPIO 端口的首地点,使用时直接用该宏访问寄存器即可:
- #define GPIOA ((GPIO_TypeDef *)GPIOA_BASE)
- #define GPIOB ((GPIO_TypeDef *)GPIOB_BASE)
- #define GPIOC ((GPIO_TypeDef *)GPIOC_BASE)
- #define GPIOD ((GPIO_TypeDef *)GPIOD_BASE)
- #define GPIOE ((GPIO_TypeDef *)GPIOE_BASE)
复制代码 所以,要想给GPIOC引脚都设为高电平
但要想单独将GPIOC口的第十三个引脚设置为高电平,代码可写为:
还是有0x2000这样的值,可读性还是差,于是HAL库又增加了一个函数:
- HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
复制代码 函数内容如下:
- /**
- * @brief Sets or clears the selected data port bit.
- *
- * @note This function uses GPIOx_BSRR register to allow atomic read/modify
- * accesses. In this way, there is no risk of an IRQ occurring between
- * the read and the modify access.
- *
- * @param GPIOx: where x can be (A..G depending on device used) to select the GPIO peripheral
- * @param GPIO_Pin: specifies the port bit to be written.
- * This parameter can be one of GPIO_PIN_x where x can be (0..15).
- * @param PinState: specifies the value to be written to the selected bit.
- * This parameter can be one of the GPIO_PinState enum values:
- * @arg GPIO_PIN_RESET: to clear the port pin
- * @arg GPIO_PIN_SET: to set the port pin
- * @retval None
- */
- void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)
- {
- /* Check the parameters */
- assert_param(IS_GPIO_PIN(GPIO_Pin));
- assert_param(IS_GPIO_PIN_ACTION(PinState));
- if (PinState != GPIO_PIN_RESET)
- {
- GPIOx->BSRR = GPIO_Pin;
- }
- else
- {
- GPIOx->BSRR = (uint32_t)GPIO_Pin << 16u;
- }
- }
复制代码 这样STM32的使用会变得非常方便,其他所有的外设的寄存器都有对应的操作函数,这样就可以从复杂的寄存器操作中摆脱出来,从而有更多的时间和更少的失误去在算法方面进行操作。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |