51下是简单的任务调度
工程下载地址为:http://download.csdn.net/detail/toraloo/5685109
此工程实现功能是为3个任务的切换运行与数码管动态显示,任务1:扫描获取按键值,然后通过串口发出对应的数据;任务2:控制led闪烁;任务3:控制蜂鸣器报警。
数据结构关联,如图1所示。Task_stack块由task1_stack、task2_stack、task3_stack三个数组组成,它们结构为0号元素保留,1号元素保存函数入口地址低8位,2号元素保存函数入口地址高8位。Task_sp块中的task_sp每个元素保存taskn_stack的栈元素(即数组最后位元素)。Funcation_address_map块为各函数入口地址。
图 1
图上Task_stack块数据与Task_sp映射关系通过如下两个宏实现:
#define SAVE_ENTRY do { \task1_stack[1] = (unsigned int)scan_button & 0xff; \task1_stack[2] = (unsigned int)scan_button >> 8; \task2_stack[1] = (unsigned int)flash_led & 0xff; \task2_stack[2] = (unsigned int)flash_led >> 8; \task3_stack[1] = (unsigned int)control_bee & 0xff; \task3_stack[2] = (unsigned int)control_bee >> 8; \
} while (0)
#define SAVE_SP do { \task_sp[0] = task1_stack; \task_sp[0] += 2; \task_sp[1] = task2_stack; \task_sp[1] += 2; \task_sp[2] = task3_stack; \task_sp[2] += 2; \
} while (0)
接下来是关于函数调用与返回时,对于这次设计需要关心的部分进行讲述。当发生一个函数调用时(lcall/call),有如下至关重要的几步:
1. SP<--SP+1
2. (SP)<--PC低8位
3. SP<--SP+1
4.(SP)<--PC高8位
而当发生一个函数的返回时(ret/reti):
1. PC高8位<--SP
2. SP<--SP-1
3. PC低8位<--(SP)
4. SP<--SP-1
(注:在此只讲述了PC与SP在函数调用/返回的变化,实际设置一个调度器的话还要考虑其他变量的保存与恢复)
如上所述,即可得知要使函数真正实现调用与退出则主要关系到PC的变化,而这两个过程中影响PC变化的便是SP,那么我们人为的修改SP的值,即可掌控PC的跳转的方位了,因此我们的任务切换函数就可设计为如下样子:
void schedule(unsigned int task_number)
{CLI; // 关中断
// RELOAD_ENTRY; // 重装函数入口地址
// RESAVE_SP; // 重装栈顶地址task_sp[exec_num] = SP; // 保存上个函数入口地址exec_num = task_number; // 运行任务号切换SP = task_sp[exec_num]; // 得到对应函数入口地址STI; // 开中断
}
上述的任务切换函数还算是合理的最起码将该段设置为了临界区,避免了切换时中断的干扰。而我所发的工程代码下,则不是这样设计,而是去掉了临界保护,加入RELOAD_ENTRY和SAVE_SP,如下所示:
void schedule(unsigned int task_number)
{
// CLI; // 关中断RELOAD_ENTRY; // 重装函数入口地址RESAVE_SP; // 重装栈顶地址task_sp[exec_num] = SP; // 保存上个函数入口地址exec_num = task_number; // 运行任务号切换SP = task_sp[exec_num]; // 得到对应函数入口地址
// STI; // 开中断
}
取消临界保护为加入两个数据重载后整个切换函数执行时间加长,加入临界保护影响了中断的响应性;而加入两个数据重载的原因为,整个工程中使用了两个中断(timer0和uart),中断服务程序使用寄存器空间与全局变量存储空间发生冲突,出现全局变量数据的改写,所以每次切换时则进行数据的重装。(注:为解决上述空间冲突,1. 使用过指定中断程序工作寄存器组,结果无法解决;2. 使用人工指定全局变量存取地址,即_at_关键字的使用,结果未能解决;3. 使用xdata声明全局变量,结果未能成功,取值过程使用的是dptr指针,值传给SP时不正确,不知是什么原因。)
还有则是在工程中init_task_stack()函数,即使两个数据重装操作的包装。那为何不在schedule()函数中调用它,而是采用直接在schedule()函数中使用两个数据重装的宏,原因为在函数的调用和返回中都会涉及到PC和SP的修改,从而会用程序运行之不可预知的内存地址处。
整个系统的时钟片为5ms,实现数码管动态显示,与一些参量的改变。实现代码如下:
void timer0_interrupt(void) interrupt 1
{TH0 = 0xEE;TL0 = 0x00;my_dat.flag = !my_dat.flag;if (!my_dat.flag) {P2 = 0xfe;P0 = my_dat.recv_data[0];} else {P2 = 0xfd;P0 = my_dat.recv_data[1];}g_count.dly_cnt--;g_count.swap_time--;if (BEE_LED_ON == g_count.led_bee_flag) {g_count.dly_led--;g_count.dly_bee--;}
}
函数中涉及到的g_count.swap_time、g_count.dly_led与g_count.dly_bee,实际上可以设计到对应任务的TCB中作为私有成员进行处理,这样可减少程序的内聚性。尤其是swap_time这个变量,它充当了正统操作系统中的任务享有的运行时间片的角色。在工程中我未设计TCB这个数据结构。
系统使用的第二个中断为串口中断,功能为实现接收上位机发送的两位数据,并判断和改变想要改变的标志位。中断服务程序如下:
void uart_interrupt(void) interrupt 4
{if (RI) {RI = 0;my_dat.recv_data;[my_dat.recv_cnt] = SBUF;my_dat.recv_cnt++;if (my_dat.recv_cnt > 1) {my_dat.recv_cnt = 0;}if ((my_dat.recv_data[0] == 0xC0) && (my_dat.recv_data[1] == 0xC0)) {g_count.led_bee_flag = BEE_LED_ON;} else {g_count.led_bee_flag = BEE_LED_OFF;}} else {while (!TI);TI = 0;}
}
(注:中断服务程序中最好不要自己指定使用的寄存器组,因为这样更加容易造成数据的破坏。)
附上网上找到的关于全局变量存储空间与中断服务程序使用空间冲突的解决方案(都尝试过,但均未见效果。):
我查了查关于全局变量的使用,看到有个帖子说到全局变量会跟中断用的寄存器组发生冲突,也就是全局变量的地址会被KEIL分配到中断用的寄存器组里。
下面是我从网上搜集到的关于全局变量使用的注意点:
1. 全局变量要少用,能不用就不用;
2. 在主程序外面只对全局变量做声明,不做定义;
3. 使用中断时,要加上使用的寄存器组;
4. 裸露的全局变量全部用结构体封装起来;
5. 中断与主程序共享全局变量,用函数(含临界段)封装起来;
6. 使用全局变量出错时,可以给它指定一个地址(注意:不要和当前使用的寄存器组发生冲突);
7. 将大部分全局/静态变量(特别是数组)定义到xdata段中;
8. 有些变量可能会随时改变,例如:在中断中赋值的变量,以及硬件修改的输入/输出寄存器等,在程序中使用这些变量时,最好加上“volatile”关键词,告诉C51编译器:
(1)不要优化该变量,例如相连的两个相同的赋值语句,第二个不要优化掉,因其处于不同“时刻”赋值结果可能不一样。
(2)每次取该变量值时要从其实际地址的寄存器取,不要从内存的副本中取,因其值可能随时比改变了。
51下是简单的任务调度相关推荐
- 基于单片机的简单的任务调度器
近来工作之余,研究了一下APM的源码. APM源码连接https://pan.baidu.com/s/17Dg1oEJT_fj12DM1BmZWxA 发现源码中有一个简单的任务调度器,不太重要的任务都 ...
- python编辑器编程猫_编程猫Python编辑器 Mac版0.4.0 下载 - 51下载网
Tags: 编程工具 51下载网提供Python编辑器<编程猫Python编辑器 Mac版>0.4.0 下载,该软件为免费软件,文件大小为55.2 MB,推荐指数3颗星,作为国产软件中的顶 ...
- 基于51单片机的简单计算器
在上一篇中,我们已经说过了基于51单片机的简单拨号器,在下边,我们将写一个计算器程序,原理很简单,只需要在拨号器的基础上,算出拨号器所表示的数字,并进行计算即可. 代码如下: #include&quo ...
- [Android] Android MVP 架构下 最简单的 代码实现
Android MVP 架构下 最简单的 代码实现 首先看图: 上图是MVP,下图是MVC MVP和MVC的区别,在于以前的View层不仅要和model层交互,还要和controller层交互.而 ...
- 用C语言编写一个Linux下的简单shell程序
这是一个简单的C程序,展示了如何进行系统调用执行logout cd ls pwd pid rm mkdir mv cp等命令,这是一个简单的命令解释程序shell,其源代码如下: #include & ...
- nginx Win下实现简单的负载均衡(2)站点共享Session
快速目录: 一.nginx Win下实现简单的负载均衡(1)nginx搭建部署 二.nginx Win下实现简单的负载均衡(2)站点共享Session 三.nginx Win下实现简单的负载均衡(3) ...
- ubuntu 运行c++_06_Linux下VSCode简单编程(远程开发WSL_Ubuntu_18.04) | C语言入门
06_Linux下VSCode简单编程(远程开发WSL_Ubuntu_18.04) 本系列主题 Linux下C语言彩色控制台编程实践_基于gcc,gdb,VSCode,git和WSL_Ubuntu_1 ...
- artDialog对话框在PHP下的简单应用-artDialog弹出层篇
本教程使用的是artDialog 4.1.7版本,由于需要iframe的支持,所以选择这个版本,artDialog 5.0.3不支持iframe. 本教程是基于本站站长在网页设计写代码过程中与PHP页 ...
- linux上用的端口转发工具,linux下最简单好用的的端口转发工具
linux下最简单好用的的端口转发工具 解压安装 tar zxvf rinetd.tar.gz make make install 编辑配置 vi /etc/rinetd.conf 0.0.0.0 8 ...
最新文章
- 为什么会需要HTTPS?
- php调用shell执行scp,Shell中使用scp命令实现文件上传代码
- servlet跳转页面的几种方法
- java jdbc修改_java----jdbc(数据库的添加,删除,修改,更新)
- 使用TestContainers提高测试性能
- python 风玫瑰图_python之windrose风向玫瑰图的用法
- webpack4.0各个击破(5)—— Module篇
- CDATA不支持html,我应该在HTML5中使用(Should I use in HTML5?)
- vue引用echarts
- keras图像风格迁移
- Weights Biases的使用
- 小程序成为多社交平台引流利器
- 高考失利之后,属于我的大学本科四年
- 使用UltraISO制作U盘启动盘完整教程
- scite for php,SCITE配置系列
- 黑客攻破美一女孩房间安全摄像头并称自己是圣诞老人
- WaitForSingleObject 返回值为 WAIT_ABANDONED 的情况
- 新房装修|厨房台面给我做高了10公分,做饭不方便
- Android studio小能手之色卡对照表
- 【信奥赛一本通】1333:【例2-2】Blah数集(详细代码)
热门文章
- three.js案例解析之游戏帧碰撞检测
- 两种GPU计算平台:CUDA 与 OpenCL
- 艾顿系统服务器名称,艾顿系统设计方案详细分解.doc
- ACM-ICPC比赛随想——刘汝佳
- 程序洞穴生成六(Procedural Cave Generation)
- CT三维重建基本后处理方法
- 全球及中国智慧校园行业竞争格局与发展态势展望报告2022-2028年版
- 电脑端实现同时登陆两个微信
- CAD二次开发--像纬地与CASS程序一样双击桌面图标实现插件的自动挂载(不用netload也不用进入后输入挂载命令)
- 世界网络奇观!QQ论坛被可能都是“枪手”的人疯狂灌水