STM32F407 纯寄存器操作GPIO,串口,中断(专治花里胡哨)

配置时钟,延时函数,均使用正点原子的SYSTEM文件
硬件:正点原子探索者,STLINK调试

系统时钟配置步骤
**加粗样式**
正点原子,时钟配置函数(我都是直接把sys.c和sys.h拷贝过来,直接使用)

//设置向量表偏移地址
//NVIC_VectTab:基址
//Offset:偏移量		 
void MY_NVIC_SetVectorTable(u32 NVIC_VectTab,u32 Offset)	 
{ 	   	  
	SCB->VTOR=NVIC_VectTab|(Offset&(u32)0xFFFFFE00);//设置NVIC的向量表偏移寄存器,VTOR低9位保留,即[8:0]保留。
} 
//时钟设置函数
//Fvco=Fs*(plln/pllm);
//Fsys=Fvco/pllp=Fs*(plln/(pllm*pllp));
//Fusb=Fvco/pllq=Fs*(plln/(pllm*pllq));

//Fvco:VCO频率
//Fsys:系统时钟频率
//Fusb:USB,SDIO,RNG等的时钟频率
//Fs:PLL输入时钟频率,可以是HSI,HSE等. 
//plln:主PLL倍频系数(PLL倍频),取值范围:64~432.
//pllm:主PLL和音频PLL分频系数(PLL之前的分频),取值范围:2~63.
//pllp:系统时钟的主PLL分频系数(PLL之后的分频),取值范围:2,4,6,8.(仅限这4个值!)
//pllq:USB/SDIO/随机数产生器等的主PLL分频系数(PLL之后的分频),取值范围:2~15.

//外部晶振为8M的时候,推荐值:plln=336,pllm=8,pllp=2,pllq=7.
//得到:Fvco=8*(336/8)=336Mhz
//     Fsys=336/2=168Mhz
//     Fusb=336/7=48Mhz
//返回值:0,成功;1,失败。
u8 Sys_Clock_Set(u32 plln,u32 pllm,u32 pllp,u32 pllq)
{ 
	u16 retry=0;
	u8 status=0;
	RCC->CR|=1<<16;				//HSE 开启 
	while(((RCC->CR&(1<<17))==0)&&(retry<0X1FFF))retry++;//等待HSE RDY
	if(retry==0X1FFF)status=1;	//HSE无法就绪
	else   
	{
		RCC->APB1ENR|=1<<28;	//电源接口时钟使能
		PWR->CR|=3<<14; 		//高性能模式,时钟可到168Mhz
		
		RCC->CFGR|=(0<<4)|(5<<10)|(4<<13);//HCLK 不分频;APB1 4分频;APB2 2分频. 
		RCC->CR&=~(1<<24);	//关闭主PLL
		RCC->PLLCFGR=pllm|(plln<<6)|(((pllp>>1)-1)<<16)|(pllq<<24)|(1<<22);//配置主PLL,PLL时钟源来自HSE
		RCC->CR|=1<<24;			//打开主PLL
		while((RCC->CR&(1<<25))==0);//等待PLL准备好 
		FLASH->ACR|=1<<8;		//指令预取使能.
		FLASH->ACR|=1<<9;		//指令cache使能.
		FLASH->ACR|=1<<10;		//数据cache使能.
		FLASH->ACR|=5<<0;		//5个CPU等待周期. 
		RCC->CFGR&=~(3<<0);		//清零
		RCC->CFGR|=2<<0;		//选择主PLL作为系统时钟	 
		while((RCC->CFGR&(3<<2))!=(2<<2));//等待主PLL作为系统时钟成功. 
	} 
	return status;
}  

//系统时钟初始化函数
//plln:主PLL倍频系数(PLL倍频),取值范围:64~432.
//pllm:主PLL和音频PLL分频系数(PLL之前的分频),取值范围:2~63.
//pllp:系统时钟的主PLL分频系数(PLL之后的分频),取值范围:2,4,6,8.(仅限这4个值!)
//pllq:USB/SDIO/随机数产生器等的主PLL分频系数(PLL之后的分频),取值范围:2~15.
void Stm32_Clock_Init(u32 plln,u32 pllm,u32 pllp,u32 pllq)
{  
	RCC->CR|=0x00000001;		//设置HISON,开启内部高速RC振荡
	RCC->CFGR=0x00000000;		//CFGR清零 
	RCC->CR&=0xFEF6FFFF;		//HSEON,CSSON,PLLON清零 
	RCC->PLLCFGR=0x24003010;	//PLLCFGR恢复复位值 
	RCC->CR&=~(1<<18);			//HSEBYP清零,外部晶振不旁路
	RCC->CIR=0x00000000;		//禁止RCC时钟中断 
	Sys_Clock_Set(plln,pllm,pllp,pllq);//设置时钟 
	//配置向量表				  
#ifdef  VECT_TAB_RAM
	MY_NVIC_SetVectorTable(1<<29,0x0);
#else   
	MY_NVIC_SetVectorTable(0,0x0);
#endif 
}	

时钟与外设

在这里插入图片描述
使能则相应位置一

APB1 APB2 AHB1 AHB2 AHB3
UART2,3,4,5,7,8 USART1,6
DAC,PWR,CAN1,CAN2 SYSCFG,EXIT,SDIO MAC,BKPSRAM RNG,HASH FSMC
I2C1,2,3 ** DMA1,2
TIM2,3,4,5,6,7 TIM1,8,9,10,11 GPIOA-GPIOI
TIM12,13,14 **
SPI2,3 SPI1,45,6
I2S2,3 ADC1,2,3
IWDG,WWDG ** FLASH,RCC,CRC CRYP,DCMI,USB OTG

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

 RCC->AHB1ENR |= 0x00000008; 	//使能GPIOD时钟
 RCC->APB2ENR |= (1<<14);  	//使能syscfg时钟

GPIO

在这里插入图片描述

设置步骤:

  • 使能相关时钟

  • 设置相应的IO口为输入或输出 BSRRH BSRRL

  • 设置输入\输出的类型 OTYPER

  • 设置输出的速度(如果设置为输入,此步跳过) OSPEEDR

  • 如果输出速度>=50M,开启补偿单元 SYSCFG->CMPCR

  • 设置上拉下拉寄存器 PUPDR

注意:
IDR寄存器,(只读),对GPIO输入的读取
ODR寄存器,对GPIO输出的读写
BSSR寄存器,(只写),对GPIO输出,低16位,输出高电平,高16位,输出低电平
//reset register GPIOx_BSRRH, write only
//set register GPIOx_BSRRL, write only
在这里插入图片描述
GPIO F9,F10 交替闪烁

#include <stm32f4xx.h> 
#include <sys.h>
#include <delay.h>
 
int main ()
{   
  Stm32_Clock_Init(336,8,2,7); //系统时钟
	
	delay_init(48);
  
  RCC->AHB1ENR |= 0x00000020; 	//使能GPIOF时钟
  
  GPIOF->MODER &= 0x00000000;	 //设置F9,F10  0001 0100 0000 0000 0000 0000
  GPIOF->MODER |= 0x00140000; 
  
  GPIOF->OTYPER &= 0x00000600; 	//设置F9,F10推挽输出
  
  GPIOF->OSPEEDR &= 0x00000000; 	//F9,F10 速度50m 0010 1000 0000 0000 0000 0000
  GPIOF->OSPEEDR |= 0x00280000;
  
  
  //GPIOF->PUPDR &= 0x00280000;  	 //10下拉   00无   
	//GPIOF->PUPDR &= 0x00140000;    //01上拉
  
  
  while(1)
  {
      GPIOF->BSRRH = 0x0200; //0000 0000 0010
	  delay_ms(1000);
	  GPIOF->BSRRL = 0x0200; //0000 0000 0010
    		
      GPIOF->BSRRH = 0x0400; 
	  delay_ms(1000);
	  GPIOF->BSRRL = 0x0400; 
		
//			GPIOF->ODR=0x00000600;
//			delay_ms(1000);
//			GPIOF->ODR=0x00000000;
//			delay_ms(1000);

  }
}

串口与GPIO复用

GPIO复用功能映射表,只给出PORTA,B,C
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
根据表中找出对应外设的复用引脚,及复用功能选择位,AFn。

GPIO复用:
由两个寄存器控制,GPIOx_AFRH(高八位),GPIOx_AFRL(低八位)。
在程序中,GPIOB->AFR[0]表示GPIOx_AFRL(低八位),
GPIOB->AFR[1]表示GPIOx_AFRH(高八位)。
在这里插入图片描述
在这里插入图片描述
找到对应的引脚y,写入AFn。
比如串口三对应的复用功能是AF7,引脚是B10,11。则设置引脚的输出类型,速度,上拉下拉。
引脚位于高八位,写入GPIOx_AFRH,GPIOB->AFR[1] = 0x00007700

串口三 内容回显
内容以回车(0x0D)结束

设置步骤:

  • 使能相关时钟
	RCC->AHB1ENR|=1<<1;   			//使能PORTB口时钟  
	RCC->APB1ENR|=1<<18;  			//使能串口3时钟 
  • 设置相应的IO口为复用输出
	GPIO_Set(GPIOB,PIN10|PIN11,GPIO_MODE_AF,GPIO_OTYPE_PP,GPIO_SPEED_50M,GPIO_PUPD_PU);//PB10,PB11,复用功能,上拉输出
 	GPIO_AF_Set(GPIOB,10,7);		//PB10,AF7
	GPIO_AF_Set(GPIOB,11,7);		//PB11,AF7
  • 波特率设置
//计算波特率
	float temp;
	u16 mantissa;
	u16 fraction;	   
	temp=(float)(pclk1*1000000)/(bound*16);//得到USARTDIV,OVER8设置为0
	mantissa=temp;				 	    //得到整数部分
	fraction=(temp-mantissa)*16; 	//得到小数部分,OVER8设置为0	 
   mantissa<<=4;
	mantissa+=fraction; 
//串口设置
USART3->BRR=mantissa; 			//波特率设置	 
USART3->CR1|=1<<3;  			//串口发送使能  
USART3->CR1|=1<<2;  			//串口接收使能
USART3->CR1|=1<<5;    			//接收缓冲区非空中断使能	
USART3->CR1|=1<<13;  			//串口使能  
//中断优先级
MY_NVIC_Init(0,0,USART3_IRQn,2);//组2,优先级0,0,最高优先级 
  • 状态寄存器 USART3->SR

  • 数据寄存器 USART3->DR

  • 缓冲区数组内容清空 memset(USART3_RX_BUF,0,USART3_MAX_RECV_LEN)

完整代码:

#include <stm32f4xx.h>
#include <sys.h>
#include <delay.h> 	 
#include "string.h"
 
u8 ok_to_send; //可以发送的标志
u8 Rx_data_counter;

#define USART3_MAX_RECV_LEN		400					//最大接收缓存字节数

//串口接收缓存区 	
u8 USART3_RX_BUF[USART3_MAX_RECV_LEN]; 				//接收缓冲,最大USART3_MAX_RECV_LEN个字节.

u8 Rx_data_counter=0;
u8 ok_to_send=0;

void USART3_IRQHandler(void)
{
  if(USART3->SR & (1<<5)) //接收数据寄存器非空
	{
		
		USART3_RX_BUF[Rx_data_counter] = USART3->DR;
		Rx_data_counter++;
		if(USART3_RX_BUF[Rx_data_counter-1] ==0x0D)
		{
      Rx_data_counter = 0;
			ok_to_send=1;
		}
	 } 		 
} 


//初始化IO 串口3
//pclk1:PCLK1时钟频率(Mhz)
//bound:波特率 

void usart3_init(u32 pclk1,u32 bound)
{  	 
	float temp;
	u16 mantissa;
	u16 fraction;	   
	temp=(float)(pclk1*1000000)/(bound*16);//得到USARTDIV,OVER8设置为0
	mantissa=temp;				 	//得到整数部分
	fraction=(temp-mantissa)*16; 	//得到小数部分,OVER8设置为0	 
  mantissa<<=4;
	mantissa+=fraction; 
	
	RCC->AHB1ENR|=1<<1;   			//使能PORTB口时钟  
	RCC->APB1ENR|=1<<18;  			//使能串口3时钟 
	
	GPIO_Set(GPIOB,PIN10|PIN11,GPIO_MODE_AF,GPIO_OTYPE_PP,GPIO_SPEED_50M,GPIO_PUPD_PU);//PB10,PB11,复用功能,上拉输出
 	GPIO_AF_Set(GPIOB,10,7);		//PB10,AF7
	GPIO_AF_Set(GPIOB,11,7);		//PB11,AF7
	
	//波特率设置
 	USART3->BRR=mantissa; 			// 波特率设置	 
	USART3->CR1|=1<<3;  			//串口发送使能  
	USART3->CR1|=1<<2;  			//串口接收使能
	USART3->CR1|=1<<5;    			//接收缓冲区非空中断使能	
	USART3->CR1|=1<<13;  			//串口使能  
	
	MY_NVIC_Init(0,0,USART3_IRQn,2);//组2,优先级0,0,最高优先级 
	
}

 
int main()
{ 

	u8 j=0;

	Stm32_Clock_Init(336,8,2,7); 

	usart3_init(42,9600);//初始化串口3为:9600,波特率.

	while(1)
	{ 
			
			if(ok_to_send==1)
			{
				for(j=0;j<strlen((char*)USART3_RX_BUF);j++)//循环发送数据
				{ 
					USART3->DR=USART3_RX_BUF[j]; 	
					while((USART3->SR&0X40)==0);//循环发送,直到发送完毕 
					
					if( USART3_RX_BUF[j] ==0x0D)
					{
						ok_to_send=0;
						j=0;
						memset(USART3_RX_BUF,0,USART3_MAX_RECV_LEN);						
				  }
		    }
			}
	}
}

注意:
在这里插入图片描述
串口数据的读写,可以看到,是以8bit,一个字节为单位来读写的。利用DR寄存器,对DR进行读取,即DR赋值给别人,BUFF=DR。对DR进行写入,即别人赋值给DR,DR=BUFF。

再利用状态寄存器,SR来读取状态信息控制。

在这里插入图片描述

在这里插入图片描述

能否像小船博主一样,全部用寄存器来设置呢?
https://blog.csdn.net/w471176877/article/details/7957187

也就是关于GPIO复用
在这里插入图片描述
在这里插入图片描述
注释了GPIO设置的三个函数,完全用寄存器来设置

//	GPIO_Set(GPIOB,PIN10|PIN11,GPIO_MODE_AF,GPIO_OTYPE_PP,GPIO_SPEED_50M,GPIO_PUPD_PU);//PB10,PB11,复用功能,上拉输出
// 	GPIO_AF_Set(GPIOB,10,7);		//PB10,AF7
//	GPIO_AF_Set(GPIOB,11,7);		//PB11,AF7
GPIOB->AFR[1] = 0x00007700;//选择PB10,11复用功能 
 
GPIOB->MODER &= 0xFF0FFFFF; //设置PB10,11,复用模式
GPIOB->MODER |= 0x00A00000; 


GPIOB->OSPEEDR &= 0xFFCFFFFF; //PB10速度50m
GPIOB->OSPEEDR |= 0x00200000;
 
GPIOB->PUPDR &= 0xFFCFFFFF; //PB10
GPIOB->PUPDR |= 0x00100000;

中断优先级函数也用寄存器来替代了

  //MY_NVIC_Init(0,0,USART3_IRQn,2);//组2,优先级0,0,最高优先级 
	
  SCB->AIRCR = 0x05FA0000 | 0x400;  //中断优先级分组 抢占:响应=3:1
	
	NVIC->IP[39] = 0xf0; //最低抢占优先级,最低响应优先级1111
  NVIC->ISER[1] |= (1<<(39-32)); //使能中断线39,也就是usart3中断

除了时钟树配置,其它都自己用寄存器来配置了。

#include <stm32f4xx.h>
#include <sys.h>
#include <delay.h> 	 
#include "string.h"
 
u8 suffer[100];
u8 ok_to_send;
u8 Rx_data_counter;

#define USART3_MAX_RECV_LEN		400					//最大接收缓存字节数

//串口接收缓存区 	
u8 USART3_RX_BUF[USART3_MAX_RECV_LEN]; 				//接收缓冲,最大USART3_MAX_RECV_LEN个字节.

u8 Rx_data_counter=0;
u8 ok_to_send=0;

void USART3_IRQHandler(void)
{
  if(USART3->SR & (1<<5)) //接收数据寄存器非空
	{
		
		USART3_RX_BUF[Rx_data_counter] = USART3->DR;
		Rx_data_counter++;
		if(USART3_RX_BUF[Rx_data_counter-1] ==0x0D)
		{
      Rx_data_counter = 0;
			ok_to_send=1;
		}
	 } 		 
} 


//初始化IO 串口3
//pclk1:PCLK1时钟频率(Mhz)
//bound:波特率 

void usart3_init(u32 pclk1,u32 bound)
{  	 
	float temp;
	u16 mantissa;
	u16 fraction;	   
	temp=(float)(pclk1*1000000)/(bound*16);//得到USARTDIV,OVER8设置为0
	mantissa=temp;				 	//得到整数部分
	fraction=(temp-mantissa)*16; 	//得到小数部分,OVER8设置为0	 
  mantissa<<=4;
	mantissa+=fraction; 
	
	RCC->AHB1ENR|=1<<1;   			//使能PORTB口时钟  
	RCC->APB1ENR|=1<<18;  			//使能串口3时钟 
	

GPIOB->AFR[1] = 0x00007700;//选择PB10,11复用功能 
 
GPIOB->MODER &= 0xFF0FFFFF; //设置PB10,11,复用模式
GPIOB->MODER |= 0x00A00000; 


GPIOB->OSPEEDR &= 0xFFCFFFFF; //PB10速度50m
GPIOB->OSPEEDR |= 0x00200000;
 
GPIOB->PUPDR &= 0xFFCFFFFF; //PB10
GPIOB->PUPDR |= 0x00100000;

	
	//串口设置
 	USART3->BRR=mantissa; 			//波特率设置	 
//	USART3->CR1|=1<<3;  			//串口发送使能  
//	USART3->CR1|=1<<2;  			//串口接收使能
//	USART3->CR1|=1<<5;    			//接收缓冲区非空中断使能	
//	USART3->CR1|=1<<13;  			//串口使能  

USART3->CR1 |= (( 1<<13 ) | ( 1<<3 ) | ( 1<<2 ) | ( 1<<5 )); 
	
	
  SCB->AIRCR = 0x05FA0000 | 0x400;  //中断优先级分组 抢占:响应=3:1
	
	NVIC->IP[39] = 0xf0; //最低抢占优先级,最低响应优先级1111
  NVIC->ISER[1] |= (1<<(39-32)); //使能中断线39,也就是usart3中断
	
}

 
int main()
{ 

	u8 j=0;

	Stm32_Clock_Init(336,8,2,7); 

	usart3_init(42,9600);//初始化串口3为:9600,波特率.

	while(1)
	{ 
			
			if(ok_to_send==1)
			{
				for(j=0;j<strlen((char*)USART3_RX_BUF);j++)//循环发送数据
				{ 
					USART3->DR=USART3_RX_BUF[j]; 	
					while((USART3->SR&0X40)==0);//循环发送,直到发送完毕 
					
					if( USART3_RX_BUF[j] ==0x0D)
					{
						ok_to_send=0;
						j=0;
						memset(USART3_RX_BUF,0,USART3_MAX_RECV_LEN);						
				  }
		    }
			}
	}
}

在这里插入图片描述
软件自动添加了回车(0x0D)

可以看看工程文件,四个个c文件,一个启动文件。当然还有头文件,使用的都是正点原子的SYSTEM文件夹里的。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

EXTI 外部中断

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
EXTIx与引脚的x相关,所以用几号EXTI就用几号的引脚。
在这里插入图片描述
设置步骤:

  • 使能相关时钟

  • 设置中断优先级分组(如果之前没有设置),这个最好一个程序里只在开头设置一次。

  • 设置中断线所要连接到的IO的输入模式

  • 设置屏蔽寄存器,不能把中断屏蔽掉

  • 设置中断方式

  • 把中断线连接到IO口

  • 设置中断优先级

  • 使能中断线

  • 编写中断服务函数(函数名是固定的)

  • 编写相应的程序

  • 清除中断挂起

用两个外部中断,也做个优先级的实验。
EXTI2和EXTI3,注意2和3没有优先级区分,和所有端口序号有关。
选用端口PE2,PE3

在这里插入图片描述

中断优先级,两位抢占,两位响应。AIRCR,8,9,10bit控制
SCB->AIRCR = 0x05FA0000 | 0x500;

所以IP寄存器4,5bit控制响应,6,7bit控制抢占
设置两个led,PF8,PF9。推挽输出 50,无上拉下拉。

在这里插入图片描述

系统配置控制器 (SYSCFG)
系统配置控制器主要用于管理对可执行代码的存储区域的地址重映射、选择以太网 PHY 接
口以及管理 GPIO 的外部中断线连接。

实验感受:

  • 确实是抢占和响应值越小级别越高。
  • 抢占级一样,只有响应级,感觉只要触发都会发生,如LED0和LED1都会被触发,同时亮,只是比单个亮的亮度暗一些。
  • 响应级一样,抢占级不同,这就不一样了。会发生抢占,抢占值越小,级别越高。当低优先级触发时,高优先级可以打断,无论响应优先级多少。
  • //EXTI->PR=1<<2; //中断标志位,如果每次触发都被清除,那就和没有优先级一样,两个中断服务函数都可以被随时打断

完整代码:

#include<sys.h>
#include<delay.h>
#include<stm32f4xx.h>

void Led_Init(void);
void EXTI_Init(void);

int main()
{
	Stm32_Clock_Init(336,8,2,7); 
	EXTI_Init();
	Led_Init();
	
  SCB->AIRCR = 0x05FA0000 | 0x500;
	NVIC->IP[8] = 0x50; //最低抢占优先级,最低响应优先级01 01  EXTI2中断	F9  KEY2
	NVIC->IP[9] = 0x00; //最低抢占优先级,最低响应优先级00 00  EXTI3中断	F10  KEY1
	
  NVIC->ISER[0] |= (1<<8); //使能中断线8,也就是EXTI2中断	
	NVIC->ISER[0] |= (1<<9); //使能中断线9,也就是EXTI3中断	
	
	while(1)
	{
		;
	}
	
	
	
}

void EXTI_Init()
{
	RCC->AHB1ENR |= 0x00000010; //使能GPIOE时钟
  RCC->APB2ENR |= (1<<14);  //使能syscfg时钟
	
  GPIOE->MODER &= 0xFFFFFF0F; //浮空输入模式
	
  GPIOE->PUPDR &= 0xFFFFFFAF; 
  
  EXTI->IMR |= ( 1 << 2 )|( 1 << 3 );  //不屏蔽外中断线2,3
  
  EXTI->RTSR |= ( 1 << 2 )|( 1 << 3 ); //上降沿触发
  
  SYSCFG->EXTICR[0] = 0xFFFF44FF;  //配置外中断线2,3到PE口


}


void Led_Init()
{
  RCC->AHB1ENR |= 0x00000020; 	//使能GPIOF时钟
  
  GPIOF->MODER &= 0x00000000;	 //设置F9,F10  0001 0100 0000 0000 0000 0000
  GPIOF->MODER |= 0x00140000; 
  
  GPIOF->OTYPER &= 0x00000600; 	//设置F9,F10推挽输出
  
  GPIOF->OSPEEDR &= 0x00000000; 	//F9,F10 速度50m 0010 1000 0000 0000 0000 0000
  GPIOF->OSPEEDR |= 0x00280000;
	
	GPIOF->BSRRH = 0x0200; //PF9 DS0亮
	GPIOF->BSRRH = 0x0400; //PF10 DS1亮
}

void EXTI2_IRQHandler(void) //KEY2
{
	
      GPIOF->BSRRL = 0x0200; // F9 DS0灭
			GPIOF->BSRRH = 0x0400; //PF10 DS1亮
			EXTI->PR=1<<2; //中断标志位
			

}

void EXTI3_IRQHandler(void)  //KEY1 DS1
{
	

      GPIOF->BSRRL = 0x0400; //F10 DS1灭
			GPIOF->BSRRH = 0x0200; //PF9 DS0亮
			EXTI->PR=1<<3;

}

发布了82 篇原创文章 · 获赞 72 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/fzf1996/article/details/98502413