STM32 GPIO初始化配置学习笔记

简介:

        由于刚开始没有学懂GPIO的配置原理,导致后面学习其它外设的时候总是产生阻碍,因为其它外设要使用前,大部分都要配置GPIO的初始化,因此这几天重新学习了一遍GPIO的配置,记录如下。

        首先我们要知道芯片上的引脚,并不是只有GPIO的功能,还能复用成其他功能,比如PA9和PA10引脚,既可以配置成普通的IO口输出高低电平,也可以配置成UART1的TX和RX引脚。今天就以这这两个例子记录一下配置过程。

实验平台芯片是stmf103,野火的指南者开发板。使用hal库开发。

例1:PB5\PB0\PB1配置成普通IO口,控制LED灯的亮灭,

例2:将PA9配置成UART1的TX,将PA10配置成UART1的RX。

一、GPIO的功能框图

此图来自野火的《零死角玩转stm32》,此图在《stm32f10x参考手册》的GPIO章节也有,野火的标注得有需要,方便说明。

1.输出模式:即由单片机控制引脚的高低电平。

输出模式有:推挽输出、开漏输出、复用推挽输出、复用开漏输出。常用推挽输出,复用推挽输出。

①处就是芯片外部引出的引脚,左边的是芯片内部的,从外面看不见。一个引脚在同一时刻,要么是输出,要么是输入,不能同时存在。

②处两个反向串联的MOS管,就实现了推挽输出,这种模式就是普通的IO口输出高低电平,通过操作③和位置位/清除寄存器(BSRR)来控制引脚输出高低电平,从而控制外部的灯的亮灭。

④处是接片上外设,将引脚配置成其他功能,如我们将PA9配置成UART1的TX功能,这时候就不需要去配置③处的ODR和BSRR寄存器。 

2.输入模式:即由外部控制引脚的高低电平

        输入模式有:浮空输入、模拟输入、上拉输入、下拉输入,如果是输入模式的话,就是由外部控制引脚的高低电平变化,单片机去读引脚的电平变化,从而知道外部干了什么。

        比如外部接了按键,按键按下产生低电平,按键释放产生高电平,这时候单片机通过引脚高低变化就知道了按键按下了,进而产生相应的处理,此功能往往和中断一起。在此不做过多的讲解,后面学了中断之后再记录。

3.GPIO寄存器

在《stm32f10x参考手册》GPIO寄存器章节有各个寄存器的功能和作用讲解,由于我们使用hal库开发,操作寄存器的工作由hal做了,我们只需要了解有哪些参数可以修改及其作用。如果有兴趣可以看看源码是怎么操作寄存器的。

1.GPIO寄存器结构体定义

GPIO_TypeDef中就是控制GPIO的7个32位寄存器,具体每个bit位的作用记得看参考手册。

这个结构体不需要我们传入参数。我们需要修改的是GPIO_InitTypeDef结构体。

typedef struct
{
  __IO uint32_t CRL;
  __IO uint32_t CRH;
  __IO uint32_t IDR;
  __IO uint32_t ODR;
  __IO uint32_t BSRR;
  __IO uint32_t BRR;
  __IO uint32_t LCKR;
} GPIO_TypeDef;

GPIO_InitTypeDef结构体定义如下,可以由我们自己配置gpio引脚,速度,模式,这三个参数都是去修改GPIO_TypeDef里面的成员的某个位的。


typedef struct
{
  uint16_t GPIO_Pin;             /*!< Specifies the GPIO pins to be configured.
                                      This parameter can be any value of @ref GPIO_pins_define */

  GPIOSpeed_TypeDef GPIO_Speed;  /*!< Specifies the speed for the selected pins.
                                      This parameter can be a value of @ref GPIOSpeed_TypeDef */

  GPIOMode_TypeDef GPIO_Mode;    /*!< Specifies the operating mode for the selected pins.
                                      This parameter can be a value of @ref GPIOMode_TypeDef */
}GPIO_InitTypeDef;

2.驱动GPIO_TypeDef中寄存器的库函数函数GPIO_Init

        我们只需要传入端口和初始化结构体即可。请看下面二的例程。

/**
  * @brief  Initializes the GPIOx peripheral according to the specified
  *         parameters in the GPIO_InitStruct.
  * @param  GPIOx: where x can be (A..G) to select the GPIO peripheral.
  * @param  GPIO_InitStruct: pointer to a GPIO_InitTypeDef structure that
  *         contains the configuration information for the specified GPIO peripheral.
  * @retval None
  */
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
{
  uint32_t currentmode = 0x00, currentpin = 0x00, pinpos = 0x00, pos = 0x00;
  uint32_t tmpreg = 0x00, pinmask = 0x00;
  /* Check the parameters */
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
  assert_param(IS_GPIO_MODE(GPIO_InitStruct->GPIO_Mode));
  assert_param(IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin));  
  
/*---------------------------- GPIO Mode Configuration -----------------------*/
  currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F);
  if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00)
  { 
    /* Check the parameters */
    assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed));
    /* Output mode */
    currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed;
  }
/*---------------------------- GPIO CRL Configuration ------------------------*/
  /* Configure the eight low port pins */
  if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00)
  {
    tmpreg = GPIOx->CRL;
    for (pinpos = 0x00; pinpos < 0x08; pinpos++)
    {
      pos = ((uint32_t)0x01) << pinpos;
      /* Get the port pins position */
      currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;
      if (currentpin == pos)
      {
        pos = pinpos << 2;
        /* Clear the corresponding low control register bits */
        pinmask = ((uint32_t)0x0F) << pos;
        tmpreg &= ~pinmask;
        /* Write the mode configuration in the corresponding bits */
        tmpreg |= (currentmode << pos);
        /* Reset the corresponding ODR bit */
        if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
        {
          GPIOx->BRR = (((uint32_t)0x01) << pinpos);
        }
        else
        {
          /* Set the corresponding ODR bit */
          if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
          {
            GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
          }
        }
      }
    }
    GPIOx->CRL = tmpreg;
  }
/*---------------------------- GPIO CRH Configuration ------------------------*/
  /* Configure the eight high port pins */
  if (GPIO_InitStruct->GPIO_Pin > 0x00FF)
  {
    tmpreg = GPIOx->CRH;
    for (pinpos = 0x00; pinpos < 0x08; pinpos++)
    {
      pos = (((uint32_t)0x01) << (pinpos + 0x08));
      /* Get the port pins position */
      currentpin = ((GPIO_InitStruct->GPIO_Pin) & pos);
      if (currentpin == pos)
      {
        pos = pinpos << 2;
        /* Clear the corresponding high control register bits */
        pinmask = ((uint32_t)0x0F) << pos;
        tmpreg &= ~pinmask;
        /* Write the mode configuration in the corresponding bits */
        tmpreg |= (currentmode << pos);
        /* Reset the corresponding ODR bit */
        if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
        {
          GPIOx->BRR = (((uint32_t)0x01) << (pinpos + 0x08));
        }
        /* Set the corresponding ODR bit */
        if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
        {
          GPIOx->BSRR = (((uint32_t)0x01) << (pinpos + 0x08));
        }
      }
    }
    GPIOx->CRH = tmpreg;
  }
}

二、PB5\PB0\PB1点亮LED灯例程

        指南者的RGB灯接在stm32f103上的PB5、PB0、PB1三个引脚上,故我们控制对应引脚输出低电平,即可点亮LED灯。

PB5点亮LED1的代码如下。编译后,野火的指南者扳子上,RGB灯以0.5s闪烁红灯。其余两个LED灯的控制,把PB5换成PB0或者PB1即可。

#include "stm32f10x.h"

void delay_nms(u16 time);
void LED_GPIO_Config(void);


//1.主函数
int main(void)
{	
	/* LED 端口初始化 */
	LED_GPIO_Config();

	while(1)
	{
		//GPIO_Pin_5置低电平,则LED1亮
		GPIO_ResetBits(GPIOB, GPIO_Pin_5);
		
		//延时让led1亮保持
		delay_nms(500);

		//GPIO_Pin_5置高电平,则LED1灭
		GPIO_SetBits(GPIOB, GPIO_Pin_5);
		//延时让led1亮保持
		delay_nms(500);
	}
}

//2.毫秒延时,72M,需要精确延时的话还是得定时器
void delay_nms(u16 time)
{    
   u16 i=0;  
   while(time--)
   {
      i=12000;  //自己定义
      while(i--) ;    
   }
}

//3.LED1初始化函数
void LED_GPIO_Config(void)
{		
		/*定义一个GPIO_InitTypeDef类型的结构体*/
    GPIO_InitTypeDef GPIO_InitStructure;

    /*开启GPIOB外设时钟*/
    RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE);

    /*选择要控制的GPIO引脚*/
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;	

    /*设置引脚模式为通用推挽输出*/
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;   

    /*设置引脚速率为50MHz */   
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 

    /*调用库函数,初始化GPIO*/
    GPIO_Init(GPIOB, &GPIO_InitStructure);	
    
    /* 给GPIO_Pin_5置高电平,即LED1关闭	*/
    GPIO_SetBits(GPIOB, GPIO_Pin_5);
	delay_nms(20);

}	 

三、PA9\PA10做UART1的TX和RX例程

        控制LED灯的亮灭只需要控制引脚输出高低电平即可,但是串口是通信接口,所以需要先说一下串口的数据帧格式,至于串口发送的时序图,咱们写软件的暂时可以不了解。

从上面的帧格式可以知道可变的是数据位、校验位、停止位,起始位固定由库函数实现。

在stm32f103单片机上,UART1是由PA9和PA10复用过来的,所以我们需要对GPIOA的pin9是TX要配置成复用推挽输出模式,pin10引脚是RX要配置成浮空输入模式。

配置流程:

①GPIO复用功能配置

②UART串口配置

③main函数里面调用串口收发函数,和PC上的串口助手互相发送消息

1.GPIO初始化配置

{

	GPIO_InitTypeDef GPIO_InitStructure;

	// 打开串口GPIOA的时钟
	DEBUG_USART_GPIO_APBxClkCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	// 将PA9的GPIO配置为推挽复用模式
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);

  // 将USART Rx的GPIO配置为浮空输入模式
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
}

2.串口1初始化

        由于串口接受是由外部控制引脚电平,所以需要开启串口接受中断。

{

	USART_InitTypeDef USART_InitStructure;

	// 打开串口外设的时钟
	DEBUG_USART_APBxClkCmd(RCC_APB2Periph_USART1, ENABLE);
	// 配置串口的工作参数
	// 配置波特率
	USART_InitStructure.USART_BaudRate = 115200;
	// 配置 针数据字长
	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);
	
	//串口中断优先级配置
	NVIC_Configuration();
	
	// 使能串口接收中断
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);	
	
	// 使能串口
	USART_Cmd(USART1, ENABLE);	    
}

3.串口发送和发送函数

串口是按照字节发送的,每次发送一个字节,所以要发送多个字节的时候,需要用用循环发送。

/* 发送一个字节 */
void Usart_SendByte(USART_TypeDef* pUSARTx, uint8_t data)
{
	USART_SendData(pUSARTx, data);
	while( USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET );
}

4.完成代码

        实现下载到开发板之后,自动向串口1发送字符A,接受到PC发送的字符后,立马发给PC.

代码全部写在main.c里面即可。

#include "stm32f10x.h"
// #include "bsp_led.h"
// #include "bsp_usart.h"
// #include "stdlib.h"

void USART_Config(void);
void Usart_SendByte(USART_TypeDef* pUSARTx, uint8_t data);
static void NVIC_Configuration(void);

//1.主函数
int main(void)
{	
 
	USART_Config();
	Usart_SendByte(USART1,'A');

  while(1)
  {

  }
	return 0;
	
}
//2.串口初始化函数
void USART_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;

	// 打开串口GPIO的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	// 打开串口外设的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);

	// 将USART Tx的GPIO配置为推挽复用模式
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);

  // 将USART Rx的GPIO配置为浮空输入模式
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	// 配置串口的工作参数
	// 配置波特率
	USART_InitStructure.USART_BaudRate = 115200;
	// 配置 针数据字长
	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);
	
	//串口中断优先级配置
	NVIC_Configuration();
	
	// 使能串口接收中断
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);	
	
	// 使能串口
	USART_Cmd(USART1, ENABLE);	    
}

//3.串口接受中断函数
static void NVIC_Configuration(void)
{
  NVIC_InitTypeDef NVIC_InitStructure;
  
  /* 嵌套向量中断控制器组选择 */
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
  
  /* 配置USART为中断源 */
  NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
  /* 抢断优先级*/
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
  /* 子优先级 */
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
  /* 使能中断 */
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  /* 初始化配置NVIC */
  NVIC_Init(&NVIC_InitStructure);
}

//4.串口发送函数
void Usart_SendByte(USART_TypeDef* pUSARTx, uint8_t data)
{
	USART_SendData(pUSARTx, data);
	while( USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET );
}

//5.串口接受中断服务函数,USART1_IRQHandler在startup_stm32f10x_hs.s中定义,发生串口中断,自动执行这个函数。
void USART1_IRQHandler(void)
{
    uint8_t ucTemp;
	if(USART_GetITStatus(USART1,USART_IT_RXNE)!=RESET)
	{		
  
      ucTemp = USART_ReceiveData(USART1);
      Usart_SendByte(USART1,ucTemp);
	}

}

作者:Linux嵌入式木子

物联沃分享整理
物联沃-IOTWORD物联网 » STM32 GPIO初始化配置学习笔记

发表评论