[讨论] 只为uC而生,uS成长历程4

辛昕   2013-8-2 19:58 楼主
昨晚我花了不少时间,做了一系列关于开销的测试。
我们可以得出一个我们很关心的结论:

那就是,相比于平常的直接调用变量和函数。
采用指针,乃至通过结构体引用,相比而言,多出来的 时间开销并不是十分大,大多数情况下,不足以成为影响效率和实时性的问题。

故而,从此开始,我们不再有任何这方面的忧虑,可以大胆的使用这种方法。

而前面也说了。相比于时间开销,一个更大的问题应该是 作为使用者,用起来会比较辛苦。
复杂的使用方法,尽管不是一个硬伤,但是,它仍然有违我们的初衷。

然而,我们也分析过使用函数指针调用代替 直接调用 的 一些好处,不好放弃。
于是,我们试图重新梳理实现思路,尽可能简化使用的方式。

回复评论 (17)

uSCore的全局考虑

尽管我前面一步一步从定时器开始,是为了强调一种思路,为了提高编程的执行力,我们不要考虑的太多,而是要尽可能行动。

而实质上,我早在开始写这一系列的帖子以前,我已经全盘考虑过整个uS的大致构成。
其中,对于uSCore。

截至目前,我考虑实现的两个部件是
1.定时器中断;
2.数据收发功能(目前将以串口实现);

定时器中断的基本功能是:
1.作为一个异步定时器;
2.预留一个粗糙的时间片轮训机制的实现基础;

而串口收发功能 是为了实现 可以直接从uSCore内部向外部输出信息。
这和我们直接在Apper里实现的区别是什么呢?

区别主要是:
1.并不是所有的uC应用程序都需要串口。(当然,这个可以根据实际实现情况,换成其他数据口,比如以太网口或者无线通信接口)
   这里似乎有点强制Apper实现。
   它的功能如同C标准里的stderr流,用来输出错误信息。

2.把它实现在uSer内部,可以把包括向外输出uSer错误或者其他状态信息的功能都集中到uSer里,而无需Apper多花精力去实现。

两个主要部件,意味着至少要带入两个函数指针,也可能不止如此。
此外,我们不能不考虑将来uSCore的功能部件可能扩展。

此外,作为uSer的最重要逻辑块,也就是我们的 uS_Pheriphal,即大家最关心的 外围设备的通用接口。
它们无论如何逃不了若干个IO函数。
因此可以想见,这些函数指针的数量极其繁多。

因此,通过单一一个函数指针传递是不现实的。
因此我们考虑一种新的封装方法。

我们把这些函数指针全部封装成一个新的结构体。
通过这个结构体可以大幅度简化 初始化(也可称之为向uSer的注册)函数 的 接口形参数量。

饭好了,先吃饭~~
点赞  2013-8-2 20:12

新的uSCore模块

  1. //  in uSCore.h

  2. #ifndef _US_CORE_
  3. #define _US_CORE_

  4. #include "typedef.h"
  5. #include "CommonMacro.h"

  6. typedef struct
  7. {
  8.     void (*TimerIsrContainer)(void);
  9. }uSCoreFunc;

  10. void uSCoreFunc_Initial(uSCoreFunc *Str);
  11. void uSCore_Initial(uSCoreFunc *pFuns);

  12. #endif
  1. #include "uSCore.h"
  2. #include
  3. #include "uSCoreTest.h"

  4. uSCoreFunc uSCoreInit;

  5. void uSCoreFunc_Initial(uSCoreFunc *Str)
  6. {
  7.      Str->TimerIsrContainer = NULL;
  8. }

  9. void uSCore_Initial(uSCoreFunc *pFuns)
  10. {
  11.      uSCoreFunc_Initial(&uSCoreInit);
  12.      
  13.      if( (uSCoreInit.TimerIsrContainer != NULL) && (pFuns->TimerIsrContainer != NULL) )
  14.         pFuns->TimerIsrContainer = uSCoreInit.TimerIsrContainer;      
  15. }

  16. // end of file -----------------------------------------------------------------
这里要注意一个 结构体初始化 的函数。
两点说明:

1.之所以以结构体指针传递要初始化的结构体,因为在Apper必然要使用到这个类型的结构体来传递函数指针。
因此,它的初始化不只是在这里要用大;

2.其二为什么要首先初始化NULL。初始化为NULL或者变量初始化为0,很多人也许只是一个良好习惯而不知道为什么。
这里理由很简单,你需要任何时候让变量处在一个确定的状态,否则,特别是 状态类变量,总会有让你意外的事情发生,理由也很简单,因为你没有初始化他,它处在一个未定状态。自然程序也会有不确定走向。

另外就是,我们前面说过,在执行一个函数指针以前,必须要判断它是否非NULL,否则万一执行了一个NULL指针,谁也不知道程序到底会跑到什么地方去。

在我的stm8s105系统里,它是进入一种类似死机的状态

[ 本帖最后由 辛昕 于 2013-8-2 20:58 编辑 ]
点赞  2013-8-2 20:46

回到uS_App.考虑如何调用

在uSCoreFunc里包含了(目前只有它,因为我们只需要它)
包含了我们要放到 定时中断里的函数。

因此,我们在Apper里,首先要找一个合适的地方,定义这样一个结构体指针。

最终,综合考虑一下,我放弃了原来的简单 把初始化放在main()函数前,而是另外设置一个uSInit作为独立的初始化模块——当然,它不仅限于只初始化uSCore部分,以后的所有uS初始化都将放在这里,否则这个模块就划分的太不科学了,太繁琐了。
  1. #include "uSInit.h"
  2. #include "uSCore.h"
  3. #include

  4. uSCoreFunc uSCoreReg;

  5. void uSCore_Pre(void)
  6. {
  7.      uSCoreFunc_Initial(&uSCoreReg);
  8. }

  9. void uS_Init(void)
  10. {
  11.     uSCore_Pre();
  12.     uSCore_Initial(&uSCoreReg);
  13. }
我们将注意到。
这个初始化模块,包含了一个非常重要的结构体, uSCore_Reg。
因为它,它将被大多数模块调用。

因此,考虑到这个关系。
我决定给这个模块换个名字。
叫什么好呢?

.........................

想了好一会都没想到,算了,先押下。
毕竟这个历程是软件的生命之旅,所以任何时候都可以改变。
这是这个 软件隐喻 最温暖人心的地方。
点赞  2013-8-2 21:13

终于回到我们的正题,把LED闪烁函数加入定时器中断,测试uSCore...

这个帖子的时间间隔稍微长了一点。

因为我现在继续写这个帖子的时候,我已经完成了 标题里写的内容了。

几乎零点了。

之所以慢,是因为真切遇到一些需要考虑如何实现更好的方法。
几乎可以说,这是第一次实质性遇到的麻烦,也将决定了将来这个uSer的实现基本思路。

我决定先贴出部分代码,然后洗完澡,接着或者明天才来具体解释。

代码从下一个帖子开始贴
点赞  2013-8-2 23:42

当前的 uSer

当前uSer只有两个源文件

头文件基本只是函数声明,所以除非我新增加了什么重要的宏或者结构体定义,否则就不再贴出头文件了。
  1. #include "uSCore.h"
  2. #include
  3. #include "uSCoreTest.h"
  4. #include "uSTask.h"

  5. uSCoreFunc uSCoreInit;

  6. uSCoreFunc *get_uSCoreInit(void)
  7. {
  8.      return (&uSCoreInit);
  9. }

  10. void uSCoreFunc_Initial(uSCoreFunc *Str)
  11. {
  12.      Str->TimerIsrContainer = NULL;
  13.      Str->Led_4_Test = NULL;
  14. }

  15. void uSCore_Initial(uSCoreFunc *pFuns)
  16. {
  17.      uSCoreFunc_Initial(&uSCoreInit);
  18.      
  19.      if(pFuns->Led_4_Test != NULL)
  20.         uSCoreInit.Led_4_Test = pFuns->Led_4_Test;
  21.      
  22.      uSCoreInit.TimerIsrContainer = uS_TaskList;
  23.      
  24.      if(uSCoreInit.TimerIsrContainer != NULL)
  25.         pFuns->TimerIsrContainer = uSCoreInit.TimerIsrContainer;   
  26. }



  27. // end of file -----------------------------------------------------------------
  1. #include "uSTask.h"
  2. #include "uSCore.h"

  3. void uS_TaskList(void)
  4. {
  5.      static U16 Times = 0;
  6.      
  7.      Times++;
  8.      
  9.      if(Times & 0x100)
  10.        (*(get_uSCoreInit()->Led_4_Test))();
  11. }
这里先简单介绍一下这个uSTask的设置原因。

因为,整个uSer的各个部件,都有可能往这个 将放在定时器中断服务程序 里的函数,这个时候我们不妨称之为 Task也就是任务了。

因此,它应该独立出来,高于其他已知设定的两三个独立逻辑部块。


[ 本帖最后由 辛昕 于 2013-8-2 23:51 编辑 ]
点赞  2013-8-2 23:46

当前的Apper 在我的项目里,我命名为 uS_App,作为开发uS过程中的测试项目

包含4个源文件

第一个自然是主源文件main函数所在
  1. #include "uSInit.h"
  2. #include "uCGpio.h"
  3. #include "uCTimer.h"

  4. void SystemInitial(void)
  5. {
  6.      uSReset();
  7.      uCGpio_Initial();

  8. }

  9. void main(void)
  10. {
  11.     SystemInitial();
  12.     uS_Init();
  13.     Timer_Initial();
  14.    
  15.     while(1);
  16. }
这里必须说明,因为这个时候,有一个顺序的问题要处理(跟通过函数指针和uSer进行“进进出出”的函数交换所致),所以,目前还是暂时调出预期效果的结果。

接下来的整理和重构,第一个要对付的就是这个地方,一定要努力使他与顺序无关或者明确一个好的顺序,或者让这个顺序不易混淆的措施。
一定要让使用者容易使用为己任。


接着是新增加的uSInit模块,它是给Apper集中初始化uS各部件的。
  1. #include "uSInit.h"
  2. #include "uSCore.h"
  3. #include

  4. uSCoreFunc uSCoreReg;

  5. void uSReset(void)
  6. {
  7.      uSCoreFunc_Initial(&uSCoreReg);
  8. }

  9. void uS_Init(void)
  10. {
  11.     uSCore_Initial(&uSCoreReg);
  12. }

  13. uSCoreFunc *get_uSCoreReg(void)
  14. {
  15.     return (&uSCoreReg);
  16. }
然后是 定时器中断 和 GPIO的底层设置
  1. #include "uCTimer.h"
  2. #include "stm8s_tim2.h"
  3. #include "uSInit.h"
  4. #include

  5. void (*TimerIsr)(void) = NULL;

  6. void Timer_Initial(void)
  7. {
  8.     TIM2_TimeBaseInit( TIM2_PRESCALER_16 ,400);
  9.     TIM2_PrescalerConfig(TIM2_PRESCALER_16,TIM2_PSCRELOADMODE_IMMEDIATE);
  10.     TIM2_ARRPreloadConfig(ENABLE);
  11.     TIM2_ITConfig(TIM2_IT_UPDATE , ENABLE);
  12.     TIM2_Cmd(ENABLE);
  13.    
  14.     if(get_uSCoreReg()->TimerIsrContainer != NULL)
  15.        TimerIsr = get_uSCoreReg()->TimerIsrContainer;   
  16.    
  17.     __enable_interrupt();
  18. }

  19. #pragma vector=0xF
  20. __interrupt void TIM2_UPD_OVF_BRK_IRQHandler(void)
  21. {  
  22.    TIM2_ClearITPendingBit(TIM2_IT_UPDATE);
  23.    
  24.    if(TimerIsr != NULL)
  25.       (*TimerIsr)();
  26. }
  1. #include "uCGpio.h"
  2. #include "stm8s_gpio.h"
  3. #include "uSInit.h"
  4. #include

  5. void (*Led_4_Test)(void) = NULL;

  6. void uCGpio_Initial(void)
  7. {
  8.      GPIOD->DDR |= 1<<0;
  9.      GPIOD->CR1 |= 1<<0;
  10.      
  11.      get_uSCoreReg()->Led_4_Test = uCGpio_Toggle;
  12. }

  13. void uCGpio_Toggle(void)
  14. {
  15.      static U8 Status = 0;
  16.      
  17.      Status = !Status;
  18.      
  19.      if(Status == 0)
  20.         GPIOD->ODR |= 1<<0;
  21.      else
  22.         GPIOD->ODR &= ~(1<<0);
  23. }

[ 本帖最后由 辛昕 于 2013-8-2 23:52 编辑 ]
点赞  2013-8-2 23:50
楼主头像戴着红领巾,,顶一个
点赞  2013-8-4 19:44

回复 8楼kobe1941 的帖子

那是 红领带.......

虽然差了一个字,,,,红领巾是我十来岁的时候带的
带红领带的时候我快24了~~~大学毕业照
点赞  2013-8-4 21:18

回复 板凳辛昕 的帖子

void uSCore_Initial(uSCoreFunc *pFuns)
{
     uSCoreFunc_Initial(&uSCoreInit);
     
     if( (uSCoreInit.TimerIsrContainer != NULL) && (pFuns->TimerIsrContainer != NULL) )
        pFuns->TimerIsrContainer = uSCoreInit.TimerIsrContainer;      
}

不知道这里的if 语句还有什么作用,第一行就已经
uSCoreInit.TimerIsrContainer  = NULL,
那if根本就没起作用啊,不理解啊不理解
话说开头看就优点被绕晕的感觉,菜鸟就是这样了,什么时候才能得到这高度啊
点赞  2013-8-27 21:30

回复 10楼木瓜子 的帖子

是的。
这个地方写错了。
我刚检查了我最新的代码里,因为改动比较大(名字,甚至是后来的 注册方式 都变化了很多)

但从我对比和回忆。
我当时的确是这个地方写错了。

我记得我后来好像改成 只检查 参数的成员 非NULL。
点赞  2013-8-27 23:22

回复 6楼辛昕 的帖子

typedef struct
{
    void (*TimerIsrContainer)(void);
}uSCoreFunc;

这是定义的结构体,里面只一个函数指针成员,但我不知道下面的这个函数,其参数怎么会出现了另一个成员Led_4_Test  ?

void uSCoreFunc_Initial(uSCoreFunc *Str)
{
     Str->TimerIsrContainer = NULL;
     Str->Led_4_Test = NULL;
}

话说怎会这么少人关注啊,我是才开始发现版主发的这个系列us成长,就关注起来,俺一个菜鸟,多学习一些前辈高手的编程思想

[ 本帖最后由 木瓜子 于 2013-8-28 23:11 编辑 ]
点赞  2013-8-28 23:07

回复 12楼木瓜子 的帖子

well
是这样的。
我这是直播的内容,所以前后改动比较大。
你现在弄的是 定时器那一部分。
我建议你去 第六 还是第七篇,找 uS-v0.1释出版。
那是完整的而又纯粹的只有定时器 的 最终版。

当然你也可以找到我现在醉心释出的 uS v0.2
但我记得,这个部分我做过的改动并不多,只是改了个名字。

这里跟你道个歉。
因为理论上来说,我不应该随便改变接口或者名字。
但是因为这个东西是从无到有,我一开始很难做到一成不变的设计。
所以整个过程都是很迅猛变化的。

我的建议是

你找到两个 释出版本
uS v0.1 和 uS v0.2
然后分别往回看,这样不至于出现太多奇怪的地方。
点赞  2013-8-28 23:13

回复 12楼木瓜子 的帖子

嗯,多谢你的夸奖。

我喜欢你提到的 成长。

是的,这也是我这个系列直播贴的初衷。

另外感谢你作为第一个真的在看我做的整个过程的人。
对于前面说的,我加一个补充。

你在看的过程中会遇到一些你觉得奇怪的地方,这是正常的。
正如前面所说,这是一个成长的过程。加上我写程序的时候,并不急于保证每一步做的都是可以马上编译运行——而是一个设计过程和思路的完整展现。
——事实上我觉得这样的编码方式更健康,更容易进行——这叫增量式开发过程。

所以,我给你的建议是,你遇到看不懂不理解的地方,可以先记下,然后接着往下看。

我每一次都只完成一个独立模块,然后分别以 uSv0.1 uSv0.2 冠名。
因此你每次也不需要看的太远,只要看到当前的 模块释出 就可以 回头整理 所遇到的问题了。

——我相信,在我的直播贴里,你会看到所有变化的源头和原因。

然后,非常欢迎你经常这样反馈意见。
因为对于你的回复,我非常感动!
点赞  2013-8-28 23:19

回复 11楼辛昕 的帖子

还有不对劲的啊, 我怎么觉得是应该第一句去掉的,看着这个函数的本意似乎是,如果被传递的函数指针不为空,且参数所指的函数指针也不为空,就将被传递的函数指针赋值给参数所指的指针

void uSCore_Initial(uSCoreFunc *pFuns)
{
     uSCoreFunc_Initial(&uSCoreInit);
     
     if( (uSCoreInit.TimerIsrContainer != NULL) && (pFuns->TimerIsrContainer != NULL) )
        pFuns->TimerIsrContainer = uSCoreInit.TimerIsrContainer;      
}
也就是应该是
void uSCore_Initial(uSCoreFunc *pFuns)
{
     if( (uSCoreInit.TimerIsrContainer != NULL) && (pFuns->TimerIsrContainer != NULL) )
        pFuns->TimerIsrContainer = uSCoreInit.TimerIsrContainer;      
}

不知道我说的对不对
点赞  2013-8-28 23:21
好,我找下你说的那两个版本,仔细看下,我看的时候是一点一点仔细看,想明白了才往下看,所以总是被卡住
点赞  2013-8-28 23:27

回复 16楼木瓜子 的帖子

嗯,先从每个释出版的地方获得当时的最后版本,再回去看就好了。

另外,我强烈建议你,在你使用的单片机上试用一下这个程序。

我有一个uS_App,你只需要根据你自己的程序,修改一下相关的寄存器设置就可以了。
点赞  2013-8-28 23:30

回复 17楼辛昕 的帖子

我会试试看的,谢谢斑竹的指教
点赞  2013-8-28 23:35
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复