在Zynq平台上使用uCOS [原创www.cnblogs.com/helesheng]
Posted 一个电子爱好者的工作记录
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在Zynq平台上使用uCOS [原创www.cnblogs.com/helesheng]相关的知识,希望对你有一定的参考价值。
uCOS是我个人熟悉和喜欢的操作系统,从最早的C51到后来的LPC2000和STM32,uCOS-II或uCOS-III都是我进行产品开发的首选的实时操作系统。但却从未尝试过在全可编程片上系统(APSoC)上使用过uCOS,这几天心血来潮想来试试看。却发现采用Zynq + uCOS的工程师并不多,网上虽然有一些介绍文章,但照着操作做后依然存在这样那样的问题。这篇博文将我使用Zynq + uCOS方案时遇到的问题罗列一下,供后来者参考。由于也是浅尝而止,如果用这个方案开发产品,估计还会有大量的问题,以后遇到再逐渐补齐吧,也欢迎网友们在评论区指出和补充。
以下原创内容欢迎网友转载,但请注明出处: https://www.cnblogs.com/helesheng
一、在SDK中配置Zynq-7000上的uCOS的开发环境
uCOS的开发商Micrium公司(现已被Silicon Labs公司收购)已经帮助大家将uCOS-II和uCOS-III移植到了常见的所有品牌处理器包括Zynq-7000上。大家可以到Silicon Labs公司的网站的相关连接(https://www.silabs.com/developers/micrium)上直接下载。但下载需要有Silicon Labs公司的账号,不知为什么我用QQ邮箱和学校教工的邮箱都没法收到Silicon Labs的确认邮件只好作罢。在GitHub上找到一个网友分享的链接:https://github.com/suisuisi/zynq_guide/tree/main/ucos,下载到了一个mirium的压缩包ucos_v1_45.7z,在此对不知名的朋友表示感谢。
下载并解压后得到如下图所示的目录结构。
在Zynq的软件开发工具SDK中可以配置uCOS的开发环境,从而在SDK中新建应用时直接生成uCOS-II或uCOS-III的应用工程。具体方法是,在SDK的主菜单中单击Xilinx->Repositories,在Local Repositories栏中导入刚才解压的uCOS开发包。但需要注意的是,指定的路径必须到压缩包中的../ucos/文件夹下,如下图所示,否则SDK将找不到新建工程的模板。
二、建立一个验证ucos实时操作系统的简单的Zynq硬件系统
做一个ucos简单的多任务演示系统,不同任务控制不同GPIO点亮不同的LED,方便观察。我用的开发板是PYNQ-Z2,MIO没有连接到足够的LED,用EMIO来连接LED。具体步骤如下:
1、新建Vivado工程,创建Block Design,在Block Design中添加ZYNQ7 Processing System IP核。
2、配置ZYNQ7 Processing System IP核。我直接使用了PYNQ-Z2预置的配置文件pynq_revC.tcl。
加载预置的配置文件后,需要手动将官方配置文件没有引出的EMIO引出2个。完成下图所示的配置后,单击OK在Block Design界面中就可以找到ZYNQ处理器模块上的GPIO端子了。
3、为EMIO添加输出连接端口。并通过约束文件将这两个EMIO连接到PYNQ-Z2上的LED上。
#LED_PS
set_property -dict PACKAGE_PIN N16 IOSTANDARD LVCMOS33 [get_ports GPIOA_tri_io[0]]
set_property -dict PACKAGE_PIN M14 IOSTANDARD LVCMOS33 [get_ports GPIOA_tri_io[1]]
4、综合并产生二进制配置文件,并将其导出。
三、在SDK中开发uCOS代码
1、启动SDK开发环境,选择新建应用工程,此时弹出的新建工程向导如下图所示。
和之前的新建应用工程最大的不同是OS Platform下拉菜单中多了一个ucos的选项。选择ucos选项,并输入工程名称后单击Next,进入新建工程模板选择窗口。在下图所示的工程模板选择窗口中选择的Micrium uC/OS-II Hello World或Micrium uC/OS-III Hello World工程模板。
2、修改ucos工程模板创建工程的标准输入/输出设备
不知什么原因,Micrium的模板并未将标准的输入输出设备指定为调试的uart0口。这将导致直接编译运行该工程模板后无法看到输出的Hello World。修改起来也非常容易:双击刚才新建的应用工程的板级支持包(BSP)工程的配置文件system.mss(在左侧导航窗口的bsp工程下)。在弹出的下图界面中单击Modify this BSP’s Setting。
在随后弹出的配置窗口如下图所示,在左侧选择ucos_standalone。在右侧stdin/stdout(标准输入/输出)设备都选择为ps7_uart_0。
3、在工程模块框架下编写任务代码
嵌入式系统中,实时操作系统所起到的最核心作用就是管理和分配系统中的各种资源,尤其是嵌入式系统最为重要的资源:CPU的时间。uCOS以“任务”作为CPU时间分配的基本对象。程序员在开发单个uCOS任务时,最重要的“心法”就是:认为本任务独占CPU。
为实现上述控制两个独立的LED采用不同频率闪烁的目标,我设计采用两个任务各自控制一个LED,它们各自按照自己的节奏延时和刷新EMIO状态。两个任务的代码如下:
//控制LED0的亮灭. void TaskLed(void *pdata) while(1) XGpioPs_WritePin(&psGpioInstancePtr, 54, 1);//EMIO的第0位输出1 OSTimeDly(300); XGpioPs_WritePin(&psGpioInstancePtr, 54, 0);//EMIO的第0位输出0 OSTimeDly(300); //控制LED1的亮灭. void TaskLed1(void *pdata) while(1) XGpioPs_WritePin(&psGpioInstancePtr, 55, 1);//EMIO的第1位输出1 OSTimeDly(200); XGpioPs_WritePin(&psGpioInstancePtr, 55, 0);//EMIO的第1位输出0 OSTimeDly(200);
上面代码中的关键是OSTimeDly();函数,它是uCOS提供的系统函数。该函数“告诉”操作系统:当前任务要延时固定的时钟节拍(Tick)时间,可以在这段时间内将CPU让给其他任务来使用,延迟节拍数到达后本任务再通过“竞争上岗”继续运行。和OSTimeDly();类似的系统函数还有OSTimeDlyHMSM();它们的区别在于OSTimeDly();参数的单位是时钟节拍数(Ticks),而OSTimeDlyHMSM();的单位则是时、分、秒。这里两个任务分别按照200个Ticks和300个Ticks的间隔切换LED的亮灭状态。
接下来查看一下每个Ticks的时长:打开本工程的板级支持包(bsp)工程xxx_bsp下include文件夹下的os_cfg.h,并在其中搜索宏OS_TICKS_PER_SEC,它定义了每秒钟内时钟节拍的数量。缺省情况下,这个宏被配置为1000,即每个Tick的时长为1ms。两个任务中的延迟也就分别是200ms和300ms。个人觉得,这个时间片长度对于运行速度为650MHz的Cortex-A9内核还是有点偏长,实际应用中可以考虑适当增加OS_TICKS_PER_SEC的数量。
4、编写任务配套的初始化代码
和其他所有uCOS开发一样,我们也需要为每个任务完成设置优先级、分配堆栈和创建任务等工作。代码如下所示:(注意:这些代码有问题,关于解决和改正的办法,将在本博文后续部分介绍)
//设置任务优先级 #define LED_TASK_Prio 3 #define LED1_TASK_Prio 5 //设置任务堆栈大小 #define LED_STK_SIZE 64 #define LED1_STK_SIZE 64 //任务堆栈 OS_STK TASK_LED1_STK[LED_STK_SIZE]; OS_STK TASK_LED_STK[LED1_STK_SIZE];
在main函数中补充EMIO初始化的代码:
XGpioPs_Config* GpioConfigPtr; int xStatus; //EMIO的初始化 GpioConfigPtr = XGpioPs_LookupConfig(XPAR_PS7_GPIO_0_DEVICE_ID); if(GpioConfigPtr == NULL) return XST_FAILURE; xStatus = XGpioPs_CfgInitialize(&psGpioInstancePtr,GpioConfigPtr, GpioConfigPtr->BaseAddr); //EMIO的输入输出设置 XGpioPs_SetDirectionPin(&psGpioInstancePtr, 54,1); XGpioPs_SetDirectionPin(&psGpioInstancePtr, 55,1); //使能EMIO输出 XGpioPs_SetOutputEnablePin(&psGpioInstancePtr, 54,1); XGpioPs_SetOutputEnablePin(&psGpioInstancePtr, 55,1);
随后还可看启动操作系统的函数:
UCOSStartup(MainTask);
在MainTask任务中添加两个LED任务的启动函数:
//初始化任务 OSTaskCreate(TaskLed, (void * )0, (OS_STK *)&TASK_LED_STK[LED_STK_SIZE-1], LED_TASK_Prio); OSTaskCreate(TaskLed1, (void * )0, (OS_STK *)&TASK_LED1_STK[LED1_STK_SIZE-1], LED1_TASK_Prio);
另外在MainTask中保留不断输出任务的代码,起作用是每隔1秒中输出一遍启动时间。
int i=0; while (DEF_TRUE) OSTimeDlyHMSM(0, 0, 1, 0); printf("%d seconds from main task start.\\r\\n",i); i++; ;
四、在调试中解决的几个问题
在SDK中编译代码后将FPGA的配置文件和PS的程序下载到Zynq器件中,运行程序后会发下TaskLed1任务无法正常运行,返回代码中查找问题。
1、任务优先级重新分配
由于TaskLed1任务无法正常运行,我首先想到的是任务优先级分配的问题。首先查看工程xxx_bsp下include文件夹下的os_cfg.h中的宏OS_LOWEST_PRIO,发现该宏定义的最低优先级为63。我自己编写代码中定义的优先级LED_TASK_Prio和LED1_TASK_Prio分别为3和5,远远没有达到这个极限,说明问题不在这里。
随后检查模板中定义的唯一一个任务MainTask的优先级,进入模板中启动这个任务的函数UCOSStartup(MainTask);。其中创建该任务的函数中定义的优先级为UCOS_START_TASK_PRIO,其值居然为5!也就是说我随意定义的任务优先级LED1_TASK_Prio居然和模板中使用的唯一一个优先级是重复的——这就是TaskLed1任务无法正常运行的原因。
2、任务堆栈的修改
不幸的是修改TaskLed1任务的优先级数后,发现这两个任务居然都不能正常运行了。重新对照模板中提供的MainTask代码,来检查自己编写的代码,发现这个任务的堆栈深度居然为784,远大于我分配给TaskLed和TaskLed1两个任务的64!
我尝试将TaskLed和TaskLed1两个任务的堆栈分配为1024后,代码如下:
//设置任务优先级 #define LED_TASK_Prio 3 #define LED1_TASK_Prio 4 //设置任务堆栈大小 #define LED_STK_SIZE 1024 #define LED1_STK_SIZE 1024 //任务堆栈 OS_STK TASK_LED1_STK[LED_STK_SIZE]; OS_STK TASK_LED_STK[LED1_STK_SIZE];
修改后的代码就正常了。分析原因,应该是EMIO操作函数XGpioPs_WritePin(&psGpioInstancePtr, 54, 1);占用的内存较多。但幸运的是,Zynq系统使用DDR内存系统往往达到100MB以上甚至数GB,为每个任务分配数KB的堆栈应该问题不大。
在cortex-m3平台上,UCOS-III为啥不使用SVC来执行pendsv?
【中文标题】在cortex-m3平台上,UCOS-III为啥不使用SVC来执行pendsv?【英文标题】:On the cortex-m3 platform, why does UCOS-III not use SVC to perform pendsv?在cortex-m3平台上,UCOS-III为什么不使用SVC来执行pendsv? 【发布时间】:2018-05-21 07:58:42 【问题描述】:最近在看UCOS-III的源码,有一个关于UCOS-III在Cortex-M3平台上运行时的任务切换问题。它使用 PendSV 进行任务切换,直接写寄存器 SCB_ICSR(中断控制和状态寄存器),但访问寄存器 SCB_ICSR 需要特权操作级别。这意味着处理器在特权操作级别以进程模式运行,没有异常和中断,我认为这并不安全。为什么 UCOS-III 不使用 SVC 来执行 pendsv?是效率问题吗?有人可以为我解释一下吗?谢谢。
背景: 软件:UCOS-III
硬件:Cortex-M3(STM32F103)
代码:
.thumb_func
OSStartHighRdy:
LDR R0, =NVIC_SYSPRI14 @ Set the PendSV
exception priority
LDR R1, =NVIC_PENDSV_PRI
STRB R1, [R0]
MOVS R0, #0 @ Set the PSP to 0 for initial context switch call
MSR PSP, R0
LDR R0, =OS_CPU_ExceptStkBase @ Initialize the MSP to the OS_CPU_ExceptStkBase
LDR R1, [R0]
MSR MSP, R1
LDR R0, =NVIC_INT_CTRL @ Trigger the PendSV exception (causes context switch)
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
CPSIE I @ Enable interrupts at processor level
我认为这种方法更好: Cortex-M3 task switch using SVC and Pensv
-
任务 A 调用 SVC 进行任务切换(例如,等待某些工作完成)。
操作系统收到请求,准备上下文切换,并挂起 PendSV 异常。
当 CPU 退出 SVC 时,它会立即进入 PendSV 并进行上下文切换。
当 PendSV 完成并返回线程级别时,它会执行任务 B。
发生中断并进入中断处理程序。
在运行中断处理程序例程时,会发生 SYSTICK 异常(针对 OS 滴答)。
操作系统执行基本操作,然后挂起 PendSV 异常并准备就绪
用于上下文切换。
当 SYSTICK 异常退出时,它返回到中断服务程序。
当中断服务程序完成时,PendSV 启动并执行实际上下文
切换操作。
当 PendSV 完成后,程序返回线程级;
这次它返回到任务 A 并继续处理。
【问题讨论】:
你问的时候作者说了什么? 我在 micrium 论坛上提出了这个问题,正在等待回复。 【参考方案1】:除非您使用的是 MPU 扩展,否则在用户模式或特权模式下运行并没有太大区别。是的,在用户模式下运行会更安全一些,因为您无法修改所有寄存器,但是您必须提供 SVC 调用才能提高权限并能够创建用户模式或特权模式的任务。我希望当您拥有 MPU 扩展时会提供此功能。
我不了解 UCOS-III,但我会假设所有任务都像大多数 Cortex-M RTOS 一样以特权运行,除非它们支持 MPU。
【讨论】:
通过查看用户手册发现STM32F103支持MPU特性。您能否提供一些在具有 MPU 和 SVC 的 Cortex-M3 平台上运行的 RTOS 示例?谢谢。 如果您下载 FreeRTOS,其中有一些示例,但不确定它们是否是最新的。虽然 MPU 非常有用并提供更好的保护,但除非您了解它的工作原理,否则它会让您的生活更加艰难。我建议阅读 ARM 文档(Cortex-M4 通用用户指南是一个好的开始)。【参考方案2】:例如,SafERTOS 在 Cortex-M3 上使用 MPU。
【讨论】:
这不是问题的答案。以上是关于在Zynq平台上使用uCOS [原创www.cnblogs.com/helesheng]的主要内容,如果未能解决你的问题,请参考以下文章
在cortex-m3平台上,UCOS-III为啥不使用SVC来执行pendsv?
1.uCOS-II简介及移植uCOS-II到STM32F103平台详细步骤