STM32F103 GPIO和串口实战

news2024/11/22 20:06:04

本节我们将会对STM32F103的硬件资源GPIO和串口进行介绍。

一、GPIO

1.1 电路原理图

LED电路原理图如下图所示:

其中:

  • LED1连接到PA8引脚,低电平点亮;
  • LED2连接到PD2引脚,低电平点亮;
1.2 GPIO引脚介绍

STM32F103系列共有7组GPIO,分别为 GPIOA, GPIOB, GPIOC, GPIODGPIOEGPIOFGPIOG。其中,GPIOAGPIOBGPIOCGPIOD是常用的,GPIOE~GPIOG只有一部分引脚在某些型号的STM32F103系列中有效。

每组GPIO通常包含16个引脚。但并非所有引脚都在所有STM32F103型号中都有效,具体取决于具体芯片封装。

  • GPIOA:16 个引脚(PA0 - PA15);
  • GPIOB:16 个引脚(PB0 - PB15);
  • GPIOC:16 个引脚(PC0 - PC15);
  • GPIOD:16 个引脚(PD0 - PD15);
  • GPIOE:16个引脚(PE0 - PE15);
  • GPIOF:16个引脚(PF0 - PF15);
  • GPIOG:16个引脚(PG0 - PG15)。
1.3 GPIO寄存器

STM32F103中,GPIO引脚的配置和控制是通过寄存器来实现的。每个GPIO端口(如GPIOAGPIOB 等)都有一组寄存器来控制引脚的方向、输出类型、上拉/下拉电阻、输入模式等。;

  • 两个32位配置寄存器,GPIOx_CRHGPIOx_CRL;
  • 两个32位数据寄存器,GPIOx_IDRGPIOx_ODR
  • 一个32位设置/清除寄存器,GPIOx_BSRR
  • 一个16位复位寄存器,GPIOx_BRR
  • 一个32位锁定寄存器,GPIOx_LCKR
1.3.1 端口配置低寄存器(GPIOx_CRL)

GPIOx_CRL端口配置低8位寄存器(x=A~E),四位控制1个引脚,共控制8个引脚(0~7引脚);

31:2827:2423:2019:1615:1211:87:43:0
CNF7[1:0]
MODE7[1:0]
........................CNF0[1:0]
MODE0[1:0]

其中:

MODE[1:0]CNF[1:0]
输入模式0 00 0:模拟输入模式
0 1:浮空输入模式
1 0:上拉/下卡输入模式
1 1:保留
输出模式0 1:最大速度10MHz
1 0:最大速度2MHz
1 1:最大速度50MHz
0 0:通用推挽输出模式
0 1:通用开漏输出模式
1 0:复用功能推挽输出模式
1 1:复用功能开漏输出模式
1.3.2 端口配置高寄存器(GPIOx_CRH)

GPIOx_CRH四位控制1个引脚(x=A~E),共控制8个引脚(8~15引脚);同上不再重复介绍。

1.3.3 端口输入数据寄存器(GPIOx_IDR)

GPIOx_IDR端口输入数据寄存器(x=A~E);

31~161514131211109876543210
保留IDR15............................IDR0

31~16位始终读0,低16位为只读,并且只能以字(16位)的形式读出。

1.3.4 端口输出数据寄存器(GPIOx_ODR)

GPIOx_ODR端口输出数据寄存器(x=A~E);

31~161514131211109876543210
保留ODR15............................ODR0

31~16位始终读0,低16位可读可写,并且只能以字(16位)的形式操作。

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

GPIOx_BSRR端口设置/清除寄存器(x=A~E);

31~1615~0
BR15~BR0BS15~BS0
清除端口x的位y,这些位只能以字(16位)的形式操作。
0:对对应的ODRy位不产生影响
1:清除对应的ODRy位为0
设置端口x的位y,这些位只能以字(16位)的形式操作。
0:对对应的ODRy位不产生影响
1:设置对应的ODRy位为1
1.3.6 端口清除寄存器(GPIOx_BRR)

GPIOx_BRR端口清除寄存器(x=A~E);

31~1615~0
保留BR15~BR0
清除端口x的位y,这些位只能以字(16位)的形式操作。
0:对对应的ODRy位不产生影响
1:清除对应的ODRy位为1
1.4 GPIO输出输出编码流程

GPIO的输入/输出一般步骤如下:

(1)使能外设时钟:RCC->APB2ENR

在设置STM32外设的时候,任何时候都需要先使能该外设的始终,APB2ENRAPB2(高速外设)总线上的外设时钟使能寄存器;

15141312111098
ADC3 ENUART1 ENTIM8 ENSPI1 ENTIM1 ENADC2 ENADC1 ENIOPG
76543210
IOPFIOPEIOPDIOPCIOPBIOPA保留AFIO EN

其中对应位置1表示使能。如果使能I/O复用,需要设置AFIO EN位为1。

(2)配置输入/输出:GPIOx->CRLGPIOx->CRH

(3)输入GPIOx->IDR、输出GPIOx->ODR

1.5 代码实现
1.5.1 GPIO初始化函数
/******************************************************** ********************
* File Name          : gpio.c
* Author             : Zhengyang
* Description        : 初始化GPIO口
					   portx:端口号PORTA~E
					   pinx:引脚号PORT0~15
					   gpio_cfg:输入输出模式配置
					   bit:输出电平设置 高:HIGH  低:LOW
********************************************************************************/ 
void gpio_init(PORTx_PINx portx_pinx,GPIO_CFG gpio_cfg,BIT_ACTION bit)
{
    GPIO_TypeDef *GPIO;
	u8 portx = portx_pinx/16;                 //获取端口号
  	u8 pinx = portx_pinx%16;	        	  //获取引脚号

   	if(portx==0)
	     GPIO=GPIOA;
    else if(portx==1)
		 GPIO=GPIOB;
    else if(portx==2)
		 GPIO=GPIOC;
    else if(portx==3)
	     GPIO=GPIOD;
    else
	     GPIO=GPIOE;

	RCC->APB2ENR|=1<<(portx+2);	                   //外设时钟使能

	if(pinx<8)
	{
	     GPIO->CRL &=~(0x0F<<(pinx*4));             //该引脚模式配置四位清零
	     GPIO->CRL|=gpio_cfg<<(4*pinx);             //端口配置寄存器CRL
	}
	else if(pinx>=8)    
	{
	     GPIO->CRH &=~(0x0F<<((pinx-8)*4));             //该引脚模式配置四位清零
	     GPIO->CRH|=gpio_cfg<<(4*(pinx-8));             //端口配置寄存器CRH
	}
	if((gpio_cfg&0x03)!=0x00)             				                //输出
	{
	 		 if(pinx<8)
			    GPIO->ODR|=bit<<pinx;		        //端口数据输出寄存器ODR
		     else								
				GPIO->ODR|=bit<<pinx;	   	        //端口数据输出寄存器	 
	}
}

这里PORTx_PINxGPIO_CFGBIT_ACTION均是枚举类型;

/************************************* 端口配置 ************************************************************/
typedef  enum   			    //宏定义端口配置
{
	//这里的值不能改!!!
    GPI                     = 0x00,                       //定义管脚输入方向    
    GPO_SpeedMax_10         = 0x01,                       //定义管脚输出方向   最大速度10MHZ
	GPO_SpeedMax_2          = 0x02,                       //定义管脚输出方向   最大速度2MHZ
    GPO_SpeedMax_50         = 0x03,                       //定义管脚输出方向   最大速度50MHZ
	//输入模式可用
    GPI_DOWN    = 0x08|GPI,                       //输入下拉        PxODR需配置为0          
    GPI_UP      = 0x08|GPI,                       //输入上拉        PxODR需配置为1       		 复用功能时一般采用
    GPI_ANALOG  = 0x00|GPI,                       //模拟输入
    GPI_FLOAT   = 0x04|GPI ,                      //浮空输入
   
   //输出模式不能用
	GPO_PUSH_PULL     = 0x00,                     //通用推挽输出
	GPO_OPEN_DRAIN    = 0x04,                     //通用开漏输出
   	GPO_MULPUSH_PULL  = 0x08,                     //复用推挽输出
	GPO_MULOPEN_DRAIN = 0x0C,                     //复用开漏输出

	//输出模式可用
	GPO_PUSH_PULL_50         =  GPO_SpeedMax_50| GPO_PUSH_PULL,                 //通用推挽输出,最大速度50MHZ   通用输出一般设置
	GPO_PUSH_PULL_10         =  GPO_SpeedMax_10| GPO_PUSH_PULL,                 //通用推挽输出,最大速度10MHZ   
	GPO_PUSH_PULL_2          =  GPO_SpeedMax_2 | GPO_PUSH_PULL,                 //通用推挽输出,最大速度2MHZ
	GPO_MULPUSH_PULL_2       =  GPO_SpeedMax_2| GPO_MULPUSH_PULL,  				//复用推挽输出,最大速度2MHZ
	GPO_MULPUSH_PULL_10      =  GPO_SpeedMax_10| GPO_MULPUSH_PULL, 				//复用推挽输出,最大速度10MHZ
	GPO_MULPUSH_PULL_50      =  GPO_SpeedMax_50| GPO_MULPUSH_PULL               //复用推挽输出,最大速度50MHZ   复用功能 输出模式一般采用这样设置
}GPIO_CFG;

/***************************************** 宏定义引脚号 *************************************************************/ 
typedef enum 
{
   PA0=0,
   PA1=1,
   PA2=2,
   PA3=3,
   PA4=4,
   PA5=5,
   ......
   PE13=77,
   PE14=78,
   PE15=79
 } PORTx_PINx;

typedef enum   					    //外部端口输出电平
{ 
  LOW  = 0,
  HIGH = 1
} BIT_ACTION;
1.5.2 PXout/PXIn

定义GPIO输入输出宏:

/********************** 位运算符优先级低于算术运算符 <<  &  | ~  所以位运算符必须加括号	 ***********************/
#define  Bit_Band(Addr,Bit_Num)     *((volatile unsigned long *)((Addr&0xF0000000)+0x02000000+((Addr&0xfffff)<<5)+(Bit_Num<<2)))


/********************************************** I/O 地址映射 **************************************************/
#define GPIOA_IDR         (GPIOA_BASE + 0x08)       //0x40010808
#define GPIOA_ODR         (GPIOA_BASE + 0x0C) 		//0x4001080C
#define GPIOB_IDR         (GPIOB_BASE + 0x08) 		//0x40010C08
#define GPIOB_ODR         (GPIOB_BASE + 0x0C)  		//0x40010C0C
#define GPIOC_IDR         (GPIOC_BASE + 0x08) 		//0x40011008
#define GPIOC_ODR         (GPIOC_BASE + 0x0C) 		//0x4001100C
#define GPIOD_IDR         (GPIOD_BASE + 0x08) 		//0x40011408
#define GPIOD_ODR         (GPIOD_BASE + 0x0C) 		//0x4001180C
#define GPIOE_IDR         (GPIOE_BASE + 0x08) 		//0x40011808
#define GPIOE_ODR         (GPIOE_BASE + 0x0C)  		//0x4001180C


/********************************************** 宏定义输入输出	 ************************************************/
#define  PAout(Bit_Num)		                    Bit_Band(GPIOA_ODR,Bit_Num)	  //输出  1位输出
#define  PAin(Bit_Num)							Bit_Band(GPIOA_IDR,Bit_Num)	  //输入
#define  PBout(Bit_Num)		                    Bit_Band(GPIOB_ODR,Bit_Num)
#define  PBin(Bit_Num)							Bit_Band(GPIOB_IDR,Bit_Num)
#define  PCout(Bit_Num)		                    Bit_Band(GPIOC_ODR,Bit_Num)
#define  PCin(Bit_Num)							Bit_Band(GPIOC_IDR,Bit_Num)
#define  PDout(Bit_Num)		                    Bit_Band(GPIOD_ODR,Bit_Num)
#define  PDin(Bit_Num)							Bit_Band(GPIOD_IDR,Bit_Num)
#define  PEout(Bit_Num)		                    Bit_Band(GPIOE_ODR,Bit_Num)
#define  PEout(Bit_Num)		                    Bit_Band(GPIOE_ODR,Bit_Num)

这里利用到了STM32的位段功能,Cortex-M3储存器包含两个位段(bit-band)区,将别名寄存器的每个字映射到位段寄存器的一个位。

映射公式(外设寄存器和SRAM可被映射):

\[bit\_word\_addr = bit\_band\_base + (byte\_offset \times 32) +(bit\_number * 4) \]

其中:

  • bit_word_addr:别名寄存器中的地址映射到某一个目标位;
  • bit_band_base:别名区的起始地址;
  • byte_offset:目标位的字节在位段的序号;
  • bit_number:目标位所在位置(0~31);

例如:SRAM中某一变量地址为0x20000300字节中的位2;

\[0\times22006008 = 0\times22000000 + ((0\times20000300 - 0\times20000000) \times 8 + 2) \times 4% \]
1.5.3 点亮LED

如需点亮LED,我们只需要在main函数添加如下代码:

gpio_init(PD2,GPO_SpeedMax_50,HIGH);	             // PD2接入LED2 
gpio_init(PA8,GPO_SpeedMax_50,HIGH);                 // PA8接入LED1 
while(1)
{	    
	PAout(8) = 0;
	PDout(2) = 1;
	delay_ms(1500);
	PAout(8) = 1;
	PDout(2) = 0;
	delay_ms(1500);
}
1.5.4 烧录测试

编译完成后通过ISP烧录到开发版,我们可以看到LED1LED2交替点亮,时间间隔为1.5s

二、串口

STM32F103微控制器提供了5组串口,分别是USART1, USART2, 和USART3UART4UART5

  • 其中1-3是通用同步/异步串行接口USART(Universal Synchronous/Asynchronous Receiver/Transmitter)
  • 4,、5是通用异步串行接口UART(Universal Asynchronous Receiver/Transmitter)

这些串口可以用来进行UART 通信,例如发送和接收数据。

2.1 串口引脚介绍
串口TXRX
USART_1PA9PA10
USART_2PA2PA3
USART_3PB10PB11
UART_4PC10PC11
UART_5PC12PD2
2.2 串口寄存器
2.2.1 波特率设置寄存器(UARTx_BRR)

波特率计算公式如下:$$波特率=\frac{f_{pclkx}}{ 16 \times USARTDIV}$$

其中:

  • PCLK1用于USART2USART3USART4USART5,最大36MHz
  • PCLK2用于USART1,最大72MHz

例如:PCLK2=72MHz,设置波特率为9600

USARTDIV = 72000000 / (9600 * 16) = 468.75

DIV_Fraction = 16 * 0.15 = 12 = 0x0C

DIV_Mantissa = 468.75 = 0x1D4

2.2.2 控制寄存器1(UARTx_CR1)
2.2.3 控制寄存器2(UARTx_CR2)

2.2.4 控制寄存器3(UARTx_CR3)

2.2.5 数据寄存器(UARTx_DR)

USART_DR实际是包含了两个寄存器,一个专门用于发送的TDR,一个专门用于接收的RDR

  • 进行发送数据操作时,往USART_DR写入数据会自动存储在TDR内;
  • 当进行读取数据操作时,向USART_DR读取数据会自动提取RDR数据。

串行通信时一位一位传输的,所以TDRRDR寄存器都是介于系统总线和移位寄存器间的;发送数据时把TDR内容转移到发送移位寄存器上,接收数据时则是把接收到的每一位顺序保存在接收移位寄存器内进而转移到RDR

2.2.6 状态寄存器(UARTx_SR)

状态寄存器适用于检测串口此时所处的状态。
它能够检测到的状态有:发送寄存器空位、发送完成位、读数据寄存器非空位、检测到主线空闲位、过载错误为等等。
主要关注两个位:RXNETC(第5、6两位);

  • RXNE(读数据寄存器非空):当该位被置1的时候,就是提示已经有数据被接收到了,并且可以读出来了(即RDR移位寄存器中的数据被转移到USART_DR寄存器中);这时候要做的就是尽快读取USART_DR,从而将该位清零,也可以向该位写0,直接清除;
  • TC(发送完成):当该位被置1的时候,表示USART_DR内的数据已经被发送完成了。如果设置了这个位的中断,则会产生中断。该位也有两种清零方式:读USART_SR,写USART_DR;直接向该位写0
2.3 串口发送/接收编码流程

(1)串口时钟使能;RCC->APB2ENR

在设置STM32外设的时候,任何时候都需要先使能该外设的始终,APB2ENRAPB2(高速外设)总线上的外设时钟使能寄存器;

15141312111098
ADC3 ENUART1 ENTIM8 ENSPI1 ENTIM1 ENADC2 ENADC1 ENIOPG
76543210
IOPFIOPEIOPDIOPCIOPBIOPA保留AFIO EN

USART1是挂在APB2,如果使用串口1,需要使能位14

(2)串口复位/停止复位;RCC->APB2RSTR

一般在系统刚开始配置外设时,都会先复位该外设的操作;

15141312111098
ADC3 RSTUART1 RSTTIM8 RSTSPI1 RSTTIM1 RSTADC2 RSTADC1 RSTIOPG RST
76543210
IOPF RSTIOPE RSTIOPD RSTIOPC RSTIOPB RSTIOPA RST保留AFIO RST

写1复位,使其重新工作,写0结束复位。

(3)使能串口时钟,以及I/O状态设置;

  • RX:下拉输入;
  • TX:复用推挽输出模式;

(4)设置USART_CR1PEIERXNEIE

  • PEIE:校验中断使能 ,校验错误,硬件PE位置1;
  • RXNEIE:使能接收中断;

(5)串口参数设置;

  • 计算波特率,利用USART_BRR寄存器选择要求的波特率;
  • 设置USART_CR1M位来定义字长;
  • 设置USART_CR2STOP位来定义停止位的位数;

(6)设置USART_CR1中的TE位,发送使能;

(7)设置USART_CR1中的RE位,接收使能;

(8)设置USART_CR1中的UE位来使能串口;

(9)把要发送的数据写进USART_DR寄存器(此动作清除TXE)位,在只有一个缓存器的情况下对每个待发送的数据重复操作;

(10)在USART_DR寄存器中写入最后一个数据字后,要等待UART_SR寄存器TC=1,它表示最后一个数据帧的传输结束,当需要关闭USART或需要进入停机模式前。需要确认传输结束,避免破坏最后一次传输。

2.4 代码实现
2.4.1 串口初始化
/***************************************************************************************************************
 *  Description:      初始化串口,设置波特率
 *  parameter  :      USARTn_e       模块号(USART_1~UART_5)
                      baud          波特率,如9600、19200、56000、115200等
					  pclk          pclk2时钟用于USART1    等于系统时钟=外部晶振*9 单位MHX  一般为72
					                pclk1时钟用于USART2~5  等于系统时钟=外部晶振*9 单位MHX  一般为36    
 *  Sample usage:     usart_init (USART_3, 9600);        //初始化串口3,波特率为9600

 *                    USARTx->BRR(32)   波特率设置寄存器 15~4:USART分频器除法因子(USARTDIV)的整数部分 
													3~0 :USART分频器除法因子(USARTDIV)的小数部分
	                  波特率=f(plckx)/(16*USARTDIV)												 

 **************************************************************************************************************/
void usart_init (USARTn usartn, u32 baud)
{
     u8 pclk;
     float temp;
	 u16 mantissa;                     //USARTDIV的整数部分
	 u16 fraction;                     //USARTDIV小数部分

	 if(usartn<1)			           //串口时钟选择
	    pclk=PCLK2;
	 else
	    pclk=PCLK1;
	 temp = (float)(pclk*1000000)/(baud*16);
	 mantissa=  temp;                  //得到整数部分
	 fraction=(temp-mantissa)*16;       //得到小数部分
	 mantissa<<=4;
	 mantissa+=fraction;				//USART分频器除法因子(USARTDIV)


    /* 配置 UART功能的 复用管脚 */
    switch(usartn)
    {
//***********************************************************USART1***********************************************
    case USART_1:
	    RCC->APB2ENR  |= 1<<14;                       //使能USART1时钟
		RCC->APB2RSTR |= 1<<14;                       //复位USART1
		RCC->APB2RSTR &=~(1<<14);                      //停止复位  
        if(USART1_TX == PA9)
        {
			gpio_init(PA9,GPO_MULPUSH_PULL_50,HIGH);   //复用推挽输出,最大速度50MHZ 
	
        }
        else if(USART1_TX == PB6)
        {
		    RCC->APB2ENR |= 1<<0;                       //使能I/O复用时钟
          	gpio_init(PB6,GPO_MULPUSH_PULL_50,HIGH);    //复用推挽输出,最大速度50MHZ
			AFIO->MAPR |= 1<<2;                          //USART1重映射 
        }
        else
        {
            ASSERT(0);                                //上诉条件都不满足,直接断言失败了,设置管脚有误?
        }

        if(USART1_RX == PA10)
        {
             gpio_init(PA10,  GPI_DOWN,HIGH);          //输入下拉
			 PAout(10)=0;                              //PxODR需配置为0      
        }
        else if(USART1_RX == PB7)
        {
             gpio_init(PB7,GPI_DOWN,HIGH);             //输入下拉
			 PBout(7)=0;  
			 AFIO->MAPR |= 1<<2;                        //USART1重映射   
        }
        else
        {
            ASSERT(0);                                 //上诉条件都不满足,直接断言失败了,设置管脚有误?
        }

#if EN_USART1_RX                                       
        USARTx[usartn]->CR1|=1<<8;                              //位8: PEIE   校验中断使能  校验错误,硬件PE位置1
        USARTx[usartn]->CR1|=1<<5;                              //位5:  RXNEIE 使能接收中断  
#endif

#if EN_USART1_TX                                       
        USARTx[usartn]->CR1|=1<<6;                              //位6:TCIE 发送完成中断使能位 当SR中的TC硬件置1,将产生中断
#endif   
        break;	


//*****************************************************USART2********************************************************
    case USART_2:
	    RCC->APB1ENR  |= 1<<17;                       //使能USART2时钟
		RCC->APB1RSTR |= 1<<17;                       //复位USART2
		RCC->APB1RSTR &=~(1<<17);                      //停止复位  
        if(USART2_TX == PA2)
        {
			gpio_init(PA2,GPO_MULPUSH_PULL_50,HIGH);   //复用推挽输出,最大速度50MHZ 
	
        }
        else if(USART2_TX == PD5)
        {
		    RCC->APB2ENR |= 1<<0;                       //使能I/O复用时钟
          	gpio_init(PD5,GPO_MULPUSH_PULL_50,HIGH);    //复用推挽输出,最大速度50MHZ
			AFIO->MAPR |= 1<<3;                          //USART2重映射 
        }
        else
        {
            ASSERT(0);                                //上诉条件都不满足,直接断言失败了,设置管脚有误?
        }

        if(USART2_RX == PA3)
        {
             gpio_init(PA3,  GPI_DOWN,HIGH);          //输入下拉
			 PAout(3)=0;    
        }
        else if(USART2_RX == PD6)
        {
             gpio_init(PD6,GPI_DOWN,HIGH);            //输入下拉
			 PDout(6)=0;  
			 AFIO->MAPR |= 1<<3;                       //USART2重映射   
        }
        else
        {
            ASSERT(0);                                 //上诉条件都不满足,直接断言失败了,设置管脚有误?
        }

#if EN_USART2_RX                                       
        USARTx[usartn]->CR1|=1<<8;                              //位8: PEIE   校验中断使能  校验错误,硬件PE位置1
        USARTx[usartn]->CR1|=1<<5;                              //位5:  RXNEIE 使能接收中断  
#endif

#if EN_USART2_TX                                       
        USARTx[usartn]->CR1|=1<<6;                              //位6:TCIE 发送完成中断使能位 当SR中的TC硬件置1,将产生中断
#endif   
        break;	

//*****************************************************USART3********************************************************
    case USART_3:
	    RCC->APB1ENR  |= 1<<18;                       //使能USART3时钟
		RCC->APB1RSTR |= 1<<18;                       //复位USART3
		RCC->APB1RSTR &=~(1<<18);                     //停止复位  
        if(USART3_TX == PB10)
        {
			gpio_init(PB10,GPO_MULPUSH_PULL_50,HIGH);   //复用推挽输出,最大速度50MHZ 
	
        }
        else if(USART3_TX == PC10)
        {
		    RCC->APB2ENR |= 1<<0;                        //使能I/O复用时钟
          	gpio_init(PC10,GPO_MULPUSH_PULL_50,HIGH);    //复用推挽输出,最大速度50MHZ
			AFIO->MAPR &=~(0x03<<4);                     //位5~4清零
			AFIO->MAPR |= 0x01<<4;                       //USART3部分重映射 
        }
		else if(USART3_TX == PD8)
        {
		    RCC->APB2ENR |= 1<<0;                        //使能I/O复用时钟
          	gpio_init(PD8,GPO_MULPUSH_PULL_50,HIGH);     //复用推挽输出,最大速度50MHZ
			AFIO->MAPR &=~(0x03<<4);                     //位5~4清零
			AFIO->MAPR |= 0x03<<4;                       //USART3完全重映射 
        }
        else
        {
            ASSERT(0);                                //上诉条件都不满足,直接断言失败了,设置管脚有误?
        }

        if(USART3_RX == PB11)
        {
             gpio_init(PB11,  GPI_DOWN,HIGH);          //输入下拉
			 PBout(11)=0;    
        }
        else if(USART3_RX == PC11)
        {
             gpio_init(PC11,GPI_DOWN,HIGH);            //输入下拉
			 PCout(11)=0;  
			 AFIO->MAPR &=~(0x03<<4);                  //位5~4清零
			 AFIO->MAPR |= 0x01<<4;                    //USART3部分重映射   
        }
        else if(USART3_RX == PD9)
        {
             gpio_init(PD9,GPI_DOWN,HIGH);            //输入下拉
			 PDout(9)=0;  
			 AFIO->MAPR &=~(0x03<<4);                  //位5~4清零
			 AFIO->MAPR |= 0x03<<4;                    //USART3完全重映射   
        }
        else
        {
            ASSERT(0);                                 //上诉条件都不满足,直接断言失败了,设置管脚有误?
        }

#if EN_USART3_RX                                       
        USARTx[usartn]->CR1|=1<<8;                              //位8: PEIE   校验中断使能  校验错误,硬件PE位置1
        USARTx[usartn]->CR1|=1<<5;                              //位5:  RXNEIE 使能接收中断  
#endif

#if EN_USART3_TX                                       
        USARTx[usartn]->CR1|=1<<6;                              //位6:TCIE 发送完成中断使能位 当SR中的TC硬件置1,将产生中断
#endif   
        break;	
//***********************************************************UART4***********************************************
    case UART_4:
		RCC->APB1ENR  |= 1<<19;                       //使能UART4时钟
		RCC->APB1RSTR |= 1<<19;                       //复位UART4
		RCC->APB1RSTR &=~(1<<19);                     //停止复位   
        if(UART4_TX == PC10)
        {
			gpio_init(PC10,GPO_MULPUSH_PULL_50,HIGH);   //复用推挽输出,最大速度50MHZ 
	
        }
        else
        {
            ASSERT(0);                                //上诉条件都不满足,直接断言失败了,设置管脚有误?
        }

        if(UART4_RX == PC11)
        {
             gpio_init(PC11,  GPI_DOWN,HIGH);          //输入下拉
			 PCout(11)=0;    
        }
        else
        {
            ASSERT(0);                                 //上诉条件都不满足,直接断言失败了,设置管脚有误?
        }

#if EN_UART4_RX                                       
        USARTx[usartn]->CR1|=1<<8;                              //位8: PEIE   校验中断使能  校验错误,硬件PE位置1
        USARTx[usartn]->CR1|=1<<5;                              //位5:  RXNEIE 使能接收中断  
#endif

#if EN_UART4_TX                                       
        USARTx[usartn]->CR1|=1<<6;                              //位6:TCIE 发送完成中断使能位 当SR中的TC硬件置1,将产生中断
#endif   
        break;
//***********************************************************USART1***********************************************
    case UART_5: 
	    RCC->APB1ENR  |= 1<<20;                       //使能UART5时钟
		RCC->APB1RSTR |= 1<<20;                       //复位UART5
		RCC->APB1RSTR &=~(1<<20);                     //停止复位  
        if(UART5_TX == PC12)
        {
			gpio_init(PC12,GPO_MULPUSH_PULL_50,HIGH);   //复用推挽输出,最大速度50MHZ 
	
        }
        else
        {
            ASSERT(0);                                //上诉条件都不满足,直接断言失败了,设置管脚有误?
        }

        if(UART5_RX == PD2)
        {
             gpio_init(PD2,  GPI_DOWN,HIGH);          //输入下拉
			 PDout(2)=0;    
        }
        else
        {
            ASSERT(0);                                 //上诉条件都不满足,直接断言失败了,设置管脚有误?
        }

#if EN_UART5_RX                                       
        USARTx[usartn]->CR1|=1<<8;                              //位8: PEIE   校验中断使能  校验错误,硬件PE位置1
        USARTx[usartn]->CR1|=1<<5;                              //位5:  RXNEIE 使能接收中断  
#endif

#if EN_UART5_TX                                       
        USARTx[usartn]->CR1|=1<<6;                              //位6:TCIE 发送完成中断使能位 当SR中的TC硬件置1,将产生中断
#endif   
        break;				
	}

//***************************************************************************************************************
        USARTx[usartn]->BRR=mantissa;                           //波特率设置
		USARTx[usartn]->CR1|=1<<2;                              //位2:RE 接收使能
		USARTx[usartn]->CR1|=1<<3;                              //位3:TE 发送使能 
		USARTx[usartn]->CR1&=~(1<<12);                          //位12:M 定义字长0:8位   1:9位
      //USARTx[usartn]->CR3|=1<<7;                              //位7:  DMAT使能发送	
	  //USARTx[usartn]->CR3|=0<<6;  							//位6: DMAR禁止接收
        USARTx[usartn]->CR1|=1<<13;                             //位13:UE 串口使能
}
2.4.2 串口接收中断

当开启串口接收中断时,如果接收到数据,串口状态寄存器RXNE位被设置为1;接着就可以从数据寄存器读取到接收到的数据;

#define USART_RX_LEN          1024          //定义接收到的字符串最大长度	最大不超过2^14-1=16383

u16  USART1_RX_STA  =  0  ;         //自定义接收状态寄存器   
u16  USART2_RX_STA  =  0  ;         //自定义接收状态寄存器 
u16  USART3_RX_STA  =  0  ;         //自定义接收状态寄存器
u16  UART4_RX_STA   =  0  ;         //自定义接收状态寄存器  
u16  UART5_RX_STA   =  0  ;         //自定义接收状态寄存器 

volatile   USART_TypeDef *USARTx[5] = {USART1,USART2,USART3,UART4,UART5}; //定义五个指针数组保存 USARTx 的地址

/***********************************************************************************************************************
 *
 *			 Description:    USART1中断函数
 *			 USARTx->SR(32) :	 接收状态寄存器	      7:TXE                      6:TC                    5:RXNE
                                    		         发送缓冲区为空         发送完成标志位		   接收到数据标志位(读取DR则自动清0)
             USARTx->DR(32) :    接收/发送数据缓冲区   只使用低8位
			 当接收到回车(回车由两个字节组成回车符0x0D和换行符0x0A),表示一次接收数据完成
 *
 **********************************************************************************************************************/
void USART1_IRQHandler(void)
{
   u8 temp;
   if(USART1->SR&(0x1<<5))                   //接收到数据
   {
       temp = USART1->DR;
	   if((USART1_RX_STA&0x8000)==0)	     //接收数据未完成
	   { 
	       if(USART1_RX_STA&0x4000)          //已经接收到了0xOD
		   {
		     if(temp!=0x0A)                  //接收到的数据有误     
			     USART1_RX_STA=0;			 //状态寄存器清零
			 else
			     USART1_RX_STA |=0x8000;     //接收完成了		   
		   }        
		   else								 //没有接收到0xOD
		   {
	           if(temp == 0x0D)	             //接收到回车符第一个字符 0xOD
	           {
	             USART1_RX_STA|=0x4000;
	           }
			   else 			              //没有接收到0xOD  
			   {
			     USART1_RX_BUF[USART1_RX_STA&0x3FFF]=temp;       //保存数据
			   	 USART1_RX_STA++;
				 if(USART1_RX_STA>(USART_RX_LEN-1))
				        USART1_RX_STA=0;                        //接收数据超过USART_RX_LEN,则丢失数据,重新接收			   
			   }
		   } 
	   }
	}
   		 /*******************************************************************************************/
   	     //可在这里自定义其他任务	
	    	   		   
   
   		 /********************************************************************************************/
}

在上面代码中, 当接收到回车(回车由两个字节组成回车符0x0D和换行符0x0A),表示一次接收数据完成,接收到的数据存放在字符数组USART1_RX_BUF

/***************************************************************************************************************
 *
 *		 USARTx_RX_BUF[USART_RX_LEN]:保存接收的数据
 *       USARTx_RX_STA              :自定义16位接收状态寄存器                                     
 									bit15            bit14              bit13~0
 									接收完成标志	 接收到0xOD标志		接收到有效数据的个数
 *		 当接收到回车(回车由两个字节组成回车符0x0D和换行符0x0A),表示一次接收数据完成
 *
 **************************************************************************************************************/
u8 USART1_RX_BUF[USART_RX_LEN];		//接收缓冲区
u8 USART2_RX_BUF[USART_RX_LEN];		//接收缓冲区
u8 USART3_RX_BUF[USART_RX_LEN];		//接收缓冲区
u8 UART4_RX_BUF[USART_RX_LEN];		//接收缓冲区
u8 UART5_RX_BUF[USART_RX_LEN];		//接收缓冲区
2.4.3 串口发送
/***************************************************************************************************************
 *
 *   函数名称:uart_putchar
 *   功能说明:串口发送一个字节
 *   参数说明:USARTn       模块号(USART_1~USART_5)
 *   函数返回:无
 *
***************************************************************************************************************/
void usart_putchar (USARTn usartn, char ch)
{
    //等待发送缓冲区空
    while(!(USARTx[usartn]->SR&(1<<7)));	       //等待TXE=1	 发送缓冲区为空 数据已转到移位寄存器
    //发送数据
    USARTx[usartn]->DR=ch;
//	while(!(USARTx[usartn]->SR&(1<<5)));		   //等待TC=1,发送完成
}
 
/****************************************************************************************************************
*
*  函数名称:uart_sendStr
*  功能说明:串行发送字符串
*  参数说明:USARTn      模块号(USART_1~USART_5)
*            str         字符串
*  函数返回:无
*
*************************************************************************/
void usart_sendStr (USARTn usartn, const u8 *str)
{
    while(*str)
    {
        usart_putchar(usartn, *str++);
    }
}

/****************************************************************************************************************
*
*  函数名称:uart_sendNum      
*  功能说明:串行发送字符串
*  参数说明:USARTn      模块号(USART_1~USART_5)
*            num         无符号整数
*  函数返回:无
*
*************************************************************************/
void usart_sendNum(USARTn usartn , u32 num)
{
	u8 ch[32];
	u8 i = 0;
	IntToStr(num, ch);
	while(ch[i])
    {
	    usart_putchar(usartn, ch[i++]);
    }
}
2.4.4 重定向printf函数

重定向这个C库的printf函数,修改printf函数底层调用fputc

//加入以下代码,支持printf函数,而不需要选择use MicroLIB	  
/***************************START********************************************************************/
#if 1
#pragma import(__use_no_semihosting)             
//标准库需要的支持函数                 
struct __FILE 
{ 
	int handle; 
	/* Whatever you require here. If the only file you are using is */ 
	/* standard output using printf() for debugging, no file handling */ 
	/* is required. */ 
}; 
/* FILE is typedef’ d in stdio.h. */ 
FILE __stdout;       
//定义_sys_exit()以避免使用半主机模式    
_sys_exit(int x) 
{ 
	x = x; 
} 
//重定义fputc函数 					  默认使用的是USART1口
int fputc(int ch, FILE *f)
{      
	USART1->DR = (u8) ch;      
	while((USART1->SR&0x40)==0);      //循环发送,直到发送完毕   TC=1
	return ch;
}
#endif 
/***************************END*******************************************************************************/
2.4.5 输出接收数据

如需使能串口1接收数据,我们只需要在main函数添加如下代码:

STM32_NVIC_Init(2,USART1_IRQn,0,1);		      //必须配置,其中包括中断使能
usart_init(USART_1,115200);				      //串口1初始化,波特率9600 映射到PA9 PA10

while(1)
{	    
	// 数据接收完成
    while(USART1_RX_STA&0x8000)
	{
	    // 遍历数据
	    for(i=0;i<(USART1_RX_STA&0x3FFF);i++)
	    {		  
			usart_putchar(USART_1,USART1_RX_BUF[i]);
	    }
	    usart_putchar(USART_1,'\n');
	    // 清空接收标志位 
	    USART1_RX_STA=0;
	}

	delay_ms(1500);
}
2.5.6 烧录测试

编译完成后通过ISP烧录到开发版,DTR的低电平复位,RTS高电平进BootLoader

由于ISP烧录使用的也是串口1,因此可以使用usb线连接到开发板ISP接口,然后打开串口助手。即可通过ISP接口与串口1进行数据交互;

这里有几点需要注意:

  • 如果我们发送的是HEX,一行数据的结尾我们需要追加0x0D(\r),0x0A(\n);
  • 中断程序不能执行耗时长的程序,否则可能导致后面的数据到达的时候无法接受,会造成只接收到一个/两个字节的现象;因此需要避免在USART1_IRQHandler中使用printf函数。

三、源码下载

源码下载路径:stm32f103

参考文章

[1] STM32F103时钟系统讲解

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2245527.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Statsmodels之OLS回归

目录 Statsmodels基本介绍OLS 回归实战实战1&#xff1a;实战2&#xff1a; Statsmodels基本介绍 Statsmodels 是 Python 中一个强大的统计分析包&#xff0c;包含了回归分析、时间序列分析、假设检验等等的功能。Statsmodels 在计量的简便性上是远远不及 Stata 等软件的&…

在 macOS 和 Linux 中,波浪号 `~`的区别

文章目录 1、在 macOS 和 Linux 中&#xff0c;波浪号 ~macOS示例 Linux示例 区别总结其他注意事项示例macOSLinux 结论 2、root 用户的主目录通常是 /root解释示例切换用户使用 su 命令使用 sudo 命令 验证当前用户总结 1、在 macOS 和 Linux 中&#xff0c;波浪号 ~ 在 macO…

人工智能之机器学习5-回归算法2【培训机构学习笔记】

培训班ppt内容&#xff1a; 个人精进总结&#xff1a; 可解释方差 定义 可解释方差的回归评分函数是一种用于评估回归模型性能的指标&#xff0c;以下从其定义、计算公式、取值范围及意义、应用场景等方面进行详细介绍&#xff1a; 可解释方差&#xff08;Explained Varian…

vue2中引入cesium全步骤

1.npm 下载cesium建议指定版本下载&#xff0c;最新版本有兼容性问题 npm install cesium1.95.0 2.在node_models中找到cesium将此文件下的Cesium文件复制出来放在项目的静态资源public中或者static中&#xff0c;获取去github上去下载zip包放在本地也可以 3.在index.html中引…

数据结构(顺序栈——c语言实现)

栈的基本概念&#xff1a; 栈是限制在一端进行插入操作和删除操作的线性表&#xff08;俗称堆栈&#xff09;&#xff0c;允许进行操作的一端称为“栈顶”&#xff0c;另一固定端称为“栈底”&#xff0c;当栈中没有元素时称为“空栈” 特点&#xff1a;先进后出&#xff08;FI…

基于Windows系统用C++做一个点名工具

目录 一、前言 二、主要技术点 三、准备工作 四、主界面 1.绘制背景图 2、实现读取花名册功能 3.实现遍历花名册功能 4.实现储存功能 4.1创建数据库 4.2存储数据到数据库表 4.3读取数据库表数据 一、前言 人总是喜欢回忆过去&#xff0c;突然回忆起…

前端监控之sourcemap精准定位和还原错误源码

一、概述 在前端开发中&#xff0c;监控和错误追踪是确保应用稳定性和用户体验的重要环节。 随着前端应用的复杂性增加&#xff0c;JavaScript错误监控变得尤为重要。在生产环境中&#xff0c;为了优化加载速度和性能&#xff0c;前端代码通常会被压缩和混淆。这虽然提升了性…

算法编程题-排序

算法编程题-排序 比较型排序算法冒泡排序选择排序插入排序希尔排序堆排序快速排序归并排序 非比较型排序算法计数排序基数排序 本文将对七中经典比较型排序算法进行介绍&#xff0c;并且给出golang语言的实现&#xff0c;还包括基数排序、计数排序等非比较型的算法的介绍和实现…

Jenkins修改LOGO

重启看的LOGO和登录页面左上角的LOGO 进入LOGO存在的目录 [roottest-server01 svgs]# pwd /opt/jenkins_data/war/images/svgs [roottest-server01 svgs]# ll logo.svg -rw-r--r-- 1 jenkins jenkins 29819 Oct 21 10:58 logo.svg #jenkins_data目录是我挂载到了/opt目录&…

【MyBatisPlus·最新教程】包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段

文章目录 一、MyBatis-Plus简介二、快速入门1、环境准备2、将mybatis项目改造成mybatis-plus项目&#xff08;1&#xff09;引入MybatisPlus依赖&#xff0c;代替MyBatis依赖&#xff08;2&#xff09;配置Mapper包扫描路径&#xff08;3&#xff09;定义Mapper接口并继承BaseM…

云讷科技Kerloud无人飞车专利发布

云讷科技Kerloud无人飞车获得了“一种室内外两用的四旋翼无人飞车”的实用新型专利证书&#xff0c;作为科教社区第一款四旋翼飞车&#xff0c;这项技术结合了无人机和无人车的优势&#xff0c;提供了一种能够在多种环境下使用的多功能飞行器。 这项设计的优势如下&#xff…

Applied Intelligence投稿

一、关于手稿格式&#xff1a; 1、该期刊是一个二区的&#xff0c;模板使用Springer nature格式&#xff0c; 期刊投稿要求&#xff0c;详细期刊投稿指南&#xff0c;大部分按Soringernature模板即可&#xff0c;图片表格声明参考文献命名要求需注意。 2、参考文献&#xff…

Spark SQL大数据分析快速上手-完全分布模式安装

【图书介绍】《Spark SQL大数据分析快速上手》-CSDN博客 《Spark SQL大数据分析快速上手》【摘要 书评 试读】- 京东图书 大数据与数据分析_夏天又到了的博客-CSDN博客 Hadoop完全分布式环境搭建步骤-CSDN博客,前置环境安装参看此博文 完全分布模式也叫集群模式。将Spark目…

零基础上手WebGIS+智慧校园实例(1)【html by js】

请点个赞收藏关注支持一下博主喵&#xff01;&#xff01;&#xff01; 等下再更新一下1. WebGIS矢量图形的绘制&#xff08;超级详细&#xff01;&#xff01;&#xff09;&#xff0c;2. WebGIS计算距离&#xff0c; 以及智慧校园实例 with 3个例子&#xff01;&#xff01;…

[开源] 告别黑苹果!用docker安装MacOS体验苹果系统

没用过苹果电脑的朋友可能会对苹果系统好奇&#xff0c;有人甚至会为了尝鲜MacOS去折腾黑苹果。如果你只是想体验一下MacOS&#xff0c;这里有个更简单更优雅的解决方案&#xff0c;用docker安装MacOS来体验苹果系统。 一、项目简介 项目描述 Docker 容器内的 OSX&#xff08…

IDEA:2023版远程服务器debug

很简单&#xff0c;但是很多文档没有写清楚&#xff0c;wocao 一、首先新建一个远程jvm 二、配置 三、把上面的参数复制出来 -agentlib:jdwptransportdt_socket,servery,suspendn,address5005 四、然后把这串代码放到服务器中&#xff08;这里的0.0.0.0意思是所有IP都能访问&a…

卷积神经网络的padding是什么?如何计算?

文章目录 为什么需要padding&#xff1f;1.Valid Padding&#xff08;有效填充&#xff09;2.Same Padding&#xff08;相同填充&#xff09;2.1.如何计算padding&#xff1f;1. 计算总 padding2. 分配 padding&#xff1a; 2.2.举例子1. 步幅为 1 的 Same Padding2. 步幅不为 …

介绍一下strncmp(c基础)

strncmp是strcmp的进阶版 链接介绍一下strcmp(c基础)-CSDN博客 作用 比较两个字符串的前n位 格式 #include <string.h> strncmp (arr1,arr2,n); 工作原理&#xff1a;strcmp函数按照ACII&#xff08;字符编码顺序&#xff09;比较两个字符串。它从两个字符串的第一…

列出D3的所有交互方法,并给出示例

D3.js 提供了丰富的交互方法&#xff0c;可以用来增强图表的用户交互体验。以下是一些常用的交互方法及其示例&#xff1a; 1. 鼠标事件 on("mouseover", function) 用途: 当鼠标悬停在元素上时触发。示例:svg.selectAll(".bar").on("mouseover&qu…

丹摩征文活动 | AI创新之路,DAMODEL助你一臂之力GPU

目录 前言—— DAMODEL&#xff08;丹摩智算&#xff09; 算力服务 直观的感受算力提供商的强大​ 平台功能介绍​ 镜像选择 云磁盘创建 总结 前言—— 只需轻点鼠标,开发者便可拥有属于自己的AI计算王国 - 从丰富的GPU实例选择,到高性能的云磁盘,再到预配置的深度学习…