
6.1 STM32结构 从“2.2 ARM与STM32的关系”可知,ARM公司负责设计内核,半导体芯片厂商拿到内核授权后,根据产品需求,添加各类组件,生产芯片售卖。如图 6.1.1 所示,为STM32的组成示意图,其中Cortex-M3内核、调试系统都是ARM公司设计,内部总线、外设、存储、时钟复位等都由ST公司开发。 在编程之前,对STM32的总线结构、存储结构、外设寄存器等有个大致了解,有助于理解编程中的一些操作。此外,中断与异常(Nested Vectored Interrupt Controller,NVIC)、时钟复位(Reset and Clock,RCC)也很重要,且与编程紧密相关,在后面相关实验章节里再专门讲解。 对于开发者,掌握一款MCU的开发需要重点关注四大模块:时钟复位、中断异常、存储映射和外设寄存器组。 ![]() 图 6.1.1 STM32组成示意图 6.1.1 STM32总线结构 总线(Bus)是各种信号线的集合,是嵌入式系统中各部件之间传输数据信息、地址信息和控制信息的公共通道。 与总线相关的主要参数有总线宽度、总线频率和总线带宽。总线宽度是指总线能同时传输的数据位数,如8位、32位、64位;总线频率是指总线的工作速度,频率越高,速度越快;总线带宽用来描述总线传输数据的快慢,总线带宽=总线宽度x总线频率/8,单位为MB/s。 STM32的总线结构如图 6.1.2 所示,可以分为6部分。 ![]() 图 6.1.2 STM32总线结构框图 ①ICode总线(Instruction bus):用于访问存储空间里指令的总线; ②DCode总线(Data bus):用于访问存储空间里数据的总线; ③System总线:用于访问指令、数据以及调试模块接口; ④DMA总线:用于内存与外设之间的数据传输; ⑤Bus matrix(总线矩阵):用于总线之间的访问优先级管理控制; ⑥APB总线:用于外设接口的数据传输;ARM公司推出AMBA片上总线结构,该总线主要包含先进高速总线(Advanced High-speed Bus,AHB)和先进外设总线(Advanced Peripheral Bus,APB),分别连接高速设备和低速设备。基于这个总线结构,ICode、Dcode、System Bus都是AHB总线。这里AHB系统总线经过两个AHB-APB桥转换成了两个APB总线。APB1上挂接有DAC、UART等外设,其最高频率可达36MHz;APB2上挂接有ADC、GPIO等外设,其最高频率可达72MHz。 在MCU每次复位后,所有的外设时钟都会默认处于关闭状态。因此,在使用外设前需要操作复位和时钟寄存器(Reset and Clock Control,RCC)开启所需外设的时钟。 6.1.2 STM32存储结构 CPU通过总线访问各个外设,现在通往外设的“路”已经铺好,还需要规定各个外设的“门牌号”,以便精准控制每个外设。ARM Cortex-M3系列的处理器,采用存储器与I/O设备(外设)统一编址的方式,将部分存储器地址范围用于外设,这种通过存储器地址访问外设的方式,称为存储器地址映射。 对于32位的处理器,可寻址的范围为232字节,即,也就是0x00000000至0xFFFFFFFF。ARM将这4G空间从低地址到高地址依次划分为代码区(Code)、片上SRAM区(SRAM)、片上外设(Peripheral)、片外RAM(External RAM)、片外外设(External Device)和系统级(System level),如图 6.1.3 所示。 ![]() 图 6.1.3 ARM Cortex-M3存储器映射结构图 ARM公司只是大概的规定了存储器空间的映射,允许各芯片厂商在指定范围内自行定义和使用这些存储空间,未分配的空间为保留的地址空间。 STM32在ARM规定的基础上,将4G空间分为了Block0、Block1、Block2、……、Block7,共8块,每块大小为512MB,如下表 6.1.1 所示,详细结构如图 6.1.4 所示。 ![]() 表 6.1.1 STM32存储器映射结构 ![]() 图 6.1.4 STM32存储器映射结构 ①0x0000 0000 ~ 0x1FFF FFFF(512MB):作为代码区,用于存放下载的代码。系统上电后,将从该部分读取代码; ②0x2000 0000 ~ 0x3FFF FFFF(512MB):作为SRAM区,用于存放运行代码。系统上电后,将从Flash读取代码,放到SRAM里,CPU再从SRAM读取代码运行; ③0x4000 0000 ~ 0x5FFF FFFF(512MB):作为片上外设区,用于存放厂商外设寄存器。要操作外设,即修改这里对应的外设寄存器;注意这里的RCC和Port B外设的地址范围,后面很快就会用到; ④0x6000 0000 ~ 0x9FFF FFFF(1GB):作为片外RAM,用于扩展RAM。当SRAM或者Flash不够用时,MCU通过FSMC外接其它IC芯片,则在这个地址范围读写IC芯片数据; ⑤0xA000 0000 ~ 0xDFFF FFFF(1GB):作为片外外设区,用于读写扩展IC芯片的寄存器。ST只用了这里的一半空间,另外一空间未使用; ⑥0xE000 0000 ~ 0xFFFF FFFF(512MB):作为内核外设区,用于存放Cortex-M3内核的内部外设。Cortex-M3内核的内部外设有NVIC、Systick等; 在Block0、Block1圈出的三个范围,分别对应前面介绍的三个启动模式。当从主存储器启动,则是从Flash启动;当从系统存储器启动,则是从System memory启动;当从SARM启动,则是从SRAM启动。 对于开发人员,通常就是对Block2的片上外设寄存器进行读写操作,以控制外设资源,实现所需效果。 6.1.3 STM32寄存器 寄存器是用来存储二进制数据的时序逻辑电路,由众多晶体管组成。 前面提到的寄存器,都是外设寄存器。这些外设寄存器由芯片厂商设计,与存储器统一编址,常用C语言的指针来表示外设寄存器地址,实现对外设寄存器的访问和操作。 在嵌入式系统中,除了外设寄存器,还有一类叫CPU内部寄存器。这些内部寄存器由ARM设计,在CPU内部,常用汇编语言直接操作,用于暂存参与运算的数据和内核的一些控制。 开发人员,通常只操作外设寄存器实现需求功能,后面实验会详细讲解外设寄存器。而内部寄存器在实际开发中接触会比较少,后面汇编点灯实验会涉及部分相关知识,本小结简单介绍下内部寄存器。 ARM Cortex-M3微处理器的内部寄存器,又分为普通寄存器和特殊功能寄存器。普通寄存器如图 6.1.5 所示。 ![]() 图 6.1.5 Cortex-M3通用寄存器 R0-R12(General-Purpose Registers):用于数据操作的32位通用寄存器;一些16位的Thumb指令,只能访问低寄存器(R0~R7); R13(Stack Pointers,SP)Cortex-M3包含两个堆栈指针寄存器;同一时刻只能看到其中一个; 主堆栈指针寄存器(Main Stack Pointer,MSP):操作系统(OS)内核和异常处理程序使用的默认堆栈指针; 进程堆栈指针寄存器(Process Stack Pointer,PSP):用于用户代码; R14(Link Register,LR):链接寄存器;调用子例程时,返回地址将存储在链接寄存器中; R15(Program Counter,PC):程序计数器;总是指向下一条指令所在单元的地址,可以写入该寄存器以控制程序流; Cortex-M3处理器还具有许多特殊寄存器,如图 6.1.6 所示。 ![]() 图 6.1.6 Cortex-M3特殊寄存器 xPSR(Program Status registers):程序状态寄存器;用于存放程序运作中的各种状态信息以及中断等状态;由应用状态寄存器(APSR)、中断状态寄存器(IPSR)和执行状态寄存器(EPSR)组成; PRIMASK、FAULTMASK和BASEPRI:中断屏蔽寄存器;用于控制异常和中断的屏蔽; CONTROL:控制寄存器;用于定义特权状态和当前使用哪一个堆栈指针; 【总结】 STM32由ARM公司设计的Cortex-M3内核与ST公司开发的外设资源组成。 Cortex-M3内核有内部寄存器,主要用于运算和内核的控制,这块对于初学者较难,暂时了解即可。 Cortex-M3内核通过总线和外设连接,重点了解大部分外设都挂载APB即可。 STM32采用存储器与外设统一编址的方式,控制外设,则对应操作指定地址的外设寄存器即可,这是后续实验的重点。 6.2 使用汇编开发 汇编语言(Assembly Language)是一种用于电子计算机、微处理器、微控制器或其它可编程器件的低级语言。 在单片机出现之初,由于性能限制,都是使用汇编进行开发。随着技术的发展,制程工艺的提升,单片机的处理速度越来越快,越来越多的单片机使用C语言开发。如今,一些低端MCU还在使用汇编开发;一些高要求程序优化的场合也会使用汇编;MCU/MPU的启动初始化部分也是汇编。本小结就带领读者感受下如何使用汇编操作开发板LED灯。 在编写代码前,本应该分析《开发板原理图》和《参考手册》,从而得知需要操作哪些外设寄存器,但这不是本小结主要内容,具体的分析方法放在后面对应的实验章节,读者暂时无需深入理解以下内容的由来: 1. 从《开发板原理图》可知,控制GPIOB 0引脚,即可控制开发板三色灯的红色灯; 2. 从前面STM32总线结构分析可知,GPIOA挂载APB2上,由RCC控制; 3. 从前面STM32存储结构分析可知,RCC起始地址为0x4002 1000,再仔细查看《参考手册》RCC寄存器部分,可知寄存器RCC_APB2ENR(偏移地址:0x18)的第2位(IOPAEN),设置为1则GPIO A组的使能; 4. 从前面STM32存储结构分析可知,GPIO A起始地址为0x4001 0800,再仔细查看《参考手册》GPIO寄存器部分,可知寄存器GPIOx_CRL(偏移地址:0x00)的第4:5位(MODEy),设置为1则GPIO A1为输出,寄存器GPIOx_ODR(偏移地址:0x0C)的第1位(ODRy),设置为1则GPIO A1为输出高,设置为0则GPIO A1为输出低; 有了以上基础,就可以编程控制LED灯了,本章涉及的代码位于100ASK_STM32F103_MINI开发板资料的“5_程序源码\0_单片机开发模式的发展\1_使用汇编开发\”。 如代码段 6.2.1 所示。汇编代码对初学者不友好,读者对代码内容不理解也正常,也无需深入理解汇编指令,这里主要目的是展示如何用汇编操作寄存器。 代码段 6.2.1 汇编操作寄存器(startup_stm32f10x_md.s)
4~8行:设置外部寄存器RCC_APB2ENR的第2位(IOPAEN)为1,使能GPIO A组的时钟; 5行:将0x40021018(RCC_APB2ENR的基地址+偏移地址)放入内部寄存器R0中; 6行:将0x40021018地址的值(此时RCCC_APB2ENR寄存器的值),放入内部寄存器R1; 7行:将R1的Bit2设置为1,并将设置后的结果放入R1; 8行:将R1的内容,放入R0所指向的地址,也就是将修改后的数据放入RCCC_APB2ENR寄存器; 10~14行:设置外部寄存器GPIOA_ CRL的第4:5位(MODE)为1,让GPIO A1为输出模式; 16~30行:设置外部寄存器GPIOA_ODR的第0位(ODR)先后为1和0,让GPIO A1为输出高、低电平; 32~40行:循环减R0实现延时效果; 以上汇编代码就实现了开发板用户LED灯(蓝色)交替闪烁效果,读者可以打开配套资料的“5_程序源码\0_单片机开发模式的发展\1_使用汇编开发\ Project\Led_Reg.uvprojx”工程,编译、下载,体验效果。 可以感受到,使用汇编编写程序,生涩难懂,因此越来越少的单片机使用汇编来编写代码,下面再来体验下C语言实现一样的效果。 6.3 C语言操作寄存器开发 在使用C语言操作寄存器前,仍需要先分析《开发板原理图》和《参考手册》,从而得知需要操作哪些外设寄存器,假设读者已经了解需要操作哪些外设寄存器。 本章涉及的代码位于100ASK_STM32F103_MINI开发板资料的“5_程序源码\0_单片机开发模式的发展\2_C语言操作寄存器开发\”。 使用C语言之前,需要先设置栈,如代码段 6.3.1 所示,在上电复位后立即设置栈,随后跳入main函数执行。 代码段 6.3.1 设置栈(startup_stm32f10x_md.s)
代码段 6.3.2 C语言操作寄存器(main.c)
主函数如代码段 6.3.2 所示。 1~4行:用宏定义了需要用到的各外设寄存器地址:RCC_APB2ENR、GPIOA_ CRL、GPIOA_ODR; 6~9行:循环递减传入的变量i,实现延时效果; 13~15行:定义指针变量; 17~19行:设置指针指向对应寄存器; 25~30行:设置RCC_APB2ENR、GPIOA_ CRL对应位; 32~38行:循环修改GPIOA_ODR实现LED灯交替闪烁; 以上代码就实现了开发板用户LED灯(蓝色)交替闪烁效果,读者可以打开配套资料的“5_程序源码\0_单片机编程模式的发展\ 2_C语言操作寄存器开发\ Project\Led_Reg.uvprojx”工程,编译、下载,体验效果。 使用C语言后,只需定义指针变量指向对应寄存器,修改该指针变量的值,即可修改对应寄存器,操作上方便了很多。 6.4 C语言使用标准库开发 前面的两种开发方式,适用于任何一款芯片,但需要仔细阅读《参考手册》找到对应寄存器,然后用指针变量指向寄存器。 ST公司为了让用户更快上手开发,产生用户粘性,将外设寄存器提前定义好,准备一些常用接口函数供用户使用,这就是标准库的雏形。 下面来感受下使用标准库的简便性。使用标准库通常不需要知道外设寄存器地址,只需要分析《开发板原理图》得知会用到哪一个引脚。 本章涉及的代码位于100ASK_STM32F103_MINI开发板资料的“5_程序源码\0_单片机开发模式的发展\2_C语言使用标准库开发\”。 首先使用C语言需要设置栈,这些初始化操作标准库已经帮忙完成了,我们只需要编写主函数即可,如代码段 6.4.1 所示。 代码段 6.4.1 C语言使用标准库开发(main.c)
5~8行:循环递减传入的变量i,实现延时效果; 14行:调用编写的“LedGpioInit()”函数,初始化需要的GPIO,该函数定义在“driver_led.c”里,如代码段 6.4.2 所示; 17~23行:循环修改GPIOA_ODR实现LED灯交替闪烁;这里不再涉及寄存器操作,只需要调用标准库提供的“GPIO_WriteBit()”函数即可,该函数需要三个参数,分别是:GPIO端口、该端口引脚号、电平高低。这里操作GPIOA 1引脚,因此传入“GPIOA”、“GPIO_Pin_1”、“Bit_SET”或“Bit_RESET”,使用这些宏定义,用户基本就无须查阅《参考手册》,上手相对容易很多; 代码段 6.4.2 GPIO初始化(driver_led.c)
15行:使用标准库提供的“RCC_APB2PeriphClockCmd()”,传入需要设置的外设名字、是否使能; 13~22行:将GPIO属性封装为结构体,通过设置结构体成员的属性,完成对GPIO的设置; 以上代码就实现了开发板用户LED灯(蓝色)交替闪烁效果,读者可以打开配套资料的“5_程序源码\0_单片机编程模式的发展\ 3_C语言使用标准库开发\ Project\ Led_Standard.uvprojx”工程,编译、下载,体验效果。 使用标准库后,基本不涉及外设寄存器的地址,同时提供很多函数实现相关功能的操作,用户只要熟悉之后,很快就能举一反三,修改相关传入参数,实现自定义需求。 6.5 C语言使用HAL库开发 ST公司先后推出了两套主要库:标准外设库(Standard Peripherals Library)和HAL库(Hardware Abstraction Layer)。标准外设库是STM32最早发布的固件库,对STM32芯片进行了完整的封装,包含所有外设的性能特征,每个外设驱动都由一组函数组成,这组函数覆盖了该外设所有功能。因为发布比较早,教程比较多,目前仍有不少开发者使用。标准外设库是针对某一系列STM32芯片而设计的,没有可移植性,目前该库已将停止了更新、研发,最近几年发布的新STM32芯片已经不再支持。 HAL库是ST公司这几年极力推荐的官方库,目前支持STM32全系产品,可以说HAL库就是用来取代之前的标准外设库的。相比标准外设库,HAL库具有更高的抽象整合水平,API集中关注外设的公共函数功能,使得可以轻松实现从一个STM32产品移植到另一个不同的STM32系列产品。此外还有LL库(Low Layer),相比其它固件库更接近硬件层,一般和HAL库结合使用,可以看作是HAL库的补充。 本章涉及的代码位于100ASK_STM32F103_MINI开发板资料的“5_程序源码\0_单片机开发模式的发展\2_C语言使用HAL库开发\”。 HAL库和标准库的使用区别不大,也无需知道外设寄存器地址,主函数如代码段 6.5.1 所示。 代码段 6.5.1 C语言使用HAL库开发(main.c)
6~9行:使用HAL的一些初始化操作; 11~12行:调用编写的“LedGpioInit()”函数,初始化需要的GPIO,该函数定义在“driver_led.c”里,如代码段 6.5.2 所示; 15~21行:通过调用HAL提供的“HAL_GPIO_WritePin”函数控制引脚输出的电平高低;HAL库还提供延时函数“HAL_Delay()”,用户不用再自己编写延时函数; 代码段 6.5.2 GPIO初始化(driver_led.c)
15行:使用HAL库提供的“__HAL_RCC_GPIOB_CLK_ENABLE()”使能GPIO A端口时钟; 13~23行:将GPIO属性封装为结构体,通过设置结构体成员的属性,完成对GPIO的设置; 以上代码就实现了开发板用户LED灯(蓝色)交替闪烁效果,读者可以打开配套资料的“5_程序源码\0_单片机编程模式的发展\ 4_C语言使用HAL库开发\ Project\ Led_Hal.uvprojx”工程,编译、下载,体验效果。 使用HAL库的体验和标准库差不多,但HAL兼顾了其它ST芯片,用户可以无缝过渡到ST其它芯片的使用。各开发模式之间的差异如表 6.5.1 所示。 ![]() 表 6.5.1 各开发模式差异 【总结】 本章开始讲解了一些STM32结构的基础知识,这些知识对STM32的整体理解会有很大帮助,读者应多理解。 随后介绍的四种开发模式,读者重点理解C语言操作寄存器开发和使用HAL库开发。后续的实验会以HAL库为主,但读者也应理解HAL库的本质也是操作寄存器,因此理解如何操作寄存器,对以后学习、调试,都会有帮助。 作者:攻城狮子黄 |
【STM32C0测评】Nucleo-C092开发板驱动DS1302实验
STM32 GUI LTDC 最大像素时钟评估方法
【2025·STM32峰会】GUI解决方案实训分享1-对LVGL咖啡机例程的牛刀小试以及问题排查
Keil下的STM32N6之RAM运行工程配置说明
【STM32MP257-DK】01开发板开箱、ST MPU 生态资源使用、环境搭建以及镜像更新
兔哥的初代M33【002】-H503Nucleo 内部flash操作
兔哥的杂谈【002】——如何性价比更高地去编译STM32
OpenBLT移植到STM32F405开发板
汇编浮点库qfplib移植STM32F769I-DISCO开发板与硬件浮点运算性能测试对比
为什么要先开启STM32外设时钟?