reayfei

飞思卡尔MC9S12单片机实时操作系统μC/OS-II 移植

0
阅读(8750)

第1章 绪论
1.1.开发背景
Freescale MC9S12 系列MCU 是以高速CPU12 内核为基础的微控制器系列,简称S12 系
列。典型的HC12 总线频率为8MHz,而典型的S12 总线频率为25 MHz。HC12 与S12 指令完
全兼容,故在很多场合统称为HCS12 系列微控制器。HCS12 是世界上第一款包含完整的模
糊逻辑指令的标准MCU,应用模糊控制指令可以简化控制系统,减少代码,加快程序运行
速度。
智能产品的设计人员可利用S12 系列微控制器低成本的FLASH 存储器,轻松实现以微
控制器为基础的远程升级、换代和现场进行快速再编程系统设计,可缩短嵌入式产品的设
计周期改善性能,同时亦能降低售后服务系统的整体成本。S12 微控制器已广泛应用于通
信、工业以及无数消费类电子产品中。
M9S12 单片机学习和应用开发平台开发在配套BDM 工具的支持下,实现对M9S12 单片
机各个功能模块的编程及调试。板载实验资源和接口如下:
 8 路LED ;6 位数码管;1 个蜂鸣器 ; 16*2 LCD 字符型液晶接口 ;
 4×4 个按键;
 RS232 串口通讯电路;RS485 接口电路;
 FLASH 在线编程,通过SCI 接口的在线系统编程;
 定时器;SPI 串行接口;A/D 模块接口;
 A/D 转换;PWM D/A 转换;
 BDM 下载接口 。
1.2.设计目标
实现μC/OS-II 移植到S12 单片机上。设计和实现一个学习和应用开发平台。该学习
系统又名S12 单片机开发工具包,它将MC9S12DG128 芯片中所具有的模块都引出,可以很
方便地进行各个模块的编程学习,与之学习套件提供规范的、丰富的汇编和C 语言源代码,
并附有详细注释。通过该开发板能够体会Freescale 系列MCU 的诸多优点,快速掌握M9S12
系列MCU 的编程方法。该开发板可与M9S12 BDM 调试器连接到了一起,使得在使用的时候
更为方便,节约了制作写入调试器的一些费用。
2
利用Metrowerks CodeWarrior 开发工具完成μC/OS-II 的移植代码的编写和测试。并通
过通信工具中的超级终端完成代码移植,其中要完成最终移植还要解决一些问题,要移植
μC/OS-11,目标处理必须满足以下要求:
(1)处理器的C 编译器能产生可重入代码;
(2)用C 语言就可以打开和关闭中断;
(3)处理器支持中断,并且能产生定时中断(通常在10~100Hz 之间);
(4)处理器支持足够的RAM,保存全局变量和作为多任务环境下的任务堆栈;
(5)处理器有将堆栈指针和其他CPU 寄存器读出和存储到堆栈或内存中的指令。
1.3.方案分析
μC/OS-II 是一个完整的、移植、固化、剪裁的占先实时多任务内核,而S12 单片机有
8KB 片内RAM,128KB 片内Flash,也就是说程序空间有128KB 拿几KB 来运行实时内
核没有问题所以μC/OS-II 完全可以移植到S12 单片机上并能正常运行。
进行μC/OS-II 的移植工作,一般需要遵循以下的几个步骤:
 深入了解所采用的系统核心;
 分析所采用的C 语言开发工具的特点;
 编写移植代码;
 进行移植的测试;
 针对项目的开发平台,封装服务函数
1
第2章 硬件描述
2.1. S12 微控制器的组成
MC9S12DG128 是Freescale 公司推出的S12 系列微控制器中的一款增强型16 位微控制
器。其集成度高,片内资源丰富,接口模块包括SPI、SCI、I 2 C、A/D、PWM 等。MC9S12DG128
微控制器采用增强型16 位S12CPU,片内总线时钟频率最高可达25MHz;片内资源包括8KB
RAM、128KB FLASH、2KB EEPROM;SCI、SPI、PWM 串行接口模块;PWM 模块可设置成4 路8
位或2 路16 位,可宽范围选择逻辑时钟频率;它还提供2 个8 路10 位精度A/D 转换器、
控制器局域网模块CAN 和增强型捕捉定时器,并支持背景调试模式(BDM)。
2.1.1 MC9S12DG128 的结构
MC9S12DG128 系统结构图如下:
图 1 MC9S12DG128 系统结构图
2
MC9S12DG128 系统结构大致可分为MCU 核心与MCU 外设两部分,对应于图的左和
右半边。
(1)MCU 核心
该部分包括MCU 的3 种存储器(FLASH、RAM、EEPROM);多电压调整器,包括
数字电路和模拟电路电源电压;具有单线背景调试接口(BDM)和运行监视功能的增强
S12CPU;程序存储器的页面模式控制;具有中断识别、读/写控制、工作模式等控制功能
的系统综合模块(SIM);可用于系统扩展的分时复用总线端口,其中A 口、B 口作为外扩
存储器或接口电路时的分时复用地址/数据总线,E 口的部分口可作为控制总线。
(2)MCU 外设
S12 外设部分包括:A/D 转换器(ATD0、ATD1),增强型定时与捕捉模块(ECT),串
行接口SPI、I 2 C、CAN、Byteflight 等接口是许多微控制器所没有的。
2.1.2 MC9S12DG128 的引脚功能
MC9S12DG128 有LQFP-112 和QFP-80 两种封装形式。采用QFP-80 封装的微控制器
没有引出用于扩展的端口,只引出了一个8 路A/D 接口。下面是MC9S12DG128 引脚分布
图:
图 2 MC9S12DG128 引脚分布图
S12MCU 的每一种接口大都具有双重或多重功能,即通用I/O 功能和特殊接口功能。
3
在单片机模式下,A 口、B 口和部分E 口也可以用作通用I/O 接口。如果所有接口工作在
通用I/O 方式下,那么I/O 口将达到63 个。这些双重功能的I/O 口本身及其控制逻辑完全
集成在MCU 内部,其体积、功耗、可靠性、应用简单方便程度都与用户自行扩充的I/O
口有着重要区别。
2.2. S12 储存器
微控制器存储器是一种半导体电路,用来存放程序和数据。存储器可以是单独的片外
芯片,也可以集成在微控制器内部,是微控制器系统的重要组成部分。
MC9S12DG128 采用普林斯顿总线结构,程序存储器、数据存储器和I/O 端口为统一
编址方式,总的地址空间为64KB,内部集成外设的管理接口,也占用地址空间,因此实
际可用的程序和数据空间不足64KB。这些存储器和内部集成模块的地址分配并不是固定
不变的,用户自己可以重新分配,这就需要了解有关的地址空间映射方面的内容。下面是
存储器空间分配图。
图 3 存储器空间分配图
当 MC9S12DG128 的存储分配出现地址重叠时,S12MCU 内部的优先级控制逻辑会自
动屏蔽级别较低的资源,保留级别最高的资源。
2.3. S12 串行通信模块
4
串行通信是微控制器与外围设备或其他计算机之间通信的一种重要渠道,计算机或微
控制器与外界进行数据交换称为通行。下面介绍一下SCI 串行通信接口,因为下面设计中
要用到。
SCI 串行通信接口是一种异步串行通信系统,它是计算机最常用的通信接口之一。S12
的SCI 是全双工异步串行通信接口,通常用于微控制器与其他计算机、调制解调器等设备
之间的通信,在S12 微控制器中集成了2 个SCI 串行通信模块。
S12MCU 的SCI 功能特点:双线串行接口;标准NRZ 格式;硬件自动生成奇偶标志;全
双工操作;独立的波特率产生逻辑;独立的发送器和接收器允许控制位;通信过程可采用
中断驱动机制;具有回送方式,方便了调试;可以监视发送器的输出,实现通信过程的自
诊断。下面就是SCI 的内部结构图和SCI 模块程序流程图:
图4 SCI 内部结构
5
开始
SCI 初始化
发送数据
清标志位
发送完毕?
结束
进入中断
清标志位
处理接受数据
中断返回
图 5 SCI 模块程序流程图
6
第3章 μC/OS-II内核结构
3.1 μC/OS-II 的文件结构
μC/OS-II 的文件结构如下图所示,分为4 个部分,应用软件层位于最顶端,由用户自
行编写。与处理器无关代码即是内核部分,包括了所有的内核代码和系统功能代码,
μC/OS-II 配置定义了所有与内核裁剪有关的宏定义及主头文件。μC/OS-II 移植包含了与处
理器相关的代码。
图6 μC/OS-II 的文件结构
μC/OS-II 是一种专门为微处理器设计的占先式实时多任务操作系统,具有源代码公
开、可移植性和可裁减性强、代码可固化、稳定性和可靠性高等特点。其内核主要提供任
务管理、内存管理、时间管理等服务,系统最多可以支持64 个任务(8 个留于系统),每个
任务均有自己独立的优先级。由于内核为占先式的,因此总是运行优先级最高的任务。系
统提供了丰富的函数可供调用,实现任务间的通信和切换。μC/OS-II 的大部分代码都是
应用代码(用户代码)
μC/OS-II
(与处理器无关代码)
OS_CORE.C
OS_FLAG.C
OS_MBOX.C
OS_MUTEX.C
OS_Q.C
OS_SEM.C
OS_TASK.C
OS_TIME.C
μC/OS-II.C
μC/OS-II.H
μC/OS-II 配置
(与应用相关)
OS_CFG.H
INCLUDES.H
μC/OS-II 移植(处理器相关代码)
OS_CPU.H
OS_CPU_A.ASM
OS_CPU_C.C
CPU
定时器
(Timer)
软件
硬件
7
使用标准的ANISC 编写的,只有与处理器相关的一部分代码使用汇编语言。因此具有极
强的移植性,在大多数8 位、16 位和32 位处理器上都能稳定的运行。
3.2. μC/OS-II 的内核结构
μC/OS-II 的内核结构主要是临界段、任务、任务状态、任务控制块、就绪表、任务调
度、任务级的任务切换、给调度上锁和开锁、空闲任务、统计任务、μC/OS-II 中的中断、
时钟节拍等。其中临界段的作用是为了保护主要的程序运行期间不被中断;任务是一个无
限循环,在 μC/OS-II 中可以管理多达64 个任务,但是实际可用的只有56 个用户任务;
任务状态可以用一个图将其表达出来如下图,在此就不多用文字进行表达。
图 7 任务状态
任务控制块是一个数据结构,当任务的CPU 使用权被剥夺时,μC/OS-II 用它来保存该
任务的状态,当任务重新得到CPU 使用权时,任务可控制块能确保任务从当时被中断的那
一点丝毫不差地继续执行;就绪表就是就绪的任务存放的地方;任务调度就是任务优先级
最高的先运行,接着运行它下面的任务,这一工作的完成是由调度器完成的;任务级的切
换是在任务运行的过程中由调度器确定更重要的任务运行;给调度器上锁和开锁是用于禁
止任务调度,直到任务完成后,调用开锁函数,调用上锁函数是保持对CPU 的使用权,尽
管有个优先级更高的任务进入了就绪态;空闲任务是μC/OS-II 总要建立一个空闲任务,
这个任务在没有其他任务进入就绪态时投入运行,永远设为最低优先级,不可能被应用软
件删除;统计任务该任务主要是统计运行时间的任务;μC/OS-II 中的中断是CPU 处理外
8
部突发事件的一个重要技术,它能使CPU 在运行过程中对外部事件发出的中断请求及时
地进行处理,处理完成后又立即返回断点,继续进行CPU 原来的工作,CPU 为了处理并
发的中断请求,规定了中断的优先权。中断优先权由高到低的顺序是: 除法错、溢出中
断、软件中断,不可屏蔽中断,可屏蔽中断,单步中断;在μC/OS-II 中需要提供周期
性信号源,用于实现时间延时和确认超时,就要用到时钟节拍。以上是对μC/OS-II 内核
结构做了非常简单的介绍。
1
第4章 μC/OS-II的移植
实现μC/OS-II 的移植,需要做两方面的工作:一是重新定义内核的大小和功能;二是
内核编写与硬件相关的代码。
配置文件OS_CFG.C 需要根据应用配置,主要作用是确定μC/OS-II 提供的系统功能函
数中,应用程序用哪些、不用哪些,这个文件移植时要修改。
与 CPU 类型有关的代码文件主要有3 个:OS_CPU.H、OS_CPU_A.ASM 和
OS_CPUC.C。OS_CPU.H 文件定义用于特定CPU 的数据类型、定义相关的宏。
OS_CPU_A.ASM 是用汇编语言写的与硬件有关的代码,OS_CPU_C.C 文件是用C 交叉汇
编语言写的与硬件有关的代码。与硬件相关的还有产生时钟节拍的定时中断,它是来自单
片机内部但并非来自CPU12 内部,这部分代码在下面会介绍。
4.1. 新定义内核的大小和功能
公共头文件INCLUDES.H,这个文件会被所有的C 源程序引用。下面是该文件的代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "os_cpu.h"
#include "os_cfg.h"
#include "ucos_ii.h"
前四个头文件是C 函数库、预定义的类型等,和移植没有关系,是否一定要加取决于
所用编译器。后三个头文件必须被引用。
需要根据应用修改的文件是OS_CFG.H,这个文件用于配置内核的属性。
在开始移植时,为了简单,首先应尽量减少内核中不必要的模块,这里去掉了人为将
任务挂起、唤醒和删除等扩展功能,但仍然支持任务的创建和管理,也保留了信号量模块,
以用于任务间的通信。
首先是文件OS_CPU.H,用于设置与微控制器的CPU 核心相关的属性,包括各种数据
类型对应的存储长度等等。
4.2. OS_CPU.H
2
在这个文件中定义了数据类型、堆栈宽度和增长方向以及任务调度函数
OS_TASK_SW() 的宏定义。其中用的宏定义是: #define OS_SAVE_SP() ;
if(OSIntNesting==1){asm ldx OSTCBCur; asm sts 0,x;},它可以彻底解决进入和脱离临界
段代码问题,给凡是有临界段代码宏调用的函数都增加一个局部变量,用以保存进入临界
段代码前的CCR 寄存器。这个局部变量在脱离该函数后会自动释放掉。缺点是,要给很
多函数增加这个局部变量。
4.3. 编写与硬件相关的代码
接下来需要编写与硬件相关的代码。这部分代码可以用C 语言,也可以用汇编语言。
移植中与硬件相关的文件中最重要的是OS_CPU_C.C 文件和汇编文件OS_CPU_A.ASM。
在这次移植中将这两个文件合为一个文件,它们的文件名就为OS_CPU_C.C,这个文件中
有以下几个函数:
 中断服务子程序OSTicISR();
 任务堆栈初始化函数OSTaskStkInit();
 让优先级最高的就绪态任务开始运行函数OSStartHighRdy();
 中断级任务切换函数OSIntCtxSw();
 任务级任务切换函数OSCtxSw();
 相关接口函数。
下面介绍OS_CPU_C.C 具体的程序代码。
在本移植中,时钟节拍由芯片内的实时时钟中断产生,该模块的中断标志需要在时钟
节拍中断服务子程序OSTickISR()中清除,因此定义包含该中断标志的寄存器CRGFLG。
#define OS_CPU_GLOBALS
#include "includes.h"
#define CRGFLG (*((volatile unsigned char*)(0x0037)))
4.3.1 中断服务子程序OSTickISR()
时钟节拍中断发生时,CPU12 会自动把CPU 寄存器推入堆栈,但是并不包括存储页
面寄存器PPAGE 也推入堆栈,然后是清中断标志。如果CPU12 构成的单片机系统的寻址
范围不超过64KB,存储页面寄存器没有用。将存储页面寄存器PPAGE 入栈、出栈的语句
可以去掉。
由于时钟节拍中断可服务子程序可能激活一个优先级高于当前被中断任务的优先级
3
的任务,例如,那个高优先级的任务曾经将自身延迟了若干时钟节拍,当前的时钟节拍中
断服务子程序发现延迟时间到,于是让这个进入就绪态的任务立即运行。其实现过程是,
时钟节拍中断服务子程序要连续调用:OSIntEnter();OSTimeTick();OSIntExit();这3 个
C 语言写的函数。OSIntEnter()通知μC/OS-II 进入中断服务子程序了。OSTimeTick()给要求
延迟若干时钟节拍的任务延迟计数器减1,减1 后为0 则该任务进入就绪态。OSIntExit()
函数告诉μC/OS-II 时钟节拍中断服务子程序结束。注意,如果这是没有更高优先级的任务
进入了就绪态,中断服务子程序接着执行OSIntExit()下面的恢复页面寄存器、中断返回语
句,中断返回时CPU12 会自动恢复CPU 寄存器进入中断服务子程序前的状态,接着运行
被中断了的任务;如果有任务进入就绪态,就做任务调度,让更高优先级的任务运行。
OSIntExit()函数调用后面的恢复页面寄存器、中断返回的指令就执行不到了。OSIntExit()
函数会调用中断级的任务切换函数OSIntCtxSw()做任务切换。OSIntCtxSw()函数做任务切
换,从即将运行的任务栈中弹出新任务的PPAGE 号,然后通过RTI 指令让新任务运行。
void OSTickISR(void)
{
asm{
ldaa $30 //save ppage to stack
psha
}
OSIntEnter();
OS_SAVE_SP();
CRGFLG &=0xEF; // clear the interrupt flag
OSTimeTick();
OSIntExit(); // exit interrupt and task switch
asm{
pula
staa $30 //restore ppage from stack
nop
rti
}
}
4.3.2 任务堆栈初试化函数OSTaskStkInit()
OSTaskCreate()和OSTaskCreateExt()通过调用OSTaskStkInit(),初始化任务的栈结构;
4
因此,堆栈看起来就像中断刚发生过一样,所有寄存器都保存在堆栈中。
这个函数虽然是用C 语言写的,但是这是一个与CPU 硬件有关的函数。这个函数初
试化任务的堆栈,由建立任务的函数OSTaskCreat()或扩展的建立任务函数OSTaskCreatExt()
调用:
INT8U OSTaskCreate (void (*task)(void *pd), void *pdata, OS_STK *ptos, INT8U prio)
INT8U OSTaskCreateExt (void (*task)(void *pd), void *pdata,OS_STK*ptos,INT8U
prio,INT16U id,OS_STK *pbos,INT32U stk_size,void *pext,INT16U opt)
建立任务的函数或扩展的建立任务的函数调用这个初试化任务堆栈的函数,建立任务
的函数带有4 个形式参数,扩展的建立任务的函数有8 个参数。这些参数在建立任务时传
递给任务控制快、任务堆栈等。其中pdata 用于向任务传递参数。利用了这个参数将页面
寄存器PPAGE 参数传给建立的任务。
下面是初始化堆栈结构,pdata 参数传递给堆过程:
μC/OS-II 设定这个参数的目的是,将pdata 作为一个指向某数据结构的指针,将所有
想带入的参数都定义在这个结构中,这就需要为每个任务定义一个数据类型为这种数据结
构的全局变量。在main.h 这个用户文件中,说明了一个新的数据结构类型TASKDATA,
并且给每个任务定义了一个该数据结构类型的全局变量,虽然该结构中只有一个单字节的
变量:
堆栈指针(5)
堆栈增长方向
低端内存
已保存的处理器寄存器
中断返回地址
处理器状态字
任务起始地址
pdata
高端内存
(4)
(3)
(2)
(1)
图8 初始化堆栈结构,pdata 参数传递给堆栈
5
typedef struct{
INT8U PPAGE;
}TASKDATA;
EXT TASKDATA TaskStartData;
EXT TASKDATA Task1Data;
EXT TASKDATA Task2Data;
目前,这个结构中只有一个字节的变量PPAGE,就是在建立任务时要传给任务的值。
而传递是通过传递指向这个结构的指针完成的。如果需要传递更多的变量到任务中,只要
在上述结构中增加变量就可以了。
在定义任务时,写成:
void Task1(TASKDATA* pdata);
这里,pdata 是指向要传递的参数数组的指针。
其实向任务传参数,总是可以用全局变量传递的,这里只需要传递一个字节的值,如
果只传一个参数,对目前所使用的编译器,这个参数在D 寄存器中,不必另设全局变量。
可以把pdata 当作一个参数传入,不必定义上面的数据结构,也不必为每个任务开辟一个
结构类型的全局变量,直接取pdata 值当作PPAGE 放在D 寄存器中。这样做虽然不很正
规,少占用内存,缺点是写任务函数就不能有形式参数了。
在定义任务时,只能写成:
void Task1(void);
任务开始运行并不是通过被某一个C 程序调用而开始的,是μC/OS-II 内核做任务调度
时,让这个任务运行这个任务才开始运行的,故用C 语言中标准的参数传递方法是传递不
了参数的。要传递其他参数时,请仔细研究以下下面的代码。由于每个任务都是一个单独
的文件,可以单独编译,而在链接时,编译器会发现每个任务的函数都没有被调用过,编
译器可能根本不把没有用到的函数链接进去。
void *OSTaskStkInit (void (*task)(void *pd), void *pdata, void *ptos, INT16U opt)
{
INT16U *stk;
stk = (INT16U *)ptos; // Load stack pointer
*--stk = opt; // opt There is one byte blank
*--stk = (INT16U)(task); // PC for use of opt in task
*--stk = (INT16U)(task); // PC
*--stk = (INT16U)(0x1122); // Y
6
*--stk = (INT16U)(0x3344); // X
((INT8U *)stk)--; // Only one byte needed for A
*(INT8U *)stk = (INT8U)(((INT16U)pdata)>>8); // A
((INT8U *)stk)--; // Only one byte needed for B
*(INT8U *)stk = (INT8U)(pdata); // B
((INT8U *)stk)--; // Only one byte needed for CCR
*(INT8U *)stk = (INT8U)(0x00); // CCR
((INT8U *)stk)--; // Only one byte needed for PPAGE
*(INT8U *)stk = *(INT8U *)pdata; // PPAGE
return ((void *)stk);
}
4.3.3 让优先级最高的就绪态任务开始运行OSStartHighRdy()
这一段代码是用汇编语言写的,将CPU 的堆栈指针SP 的值,改成优先级最高的就绪
态任务的堆栈指针的值,该值是其任务控制块的第一个参数;然后将该任务的状态字由非
运行态“0”,改为运行态“1”;恢复任务代码所在的存储器页面的值以换入即将运行任务
的代码,换出被挂起的任务的代码;然后执行中断返回指令RTI 以开始运行这个任务。前
面的接口函数调用,可以插入一些自定义的代码。
void OSStartHighRdy(void)
{
OSTaskSwHook(); // Call Hook function
asm{
ldx OSTCBCur // Load the value in OSTCBCur or the TCB's address to x
lds 0,x // Load the value pointed by OSTCBCur to sp
ldaa OSRunning
inca // OSRunning = 1
staa OSRunning
pula
staa $30 //restore ppage from stack
nop
rti
}
}
7
4.3.4 任务级任务切换函数OSCtxSw()
任务级的切换是通过执行软中断指令来实现的。OSCtxSw()实际上就是软中断服务子
程序,软中断服务子程序的向量地址必须指向OSCtxSw()。
如果当前任务调用μC/OS-II 提供的功能函数OSSched(),并由此推断出当前任务不再
是需要运行的最重要的任务了。OSSched()先将最高优先级任务的地址装到OSTCBHighRdy
中,在通过调用OS_TASK_SW()来执行软中断指令。注意,变量OSTCBCur 已经包含了指
向当前任务的任务控制块(OS_TCB)的指针。软中断指令会强制将CPU12 的除了PPAGE
寄存器之外的所有寄存器保存到当前任务的堆栈中,并使处理器执行软中断服务子程序
OSCtxSw()。故在软中断服务子程序中,必须:
 保存被挂起的任务的页面寄存器的值,即将PPAGE 推入当前任务栈;
 保存即将被挂起任务的堆栈指针到它的任务控制块中;
 需要时在这里调用用户接口函数;
 由于新的当前任务是将要重新开始运行的那个任务,任务切换代码将优先级最高
的就绪态任务的任务控制块指针OSTCBHighRdy 赋给当前运行任务的优先级OSPrioCur;
 换入新任务的页面寄存器,即换入新任务代码,换出挂起任务的代码;
 运行中断返回指令RTI,从OS_TCB 中找出将要重新开始运行的那个任务的堆栈
指针,装入CPU 的SP 寄存器中,新任务恢复运行。
void OSCtxSw(void)
{
asm{
ldaa $30 //save ppage to stack
psha
ldx OSTCBCur // Get the TCB's address
sts 0,x // Save the sp to TCB's first word
}
OSTaskSwHook(); // Call Hook function
OSTCBCur = OSTCBHighRdy; // Change OSTCBCur and OSPrioCur
OSPrioCur = OSPrioHighRdy;
asm{
ldx OSTCBCur // Get the new task's TCB's address
lds 0,x // Load the new task's sp to sp register from its TCB
pula
8
staa $30 //restore ppage from stack
nop
rti
}
}
4.3.5 中断级任务切换函数OSIntCtxSw()
中断级的任务切换和任务级的任务切换非常相似,在任务级作切换,用的是软中断。
中断级的任务切换是这样发生的,在每次中断服务结束时,中断服务子程序会调用
OSIntExit(),如果OSIntExit()发现中断激活了更高优先级的任务,则调用中断级的任务切
换函数。由于中断时包括PPAGE 寄存器的所有CPU 寄存器都保存过了,故不需要再保存。
下面是中断级任务切换函数OSIntExit()的代码:
void OSCtxSw(void)
{
OSTaskSwHook(); // Call Hook function
OSTCBCur = OSTCBHighRdy; // Change OSTCBCur and OSPrioCur
OSPrioCur = OSPrioHighRdy;
asm{
ldx OSTCBCur // Get the new task's TCB's address
lds 0,x // Load the new task's sp to sp register from its TCB
pula
staa $30 //restore ppage from stack
nop
rti
}
}
4.3.6 相关接口函数
相关接口函数目前都是空函数,留给用户应用程序,只有当用户希望向内核添加用户
定义的新功能时才会用到。
4.4. 产生时钟节拍中断
最后还需要编写利用单片机内的定时器产生时钟节拍中断服务程序。μC/OS-II 要求微
9
控制器提供一个简单的时钟,用于任务的延时等功能。对于S12 单片机,最简单的产生时
钟节拍中断的方法是利用时钟产生器内的实时时钟系统时钟(RTI)模块直接分频得到。
1. 实时中断控制寄存器
实时中断控制寄存器RTICTL 的默认地址为$3B,有效位为7 位,最高位b7 无效,恒
为0,复位时寄存器所有位清0,分频器不工作。低7 位为有效分频因子,分为低4 位m
和高3 位n。分频系数为(m+1)*2 n+9 ,m、n 不得为0。(m+1)的范围是1-16,n 的范围是
0-7,故2 n+9 的范围在2 9 -216 。
2. 时钟产生器中断允许寄存器
时钟产生器中断允许寄存器用于允许和禁止时钟产生器的中断,如下图:
CEFINR $38 b7 b6 b5 b4 b3 b2 b1 b0
RTIE 0 0 LOCKIE 0 0 SCME 0
复位时: 0 0 0 0 0 0 0 0
图9 时钟产生器中断允许寄存器
RTIE 位,实时中断允许位。置为1,允许实时中断请求,中断时发生时,标志寄存器
CRGFLG 的RTIF 标志置位,向RTIF 写1 清实时中断;清零,禁止实时中断RTI。
LOCKIE 位,允许PLL 锁定时产生中断请求,中断发生时,标志寄存器CRGFLG 的
LOCKIF 标志置位,向LOCKIF 写1 清该标志位;清零,禁止PLL 锁定中断。
SCMIE 位,允许进入自时钟模式时产生中断请求,置为1,允许进入自时钟模式时产
生中断请求,标志寄存器CPGFLG 的SCMIF 位置位;清零,禁止进入自时钟模式产生中
断。
下面是利用MC9S12DG128 的16 位定时器的溢出中断实现了一个49Hz 的时钟,其初
始化代码如下:
#define CRGINT (*((volatile unsigned char*)(0x0038)))
#define RTICTL (*((volatile unsigned char*)(0x003B)))
RTICTL = 0x74; /* (4+1)*(2(7+9)) */
CRGINT |=0x80; /* Enable Interrupt */
CRGINT 是时钟中断允许寄存器,向该寄存器写0x80 是允许时钟中断的意思。
RTICTL 是实时时钟控制寄存器,向这一寄存器写入分频系数0x74,对系统的外部
晶振频率做5*65536 分频。得到每秒48.828125 次中断。前面两句程序放在main.c 文件中,
写时钟分频系数和开中断这两句没有放在单片机初始化子程序中,而是放在taskstart.c 中。
因为开时钟节拍中断的动作必须在μC/OS-II 已经初始化完毕,多任务开始之前。
10
4.5 制作自己的项目
在为内核编写了上述与硬件相关的代码以后,就可以为自己的项目编写实际的代码
了。在这里有三个任务任务,其中两个任务都是串行通信的输出,一个任务是控制灯的闪
烁。其中还穿插了中断控制,当你在键盘上键入任何字符时就会执行中断。下面是一些主
要的需要编写的代码的实现。
4.5.1 main.h
在main.h 文件中,定义了一些与硬件有关的常数、I/O 寄存器的硬件地址、应用程序
的全局变量如串行口寄存器的地址、驱动发光二极管LED 的口地址,以便在C 程序中可
以直接使用I/O 语句。函数原型的声明也在这个文件中。
4.5.2 main.c
在主程序main.c 中,先调用OSInit(),对μC/OS-II 做初始化,然后创建了一个信号量,
因为下面建立的几个任务都要用到printp()这个函数,这个函数等同于C 语言中的printf(),
而printp()是一个不可重入函数,调用前要防止多个任务同时调用,这个信号量用于保护
printp()函数。Main()再把3 个任务分配到3 个不同的页面中去,接着创建了三个任务,两
个任务的优先级反别是9、10、11,最后调用OSStart()以启动内核运行,于是任务在操作
系统的管理与调度下有条不紊地运行起来了。
主要的代码是:
void main(void)
{
HardwareInit();
OSInit();
/*创建任务*/
OSTaskCreate(TaskStart, (void*)0, (void*)&TaskStartStk[TASK_STK_SIZE-1], 9);
OSTaskCreate(Testtask1, (void*)0, (void*)&Task1Stk[TASK_STK_SIZE-1], 10);
OSTaskCreate(Testtask2, (void*)0, (void*)&Task2Stk[TASK_STK_SIZE-1], 11);
OSStart();
}
4.5.3 Hardware.c
与硬件相关的函数放在Hardware.c 这个文件中,这里包括硬件初始化、初始化RS-232
11
口,基本输入输出函数,如通过串行口输出一个字符的uart_putchar()函数,用户应用程序
用到的更多的硬件相关函数可以放在这个文件中。这里输出一个字符的函数采用了查询方
式,查询方式的优点是简单,缺点是占用了很多CPU 时间,当应用需要将查询方式改为中
断方式时,在这个文件中修改就可以了。其中主要的代码实现是:
/******************uart_init()****************/
void uart_init(void)
{
SCI0CR2=0x2c;
SCI0BD=0x001A; //设置串行口0 波特率19200,并允许接收中断
SCI1CR2=0x2c;
SCI1BD=0x001A; //设置串行口0 波特率19200,并允许接收中断}
}
/**********uart0_putchar()****************/
void uart0_putchar(unsigned char ch)
{
while(!(SCI0SR1&0x80))
{}
SCI0DRL=ch;
}
PORTB = 0xff;
DDRB =0xff;
PORTA = 0xFD;
DDRA = 0XFF;
}
4.5.4 userlib.c
这个文件中放的是和硬件无关的函数。
4.5.5 链接与程序定位
交叉汇编软件CodeWarrior 给出的链接文件default.prm 是程序最终链接与定位用的,
是软件工具开发商Metriwerk 提供的。在其提供的例子基础上针对本系统中应用方式做了
一些修改。
每个任务的起始地址都被强制定位在0x8000 这个固定地址上,但分配在不同的页面
12
中,分配在不同的页面中是在程序下载时解决的。已经给任务定义了0x8000-0xBFFF 的
16KB 空间,类型是只读,READ_ONLY,名为TASK_ROM。在定位时,将任务的程序代
码TASKCODESEG 和常数代码TASKSTRINGSEG 放在TASK_ROM 段内。
在每个任务的.c 文件代码中有如下语句:
#pragma CODE_SEG TASKCODESEG
#pragma STRING_SEG TASKSTRINGSEG
说明任务代码要放在TASKCODESEG 和TASKTRINGSEG 代码段。
前面提到,由于3 个任务虽然分别用相应的.c 文件定义了,但并没有被任何程序调用
过,它们的运行是靠μC/OS-II 的任务调度实现的。没有被调用的函数不会被自动链接到最
终生成的目标代码中,故这里要加上:
ENTRIES
TaskStart
END
这样TaskStart 任务的代码就生成了。其中还要提一下在这个链接文件里定义了五个向
量,分别是:VECTOR ADDRESS 0xFFFE _Startup、VECTOR ADDRESS 0xFFF0 OSTickISR、
VECTOR ADDRESS 0xFFF6 OSCtxSw、VECTOR ADDRESS 0xFFD6 SCI0RECEIVE_ISR、
VECTOR ADDRESS 0xFFD4 SCI1RECEIVE_ISR。第一个_Startup 是本应用程序的起始地
址,可以来自CodeWarrior 提供的startup.c 文件,也可以来自自己的cstartup.c 文件,这个
文件建立C 语言程序的运行环境。后面的几个向量,一个是定时中断服务子程序的入口地
址OSTickISR,一个是软中断向量OSCtxSw,最后两个是穿行通行服务程序的开始地址
SCI0RECEIVE_ISR、SCI1RECEIVE_ISR 但是后一个串行通信并没有用到,用到的话还要
接外接设备,但在这里还是预先定义了。由于监控程序将整个向量表顶点从0xFFF 移到了
0xEFFF,故在保留监控程序的情况下,要将中断向量表这样定位。要是用到其他的中断,
也要在这里定义。
13
第5章 μC/OS-II的移植代码测试及下载
现在开始做移植代码的测试。
首先,从开始菜单中打开Freescale CodeWarrior 中的CW for HC12 V4.6 中的
Codwarrior IDE,单击File 在下拉菜单中的New,创建一个新的工程文件,将Source 下的内
容全部删掉,添加你编写的μC/OS-II 移植文件中的Source 内容,单击从左边起第三个Make
图10 编辑窗口
图标,进行编译和链接项目,会发现IDE 会在Errors&Warnings 窗口中显示警告信息,不
用管它,将它忽略就可以了,编译成功后,单击上面的第四个Debug 图标,对项目进行仿
真调试。此时,会弹出另外一个Ture-Time Simulator & Real-TimeDebugger 窗口,在
Ture-Time Simulator & Real-TimeDebugger 调试中,会弹出一些小窗口,如Source 窗口、
Date 窗口、Command 窗口和Assembly 窗口等,如下图所示:
图 11 编译窗口
接着,会生成一个bin 文件夹,里面有一个S19 文件,将它下载到MC9S12DG128 中,
在这里要说一下在下载S19 文件以前要在MC9S12DG128 单片机上通过BDM 下载
Debug128 监空程序,在此基础上再下载μC/OS-II 移植的S19 文件。下面是进入超级终端
的过程:
14
打开开始菜单中的附件->通讯->超级终端,就出现下面的窗口:
图 12 是否默认Telnet 程序
单击否,会弹出一个“连接描述”对话框。然后在对话框中输入所要建立通信文件的
名称,这里用MC9S12DG128,选择一个图标,如下图:单击“确定”按钮进入下一步。
图 13 连接描述
在弹出的对话框中,如果开发版连接到串口COM1,则选择连接到串口COM1;然后
单击“确定”按钮进入下一步。在弹出的下一个对话框中设置串口通信协议,这里使用19200
波特率,8 位传送,无奇偶校验,一个停止位,无流量控制协议,然后单击“确定”按钮
进入下一步。
按开发板S1 复位键会看到如下图所示:
图 14 超级终端显示窗口
15
而它的实现是由main.c 中的如下测试代码实现的。
void TaskStart(void *pdata)
{
int j=0;
int i=0;
RTICTL = 0x74;
CRGINT |=0x80;
pdata = pdata;
for(;;) {
uart0_putchar('T');
uart0_putchar('a');
uart0_putchar('s');
uart0_putchar('k');
uart0_putchar('1');
uart0_putchar(':');
uart0_putchar('S');
uart0_putchar('c');
uart0_putchar('i') ;
uart0_putchar('o');
uart0_putchar('u');
uart0_putchar('t');
uart0_putchar(0x0a);
uart0_putchar(0x0d);
OSTimeDly(100); //delay 1 second
}
}
这部分是串行通信的输出部分,在图片中可以看到输出的是Task1:Sciout。
void Testtask1(void *pdata)
DDRB=0XFF;
PORTB=0X0F;
for(;;) {
PORTB=~PORTB;
uart0_putchar('T');
uart0_putchar('a');
16
uart0_putchar('s');
uart0_putchar('k');
uart0_putchar('3');
uart0_putchar(':');
uart0_putchar('L');
uart0_putchar('i');
uart0_putchar('g');
uart0_putchar('h');
uart0_putchar('t');
uart0_putchar(0x0a);
uart0_putchar(0x0d);
OSTimeDly(30); //delay 1 second
}
}
这部代码是跑马等的实现还有输出字符的功能,在图片上显示的是Task3:Light,而单片
机上实现的是四个跑马灯高四位和低四位交替循环闪烁如下图所示:
图 15 MC9S12DG128 板子的运行结果
void Testtask2(void *pdata)
{ PORTA = 0xFD;
DDRA = 0XFF;
pdata = pdata;
for(;;) {
uart0_putchar('T');
uart0_putchar('a');
uart0_putchar('s');
uart0_putchar('k');
uart0_putchar('2');
uart0_putchar(0x0a);
17
uart0_putchar(0x0d);
OSTimeDly(100); //delay 1 second
}
}
这部分和void TaskStart 是一样的只是个输出字符的任务。
/**********串口0 中断服务子程序***************/
#pragma TRAP_PROC
interrupt void SCI0RECEIVE_ISR(void)
{ unsigned char ch;
OSIntEnter();
OS_SAVE_SP();
if(SCI0SR1_RDRF == 1) SCI0SR1_RDRF = 1;
ch=SCI0DRL;
uart0_putchar('T');
uart0_putchar('h');
uart0_putchar('e');
uart0_putchar('D');
uart0_putchar('a');
uart0_putchar('t');
uart0_putchar('a');
uart0_putchar(':');
uart0_putchar(ch);
uart0_putchar(0x0a);
uart0_putchar(0x0d);
OSIntExit();
}
这部分代码是中断任务的实现,当你敲击键盘上的任意键时,就会在超级终端中显
示TheData:+敲击键盘的字符。
到此, 测试工作完成,μC/OS-II 已经可以正常工作, 说明移植成功, 可将
MC9S12DG128 安装到MC9S12 单片机上,为MC9S12 单片机应用系统开发平台软件设计的
完成奠定基础。