搜索
bottom↓
回复: 15

嵌入式内核学习笔记01(建立一个属于自己的AVR 的RTOS)_2014_4_5

[复制链接]

出0入0汤圆

发表于 2014-4-5 17:48:55 | 显示全部楼层 |阅读模式
*******************
学习背景

*******************


路线                                                                           

学习目的比较绕,开始是打算学 AVR 应用笔记的,辅助资料不多,然后 看了下GORDON的 AVR GUI,再找到 RTOS,没用UCOS的原因是 先选 AVR 平台。选择AVR,是因为 过去AVR 积累了很多不错的开源软件。
用AVR RTOS先作为入门,顺利的话,再学 UCOS 等。


入门资料                                                                          

《教程: 建立一个属于自己的AVR 的RTOS》,作者:黄健昌,论坛里有HTML版的,网上可以下载PDF。
  选择的主要原因,感觉 文档 结构清晰,层次分明。唯一不足之处,8年前的文章,没有第二版,不知道过时了没有,这方面没有概念。


没有概念也敢学的原因                                                


摘录原文,                        
我所有的知识和资源有:
Proteus6.7 可以用来模拟仿真avr 系列的单片机 WinAVR v2.0.5.48 基于GCC AVR 的编译环境,好处在于可以在C 语言中插入asm 的语句。
mega8 1K 的ram 有8K 的rom,是开发8 位的RTOS 的一个理想的器件,并且我对它也比较熟悉。
写UCOS 的Jean J.Labrosse 在他的书上有这样一句话,“渐渐地,我自然会想到,写个实时内核直有那么难吗?不就是不断地保存,恢复CPU 的那些寄存器嘛。”


***************************************


第一篇:函数的运行
***************************************

作者只说了,“在一般的单片机系统中,是以前后台的方式(大循环+中断)来处理数据和作出反应的。



这样有什么问题?                                 

对我这样的新手,就要多找些资料来理解。

http://blog.csdn.net/jorya_txj/article/details/8559422
http://blog.csdn.net/rabinsong/article/details/9397941
http://www.amobbs.com/thread-3640606-1-1.html

单片机前后台系统:缺点,就是改变前台程序,CPU的时间片就会改变。http://www.amobbs.com/thread-5457466-1-1.html


*****************************************

作者的目的不是为了说明 前后台缺点的,对他来说已经是已知的,对我还需要补充这方面知识。

作者举了个例子(头文件全部省略)



首先,提出一个问题:如果要调用一个函数,真是只能以上面的方式进行吗?

又给出了两个例子,



重点是方法2,继续,



知道怎样找51 和 MIPS 的汇编指令,AVR 没有接触过,网上资料太少,应该差不多。暂时,按照作者的意思理解,在调用void RunFun(void (*pfun)())的时候,的确可以把fun1 的地址通过r24 和r25 传递给RunFun()。

这样第一篇就结束了,引出了 AVR 的汇编 (基于 函数指针变量)。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2014-4-5 18:05:11 | 显示全部楼层
不错,留个印,值得学习

出0入0汤圆

发表于 2014-4-5 18:09:20 | 显示全部楼层
我的天啊,现在还有人盯着AVR啊,咋的也得转向ST系列或NXP系列,要不就去找飞思卡尔或瑞萨系列;
要不就干脆上那个国产姚或台系的片子;

出0入0汤圆

发表于 2014-4-5 20:51:14 | 显示全部楼层
好东西,关注ing

出0入0汤圆

发表于 2014-4-5 20:51:36 | 显示全部楼层
好东西,关注ing

出0入0汤圆

 楼主| 发表于 2014-4-5 21:48:08 | 显示全部楼层
kinsno 发表于 2014-4-5 18:09
我的天啊,现在还有人盯着AVR啊,咋的也得转向ST系列或NXP系列,要不就去找飞思卡尔或瑞萨系列;
要不就干 ...

AVR 性价比 不高,但是 有很多学习资源,尤其是国外开源资料。

出0入0汤圆

发表于 2014-4-5 23:41:07 来自手机 | 显示全部楼层
kinsno 发表于 2014-4-5 18:09
我的天啊,现在还有人盯着AVR啊,咋的也得转向ST系列或NXP系列,要不就去找飞思卡尔或瑞萨系列;
要不就干 ...

够用就好吧,在这个前提下,萝卜青菜各有所爱了。一直都用AVR,因为做的东西很简单,又不用死抠成本。对我来说,AVR现在好买的mega系列除了没有DAC是一大遗憾外,别的外设都很满意。还有就是翻新的太多了。

出0入0汤圆

 楼主| 发表于 2014-4-6 18:02:56 | 显示全部楼层
本帖最后由 oldbeginner 于 2014-4-6 18:19 编辑

****************************
第二篇: 人工堆栈

****************************


这篇非常短,对我也非常有挑战性,因为自己基本上不怎么看 汇编指令,使用的也是高级语言,根本不理睬堆栈的。

还好,有人帮忙排雷了,不知道作者是不是清楚。        
http://www.amobbs.com/thread-739915-1-1.html

//原文函数地址的高低字节入栈次序是相反的,虽然编译没问题,但实际运行出错。

   *pStack--=(unsigned int)pfun;         //将函数的地址低位压入堆栈,

*pStack--=(unsigned int)pfun>>8;     //将函数的地址高位压入堆栈



回到正文                                                                        


在单片机的指令集中,一类指令是专门与堆栈和PC 指针打道的,它们是
rcall 相对调用子程序指令
icall 间接调用子程序指令
ret 子程序返回指令
reti 中断返回指令

上面的指令不是本篇的重点,但是我都不熟悉,需要找些资料,







有了这个基础,就可以建立我们的人工堆栈了。           


然后,利用http://hi.baidu.com/master_wg/item/529840d43cebda1921e25030 的代码


运行,


作者是这样解释的,
RunFunInNewStack(),将指向函数的指针的值保存到一个unsigned char 的数组Stack
中,作为人工堆栈。并且将栈顶的数值传递组堆栈指针SP,因此当用"ret"返回时,从SP 中恢
复到PC 中的值,就变为了指向fun1()的地址,开始运行fun1().


天啊,我一点没看懂。

不过,利用仿真,可以看出 PROTC 的值在变化。


函数指针数组 + 人工堆栈 +汇编指令 = 很难理解

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

 楼主| 发表于 2014-4-7 15:51:40 | 显示全部楼层
oldbeginner 发表于 2014-4-6 18:02
****************************
第二篇: 人工堆栈

*****************
简化第二篇的理解
*****************


去除汇编,只有函数指针,



功能很明确,只是需要理解背后的机制,是人工堆栈再起作用。先继续,堆栈是学习重点。


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

 楼主| 发表于 2014-4-7 16:41:55 | 显示全部楼层
oldbeginner 发表于 2014-4-7 15:51
*****************
简化第二篇的理解
*****************

******************
第三篇:GCC 中对寄存器的分配与使用

目的不够明确,结论不够具体
******************


不清楚作者的目的,只是顺便提示一下,还是重点关注,如果是重点关注,则缺乏详细说明。

试了一下作者的两段代码,细节好像和不太一致,但不影响结论:
发现编译器一般使用如下寄存器:
第1 类寄存器,第2 类寄存器的r28,r29,第3 类寄存器



本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

 楼主| 发表于 2014-4-8 17:07:57 | 显示全部楼层
本帖最后由 oldbeginner 于 2014-4-8 17:17 编辑
oldbeginner 发表于 2014-4-7 16:41
******************
第三篇:GCC 中对寄存器的分配与使用


***************************
第四篇:只有延时服务的协作式的内核

找到辅助资料
***************************


应该是第一个大考验,好在有一篇辅助理解的文章,http://blog.csdn.net/zhiyu520/article/details/1912790

就copy 这篇文章,边copy 边理解。
  1. int main(void){
  2.   TCN0Init();
  3.   OSRdyTbl=0;
  4.   OSTaskRunningPrio=0;

  5.   OSTaskCreate(Task0,&Stack[49],0);
  6.   OSTaskCreate(Task1,&Stack[99],1);
  7.   OSTaskCreate(Task2,&Stack[149],2);
  8.   OSTaskCreate(TaskScheduler,&Stack[199],OS_TASKS);
  9. //经过上面四个语句后,建立了四个任务,都处于就绪态
  10.   OSStartTask();
  11. }
复制代码


在上面的例子中,一切变得很简单:
1、三个正在运行的主任务,都通过延时服务,主动放弃对CPU的控制权。
2、在时间中断中,对各个任务的的延时进行计时,如果某个任务的延时结束,将任务重新在就绪表中置位。
3、最低级的系统任务TaskScheduler(),在三个主任务在放弃对CPU的控制权后开始不断地进行调度。如果
   某个任务在就绪表中置位,通过调度,进入最高级别的任务中继续运行。

先这样理解,利用相对运动,让 cpu 移动。


按函数执行顺序的详细流程文字说明:                                    

主函数main()
OSTaskCreate()                                             

函数建立了4个任务,分别是task0,task1,task2和TaskScheduler,在这个函数中,我们做了三件主要的事情
1、我们先把每个任务的函数地址分别压到它相对应的人工堆栈里面
2、压完了任务函数地址,我们就把人工堆栈的栈顶指针赋给TCB结构体数组里面的OSTaskStackTop中,
    保存住(其实就是为了后面的寻找相应任务的函数地址)
3、把每个任务都在任务就绪表的相应位置1,表示任务可以被调用得到CPU的能力之一。但其实还要靠任务的优先级,有
    点需要注意的是:任务就绪表中相应位越低,优先级越高,比如,bit0就比bit1低,bit0的那个任
    务优先级就高。优先级体现在任务调度函数OSSched()里面的任务调度for循环语句中。

首先,可以理解,如下




OSStartTask()                                 
  准备开始执行任务,因为函数里面“OSTaskRunningPrio=OS_TASKS;”这里(只是这里)根本就没需要考虑优先级的情况,
  所以第一个准备执行(还没开始)的任务是TaskScheduler。
  1. <font color="Green">//开始任务调度,从最低优先级的任务的开始</font>
  2. void OSStartTask()
  3. {
  4. //首先运行TaskScheduler任务
  5. OSTaskRunningPrio=OS_TASKS;

  6. //等号右边得到任务TaskScheduler的入口地址
  7. SP=TCB[OS_TASKS].OSTaskStackTop+17;

  8. //ret和reti,它们都可以将堆栈栈顶SP的两个字节弹出来送入程序计数器PC中
  9. __asm__ __volatile__( "reti" "\n\t" );
  10. }
复制代码
第二句SP=TCB[OS_TASKS].OSTaskStackTop+17;等号右边得到任务TaskScheduler的入口地址,接着赋给SP。
  给它干吗,当然是有用了,你还记得“2人工堆栈”里面有一句话“ 对于ret和reti,它们都可以将堆栈栈顶的
  两个字节弹出来送入程序计数器PC中,一般用来从子程序或中断中退出。”,显然,那就是等接着的第三句去完
  成他的使命了。呵呵,第三句"reti"一完事,你的avrCPU就可以去程序计数器PC里面取地址,而这个地址呢,
  就正是任务TaskScheduler的函数地址,哈哈,这才是正式开始执行第一个任务TaskScheduler。

现在,你看,我们已经不用再看主函数里面的函数了,因为以后也不会涉及到。以后的任务就是靠任务调度了。我们接着来看吧

刚才不是任务TaskScheduler是执行态嘛,这个任务又去反复不断调用OSSched函数,OSSched是个关键哦,这个函数在
这个“4只有延时服务的协作式的内核.”里面应该是最主要的,操作系统要靠调度,调度就是你妈,吃饭的时候,给
你吃饭你就可以吃,不给你吃你就那里先撑着,还给"TMD"按孩子从大到小(优先级)来分,不过不会相信你妈有那么偏心。

调度任务里面的调度函数OSSched()                 
OSSched()
  一大堆PUSH指令
  这里的一大堆PUSH汇编指令是为了保存一些通用积存器寄存器,也就是保护现场,不懂的,先得看看操作系统里面的书(看不懂《操作系统》,老师说去看一个小程序RTOS;RTOS没看懂,让你去看《操作系统》,没学问的孩子像个皮球)。


中断就是这样处理的,和这个差不多。刚才我们不是运行了第一个任务TaskScheduler吗,这里保存就是任务TaskScheduler
  的现场,保存了大堆的寄存器。需要注意的是,这里PUSH的压可不是象建立任务OSTaskCreate()里面的把寄存器压到
  每人任务相应的人工堆栈里面,而是压到avr芯片里面专门定义的一片堆栈里面。

  接着TCB[OSTaskRunningPrio].OSTaskStackTop=SP;呵呵,你看了这里的SP是区别于人工堆栈里面的栈顶指针,一旦
  这里的SP赋给了TCB[OSTaskRunningPrio].OSTaskStackTop,那么相应的,此任务的OSTaskStackTop原本指向人工堆
  栈的栈顶指针就再也找不到,看OSTaskCreate()仔细对照一下吧。现在呢,我们OSTaskStackTop保存是正在运行的任务
  的栈顶指针。其实保存SP也应该算是保存现场的一部分工作。

先休息一下,TCB 还没有理解。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

 楼主| 发表于 2014-4-9 21:12:29 | 显示全部楼层
oldbeginner 发表于 2014-4-8 17:07
***************************
第四篇:只有延时服务的协作式的内核

*************************
先来一个中断

给51 DIY超轻量级多任务操作系统
**************************


http://www.amobbs.com/thread-1398508-1-1.html

AVR 的RTOS 内容 相对 我的基础 还是有 跳跃,好在又找到了好资料,先补一下再继续AVR RTOS。

这个系统 更简单,更好理解,去掉了 汇编 和人工堆栈,一下就理解了。

系统分为两部分,
协作式 任务核心部分                                 
只有 10 行









以下为测试代码                                                





(下面基本 copy paste)

现在来看看这个多任务系统的原理:                                                

这个多任务系统准确来说,叫作"协同式多任务".



所谓"协同式",指的是当一个任务持续运行而不释放资源时,其它任务是没有任何机会和方式获得运行机会,除非该任务主动释放CPU.
在本例里,释放CPU是靠task_switch()来完成的.task_switch()函数是一个很特殊的函数,我们可以称它为"任务切换器".
要清楚任务是如何切换的,首先要回顾一下堆栈的相关知识.

有个很简单的问题,因为它太简单了,所以相信大家都没留意过:
我们知道,不论是CALL还是JMP,都是将当前的程序流打断,请问CALL和JMP的区别是什么?
你会说:CALL可以RET,JMP不行.没错,但原因是啥呢?为啥CALL过去的就可以用RET跳回来,JMP过去的就不能用RET来跳回呢?

很显然,CALL通过某种方法保存了打断前的某些信息,而在返回断点前执行的RET指令,就是用于取回这些信息.
不用多说,大家都知道,"某些信息"就是PC指针,而"某种方法"就是压栈.
很幸运,在51里,堆栈及堆栈指针都是可被任意修改的,只要你不怕死.那么假如在执行RET前将堆栈修改一下会如何?往下看:
当程序执行CALL后,在子程序里将堆栈刚才压入的断点地址清除掉,并将一个函数的地址压入,那么执行完RET后,程序就跳到这个函数去了.
事实上,只要我们在RET前将堆栈改掉,就能将程序跳到任务地方去,而不限于CALL里压入的地址.

重点来了......                                                                                                 
首先我们得为每个任务单独开一块内存,这块内存专用于作为对应的任务的堆栈,想将CPU交给哪个任务,只需将栈指针指向谁内存块就行了.
接下来我们构造一个这样的函数:

当任务调用该函数时,将当前的堆栈指针保存一个变量里,并换上另一个任务的堆栈指针.这就是任务调度器了.

OK了,现在我们只要正确的填充好这几个堆栈的原始内容,再调用这个函数,这个任务调度就能运行起来了.
那么这几个堆栈里的原始内容是哪里来的呢?这就是"任务装载"函数要干的事了.

在启动任务调度前将各个任务函数的入口地址放在上面所说的"任务专用的内存块"里就行了!对了,顺便说一下,这个"任务专用的内存块"叫作"私栈",私栈的意思就是说,每个任务的堆栈都是私有的,每个任务都有一个自已的堆栈.

话都说到这份上了,相信大家也明白要怎么做了:                              

1.分配若干个内存块,每个内存块为若干字节:
这里所说的"若干个内存块"就是私栈,要想同时运行几少个任务就得分配多少块.而"每个子内存块若干字节"就是栈深.记住,每调一层子程序需要2字节.如果不考虑中断,4层调用深度,也就是8字节栈深应该差不多了.

unsigned char idata task_stack[MAX_TASKS][MAX_TASK_DEP]

当然,还有件事不能忘,就是堆指针的保存处.不然光有堆栈怎么知道应该从哪个地址取数据啊
unsigned char idata task_sp[MAX_TASKS]

上面两项用于装任务信息的区域,我们给它个概念叫"任务槽".有些人叫它"任务堆",我觉得还是"槽"比较直观

对了,还有任务号.不然怎么知道当前运行的是哪个任务呢?
unsigned char task_id
当前运行存放在1号槽的任务时,这个值就是1,运行2号槽的任务时,这个值就是2....

2.构造任务调度函函数:
void task_switch(){
        task_sp[task_id] = SP;//保存当前任务的栈指针

        if(++task_id == MAX_TASKS)//任务号切换到下一个任务
                task_id = 0;

        SP = task_sp[task_id];//将系统的栈指针指向下个任务的私栈.
}


3.装载任务:
将各任务的函数地址的低字节和高字节分别入在
task_stack[任务号][0]和task_stack[任务号][1]中:

为了便于使用,写一个函数:  task_load(函数名, 任务号)

void task_load(unsigned int fn, unsigned char tid){
        task_sp[tid] = task_stack[tid] + 1;
        task_stack[tid][0] = (unsigned int)fn & 0xff;
        task_stack[tid][1] = (unsigned int)fn >> 8;
}

4.启动任务调度器:
将栈指针指向任意一个任务的私栈,执行RET指令.注意,这可很有学问的哦,没玩过堆栈的人脑子有点转不弯:这一RET,RET到哪去了?嘿嘿,别忘了在RET前已经将堆栈指针指向一个函数的入口了.你别把RET看成RET,你把它看成是另一种类型的JMP就好理解了.

SP = task_sp[任务号];
return;

做完这4件事后,任务"并行"执行就开始了.你可以象写普通函数一个写任务函数,只需(目前可以这么说)注意在适当的时候(例如以前调延时的地方)调用一下task_switch(),以让出CPU控制权给别的任务就行了

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2014-4-10 00:03:13 来自手机 | 显示全部楼层
LZ很努力啊,支持你,可别中途太监了。

出0入0汤圆

发表于 2014-4-28 11:44:06 | 显示全部楼层
路过路过 顺便看看  顶

出0入0汤圆

发表于 2014-6-26 22:25:31 | 显示全部楼层
顶你,再顶你!

出0入0汤圆

发表于 2014-9-11 12:41:47 | 显示全部楼层
有没有跑过操作系统额
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-5-11 03:09

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

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