找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 3323|回复: 49
打印 上一主题 下一主题
收起左侧

请优化你的MCU代码 Ver2.0

  [复制链接]
回帖奖励 500 黑币 回复本帖可获得 5 黑币奖励! 每人限 1 次
跳转到指定楼层
楼主
- 前言 -
⼤家,好久不⻅。
此⽂旨在于对2019年我发表在51黑论坛的贴⼦《请⼯程化,定制化你的单⽚机代码http://www.51hei.com/bbs/dpj-162218-1.html 进⾏更新,升级和完善。
虽说现在回头⼀看,那篇贴⼦在⼀些细节确实有⼀些问题,不过最⼤的问题是——它不是⼀个完整的⽂章,我匆忙的把⼀些编程要点塞进了帖⼦⾥,然后就⼀直没有更新了。


如今⼀晃就是6年,机缘巧合下,我突然⼜找到了那篇贴⼦。在不知不觉之间,帖⼦已经被置顶,有了这么多的评论和阅读量,这令我甚是惭愧。
感谢⼤家的⽀持。
我近期刚好有时间,那就把帖⼦重写⼀下,并更完⽂章吧。

- 第⼀章:设计⼀个时钟基线 -
在⼀些初级MCU编程⼊⻔书中,经常能够⻅到类似
  1. delay1s();  //延时1秒
复制代码
或者
  1. while(K1 == 0); //等待⽤户释放键盘上的K1按键
复制代码
这样的代码,表⾯上看没有什么问题,但是本质上跟下⾯的逻辑差不多:
“已知⼩明同学从教室跑去⼩卖部买糖再回来,⼤概需要1分钟,所以,当我们需要计时12⼩时的时候,让⼩明同学这样来回往返个720次,应该就差不多了。”
“你戳⼩明⼀下,他就会叫⼀声但是如果你⼀直戳着不松开,他就会由于脑⼦不够⽤,不但叫不出来,就算你跟他说话他也没法听进去。”


在这样的代码中,MCU的PC指针(这东⻄代表着程序当前在哪个代码⾥运⾏)就如同悲催的⼩明同学,被困在那两⾏代码中。也许是巨⼤的资源浪费,也许是⽆法对外部的信息变化做出反应,都是很糟糕的事情。
那么,要怎么样来编写单⽚机程序,才可以⼜实现这些逻辑功能,⼜释放PC指针呢这就是我们接下来要讨论的内容。

1.1 带有时基的主程序架构

正如“ 每个不得不跑去⼩卖部的⼩明,其实他去年买了个表,根本不⽤跑 ”的道理()。
即使是普通的51单⽚机,他也是有定时器的。
我们需要设置⼀个定时器,让定时器会告诉单⽚机:“好的,现在已经过了1ms了(或者10ms这都没关系)”单⽚机⼀拍脑袋:“啊,已经到了1ms了么,那我就把⼀些事情做⼀下吧”。就像下面这样子:


在这个框架下,我们的代码看起来会像下⾯这个样⼦:
  1. unsigned char flag1ms; //全局变量

  2. void main()
  3. {
  4.     flag1ms = 0;
  5.     mcuConfig(); //MCU的⼀些其他初始化,此处略过
  6.     timer0Init(); //定时器0初始化,略过,需设置1ms定时周期

  7.     while(1)
  8.     {
  9.         if(flag1ms)
  10.         {
  11.             flag1ms = 0;
  12.             doAnything(); //单片机可以做任意事情
  13.         }
  14.     }
  15. }

  16. void timer0XINT() interrupt 1 //定时器T0中断
  17. {
  18.     flag1ms = 1;
  19. }
复制代码
为了⽅便理解,这⾥的“⼀系列⼯作”在代码中只⽤了⼀个doAnything()来表⽰,实际应⽤中,往往会放好⼏个⼦程序,没什么问题。
在本程序中,单⽚机每隔1ms会将⼀系列⼯作执⾏⼀遍。从现在开始,你的⼦程序必须要摒弃那些“傻等⾏为”,重新设计你的程序逻辑。

1.2 ⼀个按键程序的例⼦
我们先拿⼀个⾮常恶⼼的按键函数来开⼑,如果没有时基,我们可能会写出这样的⼀个按键程序:
  1. /* 在没有时基的时候,代码是这样写的 */
  2. #define K1_PRESSING() ((P1&0x01)==0) //按键位于P1^0引脚
  3. void keyPress()
  4. {
  5.     unsigned int key_press_time = 0;
  6.     while(K1_PRESSING()) //等待松开
  7.     {
  8.         ++key_press_time;
  9.         if(key_press_time==300) //300*10ms = 3s
  10.         {
  11.             //⻓按3s时要做的事情
  12.         }
  13.         delay10ms(); //delay计时
  14.     }
  15.     if((key_press_time < 300) && (key_press_time >= 2))
  16.     {
  17.         //短按的处理
  18.     }
  19. }
复制代码
这个函数兼具delay()语句和while(KEY==0)语句,可以说是⼆毒俱全了。
如果是在新的代码架构中,它可以修改成这样的形式
  1. /* 本程序将在每个1ms时基中被调⽤ */
  2. #define JUST_INC(x) if(++x<=0) --x //x在不能溢出的前提下++
  3. #define K1_PRESSING() ((P1&0x01)==0) //按键位于P1^0引脚
  4. void keyPress()
  5. {
  6.     static unsigned int key_press_time = 0;  // ……请标为静态变量
  7.     if(K1_PRESSING())
  8.     {
  9.         JUST_INC(key_press_time);//计量按键时间,并避免数据溢出
  10.         if(key_press_time==3000)
  11.         {
  12.             //在此写下按键⻓按3s时要做的事情
  13.         }
  14.     }
  15.     else
  16.     {
  17.         if((20<=key_press_time) && (key_press_time < 3000))
  18.         {
  19.             //20ms ~ 3s之间,视为短按,在此写下写短按的处理代码
  20.         }
  21.         key_press_time=0;
  22.     }
  23. }
复制代码
功能是⼀样的,但实际⽤起来区别很⼤——旧程序跑1次⻓按功能,新程序已经跑了3000次,且后者不会⼀直占⽤PC指针。
新的程序有些细节,可以展开说⼀说:
①key_press_time现在是⼀个静态变量,static关键字可以让该变量在每次重新进⼊函数的时候不会重新赋值为0,⽽是保留上次退出函数时的值。
②“JUST_INC(key_press_time)”这⼀句,看起来⽤“++key_press_time”就能搞定,但是,谁也不能保证⽤户真的不会按按键超过65秒的啊万⼀他真的按了65576ms单⽚机还就真的以为⽤户“短按”了⼀次呢(65576-65536=40ms,属于短按范畴),下⾯那个短按程序段也会被执⾏现在这样写,哪怕你按100年也没关系了,反正单⽚机就每隔1ms进来看⼀次,K1这个按钮你想按多久就按多久,掉在范围内就处理,超出范围就⽆视
③“if(key_press_time==3000)”这⾥的3000只是随便设置的⼀个3秒⻓按时间,如果需要做按键⻓短按功能,这⾥就是⻓按程序所放的位置也可以不⽤3000,⽤2000、50000都没事,别超过65534就⾏。
④“if((20<=key_press_time) && (key_press_time < 3000))”这⾥,前⾯的>=20是短按的消抖设计——再强的⼈类也不可能1秒按⼀个按键超过20次,也就是不可能⼩于50ms的时间——这⾥⽤了20ms相当于兼容“超级快男”来按按键了。后⾯<3000是不能和⻓按的时间冲突,因为3s我们已经⼈为的设置成⻓按时间节点了。
⑤这⾥的参数遵循乘法原则。举个例⼦,如果时基是10ms,那么这⾥的if(key_press_time==3000)就会代表30秒,如果想设置⻓按3秒的话,得把3000改成300。


1.3 关于时基的进阶设置
回到刚刚讨论的时钟基线处,这⾥有⼏个⼩问题。
(1)假如,1ms的周期不够把事情做完怎么办
——那就把时基设置⼤⼀些,⽐如5ms,10ms,20ms,还不够就该换芯⽚了,要不就是代码还有很⼤的其他问题,需要好好排查。
(2)我该怎么⼤致估算代码跑完“⼀系列⼯作”需要的时间,好让我设置更合适的时基
——好问题这⾥有⼀个简单且准确的⽅案任意找个闲置端⼝,⽐如P0^0,在⼲活之前先把这个端⼝拉⾼,⼲完活之后⻢上把它拉低,通过外接⽰波器或者逻辑分析仪,就能直观的测量出耗时了。



(3)只有1个时基不⽅便,我的按键只需要10ms采集1次,但是LCD显⽰我需要100ms才刷新⼀次。
——时基是可以扩展的!在1个时基的基础上,只要你有需要,完全可以扩展出N多个时基匹配你的N个⼦程序。
⽐如下⾯的代码,就在1ms的时基上分别扩展出了10ms和100ms的时基:
(4)我的各个函数应该放在哪个时基⾥⾯呢
——这要求我们要对⾃⼰的程序要有清楚的把握,以及⼀定的产品思维,以下是⼀⼰之⻅。
①⾸先,所有的函数都要写得简洁⼲净,不要有任何模块的delay()加起来超过0.2ms,⼦程序⾥⾯放⼏个nop倒是⽆伤⼤雅。
按键,检测,通信这类的⼦程序放到10ms时基⾥。输出,显⽰这类的放100ms时基就OK了。
③1ms时基⾥⾯应该放什么呢可以什么也别放,空着就好。或者把主程序的基础时基换成10ms也可以,其实很少有东⻄需要刷新得这么快的。如果基础时基打算⽤10ms的话,可以将定时器的中断设置为每10ms触发(这样可以删掉flag1ms变量,此处不展开)也可以偷个懒,原本的程序框架改成这样即可


④如果有特别需要关照的部分,⽐如说步进电机的驱动啥的,请放到另⼀个定时器中断⾥(单⽚机基本都⾄少有俩定时器的,不⽤⽩不⽤),按你需要的来设置。
⑤定时器的中断触发时间建议不要少于0.5ms,不然进中断就太频繁了。
现在,我们的代码已经初具雏形,⼤家可以⾃⼰搭⼀下这个框架来体验⼀下。
(当当当,现在是中场休息时间,喝杯⽔吧)




1.截至2025年10月的点击量.png (39.4 KB, 下载次数: 0)

1

1

请优化你的代码-附件程序F103.7z

684.3 KB, 下载次数: 0, 下载积分: 黑币 -5

请优化你的单片机代码.pdf

3.22 MB, 下载次数: 0, 下载积分: 黑币 -5

评分

参与人数 3黑币 +45 收起 理由
yinds5092 + 15
1109 + 15 赞一个!
莫烨 + 15 很给力!

查看全部评分

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏22 分享淘帖 顶9 踩
回复

使用道具 举报

沙发
ID:476527 发表于 2025-10-30 20:43 | 只看该作者
本帖最后由 Similarv 于 2025-11-3 08:57 编辑

全文共有3页,请手动翻页。
另外,附件的排版应该比网页的排版干净。
由于不可抗力,从第5个代码块开始,网页端po出的代码就开始出现乱码,我重复编辑了很多次,都无法解决。因此后面很多代码块都变成了图片格式,影响了大家的阅读,望谅解。
回复

使用道具 举报

板凳
ID:476527 发表于 2025-10-30 20:47 | 只看该作者
终于整完了,这次特意在换工作的空窗期,抽出时间完成了这个主题。大家万圣节快乐。


祝我明天面试顺利,嘻嘻。
回复

使用道具 举报

地板
ID:190832 发表于 2025-10-30 22:40 | 只看该作者
这样不好吗void ledTask(void)
{
    static u32 usBck = 0, usNow = 0;
    static u8 sta = 0;

    usNow = mSysTimerGetUs();
    if((usNow - usBck) > 250000)
    {
      usBck = usNow;
      sta = (sta == 0) ? 1 : 0;
      if(sta == 0)
      {
        LED_RUN_OFF; LED_STOP_OFF;
      }
      else
      {
        LED_RUN_ON; LED_STOP_ON;
      }
}
回复

使用道具 举报

5#
ID:476527 发表于 2025-10-30 22:59 | 只看该作者
hange_v 发表于 2025-10-30 22:40
这样不好吗void ledTask(void)
{
    static u3 ...

你好,请问这是在?
回复

使用道具 举报

6#
ID:1097337 发表于 2025-10-31 04:49 来自触屏版 | 只看该作者
非常不错,谢谢小猪猪的指导:D,收藏了!
回复

使用道具 举报

7#
ID:190832 发表于 2025-10-31 09:15 | 只看该作者
发表于 2025-10-30 22:59
你好,请问这是在?

闪烁LED  250ms
回复

使用道具 举报

8#
ID:476527 发表于 2025-10-31 16:48 | 只看该作者

我推测,这段代码只能保证“LED不会在250ms以内就翻转电平”,并不能确保刚好总是250ms的时候就立马翻转。
回复

使用道具 举报

9#
ID:190832 发表于 2025-10-31 20:46 | 只看该作者
Similarv 发表于 2025-10-31 16:48
我推测,这段代码只能保证“LED不会在250ms以内就翻转电平”,并不能确保刚好总是250ms的时候就立马翻转 ...

那得做到定时器中断,且优先级最高
回复

使用道具 举报

10#
ID:142652 发表于 2025-10-31 22:16 | 只看该作者
感谢大师分享
回复

使用道具 举报

11#
ID:477512 发表于 2025-11-1 07:21 | 只看该作者
这得好好学习一下!
回复

使用道具 举报

12#
ID:945281 发表于 2025-11-1 15:24 | 只看该作者
谢谢分享
回复

使用道具 举报

13#
ID:945281 发表于 2025-11-1 15:28 | 只看该作者
真的学到很多知识,再次谢谢
回复

使用道具 举报

14#
ID:22119 发表于 2025-11-2 14:57 来自触屏版 | 只看该作者
写的真好
回复

使用道具 举报

15#
ID:77675 发表于 2025-11-2 18:07 | 只看该作者
大神终于有更新了,受教了。
回复

使用道具 举报

16#
ID:308541 发表于 2025-11-2 20:45 | 只看该作者
写得很好
回复

使用道具 举报

17#
ID:86450 发表于 2025-11-2 23:33 | 只看该作者
[em05]
回复

使用道具 举报

18#
ID:82399 发表于 2025-11-3 07:44 | 只看该作者
好贴,学习了!
回复

使用道具 举报

19#
ID:1144680 发表于 2025-11-3 09:51 | 只看该作者
楼主说的全部中招
回复

使用道具 举报

20#
ID:901570 发表于 2025-11-3 18:53 | 只看该作者
感谢大佬的教程,已保存,慢慢研究!
回复

使用道具 举报

21#
ID:1157477 发表于 2025-11-6 16:45 | 只看该作者
非常感谢楼主的付出。虽然我是小小小小白,只会复制别人的代码
回复

使用道具 举报

22#
ID:277290 发表于 2025-11-7 08:19 | 只看该作者
非常不错,谢谢指导!
回复

使用道具 举报

23#
ID:277290 发表于 2025-11-7 08:20 | 只看该作者
非常不错,谢谢指导!
回复

使用道具 举报

24#
ID:60656 发表于 2025-11-8 22:27 | 只看该作者
真的学到很多知识,再次谢谢
回复

使用道具 举报

25#
ID:46750 发表于 2025-11-9 20:24 | 只看该作者
大神终于有更新了,受教了。
回复

使用道具 举报

26#
ID:158391 发表于 2025-11-13 02:22 | 只看该作者
菜鸟一枚,现在的能写出的都是让大神恶心的代码
回复

使用道具 举报

27#
ID:283207 发表于 2025-11-13 23:06 | 只看该作者

非常感谢楼主的付出。
回复

使用道具 举报

28#
ID:34149 发表于 2025-11-13 23:42 | 只看该作者
学习了大师的编程思维!真的受益良多!非常感谢!
回复

使用道具 举报

29#
ID:476527 发表于 2025-11-14 14:06 | 只看该作者
dyx811 发表于 2025-11-13 23:42
学习了大师的编程思维!真的受益良多!非常感谢!

大师不至于,这是一个面向新手的教程,目的是让大伙起步的时候少走一些弯路。
至于怎么成为大师,我自己也在摸索前进。
回复

使用道具 举报

30#
ID:1144680 发表于 2025-11-17 10:27 | 只看该作者
新手表示看不懂啊。
如果要设计一个短按与长按的函数,是直接让定时器计时吗?
回复

使用道具 举报

31#
ID:476527 发表于 2025-11-17 11:46 | 只看该作者
dcc60 发表于 2025-11-17 10:27
新手表示看不懂啊。
如果要设计一个短按与长按的函数,是直接让定时器计时吗?

你的主程序是每10ms才跑一次的,静态变量的计数值就是计时值
回复

使用道具 举报

32#
ID:232366 发表于 2025-11-17 11:49 | 只看该作者
又学习了
回复

使用道具 举报

33#
ID:98767 发表于 2025-11-17 14:07 | 只看该作者
谢谢分享,学习优化
回复

使用道具 举报

34#
ID:366877 发表于 2025-11-17 22:47 | 只看该作者
有个问题想请教:文中提到“不要有任何模块的delay()加起来超过0.2ms”,但对于数码管动态扫描如何控制每个数码管点亮2ms?
回复

使用道具 举报

35#
ID:476527 发表于 2025-11-17 23:13 | 只看该作者
wdgao 发表于 2025-11-17 22:47
有个问题想请教:文中提到“不要有任何模块的delay()加起来超过0.2ms”,但对于数码管动态扫描如何控制每 ...

个人建议你用低优先级的定时器中断,2ms周期扫一下
回复

使用道具 举报

36#
ID:18961 发表于 2025-11-18 10:01 | 只看该作者
非常不错,值得学习一下!
回复

使用道具 举报

37#
ID:366877 发表于 2025-11-19 07:07 | 只看该作者
发表于 2025-11-17 23:13
个人建议你用低优先级的定时器中断,2ms周期扫一下

谢谢指导,是个好办法。
回复

使用道具 举报

38#
ID:140411 发表于 2025-11-29 08:21 | 只看该作者
很好的编程思维,值得学习。
回复

使用道具 举报

39#
ID:492633 发表于 2025-11-30 12:13 来自触屏版 | 只看该作者
能不用阻塞延时就不用
回复

使用道具 举报

40#
ID:1110792 发表于 2025-12-2 18:43 | 只看该作者
真不错,学习了!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表