您好,欢迎来到好走旅游网。
搜索
您的当前位置:首页ucos操作系统在ARM上的移植

ucos操作系统在ARM上的移植

来源:好走旅游网
UC/OS-II嵌入式系统在ARM上的移植

UC/OS-II操作系统是一款完全公开的源代码,它非常精简,整个操作系统的代码只有几千行,是专门针对于嵌入式开发而产生的一款代码。它有几个特点,分别是可移植性(Portable)、可固化(ROMable)、可裁剪(Scalable)、多任务、可确定性、任务栈、系统服务、中断管理、稳定性可靠性。

UC/OS-II主要就是一个内核,由ANSIC语言编写而成。负责任务管理和任务调度,没有文件系统和界面系统。它的代码是公开的,系统的实时性强、移植性好、可多任务。

UC/OS-II作为基于优先级的抢占式多任务的实时操作系统,包含了实时内核、任务管理、时间管理、任务间通信同步和内存管理的功能。它使得任务的独立性,不相互干涉,非常的准时和高效,且易于设计和扩展。

UO/OS-II共有16个内核文件,11个与CPU类型无关,就是说可以直接使用不需要修改。还有3个内核文件与CPU有关系,要根据需要作出相应的改动。剩下的两个内核文件和具体的应用有关。如图所示UC/OS-II的16个内核文件的层次。

μC/OS-II内核文件

多任务操作的核心是系统调度器,利用TCB来管理任务调度功能。它的主要

CPU 定时器 与处理器类型有关的内核代码 OS_CPU.H OS_CPU.ASM OS_CPU_C.C 软件 硬件

与处理器类型无关的内核代码 OS——CORE.C OS_FLAG.C OS_MBOX.C OS_MEN.C OS_MUTEX.C OS_Q.C OS_SEM.C OS_TASK.C μC/OS-II.C OS_TIME.C μC/OS-II.C 与应用有关的内核配置文件 OS_CFG.H INCLUDE.H 应用程序 功能是保存任务的当前态、优先级、等待事件、代码起始地址、初始堆栈指针等。程序的设计关键就是确定划分多任务的问题,以及任务优先级和任务通信。

优先级的意思是每个任务都是无限循环的,有运行态度、就绪态、休眠态、挂起态和中断五种状态。当有高一级优先级的任务就绪后,低优先级立即停止运行,转为挂起态或就绪态。这就是可剥夺型的内核。当中断一个高优先级任务,中断时挂起,中断结束后任务继续运行,并立即剥夺低优先级的任务。

对于这种可剥夺型内核,CPU的使用时可以确定的,可优化任务级响应。在很多单片机或ARM板上很容易就可以移植UC/OS-II。当然本次设计使用的TQ2440,也可以完美的移植它。移植程序在网上都可以找得到,所以设计中就不做解释了。

本次设计实现的是串口协议和网口协议组合成的一个数据网关。其主要的流程图如下所示:

以太网

RJ45接口

IP分组

解帧 封帧

封帧 解帧

RS232接口

现场数据网

开始

系统初始化OSInit() OS_InitMisc() OS_InitRdyList(混合变量初始化 ) OS_FlagInit()事件标志初始化 OS_MemInit() 内存管理 OS_InitTCBList() OS_InitEventList() 任务控制块初始化 事件控制块初始OS_SemInit() 消息初始化 OS_InitTaskIdle() 创建空闲任务

定时器初始化 串行口初始化 创建任务 TASK_UipRec 以太网接收 优先级:4 TASK_UipTimeOut 定时任务 优先级:3 TASK_UipProcess:uip协议处理 优先级: 2

OSStart()启动调度开始运行

UART中断 DM9000外部中

根据延时或消息调度运行

如图所示可以很清楚的看出内核操作系统对于整个系统的控制过程,下面我们要介绍一下UC/OS-II里面的一些程序。

用户应用程序任务定义代码如下:

void MyTask( void *pdata) //开始定义用户任务 { for(;;) {

... } }

void main() { TargetInit(); //完成初始化目标开发板。

OSInit (); //完成初始化UCOS-II。 OSTimeSet(0); //完成初始化系统时基。

OSTaskCreate (MainTask,(void *)0, &MainTaskStk[MainTaskStkLengh MainTaskPrio); //开始创建系统初始任务。

OSStart (); //整个任务开始。

return 0; }

void Task0(void *pdata) //主任务建立:taks0 和 task1 { #if OS_CRITICAL_METHOD == 3

/* 分配CPU状态寄存器*/

OS_CPU_SR cpu_sr;

#endif

OS_ENTER_CRITICAL();

Timer0Init();

//初始化报时信号

- 1], ISRInit(); //初始化中断优先级

OSPrintfInit();

//用户任务给串口

OS_EXIT_CRITICAL();

DM9000_init(); }

需要注意的是,μC/OS-II的应用程序要使用空闲任务OSTaskldle(),而它是不可删除的系统文件。

下面开始对应用程序进行移植了。

本次设计是在ARM开发板上实现串口、网口数据转换的的功能。在TQ2440上有串口和网口。通信过程简单的描述就是PC串口发送数据给ARM板,ARM接到串口数据后,从网口再传给PC。相反也是同样的道理,由网口发送数据,有ARM控制由串口发回数据。

首先是TCP/IP协议的移植。在编写移植程序前,有必要解释一下基本的协议栈作用和意义。

`就常见的网络通信方式一般来说有两种:

1、UART-RS232,此时只需要pc上有串口调试助手即可。

2、TCP/IP,这时候和普通pc与pc通信一样 可以用socket套接字编程也可用别人写好的软件侦听。

而现在我们要实现的是串口控制单片机与PC机的通信,在这里用PC来代替以太网。

接下来还要介绍一下以太网接口。 while(1) { }

OSPrintf(\"\\nEnter Main Task\\n\"); //测试Dm9000 //打印DM9000寄存器

OSTimeDly(OS_TICKS_PER_SEC); OSStatInit();

以太网技术如今已经相当的成熟了,其相应的网络产品价格低廉、技术完善。而数据总线如今越来越难以满足人们日益提升的需要,这时以太网控制网络技术得到了快速的发展,并形成了现场总线的新标准。加上国内大部分局域网是以太网,给予以太网实现现场总线有了雄厚的物质基础。

以太网的接口就是以太网同信的基础,是通信介质通信的中间处理部件,实现报文的发送与接收功能,位于TCP/IP协议栈的数据链路层。每一个以太网接口(有时候也叫网卡),在连通后就可以随时的发送和接收网络上的数据,执行EEE802.3标准。

TCP/IP对应的ISO结构如图所示: TCP/IP协议栈

而单片机与计算机的TCP/IP协议的实现也有很大的不同,原本在计算机里编写的程序可以不考虑代码的大小和效率,但在嵌入式开发板上都要考虑到这些问题。在操作系统、内存分配、指针、参数传递、协议支持以及硬件接口的设计方面有些不同。

首先就是操系统,嵌入式的特点之一就是简洁高效,有很强的专业功能。相对而言计算机上的操作系统的侧重点就是兼容性,所以资源要求全面支持,所以很复杂。其次就是内存上的分配,计算机像windows系统它的内存分配是动态的,而在单片机上却不能同样如此,应为RAM的容量所限,所以其中存放以太网的数据包是固态的。由于ARM相对于单片机的能力而言有了很大的提升,所以可以突破单片机的一些约束。

如下图所示,TCP/IP协议栈中的内容,从上往下分别对应应用层、传输层、网

网络层 数据链路层 网络接口层 物理层 网络层 会话层 传输层 传输层 应用层 表示层 应用层 络层和网络接口层。

TCP/IP协议栈

在本次设计中采用Lwip协议栈来实现ARM与以太网的连接。

完成移植后,需要介绍一下以太网的初始化过程和数据收发过程。LWIP的初始化要在UC/OS-II之后,在其它任务创建之前。因为LWIP要对以太网协议栈初始化以及新线程的建立,LWIP初始化如图所示。

ARP 硬件接口 ICMP IP TCP UDP 用户进程 用户进程 用户进程 (3) lwip_init() (2) Tcpip_Init() (4) Sys_mbox_new() (5) Sys_thread_new () (8) (7) 指向 tcpip_input () netif->input (10(9) 指向 ) etharp_output () netif->output (12(11) ) netif->linkoutpu指向 Low_level_outpt ut() (14(13) Low_level EMACInit() ) _Init() 以太网初始化流程

(1) LwIP_Init() (6) Ethernetif_Init() LWIP程序可以实现很多功能,在本设计中没有实现他的全部功能。而简单的PING通需要DM9000来实现。

以太网的接收是通过中断方式,如果有数据进入以太网中断函数。其主要任务就是读取和分析数据包。如果数据有效则在Tcpip_input()函数把数据发送出去,并在LWIP初始化创建的线程中就可以得到此消息。然后通过ip_input()函数进入传输层后,再把把数据传到应用层。具体流程如下图所示:

应用层 netconn_recv () Recv_mbox方式进行数据recv_tcp () tcp_receive () TCP_EVENT_RE应用层 netconn_write () TCPIP_APIMSG () tcpip_apimsg() Mbox方式传递数据,tcpip_thread()CV () tcp_process () tcp_input () update_arp_entry ip_input () () etharp_ip_input () ethernet_input() Mbox方式传递数据,在tcpip_thread(Tcpip_input() 通过网络接口GetInputPacketLe函数n() netif->intput EMACReadPacket () Ethernetif_input() Low_level_input() ETH_IRQHandlert() 以太网数据接收流程

do_write () tcp_write () tcp_enqueuet() tcp_output () ip_output () ip_output_if () 通过网络接口函数netif->output etharp_output () etharp_send_ip () 通过网络接口函数netif->linkoutput low_level_output () ETH_TxPkt_ChainMode () 以太网数据发送流程

为什么要选择DM9000网络驱动器呢?如今嵌入式中大量使用10/100M的以太网卡,实际上并不实用。因为它不能既满足快速传输速率又满足成本控制。这时,DM9000作为一种综合低成本的单一快速以太网控制芯片就有了很高的实用价值。它具有通用的接口,设计简单,可满足不同系统的软件驱动。

DM9000程序的体系结构可以分为网络协议接口、设备接口层、功能层及媒介层。

下面是DM9000的部分驱动程序的编写。 定义DM9000: #include \"config.h\"

#define DM_ADD (*((volatile unsigned short *) 0x20000300))

#define DM_CMD (*((volatile unsigned short *) 0x20000304))

#define Printf OSPrintf

uint8 Buffer[1000]; //定义了一个1000字节的接收发送缓冲区

uint8 host_mac_addr[6]= { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; //主机的MAC地址

uint8 mac_addr[6] = { 0x0a, 0x1b, 0x2c, 0x3d, 0x4e, 0x5f }; //开发板{ 0a,1b,2c,3d,4e,5f },这实际上是一个软地址,在本设计中不需要特指。

uint8 ip_addr[4] = { 192, 168, 1, 6}; uint8 host_ip_addr[4] = { 192, 168, 1, 100 }; uint16 packet_len; //接收、发送数据包的长度,单位为字节

uint8 arpsendbuf[60]={

0xff,0xff,0xff,0xff,0xff,0xff, //以太网目标地址 0x0a,0x1b,0x2c,0x3d,0x4e,0x5f, //以太网源地址 0x08,0x06, //帧类型: ARP帧 0x00,0x01, //硬件类型:以太网

0x08,0x00, //协议类型:IP协议 0x06, //硬件地址长度:6字节 0x04, //协议地址长度:4字节 0x00,0x00, //操作: ARP请求 0x0a,0x1b,0x2c,0x3d,0x4e,0x5f, //发送端硬件地址 192, 168, 1, 6, //发送端协议地址 0x00,0x00,0x00,0x00,0x00,0x00, //接收端硬件地址 192, 168, 1, 100 //接收端协议地址 };

#define DM9KS_ID 0x90000A46 #define DM9KS_VID_L 0x28 #define DM9KS_VID_H 0x29 #define DM9KS_PID_L 0x2A #define DM9KS_PID_H 0x2B

#define DM9KS_BASE_ADDR_ETH0 0x20000000//nGCS4 #define DM9KS_Index (*((volatile unsigned short *)(DM9KS_BASE_ADDR_ETH0 + 0x300)))

#define DM9KS_Data (*((volatile unsigned short *)(DM9KS_BASE_ADDR_ETH0 + 0x304)))

接下是来对DM9000进行初始化的程序部分: void DM9000_init(void) {

uint32 i;

//Test_DM9000AE();

IOSetInit(); //设置中断EINIT7

//初始化设置步骤: 1

dm9000_reg_write(DM9000_GPCR, 0x01); //设置GPCR(1EH) bit[0]=1,使DM9000的GPIO3为输出。

dm9000_reg_write(DM9000_GPR, 0x00); //GPR bit[0]=0 使DM9000的GPIO3输出为低以激活内部PHY。

udelay(5000); //延时2ms以上等待PHY上电。

//初始化设置步骤: 2

dm9000_reg_write(DM9000_NCR, 0x03); //软件复位 udelay(3000); //延时20us以上等待软件复位完成

dm9000_reg_write(DM9000_NCR, 0x00); //复位完成,设置正常工作模式。

dm9000_reg_write(DM9000_NCR, 0x03); //第二次软件复位,为了确保软件复位完全成功。此步骤是必要的。

udelay(3000);

dm9000_reg_write(DM9000_NCR, 0x00); //以上完成了DM9000的复位操作

//初始化设置步骤: 3

dm9000_reg_write(DM9000_NSR, 0x2c); //清除各种状态标志位

dm9000_reg_write(DM9000_ISR, 0xbf); //清除所有中断标志位 //以上清除标志位

//初始化设置步骤: 4

dm9000_reg_write(DM9000_RCR, 0x39); //接收控制 dm9000_reg_write(DM9000_TCR, 0x00); //发送控制 dm9000_reg_write(DM9000_BPTR, 0x3f);

dm9000_reg_write(DM9000_FCTR, 0x38); //接收FIFO门限3k 8k

dm9000_reg_write(DM9000_FCR, 0xff); dm9000_reg_write(DM9000_SMCR, 0x00); //以上是功能控制

//初始化设置步骤: 5 for(i=0; i<6; i++) dm9000_reg_write(DM9000_PAR + i, mac_addr[i]);//mac_addr[]自己定义一下吧,6个字节的MAC地址

//以上存储MAC地址(网卡物理地址)到芯片中去,这里没有用EEPROM,所以需要自己写进去

//初始化设置步骤: 6

dm9000_reg_write(DM9000_NSR, 0x2c); //清除各种状态标志位

dm9000_reg_write(DM9000_ISR, 0x3f); //清除所有中断标志位 //初始化设置步骤: 7

dm9000_reg_write(DM9000_IMR, 0x81); //中断使能 //中断使能(或者说中断屏蔽),即开启我们想要的中断,关闭不想要的,这里只开启的一个接收中断

udelay(10000); //延时2ms以上等待PHY上电。

//PrintfDM9000Reg();

Printf(\"DM9000初始化完毕\\r\\n\"); }

接下来是数据包的接收和发送流程图和程序: DM9000收到封包,置于接收内存的0C00h-3FFFh。若超过最大值时,会自动把位置移到0C0h0位置。每个封包有4字节是存放一些与封包相关的信息。若内存的第1字节值为“01h”,表明封包已经在内存中了。在读取其它字节前,先要确定第2个字节,则是这个封包的相关信息。如下图是DM9000接收封包流程:

开始

初始化

N 无新包 MRCMDX=0x01?

Y

读前4个字节

Y

N

封包正确 接收错误

Y

N

时ARP或IP包?

剩下的数据

返回1 返回0

DM9000接收封包流程

如图可以看出,DM9000从网络中接到一个数据包后,在数据包前面加了4个字节,分别为“01H”、“status”、“LENL”(包长的低8位)、“LENH”(包长的高8位)。这4个字节来确定数据包的状态,“01H”表示接下来的是有效数据包,“00H”则表示没有数据包,其它值则表示没有正确初始化,并重新初始化。

若数据包长度小于60字节,则DM9000芯片会自动为不足的字节补上0。同时,在接收到的包后程序还会自动添加4个CRC校验字节。于是,接收到的数据包至少为64字节。也根据TCP/IP协议从首部字节中找出有效字节数,这就是TCP/IP协议栈的功能了。

具体程序如下:

//接受数据包

//参数:datas为接收到是数据存储位置(以字节为单位) //返回值:接收成功返回数据包类型,不成功返回0 uint32 receivepacket(uint8 *datas) {

uint16 i,tmp,status,len; uint8 ready; uint32 st; ready = 0; //希望读取到\"01H\" status = 0; //数据包状态 len = 0; //数据包长度

if(dm9000_reg_read(DM9000_ISR) & 0x01) {

dm9000_reg_write(DM9000_ISR, 0x01); //清除接收中断标志位

}

ready = dm9000_reg_read16(DM9000_MRCMDX); // 第一次读取,一般读取到的是 00H

Printf(\"ready1 = %x\\r\\n\ if((ready & 0x0ff) != 0x01) {

ready = dm9000_reg_read16(DM9000_MRCMDX); // 第二次读取,总能获取到数据

Printf(\"ready2 = %x\\r\\n\ if((ready & 0x01) != 0x01) {

if((ready & 0x01) != 0x00) //若第二次读取到的不是01H或00H ,则表示没有初始化成功

{

dm9000_reg_write(DM9000_IMR, 0x80);//屏蔽网卡中断

DM9000_init(); //重新初始化

dm9000_reg_write(DM9000_IMR, 0x81);//打开网卡中断

}

return 0; } }

// status = dm9000_reg_read16(DM9000_MRCMD); DM_ADD = DM9000_MRCMD; st = DM_CMD; status = st;

len = 64;// DM_CMD;

Printf(\"st=%x status=%x len= %x\\r\\n\ if( (len < 1522))//!(status & 0xbf) && {

for(i=0; iudelay(20);

tmp = DM_CMD;

datas[i] = tmp & 0x0ff;

datas[i + 1] = (tmp >> 8) & 0x0ff; } } else return 0; if(len > 1000) return 0;

// if( (HON( ETHBUF->type ) != ETHTYPE_ARP) && (HON( ETHBUF->type ) != ETHTYPE_IP) )

// return 0;

packet_len = len; return len; }

在发送封包之前,需要将其中的数据存放在DM9000传送内存0000h~0BFFh。当超过0BFFh时,位置自动回到0000h的位置。封包的数据存放在MWCND中,芯片会吧数据自动的存入传送内存的。其封包的大小存放在低字节的TXPLL和高字节的TXPLH中。然后把TCR bit()设置成1,开始传送封包。完成后,将是否完成的信息存入TSRI、TSRII中。其顺序为TSRI->TERII->TSRI循环。故需按照NSR bit-3来判断完成进度。

以下是发送数据包的流程图和程序:

开始 初始化 包长不小于60字节? Y 设置源端物理地N 设定长度为60字节 将数据依次写入MWCMD寄存器 将封包长度写入TXPLL和TXPLH中 最多发重发5次 启动发送 等待发送结束 N 发送成功? 查错重发 返回 DM9000发送封包流程 以上是发送数据包,过程很简单。 //发送数据包

//参数:datas为要发送的数据缓冲区(以字节为单位),length为要发送的数据长度(两个字节)。

void DM9000_sendPcket(uint8 *datas, uint32 length) {

uint32 len,i; uint8 tmp;

Printf(\"发送数据\\r\\n\");

dm9000_reg_write(DM9000_IMR,0x80); //先禁止网卡中断,防止在发送数据时被中断干扰

len = length; //把发送长度写入

/*这两句是将要发送数据的长度告诉DM9000的寄存器*/ dm9000_reg_write(DM9000_TXPLH, (len>>8) & 0x0ff); dm9000_reg_write(DM9000_TXPLL, len & 0x0ff);

DM_ADD = DM9000_MWCMD;

//将要发送的数据写到DM9000的内部SRAM中的写FIFO中 for(i=0; iudelay(2);

DM_CMD = datas[i] | (datas[i+1]<<8); }

dm9000_reg_write(DM9000_TCR, 0x01); //发送数据到以太网上

while(1)//等待数据发送完成 {

uint8 data;

data = dm9000_reg_read(DM9000_TCR);//DM9000_NSR if((data&0x01) == 0x00) break; }

tmp = dm9000_reg_read(DM9000_NSR);

if((tmp & 0x01) == 0x04) {

if((dm9000_reg_read(DM9000_TSR1)&0xfc) == 0x00) Printf(\"TSR1成功\\r\\n\"); else Printf(\"TSR1失败r\\n\"); } else {

if((dm9000_reg_read(DM9000_TSR2)&0xfc) == 0x00) Printf(\"TSR2成功\\r\\n\"); else Printf(\"TSR2失败r\\n\"); }

dm9000_reg_write(DM9000_NSR, 0x2c); //清除状态寄存器,由于发送数据没有设置中断,因此不必处理中断标志位

dm9000_reg_write(DM9000_IMR, 0x81); //DM9000网卡的

接收中断使能

Printf(\"发送数据完成\\r\\n\"); }

TQ2440串口初始化程序: static int whichUart=0;

void Uart_Init(int pclk,int baud) {

int i;

if(pclk == 0)

pclk = PCLK;

rUFCON0 = 0x0; //UART channel 0 FIFO control register, FIFO disable

rUFCON1 = 0x0; //UART channel 1 FIFO control register, FIFO disable

rUFCON2 = 0x0; //UART channel 2 FIFO control register, FIFO disable

rUMCON0 = 0x0; //UART chaneel 0 MODEM control register, AFC disable

rUMCON1 = 0x0; //UART chaneel 1 MODEM control register, AFC disable

//UART0

rULCON0 = 0x3; //Line control register : Normal,No parity,1 stop,8 bits

// [10] [9] [8] [7] [6] [5] [4] [3:2] [1:0]

// Clock Sel, Tx Int, Rx Int, Rx Time Out, Rx err, Loop-back, Send break, Transmit Mode, Receive Mode

// 0 1 0 , 0 1 0 0 , 01 01

// PCLK Level Pulse Disable Generate Normal Normal Interrupt or Polling

rUCON0 = 0x245; // Control register

rUBRDIV0=( (int)(pclk/16./baud+0.5) -1 ); //Baud rate divisior register 0

//UART1

rULCON1 = 0x3; rUCON1 = 0x245;

rUBRDIV1=( (int)(pclk/16./baud+0.5) -1 ); //UART2

rULCON2 = 0x3; rUCON2 = 0x245;

rUBRDIV2=( (int)(pclk/16./baud+0.5) -1 );

Uart_TxEmpty(whichUart); //for(i=0;i<100;i++); }

//===================================================================

void Uart_Select(int ch) {

whichUart = ch; }

//===================================================================

void Uart_TxEmpty(int ch) {

if(ch==0)

while(!(rUTRSTAT0 & 0x4)); //Wait until tx shifter is empty.

else if(ch==1)

while(!(rUTRSTAT1 & 0x4)); //Wait until tx shifter is empty.

else if(ch==2)

while(!(rUTRSTAT2 & 0x4)); //Wait until tx shifter is empty. }

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- haog.cn 版权所有

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务