使用STM32标准库函数,
基于正点原子原码进行修改.
作业要求
1.短按按键(用手按下,就起来,手按下按键的时间在3秒以内,速度相对比较快),松手后红灯亮,一秒后熄灭。
2.长按按键,手按下按键超过3秒后,手没有抬起绿灯就亮,同时蜂鸣器响起,一秒后熄灭。
3. 检测按键输入采用扫描方式实现。
一 、逻辑结构:
主函数
{
初始化部分;
loop循环
{
标志位置零;计时变量置零;
while(使用io读取函数)
{
延迟和计时变量累加;当变量累加到一定值时触发事件,绿的亮一秒然后熄灭,
与此同时蜂鸣器响一秒然后停止.随后将标志位置一.
}
判断计时变量,如果在0到3000之间就触发事件:红灯亮一秒然后熄灭.
}
}
二、函数代码:
主函数代码如下 :
int main(void)
{
int pressedtime = 0;//用于记录按压时间
int tipsgreen = 0; //用于记录绿灯的亮灭与否
vu8 key=0;
delay_init(); //延时函数初始化
LED_Init(); //LED端口初始化
KEY_Init(); //初始化与按键连接的硬件接口
BEEP_Init(); //初始化蜂鸣器端口
while(1)
{
tipsgreen = 0;
pressedtime = 0;
while(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2) == 0)//当按键2按下不松开时将会一直在循环内
{
pressedtime++;//每次循环该变量加一
delay_ms(1);//每次循环约1毫秒
if(pressedtime >= 3000 && tipsgreen == 0)//当手指还没松开时就判断按住的时长,大于等于3秒且之前没触发过就触发以下事件
{
LED_GreenCtr(On);//绿灯打开
BEEP_Ctr(On);//蜂鸣器打开
delay_ms(1000);//延时1秒
LED_GreenCtr(Off);//绿灯关闭
BEEP_Ctr(Off);//蜂鸣器关闭
tipsgreen = 1;//标志位1,代表已经触发过一次
}
}
if(pressedtime < 3000 && pressedtime > 0)//判断按下时间小于3秒时
{
LED_RedCtr(On);//红灯开
delay_ms(1000);//延时1秒
LED_RedCtr(Off);//红灯关
}
delay_ms(10);//防止程序卡死
}
}
蜂鸣器涉及到的函数:
#ifndef __BEEP_H
#define __BEEP_H
#define On 1
#define Off 0
#include "sys.h"
//蜂鸣器端口定义
#define BEEP PBout(8) // BEEP,蜂鸣器接口
void BEEP_Init(void); //初始化
void BEEP_Ctr(unsigned int situation);//控制函数
#endif
#include "beep.h"
//初始化PB8为输出口.并使能这个口的时钟
//蜂鸣器初始化
void BEEP_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能GPIOB端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //BEEP-->PB.8 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度为50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure); //根据参数初始化GPIOB.8
GPIO_ResetBits(GPIOB,GPIO_Pin_8);//输出0,关闭蜂鸣器输出
}
//函数名;BEEP_Ctr
//参数:On或Off
//作用:开关蜂鸣器
void BEEP_Ctr(unsigned int situation)
{
if(situation == On)
GPIO_SetBits(GPIOB, GPIO_Pin_8);
if(situation == Off)
GPIO_ResetBits(GPIOB, GPIO_Pin_8);
}
LED灯涉及到的函数:
#ifndef __LED_H
#define __LED_H
#define On 1
#define Off 0
#include "sys.h"
#define LED0 PBout(5)// PB5
#define LED1 PEout(5)// PE5
void LED_Init(void);//初始化
void LED_RedCtr(unsigned int situation);
void LED_GreenCtr(unsigned int situation);
#endif
#include "led.h"
//LED IO初始化
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE, ENABLE); //使能PB,PE端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED0-->PB.5 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure); //根据设定参数初始化GPIOB.5
GPIO_SetBits(GPIOB,GPIO_Pin_5); //PB.5 输出高
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED1-->PE.5 端口配置, 推挽输出
GPIO_Init(GPIOE, &GPIO_InitStructure); //推挽输出 ,IO口速度为50MHz
GPIO_SetBits(GPIOE,GPIO_Pin_5); //PE.5 输出高
}
//函数名:LED_RedCtr
//参数:On 或者 Off
//分别对应开和关
//作用:控制红灯亮灭
void LED_RedCtr(unsigned int situation)
{
if(situation == On)
GPIO_ResetBits(GPIOB, GPIO_Pin_5);
if(situation == Off)
GPIO_SetBits(GPIOB,GPIO_Pin_5);
}
//函数名:LED_GreenCtr
//参数:On 或者 Off
//分别对应开和关
//控制绿灯亮灭
void LED_GreenCtr(unsigned int situation)
{
if(situation == On)
GPIO_ResetBits(GPIOE, GPIO_Pin_5);
if(situation == Off)
GPIO_SetBits(GPIOE,GPIO_Pin_5);
}
延时函数:
#ifndef __DELAY_H
#define __DELAY_H
#include "sys.h"
void delay_init(void);
void delay_ms(u16 nms);
void delay_us(u32 nus);
#endif
#include "delay.h"
static u8 fac_us=0; //us延时倍乘数
static u16 fac_ms=0; //ms延时倍乘数,在ucos下,代表每个节拍的ms数
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS定义了,说明要支持OS了(不限于UCOS).
//当delay_us/delay_ms需要支持OS的时候需要三个与OS相关的宏定义和函数来支持
//首先是3个宏定义:
// delay_osrunning:用于表示OS当前是否正在运行,以决定是否可以使用相关函数
//delay_ostickspersec:用于表示OS设定的时钟节拍,delay_init将根据这个参数来初始哈systick
// delay_osintnesting:用于表示OS中断嵌套级别,因为中断里面不可以调度,delay_ms使用该参数来决定如何运行
//然后是3个函数:
// delay_osschedlock:用于锁定OS任务调度,禁止调度
//delay_osschedunlock:用于解锁OS任务调度,重新开启调度
// delay_ostimedly:用于OS延时,可以引起任务调度.
//本例程仅作UCOSII和UCOSIII的支持,其他OS,请自行参考着移植
//支持UCOSII
#ifdef OS_CRITICAL_METHOD //OS_CRITICAL_METHOD定义了,说明要支持UCOSII
#define delay_osrunning OSRunning //OS是否运行标记,0,不运行;1,在运行
#define delay_ostickspersec OS_TICKS_PER_SEC //OS时钟节拍,即每秒调度次数
#define delay_osintnesting OSIntNesting //中断嵌套级别,即中断嵌套次数
#endif
//支持UCOSIII
#ifdef CPU_CFG_CRITICAL_METHOD //CPU_CFG_CRITICAL_METHOD定义了,说明要支持UCOSIII
#define delay_osrunning OSRunning //OS是否运行标记,0,不运行;1,在运行
#define delay_ostickspersec OSCfg_TickRate_Hz //OS时钟节拍,即每秒调度次数
#define delay_osintnesting OSIntNestingCtr //中断嵌套级别,即中断嵌套次数
#endif
//us级延时时,关闭任务调度(防止打断us级延迟)
void delay_osschedlock(void)
{
#ifdef CPU_CFG_CRITICAL_METHOD //使用UCOSIII
OS_ERR err;
OSSchedLock(&err); //UCOSIII的方式,禁止调度,防止打断us延时
#else //否则UCOSII
OSSchedLock(); //UCOSII的方式,禁止调度,防止打断us延时
#endif
}
//us级延时时,恢复任务调度
void delay_osschedunlock(void)
{
#ifdef CPU_CFG_CRITICAL_METHOD //使用UCOSIII
OS_ERR err;
OSSchedUnlock(&err); //UCOSIII的方式,恢复调度
#else //否则UCOSII
OSSchedUnlock(); //UCOSII的方式,恢复调度
#endif
}
//调用OS自带的延时函数延时
//ticks:延时的节拍数
void delay_ostimedly(u32 ticks)
{
#ifdef CPU_CFG_CRITICAL_METHOD
OS_ERR err;
OSTimeDly(ticks,OS_OPT_TIME_PERIODIC,&err); //UCOSIII延时采用周期模式
#else
OSTimeDly(ticks); //UCOSII延时
#endif
}
//systick中断服务函数,使用ucos时用到
void SysTick_Handler(void)
{
if(delay_osrunning==1) //OS开始跑了,才执行正常的调度处理
{
OSIntEnter(); //进入中断
OSTimeTick(); //调用ucos的时钟服务程序
OSIntExit(); //触发任务切换软中断
}
}
#endif
//初始化延迟函数
//当使用OS的时候,此函数会初始化OS的时钟节拍
//SYSTICK的时钟固定为HCLK时钟的1/8
//SYSCLK:系统时钟
void delay_init()
{
#if SYSTEM_SUPPORT_OS //如果需要支持OS.
u32 reload;
#endif
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //选择外部时钟 HCLK/8
fac_us=SystemCoreClock/8000000; //为系统时钟的1/8
#if SYSTEM_SUPPORT_OS //如果需要支持OS.
reload=SystemCoreClock/8000000; //每秒钟的计数次数 单位为K
reload*=1000000/delay_ostickspersec; //根据delay_ostickspersec设定溢出时间
//reload为24位寄存器,最大值:16777216,在72M下,约合1.86s左右
fac_ms=1000/delay_ostickspersec; //代表OS可以延时的最少单位
SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk; //开启SYSTICK中断
SysTick->LOAD=reload; //每1/delay_ostickspersec秒中断一次
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启SYSTICK
#else
fac_ms=(u16)fac_us*1000; //非OS下,代表每个ms需要的systick时钟数
#endif
}
#if SYSTEM_SUPPORT_OS //如果需要支持OS.
//延时nus
//nus为要延时的us数.
void delay_us(u32 nus)
{
u32 ticks;
u32 told,tnow,tcnt=0;
u32 reload=SysTick->LOAD; //LOAD的值
ticks=nus*fac_us; //需要的节拍数
tcnt=0;
delay_osschedlock(); //阻止OS调度,防止打断us延时
told=SysTick->VAL; //刚进入时的计数器值
while(1)
{
tnow=SysTick->VAL;
if(tnow!=told)
{
if(tnow<told)tcnt+=told-tnow; //这里注意一下SYSTICK是一个递减的计数器就可以了.
else tcnt+=reload-tnow+told;
told=tnow;
if(tcnt>=ticks)break; //时间超过/等于要延迟的时间,则退出.
}
};
delay_osschedunlock(); //恢复OS调度
}
//延时nms
//nms:要延时的ms数
void delay_ms(u16 nms)
{
if(delay_osrunning&&delay_osintnesting==0) //如果OS已经在跑了,并且不是在中断里面(中断里面不能任务调度)
{
if(nms>=fac_ms) //延时的时间大于OS的最少时间周期
{
delay_ostimedly(nms/fac_ms); //OS延时
}
nms%=fac_ms; //OS已经无法提供这么小的延时了,采用普通方式延时
}
delay_us((u32)(nms*1000)); //普通方式延时
}
#else //不用OS时
//延时nus
//nus为要延时的us数.
void delay_us(u32 nus)
{
u32 temp;
SysTick->LOAD=nus*fac_us; //时间加载
SysTick->VAL=0x00; //清空计数器
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数
do
{
temp=SysTick->CTRL;
}while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
//延时nms
//注意nms的范围
//SysTick->LOAD为24位寄存器,所以,最大延时为:
//nms<=0xffffff*8*1000/SYSCLK
//SYSCLK单位为Hz,nms单位为ms
//对72M条件下,nms<=1864
void delay_ms(u16 nms)
{
u32 temp;
SysTick->LOAD=(u32)nms*fac_ms; //时间加载(SysTick->LOAD为24bit)
SysTick->VAL =0x00; //清空计数器
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数
do
{
temp=SysTick->CTRL;
}while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
#endif
三、总结反思
由于是很久之后再次使用stm32,有许多小细节没有注意,
比如定义变量应该在开头部分。
正点原子自带的LED灯DS0和DS1的点亮方式不同,一个是低电平点亮,一个是高电平点亮。
主函数中有一个小Bug,如果在红灯的判别条件中选择小于等于3000,那么如果在绿灯亮起时松手,红灯会在绿灯熄灭之后接着亮起,所以应该吧等号去掉。
四、视频效果展示:
【正点原子战舰V3开发版/按键扫描的方式控制LED灯的亮灭】 https://www.bilibili.com/video/BV1hLRnYREfg/?share_source=copy_web&vd_source=33adf9f4b4c4b1221219d2246aa376b1