概述
STM32F103RBT6为例说明:
1、STM32一共有三种启动模式,相关的配置说明如下
所谓启动,一般来说就是指我们下好程序后,重启芯片时,SYSCLK的第4个上升沿,BOOT引脚的值将被锁存。用户可以通过设置BOOT1和BOOT0引脚的状态,来选择在复位后的启动模式。
-----------------------
1)Main Flash memory
是STM32内置的Flash,一般我们使用JTAG或者SWD模式下载程序时,就是下载到这个里面,重启后也直接从这启动程序。正常工作就在这种模式下,STM32的FLASH可以擦出10万次,所以不用担心芯片哪天会被擦爆!STM32从Flash存储的第一条指令开始执行,即执行STM32的启动代码stm32f10x_vector.s(或stm32F10x_xxx_xxx.s或startup_xxx.s 根据STM32 Firmware library的不同而不同),执行启动代码后会跳到main函数,执行用户程序。
-----------------------
2)System memory
System memory is used to boot the device in System memory boot mode. The area is reserved for use by STMicroelectronics and contains the boot loader which is used to reprogram the Flash memory using the USART1 serial interface. It is programmed by ST when the device is manufactured, and protected against spurious write/erase operations. For further details please refer to AN2606.
从系统存储器启动,这种模式启动的程序功能是由厂家设置的。一般来说,这种启动方式用的比较少。
系统存储器是芯片内部一块特定的区域,STM32在出厂时,由ST在这个区域内部预置了一段BootLoader,也就是我们常说的ISP程序,这是一块ROM,出厂后无法修改。
一般来说,我们选用这种启动模式时,是为了从串口下载程序,因为在厂家提供的BootLoader中,提供了串口下载程序的固件(比如mcuisp.exe),可以通过这个BootLoader将程序下载到系统的Flash中。但是这个下载方式需要以下步骤:
将BOOT0设置为1,BOOT1设置为0,然后按下复位键(或重启),这样才能从系统存储器启动BootLoader;
最后在BootLoader的帮助下,通过串口下载程序到Flash中;
程序下载完成后,又需要将BOOT0设置为GND(通过电阻连到地),手动复位(或重启),这样STM32才可以从Flash中启动。
关于DB9的针脚定义见“通信-RS232、RS485、RS422
之一、RS-232和RS-485的优缺点及针脚定义之3、DB9针脚定义”
-----------------------
3)Embedded SRAM
内置SRAM,既然是SRAM,自然也就没有程序存储的能力了,这个模式一般用于程序调试。
(1)故障的局部诊断,写一段小程序加载到SRAM中诊断板上的其他电路,或用此方法读写板上的Flash或EEPROM等。
(2)通过这种方法解除内部Flash的读写保护,当然解除读写保护的同时Flash的内容也被自动清除,以防止恶意的软件拷贝。
(3)只修改了代码中一个小小的地方,然后就需要重新擦除整个Flash,比较的费时,可以考虑从这个模式启动代码(也就是STM32的内存中),用于快速的程序调试,等程序调试完成后,在将程序下载到Flash中。
-----------------------------------------------
2、启动模式配置附录
STM32的内部闪存(Flash)地址起始于0x08000000,一般情况下,程序文件就从此地址开始写入。此外STM32是基于Cortex-M3内核的微控制器,其内部通过一张“中断向量表”来响应中断,程序启动后,首先从“中断向量表”取出复位中断向量执行复位中断程序完成启动,而这张“中断向量表”的起始地址是0x08000004(0x8003000的程序中,中断向量表的地址0x8003000),当中断来临,STM32的内部硬件机制亦会自动将PC指针定位到“中断向量表”处,并根据中断源取出对应的中断向量执行中断服务程序。
在图53.1.1中,STM32在复位后,先从0X08000004地址取出复位中断向量的地址,并跳转到复位中断服务程序,如图标号所示;在复位中断服务程序执行完之后,会跳转到我们的main函数,如图标号所示;而我们的main函数一般都是一个死循环,在main函数执行过程中,如果收到中断请求(发生重中断),此时STM32强制将PC指针指回中断向量表处,如图标号所示;然后,根据中断源进入相应的中断服务程序,如图标号所示;在执行完中断服务程序以后,程序再次返回main函数执行,如图标号所示。
-------------------------------------------------------------------------------------------------------------------
三、STM32硬件_总线AMBA、AHB、APB
1、AMBA
AMBA(Advanced Microprocessor Bus Architecture)是ARM公司提出的一种开放性的SoC总线标准,现在已经广泛的应用于RISC的内核上了。
AMBA定义了一种多总线系统(multilevel busing system),包括系统总线和等级稍低的外设总线。
AMBA支持32位、64位、128位的数据总线,和32位的地址总线,同时支持byte和half-word设计。
它定义了两种总线:
但是,如果设备A和设备B不具有一致性,那么设备A和设备B就必须挂在两条不同的总线上,这时候我们就需要一个“翻译”,把设备A上的总线上的数据和地址转换成设备B可以解析的格式,然后放到设备B的总线上,这个“翻译”就是“Bus Bridge”,下面这幅图就形象的说明了Bus Bridge在AHB和APB之间的作用。
下面是F105和F107的总线构架:
STM32上APB1和APB2的地址映射
四、STM32_NVIC
1、简述
需要在STM32上移植RTOS,那么首先必须深入理解它的中断系统。什么是NVIC?即嵌套向量中断控制器(Nested Vectored Interrupt Controller)。STM32的中有一个强大而方便的NVIC,它是属于Cortex内核的器件,不可屏蔽中断 (NMI)和外部中断都由它来处理,而SYSTICK不是由 NVIC来控制的。
特性:
60个可屏蔽中断通道(不包含16个Cortex™-M3的中断线);
16个可编程的优先等级(使用了4位中断优先级);
低延迟的异常和中断处理;
电源管理控制;
系统控制寄存器的实现;
-----------------------------------------------
2、中断优先级分组
STM32(Cortex-M3)中有两个优先级的概念--抢占式优先级和响应优先级,有人把响应优先级称作'亚优先级'或'副优先级',每个中断源都需要被指定这两种优先级。
具有高抢占式优先级的中断可以在具有低抢占式优先级的中断处理过程中被响应,即中断嵌套,或者说高抢占式优先级的中断可以嵌套在低抢占式优先级的中断中。
当两个中断源的抢占式优先级相同时,这两个中断将没有嵌套关系,当一个中断到来后,如果正在处理另一个中断,这个后到来的中断就要等到前一个中断处理完之后才能被处理。如果这两个中断同时到达,则中断控制器根据他们的响应优先级高低来决定先处理哪一个;如果他们的抢占式优先级和响应优先级都相等,则根据他们在中断表中的排位顺序决定先处理哪一个。
Cortex内核具有强大的异常响应系统,它把能够打断当前代码执行流程的事件分为异常(exception)和中断(interrupt),并把它们用一个表管理起来,编号为0~15的称为内核异常,而16以上的则称为外部中断,这个表就称为中断向量表。
正是因为每个中断源都需要被指定这两种优先级,就需要有相应的寄存器位记录每个中断的优先级;在Cortex-M3中定义了8个比特位用于设置中断源的优先级,这8个比特位可以有8种分配方式,如下:
1)所有8位用于指定响应优先级
2)最高1位用于指定抢占式优先级,最低7位用于指定响应优先级
3)最高2位用于指定抢占式优先级,最低6位用于指定响应优先级
4)最高3位用于指定抢占式优先级,最低5位用于指定响应优先级
5)最高4位用于指定抢占式优先级,最低4位用于指定响应优先级
6)最高5位用于指定抢占式优先级,最低3位用于指定响应优先级
7)最高6位用于指定抢占式优先级,最低2位用于指定响应优先级
8)最高7位用于指定抢占式优先级,最低1位用于指定响应优先级
以上便是优先级分组的概念,但是Cortex-M3允许具有较少中断源时使用较少的寄存器位指定中断源的优先级。
而STM32对这个表重新进行了编排,把编号从-3至6的中断向量定义为系统异常,编号为负的内核异常不能被设置优先级,如复位(Reset)、不可屏蔽中断 (NMI)、硬错误(Hardfault)。从编号7开始的为外部中断,这些中断的优先级都是可以用户更改的。详细的 STM32中断向量号可以在startup_stm32f10x_XX.s中查找。
因此STM32把指定中断优先级的寄存器位减少到4位,这4个寄存器位的分组方式如下:
第0组:所有4位用于指定响应优先级(16种)
第1组:最高1位用于指定抢占式优先级,最低3位用于指定响应优先级(8种)
第2组:最高2位用于指定抢占式优先级,最低2位用于指定响应优先级(4种)
第3组:最高3位用于指定抢占式优先级,最低1位用于指定响应优先级(2种)
第4组:所有4位用于指定抢占式优先级
这里便对于于文章最前提到的固件库里相关的函数了——NVIC_PriorityGroupConfig(u32 NVIC_PriorityGroup),函数的参数共有5种:
这个函数的参数(NVIC_PriorityGroup值)有下列5种:
NVIC_PriorityGroup_0 => 选择第0组
NVIC_PriorityGroup_1 => 选择第1组
NVIC_PriorityGroup_2 => 选择第2组
NVIC_PriorityGroup_3 => 选择第3组
NVIC_PriorityGroup_4 => 选择第4组
这其实也很好理解,比如选择NVIC_PriorityGroup_1,那么抢占式优先级便占一位,也就是说可以有2^1个级别,可以设置为0和1,而响应优先级则占3位,也就是说可以有2^3个选择,可以设置为0~7;总共来说就可以区别>16种优先级了。
//NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
//NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
-----------------------------------------------
3、举例说明
假如现在有4个外部中断,还有一个EXTI9_5中断,那么如果选择优先级分组为第1组,那么抢占式优先级便只有两种,5个中断就至少有3个在抢占式优先级上是相同的优先级上,其他两个在令一优先级别。接着设置响应优先级可以有8种选择;假如现在同时有两个抢占式优先级别相同的中断发生,那么处理的顺序是谁的响应优先级高则谁优先进入中断,另外这点是需要注意的,如果此时进入这个中断之后又来了一个抢占式优先级相同但是响应优先级更高的中断,这时也是不会打断已有的中断的。
void NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
#ifdef VECT_TAB_RAM
//Set the Vector Table base location at 0x20000000
NVIC_SetVectorTable(NVIC_VectTab_RAM, 0x0);
#else
//Set the Vector Table base location at 0x08000000
NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0);
#endif
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); //中断优先级组 :1组(整个系统为同一组)
// 设置先占优先级0~1,响应优先级0~7
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// Enable the TIM3 Interrupt
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; // TIM3 全局中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 先占优先级 1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; // 从优先级 1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // IRQ通道被使能
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //先占优先级0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //响应优先级0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
-------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------
1、串口中断编程过程
本文以USART1为例,叙述串口中断的编程过程。
1)应用串口中断时涉及到的一些库文件
首先对于STM32外设库文件的应用编程,misc.c和stm32f10x_rcc.c是肯定要添加到。
接下来就是我们要用到的相关外设了。毫无疑问,串口文件stm32f10x_usart.c是必须的。串口通信是对通用GPIO端口引脚的功能复用,所以还需要stm32f10x_gpio.c文件。另外,因为有中断的产生,所以中断文件stm32f10x_it.c也是必要的,当然这个文件一般和main.c放在一个文件夹下(一般习惯为User文件夹),因为我们的中断响应函数是要在里面自己编写的。
当然还有其他的基本必须文件如系统配置文件等在这地方就不说了,这个是创建一个工程应该知道的。
-----------------------
2)初始化
对于串口通信的初始化,不仅仅只是对串口的初始化(这个地方是比较烦人的,不像别的芯片那样简洁明了)。
(1)首先时钟使能配置。STM32内部的时钟有很多,感兴趣的自己看看参考手册。此处以USART1为例说明。有USART1时钟、GPIOA时钟、GPIO复用(AFIO)时钟。由于此处USART1和GPIOA、AFIO均在APB2上,所以可以一次配置完成。如下:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO|RCC_APB2Periph_USART1 ,ENABLE);
(2)其次中断配置。主要有优先级组设定、USART1中断使能、该中断的优先级,中断初始化。程序如下:
void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);//选择分组方式0
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
(3)然后GPIO复用功能配置。一般情况下我们使用原始的外设和GPIO端口引脚的映射关系,如果要改变其映射的话,请另外查看参考手册上关于GPIO重映射部分。对于GPIO的复用,其引脚的输入与输出模式都有要求,在参考手册上有详细说明。
void GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(USARTy_GPIO, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(USARTy_GPIO, &GPIO_InitStructure);
}
(4)串口初始化配置。主要有串口基本参数配置(如波特率、数据位、工作方式等),串口中断使能,串口使能。
基本参数配置
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 9600;//波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//数据长度
USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//校验
USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;//无硬件流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //发送与接受两种方式
USART_Init(USART1, &USART_InitStructure);//用配置的参数激活串口初始化
串口中断使能
USART_ITConfig(USARTy, USART_IT_RXNE, ENABLE);//使能接受中断,在接受移位寄存器中有数据时产生
USART_ITConfig(USARTy, USART_IT_TXE, ENABLE);//使能发送中断,在发送完数据 后产生。
一般情况下,如果与PC通信的话,我们只用接受中断即可。
串口使能
USART_Cmd(USART1, ENABLE); //USART1使能
好了,经过以上步骤之后呢,我们就可以进行数据的收发了。
-----------------------
3)发送数据
//使用函数USART_SendData(USART1, char data),一次只能发送一个字符。当然我们可以用如下函数发送字符串。
void USART1_Puts(char * str)
{
while(*str)
{
USART_SendData(USART1, *str++); //发送一个字符
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); //等待发送完毕
}
}
//当然我们也可以循环发送字符串数组
for(i = 0; TxBuf1 != '