stm32l4 外部中断按键会卡死_单片机小书3-中断是什么

本文介绍了如何在STM32L4单片机中利用外部中断实现按键切歌功能,分别讲解了电平控制和边沿控制两种模式,并探讨了中断在程序设计中的重要性,强调了代码格式规范和函数封装在软件开发中的价值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这部分我们将会将按键与外部中断放在一起学习,实现音乐盒的切换歌曲-切歌。

首先我们先看看按键的电路图

eabf36f380da28184df96bc1bdac2942.png

考虑到51单片机的内部结构,这个按键只能在P1-P3口上用(自带上拉电阻),如果想在P0口上用,需要加上拉电阻(注意P0-3口三者IO内部结构是不同的)。

这里我们用P3.2口作为按键输入口,从电路图里,我们看到,

按键松开的时候,通过内部上拉输入为高电平,读取P3.2输入为1

按键按下的时候,通过外部短路接地为低电平,读取P3.2输入为0

下面我们先用按键,实现两种切歌功能:

1。电平控制

松开按键的时候,播放歌曲1,按下按键播放歌曲2,一直按着会一直播放歌曲2,直到松开后又播放歌曲1

2。边沿控制

每按下一次按键,切换一次歌曲

是不是有点懵逼,这两个功能难道不一样吗?????

NO。NO。NO。

下面我们看一下对比图:

d95250fbfae48a537d3da26f3383ab2c.png

看了是不是能明白,两种模式的玩法,其实这个在Linux的epoll网络编程里也有类似的玩法,电平触发还是事件触发模式,本质上是输入信号与输出状态的映射方式。

电平模式是输入什么状态会直接映射到输出上去,比如一直按着按键同时一直触发播放歌曲2,松开按键后改变状态,同时切歌到播放歌曲1。

边沿模式是不断检测输入变化从而触发输出状态变化,比如每按下一次按键,会产生一个下降沿触发,然后去改变播放歌曲的状态。

我们先做一个假设,已经实现了播放歌曲的函数(后面我们会想办法实现代码封装)

void playMusic(unsigned char *music,unsigned char i)//music为乐谱指针,i为播放第几个音符。

code unsigned char music1[]={} //歌曲1乐谱

code unsigned char music2[]={} //歌曲2乐谱

然后我们电平模式的切歌玩法怎么玩呢?

我们看一下代码吧(为了方便,这里代码跟前一小节略有不同,我们稍后会重构整个代码)

sbit  KEY=P1^0;

//原始代码
while(1){
     i=0;
     while( i<33 ){
         playMusic(music1,i);
     i=i+1;
}
//修改后代码
while(1){
     i=0;
     while( i<33 ){
         if(KEY==1)
              playMusic(music1,i);
         else
              playMusic(music2,i);
         i=i+1;
     }
}

上面我们完成了通过判断KEY的状态,然后用一个简单的if—else分支就可以解决电平模式的按键切歌。那问题来了,如果我们想要实现边沿触发的模式怎么玩呢?

让我们想一想,边沿触发是一种变化检测,也就是我们要检测按键按下的这个动作变化,所以我们需要按键状态的去做判断,这就意味着我们需要两个值做对比。

ac92381865b581246faf83db807b251d.png

于是我们可以实现一个函数,当检测到按键按下时,会返回1,其他的情况返回0

char readKey(void){
     static char last=1;//默认初始化
     char now=KEY;      //读取当前状态
     char ret=0;
     
     if(last==1 && now==0)//上次为高电平,这次为低电平
         ret=1;
     last = now;//保存这次状态,方便下次使用
}

如果按键为多个,我们怎么修改代码呢

unsigned char readKey(void){
     static unsigned char last=0xFF;//默认初始化,8个全部为1
     unsigned char now=KEY;      //读取当前状态
     unsigned char ret;
     
     ret = (last^now) & last;
     return ret
}
//其中ret里的每一位代表当前有没有按键按下

我们现在完成了按键的状态检测,下面如何实现切歌呢,有的同学觉得,是不是可以这样玩。。。

//修改后,代码
while(1){
     i=0;
     while( i<33 ){
         if(readKey()==1)
              playMusic(music1,i);
         else
              playMusic(music2,i);
         i=i+1;
     }
}

NO! NO! NO!

这样写,会导致只有在按下按键的那一刻,播放music1,其他时候都播放music2。

我们需要一个全局状态变量,来确定目前在播放哪首歌曲

char curMusic=0;
#define MAX_MUSIC 2

//修改后,代码
while(1){
     i=0;
     while( i<33 ){
         //切歌状态
         if(readKey()==1){
              curMusic+=1;//顺序切歌
              if (curMusic >= MAX_MUSIC )
                   curMusic=0;
              i=0;//从头播放
         }

         //根据状态播放歌曲
         switch(curMusic){
             case 0:
                 playMusic(music1,i);
                 break;
             case 1:
                 playMusic(music2,i);
                 break;
             default:
                 playMusic(music1,i);
                 break;
         }
     i=i+1;
}

到目前为止,我们实现了,用两种方式实现切歌的功能。

那问题来了。不是要讲外部中断检测按键吗??外部中断能做什么。。。。

我们正常设计一个单片机死循环程序一般的套路是

while(1){
    task1_run();
    task2_run();
    task3_run();
}

上面的代码里有3个任务,比如我们上面音乐盒里面的任务(检测按键,播放音符等),我们不妨做个假设,任务1执行时间2ms,任务2执行时间3ms,任务3执行时间5ms,由于程序结构内部存在if-else,还回导致时间偏差,这样带来的问题就是我们整个循环的周期大概在10ms左右。

那假如我有一个很重要很重要的任务呢,一旦发生必须以最快速的速度进行,比如某村长突然接到某县长的任务,他一定是放下现在的一切事务,转而去做那件重要的事,因为关系到乌纱帽,这个时候,如果在原来大循环的结构下,就会发生,村长被撤职的问题,因为在接到县长任务后,他会依然选择执行完自己手头的任务,而不是立刻响应县长,所以不撤才怪!!

如果我放下自己手头的任务,立即响应更高级更紧急的任务,这就是中断,因为老子现在手里的活都停下来了,只为了服务好你!

那问题来了,在按键切歌这里我们怎么用上外部中断呢

之前我们检测IO按下状态需要用代码区实现,那现在就不需要了,因为单片机外部中断硬件会自动检测对应的IO状态,一旦符合中断条件,就会打断我们的主程序调用中断服务程序,所以我们可以试试。

变更后的代码:

void EXT0_ISR() interrupt 0 using 1
{
      curMusic++;//顺序切歌
      if (curMusic >= MAX_MUSIC )
           curMusic=0;
}
//修改后,代码
while(1){
     i=0;
     while( i<33 ){
         //根据状态播放歌曲
         switch(curMusic){
             case 0:
                 playMusic(music1,i);
                 break;
             case 1:
                 playMusic(music2,i);
                 break;
             default:
                 playMusic(music1,i);
                 break;
         }
         i++;
     }
}

中断带来的好处是什么呢??

实际的外部中断,简单点说,就是响应速度快,一般用于快速外部事件处理上,比如报警,过压过流上。

就好比刚才我们举得3个任务总共10ms的例子,如果又来一个紧急任务,一旦出事,立马响应,你会发现,不管怎么玩,至少是10ms才能响应,要知道如果是开关电源过压或者短路过流,这个时间,已经BOOM爆炸了。。。。。

在我们这里暂时体现不出来,因为切歌按键本身不是一个紧急事件,不需要快速响应。

我自己觉得,其实最近单片机这两小节讲的东西,技术上没有什么,都是一些很基本的知识,不写吧,觉得缺点什么,写吧,又感觉太基础没逼格。。哈哈。

但是,但是,但是,我用三个但是来强调一下这里主要说2点

1。代码格式问题

变量与函数命名,这个问题算不上是技术,但是非常关键比如

我们是用驼峰命名,还是下划线命名

以函数名为例

驼峰法 readKey

下划线 read_key

这两种命名没有优劣,但是一个工程里,最好是统一为一种。

还有一个问题,千万不宜用中文拼音命名,比如

上面的函数 duQuAnAian

难道你自己不觉得有什么不对吗?函数和变量,都不宜用中文拼音命名,万一你开源被外国有人看了,会一脸懵逼的!

还有就是{}有两种玩法

//一种是
while(1){
     //空4格,代码块
}

//另一种
while(1)
{
     //空4格,代码块
}

唯一的区别就是,第一个{是放在同行,还是另一一行,各有各的好 第一个,代码短,第二个看着更整齐 我的建议是,两种都可以,随你喜欢,但是4个格务必要空出来!!!!!

但是切记不要如下码代码,乱成一锅粥了。。。。谁看了都想踹两脚

while(1)
{//你播放哪首歌曲,从那开始播放
gPlayState.curFu=0;
PlayModeRun();   
//播放一首歌曲
while(gPlayState.curFu*3<100){
ReadPlayState();//读取音乐盒状态
Song(MUSIC[gPlayState.curSong]);//播放一个音符
LED_BlingMode();//LED 闪灯模式
gPlayState.curFu++;
}
} 

2。函数封装问题

我们不管是做Web,还是做嵌入式,随时都要想着一个问题,就是控制代码的复杂度,必须要考虑这个事情,这个非常重要。

最基本的控制复杂度方式,就是封装函数,将类似行为的代码抽象出来作为函数,将可变因素作为函数参数,从而实现代码复用。

紧接着是模块化封装,将某些功能相对接近或者集中的,封装成相对集中的模块,模块对外部提供不同的函数接口。这里一般是用.c和.h文件构建成一个模块。

再接着就是根据系统层次把相近的模块放到一层级,进行分层设计,具体可以看我之前的写得这篇文章。

三郎:嵌入式小书1-系统分层设计​zhuanlan.zhihu.com

我觉得重构代码,是一个软件工程师时时刻刻都要考虑的事情,

写出优雅的代码来,就是你生命中最美的时刻!

愿我们,一路同行,成就更好的自己!!!!!!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值