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