STM32

STM32是STMicroelectronics(意法半导体)推出的一系列基于ARM Cortex-M内核的32位微控制器(MCU)产品。这些微控制器提供了广泛的产品系列,覆盖了多种不同的性能和功能需求,适用于各种应用领域,包括工业控制、汽车电子、消费类电子、医疗设备等。

STM32系列微控制器以其高性能、低功耗、丰富的外设接口和灵活的开发工具而闻名。它们通常具有丰富的存储器、多种通信接口(如UART、SPI、I2C、CAN等)、模拟数字转换器(ADC)、定时器、PWM输出等功能,以满足不同应用场景下的需求。

STM32微控制器通常使用标准的ARM Cortex-M内核,包括Cortex-M0、M0+、M3、M4和M7等,这些内核具有不同的性能和功耗特性,可根据具体应用的需求进行选择。此外,STM32系列还提供了多种封装和引脚配置,以满足不同尺寸和集成度的要求。

STMicroelectronics为STM32系列提供了丰富的开发工具和支持资源,包括基于ARM开发环境的集成开发环境(IDE)、调试器、评估板和参考设计等。这些工具和资源有助于开发人员快速开发和部署他们的应用,并提供了全面的技术支持和文档资料,帮助用户充分发挥STM32微控制器的性能和功能优势。

作者: Miler Shao

来源:茶话MCU 微信号:stmcu832

所有的STM32芯片中都带有逐次逼近型ADC模块,关于它的应用非常广泛和频繁。不过,应用过程中时常也会遇到些问题,这尽力小结下,与大家分享出来算作一些提醒。

1、Vdda没有供电或没有正常供电;STM32系列众多,该参数不可一概而论,细节请参考各个芯片数据手册。



2、采样电阻取值不合适,跟采样时间不匹配,经常表现为输入电阻过大、配置的采样时间偏短。实际设计时可以参考下STM32官方各系列评估板的相关电路。另外可以参考ST官方的应用笔记AN2834。关于ADC 应用其它的应用笔记,可以去WWW.STMCU.COM.CN搜索ADC即可。


3、ADC上电开启到稳定需要一段时间,即Tstab,该参数在数据手册里有介绍。在使用寄存器操作时要特别注意这个时间。另外要注意给ADC外设上电、使能ADC功能、启动ADC转换、实质AD转换是不一样的动作和不同的时间点。


4、输入信号幅度超过ADC参考电压范围导致转换结果的数据错误。

5、芯片供电的波动尤其VREF的波动和外来干扰都会导致ADC转换值的异常。

6、在使用注入触发转换时,触发事件的时间间隔必须大于注入转换序列所需的转换时间。比方有两个注入通道所需转换时间为28 ADCLK,那触发事件的间隔必须大于28个ADCLK,比方29,30 个ADCLK等都可以。

7、大多数STM32的ADC模块在使用前需要校准。校准须在启动AD转换之前完成。原则上给ADC外设上电后校准一次就够,但当参考电压波动较大、温度变化较剧烈时需再次校准。

8、开启ADC的DMA功能,建议在ADC校准之后进行。换句话说校准ADC前不要使能其ADC的DMA功能。尤其涉及到多通道ADC DMA传输时要注意这个次序。

9、如果使用ADC的DMA传输,在启动AD转换时,DMA需配置好且被使能待命。

10、当使用内部SENSOR ADC通道时,注意这些通道从开启到稳定跟开启ADC模块一样都是需要时间的;针对这些特定传感器通道的AD采样时间,手册里往往有相关参数明确告知,请参照使用。比方内部温度传感器通道的采样时间推荐为17us.

11、ADC通道序列的修改应该保证在ADC的停止状态下进行。

12、在多通道ADC DMA传输时,经常出现因为缓冲区数据类型、源数据类型不一致导致的异常状况。这里主要是因为数据宽度不一致所导致的问题。

13、当外部信号被选择为注入转换的触发信号时,只有其上升沿才有效。

上面提到的都只是抛砖引玉的提醒, 设计应用时多留意下,特别是第7、8、9、12四点提醒。STM32的ADC外设在不同系列间也不完全相同,尤其涉及多个ADC模块配合采样转换的时候还是挺复杂的。任何时候都不忘多查看STM32英文参考手册和数据手册。

本文转自:茶话MCU(微信号:stmcu832),作者: Miler Shao,转载此文目的在于传递更多信息,版权归原作者所有。

围观 1070

STM32F1xx官方资料:
《STM32中文参考手册V10》-第8章通用和复用功能IO(GPIO和AFIO)
《Cortex-M3权威指南(中文)》第5章 位带操作

硬件连接


假设跑马灯实验的硬件连接如上图所示,LED0连接PB5,LED1连接PE5。由于在LED的另一端是VCC3.3,所以当PB5或PE5为低电平的时候,LED灯会亮。此时GPIO应采取推挽输出的模式。

GPIO的相关配置寄存器

STM32的每组GPIO口包括7个寄存器。也就是说,每个寄存器可以控制一组GPIO的16个GPIO口。

这7个寄存器分别为:
- GPIOx_CRL:端口配置低寄存器(32位)
- GPIOx_CRH:端口配置高寄存器(32位)
- GPIOx_IDR:端口输入寄存器(32位)
- GPIOx_ODR:端口输出寄存器(32位)
- GPIOx_BSRR:端口位设置/清除寄存器(32位)
- GPIOx_BRR:端口位清除寄存器(16位)
- GPIOx_LCKR:端口配置锁存寄存器(32位)

端口配置低寄存器(GPIOx_CRL)


由于每个GPIO口需要4位来进行配置输入输出模式(2位配置MODE,2位配置CNF),这样的话每组16个GPIO口则需要64位,这也就表明需要两个32位寄存器。于是GPIOx_CRL用于配置GPIO0-GPIO7的输入输出模式。同理GPIOx_CRH则用于配置GPIO8-GPIO15的输入输出模式。

同时对于上面的这个表可以总结出端口位配置的信息:


需要注意的是,当MODE选择00,CNF为选择10时,代表着上拉/下拉输入模式。到底是上拉还是下拉呢?此时需要PxODR(端口输出寄存器)来确定,0为下拉输入,1为上拉输入。

端口输入寄存器(GPIOx_IDR)


IDR寄存器低16位,每个位控制该组GPIO口的一个IO口,对应的是该IO口的输入电平。在输入模式下,可以读取I/O端口的电平值;在输出模式下,也可以读取I/O端口的电平值(在开漏输出时,读取到的I/O端口的电平值,不一定就是输出的电平值)。

端口输出寄存器(GPIOx_ODR)


ODR寄存器的低16位,每个位控制该组GPIO口的一个IO口,对应的是该IO口的输出电平。在输出模式下,可以通过写寄存器的值,来达到某个IO口的电平输出;在输入模式下,还可以通过写值,来确定是上拉还是下拉输入模式。

端口位设置/清除寄存器(GPIOx_BSRR)


在GPIO的开漏输出模式或者推挽输出模式下,都可以直接给ODR寄存器赋值来进行某个IO口的电平输出;同时,也可以通过对BSRR进行赋值来达到对ODR寄存器的控制来进行对IO口的电平输出。其实,BSRR寄存器的底层也是对ODR寄存器的控制。

BSRR寄存器的低16位可以只对ODR赋1,高16位可以只对ODR赋0。这样的好处是,只可以对ODR的某些特定位产生影响,而不对其他的位产生影响。而且可以一次性对ODR的许多位同时进行控制。

端口位清除寄存器(GPIOx_BRR)


BRR寄存器的功能其实和BSRR寄存器的高16位的功能是一样的,通常情况下,使用BSRR寄存器的低16位来赋1,使用BRR寄存器来赋0。

GPIO的寄存器版本

1. 使能IO口时钟。配置寄存器RCC_APB2ENR;
2. 初始化IO口模式。配置寄存器GPIOx_CRH/CRL;
3. 操作IO口,输出高低电平。配置寄存器GPIOX_ODR或者BSRR/BRR。

具体的程序内容:

void LED_Init(void){
	RCC->APB2ENR|=1<<3;
	RCC->APB2ENR|=1<<6;
	
	GPIOB->CRL&=0xFF0FFFFF;
	GPIOB->CRL|=0x00300000;
	
	GPIOB->ODR|=1<<5;
	
	GPIOE->CRL&=0xFF0FFFFF;
	GPIOE->CRL|=0x00300000;
	
	GPIOE->ODR|=1<<5;
}
 int main(void)
 {	
	LED_Init();
	delay_init();
	while(1){
		GPIOB->ODR|=1<<5;
		GPIOE->ODR|=1<<5;
		
		delay_ms(500);
		
		GPIOB->ODR&=~(1<<5);
		GPIOE->ODR&=~(1<<5);
		
		delay_ms(500);
			
	}
 }

GPIO的库函数版本

需引用的文件:stm32f10x_gpio.h、stm32f10x_rcc.h、misc.h

需定义的文件:led.h

GPIO库函数介绍

  •  1个初始化函数:

void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);

作用:初始化一个或者多个IO口(同一组)的工作方式和速度。该函数主要是操作GPIO_CRL(CRH)寄存器,在上拉或者下拉的时候有设置BSRR或者BRR寄存器 。

注意:外设(包括GPIO)在使用之前,几乎都要先使能对应的时钟。

  •  2个读取输入电平函数:

uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);

作用:读取某个(某组)GPIO的输出电平。实际操作的是GPIO_ODR寄存器。

  •  4个设置输出电平函数:

void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);

作用:设置某个IO口输出为高电平(低电平)。实际操作BSRR寄存器。后两个函数的作用类似。

GPIO库函数版本的跑马灯

1. 使能IO口时钟。调用函数RCC_APB2PeriphColckCmd();

2. 初始化IO口模式。调用函数GPIO_Init();

3. 操作IO口,输出高低电平。调用函数GPIO_SetBits();GPIO_ResetBits()。

具体的程序内容:

void LED_Init(void){
	GPIO_InitTypeDef GPIO_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,ENABLE);
	
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_5;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructure);
	
	GPIO_SetBits(GPIOB,GPIO_Pin_5);
	
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_5;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOE,&GPIO_InitStructure);
	
	GPIO_SetBits(GPIOE,GPIO_Pin_5);
	
}
 int main(void)
 {	
	LED_Init();
	delay_init();
	while(1){
		GPIO_SetBits(GPIOB,GPIO_Pin_5);
		GPIO_SetBits(GPIOE,GPIO_Pin_5);
		
		delay_ms(500);
		
		GPIO_ResetBits(GPIOB,GPIO_Pin_5);
		GPIO_ResetBits(GPIOE,GPIO_Pin_5);
		
		delay_ms(500);
		
	}
 }

GPIO的位操作版本

位操作的原理

把每位膨胀为一个32位的地址,当访问这些地址的时候就达到了访问该位的目的。比如说BSRR寄存器有32个位,那么可以映射到32个地址上,我们去访问(读-改-写)这32个地址就达到访问32个比特的目的。

也就是说,位操作就是可以读、写单独的一个比特位,由于在STM32中没有像51单片机的sbit来实行位定义,但是它可以通过位带别名区来实现。 


哪些区域支持位操作:

SRAM 区的最低 1MB 范围,0x20000000 ‐ 0x200FFFFF(SRAM区中的最低 1MB);
片内外设区的最低 1MB范围,0x40000000 ‐ 0x400FFFFF(片上外设区中最低 1MB)。
位带区:支持位带操作的地址区

位带别名:对别名地址的访问最终作用到位带区的访问上(注意:这中间有一个地址映射过程)

这两个1MB的空间可以像普通RAM一样操作外(修改内容时用读、改、写),它们还有自己的位带别名区,位带别名区把这1MB的空间的每一位膨胀为一个32位的字。确切的说,这个字就是一个地址,当操作这个地址时,就可以达到操作这个位带区某个位的目的。 

在位带区中,每个比特位都映射到别名地址区的一个地址,注意,这只是只有LSB有效的字(最低一位有效的字)。当一个别名地址被访问时,会把该地址转换为为位带操作。

映射方式

对片内外设位带区的某个比特位,记它的所在字节的地址为A,位序号为n(0<=n<=7),则该比特位在别名区的地址为:

AliasAddr = 0x42000000 + ((A - 0x40000000) * 8 + n) * 4
          = 0x42000000 + (A - 0x40000000) * 32 + n * 4

一开始,我对n(0<=n<=7)很不理解,既然n表示位序号,为什么不是0<=n<=31呢?其实是我忽略了“所在字节”四个字,也就是说在位带区中,不是以一个寄存器一个寄存器为分隔单元,而是以一个字节一个字节来分隔单元的。 

对于映射的公式,稍微解释一下:

1. A - 0x40000000 = 当前字节偏离外设基地址的偏移字节数 ;

2. 偏移字节数 * 8 = 偏移了多少位 ;

3. 因为位带区每一位对应位带别名区的一个地址(32位4字节),而地址是以字节计算的,所以位带别名区对应偏移量最后一个的地址 = 偏移了多少位 * 4 ;

4. n * 4 = 偏移量后面的n位对应位带别名区的地址。
同理,对于SRAM位带区的某个比特位,记它所在字节地址为A,位序号为n(0<=n<=7),则该比特位在别名区的地址为:

AliasAddr = 0x22000000 + ((A - 0x20000000) * 8 + n) * 4 
          = 0x22000000 + (A - 0x20000000) * 32 + n * 4

只是对位带基地址和位带别名区基地址做了改变即可。

为了方便操作,我们可以把这两个公式合并成一个公式,把“位带地址 + 位序号”转换成别名地址:

//把位带区地址 + 位序号转换成位带别名去的宏
#define BITBAND(addr, bit_num) ((addr & 0xf0000000) + 0x02000000 + ((addr & 0x00ffffff) << 5) + (bit_num << 2))

位带操作的优越性


位带操作可以简化跳转的判断。比如之前需要跳转到某一位时,必须这样做:
- 读取整个寄存器;
- 掩蔽不需要的位;
- 比较并跳转。

现在使用位带操作只需要:
- 从位带别名区读取状态位;
- 比较并跳转。

在SYSTEM文件夹的sys.h文件中,对GPIO输入输出部分功能实现了位带操作。具体的实现过程如下面的程序所示:

#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 
#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum)) 
//IO口地址映射
#define GPIOA_ODR_Addr    (GPIOA_BASE+12) //0x4001080C  
#define GPIOA_IDR_Addr    (GPIOA_BASE+8) //0x40010808 
 
//IO口操作,只对单一的IO口!
//确保n的值小于16!
#define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)  //输出 
#define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)  //输入 

通过PAout(n)实现对GPIOA_ODR寄存器的第n位赋值的功能,通过PAin(n)实现对GPIOA_IDR寄存器的第n位赋值的功能。按照这种写法,之前库函数版本的主程序可以改写成:

 int main(void)
 {	
	LED_Init();
	delay_init();
	while(1){
		PBout(5)=1;
		PEout(5)=1;
		
		delay_ms(500);
		
		PBout(5)=0;
		PEout(5)=0;
		
		delay_ms(500);
		
	}
 }

本文转自:CSDN - Yngz_Miao,转载此文目的在于传递更多信息,版权归原作者所有。

围观 834

背景

STM32G071的PD0,PD2作为外部中断使用,外部接10K上拉电阻拉到3.3V。外部设备被触发后电平变为低电平,平常保持高电平信号。

问题

在以上背景下,按道理外部设备正常时(未触发中断),IO口输入电平应该是3.3V。但是实际上测量到的却是0.9V,这跟触发后的0V,同样会被单片机识别为低电平,故无法产生电平跳变而触发中断。

分析解决


这个引脚的电平类型为FT_c,我们知道FT是容忍5V的意思,那么FT_c是什么意思?不妨看看说明:


然后去用户手册查看GPIO相关说明,找到以下内容:


基本上了解状况了,再看一下相关寄存器说明:


具体说明:


看了以上说明,基本明白怎么操作了。在初始化IO的时候,应该把SYSCFG registers的UCPD2_STROBE位设置一下就ok了。

HAL库操作:HAL_SYSCFG_StrobeDBattpinsConfig(SYSCFG_CFGR1_UCPD2_STROBE);

LL库操作:MODIFY_REG(SYSCFG->CFGR1, (SYSCFG_CFGR1_UCPD1_STROBE | SYSCFG_CFGR1_UCPD2_STROBE), SYSCFG_CFGR1_UCPD2_STROBE);

再提醒下,其实PA8和PA15也是这样的。如果我们不需要这个下拉电阻,需要按照上面的操作调整一下。

那么为什么STM32G071有这样的设置呢?看下面IO功能即可知道,这几个引脚其实是下面几个管脚的复用脚。



即TYPE-C充电管理的相关引脚,所以才会有这样的设置。若想进一步了解TYPE-C相关知识,可以自行查找相关资料。

来源:STM32单片机,作者:小马哥,转载此文目的在于传递更多信息,版权归原作者所有。

围观 1207

作者:郭老师

之前由于工作需要,基于 RT-Thread 在 STM32 上实现了 USB 虚拟串口。为了方便大家,我在这里把在正点原子 F429 阿波罗开发板上实现 USB 虚拟串口的详细过程分享给大家,希望可以帮助到更多想要学习 USB 的人。

1、首先,需要更新了一下 RT-Thread 的源代码(因为 RT-Thread 的代码更新很快,短时间内就有可能有很多的代码更新,Github地址:https://github.com/RT-Thread/rt-thread 点star还能领10元柿饼派优惠券)

2、然后进入 rt-thread\bsp\stm32目录下,找到正点原子 F429 阿波罗开发板对应的BSP stm32f429-atk-apollo ,打开此目录。


3、然后查看一下当前 BSP 支持不支持 USB 功能。在当前目录下打开 Env 工具,输入 menuconfig 命令查看,可以看到在硬件配置的片上外设的配置菜单中并没有配置 USB 的选项,看来这个 BSP 还不支持 USB 设备。


4、想到新的 STM32 BSP 所有的 BSP 都是用的同一份驱动,这样就可以根据有没有做好的 USB 驱动来判断有没有 BSP 支持 USB 功能了。打开rt-thread\bsp\stm32\libraries\HAL_Drivers目录。如下所示,可以看到里面果然有 usb 的驱动文件,叫做drv_usbd_fs.c。


5、然后根据同一目录下的 Sconscript 脚本文件,可以查看这个驱动的依赖关系,根据下面的图片可以看出,此驱动文件依赖于 BSP_USING_USBD_FS这个配置项。


6、全局搜索此 stm32 目录下所有的 BSP ,查看哪个 bsp 下有这个配置项。根据这个配置项可以判断出哪个 BSP 支持了 USB 的功能,也可以借此看出依赖关系。搜索发现 F469 的 bsp 有这个配置项,由下图可以看出:打开这个配置的同时,也利用 select 命令打开了 RT_USING_USB_DEVICE 这个配置。


7、修改正点原子 F429 阿波罗 bsp 下 Kconfig 文件,添加这一段配置项。



8、然后,利用 Env 工具根据修改好的配置菜单配置工程。在 目录下输入 menuconfig 配置工程,开启刚刚添加的 usb 驱动的配置项。


9、然后,进入组件配置菜单下设备驱动的配置菜单中的 USB 配置,配置 usb 设备框架的选项。开启虚拟串口。


10、保存并重新生成工程。 发现编译报错。看起来是硬件没有配置。需要打开 stm32CubMX 配置 usb 的硬件引脚。


11、打开stm32f429-atk-apollo\board\CubeMX_Config目录下 stm32CubMX 的工程,配置 usb.



12、开启 usb 功能之后,时钟配置报警告,还需重新配置一下时钟。配置好之后,重新生成代码。


13、由于更新了时钟树,所以还要把stm32f429-atk-apollo\board\CubeMX_Config\Src目录下main.c中的时钟配置函数SystemClock_Config更新到stm32f429-atk-apollo\board目录下的 board.c 文件中。


14、然后重新打开工程,编译,发现还是报错,cannot open source input file "stm32f4xx_hal_exti.h": No such file or directory,注释掉报错的头文件重新编译即可。再次编译,发现没有问题了。



15、下载运行,输入 list_device 命令可以看到注册到系统中的两个 usb 相关的设备。


16、然后在 main函数里添加一段测试代码,编译下载运行。

 1  #include <rtthread.h>
 2  #include <rtdevice.h>
 3
 4  int main(void)
 5  {
 6      rt_device_t dev = RT_NULL;
 7      char buf[] = "hello rt-thread!\r\n";
 8
 9      dev = rt_device_find("vcom");
10
11      if (dev)
12          rt_device_open(dev, RT_DEVICE_FLAG_RDWR);
13      else
14          return -RT_ERROR;
15
16      while (1)
17      {
18          rt_device_write(dev, 0, buf, rt_strlen(buf));
19          rt_thread_mdelay(500);
20      }
21
22      return RT_EOK;
23  }

17、连接开发板上的 USB_SLAVE 接口到电脑上,打开设备管理器,发现多了一个 USB 串行设备,用串口工具打开,就可以接收到从 main 函数里发送过来的消息了。



这样就基于 RT-Thread 在 STM32 上实现 USB 虚拟串口了!

本文转自:RTThread物联网操作系统,作者:郭老师,转载此文目的在于传递更多信息,版权归原作者所有。

围观 2817

作者: strongerHuang

最近有朋友问了些关于STM32复位的问题,今天结合前面文章再次总结一下复位相关知识。

1、STM32的复位和时钟控制

RCC:Reset and Clock Control

每一块STM32中都有这么一个RCC复位和时钟控制模块。

STM32的复位为三类:系统复位、电源复位和后备域复位。

系统复位:

1. NRST引脚上的低电平(外部复位)
2. 窗口看门狗计数终止(WWDG复位)
3. 独立看门狗计数终止(IWDG复位)
4. 软件复位(SW复位)
5. 低功耗管理复位

电源复位:

1. 上电/掉电复位(POR/PDR复位)
2. 从待机模式中返回

备份区域复位:

1. 软件复位,备份区域复位可由设置备份域控制寄存器(RCC_BDCR)中的BDRST位产生。
2. 在VDD和VBAT两者掉电的前提下, VDD或VBAT上电将引发备份区域复位。

2、STM32的复位来源

在很多应用中,都会判断是什么引起的复位。

比如:判断为看门狗引起的复位,我们进行xxx操作。软件引起的复位,我们又执行xxx操作。

在STM32RCC模块中,有这么一个寄存器:控制/状态寄存器 (RCC_CSR):


这个寄存器就会记录各种复位的状态,我们直接读取这个寄存器(库函数有读寄存器接口)就能知道是什么引起的复位。

3、STM32的复位来源例程

之前我提供了一个简单Demo,STM32F103ZE(Keil)_复位来源(寄存器版):

http://pan.baidu.com/s/1hskScba


4、STM32系统和内核复位

内核复位:它会使STM32内核(Cortex-M)进行复位,而不会影响其外设,如GPIO、TIM、USART、SPI等这些寄存器的复位。

系统复位:这个复位会使整个芯片的所有电路都进行复位,系统默认的函数接口NVIC_SystemReset就是系统复位(位于core_cm*.h)。

1. NVIC_CoreReset内核复位

CM3 允许由软件触发复位序列,用于特殊的调试或维护目的。在CM3中,有两种方法可以执行自我复位。第一种方法,是通过置位 NVIC 中应用程序中断与复位控制寄存器(AIRCR)的VECTRESET 位(位偏移:0)。

这种复位的作用范围覆盖了整个CM3处理器中,除了调试逻辑之外的所有角落,但是它不会影响到 CM3 处理器外部的任何电路,所以单片机上的各片上外设和其它电路都不受影响。

C语言版函数:

void NVIC_CoreReset(void)
{
  __DSB();
  
  //置位VECTRESET
  SCB->AIRCR  = ((0x5FA << SCB_AIRCR_VECTKEY_Pos)      |
                 (SCB->AIRCR & SCB_AIRCR_PRIGROUP_Msk) |
                 SCB_AIRCR_VECTRESET_Msk);
  __DSB();
  while(1);
}

汇编版函数:

__asm void NVIC_CoreReset_a(void)
{
  LDR R0, =0xE000ED0C
  LDR R1, =0x05FA0001  //置位VECTRESET
  STR R1, [R0]

deadloop_Core
  B deadloop_Core
}

内核主要注意:

SCB_AIRCR_VECTRESET_Msk

LDR R1, =0x05FA0001

它是和系统复位唯一的区别。

2. NVIC_SysReset系统复位

系统复位是置位同一个寄存器中的 SYSRESETREQ 位。这种复位则会波及整个芯片上的电路:它会使 CM3 处理器把送往系统复位发生器的请求线置为有效。但是系统复位发生器不是CM3的一部分,而是由芯片厂商实现,因此不同的芯片对此复位的响应也不同。因此,读者需要认真参阅该芯片规格书,明白当发生片内复位时,各外设和功能模块都会回到什么样的初始状态,或者有哪些功能模块不受影响(比如, STM32系列的芯片有后备存储区,该区就被特殊对待)。

大多数情况下,复位发生器在响应 SYSRESETREQ 时,它也会同时把 CM3 处理器的系统复位信号(SYSRESETn)置为有效。通常, SYSRESETREQ 不应复位调试逻辑。

这里有一个要注意的问题:从 SYSRESETREQ 被置为有效,到复位发生器执行复位命令,往往会有一个延时。在此延时期间,处理器仍然可以响应中断请求。但我们的本意往往是要让此次执行到此为止,不要再做任何其它事情了。所以,最好在发出复位请求前,先把FAULTMASK置位。因此,我在提供源代码中有这么一句:__set_FAULTMASK(1);,也就是置位FAULTMASK。

C语言版函数:

void NVIC_SysReset(void)
{
  __DSB();

  SCB->AIRCR  = ((0x5FA << SCB_AIRCR_VECTKEY_Pos)      |
                 (SCB->AIRCR & SCB_AIRCR_PRIGROUP_Msk) |
                 SCB_AIRCR_SYSRESETREQ_Msk);
  __DSB();
  while(1);
}

汇编版函数:

__asm void NVIC_SysReset_a(void)
{
  LDR R0, =0xE000ED0C
  LDR R1, =0x05FA0004
  STR R1, [R0]

deadloop_Sys
  B deadloop_Sys
}

内核复位与系统源代码和相近,差异在于SYSRESETREQ和SYSRESETREQ这两位。

关于复位的知识,在实际项目中应用的比较多。

可以结合上面提供例程理解,以及结合Cortex-M手册理解。

本文转自:公众号『strongerHuang』,转载此文目的在于传递更多信息,版权归原作者所有。

围观 1095

STM32F1xx官方资料:
《STM32中文参考手册V10》-第8章通用和复用功能IO(GPIO和AFIO )
芯片数据手册(datasheet)

STM32的GPIO介绍

STM32引脚说明

GPIO是通用输入/输出端口的简称,是STM32可控制的引脚。GPIO的引脚与外部硬件设备连接,可实现与外部通讯、控制外部硬件或者采集外部硬件数据的功能。

STM32F103ZET6芯片为144脚芯片,包括7个通用目的的输入/输出口(GPIO)组,分别为GPIOA、GPIOB、GPIOC、GPIOD、GPIOE、GPIOF、GPIOG,同时每组GPIO口组有16个GPIO口。通常简略称为PAx、PBx、PCx、PDx、PEx、PFx、PGx,其中x为0-15。

STM32的大部分引脚除了当GPIO使用之外,还可以复用位外设功能引脚(比如串口),这部分在【STM32】STM32端口复用和重映射(AFIO辅助功能时钟) 中有详细的介绍。

GPIO基本结构

每个GPIO内部都有这样的一个电路结构,这个结构在本文下面会具体介绍。


这边的电路图稍微提一下:

保护二极管:IO引脚上下两边两个二极管用于防止引脚外部过高、过低的电压输入。当引脚电压高于VDD时,上方的二极管导通;当引脚电压低于VSS时,下方的二极管导通,防止不正常电压引入芯片导致芯片烧毁。但是尽管如此,还是不能直接外接大功率器件,须加大功率及隔离电路驱动,防止烧坏芯片或者外接器件无法正常工作。

P-MOS管和N-MOS管:由P-MOS管和N-MOS管组成的单元电路使得GPIO具有“推挽输出”和“开漏输出”的模式。这里的电路会在下面很详细地分析到。
TTL肖特基触发器:信号经过触发器后,模拟信号转化为0和1的数字信号。但是,当GPIO引脚作为ADC采集电压的输入通道时,用其“模拟输入”功能,此时信号不再经过触发器进行TTL电平转换。ADC外设要采集到的原始的模拟信号。

这里需要注意的是,在查看《STM32中文参考手册V10》中的GPIO的表格时,会看到有“FT”一列,这代表着这个GPIO口时兼容3.3V和5V的;如果没有标注“FT”,就代表着不兼容5V。

STM32的GPIO工作方式

GPIO支持4种输入模式(浮空输入、上拉输入、下拉输入、模拟输入)和4种输出模式(开漏输出、开漏复用输出、推挽输出、推挽复用输出)。同时,GPIO还支持三种最大翻转速度(2MHz、10MHz、50MHz)。

每个I/O口可以自由编程,但I/O口寄存器必须按32位字被访问。

1. GPIO_Mode_AIN 模拟输入

2. GPIO_Mode_IN_FLOATING 浮空输入

3. GPIO_Mode_IPD 下拉输入

4. GPIO_Mode_IPU 上拉输入

5. GPIO_Mode_Out_OD 开漏输出

6. GPIO_Mode_Out_PP 推挽输出

7. GPIO_Mode_AF_OD 复用开漏输出

8. GPIO_Mode_AF_PP 复用推挽输出

下面将具体介绍GPIO的这八种工作方式:

浮空输入模式


浮空输入模式下,I/O端口的电平信号直接进入输入数据寄存器。也就是说,I/O的电平状态是不确定的,完全由外部输入决定;如果在该引脚悬空(在无信号输入)的情况下,读取该端口的电平是不确定的。

上拉输入模式


上拉输入模式下,I/O端口的电平信号直接进入输入数据寄存器。但是在I/O端口悬空(在无信号输入)的情况下,输入端的电平可以保持在高电平;并且在I/O端口输入为低电平的时候,输入端的电平也还是低电平。

下拉输入模式


下拉输入模式下,I/O端口的电平信号直接进入输入数据寄存器。但是在I/O端口悬空(在无信号输入)的情况下,输入端的电平可以保持在低电平;并且在I/O端口输入为高电平的时候,输入端的电平也还是高电平。

模拟输入模式


模拟输入模式下,I/O端口的模拟信号(电压信号,而非电平信号)直接模拟输入到片上外设模块,比如ADC模块等等。

开漏输出模式


开漏输出模式下,通过设置位设置/清除寄存器或者输出数据寄存器的值,途经N-MOS管,最终输出到I/O端口。这里要注意N-MOS管,当设置输出的值为高电平的时候,N-MOS管处于关闭状态,此时I/O端口的电平就不会由输出的高低电平决定,而是由I/O端口外部的上拉或者下拉决定;当设置输出的值为低电平的时候,N-MOS管处于开启状态,此时I/O端口的电平就是低电平。同时,I/O端口的电平也可以通过输入电路进行读取;注意,I/O端口的电平不一定是输出的电平。

开漏复用输出模式


开漏复用输出模式,与开漏输出模式很是类似。只是输出的高低电平的来源,不是让CPU直接写输出数据寄存器,取而代之利用片上外设模块的复用功能输出来决定的。

推挽输出模式


推挽输出模式下,通过设置位设置/清除寄存器或者输出数据寄存器的值,途经P-MOS管和N-MOS管,最终输出到I/O端口。这里要注意P-MOS管和N-MOS管,当设置输出的值为高电平的时候,P-MOS管处于开启状态,N-MOS管处于关闭状态,此时I/O端口的电平就由P-MOS管决定:高电平;当设置输出的值为低电平的时候,P-MOS管处于关闭状态,N-MOS管处于开启状态,此时I/O端口的电平就由N-MOS管决定:低电平。同时,I/O端口的电平也可以通过输入电路进行读取;注意,此时I/O端口的电平一定是输出的电平。

推挽复用输出模式


推挽复用输出模式,与推挽输出模式很是类似。只是输出的高低电平的来源,不是让CPU直接写输出数据寄存器,取而代之利用片上外设模块的复用功能输出来决定的。

总结与分析

1、什么是推挽结构和推挽电路?

推挽结构一般是指两个参数相同的三极管或MOS管分别受两互补信号的控制,总是在一个三极管或MOS管导通的时候另一个截止。高低电平由输出电平决定。

推挽电路是两个参数相同的三极管或MOSFET,以推挽方式存在于电路中,各负责正负半周的波形放大任务。电路工作时,两只对称的功率开关管每次只有一个导通,所以导通损耗小、效率高。输出既可以向负载灌电流,也可以从负载抽取电流。推拉式输出级既提高电路的负载能力,又提高开关速度。

2、开漏输出和推挽输出的区别?

开漏输出:只可以输出强低电平,高电平得靠外部电阻拉高。输出端相当于三极管的集电极。适合于做电流型的驱动,其吸收电流的能力相对强(一般20ma以内);

推挽输出:可以输出强高、低电平,连接数字器件。

关于推挽输出和开漏输出,最后用一幅最简单的图形来概括:


该图中左边的便是推挽输出模式,其中比较器输出高电平时下面的PNP三极管截止,而上面NPN三极管导通,输出电平VS+;当比较器输出低电平时则恰恰相反,PNP三极管导通,输出和地相连,为低电平。右边的则可以理解为开漏输出形式,需要接上拉。

3、在STM32中选用怎样选择I/O模式?

浮空输入_IN_FLOATING ——浮空输入,可以做KEY识别,RX1

带上拉输入_IPU——IO内部上拉电阻输入

带下拉输入_IPD—— IO内部下拉电阻输入

模拟输入_AIN ——应用ADC模拟输入,或者低功耗下省电

开漏输出_OUT_OD ——IO输出0接GND,IO输出1,悬空,需要外接上拉电阻,才能实现输出高电平。当输出为1时,IO口的状态由上拉电阻拉高电平,但由于是开漏输出模式,这样IO口也就可以由外部电路改变为低电平或不变。可以读IO输入电平变化,实现C51的IO双向功能

推挽输出_OUT_PP ——IO输出0-接GND, IO输出1 -接VCC,读输入值是未知的

复用功能的推挽输出_AF_PP ——片内外设功能(I2C的SCL、SDA)

复用功能的开漏输出_AF_OD——片内外设功能(TX1、MOSI、MISO.SCK.SS)

来源:CSDN,作者:Yngz_Miao
原文:https://blog.csdn.net/qq_38410730/article/details/79858906
版权声明:本文为博主原创文章,转载请附上博文链接!

围观 538

STM32F1xx官方资料:《Cortex-M3权威指南-中文》-第8章最后一个小节:Systick定时器

SysTick定时器

Systick定时器,是一个简单的定时器,对于CM3、CM4内核芯片,都有Systick定时器。Systick定时器常用来做延时,或者实时系统的心跳时钟。这样可以节省MCU资源,不用浪费一个定时器。比如UCOS中,分时复用,需要一个最小的时间戳,一般在STM32+UCOS系统中,都采用Systick做UCOS心跳时钟。

Systick定时器就是系统滴答定时器,一个24 位的倒计数定时器,计到0 时,将从RELOAD 寄存器中自动重装载定时初值。只要不把它在SysTick 控制及状态寄存器中的使能位清除,就永不停息,即使在睡眠模式下也能工作。

SysTick定时器被捆绑在NVIC中,用于产生SYSTICK异常(异常号:15)。Systick中断的优先级也可以设置。

实际上,Systick就是一个定时器而已,只是它放在了NVIC中,主要的目的是为了给操作系统提供一个硬件上的中断,称之为滴答中断操作系统进行运转的时候,也会有时间节拍。它会根据节拍来工作,把整个时间段分成很多小小的时间片,而每个任务每次只能运行一个时间片的时间长度,超时就退出给别的任务运行,这样可以确保任何一个任务都不会霸占操作系统提供的各种定时功能,都与这个滴答定时器有关。因此,需要一个定时器来产生周期性的中断,而且最好还让用户程序不能随意访问它的寄存器,以维持操作系统的节拍。只要不把它在SysTick控制及状态寄存器中的使能位清除,就一直执行。 

SysTick相关寄存器

SysTick有四个寄存器,分别为CTRL(控制与状态寄存器)、LOAD(自动重装载值寄存器)、VAL(当前值寄存器)、CALIB(校准值寄存器)。

在MDK的core_m3.h文件中定义了一个结构体SysTick_Type,里面也包括了这四个寄存器。

typedef struct
{
  __IO uint32_t CTRL;                         /*!< Offset: 0x00  SysTick Control and Status Register */
  __IO uint32_t LOAD;                         /*!< Offset: 0x04  SysTick Reload Value Register       */
  __IO uint32_t VAL;                          /*!< Offset: 0x08  SysTick Current Value Register      */
  __I  uint32_t CALIB;                        /*!< Offset: 0x0C  SysTick Calibration Register        */
} SysTick_Type;

它们的各位描述如下面的表格所述:


对于STM32,外部时钟源(STCLK)是HCLK(AHB总线时钟)的1/8,内核时钟(FCLK)是HCLK(AHB总线时钟)。



SysTick相关库函数

SysTick_CLKSourceConfig(),这是Systick的时钟源选择,直接配置CTRL寄存器的值。假设HCLK为72MHz,选用外部时钟源,那么SysTick的时钟即9MHz。这就意味着,SysTick的计数器VAL每减1代表时间过去了1/9us。

具体的定义在misc.c文件中。

void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)
{
  /* Check the parameters */
  assert_param(IS_SYSTICK_CLK_SOURCE(SysTick_CLKSource));
  if (SysTick_CLKSource == SysTick_CLKSource_HCLK)
  {
    SysTick->CTRL |= SysTick_CLKSource_HCLK;
  }
  else
  {
    SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8;
  }
}

SysTick_Config(uint32_t ticks) ,这是SysTick的初始化函数,时钟为HCLK,并开启SysTick中断。其中函数的参数表示两次中断之间时间间隔期间的SysTick周期,即两次中断之间有多少个SysTick周期。

具体的定义在core_cm3.h文件中。

static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{ 
  if (ticks > SysTick_LOAD_RELOAD_Msk)  return (1);            /* Reload value impossible */
                                                               
  SysTick->LOAD  = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;      /* set reload register */
  NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);  /* set Priority for Cortex-M0 System Interrupts */
  SysTick->VAL   = 0;                                          /* Load the SysTick Counter Value */
  SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk | 
                   SysTick_CTRL_TICKINT_Msk   | 
                   SysTick_CTRL_ENABLE_Msk;                    /* Enable SysTick IRQ and SysTick Timer */
  return (0);                                                  /* Function successful */
}

void SysTick_Handler(void),这是SysTick的中断服务函数。我们举一个例子,利用中断的方式实现delay延时函数,见下面的程序:

static __IO uint32_t TimingDelay;
void Delay(__IO uint32_t nTime)
{ 
   TimingDelay = nTime;
   while(TimingDelay != 0);
}
void SysTick_Handler(void)
{
    if (TimingDelay != 0x00) 
     { 
       TimingDelay--;
     }
}
 int main(void)
 {  …
    if (SysTick_Config(SystemCoreClock / 1000)) //systick时钟为HCLK,中断时间间隔1ms
     {
     while (1);
     }
    while(1)
     { Delay(200);//200ms
     … 
     }
}

Delay延时函数讲解

之前利用中断实现延时函数,但是一直使用中断会造成资源的浪费,不建议这样实现,我们利用查询的方式实现delay延时。下面主要介绍Delay延时函数的实现:

delay_init()

首先是delay_init(),延时初始化函数。利用Syst_CLKSourceConfig()函数选择SysTick时钟源,选择外部时钟(HCLK的1/8);同时初始化fac_us和fac_ms两个变量。

void delay_init()
{
	SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);	//选择外部时钟  HCLK/8
	fac_us=SystemCoreClock/8000000;	      //为系统时钟的1/8,实际上也就是在计算1usSysTick的VAL减的数目
	fac_ms=(u16)fac_us*1000;		//代表每个ms需要的systick时钟数,即每毫秒SysTick的VAL减的数目   
}

delay_ms()

其次,delay_ms(),此函数用来延时指定的ms。

此时要注意nms的范围,SysTick->LOAD为24位寄存器,所以最大延时为:nms<=0xffffff*8*1000/SYSCLK;SYSCLK单位为Hz,nms单位为ms。对72M条件下,nms<=1864。如果超出这个值,建议多次调用此函数来实现。

void delay_ms(u16 nms)
{	 		  	  
	u32 temp;		   
	SysTick->LOAD=(u32)nms*fac_ms;			//时间加载(SysTick->LOAD为24bit)
	SysTick->VAL =0x00;           //清空计数器
	SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;          //开始倒数  
	do
	{
		temp=SysTick->CTRL;
	}while((temp&0x01)&&!(temp&(1<<16)));	//等待时间到达,看CTRL的第16位(COUNTFLAG)是否为1,看STRL的第0位(ENABLE)是否为1   
	SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;       //关闭计数器
	SysTick->VAL =0X00;       //清空计数器	  	    
} 

这段代码其实就是先把延时的时间换算成SysTick的时钟周期数,然后写入LOAD寄存器。然后清空当前寄存器VAL的内容,再开启倒数功能。等倒数结束即延时了nms、最后关闭SysTick,清空VAL的值,实现一次延时的操作。

这里特别说一下,temp&0x01,这一句用来判断SysTick定时器是否还处在开启的状态,可以防止SysTick被意外关闭导致的死循环。

delay_us()

最后,delay_us(),此函数用来延时指定的us。具体的逻辑和上面一个函数类似,就不介绍了。

void delay_us(u32 nus)
{		
	u32 temp;	    	 
	SysTick->LOAD=nus*fac_us; 				//时间加载	  		 
	SysTick->VAL=0x00;        //清空计数器
	SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;          //开始倒数	  
	do
	{
		temp=SysTick->CTRL;
	}while((temp&0x01)&&!(temp&(1<<16)));	//等待时间到达   
	SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;       //关闭计数器
	SysTick->VAL =0X00;       //清空计数器	 
}

delay延时的相关函数在SYSTEM文件夹下的delay子文件夹,在使用delay_ms()或者delay_us()函数之前一定不要忘记先初始化delay_init()。

来源:CSDN,作者:Yngz_Miao,转载此文目的在于传递更多信息,版权归原作者所有。
原文:
https://blog.csdn.net/qq_38410730/article/details/79843806
版权声明:本文为博主原创文章,转载请附上博文链接!

围观 1098

Cortex-M3内核支持256个中断,其中包含了16个内核中断(异常)和240个外部中断,并且具有256级的可编程中断设置。但是,STM32并没有使用CM3内核的全部东西,而是只用了它的一部分。STM32有84个中断,包括16个内核中断(异常)和68个可屏蔽中断,具有16级可编程的中断优先级。而STM32F103系列上面,16个内核中断(异常)不变,而可屏蔽中断只有60个(在107系列才有68个)。

注意一下:CM3的外部中断和STM32的外部中断不是一个概念。CM3:除了内核异常之外的都是外部中断;STM32:外部中断EXTI只有6个

其实,内核中断也叫内核异常。

  • 中断是指系统停止当前正在运行的程序转而其他服务,可能是程序接收了比自身高优先级的请求,或者是人为设置中断,中断是属于正常现象。
  •  

  • 异常是指由于cpu本身故障、程序故障或者请求服务等引起的错误,异常属于不正常现象。

因此,下面就直接介绍一下STM32F103系列的16个内核中断(异常)、60个中断:



在中断向量表中从优先级7-66(中断号从0-59)代表着STM32F103的60个中断。优先级号越小,优先级越高。当表中的某处异常或中断被触发,程序计数器指针(PC)将跳转到该异常或中断的地址处执行,该地址处存放这一条跳转指令,跳转到该异常或中断的服务函数处执行相应的功能。因此,异常和中断向量表只能用汇编语言编写。

在MDK中,有标准的异常和中断向量表文件可以使用(startup_stm32f10x_hd.s),在其中标明了中断处理函数的名称,不能随意定义。而中断通道NVIC_IRQChannel(即IRQn_Type类型)是在stm32f10x.h文件中进行了宏定义。

什么是NVIC?即嵌套向量中断控制器(Nested Vectored Interrupt Controller)。CM3的中有一个强大而方便的NVIC,它是属于Cortex内核的器件,中断向量表中60个中断都由它来处理。NVIC是Cortex-M3核心的一部分,关于它的资料不在《STM32的技术参考手册》中,应查阅ARM公司的《Cortex-M3技术参考手册》。Cortex-M3的向量中断统一由NVIC管理。

NVIC的核心功能是中断优先级分组、中断优先级的配置、读中断请求标志、清除中断请求标志、使能中断、清除中断等,它控制着STM32中断向量表中中断号为0-59的60个中断!!外部中断信号从核外发出,信号最终要传递到NVIC(嵌套向量中断控制器)。NVIC跟内核紧密耦合,它控制着整个芯片中断的相关功能。

STM32中断优先级分组

中断优先级分组寄存器

这60个中断,怎么管理呢?这就涉及到STM32的中断分组。STM32可以将中断分成5个组,分别为组0-4;同时,对每个中断设置一个抢占优先级和响应优先级。分组配置是由SCB->AIRCR寄存器的bit10-8来定义的。SCB->AIRCR是在哪里的呢?由于这是CM3内核定义的,在文档《Cortex-M3权威指南(中文)》中能查找到。

具体的分配关系如下所示:


其中AIRCR寄存器来确定是用哪种分组,IP寄存器是相对应于那种分组抢占优先级和响应优先级的分配比例。例如组设置成3,那么此时所有的60个中断优先寄存器高4位中的最高3位是抢占优先级,低1位为响应优先级。CM3中定义了8个Bit用于设置中断源的优先级,而STM32只选用其中的4个Bit。

抢占优先级的级别高于响应优先级,而数值越小所代表的的优先级越高。 

介绍一下抢占优先级、响应优先级的区别:

① 高优先级的抢占优先级是可以打断正在进行的低抢占优先级中断的;

② 抢占优先级相同的中断,高响应优先级不可以打断低响应优先级的中断;

③ 抢占优先级相同的中断,当两个中断同时发生的情况下,哪个响应优先级高,哪个先执行;

④ 如果两个中断的抢占优先级和响应优先级都是一样的话,则看哪个中断先发生就先执行;

除此之外有两点需要注意:

① 打断的情况只会与抢占优先级有关, 和响应优先级无关!

② 一般情况下,系统代码执行过程中,只设置一次中断优先级分组,比如分组2,设置好分组之后一般不会再改变分组。随意改变分组会导致中断管理混乱,程序出现意想不到的执行结果。

中断优先级分组库函数

CM3核的优先级分组方式,使用的设置函数NVIC_SetPriorityGrouping()。

接下来介绍STM32的中断优先级分组函数NVIC_PriorityGroupConfig(),用来进行中断分组设置的,此函数是在固件库下misc.c文件中(文件目录是:

STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\STM32F10x_StdPeriph_Driver\src\misc.c):

void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
{
  /* Check the parameters */
  assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup));
  
  /* Set the PRIGROUP[10:8] bits according to NVIC_PriorityGroup value */
  SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;
}

函数的参数的取值,是在同文件中进行宏定义的:

#define NVIC_PriorityGroup_0         ((uint32_t)0x700) /*!< 0 bits for pre-emption priority
                                                            4 bits for subpriority */
#define NVIC_PriorityGroup_1         ((uint32_t)0x600) /*!< 1 bits for pre-emption priority
                                                            3 bits for subpriority */
#define NVIC_PriorityGroup_2         ((uint32_t)0x500) /*!< 2 bits for pre-emption priority
                                                            2 bits for subpriority */
#define NVIC_PriorityGroup_3         ((uint32_t)0x400) /*!< 3 bits for pre-emption priority
                                                            1 bits for subpriority */
#define NVIC_PriorityGroup_4         ((uint32_t)0x300) /*!< 4 bits for pre-emption priority
                                                            0 bits for subpriority */

STM32中断优先级管理

中断优先级设置寄存器

分组设置好了之后,怎么设置单个中断的抢占优先级和响应优先级?

MDK为与NVIC相关的寄存器定义了如下的结构体,控制着中断向量表中60个中断(由于与中断内核有关,定义在core_cm3.h文件中):

typedef struct
{
  __IO uint32_t ISER[8];                      /*!< Offset: 0x000  Interrupt Set Enable Register           */
       uint32_t RESERVED0[24];                                   
  __IO uint32_t ICER[8];                      /*!< Offset: 0x080  Interrupt Clear Enable Register         */
       uint32_t RSERVED1[24];                                    
  __IO uint32_t ISPR[8];                      /*!< Offset: 0x100  Interrupt Set Pending Register          */
       uint32_t RESERVED2[24];                                   
  __IO uint32_t ICPR[8];                      /*!< Offset: 0x180  Interrupt Clear Pending Register        */
       uint32_t RESERVED3[24];                                   
  __IO uint32_t IABR[8];                      /*!< Offset: 0x200  Interrupt Active bit Register           */
       uint32_t RESERVED4[56];                                   
  __IO uint8_t  IP[240];                      /*!< Offset: 0x300  Interrupt Priority Register (8Bit wide) */
       uint32_t RESERVED5[644];                                  
  __O  uint32_t STIR;                         /*!< Offset: 0xE00  Software Trigger Interrupt Register     */
}  NVIC_Type;     

我们依次介绍一下这些寄存器:

先介绍几个寄存器组长度为8,这些寄存器是32位寄存器。由于STM32只有60个可屏蔽中断,8个32位寄存器中只需要2个就有64位了,每1位控制一个中断。

  • ISER[8](Interrupt Set-Enable Registers):中断使能寄存器。其中只使用到了ISER[0]和ISER[1],ISER[0]的bit0~bit31分别对应中断0~31。ISER[1]的bit0~27对应中断32~59。要使能某个中断,就必须设置相应的ISER位为1,使该中断被使能(这仅仅是使能,还要配合中断分组、屏蔽、I/O口映射等设置才算完整)。具体每一位对应哪个中断参考stm32f103x.h里面第140行。
  • ICER[8](Interrupt Clear-Enable Registers):中断移除寄存器。该寄存器的作用于ISER相反。这里专门设置一个ICER来清除中断位,而不是向ISER位写0,是因为NVIC的寄存器写1有效,写0无效。
  • ISPR[8](Interrupt Set-Pending Registers):中断挂起控制寄存器。通过置1可以将正在进行的中断挂起,执行同级或者更高级别的中断。写0无效。
  • ICPR[8](Interrupt Clear-Pending Registers):中断解挂控制寄存器。通过置1可以将正在挂起的中断解挂。写0无效。
  • IABR[8](Interrupt Active-Bit Registers):中断激活标志位寄存器。这是一个只读寄存器,可以知道当前在执行的中断是哪一个(为1),在中断执行完后硬件自动清零。

最后,介绍一个寄存器组长度为240,这个寄存器为8位寄存器。240个8位寄存器,每个中断使用一个寄存器来确定优先级。由于CM3由240个外部中断,所以这个寄存器组的数目就是240(注意与上面寄存器的区别,一个是一个寄存器控制一个,一个是一位控制一个)。

  • IP[240](Interrupt Priority Registers):中断优先级控制的寄存器。这是用来控制每个中断的优先级。由于STM32F10x系列一共60个可屏蔽中断,故使用IP[59]~IP[0]。其中每个IP寄存器的高4位[7:4]用来设置抢占和响应优先级(根据分组),低4位没有用到。而两个优先级各占几个位又要由上面讲到的中断优先级分组决定。

中断优先级设置库函数

接下来介绍如何使用库函数实现中断优先级管理,这里使用NVIC_Init()函数来进行对每个中断优先级的设置(misc.c文件中):

void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct)
{
  uint32_t tmppriority = 0x00, tmppre = 0x00, tmpsub = 0x0F;
  
  /* Check the parameters */
  assert_param(IS_FUNCTIONAL_STATE(NVIC_InitStruct->NVIC_IRQChannelCmd));
  assert_param(IS_NVIC_PREEMPTION_PRIORITY(NVIC_InitStruct->NVIC_IRQChannelPreemptionPriority));  
  assert_param(IS_NVIC_SUB_PRIORITY(NVIC_InitStruct->NVIC_IRQChannelSubPriority));
    
  if (NVIC_InitStruct->NVIC_IRQChannelCmd != DISABLE)
  {
    /* Compute the Corresponding IRQ Priority --------------------------------*/    
    tmppriority = (0x700 - ((SCB->AIRCR) & (uint32_t)0x700))> 0x08;
    tmppre = (0x4 - tmppriority);
    tmpsub = tmpsub >> tmppriority;
 
    tmppriority = (uint32_t)NVIC_InitStruct->NVIC_IRQChannelPreemptionPriority << tmppre;
    tmppriority |=  NVIC_InitStruct->NVIC_IRQChannelSubPriority & tmpsub;
    tmppriority = tmppriority << 0x04;
        
    NVIC->IP[NVIC_InitStruct->NVIC_IRQChannel] = tmppriority;
    
    /* Enable the Selected IRQ Channels --------------------------------------*/
    NVIC->ISER[NVIC_InitStruct->NVIC_IRQChannel >> 0x05] =
      (uint32_t)0x01 << (NVIC_InitStruct->NVIC_IRQChannel & (uint8_t)0x1F);
  }
  else
  {
    /* Disable the Selected IRQ Channels -------------------------------------*/
    NVIC->ICER[NVIC_InitStruct->NVIC_IRQChannel >> 0x05] =
      (uint32_t)0x01 << (NVIC_InitStruct->NVIC_IRQChannel & (uint8_t)0x1F);
  }
}

其中,NVIC_InitTypeDef为一个结构体,它的成员变量为:

typedef struct
{
  uint8_t NVIC_IRQChannel;                    /*!< Specifies the IRQ channel to be enabled or disabled.
                                                   This parameter can be a value of @ref IRQn_Type 
                                                   (For the complete STM32 Devices IRQ Channels list, please
                                                    refer to stm32f10x.h file) */
 
  uint8_t NVIC_IRQChannelPreemptionPriority;  /*!< Specifies the pre-emption priority for the IRQ channel
                                                   specified in NVIC_IRQChannel. This parameter can be a value
                                                   between 0 and 15 as described in the table @ref NVIC_Priority_Table */
 
  uint8_t NVIC_IRQChannelSubPriority;         /*!< Specifies the subpriority level for the IRQ channel specified
                                                   in NVIC_IRQChannel. This parameter can be a value
                                                   between 0 and 15 as described in the table @ref NVIC_Priority_Table */
 
  FunctionalState NVIC_IRQChannelCmd;         /*!< Specifies whether the IRQ channel defined in NVIC_IRQChannel
                                                   will be enabled or disabled. 
                                                   This parameter can be set either to ENABLE or DISABLE */   
} NVIC_InitTypeDef;

NVIC_InitTypeDef结构体有4个成员变量:

① NVIC_IRQChannel:定义初始化的是哪一个中断,这个可以在stm32f10x.h文件中查到每个中断对应的名字,如USART1_IRQn;

② NVIC_IRQChannelPreemptionPriority:定义此中断的抢占优先级别;

③ NVIC_IRQChannelSubPriority:定义此中断的响应优先级别;

④ NVIC_IRQChannelCmd:该中断是否使能。

其实我们看NVIC_Init()函数内部使能中断,也是通过ISER寄存器配置的。这与我么之前的内容并不矛盾。函数内部使用NVIC->ISER,而NVIC是一个宏定义,

#define NVIC     ((NVIC_Type *) NVIC_BASE)     /*!< NVIC configuration struct         */

也就是直接操作结构体来实现操作ISER寄存器。

比如,使能串口1中断,抢占优先级为1,响应优先级为2,初始化的方法为:

NVIC_InitTypeDef   NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//串口1中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1 ;// 抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;// 子优先级位2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//IRQ通道使能
NVIC_Init(&NVIC_InitStructure);	//根据上面指定的参数初始化NVIC寄存器

总结与分析

最后总结一下中断优先级设置的步骤:

1、系统运行后先设置中断优先级分组。调用函数:

void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);
整个系统执行过程中,只设置一次中断分组;

2、针对每个中断,设置对应的抢占优先级和响应优先级:

void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);

来源:CSDN,作者:Yngz_Miao
原文:
https://blog.csdn.net/qq_38410730/article/details/79829983
版权声明:本文为博主原创文章,转载请附上博文链接!

围观 930

简介

本文描述了如何使用在搭载了 RT-Thread 系统的 STM32 平台上使用 C++,包括 C++ 的配置和应用等,并给出了在STM32F411 NUCLEO开发板上验证的代码示例。

硬件平台简介

本文基于意法半导体 STM32F411 NUCLEO开发板,给出了 C++ 的具体应用示例代码,由于RT-Thread上层应用API的通用性,因此这些代码不局限于具体的硬件平台,用户可以轻松将它移植到其它平台上。

STM32F411 NUCLEO是意法半导体推出的一款基于ARM Cortex-M4内核的开发板,最高主频为100Mhz,该开发板具有丰富的板载资源,可以充分发挥STM32F411RE 的芯片性能。

STM32F411RE从属于销量名列前茅的STM32F4系列,众所周知,F4是STM32主打高性能和数字信号处理的“轻奢”系列。

“奢侈”在F4作为内核为Cortex-M4 (DSP+FPU)的MCU,可选180MHz 主频、2M Flash/384KB RAM、Chrom-ART加速器、MPI-DSI接口、延伸到125度的工作温度、DFSDM数字滤波器以及各种常见的音频(SAI)、连接(Ethernet、Camera、USB)、控制(CAN、UART、I2C)、存储(FMC、2/4/8 bits SPI、SDMMC)外设。

“轻”在价格让人“轻松”、尺寸“轻巧”(不到3mm*3mm的封装)、功耗“轻微”。


准备工作

1、下载 RT-Thread 源码

2、下载 ENV 工具

3、进入rt-thread\bsp\stm32f411-st-nucleo 目录,检查 BSP rtconfig.py 文件和 SConstruct 文件是否支持 C++ 配置,如下图所示

检查 rtconfig.py 文件中对 C++ 的支持


检查 SConstruct 文件中对 C++ 的支持


打开 C++ 支持:

打开 Env 工具,在 Env 命令行中输入 menuconfig,进入配置界面,使用 menuconfig 工具(学习如何使用)配置工程。在 menuconfig 配置界面依次选择 RT-Thread Components ---> C++ features ---> Support C++ features,如图所示:


编译工程: scons --target=mdk5 1. 生成 mdk5 工程,将示例代码附带的 main.cpp 替换掉 BSP 中的 main.c 并重新加入到工程中,如图所示:


编译,下载程序,在终端输入 help 命令可以看到 test_cpp 已经添加成功了。


运行 C++ 程序:

在终端输入 test_cpp 运行结果如下图所示。


C++ 全局对象构造函数的调用

RT-Thread 中对全局对象构造函数的实现中,以 GNUC 为例,在 rt-thread\components\cplusplus 目录下的 crt_init.c 文件中对 C++ 进行了系统初始化, 在特定的 BSP 目录下,连接脚本文件 link.lds 为 C++ 全局构造函数的代码分配了段,使 C++ 全局对象构造函数链接后能够存放在指定的段中。如下图所示:


crt_init.c 文件完成了 C++ 系统的初始化工作

C++ 系统初始化部分:


在 cplusplus_system_init 函数中,将全局对象的构造函数依次链接到了链接脚本文件中为其分配的段中,并且调用了 RT-Thread 组件自动初始化的宏 INIT_COMPONENT_EXPORT,所以在链接的时候,C++全局对象构造函数所产生的目标文件就被链接到了__ctors_start__和__ctors_end__组成的段中。

链接脚本中为 C++ 全局构造函数分配的段部分:


__ctors_start__ 分配了 C++ 全局构造函数段的起始地址, __ctors_end__ 分配了 C++ 全局构造函数段的结束地址,所以全局构造函数在系统初始化的时候,就会被链接到这里分配的段地址中。

RT-Thread C++ 异常说明

同样,在链接脚本文件 link.lds 中,也为 C++ 异常分配了段地址:


__exidx_start 分配了 C++ 异常的起始地址, __exidx_end 分配了 C++ 异常的结束地址,当异常产生的时候,就会被分配到指定的段地址中。

这里以一个 C++ 除零异常的抛出和捕获为例:


当除零异常发生的时候 div_func 函数会抛出一个异常,在 throw_exceptions 函数中会去捕获这个异常。

下载代码,并在终端输入 throw_exceptions 运行结果如下图所示。


到这一步为止,如何在搭载了 RT-Thread 系统的 STM32 平台上如何使用 C++ 的介绍就结束了。

参考资料

1、ENV 用户手册
https://www.rt-thread.org/document/site/programming-manual/env/env/

2、STM32F411-ST-NUCLEO BSP 源码
https://github.com/RT-Thread/rt-thread/tree/master/bsp/stm32/stm32f411-s...

本文转自网络,转载此文目的在于传递更多信息,版权归原作者所有。

围观 370

页面

订阅 RSS - STM32