程序说明:

根据洋桃电子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);
	   }
	}
}

更多推荐