stm32入门100步学习笔记:第25~30步:LED按键控制和FLASH读写程序
程序说明:
根据洋桃电子stm32教程,将LED呼吸灯、按键控制LED二进制显示和FLASH读写三种功能结合到一个程序当中。
功能说明:
1.按下Key1时单片机在LED呼吸灯与LED二进制显示两个模式之间切换
2.按下Key2时根据按下次数LED相应地显示0~3的二进制数
3.通过读写Flash将程序信息保留,掉电不丢失
工程创建:
使用洋桃电子STM32F103通用工程模板V1.1进行创建,其中hardware文件夹中已经包含key和led相关文件,但是需要在Components窗口中将key.c和led.c文件分别添加至Hardware组中,同时在Options窗口的C/C++页面的Include Paths中将两个文件夹的路径包含后文件才正常被添加到了工程中,否则会出现编译时调用相关头文件失败的情况。
另外,模板的key和led文件中有一些细节与实际教程中有出入,如果不修改可能导致程序出错,如key.h中IO接口组(KEYPORT)应定义为GPIOA而非GPIOB,若不修改则会导致与led的IO接口组冲突,并使得LED灯亮度明显降低,此外IO接口的定义中KEY1应为GPIO_Pin_0,且未定义KEY2,还有许多细节需要在实际使用的时候逐一排查并修改,直接盲目套用模板往往会导致出错。
模板中并未包含flash相关文件,要使用flash必须先将其他例程中的flash文件夹复制粘贴至模板的Basic文件夹内,再按照与前面类似的流程将flash文件添加至工程中。
代码分析:
按键和LED初始化:
按键和LED初始化函数几乎一致,本质上就是借助结构体对GPIO端口进行设置和初始化,结构体里面有 3 个参数,第 1 个是 GPIO 端口的端口号,第 2 个是 GPIO 端口的速度(以频率进行设置),第 3 个是 GPIO 端口的模式。而结构体本身则是在stm32f10x_gpio.h 文件中就声明好的,只需对其中的三个元素进行相应的设置即可。
GPIO 端口功能在单片机内部连接在 APB2 高速总线上,若要开启端口,先要在 APB2 高速总线 上启动它。因此在对结构体的内容进行设置前要先使用RCC_APB2PeriphClockCmd()开启对应的端口。随后分别设置端口号、IO端口工作方式和IO端口速度。
需要注意的是,LED和按键在结构体内容的设置上有所不同。首先LED使用GPIOB端口,按键使用GPIOA端口,因此在端口开启时只需开启给自所需端口即可,将三个端口全部开启通常也可以,但可能导致硬件耗电增加。点亮 LED 必须使用大电流驱动,所以IO端口工作方式必须选择推挽输出,代号GPIO_Mode_Out_PP;而按键在被按下后与 GND 短接,即当按键没有按下时需要让 I/O 端口保持在高电平,因此端口工作方式使用上拉电阻输入模式,代号GPIO_Mode_IPU,不同端口模式的代号均可在STM32F103固件函数库用户手册中查到。端口速度分为2MHz、10MHZ、50MHZ三档,通常采用50MHz较多,LED初始化的端口速度也设置为50MHz;按键初始化因为设置了 I/O 端口为上拉电阻输入模式,所以不需要设置 I/O 端口速度。
led.c:
#include "led.h"
void LED_Init(void){ //LED灯的接口初始化
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitStructure.GPIO_Pin = LED1 | LED2; //选择端口号(0~15或all)
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //选择IO接口工作方式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置IO接口速度(2/10/50MHz)
GPIO_Init(LEDPORT, &GPIO_InitStructure);
}
key.c:
#include "key.h"
void KEY_Init(void){ //微动开关的接口初始化
GPIO_InitTypeDef GPIO_InitStructure; //定义GPIO的初始化枚举结构
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStructure.GPIO_Pin = KEY1|KEY2; //选择端口号(0~15或all)
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //选择IO接口工作方式 //上拉电阻
GPIO_Init(KEYPORT,&GPIO_InitStructure);
}
Flash读写:
分为写函数FLASH_W()和读函数FLASH_R()。
FLASH_W()中有两个参数,第一个要写入的32位地址add,第二个是要写入的16位数据dat。Flash 操作必须是在启动了内部或外部高速时钟的情况下才能使用,因为系统初始化时已经启动了外部高速时钟,所以不需要重复设置。Flash 有操作锁定的功能,因此需要先解锁才能操作,操作完成后应重新锁定。解锁后首先需要清楚标志位,具体的标志位的含义可在STM32F103固件函数库用户手册中查到。随后依据Flash每次写入数据前都必须先按页擦除原有的数据的特性,需要使用FLASH_ErasePage()进行数据擦除,再使用FLASH_ProgramHalfWord()从给定的地址参数开始写入数据。最后再次清除标志位并锁定Flash,即完成了Flash写入函数。
void FLASH_W(u32 add,u16 dat){ //参数1:32位FLASH地址。参数2:16位数据
FLASH_Unlock(); //解锁FLASH编程擦除控制器
FLASH_ClearFlag(FLASH_FLAG_BSY|FLASH_FLAG_EOP|FLASH_FLAG_PGERR|FLASH_FLAG_WRPRTERR);//清除标志位
FLASH_ErasePage(add); //擦除指定地址页
FLASH_ProgramHalfWord(add,dat); //从指定页的addr地址开始写
FLASH_ClearFlag(FLASH_FLAG_BSY|FLASH_FLAG_EOP|FLASH_FLAG_PGERR|FLASH_FLAG_WRPRTERR);//清除标志位
FLASH_Lock(); //锁定FLASH编程擦除控制器
}
Flash存储区域地址表:

FLASH_R()函数相对简单,只需直接读取地址并将其返回即可。函数内定义一个16位变量a,通过指针操作直接读取 Flash 某个地址中的内容并赋值给a,最后通过return a;将其送出即可。
u16 FLASH_R(u32 add){ //参数1:32位读出FLASH地址。返回值:16位数据
u16 a;
a = *(u16*)(add);//从指定页的addr地址开始读
return a;
main函数:
首先引用头文件并对要写入的Flash地址进行宏定义。进入主程序,定义所需的各个变量并进行常规的时钟设置和初始化,在进入while(1)循环之前,需要先读取Flash数据并将其给到LED端口,因为此程序中Flash存储的数据为LED二进制显示模式下的内容。MENU变量用于LED呼吸灯变亮以及变暗的切换,而MODE变量用于LED在呼吸灯和二进制显示之间的模式切换。
while(1)循环中,首先检测按键1是否按下,以实现显示模式切换功能。检测按键时需要延时消抖并在最后使用while语句判断等待按键松开防止反复触发。在MODE0即呼吸灯模式下,通过调节占空比的方式使得LED点亮时间逐渐增加、熄灭时间逐渐减小产生LED逐渐变量的效果,反过来则可以使LED逐渐变暗熄灭,MENU变量用于区分LED变亮和变暗两个阶段,两个阶段在while(1)中不断循环即实现了呼吸灯效果。MODE1即二进制显示模式下,只需在检测到按键2按下时使变量a在每一次按下时在0,1,2,3之间不断循环并使用GPIO_Write()将其写入到LED端口LEDPORT即可实现两位二进制显示效果,最后将a写入Flash即可,注意到由于a是要写入Flash的数据,因此必须是16位的。
实际调试过程中发现上电时LED1为呼吸灯效果,而LED2也点亮。通过在MODE0模式下LED呼吸灯的两个阶段开始前先单独熄灭LED2,解决了此现象。
当然此程序中尚有许多不足,例如显示模式MODE的信息没法写入Flash中进行保存,需待后续研究调试。
#include "stm32f10x.h" //STM32头文件
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "key.h"
#include "flash.h"
#define FLASH_START_ADDR 0x0801f000
int main (void){//主程序
u8 MODE,MENU;
u16 t,i,a;
RCC_Configuration(); //时钟设置
LED_Init();
KEY_Init();
MODE = 0;
MENU = 0;
t = 1;
a = FLASH_R(FLASH_START_ADDR);
GPIO_Write(LEDPORT, a);
while(1){
if(!GPIO_ReadInputDataBit(KEYPORT, KEY1)){
delay_ms(20);
if(!GPIO_ReadInputDataBit(KEYPORT, KEY1)){
MODE = MODE + 1;
MODE &= 1;
while(!GPIO_ReadInputDataBit(KEYPORT, KEY1));
}
}
if(MENU == 0&&MODE == 0){
GPIO_WriteBit(LEDPORT, LED2, (BitAction)(0));
for(i = 0;i < 10;i++){
GPIO_WriteBit(LEDPORT, LED1, (BitAction)(1));
delay_us(t);
GPIO_WriteBit(LEDPORT, LED1, (BitAction)(0));
delay_us(301-t);
}
t++;
if(t == 300){
MENU = 1;
}
}
if(MENU == 1&&MODE == 0){
GPIO_WriteBit(LEDPORT, LED2, (BitAction)(0));
for(i = 0;i < 10;i++){
GPIO_WriteBit(LEDPORT, LED1, (BitAction)(1));
delay_us(t);
GPIO_WriteBit(LEDPORT, LED1, (BitAction)(0));
delay_us(301-t);
}
t--;
if(t == 1){
MENU = 0;
}
}
if(MODE == 1){
if(!GPIO_ReadInputDataBit(KEYPORT, KEY2)){
delay_ms(20);
if(!GPIO_ReadInputDataBit(KEYPORT, KEY2)){
a++;
if(a > 3)
a = 0;
GPIO_Write(LEDPORT, a);
while(!GPIO_ReadInputDataBit(KEYPORT, KEY2));
}
}
FLASH_W(FLASH_START_ADDR, a);
}
}
}
更多推荐


所有评论(0)