stm32串口例程_stm32串口例程中各个文件的结构和调用关系

stm32串口例程_stm32串口例程中各个文件的结构和调用关系入手STM32单片机的知识点总结本文将以STM32F10x为例,对标准库开发进行概览。主要分为三块内容:·STM32系统结构·寄存器·通过点灯案例,详解如何基于标准库构建STM32工程(文末有STM32、物联网开发相关的网盘资料,包括物联网开发从基础到实战、IoT-ARM结构下的各类智能产

入手STM32单片机的知识点总结   本文将以STM32F10x为例,对标准库开发进行概览。主要分为三块内容:   ·STM32系统结构   ·寄存器   ·通过点灯案例,详解如何基于标准库构建STM32工程   (文末有STM32、物联网开发相关的网盘资料,包括物联网开发从基础到实战、IoT-ARM结构下的各类智能产品的设计、STM32的开发、全国电赛优秀作品分析等,如有需要请自行领取)   STM32系统结构   
stm32串口例程_stm32串口例程中各个文件的结构和调用关系
stm32串口例程_stm32串口例程中各个文件的结构和调用关系STM32f10xxx系统结构   内核IP   从结构框图上看,Cortex-M3内部有若干个总线接口,以使CM3能同时取址和访内(访问内存),它们是:指令存储区总线(两条)、系统总线、私有外设总线。有两条代码存储区总线负责对代码存储区(即 FLASH 外设)的访问,分别是 I-Code 总线和 D-Code 总线。   I-Code用于取指,D-Code用于查表等操作,它们按最佳执行速度进行优化。   系统总线(System)用于访问内存和外设,覆盖的区域包括SRAM,片上外设,片外RAM,片外扩展设备,以及系统级存储区的部分空间。   私有外设总线负责一部分私有外设的访问,主要就是访问调试组件。它们也在系统级存储区。   还有一个DMA总线,从字面上看,DMA是data memory access的意思,是一种连接内核和外设的桥梁,它可以访问外设、内存,传输不受CPU的控制,并且是双向通信。简而言之,这个家伙就是一个速度很快的且不受老大控制的数据搬运工。   处理器外设(内核之外的外设)   从结构框图上看,STM32的外设有串口、定时器、IO口、FSMC、SDIO、SPI、I2C等,这些外设按照速度的不同,分别挂载到AHB、APB2、APB1这三条总线上。   寄存器   什么是寄存器?寄存器是内置于各个IP外设中,是一种用于配置外设功能的存储器,并且有想对应的地址。一切库的封装始于映射。   
stm32串口例程_stm32串口例程中各个文件的结构和调用关系
stm32串口例程_stm32串口例程中各个文件的结构和调用关系   
stm32串口例程_stm32串口例程中各个文件的结构和调用关系
stm32串口例程_stm32串口例程中各个文件的结构和调用关系   是不是看的眼都花了,如果进行寄存器开发,就需要怼地址以及对寄存器进行字节赋值,不仅效率低而且容易出错。   库的存在就是为了解决这类问题,将代码语义化。语义化思想不仅仅是嵌入式有的,前端代码也在追求语义特性。   从点灯开始学习STM32   
stm32串口例程_stm32串口例程中各个文件的结构和调用关系
stm32串口例程_stm32串口例程中各个文件的结构和调用关系   内核库文件分析   cor_cm3.h   这个头文件实现了:   1、内核结构体寄存器定义。   2、内核寄存器内存映射。   3、内存寄存器位定义。跟处理器相关的头文件stm32f10x.h实现的功能一样,一个是针对内核的寄存器,一个是针对内核之外,即处理器的寄存器。   misc.h   内核应用函数库头文件,对应stm32f10x_xxx.h。   misc.c   内核应用函数库文件,对应stm32f10x_xxx.c。在CM3这个内核里面还有一些功能组件,如NVIC、SCB、ITM、MPU、CoreDebug,CM3带有非常丰富的功能组件,但是芯片厂商在设计MCU的时候有一些并不是非要不可的,是可裁剪的,比如MPU、ITM等在STM32里面就没有。   其中NVIC在每一个CM3内核的单片机中都会有,但都会被裁剪,只能是CM3 NVIC的一个子集。在NVIC里面还有一个SysTick,是一个系统定时器,可以提供时基,一般为操作系统定时器所用。misc.h和mics.c这两个文件提供了操作这些组件的函数,并可以在CM3内核单片机直接移植。   处理器外设库文件分析   startup_stm32f10x_hd.s   这个是由汇编编写的启动文件,是STM32上电启动的第一个程序,启动文件主要实现了初始化堆栈指针 SP;设置 PC 指针=Reset_Handler ;设置向量表的地址,并 初始化向量表,向量表里面放的是 STM32 所有中断函数的入口地址调用库函数 SystemInit,把系统时钟配置成 72M,SystemInit 在库文件 stytem_stm32f10x.c 中定义;跳转到标号_main,最终去到 C 的世界。   system_stm32f10x.c   这个文件的作用是里面实现了各种常用的系统时钟设置函数,有72M,56M,48, 36,24,8M,我们使用的是是把系统时钟设置成72M。   Stm32f10x.h   这个头文件非常重要,这个头文件实现了:   1、处理器外设寄存器的结构体定义。   2、处理器外设的内存映射。   3、处理器外设寄存器的位定义。   关于 1 和 2 我们在用寄存器点亮 LED 的时候有讲解。   其中 3:处理器外设寄存器的位定义,这个非常重要,具体是什么意思?   我们知道一个寄存器有很多个位,每个位写 1 或者写 0 的功能都是不一样的,处理器外设寄存器的位定义就是把外设的每个寄存器的每一个位写 1 的 16 进制数定义成一个宏,宏名即用该位的名称表示,如果我们操作寄存器要开启某一个功能的话,就不用自己亲自去算这个值是多少,可以直接到这个头文件里面找。   我们以片上外设 ADC 为例,假设我们要启动 ADC 开始转换,根据手册我们知道是要控制 ADC_CR2 寄存器的位 0:ADON,即往位 0 写 1,即:   ADC->CR2=0x00000001;   这是一般的操作方法。现在这个头文件里面有关于 ADON 位的位定义:   #define ADC_CR2_ADON ((uint32_t)0x00000001)   有了这个位定义,我们刚刚的代码就变成了:   ADC->CR2=ADC_CR2_ADON   stm32f10x_xxx.h   外设 xxx 应用函数库头文件,这里面主要定义了实现外设某一功能的结构体,比如通用定时器有很多功能,有定时功能,有输出比较功能,有输入捕捉功能,而通用定时器有非常多的寄存器要实现某一个功能。   比如定时功能,我们根本不知道具体要操作哪些寄存器,这个头文件就为我们打包好了要实现某一个功能的寄存器,是以机构体的形式定义的,比如通用定时器要实现一个定时的功能,我们只需要初始化 TIM_TimeBaseInitTypeDef 这个结构体里面的成员即可,里面的成员就是定时所需要操作的寄存器。   有了这个头文件,我们就知道要实现某个功能需要操作哪些寄存器,然后再回手册中精度这些寄存器的说明即可。   stm32f10x_xxx.c   stm32f10x_xxx.c:外设 xxx 应用函数库,这里面写好了操作 xxx 外设的所有常用的函数,我们使用库编程的时候,使用的最多的就是这里的函数。   SystemInit   工程中新建main.c 。   在此文件中编写main函数后直接编译会报错:   Undefined symbol SystemInit (referred from startup_stm32f10x_hd.o).   错误提示说SystemInit没有定义。从分析启动文件startup_stm32f10x_hd.s时我们知道,   ;Reset handler   Reset_Handler PROC   EXPORT Reset_Handler [WEAK]   IMPORT __main   ;IMPORT SystemInit   ;LDR R0, =SystemInit   BLX R0   LDR R0, =__main   BX R0   ENDP   汇编中;分号是注释的意思   第五行第六行代码Reset_Handler调用了SystemInit该函数用来初始化系统时钟,而该函数是在库文件system_stm32f10x.c中实现的。我们重新写一个这样的函数也可以,把功能完整实现一遍,但是为了简单起见,我们在main文件里面定义一个SystemInit空函数,为的是骗过编译器,把这个错误去掉。   关于配置系统时钟之后会出文章RCC时钟树详细介绍,主要配置时钟控制寄存器(RCC_CR)和时钟配置寄存器(RCC_CFGR)这两个寄存器,但最好是直接使用CubeMX直接生成,因为它的配置过程有些冗长。   如果我们用的是库,那么有个库函数SystemInit,会帮我们把系统时钟设置成72M。   现在我们没有使用库,那现在时钟是多少?答案是8M,当外部HSE没有开启或者出现故障的时候,系统时钟由内部低速时钟LSI提供,现在我们是没有开启HSE,所以系统默认的时钟是LSI=8M。   库封装层级   
stm32串口例程_stm32串口例程中各个文件的结构和调用关系
stm32串口例程_stm32串口例程中各个文件的结构和调用关系   如图,达到第四层级便是我们所熟知的固件库或HAL库的效果。当然库的编写还需要考虑许多问题,不止于这些内容。我们需要的是了解库封装的大概过程。   将库封装等级分为四级来介绍是为了有层次感,就像打怪升级一样,进行认知理解的升级。   我们都知道,操作GPIO输出分三大步:   时钟控制:   STM32 外设很多,为了降低功耗,每个外设都对应着一个时钟,在系统复位的时候这些时钟都是被关闭的,如果想要外设工作,必须把相应的时钟打开。   STM32 的所有外设的时钟由一个专门的外设来管理,叫RCC(reset and clockcontrol),RCC 在STM32 参考手册的第六章。   STM32 的外设因为速率的不同,分别挂载到三条总系上:AHB、APB2、APB1,AHB为高速总线,APB2 次之,APB1 再次之。所以的IO 口都挂载到APB2 总线上,属于高速外设。   模式配置:   这个由端口配置寄存器来控制。端口配置寄存器分为高低两个,每4bit 控制一个IO 口,所以端口配置低寄存器:CRL 控制这IO 口的低8 位,端口配置高寄存器:CRH控制这IO 口的高8bit。   在4 位一组的控制位中,CNFy[1:0] 用来控制端口的输入输出,MODEy[1:0]用来控制输出模式的速率,又称驱动电路的响应速度,注意此处速率与程序无关,GPIO引脚速度、翻转速度、输出速度区别输入有4种模式,输出有4种模式,我们在控制LED 的时候选择通用推挽输出。   输出速率有三种模式:2M、10M、50M,这里我们选择2M。   电平控制:   STM32的IO口比较复杂,如果要输出1和0,则要通过控制:端口输出数据寄存器ODR来实现,ODR 是:Output data register的简写,在STM32里面,其寄存器的命名名称都是英文的简写,很容易记住。   从手册上我们知道ODR是一个32位的寄存器,低16位有效,高16位保留。低16位对应着IO0~IO16,只要往相应的位置写入0或者1就可以输出低或者高电平。   第一层级:基地址宏定义   
stm32串口例程_stm32串口例程中各个文件的结构和调用关系
stm32串口例程_stm32串口例程中各个文件的结构和调用关系   时钟控制:   
stm32串口例程_stm32串口例程中各个文件的结构和调用关系
stm32串口例程_stm32串口例程中各个文件的结构和调用关系   在STM32中,每个外设都有一个起始地址,叫做外设基地址,外设的寄存器就以这个基地址为标准按照顺序排列,且每个寄存器32位,(后面作为结构体里面的成员正好内存对齐)。   查表看到时钟由APB2外设时钟使能寄存器(RCC_APB2ENR)来控制,其中PB端口的时钟由该寄存器的位3写1使能。我们可以通过基地址+偏移量0x18,算出RCC_APB2ENR的地址为:0x。那么使能PB口的时钟代码则如下所示:   #define RCC_APB2ENR *(volatile unsigned long *)0x   // 开启端口B 时钟   RCC_APB2ENR |= 1<<3;   模式配置:   
stm32串口例程_stm32串口例程中各个文件的结构和调用关系
stm32串口例程_stm32串口例程中各个文件的结构和调用关系   同RCC_APB2ENR一样,GPIOB的起始地址是:0X4001 0C00,我们也可以算出GPIO_CRL的地址为:0x40010C00。那么设置PB0为通用推挽输出,输出速率为2M的代码则如下所示:   
stm32串口例程_stm32串口例程中各个文件的结构和调用关系
stm32串口例程_stm32串口例程中各个文件的结构和调用关系   同上,从手册中我们看到ODR寄存器的地址偏移是:0CH,可以算出GPIOB_ODR寄存器的地址是:0X4001 0C00 + 0X0C = 0X4001 0C0C。现在我们就可以定义GPIOB_ODR这个寄存器了,代码如下:   #define GPIOB_ODR *(volatile unsigned long *)0x40010C0C//PB0 输出低电平GPIOB_ODR = 0<<0;   第一层级:基地址宏定义完成用STM32控制一个LED的完整代码:   #define RCC_APB2ENR *(volatile unsigned long *)0x#define GPIOB_CRL *(volatile unsigned long *)0x40010C00#define GPIOB_ODR *(volatile unsigned long *)0x40010C0Cint main(void){ // 开启端口B 的时钟 RCC_APB2ENR |= 1<<3; // 配置PB0 为通用推挽输出模式,速率为2M GPIOB_CRL = (2<<0) | (0<<2); // PB0 输出低电平,点亮LED GPIOB_ODR = 0<<0;}void SystemInit(void){}   第二层级:基地址宏定义+结构体封装   外设寄存器结构体封装   上面我们在操作寄存器的时候,操作的是寄存器的绝对地址,如果每个寄存器都这样操作,那将非常麻烦。我们考虑到外设寄存器的地址都是基于外设基地址的偏移地址,都是在外设基地址上逐个连续递增的,每个寄存器占32个或者16个字节,这种方式跟结构体里面的成员类似。   所以我们可以定义一种外设结构体,结构体的地址等于外设的基地址,结构体的成员等于寄存器,成员的排列顺序跟寄存器的顺序一样。这样我们操作寄存器的时候就不用每次都找到绝对地址,只要知道外设的基地址就可以操作外设的全部寄存器,即操作结构体的成员即可。   下面我们先定义一个GPIO寄存器结构体,结构体里面的成员是GPIO的寄存器,成员的顺序按照寄存器的偏移地址从低到高排列,成员类型跟寄存器类型一样。   typedef struct   {   volatile uint32_t CRL;   volatile uint32_t CRH;   volatile uint32_t IDR;   volatile uint32_t ODR;   volatile uint32_t BSRR;   volatile uint32_t BRR;   volatile uint32_t LCKR;   } GPIO_TypeDef;   在《STM32 中文参考手册》8.2 寄存器描述章节,我们可以找到结构体里面的7个寄存器描述。在点亮LED的时候我们只用了CRL和ODR这两个寄存器,至于其他寄存器的功能大家可以自行看手册了解。   在GPIO结构体里面我们用了两个数据类型,一个是uint32_t,表示无符号的32位整型,因为GPIO的寄存器都是32位的。这个类型声明在标准头文件stdint.h 里面使用typedef对unsigned int重命名,我们在程序上只要包含这个头文件即可。   另外一个是volatile作用就是告诉编译器这里的变量会变化不因优化而省略此指令,必须每次都直接读写其值,这样就能确保每次读或者写寄存器都真正执行到位。   外设封装   
stm32串口例程_stm32串口例程中各个文件的结构和调用关系
stm32串口例程_stm32串口例程中各个文件的结构和调用关系   STM32F1系列的GPIO端口分A~G,即GPIOA、GPIOB。。。。。。GPIOG。每个端口都含有GPIO_TypeDef结构体里面的寄存器,我们可以根据手册各个端口的基地址把GPIO的各个端口定义成一个GPIO_TypeDef类型指针,然后我们就可以根据端口名(实际上现在是结构体指针了)来操作各个端口的寄存器,代码实现如下:   #define GPIOA ((GPIO_TypeDef *) 0X4001 0800)   #define GPIOB ((GPIO_TypeDef *) 0X4001 0C00)   #define GPIOC ((GPIO_TypeDef *) 0X4001 1000)   #define GPIOD ((GPIO_TypeDef *) 0X4001 1400)   #define GPIOE ((GPIO_TypeDef *) 0X4001 1800)   #define GPIOF ((GPIO_TypeDef *) 0X4001 1C00)   #define GPIOG ((GPIO_TypeDef *) 0X4001 2000)   外设内存映射   讲到基地址的时候我们再引人一个知识点:Cortex-M3存储器系统,这个知识点在《Cortex-M3权威指南》第5章里面讲到。CM3的地址空间是4GB,如下图所示:   我们这里要讲的是片上外设,就是我们所说的寄存器的根据地,其大小总共有512MB,512MB是其极限空间,并不是每个单片机都用得完,实际上各个MCU厂商都只是用了一部分而已。STM32F1系列用到了:0x4000 0000 ~0x5003 FFFF。现在我们说的STM32的寄存器就是位于这个区域   APB1、APB2、AHB 总线基地址   现在我们说的STM32的寄存器就是位于这个区域,这里面ST设计了三条总线:AHB、APB2和APB1,其中AHB和APB2是高速总线,APB1是低速总线。不同的外设根据速度不同分别挂载到这三条总线上。   从下往上依次是:APB1、APB2、AHB,每个总线对应的地址分别是:APB1:0x,APB2:0x4001 0000,AHB:0x4001 8000。   这三条总线的基地址我们是从《STM32 中文参考手册》2.3小节—存储器映像得到的:APB1的基地址是TIM2定时器的起始地址,APB2的基地址是AFIO的起始地址,AHB的基地址是SDIO的起始地址。其中APB1地址又叫做外设基地址,是所有外设的基地址,叫做PERIPH_BASE。   现在我们把这三条总线地址用宏定义出来,以后我们在定义其他外设基地址的时候,只需要在这三条总线的基址上加上偏移地址即可,代码如下:   #define PERIPH_BASE ((uint32_t)0x)   #define APB1PERIPH_BASE PERIPH_BASE   #define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)   #define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)   
stm32串口例程_stm32串口例程中各个文件的结构和调用关系
stm32串口例程_stm32串口例程中各个文件的结构和调用关系   GPIO 端口基地址   因为GPIO挂载到APB2总线上,那么现在我们就可以根据APB2的基址算出各个GPIO端口的基地址,用宏定义实现代码如下:   #define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)#define GPIOC_BASE (APB2PERIPH_BASE + 0x1000)#define GPIOD_BASE (APB2PERIPH_BASE + 0x1400)#define GPIOE_BASE (APB2PERIPH_BASE + 0x1800)#define GPIOF_BASE (APB2PERIPH_BASE + 0x1C00)#define GPIOG_BASE (APB2PERIPH_BASE + 0x2000)   第二层级:基地址宏定义+结构体封装完成用STM32控制一个LED的完整代码:   #include <stdint.h>   #define __IO volatile   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 struct   {   __IO uint32_t CR;   __IO uint32_t CFGR;   __IO uint32_t CIR;   __IO uint32_t APB2RSTR;   __IO uint32_t APB1RSTR;   __IO uint32_t AHBENR;   __IO uint32_t APB2ENR;   __IO uint32_t APB1ENR;   __IO uint32_t BDCR;   __IO uint32_t CSR;   } RCC_TypeDef;   #define PERIPH_BASE ((uint32_t)0x)   #define APB1PERIPH_BASE PERIPH_BASE   #define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)   #define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)   #define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)   #define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)   #define GPIOC_BASE (APB2PERIPH_BASE + 0x1000)   #define GPIOD_BASE (APB2PERIPH_BASE + 0x1400)   #define GPIOE_BASE (APB2PERIPH_BASE + 0x1800)   #define GPIOF_BASE (APB2PERIPH_BASE + 0x1C00)   #define GPIOG_BASE (APB2PERIPH_BASE + 0x2000)   #define RCC_BASE (AHBPERIPH_BASE + 0x1000)   #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)   #define GPIOF ((GPIO_TypeDef *) GPIOF_BASE)   #define GPIOG ((GPIO_TypeDef *) GPIOG_BASE)   #define RCC ((RCC_TypeDef *) RCC_BASE)   #define RCC_APB2ENR *(volatile unsigned long *)0x   #define GPIOB_CRL *(volatile unsigned long *)0x40010C00   #define GPIOB_ODR *(volatile unsigned long *)0x40010C0C   int main(void)   {   // 开启端口B 的时钟   RCC->APB2ENR |= 1<<3;   // 配置PB0 为通用推挽输出模式,速率为2M   GPIOB->CRL = (2<<0) | (0<<2);   // PB0 输出低电平,点亮LED   GPIOB->ODR = 0<<0;   }   void SystemInit(void)   {   }   第二层级变化:   ①、定义一个外设(GPIO)寄存器结构体,结构体的成员包含该外设的所有寄存器,成员的排列顺序跟寄存器偏移地址一样,成员的数据类型跟寄存器的一样。   ②外设内存映射,即把地址跟外设建立起一一对应的关系。   ③外设声明,即把外设的名字定义成一个外设寄存器结构体类型的指针。   ④通过结构体操作寄存器,实现点亮LED。   第三层级:基地址宏定义+结构体封装+“位封装”(每一位的对应字节封装)   上面我们在控制GPIO输出内容的时候控制的是ODR(Output data register)寄存器,ODR是一个16位的寄存器,必须以字的形式控制其实我们还可以控制BSRR和BRR这两个寄存器来控制IO的电平,下面我们简单介绍下BRR寄存器的功能,BSRR自行看手册研究。   
stm32串口例程_stm32串口例程中各个文件的结构和调用关系
stm32串口例程_stm32串口例程中各个文件的结构和调用关系   位清除寄存器BRR只能实现位清0操作,是一个32位寄存器,低16位有效,写0没影响,写1清0。现在我们要使PB0输出低电平,点亮LED,则只要往BRR的BR0位写1即可,其他位为0,代码如下:   GPIOB->BRR = 0X0001;   这时PB0就输出了低电平,LED就被点亮了。   如果要PB2输出低电平,则是:   GPIOB->BRR = 0X0004;   如果要PB3/4/5/6。。。。。。这些IO输出低电平呢?   道理是一样的,只要往BRR的相应位置赋不同的值即可。因为BRR是一个16位的寄存器,位数比较多,赋值的时候容易出错,而且从赋值的16进制数字我们很难清楚的知道控制的是哪个IO。   这时,我们是否可以把BRR的每个位置1都用宏定义来实现,如GPIO_Pin_0就表示0X0001,GPIO_Pin_2就表示0X0004。只要我们定义一次,以后都可以使用,而且还见名知意。“位封装”(每一位的对应字节封装) 代码如下:   #define GPIO_Pin_0 ((uint16_t)0x0001) /*!< Pin 0 selected */   #define GPIO_Pin_1 ((uint16_t)0x0002) /*!< Pin 1 selected */   #define GPIO_Pin_2 ((uint16_t)0x0004) /*!< Pin 2 selected */   #define GPIO_Pin_3 ((uint16_t)0x0008) /*!< Pin 3 selected */   #define GPIO_Pin_4 ((uint16_t)0x0010) /*!< Pin 4 selected */   #define GPIO_Pin_5 ((uint16_t)0x0020) /*!< Pin 5 selected */   #define GPIO_Pin_6 ((uint16_t)0x0040) /*!< Pin 6 selected */   #define GPIO_Pin_7 ((uint16_t)0x0080) /*!< Pin 7 selected */   #define GPIO_Pin_8 ((uint16_t)0x0100) /*!< Pin 8 selected */   #define GPIO_Pin_9 ((uint16_t)0x0200) /*!< Pin 9 selected */   #define GPIO_Pin_10 ((uint16_t)0x0400) /*!< Pin 10 selected */   #define GPIO_Pin_11 ((uint16_t)0x0800) /*!< Pin 11 selected */   #define GPIO_Pin_12 ((uint16_t)0x1000) /*!< Pin 12 selected */   #define GPIO_Pin_13 ((uint16_t)0x2000) /*!< Pin 13 selected */   #define GPIO_Pin_14 ((uint16_t)0x4000) /*!< Pin 14 selected */   #define GPIO_Pin_15 ((uint16_t)0x8000) /*!< Pin 15 selected */   #define GPIO_Pin_All ((uint16_t)0xFFFF) /*!< All pins selected */   这时PB0就输出了低电平的代码就变成了:   GPIOB->BRR = GPIO_Pin_0;   (如果同时让PB0/PB15输出低电平,用或运算,代码:   GPIOB->BRR = GPIO_Pin_0|GPIO_Pin_15;   为了不使main函数看起来冗余,上述库封装 的代码不应该放在main里面,因为其是跟GPIO相关的,我们可以把这些宏放在一个单独的头文件里面。   在工程目录下新建stm32f10x_gpio.h,把封装代码放里面,然后把这个文件添加到工程里面。这时我们只需要在main.c里面包含这个头文件即可。   第四层级:基地址宏定义+结构体封装+“位封装”+函数封装   我们点亮LED的时候,控制的是PB0这个IO,如果LED接到的是其他IO,我们就需要把GPIOB修改成其他的端口,其实这样修改起来也很快很方便。   但是为了提高程序的可读性和可移植性,我们是否可以编写一个专门的函数用来复位GPIO的某个位,这个函数有两个形参,一个是GPIOX(X=A…G),另外一个是GPIO_Pin(0…15),函数的主体则是根据形参GPIOX 和GPIO_Pin来控制BRR寄存器,代码如下:   void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)   {   GPIOx->BRR = GPIO_Pin;   }   这时,PB0输出低电平,点亮LED的代码就变成了:   GPIO_ResetBits(GPIOB,GPIO_Pin_0);   同理, 我们可以控制BSRR这个寄存器来实现关闭LED,代码如下:   // GPIO 端口置位函数   void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)   {   GPIOx->BSRR = GPIO_Pin;   }   这时,PB0输出高电平,关闭LED的代码就变成了:   GPIO_SetBits(GPIOB,GPIO_Pin_0);   同样,因为这个函数是控制GPIO的函数,我们可以新建一个专门的文件来放跟gpio有关的函数。   在工程目录下新建stm32f10x_gpio.c,把GPIO相关的函数放里面。这时我们是否发现刚刚新建了一个头文件stm32f10x_gpio.h,这两个文件存放的都是跟外设GPIO相关的。   C文件里面的函数会用到h头文件里面的定义,这两个文件是相辅相成的,故我们在stm32f10x_gpio.c 文件中也包含stm32f10x_gpio.h这个头文件。别忘了把stm32f10x.h这个头文件也包含进去,因为有关寄存器的所有定义都在这个头文件里面。   如果我们写其他外设的函数,我们也应该跟GPIO一样,新建两个文件专门来存函数,比如RCC这个外设我们可以新建stm32f10x_rcc.c和stm32f10x_rcc.h。其他外依葫芦画瓢即可。   实例编写   以上,是对库封住过程的概述,下面我们正在地使用库函数编写LED程序   ①管理库的头文件   当我们开始调用库函数写代码的时候,有些库我们不需要,在编译的时候可以不编译,可以通过一个总的头文件stm32f10x_conf.h来控制,该头文件主要代码如下:   //#include “stm32f10x_adc.h”   //#include “stm32f10x_bkp.h”   //#include “stm32f10x_can.h”   //#include “stm32f10x_cec.h”   //#include “stm32f10x_crc.h”   //#include “stm32f10x_dac.h”   //#include “stm32f10x_dbgmcu.h”   //#include “stm32f10x_dma.h”   //#include “stm32f10x_exti.h”   //#include “stm32f10x_flash.h”   //#include “stm32f10x_fsmc.h”   #include “stm32f10x_gpio.h”   //#include “stm32f10x_i2c.h”   //#include “stm32f10x_iwdg.h”   //#include “stm32f10x_pwr.h”   #include “stm32f10x_rcc.h”   //#include “stm32f10x_rtc.h”   //#include “stm32f10x_sdio.h”   //#include “stm32f10x_spi.h”   //#include “stm32f10x_tim.h”   //#include “stm32f10x_usart.h”   //#include “stm32f10x_wwdg.h”   //#include “misc.h”   这里面包含了全部外设的头文件,点亮一个LED我们只需要RCC和GPIO 这两个外设的库函数即可,其中RCC控制的是时钟,GPIO控制的具体的IO口。所以其他外设库函数的头文件我们注释掉,当我们需要的时候就把相应头文件的注释去掉即可。   stm32f10x_conf.h这个头文件在stm32f10x.h这个头文件的最后面被包含,在第8296行:   #ifdef USE_STDPERIPH_DRIVER   #include “stm32f10x_conf.h”   #endif   代码的意思是,如果定义了USE_STDPERIPH_DRIVER这个宏的话,就包含stm32f10x_conf.h这个头文件。   我们在新建工程的时候,在魔术棒选项卡C/C++中,我们定义了USE_STDPERIPH_DRIVER 这个宏,所以stm32f10x_conf.h 这个头文件就被stm32f10x.h包含了,我们在写程序的时候只需要调用一个头文件:stm32f10x.h即可。   ②编写LED初始化函数   经过寄存器点亮LED的操作,我们知道操作一个GPIO输出的编程要点大概如下:   1、开启GPIO的端口时钟   2、选择要具体控制的IO口,即pin   3、选择IO口输出的速率,即speed   4、选择IO口输出的模式,即mode   5、输出高/低电平   STM32的时钟功能非常丰富,配置灵活,为了降低功耗,每个外设的时钟都可以独自的关闭和开启。STM32中跟时钟有关的功能都由RCC这个外设控制,RCC中有三个寄存器控制着所以外设时钟的开启和关闭:RCC_APHENR、RCC_APB2ENR和RCC_APB1ENR,AHB、APB2和APB1代表着三条总线,所有的外设都是挂载到这三条总线上,GPIO属于高速的外设,挂载到APB2总线上,所以其时钟有RCC_APB2ENR控制。   GPIO 时钟控制   固件库函数:   RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE)函数的   原型为:   void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph,   FunctionalState NewState)   {   /* Check the parameters */   assert_param(IS_RCC_APB2_PERIPH(RCC_APB2Periph));   assert_param(IS_FUNCTIONAL_STATE(NewState));   if (NewState != DISABLE)   {   RCC->APB2ENR |= RCC_APB2Periph;   }   else   {   RCC->APB2ENR &= ~RCC_APB2Periph;   }   }   当程序编译一次之后,把光标定位到函数/变量/宏定义处,按键盘的F12或鼠标右键的Go to definition of,就可以找到原型。固件库的底层操作的就是RCC外设的APB2ENR这个寄存器,宏RCC_APB2Periph_GPIOB的原型是:0x00000008,即(1<<3),还原成存器操作就是:RCC->APB2ENR |= 1<<<3。相比固件库操作,寄存器操作的代码可读性就很差,只有才查阅寄存器配置才知道具体代码的功能,而固件库操作恰好相反,见名知意。   GPIO 端口配置   GPIO的pin,速度,模式,都由GPIO的端口配置寄存器来控制,其中IO0~IO7由端口配置低寄存器CRL控制,IO8~IO15由端口配置高寄存器CRH配置。固件库把端口配置的pin,速度和模式封装成一个结构体:   typedef struct   {   uint16_t GPIO_Pin;   GPIOSpeed_TypeDef GPIO_Speed;   GPIOMode_TypeDef GPIO_Mode;   } GPIO_InitTypeDef;   pin可以是GPIO_Pin_0~GPIO_Pin_15或者是GPIO_Pin_All,这些都是库预先定义好的宏。speed也被封装成一个结构体:   typedef enum   {   GPIO_Speed_10MHz = 1,   GPIO_Speed_2MHz,   GPIO_Speed_50MHz   } GPIOSpeed_TypeDef;   速度可以是10M,2M或者50M,这个由端口配置寄存器的MODE位控制,速度是针对IO口输出的时候而言,在输入的时候可以不用设置。mode也被封装成一个结构体:   typedef enum   {   GPIO_Mode_AIN = 0x0, // 模拟输入   GPIO_Mode_IN_FLOATING = 0x04, // 浮空输入(复位后的状态)   GPIO_Mode_IPD = 0x28, // 下拉输入   GPIO_Mode_IPU = 0x48, // 上拉输入   GPIO_Mode_Out_OD = 0x14, // 通用开漏输出   GPIO_Mode_Out_PP = 0x10, // 通用推挽输出   GPIO_Mode_AF_OD = 0x1C, // 复用开漏输出   GPIO_Mode_AF_PP = 0x18 // 复用推挽输出   } GPIOMode_TypeDef;   IO口的模式有8种,输入输出各4种,由端口配置寄存器的CNF配置。平时用的最多的就是通用推挽输出,可以输出高低电平,驱动能力大,一般用于接数字器件。   最终用固件库实现就变成这样:   // 定义一个GPIO_InitTypeDef 类型的结构体   GPIO_InitTypeDef GPIO_InitStructure;   // 选择要控制的IO 口   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;   // 设置引脚为推挽输出   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;   // 设置引脚速率为50MHz   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;   /*调用库函数,初始化GPIOB0*/   GPIO_Init(GPIOB, &GPIO_InitStructure);   倘若同一端口下不同引脚有不同的模式配置,每次对每个引脚配置完成后都要调用GPIO初始化函数,代码如下:   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15 ;   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入   GPIO_Init(GPIOB, &GPIO_InitStructure);   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 ;   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;   GPIO_Init(GPIOB, &GPIO_InitStructure);   GPIO 输出控制   GPIO输出控制,可以通过端口数据输出寄存器ODR、端口位设置/清除寄存器BSRR和端口位清除寄存器BRR这三个来控制。端口输出寄存器ODR是一个32位的寄存器,低16位有效,对应着IO0~IO15,只能以字的形式操作,一般使用寄存器操作。   // PB0 输出高电平,点亮LED   GPIOB->ODR = 1<<0;   端口位清除寄存器BRR是一个32位的寄存器,低十六位有效,对应着IO0~IO15,只能以字的形式操作,可以单独对某一个位操作,写1清0。   // PB0 输出低电平,点亮LED   GPIO_ResetBits(GPIOB, GPIO_Pin_0);   BSRR是一个32位的寄存器,低16位用于置位,写1有效,高16位用于复位,写1有效,相当于BRR寄存器。高16位我们一般不用,而是操作BRR这个寄存器,所以BSRR这个寄存器一般用来置位操作。   // PB0 输出高电平,熄灭LED   GPIO_SetBits(GPIOB, GPIO_Pin_0);   综上:固件库LED GPIO初始化函数   void LED_GPIO_Config(void)   {   // 定义一个GPIO_InitTypeDef 类型的结构体   GPIO_InitTypeDef GPIO_InitStructure;   // 开启GPIOB 的时钟   RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE);   // 选择要控制的IO 口   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;   // 设置引脚为推挽输出   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;   // 设置引脚速率为50MHz   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;   /*调用库函数,初始化GPIOB0*/   GPIO_Init(GPIOB, &GPIO_InitStructure);   // 关闭LED   GPIO_SetBits(GPIOB, GPIO_Pin_0);   }   主函数   #include “stm32f10x.h”   void SOFT_Delay(__IO uint32_t nCount);   void LED_GPIO_Config(void);   int main(void)   {   // 程序来到main 函数之前,启动文件:statup_stm32f10x_hd.s 已经调用   // SystemInit()函数把系统时钟初始化成72MHZ   // SystemInit()在system_stm32f10x.c 中定义   // 如果用户想修改系统时钟,可自行编写程序修改   LED_GPIO_Config();   while ( 1 )   {   // 点亮LED   GPIO_ResetBits(GPIOB, GPIO_Pin_0);   Time_Delay(0x0FFFFF);   // 熄灭LED   GPIO_SetBits(GPIOB, GPIO_Pin_0);   Time_Delay(0x0FFFFF);   }   }   // 简陋的软件延时函数   void Time_Delay(volatile uint32_t Count)   {   for (; Count != 0; Count–);   }   注意void Time_Delay(volatile uint32_t Count)只是一个简陋的软件延时函数,如果小伙伴们有兴趣可以看一看MultiTimer,它是一个软件定时器扩展模块,可无限扩展所需的定时器任务,取代传统的标志位判断方式, 更优雅更便捷地管理程序的时间触发时序。   END   闲鱼的物联网开发、STM32百度网盘资料大全打包送人包括物联网开发从基础到实战、IoT-ARM结构下的各类智能产品的设计STM32的开发、全国电赛优秀作品分析等需要的话请自行领取物联网开发:   超全物联网资料 零基础到实战   STM32:   STM32资料大全
stm32串口例程_stm32串口例程中各个文件的结构和调用关系
stm32串口例程_stm32串口例程中各个文件的结构和调用关系
stm32串口例程_stm32串口例程中各个文件的结构和调用关系
stm32串口例程_stm32串口例程中各个文件的结构和调用关系   如果对STM32感兴趣,也可以看看主页的其他文章:SugarlesS:详解五类经典电源电路SugarlesS:STM32串口通信基本原理SugarlesS:温度传感器DS18B20原理,附STM32例程代码SugarlesS:模拟信号采样与AD转换原理SugarlesS:一文读懂20种模拟电路   丨 整理文章为传播相关技术,版权归原作者所有丨   丨如有侵权,请联系删除丨

2024最新激活全家桶教程,稳定运行到2099年,请移步至置顶文章:https://sigusoft.com/99576.html

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。 文章由激活谷谷主-小谷整理,转载请注明出处:https://sigusoft.com/70468.html

(0)
上一篇 2024年 8月 7日
下一篇 2024年 8月 7日

相关推荐

关注微信