1、硬件设计
这是一个 RGB 灯,里面由红蓝绿三个小灯构成,使用 PWM 控制时可以混合成 256 不同的颜色。
这些 LED 灯的阴极都是连接到 STM32 的 GPIO 引脚,只要我们控制 GPIO 引脚的电平输出状态, 即可控制 LED 灯的亮灭。若您使用的实验板 LED 灯的连接方式或引脚不一样,只需根据我们的 工程修改引脚即可,程序的控制原理相同。
IO端口位的基本结构
2、软件设计
为了使工程更加有条理,我们把 LED 灯控制相关的代码独立分开存储,方便以后移植。在“工 程模板”之上新建“bsp_led.c”及“bsp_led.h”文件,其中的“bsp”即 Board Support Packet 的缩写 (板级支持包),这些文件也可根据您的喜好命名,这些文件不属于STM32 标准库的内容,是由我们自己根据应用需要编写的。
1.编程要点
(1). 使能 GPIO 端口时钟;
(2). 初始化 GPIO 目标引脚为推挽输出模式;
(3). 编写简单测试程序,控制 GPIO 引脚输出高、低电平。
2.代码分析
(1)LED 灯引脚宏定义
在编写应用程序的过程中,要考虑更改硬件环境的情况,例如 LED 灯的控制引脚与当前的不一 样,我们希望程序只需要做最小的修改即可在新的环境正常运行。这个时候一般把硬件相关的部分使用宏来封装,若更改了硬件环境,只修改这些硬件相关的宏即可,这些定义一般存储在头文 件。
/* 定义LED连接的GPIO端口,用户只需要修改下面LED引脚 */ // R_红色 #define LED1_GPIO_PORT GPIOB /* GPIO端口 */ #define LED1_GPIO_CLK RCC_APB2Periph_GPIOB /* GPIO端口时钟 */ #define LED1_GPIO_PIN GPIO_Pin_5 /* 连接到SCL时钟线的GPIO */ // G-绿色 #define LED2_GPIO_PORT GPIOB /* GPIO端口 */ #define LED2_GPIO_CLK RCC_APB2Periph_GPIOB /* GPIO端口时钟*/ #define LED2_GPIO_PIN GPIO_Pin_0 /* 连接到SCL时钟线的GPIO */ // B-蓝色 #define LED3_GPIO_PORT GPIOB /* GPIO端口 */ #define LED3_GPIO_CLK RCC_APB2Periph_GPIOB /* GPIO端口时钟 */ #define LED3_GPIO_PIN GPIO_Pin_1 /*连接到SCL时钟线的GPIO */
以上代码分别把控制 LED 灯的 GPIO 端口、GPIO 引脚号以及 GPIO 端口时钟封装起来了。在实际控制的时候我们就直接用这些宏,以达到应用代码硬件无关的效果。
(2)控制 LED 灯亮灭状态的宏定义
为了方便控制 LED 灯,我们把 LED 灯常用的亮、灭及状态反转的控制也直接定义成宏。
/*直接操作寄存器的方法控制 IO*/ #define digitalHi(p,i) {p->BSRR=i;} //输出为高电平 #define digitalLo(p,i) {p->BRR=i;} //输出低电平 #define digitalToggle(p,i) {p->ODR ^=i;} //输出反转状态 /* 定义控制 IO 的宏 */ #define LED1_TOGGLE digitalToggle(LED1_GPIO_PORT,LED1_GPIO_PIN) #define LED1_OFF digitalHi(LED1_GPIO_PORT,LED1_GPIO_PIN) #define LED1_ON digitalLo(LED1_GPIO_PORT,LED1_GPIO_PIN) #define LED2_TOGGLE digitalToggle(LED2_GPIO_PORT,LED2_GPIO_PIN) #define LED2_OFF digitalHi(LED2_GPIO_PORT,LED2_GPIO_PIN) #define LED2_ON digitalLo(LED2_GPIO_PORT,LED2_GPIO_PIN) #define LED3_TOGGLE digitalToggle(LED3_GPIO_PORT,LED3_GPIO_PIN) #define LED3_OFF digitalHi(LED3_GPIO_PORT,LED3_GPIO_PIN) #define LED3_ON digitalLo(LED3_GPIO_PORT,LED3_GPIO_PIN) /* 基本混色,后面高级用法使用 PWM 可混出全彩颜色, 且效果更好 */ //红 #define LED_RED \ LED1_ON;\ LED2_OFF\ LED3_OFF //绿 #define LED_GREEN \ LED1_OFF;\ LED2_ON\ LED3_OFF //蓝 #define LED_BLUE \ LED1_OFF;\ LED2_OFF\ LED3_ON //黄 (红 + 绿) #define LED_YELLOW \ LED1_ON;\ LED2_ON\ LED3_OFF //紫 (红 + 蓝) #define LED_PURPLE \ LED1_ON;\ LED2_OFF\ LED3_ON //青 (绿 + 蓝) #define LED_CYAN \ LED1_OFF;\ LED2_ON\ LED3_ON //白 (红 + 绿 + 蓝) #define LED_WHITE \ LED1_ON;\ LED2_ON\ LED3_ON //黑 (全部关闭) #define LED_RGBOFF \ LED1_OFF;\ LED2_OFF\ LED3_OFF
这部分宏控制 LED 亮灭的操作是直接向 BSRR、BRR 和 ODR 这三个寄存器写入控制指令来实现的,对 BSRR 写 1 输出高电平,对 BRR 写 1 输出低电平,对 ODR 寄存器某位进行异或操作可反转位的状态。
RGB 彩灯可以实现混色,如最后一段代码我们控制红灯和绿灯亮而蓝灯灭,可混出黄色效果。
代码中的“\”是 C 语言中的续行符语法,表示续行符的下一行与续行符所在的代码是同一行。代码中因为宏定义关键字“#define”只是对当前行有效,所以我们使用续行符来连接起来,以下的 代码是等效的:
#define LED_YELLOW LED1_ON; LED2_ON; LED3_OFF
(3)LED GPIO 初始化函数
利用上面的宏,编写 LED 灯的初始化函数
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 配置
(2) 调用库函数 RCC_APB2PeriphClockCmd 来使能 LED 灯的 GPIO 端口时钟,在前面的章节中 我们是直接向 RCC 寄存器赋值来使能时钟的,不如这样直观。该函数有两个输入参数,第 一个参数用于指示要配置的时钟,如本例中的“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 引脚。
(6) 使用宏控制 RGB 灯默认关闭。
(4)主函数
编写完 LED 灯的控制函数后,就可以在 main 函数中测试了,在 main 函数中,调用我们前面定义的 LED_GPIO_Config 初始化好 LED 的控制引脚,然后直接调 用各种控制 LED 灯亮灭的宏来实现 LED 灯的控制。
#include "stm32f10x.h" #include "bsp_led.h" #define SOFT_DELAY Delay(0x0FFFFF); void Delay(__IO u32 nCount); /** * @brief 主函数 * @param 无 * @retval 无 */ int main(void) { /* LED 端口初始化 */ LED_GPIO_Config(); while (1) { LED1_ON; // 亮 SOFT_DELAY; LED1_OFF; // 灭 LED2_ON; // 亮 SOFT_DELAY; LED2_OFF; // 灭 LED3_ON; // 亮 SOFT_DELAY; LED3_OFF; // 灭 /*轮流显示 红绿蓝黄紫青白 颜色*/ LED_RED; SOFT_DELAY; LED_GREEN; SOFT_DELAY; LED_BLUE; SOFT_DELAY; LED_YELLOW; SOFT_DELAY; LED_PURPLE; SOFT_DELAY; LED_CYAN; SOFT_DELAY; LED_WHITE; SOFT_DELAY; LED_RGBOFF; SOFT_DELAY; } } void Delay(__IO uint32_t nCount) //简单的延时函数 { for(; nCount != 0; nCount--); }
二、GPIO 输入—按键检测
1、硬件设计
按键机械触点断开、闭合时,由于触点的弹性作用,按键开关不会马上稳定接通或一下子断开,使用按键时会产生图按键抖动说明图中的带波纹信号,需要用软件消抖处理滤波,不方便输入检测。本实验板连接的按键带硬件消抖功能,它利用电容充放电的延时,消除了波纹,从而简化软件的处理,软件只需要直接检测引脚的电平即可。
从按键的原理图可知,这些按键在没有被按下的时候,GPIO 引脚的输入状态为低电平 (按键所在的电路不通,引脚接地),当按键按下时,GPIO 引脚的输入状态为高电平 (按键所在的电路导通, 引脚接到电源)。只要我们检测引脚的输入电平,即可判断按键是否被按下。
2、软件设计
同 LED 的工程,为了使工程更加有条理,我们把按键相关的代码独立分开存储,方便以后移植。 在“工程模板”之上新建“bsp_key.c”及“bsp_key.h”文件,这些文件也可根据您的喜好命名,这 些文件不属于 STM32 标准库的内容,是由我们自己根据应用需要编写的。
1.编程要点
(1). 使能 GPIO 端口时钟;
(2). 初始化 GPIO 目标引脚为输入模式 (浮空输入);
(3). 编写简单测试程序,检测按键的状态,实现按键控制 LED 灯。
2.代码分析
(1)按键引脚宏定义
同样,在编写按键驱动时,也要考虑更改硬件环境的情况。我们把按键检测引脚相关的宏定义到 “bsp_key.h”文件中
// 引脚定义 #define KEY1_GPIO_CLK RCC_APB2Periph_GPIOA #define KEY1_GPIO_PORT GPIOA #define KEY1_GPIO_PIN GPIO_Pin_0 #define KEY2_GPIO_CLK RCC_APB2Periph_GPIOC #define KEY2_GPIO_PORT GPIOC #define KEY2_GPIO_PIN GPIO_Pin_13 /** 按键按下标置宏 * 按键按下为高电平,设置 KEY_ON=1, KEY_OFF=0 * 若按键按下为低电平,把宏设置成KEY_ON=0 ,KEY_OFF=1 即可 */ #define KEY_ON 1 #define KEY_OFF 0
以上代码根据按键的硬件连接,把检测按键输入的 GPIO 端口、GPIO 引脚号以及 GPIO 端口时钟 封装起来了。
(2)按键 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); }
同为 GPIO 的初始化函数,初始化的流程与“LED GPIO 初始化函数”章节中的类似,主要区别 是引脚的模式。函数执行流程如下:
(1) 使用 GPIO_InitTypeDef 定义 GPIO 初始化结构体变量,以便下面用于存储 GPIO 配置。
(2) 调用库函数 RCC_APB2PeriphClockCmd 来使能按键的 GPIO 端口时钟,调用时我们使用“|” 操作同时配置两个按键的时钟。
(3) 向 GPIO 初始化结构体赋值,把引脚初始化成浮空输入模式,其中的 GPIO_Pin 使用宏 “KEYx_GPIO_PIN”来赋值,使函数的实现方便移植。由于引脚的默认电平受按键电路影 响,所以设置成浮空输入。
(4) 使用以上初始化结构体的配置,调用 GPIO_Init 函数向寄存器写入参数,完成 GPIO 的初始 化,这里的 GPIO 端口使用“KEYx_GPIO_PORT”宏来赋值,也是为了程序移植方便。
(5) 使用同样的初始化结构体,只修改控制的引脚和端口,初始化其它按键检测时使用的 GPIO 引脚。
(3)检测按键的状态
初始化按键后,就可以通过检测对应引脚的电平来判断按键状态了。
/* * 函数名:Key_Scan * 描述 :检测是否有按键按下 * 输入 :GPIOx:x 可以是 A,B,C,D或者 E * GPIO_Pin:待读取的端口位 * 输出 :KEY_OFF(没按下按键)、KEY_ON(按下按键) */ 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; }
(4)主函数
接下来我们使用主函数编写按键检测流程,代码中初始化 LED 灯及按键后,在 while 函数里不断调用 Key_Scan 函数,并判断其返回值,若返回值表示按键按下,则反转 LED 灯的状态。
#include "stm32f10x.h" #include "bsp_led.h" #include "bsp_key.h" /** * @brief 主函数 * @param 无 * @retval 无 */ int main(void) { /* LED端口初始化 */ LED_GPIO_Config(); LED1_ON; /* 按键端口初始化 */ Key_GPIO_Config(); /* 轮询按键状态,若按键按下则反转LED */ while(1) { if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ) { /*LED1反转*/ LED1_TOGGLE; } if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON ) { /*LED2反转*/ LED2_TOGGLE; } } }