STM32系列单片机的标准外设库、HAL库和LL库的区别和介绍
输出
宏定义
// R-红色
#define LED1_GPIO_PORT GPIOB
#define LED1_GPIO_CLK RCC_APB2Periph_GPIOB
#define LED1_GPIO_PIN GPIO_Pin_5
// G-绿色
#define LED2_GPIO_PORT GPIOB
#define LED2_GPIO_CLK RCC_APB2Periph_GPIOB
#define LED2_GPIO_PIN GPIO_Pin_0
// B-蓝色
#define LED3_GPIO_PORT GPIOB
#define LED3_GPIO_CLK RCC_APB2Periph_GPIOB
#define LED3_GPIO_PIN GPIO_Pin_1
LED GPIO初始化
void LED_GPIO_Config(void)
{
/* 定义一个GPIO_InitTypeDef 类型的结构体*/
GPIO_InitTypeDef GPIO_InitStructure;
/* 开启LED 相关的GPIO 外设时钟*/
RCC_APB2PeriphClockCmd( LED1_GPIO_CLK|
LED2_GPIO_CLK|
LED3_GPIO_CLK, ENABLE);
/* 选择要控制的GPIO 引脚*/
GPIO_InitStructure.GPIO_Pin = LED1_GPIO_PIN;
/* 设置引脚模式为通用推挽输出*/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
/* 设置引脚速率为50MHz */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
/* 调用库函数,初始化GPIO*/
GPIO_Init(LED1_GPIO_PORT, &GPIO_InitStructure);
/* 选择要控制的GPIO 引脚*/
GPIO_InitStructure.GPIO_Pin = LED2_GPIO_PIN;
/* 调用库函数,初始化GPIO*/
GPIO_Init(LED2_GPIO_PORT, &GPIO_InitStructure);
/* 选择要控制的GPIO 引脚*/
GPIO_InitStructure.GPIO_Pin = LED3_GPIO_PIN;
/* 调用库函数,初始化GPIOF*/
GPIO_Init(LED3_GPIO_PORT, &GPIO_InitStructure);
/* 关闭所有led 灯*/
GPIO_SetBits(LED1_GPIO_PORT, LED1_GPIO_PIN);
/* 关闭所有led 灯*/
GPIO_SetBits(LED2_GPIO_PORT, LED2_GPIO_PIN);
/* 关闭所有led 灯*/
GPIO_SetBits(LED3_GPIO_PORT, LED3_GPIO_PIN);
}
(1) 使用GPIO_InitTypeDef 定义GPIO 初始化结构体变量,以便下面用于存储GPIO 配置。
STM32-GPIO-8种输入输出模式理解
常用的推挽,可以设置高低电平。
开漏输出就类似于三极管的集电极输出,相当于OC门,开漏输出只能输出低电平,如果要输出高电平必须通过上拉电阻才能实现。几个组合可实现线与操作,这几个都接上拉电阻,都不输出0时才能输出高电平。
(2) 调用库函数RCC_APB2PeriphClockCmd 来使能LED 灯的GPIO 端口时钟,该函数有两个输入参数,第一个参数用于指示要配置的时钟,如本例中的“RCC_APB2Periph_GPIOB”,应用时我们使用“|”操作同时配置3 个LED 灯的时钟;第二个参数设置状态,“Disable”关闭或“Enable”使能时钟。
(3) 向GPIO 初始化结构体(引脚,输出模式,输出频率)赋值,把引脚初始化成推挽输出模式,其中的GPIO_Pin 使用宏“LEDx_GPIO_PIN”来赋值,使函数的实现方便移植。
(4) 使用以上初始化结构体的配置,调用GPIO_Init ()函数(将上面初始化好的结构体,赋值给相应的引脚)向寄存器写入参数,完成GPIO 的初始化,这里的GPIO 端口使用“LEDx_GPIO_PORT”宏来赋值,也是为了程序移植方便。
(5) 使用同样的初始化结构体,只修改控制的引脚和端口,初始化其它LED 灯使用的GPIO 引脚。
断言
这里的“assert_param”实际是一个宏,在库函数中它用于检查输入参数是否符合要求,若不符合要求则执行某个函数输出警告。
Doxygen 注释规范
/**
* @brief 初始化控制LED 的IO
* @param 无
* @retval 无
*/
如果在工程文件中按照这种规范去注释, 可以使用Doxygen 软件自动根据注释生成帮助文档。
防止头文件重复包含
#ifndef __LED_H
#define __LED_H
/* 此处省略头文件的具体内容*/
#endif /* end of __LED_H */
输入-按键GPIO 初始化函数
void Key_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* 开启按键端口的时钟*/
RCC_APB2PeriphClockCmd(KEY1_GPIO_CLK|KEY2_GPIO_CLK,ENABLE);
//选择按键的引脚
GPIO_InitStructure.GPIO_Pin = KEY1_GPIO_PIN;
// 设置按键的引脚为浮空输入
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
//使用结构体初始化按键
GPIO_Init(KEY1_GPIO_PORT, &GPIO_InitStructure);
//选择按键的引脚
GPIO_InitStructure.GPIO_Pin = KEY2_GPIO_PIN;
//设置按键的引脚为浮空输入
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
//使用结构体初始化按键
GPIO_Init(KEY2_GPIO_PORT, &GPIO_InitStructure);
}
调用库函数RCC_APB2PeriphClockCmd 来使能按键的GPIO 端口时钟,调用时我们使用“|”
操作同时配置两个按键的时钟。
#define KEY_ON 1
#define KEY_OFF 0
uint8_t Key_Scan(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin)
{
/* 检测是否有按键按下*/
if (GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == KEY_ON )
{ /* 等待按键释放*/
while (GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == KEY_ON);
return KEY_ON;
} else
return KEY_OFF;
}
库函数GPIO_ReadInputDataBit 来获取位状态
Key_Scan 函数中以GPIO_ReadInputDataBit 的返回值与自定义的宏“KEY_ON”对比,若检测到按键按下,则使用while 循环持续检测按键状态,直到按键释放,按键释放后Key_Scan 函数返回一个“KEY_ON”值;若没有检测到按键按下,则函数直接返回“KEY_OFF”。
/**
* @brief 主函数
* @param 无
* @retval 无
*/
int main(void)
{
/* LED 端口初始化*/
LED_GPIO_Config();
/* 初始化按键*/
Key_GPIO_Config();
/* 轮询按键状态,若按键按下则反转LED */
while (1)
{
if ( Key_Scan(KEY1_GPIO_PORT,KEY1_PIN) == KEY_ON )
{
/*LED1 反转*/
LED1_TOGGLE;
}
if ( Key_Scan(KEY2_GPIO_PORT,KEY2_PIN) == KEY_ON )
{
/*LED2 反转*/
LED2_TOGGLE;
}
}
}
GPIO-位操作
STM32 的全部寄存器都可以通过访问位带别名区的方式来达到访问原始寄存器比特位的效果,这比51 单片机强大很多
外设位带区:虽然说全部寄存器都可以实现比特操作,但我们在实际项目中并不会这么做,甚至不会这么做。有时候为了特定的项目需要,比如需要频繁的操作很多IO 口,这个时候我们可以考虑把IO 相关的寄存器实现比特操作。
SRAM 位带区:操作SRAM 的比特位这个用得很少。
// 单独操作GPIO 的某一个IO 口,n(0,1,2...16),n 表示具体是哪一个IO 口
#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) //输出
#define PAin(n) BIT_ADDR(GPIOA_IDR_Addr,n) //输入