Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1706791
  • 博文数量: 98
  • 博客积分: 667
  • 博客等级: 上士
  • 技术积分: 1631
  • 用 户 组: 普通用户
  • 注册时间: 2009-04-27 15:59
个人简介

一沙一世界 一树一菩提

文章分类

全部博文(98)

文章存档

2021年(8)

2020年(16)

2019年(8)

2017年(1)

2016年(11)

2015年(17)

2014年(9)

2013年(4)

2012年(19)

2011年(1)

2009年(4)

分类: LINUX

2016-02-16 16:47:03

对于apm和pixhawk一直存在疑惑,到现在还不是特别清楚。今天在http://dev.ardupilot.com/看到下面的说明,感觉很有用,对于整体理解amp代码很有帮助,所以记下来。

转载请标记出处,多谢。

理解ArduPilot的线程
当你开始学习ArduPilot的基础库的时候,你最好能理解ArduPilot是怎样处理线程的。代码里的 setup()/loop() 结构继承自arduino,肯能个看起来更像一个单线程系统,事实上不是这样的。
ArduPilot的线程方法依赖于它运行的硬件平台情况。在一些板子如APM1和APM2上,由于硬件不支持多线程,所以这类硬件上的线程其实是简单的定时器和回调函数。而在另外的硬件,如PX4和运行linux系统的硬件上,支持实时钟优先级的Posix线程模型,所以在ArduPilot中,这种线程模型得到广泛的使用。


ArduPilot的学习中,关于线程的内容,有下面几个关键概念需要理解:

1  定时器回调
2  HAL层特定线程
3  驱动特定线程
4  ardupilot驱动与平台驱动
5  平台特定线程和任务
6  ap_scheduler系统
7  信号灯
8  无锁数据结构

定时器回调

在AP_HAL层任何平台都会提供一个1KHz的定时器。在ArduPilot任何代码都可以注册成一个定时器功能函数,以1KHz的频率调用。所有注册的定时器函数按照一定顺序被调用。执行。这种简单的调用机制非常轻便并且非常有用。注册定时器回调方式如下
  hal.scheduler->register_timer_process(AP_HAL_MEMBERPROC(&AP_Baro_MS5611::_update));

这个例子来自MS5611气压计的驱动代码。AP_HAL_MEMBERPROC() 宏把一个c++功能函数封装成一个回调参数。,用函数指针将对象上下文捆绑起来。C++我懂的不多,但是这个语句的意思就是把update函数注册到定时器,然后定时器会以1KHz的频率调用update函数。
有时候,你的代码想以小于1KHz的频率执行,你就需要维护一个自己的“last_called”变量,当距离上次调用的时间不够调用周期时,立即返回即可。这个的实现,你可以通过使用hal.scheduler->millis() 和 hal.scheduler->micros()函数获得从系统启动到现在的时间,这两个函数的单位分别是毫秒和微妙。

看了上面的说明,现在你可以修改一个已经存在的例子,或者新建一段实现代码,然后添加到定时器回调里,让定时器每次递增一个计数器,然后在loop函数中每秒把计数值打印出来。然后修改你的代码让计数器每25毫秒递增计数器,来看看结果是不是和预期的一样。

HAL specific threads

在一些支持实时线程的平台上,平台对应的AP_HAL会创建一些列线程支持基本的操作。例如,在PX4平台上,下面的HAL特定线程会被建立:
    A  UART线程,用于读写平台或板子的UART或USB通信口
    B  定时器线程,这个就是上面说的1KHz的定时器
   C  IO线程,这个线程主要是写 microSD card, EEPROM 或 FRAM
每个平台的AP_HAL实现,你都可以看到在Scheduler.cpp文件中都建立了哪些线程,以及这些线程的实时优先级。如PX4平台,这个文件在libraries\ap_hal_px4\目录下。
在init函数中,建立了这三个线程。创建方式就是linux下大家熟悉的pthread_create。

如果你有一个pixhawk的硬件,你可以连接这个硬件按的serial 5端口和你的pc的串口,pc串口设置57600 8N1,等打印出来一堆信息以后,回车,然后输入ps就会看到下面信息:

PID PRI SCHD TYPE NP STATE NAME
 0 0 FIFO TASK READY Idle Task()
 1 192 FIFO KTHREAD WAITSIG hpwork()
 2 50 FIFO KTHREAD WAITSIG lpwork()
 3 100 FIFO TASK RUNNING init()
 37 180 FIFO TASK WAITSEM AHRS_Test()
 38 181 FIFO PTHREAD WAITSEM (20005400)
 39 60 FIFO PTHREAD READY (20005400)
 40 59 FIFO PTHREAD WAITSEM (20005400)
 10 240 FIFO TASK WAITSEM px4io()
 13 100 FIFO TASK WAITSEM fmuservo()
 30 240 FIFO TASK WAITSEM uavcan()

在上面的现实中你可以看到三个线程:
定时器线程(优先级181), uart线程(优先级60)和io线程(优先级59).

线程的常见用途是给驱动提供一种慢速任务调度的方式。这样不会中断主飞行代码的执行。例如,AP_Terrain库需要操作文件IO,如读写microSD卡,就是放到io线程来做的:

hal.scheduler->register_io_process(AP_HAL_MEMBERPROC(&AP_Terrain::io_timer));
在板级IO线程中,AP_Terrain::io_timer代码定期执行,这意味着这是个低实时优先级,比较适合IO存储操作的任务。像这样的低速IO任务不能在定时器线程调用,因为在处理高速传感器数据的过程中会引起延时,而传感器数据的处理更重要,所以这类IO函数不能放到定时器线程,只能放在IO线程。

Driver specific threads


有时候,需要建立驱动特定线程,支持与一个驱动的特定方式的异步处理。目前你只能以平台相关的方式建立驱动特定线程,所以只有你打算你的驱动之适用于一种autopilot板子时,这种方式才适用。如果你打算让你的驱动运行在多个AP_HAL抽象目标上,通常你有两个做法:
    A    
你可以使用register_io_process() 和 register_timer_process() 调度器来调用已经存在的定时器或IO线程。
   B     你可以添加一个新创建线程的HAL层接口,在多AP_HAL目标上提供一个通用的调用方式(需要发布补丁)
一个驱动特定线程的例子是ToneAlarm线程,  AP_HAL_Linux/ToneAlarmDriver.cpp这个文件,我没有在这个目录下找到这个文件,但是在其它目录找到这个文件了,其实就是在Firmware有一个驱动在和硬件交互,读取数据,然后其它驱动为了省事,直接open设备路径,然后从firmware层读取数据。这种方式其实挺简单,对于临时想替换个驱动,或者减少代码一类的操作,比较快捷,但是有一个最大的不足就是这个代码只能是特定目的的使用。firmware层需要提供这个驱动,并且还需要提供open或者ioctl函数来获取或者设置数据。这属于不按照常理出牌的套路。因为这对于系统整体的维护不利。如果你现在看apm代码是怎么用在pixhawk硬件上的可以明显看到这一点,apm好多代码调用了firmware的接口。

今天先到这里吧,星期6,到公司以下午,就完成这么点东西。都是自己的理解,有不对的地方,欢迎大家一起来讨论。




阅读(1956) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~