1、GPIO工作模式
1.1 端口输入数据寄存器(IDR)
1.2 端口输出数据寄存器(ODR)
1.3 端口置位/复位寄存器(BSRR)
为什么有了 ODR 寄存器,还要这个 BSRR 寄存器呢?我们先看看 BSRR 的寄存器描述,首先 BSRR 是只写权限,而 ODR 是可读可写权限。 BSRR 寄存器 32 位有效,对于低 16 位(0- 15),我们往相应的位写 1(BSy=1),那么对应的 IO 口会输出高电平,往相应的位写 0(BSy=0),对 IO 口没有任何影响,高 16 位(16-31)作用刚好相反,对相应的位写 1(BRy=1)会输出低电平,写 0(BRy=0)没有任何影响, y=0~15。
也就是说,对于 BSRR 寄存器,你写 0 的话,对 IO 口电平是没有任何影响的。我们要设置某个 IO 口电平,只需要相关位设置为 1 即可。而 ODR 寄存器,我们要设置某个 IO 口电平,我们首先需要读出来 ODR 寄存器的值,然后对整个 ODR 寄存器重新赋值来达到设置某个或者某些 IO 口的目的,而 BSRR 寄存器,我们就不需要先读,而是直接设置即可,这在多任务实时操作系统中作用很大。 BSRR 寄存器还有一个好处,就是 BSRR 寄存器改变引脚状态的时候,不会被中断打断,而 ODR 寄存器有被中断打断的风险。
2、硬件设计
要使用一个外设,首先要进行初始化,hal库初始化函数为:
void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init);
形参1为端口号,形参2为结构体变量,结构体如下:
typedef struct
{
uint32_t Pin; /* 引脚号 */
uint32_t Mode; /* 模式设置 */
uint32_t Pull; /* 上拉下拉设置 */
uint32_t Speed; /* 速度设置 */
uint32_t Alternate; /* 复用功能 */
} GPIO_InitTypeDef;
GPIO写函数:
void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx,
uint16_t GPIO_Pin,
GPIO_PinState PinState);
形参1为端口号,形参2为引脚号,形参3为输出状态。
GPIO电平翻转函数:
void HAL_GPIO_TogglePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);
形参1为端口号,形参2为引脚号。
要进行输出配置,首先使能对应的GPIO时钟再进行初始化配置。再通过写函数控制GPIO输出高低电平。
3、软件设计
流程图
led.h
#ifndef __LED_H
#define __LED_H
#include "./SYSTEM/sys/sys.h"
/******************************************************************************************/
/* 引脚 定义 */
#define LED0_GPIO_PORT GPIOF
#define LED0_GPIO_PIN GPIO_PIN_9
#define LED0_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOF_CLK_ENABLE(); }while(0) /* PF口时钟使能 */
#define LED1_GPIO_PORT GPIOF
#define LED1_GPIO_PIN GPIO_PIN_10
#define LED1_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOF_CLK_ENABLE(); }while(0) /* PF口时钟使能 */
/******************************************************************************************/
/* LED端口定义 */
#define LED0(x) do{ x ? \
HAL_GPIO_WritePin(LED0_GPIO_PORT, LED0_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(LED0_GPIO_PORT, LED0_GPIO_PIN, GPIO_PIN_RESET); \
}while(0) /* LED0 = RED */
#define LED1(x) do{ x ? \
HAL_GPIO_WritePin(LED1_GPIO_PORT, LED1_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(LED1_GPIO_PORT, LED1_GPIO_PIN, GPIO_PIN_RESET); \
}while(0) /* LED1 = GREEN */
/* LED取反定义 */
#define LED0_TOGGLE() do{ HAL_GPIO_TogglePin(LED0_GPIO_PORT, LED0_GPIO_PIN); }while(0) /* LED0 = !LED0 */
#define LED1_TOGGLE() do{ HAL_GPIO_TogglePin(LED1_GPIO_PORT, LED1_GPIO_PIN); }while(0) /* LED1 = !LED1 */
/******************************************************************************************/
/* 外部接口函数*/
void led_init(void); /* 初始化 */
#endif
led.c
#include "./BSP/LED/led.h"
/**
* @brief 初始化LED相关IO口, 并使能时钟
* @param 无
* @retval 无
*/
void led_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
LED0_GPIO_CLK_ENABLE(); /* LED0时钟使能 */
LED1_GPIO_CLK_ENABLE(); /* LED1时钟使能 */
gpio_init_struct.Pin = LED0_GPIO_PIN; /* LED0引脚 */
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(LED0_GPIO_PORT, &gpio_init_struct); /* 初始化LED0引脚 */
gpio_init_struct.Pin = LED1_GPIO_PIN; /* LED1引脚 */
HAL_GPIO_Init(LED1_GPIO_PORT, &gpio_init_struct); /* 初始化LED1引脚 */
LED0(1); /* 关闭 LED0 */
LED1(1); /* 关闭 LED1 */
}
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
delay_init(168); /* 延时初始化 */
led_init(); /* 初始化LED */
while(1)
{
LED0(0); /* LED0 亮 */
LED1(1); /* LED1 灭 */
delay_ms(500);
LED0(1); /* LED0 灭 */
LED1(0); /* LED1 亮 */
delay_ms(500);
}
}
本实验源码来自正点原子探索者开发板HAL库跑马灯例程。重点不是点灯,点灯很简单,主要是理解STM32如何控制GPIO进行输出高低电平。