程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)

STM32F103 GPIO和串口配置

本节我们将会对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:28 27:24 23:20 19:16 15:12 11:8 7:4 3:0
CNF7[1:0]
MODE7[1:0]
... .... .... .... .... ..... CNF0[1:0]
MODE0[1:0]

其中:

MODE[1:0] CNF[1:0]
输入模式 0 0 0 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~16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
保留 IDR15 .. .. .. .. .. .. .. .. .. .. .. .. .. .. IDR0

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

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

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

31~16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
保留 ODR15 .. .. .. .. .. .. .. .. .. .. .. .. .. .. ODR0

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

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

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

31~16 15~0
BR15~BR0 BS15~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~16 15~0
保留 BR15~BR0
清除端口x的位y,这些位只能以字(16位)的形式操作。
0:对对应的ODRy位不产生影响
1:清除对应的ODRy位为1

1.4 GPIO输出输出编码流程

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

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

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

15 14 13 12 11 10 9 8
ADC3 EN UART1 EN TIM8 EN SPI1 EN TIM1 EN ADC2 EN ADC1 EN IOPG
7 6 5 4 3 2 1 0
IOPF IOPE IOPD IOPC IOPB IOPA 保留 AFIO EN

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

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

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

1.5 代码实现

1.5.1 GPIO_TypeDef

GPIO寄存器结构GPIO_TypeDef,在文件stm32f10x_map.h中定义如下:

/*------------------------ General Purpose and Alternate Function IO ---------*/
typedef struct
{
  vu32 CRL;	  // 端口配置低寄存器 ;
  vu32 CRH;	  // 端口配置高寄存器 ;
  vu32 IDR;	  // 端口输入数据寄存器 ;
  vu32 ODR;	  // 端口输出数据寄存器 ;
  vu32 BSRR;  // 端口位设置/复位寄存器 ;
  vu32 BRR;	  // 端口位复位寄存器 ;
  vu32 LCKR;  // 端口配置锁定寄存器 ;
} GPIO_TypeDef;

#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)

#ifdef _GPIOA
  #define GPIOA               ((GPIO_TypeDef *) GPIOA_BASE)
#endif /*_GPIOA */

#ifdef _GPIOB
  #define GPIOB               ((GPIO_TypeDef *) GPIOB_BASE)
#endif /*_GPIOB */

#ifdef _GPIOC
  #define GPIOC               ((GPIO_TypeDef *) GPIOC_BASE)
#endif /*_GPIOC */

#ifdef _GPIOD
  #define GPIOD               ((GPIO_TypeDef *) GPIOD_BASE)
#endif /*_GPIOD */

#ifdef _GPIOE
  #define GPIOE               ((GPIO_TypeDef *) GPIOE_BASE)
#endif /*_GPIOE */

#ifdef _GPIOF
  #define GPIOF               ((GPIO_TypeDef *) GPIOF_BASE)
#endif /*_GPIOF */

#ifdef _GPIOG
  #define GPIOG               ((GPIO_TypeDef *) GPIOG_BASE)
#endif /*_GPIOG */

在前面我们已经对GPIO_TypeDef结构体中定义的大部分寄存器进行了详细的介绍,那么我们如何编码去初始化这些寄存器呢?

1.5.2 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(bit == LOW)
            GPIO->ODR|=1<<pinx;		            //端口数据输出寄存器
		else
            GPIO->ODR&=~(1<<pinx);	   	        //端口数据输出寄存器
	 }
	 else if( gpio_cfg == GPI_UP)               // 输入上拉,PxODR需配置为1
	 {
		GPIO->ODR|=1<<pinx;			            //端口数据输出寄存器
	 }
	 else if( gpio_cfg == GPI_DOWN)             // 输入下拉,PxODR需配置为0
	 {
		GPIO->ODR&=~(1<<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.3 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×32)+(bit_number4)

其中:

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

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

0×22006008=0×22000000+((0×200003000×20000000)×8+2)×4

1.5.4 点亮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.5 烧录测试

编译完成后通过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 串口引脚介绍

串口 TX RX
USART_1 PA9 PA10
USART_2 PA2 PA3
USART_3 PB10 PB11
UART_4 PC10 PC11
UART_5 PC12 PD2

2.2 串口寄存器

2.2.1 波特率设置寄存器(UARTx_BRR

波特率计算公式如下:=fpclkx16×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(高速外设)总线上的外设时钟使能寄存器;

15 14 13 12 11 10 9 8
ADC3 EN UART1 EN TIM8 EN SPI1 EN TIM1 EN ADC2 EN ADC1 EN IOPG
7 6 5 4 3 2 1 0
IOPF IOPE IOPD IOPC IOPB IOPA 保留 AFIO EN

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

亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。

日期姓名金额
2023-09-06*源19
2023-09-11*朝科88
2023-09-21*号5
2023-09-16*真60
2023-10-26*通9.9
2023-11-04*慎0.66
2023-11-24*恩0.01
2023-12-30I*B1
2024-01-28*兴20
2024-02-01QYing20
2024-02-11*督6
2024-02-18一*x1
2024-02-20c*l18.88
2024-01-01*I5
2024-04-08*程150
2024-04-18*超20
2024-04-26.*V30
2024-05-08D*W5
2024-05-29*辉20
2024-05-30*雄10
2024-06-08*:10
2024-06-23小狮子666
2024-06-28*s6.66
2024-06-29*炼1
2024-06-30*!1
2024-07-08*方20
2024-07-18A*16.66
2024-07-31*北12
2024-08-13*基1
2024-08-23n*s2
2024-09-02*源50
2024-09-04*J2
2024-09-06*强8.8
2024-09-09*波1
2024-09-10*口1
2024-09-10*波1
2024-09-12*波10
2024-09-18*明1.68
2024-09-26B*h10
2024-09-3010
2024-10-02M*i1
2024-10-14*朋10
2024-10-22*海10
2024-10-23*南10
2024-10-26*节6.66
2024-10-27*o5
2024-10-28W*F6.66
2024-10-29R*n6.66
2024-11-02*球6
2024-11-021*鑫6.66
2024-11-25*沙5
2024-11-29C*n2.88
posted @   大奥特曼打小怪兽  阅读(537)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
如果有任何技术小问题,欢迎大家交流沟通,共同进步

公告 & 打赏

>>

欢迎打赏支持我 ^_^

最新公告

程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)。

了解更多

点击右上角即可分享
微信分享提示