首页 > 技术文章 > 《实时控制软件设计》第三周作业

liqi120150 2016-12-16 21:30 原文

一、不同实时操作系统的性能和比较

1、Vxworks

Vxworks在实时操作系统行业内处于领先地位,拥有众多的用户。它支持32位、64位以及多核处理器,支持ARM、Intel、PowerPC等体系。主要应用于国防、航空航天、汽车、医疗和工业领域。它具有优秀的开发环境和软件支持,但是昂贵的价格让开发者望而却步。

2、QNX

QNX是一个分布式、嵌入式、可规模扩展、遵从POSIX规范的类Unix微内核硬实时操作系统。主要用于商用,目标市场主要是面向嵌入式系统。
其内核独立自处于一个被保护的地址空间,驱动程序、网络协议和应用程序处于程序空间中。内核仅提供4种服务:进程调度、进程间通信、底层网络通信和中断处理。所有其它OS服务,都实现为协作的用户进程,在独立的地址空间运行。因此QNX内核非常小巧(QNX4.x大约为12Kb),运行速度极快;操作系统模块与内核相互独立,具有很高的可靠性。而且与UNIX具有高度相似性,使得为数众多的稳定成熟的UNIX、LINUX应用可以直接移植到QNX这个更加稳定高效的实时嵌入式平台上。

3、Xenomai

Xenomai是一个在Linux平台上建立起的通用实时框架的自由软件项目。
早期Xenomai是一种在采用双内核机制时对不能用于强实时应用的Linux内核的扩展,其优先级高于Linux 内核。后来逐渐发展成一个成熟的实时Linux架构。
Xenomai 实时内核为开发强实时应用提供了丰富的功能,主要包括实时线程调度与管理、用户空间实时任务支持、线程同步服务、时钟服务、中断服务、动态内存申请和实时对象注册服务等。主要用于工业自动化行业。

4、Intime

INtime是与 Windows 操作系统共享硬件平台的实时操作系统。
Windows内核驱动程序管理用于运行内核和实时应用程序的内存,并且管理这两个系统之间的通讯接口。INtime 内核为实时虚拟机提供操作系统服务,包括一个基于优先级的抢先式计划程序,该程序执行基于优先级的中断处理。内核计划程序已经进行优化,以获得最佳的中断性能。

4、Sylixos

SylixOS 是一款由中国人自主设计开发的大型嵌入式硬实时操作系统,支持 SMP 多核,具有丰富的文件系统、网络系统以及众多设备驱动支持,并提供完善的集成开发环境。经过多年的持续开发与改进,已经成为一个可靠稳定,功能全面,易于开发调试的嵌入式实时系统开发平台。SylixOS的诞生可以摆脱国内一些关键性设备对国外嵌入式操作系统的依赖,为国内的嵌入式信息技术行业提供一个全新的选择。现已应用于航空航天与国防导弹等领域。

5、ucos

UCOS 是一个可以基于ROM运行的、可裁减的、抢占式、实时多任务内核,具有高度可移植性的嵌入式实时操作系统。特别适合于微处理器和控制器,适合很多商业操作系统。有着完整的网络系统和文件系统,并且开放源代码。功能支持TCP/IP、USB、CAN总线、Modbus。具有一个强大的文件系统和图形用户界面。UCOS采用可剥夺型实时多任务内核,其任务调度是完全基于任务优先级的抢占式调度,具有可靠及高效等特点。在建筑工业控制、医疗设备、航天系统等领域有广泛的应用。

二、团队项目

完成一个团队项目,主要功能是实现一个两轴机械手的运动控制仿真,主要功能包括:

  • 用户接口任务:负责接收来自用户的请求,并发送运动指令给轨迹插补任务。
  • 轨迹插补任务:接收运动指令,实时计算各轴的位置和速度设定值。
  • 物理引擎接口:基于ODE开源物理引擎,创建一个两轴机械手及环境的物理模型,用轨迹插补任务输出的各轴位置和速度设定值控制模型的运动,并把实时状态反馈给轨迹插补任务。
  • 图形化用户接口:可基于qt把上述功能集成到一个GUI界面。

我对轨迹插补任务和图形化用户接口比较感兴趣。我之前参与过一些嵌入式项目,有较强的编写代码的能力。在《数控系统》的学习过程中,就对插补很感兴趣。另外,我也希望可以接触一些图形化的编程,学习一下这方面的知识。

三、编程实现定位运动轨迹生成器

在task_trajectory_generator任务中增加代码来处理来自task_command_sender的定位运动命令new_cmd,按照该命令中给出的位置、速度、加速度、减速度,实现一个梯形加减速(原理见数控技术教材)的运动轨迹生成器,当达到目标位置时,把new_cmd.Done设为true,主程序检测到new_cmd.Done为true时将结束运行。
编程要求:

  • 轨迹生成器的代码结构能充分体现出基于状态机的编程方法。
  • 把第一个可以运行的版本发布到自己的github账号上,并不断优化和提交。
  • 创建一个TODO.md文档写下自己的编程思路和下一步要做的工作。

为了缩短运行时间和改善输出的效果,我改变了new_cmd命令中的Position值,注释掉了Task Time的printf部分,并将task_trajectory_generator的任务周期改为0.1s。
V1.0代码已发布到GitHub上

https://github.com/liqi120150/simple_motion.git

如下:

#include <stdio.h>
#include <math.h>
#include <signal.h>
#include <unistd.h>
#include <sys/mman.h>

#include <native/task.h>
#include <native/timer.h>

// Data Type

typedef struct
{
    bool Request;
    bool Response;
    bool Done;
    double Position;
    double Velocity;
    double Acceleration;
    double Deceleration;
    double Jerk;
} tPosCmd;

typedef struct
{
    double Position;
    double Velocity;
} tAxisSetpoint;

//Global variables

RT_TASK task_trajectory_generator;
RT_TASK task_command_sender;

tPosCmd new_cmd;
tAxisSetpoint axis1_setpoint;

int cycle_count = 0;

/*****************************************************
 * status machine
 * 
*****************************************************/
typedef enum
{
	Idel = 0,
	Accelerate,
	Decelerate,
	UniformVelocity	
}trajectoryStatusDef;

#define Position_DeadSize	10
#define Velocity_DeadSize	10
void task_trajectory_generator_proc(void *arg)
{
	RTIME now, previous;
	trajectoryStatusDef Status = Idel;

	/*
	 * Arguments: &task (NULL=self),
	 *            start time,
	 *            period (here: 0.1 s)
	 */
	rt_task_set_periodic(NULL, TM_NOW, 100000000);
	previous = rt_timer_read();

	axis1_setpoint.Position = 0;
	axis1_setpoint.Velocity = 0;

	int temp;

	float delta_T;
	while (1) {
		rt_task_wait_period(NULL);
		now = rt_timer_read();

		/*
		 * NOTE: printf may have unexpected impact on the timing of
		 *       your program. It is used here in the critical loop
		 *       only for demonstration purposes.
		 */
		/*
		printf("Task A Time since last turn: %ld.%06ld ms\n",
		       (long)(now - previous) / 1000000,
		       (long)(now - previous) % 1000000);
		*/
		       

		//  Add your code
		delta_T = ((double)(now - previous) / 1000000000);
		previous = now;
		
		if(new_cmd.Request)
		{
			printf("Position:%10.3f,Velocity:%10.3f at time:%10.3f s.\r\n", 
				axis1_setpoint.Position,
				axis1_setpoint.Velocity,
				(double)now / 1000000000);
			if(fabs(new_cmd.Position - axis1_setpoint.Position) > Position_DeadSize)
			{
				if(fabs(new_cmd.Velocity - axis1_setpoint.Velocity) > Velocity_DeadSize)
				{
					if(fabs((axis1_setpoint.Velocity * axis1_setpoint.Velocity) / (2*new_cmd.Deceleration)) >= fabs(new_cmd.Position - axis1_setpoint.Position))
					{
						if(axis1_setpoint.Velocity > 0)
							Status = Decelerate;
						else
							Status = Accelerate;
					}
					else if(new_cmd.Velocity > axis1_setpoint.Velocity)
						Status = Accelerate;
					else
						Status = Decelerate;
				}
				else
				{
					if(fabs((axis1_setpoint.Velocity * axis1_setpoint.Velocity) / (2*new_cmd.Deceleration)) >= fabs(new_cmd.Position - axis1_setpoint.Position))
					{
						if(axis1_setpoint.Velocity > 0)
							Status = Decelerate;
						else
							Status = Accelerate;
					}
					else
						Status = UniformVelocity;
				}
			}
			else
			{
				Status = Idel;
				new_cmd.Response = true;
				new_cmd.Done = true;
			}
			switch(Status)
			{
				case Accelerate:
					//s = s0 + v0*delta_T + 0.5*acce*(delta_T^2)
					axis1_setpoint.Position += axis1_setpoint.Velocity*delta_T + 0.5*new_cmd.Acceleration * (delta_T * delta_T);
					//v = v0 + acce*delta_T
					axis1_setpoint.Velocity += new_cmd.Acceleration*delta_T;
					break;
				case Decelerate:
					//s = s0 + v0*delta_T - 0.5*Deceleration*(delta_T^2)
					axis1_setpoint.Position += axis1_setpoint.Velocity*delta_T - 0.5*new_cmd.Deceleration * (delta_T * delta_T);
					//v = v0 - Deceleration*delta_T
					axis1_setpoint.Velocity -= new_cmd.Deceleration*delta_T;	
					break;
				case UniformVelocity:
					//s = s0 + v0*delta_T;
					axis1_setpoint.Position += axis1_setpoint.Velocity*delta_T;
					break;
				case Idel:
					break;
				default:
					break;
			}
		}
			   
		
	}
}

void task_command_sender_proc(void *arg)
{
	RTIME now, previous;

	/*
	 * Arguments: &task (NULL=self),
	 *            start time,
	 *            period (here: 2 s)
	 */
	rt_task_set_periodic(NULL, TM_NOW, 2000000000);
	previous = rt_timer_read();

        new_cmd.Request = false;
        new_cmd.Response = false;
        new_cmd.Done = false;
        new_cmd.Position = 0;
        new_cmd.Velocity = 0;
        new_cmd.Acceleration = 0;
        new_cmd.Deceleration = 0;
        new_cmd.Jerk = 0;

	while (1) {
		rt_task_wait_period(NULL);
		now = rt_timer_read();

		/*
		 * NOTE: printf may have unexpected impact on the timing of
		 *       your program. It is used here in the critical loop
		 *       only for demonstration purposes.
		 */
		 /*
		printf("Task B Time since last turn: %ld.%06ld ms\n",
		       (long)(now - previous) / 1000000,
		       (long)(now - previous) % 1000000);
		*/
		       previous = now;
                cycle_count = cycle_count + 1;
                printf("cycle_count:%d\n",cycle_count);
        
                if(cycle_count == 5)
                {
                    new_cmd.Request = true;
                    new_cmd.Response = false;
                    new_cmd.Done = false;
                    new_cmd.Position = 20000;
                    new_cmd.Velocity = 1000;
                    new_cmd.Acceleration = 50;
                    new_cmd.Deceleration = 50;
                    new_cmd.Jerk = 0;

                }

	}
}

void catch_signal(int sig)
{

}


int main(int argc, char* argv[])
{
	signal(SIGTERM, catch_signal);
	signal(SIGINT, catch_signal);

	/* Avoids memory swapping for this program */
	mlockall(MCL_CURRENT|MCL_FUTURE);

        printf("A simple motion control demo\n");
	/*
	 * Arguments: &task,
	 *            name,
	 *            stack size (0=default),
	 *            priority,
	 *            mode (FPU, start suspended, ...)
	 */
	rt_task_create(&task_trajectory_generator, "rttask_trajectory_generator", 0, 99, 0);
	rt_task_create(&task_command_sender, "rttask_command_sender", 0, 98, 0);

	/*
	 * Arguments: &task,
	 *            task function,
	 *            function argument
	 */
	rt_task_start(&task_trajectory_generator, &task_trajectory_generator_proc, NULL);
	rt_task_start(&task_command_sender, &task_command_sender_proc, NULL);

        while(!new_cmd.Done);
        printf("End! \n");
	rt_task_delete(&task_trajectory_generator);
	rt_task_delete(&task_command_sender);
	return 0;
}

代码运行效果:

显然,目前的代码利用全局变量进行任务间的通信,且一次只能发送一个定位命令。计划下一步使用Queue来进行通信,实现命令的缓冲和运动轨迹的平滑工作。

推荐阅读