5009|19

7815

帖子

57

TA的资源

裸片初长成(中级)

楼主
 

只为uC而生,uS成长历程 1 [复制链接]

在前面,我们非常happy而激动地和各位同好讨论了一下这个 非主流的uS到底有木有前途,尽管如此,非主流的辛昕童鞋毅然决定继续走下去,不管最终搞出个什么恶心死人不偿命的牲口。

闲话少说。
在我们继续coding以前,我们先来做一些关于 源文件管理的 工作。

为了更好地管理源文件,我的项目,总是把源码分门别类,放在不同的文件夹里。
这里截个图,作为一个例子。



它们所包含的的东西,如同文件夹的名字一样,人如其名,绝对童叟无欺。
只是不同阶段,我划分的类别有所不同。

这种源文件组织方式,唯一会遇到的麻烦,是编译的时候,要在编译选项里加入头文件搜索路径。

这个工作对于新人,小菜来说略有点蛋疼,但我奉劝你最好学会。
否则有一天但你的项目包含了超过四十个源模块(也就是说你大概会有80多个.c .h)我估计你不疯掉也会晕掉
叔叔曾经也有过那么一段蛋疼的岁月~~~

这里我举个例子吧。
我习惯IAR开发,在IAR下,你只要 右键 workspace的名字 选择option->c/c++ complier 下的 preproccer 里 写下
相对headfile的路径就可以。

如果你是在gcc下编译,那么你只要在编译命令里增加 -I/路径就可以了,当然这个时候,你肯定还要引入相对路径写.c文件
的所在文件夹,以及过后你链接库时,要写-L/ 库文件路径。

至于其他的ide,真的非常不好意思,我也不会,但作为一个成熟的ide,必然有这些选项的,请自行摸索。
如果你发现没有,请你一定马上把它delect...
此帖出自编程基础论坛

最新回复

现在看看这些思维,也不晚  详情 回复 发表于 2017-1-9 20:24
点赞 关注(1)
 

回复
举报

7815

帖子

57

TA的资源

裸片初长成(中级)

沙发
 

分出一个uS库项目和一个应用程序项目

如题,我做这件事只是为了保持uSer 和 Apper 绝对被隔离开来,它们除了通过函数调用是绝对不可能发生任何相互作用的。
是的,绝对不能让他们有任何奸情!!

所以,我做了如下调整——刚开始那会,因为我什么代码都没有,我实在没什么心情凭空分什么库和应用项目。
这件事情告诉我们,很多时候在你迈开第一步以前,你压根不知道下面你会做什么。
所以,极限编程告诉我们,只要有一个足够用来实现的想法,就要去coding,去实现。
然后再往下走,我对此非常认可,并且也是这样干的。

uSer刚出生.jpg (59.14 KB, 下载次数: 35)

[ 本帖最后由 辛昕 于 2013-7-30 21:34 编辑 ]
此帖出自编程基础论坛
 
 
 

回复

7815

帖子

57

TA的资源

裸片初长成(中级)

板凳
 

第一个问题

刚出生就面临一个大问题
从这幅截图可以看出,这个该死的程序,打死都没办法编译通过的。
因为,它没有实现gpio也没有实现timer。

按照我们之前的说法,这些玩意都将在Apper里实现。
那么,该肿么办呢?

老规矩,回调函数。
你以后会发现我没第二招,来来去去都是这一招......
但其实这一招基本上是解决这个问题的最简单方案。
而且我曾经百度过一下子回调函数,发现某些大大已经把这个技术上升到一定的角度,嗯,我也在继续研究这个东东。

恩,先上代码
  1. #include "uSCore.h"
  2. #include

  3. void uSCore_Initial(void (*AppInitial)(void))
  4. {
  5.      if(AppInitial != NULL)
  6.         (*AppInitial)();
  7. }
复制代码
对于这个问题,我们唯一要关心的事情只有一件:
直接调用一个函数,和通过 函数指针 进行回调。

会不会对 效率,,或者造成什么空间,时间上的麻烦呢?
对于这些问题,光主观想象是不对的,我们需要找个时间,做一个广泛的测试
——我的意思是,我最多只能测试在stm8s和cc2530上的情况,其他的情况需要各位,使用你们手上各种各样的单片机一起加入测试,最后我们就可以总结出一个大致经验,取一个最糟糕和一般结果作为我们以后使用这一招的 开销代价考虑。

对此我会提供一个测试小函数,但现在先放下。
此帖出自编程基础论坛
 
 
 

回复

7815

帖子

57

TA的资源

裸片初长成(中级)

4
 

生成 uSer 这个库

因为我们要从外部应用程序引用这个部件,而且我们要绝对避免应用者随便改写这个部件,所以我们通过编译成二进制库文件 链接的方式 来实现这件事。

从一开始就把它隔离在应用程序以外,会让我们更加习惯以后的使用方式

这将意味着你将没机会在应用程序对uSer 进行源码级调试。
因此,我们才会寻找足够好的测试方法和完善的测试用例组。

我们很奇怪,为什么人们总是对已经封装好的库还非要自己调试,而且不是独立测试,而是在应用层进行调式。
结果,他们到底是要调适自己的代码还是要调试已经完成的第三方代码呢?

我相信他们自己也是不清晰的。
因此,他们经常把问题交叉起来,当程序变得很大的时候,这种思考方式会非常非常累,而且极度缺乏安全感,缺乏信任感。

我经历过这种事情,所以现在我从自己坐起,从一开始,从第一个函数,尽管只有三行代码,我就要这样做,以使我可以最终提供一组足以让人信任的自动测试集。

而且,可以最终提供一个足够高质量的独立第三方库。
你使用它,不再需要对它小心翼翼和担心它会出问题。

回到正题

在IAR里编译库并不复杂,和一般项目相比,只需要在option里的general option里的output里选择生成lib,而不是exe就可以了。

stm8生成的库,名字均已.a为后缀。
在我们调用的时候,只要像加入.c文件一样加入它就可以了。

当然,你还要把需要用到的头文件路径填写到 那个 Apper程序里,没错,写在option的 c/c++ complier里的preproceer里就可以了。
此帖出自编程基础论坛
 
 
 

回复

2781

帖子

417

TA的资源

五彩晶圆(中级)

5
 
长知识了
此帖出自编程基础论坛
 
个人签名
 
 

回复

1729

帖子

0

TA的资源

五彩晶圆(初级)

6
 
建议还是组团打副本吧!
此帖出自编程基础论坛
 
 
 

回复

7815

帖子

57

TA的资源

裸片初长成(中级)

7
 

当前的应用程序

  1. #include "uSCore.h"

  2. void SystemInitial(void);

  3. void SystemInitial(void)
  4. {
  5.      
  6. }

  7. //==============================================================================

  8. void main(void)
  9. {
  10.     uSCore_Initial(SystemInitial);
  11.    
  12.     while(1);
  13. }


  14. // end of file -----------------------------------------------------------------
复制代码
从我写程序的习惯来看,我习惯在main程序之初,首先调用一个总的初始化函数。
因为这个总的 系统初始化函数几乎和main函数的其他部分一样调用各种模块,而它又不从属于任何模块,而我又没有打算那么啰嗦的为此另外新建一个模块,而居然只有一个函数,那就实在太操蛋了。
所以我总是把这个 系统总初始化函数和main写在主源文件里。

调用uSCore_Initial(),还是老套路,通过回调,把uSer里的初始化也给顺带掳掠上。
此帖出自编程基础论坛
 
 
 

回复

7815

帖子

57

TA的资源

裸片初长成(中级)

8
 
我也希望大家一起来啊。
但是,考虑到这个思路还没有走上正轨。人一多,恐怕意见太多,乱了套。
我高中的英语老师有一句很经典的话

“师傅多了,屋梁就要歪”

所以尽管我现在的开发过程全程直播,而且源码全部开放。
但暂时不开启 多人共同开发模式

以免这个小baby挂掉。

但是我非常希望你们
如果你们对我的这个思路有一点欣赏或者愿意沿着我的实现思路一起往下走。

也或者你们自己有一些不一样的想法,希望你们和我一样,一同在这个舞台上展示你们的实现方案。

而我这个uS而言,也许等到它开发到一定程度,我会考虑开启多人模式。
但我本人仍然会保持uS这个主发行版的绝对决定权。
当然,你们如果愿意在我的uS的任何一个版本上进行其他改动,那听君之便,只是,uS这个名字我就据为己有了啊,哈哈!
此帖出自编程基础论坛
 
 
 

回复

7815

帖子

57

TA的资源

裸片初长成(中级)

9
 
至此,我们已经初步建立起这个 框架

一个是 uSer
一个是应用它,或者是(开发过程里)测试它而用 的应用程序。

接下来,我们要把昨天在uSCore里的定时器中断移进去。

你会很奇怪,为什么现在只不过是一个非常简单非常简单的破定时器中断,我却现在就要开始整理代码,重构实现呢?

这是一种 极限编程 的典型步骤。
也是 TDD(测试驱动开发模式)的 典型步骤

它是一个微循环

1.每次,首先确定一个目标;
2.然后通过设计测试用例,来确定它要实现的预期目标;
3.然后我们开始实现;
4.然后我们按照第二步的测试用例去测试我们的代码;
5.如果我们实现了,我们就往下走到第六步,如果没有完成或者失败了,我们就回到第二步或者第三步迭代进行;
6.至此我们已经完成了第一步定下的目标;我们要整理代码,重构,使他符合我们的编程风格,习惯,并且更易于
理解和维护。

这就是微循环。
当我们确定的目标非常小的时候——比方说这个 定时器中断,也许你会很厌烦。
但是,请相信这种小目标的伟大意义。

因为小,因为简单,所以每一步你都可以很轻松地实现,不会遇到大麻烦,也不会出现大的失误。
然而,一步一步小的方案积累起来,就是你的整个方案。

这是一件让人多么欣慰而且感到神奇的事情。

在这个uS的开发,我将坚持使用这种模式。
当然我会做一定的变通,但前提是不在大方向上违背原则。

我也希望你也信仰这种想法。
人需要灵活变通,但不能以此为借口放弃原则。
为了一些所为方便,所为快速开发。

回想一下那个 日本人每天擦六次桌子的故事,而中国人却聪明的不擦六次,结果呢?
结果今天的日本产品质量以精良著称世界,而我们的中国制造呢?
不多说。
此帖出自编程基础论坛
 
 
 

回复

7815

帖子

57

TA的资源

裸片初长成(中级)

10
 

设计定时器中断的“测试用例”

翻开讲述软件测试的各类书籍。
我们经常会很纠结一件事情。
如果你写一个根据边长判断三角形的函数,你可以想到各种边界测试。

然而,面对这样一个简单的定时器中断,你会想到测试什么呢?
你有办法实现自动测试吗?

也许不行,我们要接受事实。我们是在做单片机开发,而不是PC程序。
所以我们要放开视野,重新考虑这个问题。

所谓测试,如前所述。
它是以是否实现某些我们要实现的功能(更重要,它不能做某些事情,然而这个例子里不明显,所以暂且不提。)

我们考虑一下,对于定时器我们要“测试什么”

首先,第一点当然是,我们要保证,定时中断真的已经跑起来了;
其次,我们还要考虑,定时中断准不准呢?它每次中断溢出的时间到底有多长呢?

暂时,我们想不到别的了。
那好吧,我们就先确定这两个测试用例;
现在考虑,该如何测试;

对于第一个测试:回忆过往,当你实现了一个定时器中断以后,你一般会用什么来表明你的中断已经跑起来呢?
我说说我的做法。
我总是通过在定时中断里翻转一次LED,以通过观察LED闪烁来确定定时中断的的确确运行起来了;

至于第二个测试。
我们先考虑一下,可以说,在一个纯粹的单片机环境里(不包含任何特殊外设,比如RTC时钟)

我们是没有办法完成的。

因为我们首先是通过计算时钟脉冲个数来定时。

但是,你怎么能够确定你的晶振的的确确如其标称值那样驱动着单片机的CPU呢?
假如你使用内部RC振荡源,而他的精度非常差呢?

又因为你使用的是C语言,你大概不会想回去反汇编看到底跑了多少多多少条指令,每条指令的执行时间又是多少,那样你绝对会疯掉的。


而且这是不准确的。

所以我门换一个角度考虑问题。

如果我可以拥有一个参考标准,那我就可以做到。
比如我们平时粗略测量定时器周期的方法

我经常是在定时器里 不断累积一个静态变量,等到一定次数我就翻转一次LED,然后我通过秒表去计算时间,再估算出每一次定时中断的周期来。

这个方法也是类似的,只是为了实现自动估算,我们需要引入一个参考时间标准。

我暂时想到了两个参考源
假如片上有足够准确的RTC,那么它就是一个最好的本地参考时间源。
假如没有,但是我已经实现了某种和PC电脑或者网络的通信接口,我就可以通过电脑或者网络获取时间。

当然无论是哪个方式,最终而言我其实只是通过两个方式获取时间
一个网络时间同步;
一个是PC主板上的RTC时钟,其实本质和单片机上的RTC是一样的。

但这个实现过程比较复杂。
而且需要以实现RTC的接口,或者更简单,通过和PC通信获取时间的方式,所以我们暂时放下。


因此,我们只实现第一个测试。通过LED闪烁,来判断。

我的意思是,我们要实现这个函数。

因为这属于测试uS里的定时器中断,因此,我们将把它安置在uSer里。

而在这里,我们需要使用的第一个外设,gpio正式出场。
此帖出自编程基础论坛
 
 
 

回复

7815

帖子

57

TA的资源

裸片初长成(中级)

11
 
在测试以前,仔细梳理了一下现在实现的部分
发现有一个问题

我没有如我所设想的那样实现,可以在uSCore里植入函数 放到 中断里。
不过不要紧,我们总是可以想到办法的。

今晚回来的有点晚。
已经快11点了。

可能要明晚继续了。
此帖出自编程基础论坛
 
 
 

回复

7815

帖子

57

TA的资源

裸片初长成(中级)

12
 
额,其实很容易解决。
衣服洗着洗着突然想到了。

指针就是好,按照地址传递参数,实际上是双向的
既是输入,也是输出,哈哈。

示意如下:

uSCore部分:
  1. #include "uSCore.h"
  2. #include

  3. /*
  4.    因为考虑到uSCore里很可能有其他基本组件需要其他回调函数;
  5.    我已经隐约感到这个方法的限制,那就是如果有很多个不同的回调函数呢?
  6.    当然我可以使用结构体封装起来,但是这不见得是个很好的方式;
  7.    然而目前我只能想到这个,也许问题来临时我已经能想到更好的或者针对性的其他办法了
  8.    anyway,这就是我们现在能想到的足够好的解决方案。 “足够好”就 足够了
  9. */
  10. void (*pTimerIsr)(void) = NULL;  

  11. /*
  12.      当我们在 uSCore 内部有函数或者变量需要放在定时器中断里操作的时候,我们会在其他地方,
  13.      把那些函数的地址付给 pTimerIsr 这个指针。
  14.      因此它就变成了非NULL,一旦变成了非NULL,它就会通过 TimerIsrContainner传递出去,回到
  15. Apper,否则它不会改变这个参数;
  16.      对Apper不会有任何影响,也没有任何危险;
  17. */

  18. void uSCore_Initial(void (*TimerIsrContainer)(void))
  19. {
  20.      if( (pTimerIsr != NULL) && (TimerIsrContainer != NULL) )
  21.         TimerIsrContainer = pTimerIsr;
  22. }
复制代码
Apper部分:
  1. #include "uSCore.h"
  2. #include "uCGpio.h"
  3. #include "uCTimer.h"
  4. #include

  5. //==============================================================================

  6. void (*TimerIsrContainer)(void) = NULL;

  7. //==============================================================================

  8. void SystemInitial(void);

  9. void SystemInitial(void)
  10. {
  11.      uCGpio_Initial();
  12.      Timer_Initial(TimerIsrContainer);
  13. }

  14. //==============================================================================

  15. void main(void)
  16. {
  17.     uSCore_Initial(TimerIsrContainer);
  18.     SystemInitial();
  19.    
  20.     while(1);
  21. }


  22. // end of file -----------------------------------------------------------------

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

回复

7815

帖子

57

TA的资源

裸片初长成(中级)

13
 
稍微对上面Apper的部分做个简单解释

void (*TimerIsrContainer)(void) = NULL;

这个函数指针是为了通过 uSCore_Initial时传入,去承载 在uSCore里实现的,想要放入 定时中断 的 内容。

然后带出这个指针以后,再放在SystemInitial里,被TimerIsr调用。

当然,在这种情况下,我意识到,也许Initial部分也足以封装成一个 模块了。
不过这个需求还不够强烈,所以暂时不做。

OK,今晚真的的停下来了,晚安。
此帖出自编程基础论坛
 
 
 

回复

7815

帖子

57

TA的资源

裸片初长成(中级)

14
 

一些新想法:限制开发者的某些行为

尽管作为一个第三方库,应以对开发者不具有任何限制为第一要务。
这种原则来自于我自己非常厌恶类似于360这种流氓软件经常弹出什么

正在为你杀毒 之类的 无耻行为。

然而,我们必须认识到一个写的再好的内核或者库。
假如被使用不当也会引起很多麻烦。
这个从长远角度来说会丢了 第三方库的脸,毁了它的名声。

因此对于某些行为
比如说,暂时想到的,不允许出现某些会在运行时长时间占住中断(因为main函数 uSer 是没办法也没有权利去干涉的。)
的任务,那么,uS应该有办法去限制它。
比如说把它的函数指针清成NULL,革除出任务,以避免整个系统瘫痪。
这种行为本身并不难实现。

难只难在于评判标准(而非判断方法)
以及如何传达给应用者。

因为是在运行时的功能,所以我考虑到通过某种通信管道,比如最一般的串口,向console打印信息,说明什么任务被革除,或者处于警告
——这个可以发展成一个 质量分析工具;

另外就是,某些函数在编译时就已经足以发现问题,
这个时候,可以通过 #error这些预编译手段停止。

我们知道多的是弃警告于不顾,结果最后引发了各种麻烦的开发者。
因此,我们必须通过强硬手段使其不得不改正错误。
比方说编译出错它就没有办法绕过去。

当然,根据不同级别,我们可以设置 警告 和 错误。
毕竟,限制还是不要太多的好。

遵循一定的游戏规则是必须的,这样是为了更好地游戏下去。
为了更好的开发软件。
为代码,为产品,为客户负责。
此帖出自编程基础论坛
 
 
 

回复

1100

帖子

3

TA的资源

五彩晶圆(初级)

15
 
顶棍哥。太给力。
此帖出自编程基础论坛
 
 
 

回复

5015

帖子

12

TA的资源

裸片初长成(初级)

16
 
辛昕加油!
此帖出自编程基础论坛
 
个人签名《MCU工程师炼成记》作者之一
 
 

回复

2453

帖子

19

TA的资源

五彩晶圆(中级)

17
 
虽然辛昕写得内容很好,也讲了很多编程思想和技巧,但是必须指出的是太罗嗦了。看着累
此帖出自编程基础论坛
 
 
 

回复

7815

帖子

57

TA的资源

裸片初长成(中级)

18
 

回复 17楼zca123 的帖子

嗯,我也觉得是,以后少啰嗦一点~~
此帖出自编程基础论坛
 
 
 

回复

1976

帖子

0

TA的资源

五彩晶圆(初级)

19
 
必须支持啊,都是实践之谈,值得学习
此帖出自编程基础论坛
 
 
 

回复

57

帖子

0

TA的资源

一粒金砂(中级)

20
 
现在看看这些思维,也不晚
此帖出自编程基础论坛
 
 
 

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

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

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

 
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
快速回复 返回顶部 返回列表