4993|9

7815

帖子

57

TA的资源

裸片初长成(中级)

楼主
 

程序库之旅 4: 独立于MCU的LED闪烁库 [复制链接]

   我们知道,这样一个程序,需要两个部分。
1. LED的亮灭控制;
2. 延时函数;
   如果只是像那些入门教程那样直接写在main函数里,这是非常简单的。
   

       现在让我们考虑,如何实现 独立于MCU这个目标。
   首先,我们知道,LED的亮灭控制,本质上是控制IO口输出高或者低电平。这是与MCU有关的——不同的单片机,操作的方式和操作寄存器是不一样的。
   延迟函数非常好办,因为它与平台无关。
   倒是LED的亮灭函数,它与单片机有关,因此,不适宜直接封装到库里去。
   但是,再仔细想想,尽管控制LED亮灭的寄存器不一样,然而,在这个LED闪烁函数里,让LED亮,让LED灭这个操作本身,却是与控制LED亮灭无关的。
   我的意思是,不管你通过什么寄存器来控制LED亮灭,但我什么时候让LED亮什么时候让它灭却是与寄存器无关的。
   这就好比,我想去成都旅游这件事,和我到底是坐火车还是坐汽车,或者飞机去成都这几种交通方式无关的一样。
   所以,我们的做法是,“让LED什么时候亮LED什么时候灭”这个功能可以做进库里。而LED如何亮灭,交由应用程序实现。
   

      这里头稍微有一点技术细节要处理,它也是这篇帖子里的一个技术要点(除此以外都是技术思想)。
   我们已经介绍过。
   库本身,在形式上,是一系列函数单独编译以后得到的二进制代码;
   前面说的,Led_Light();Led_Dark()
   这两个函数,在编译库的时候,我们并没有实现这两个函数。
   而我们却已经在库的源码里调用了它们。
   我承认,这确实让人感觉有点怪。
   而我本人,也是在逻辑上觉得可行,然后确实尝试了一次,发现的确管用以后,才理出了头绪,并由此,决定了这篇帖子采取的这种方式来实现独立于MCU平台。
   原理很简单:
   因为,库的编译过程,不涉及 链接 这个动作,也就是说,它不需要确保库源码里所用到的函数都已经明确定义了。
但是,它必须知道函数的接口,所以,我们只要提供一个声明即可。
   而声明和定义本身是完全无关的。我们可以写一百个一千种函数定义,然后共用同一种声明。
   而这不妨碍库里先调用它。
   这个方法巧妙就巧妙在这里。你可以把那一百个一千种定义 引申到 一百个一千种单片机机型的寄存器操作。
就这样,我们实现了 LED闪烁函数独立于  MCU平台。
   这里必须说明,对于LED这类直接操作IO的简单操作,这种库的风装方式也显得非常简单,看起来好像也没有什么太大意义。
   但我想说,这只是一个入门的简单例子,就像学单片机点亮LED一样。接下来,我会一步一步举例在不同应用模式下的封装方法,如何基于这种思想实现。
   你将会看到越来越复杂的应用实例,越来越复杂的实现方法,实现越来越酷的抽象方式——抽象于MCU机型,抽象于不同应用项目。


        下面是代码。

        具体的编译方法。
        我个人最常用各种IAR的ide。在IAR下是很简单的,区别就在 option选项里,C/C++ compiler这个选项里的output 勾选 library,而不是 execute就可以了。
        下面附上示例代码。
此帖出自编程基础论坛

最新回复

  详情 回复 发表于 2015-8-6 17:16
点赞 关注
 

回复
举报

7815

帖子

57

TA的资源

裸片初长成(中级)

沙发
 
  1. void delay(int delay)
  2. {
  3.      while(delay--);
  4. }

  5. void main(void)
  6. {
  7.      // 以51单片机为例,LED接在 P10脚
  8.      while(1)
  9.          {
  10.               P1 |= 0x01;
  11.                   delay(300);
  12.                   P1 &= ~0x01;
  13.                   delay(300);
  14.          }
  15. }

  16. //==================================
  17. //库模式

  18. // lib.h 头文件
  19. void Led_Light(void);
  20. void Led_Dark(void);
  21. void delay(int delay);

  22. // lib.c  库源码

  23. void Led_Toggle(void)
  24. {
  25.      while(1)
  26.          {
  27.              Led_Dark();
  28.                  delay(300);
  29.                  Led_Light();
  30.                  delay(300);
  31.          }
  32. }

  33. // 延迟函数,与MCU无关,是一个真正的 公共函数,我们同样封装在一个库里。
  34. // 因为它独立于MCU平台,而且很多不同 MCU的不同项目都会用到,于是我们
  35. // 把它独立封装成一个库,取名 公共函数库;

  36. //  Public.c
  37. void delay(int delay)
  38. {
  39.      while(delay--);
  40. }

  41. // 然后,在应用程序里,这个例子比较简单。

  42. #include "lib.h"

  43. // 然后,在项目文件里 ,要加入 编译好的库文件。
  44. // 这里有两个 lib.a PublicLib.a
  45. // 然后
  46. void main(void)
  47. {
  48.      Led_Toggle();
  49. }

  50. // 但是别忘了,我们还要实现 Led_Light() 和 Led_Dark();

  51. // 它很简单;

  52. void Led_Light(void)
  53. {
  54.      P1 &= ~0x01;
  55. }

  56. void Led_Dark(void)
  57. {
  58.      P1 |= 0x01;
  59. }

  60. // 这的确显得不够直接,如此当然是有目的,因为,假如我们现在换了一个MCU,情况会如何呢?、

  61. //如430.我们只需要修改....
  62. void Led_Light(void)
  63. {
  64.      P1OUT &= ~0x01;
  65. }

  66. void Led_Dark(void)
  67. {
  68.      P1OUT |= 0x01;
  69. }

  70. // 或者 stm8/32?? 没问题
  71. void Led_Light(void)
  72. {
  73.      GPIOE->ODR &= ~0x01;
  74. }

  75. void Led_Dark(void)
  76. {
  77.      GPIOE->ODR |= 0x01;
  78. }

  79. //.......
  80. //挺棒把?
  81. //但这里还不是很棒,这个问题目前我也没解决。
  82. /*
  83.    我需要在新的ide中,重新编译一次那两个库。
  84.    我可以不做么?
  85.    直觉告诉我似乎不能,因为,这是在两个平台上跑的机器代码呢?!
  86.    但是,在使用keil里,我模糊记得我似乎看到过一个选项是 链接“第三方库”。
  87.    这个第三方库的意思必然是某种其他C编译器编译出来的代码。
  88.    理论上来说这是可能的,毕竟都是C编译器,毕竟只不过是对应的机器码不一样。
  89.    
  90.    所以,下一步我同时还会考虑这个问题,这个问题如果可以得到妥善解决,意义重大。
  91.    因为,这样一来,我编写的库只需要用一个C编译器编译一次,就可以被任何C编译器链接,而无需我另外多编译一次库。
  92. */


复制代码
此帖出自编程基础论坛
 
 
 

回复

4996

帖子

19

TA的资源

裸片初长成(初级)

板凳
 
不错。。。
此帖出自编程基础论坛
 
个人签名我的博客
 
 

回复

22

帖子

0

TA的资源

一粒金砂(中级)

4
 
如果想闪烁两个LED灯呢?
如果想改变LED闪烁的频率呢?
如果有中断或者其它任务切入,LED闪烁的频率就被改变了。
用延时函数实现闪烁也太浪费MCU资源了。

独立于MCU做到了,但离库还有一定距离吧。
此帖出自编程基础论坛

赞赏

1

查看全部赞赏

 
 
 

回复

7815

帖子

57

TA的资源

裸片初长成(中级)

5
 

回复 4楼成风 的帖子

哈哈,终于收到一条有深度的回复了 。
事情是这样的。

其实你提的这三个问题,不管封不封库,不管独不独立于MCU,都是存在的。
而且解决方法也是类似的。

这三个问题里
第一个问题是最难的(以后就一个比一个容易了)。

第一个问题涉及的是 如何 结构化常用结构。

我昨天想了想,对这个问题的解决方法做一个简单的示例如下:
  1. typedef struct
  2. {
  3.        void (*LedInitial)(void);
  4.        void (*LedLight)(void);
  5.        void (*LedDark)(void);
  6. }LedStruct;
复制代码
是的这个方法的核心就是 结构体封装函数指针。
这样,我们只要把上面写得那个 同步阻塞 的 LED闪烁函数 改成 以 LedStruct 的模式就好了。
  1. 28.void Led_Toggle(LedStruct *Led)

  2. 29.{

  3. 30.     while(1)

  4. 31.         {

  5. 32.             (*(Led->LedDark))();

  6. 33.                 delay(300);

  7. 34.                 (*(Led->LedLight))();

  8. 35.                 delay(300);

  9. 36.         }

  10. 37.}
复制代码
这样在使用的地方,不管你要多少个LED,一个LedStruct就代表一个LED。

事实上,这个问题扩展开来,可以得到一系列类似的功能
比方说,一个MCU带有N个ds18b20,M个sht10,都可以做类似的处理。

函数指针 是关键,这种方法类似于 回调函数机制。
此帖出自编程基础论坛
 
 
 

回复

7815

帖子

57

TA的资源

裸片初长成(中级)

6
 

回复 4楼成风 的帖子

至于第二个问题,不难。
不管是采用我原来的方法,还是最新的这种 结构体封装LED操作的方法。

都一样,因为我可以传递参数啊。

举例
  1. 28.void Led_Toggle(LedStruct *Led,U8 delay)

  2. 29.{

  3. 30.     while(1)

  4. 31.         {

  5. 32.             (*(Led->LedDark))();

  6. 33.                 delay(delay);

  7. 34.                 (*(Led->LedLight))();

  8. 35.                 delay(delay);

  9. 36.         }

  10. 37.}
复制代码
或者
  1. 27.

  2. 28.void Led_Toggle(U8 delay)

  3. 29.{

  4. 30.     while(1)

  5. 31.         {

  6. 32.             Led_Dark();

  7. 33.                 delay(delay);

  8. 34.                 Led_Light();

  9. 35.                 delay(delay);

  10. 36.         }

  11. 37.}
复制代码
此帖出自编程基础论坛
 
 
 

回复

7815

帖子

57

TA的资源

裸片初长成(中级)

7
 

回复 4楼成风 的帖子

第三个问题,同样不难。
这只是 同步阻塞 或者 异步 方式的区别。

上面是为了简单,所以我纯粹用同步方式完成示例。
但我采用异步的话,第三个和第四个问题就都一并解决了。

如果非要做成 异步,也就不会出现你说的那种 情况。
  1. void RunTimer(U32 *Timer)
  2. {
  3.          if(*Timer <= 0xffffff)
  4.            (*Timer)++;
  5.          else
  6.            *Timer  = 0;
  7. }

  8. void ClearTimer(U32 *Timer);
  9. //就不写了,如名字,都是按地址操作变量而已;

  10. /* 这里是最简单的 判断超时,还可以做成 带标志的,乃至是多种不同超时模式。看你想怎么写,看这个东西是否常用到足以让你结构化而已*/
  11. void IsTimeout(U32 *Timer,U32 MaxTime)
  12. {
  13.         RunTimer(Timer);
  14.         if(*Timer >= MaxTime)
  15.            return True;
  16.         else
  17.            return False;
  18. }

  19. //这样以后,就可以用这个代替那个delay函数了。
  20. 这样,整个CPU就不会阻塞在这个地方。


  21. /* 这里方便起见 ,先不改成 用 结构体的,但其实是类似的。 */

  22. void Led_Toggle(U8 delay)
  23. {
  24.         static U8 Status = 0;
  25.         static U8 Timer = 0;

  26.         if(IsTimeout(&Timer,delay) == 1)
  27.         {
  28.                 Status  = !Status;
  29.                 if(Status == 1)
  30.                    Led_Light();
  31.                 else
  32.                    Led_Dark();
  33.         }
  34. }
复制代码
此帖出自编程基础论坛
 
 
 

回复

7815

帖子

57

TA的资源

裸片初长成(中级)

8
 

回复 4楼成风 的帖子

其实还有很多其他更棘手的问题。
不知道上面的回答是否让你满意?

希望你能有更多的好建议 或者 指教指教 该怎么办。谢谢

期待你更多的有深度的问题。
此帖出自编程基础论坛
 
 
 

回复

7815

帖子

57

TA的资源

裸片初长成(中级)

9
 
这个帖子很久没更新。
作为在 主题帖 里 提到的 越来越复杂的应用实例,也久久没有提供实例。
但其实最近是不断在使用这种方式,比如 一个 12864的,今天因为那个直播贴里一个哥们提问,我才回想起这个帖子,用来给他作为例子说明我的方法。

而同时,这个帖子也可以吸收手机DIY第四个帖子中关于封装12864库的例子作为 更复杂的这种抽象MCU的应用实例

https://bbs.eeworld.com.cn/thread-441498-1-1.html
此帖出自编程基础论坛
 
个人签名

强者为尊,弱者,死无葬身之地

 
 

回复

1891

帖子

2

TA的资源

纯净的硅(中级)

10
 
此帖出自编程基础论坛
 
个人签名
分享铸就美好未来。。。




 
 

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

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/7 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表