简介:
由于刚开始没有学懂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);}}