PX4 FMU [7] rgbled
PX4 FMU [7] rgbled
-------- 转载请注明出处
-------- 更多笔记请访问我的博客:merafour.blog.163.com
-------- 2014-12-15.冷月追风
-------- email:merafour@163.com
1. start
我们在命令行启动 rgbled的命令如下:
if rgbled start
then
set HAVE_RGBLED 1
rgbled rgb 16 16 16
else
set HAVE_RGBLED 0
fi
入口函数如下:
int rgbled_main(int argc, char *argv[])
{
int i2cdevice = -1;
int rgbledadr = ADDR; /* 7bit */
int ch;
/* jump over start/off/etc and look at options first */
while ((ch = getopt(argc, argv, "a:b:")) != EOF) {
switch (ch) {
case 'a':
rgbledadr = strtol(optarg, NULL, 0);
break;
case 'b':
i2cdevice = strtol(optarg, NULL, 0);
break;
default:
rgbled_usage();
exit(0);
}
}
if (optind >= argc) {
rgbled_usage();
exit(1);
}
const char *verb = argv[optind];
int fd;
int ret;
if (!strcmp(verb, "start")) {
if (g_rgbled != nullptr)
errx(1, "already started");
if (i2cdevice == -1) {
// try the external bus first
i2cdevice = PX4_I2C_BUS_EXPANSION;
g_rgbled = new RGBLED(PX4_I2C_BUS_EXPANSION, rgbledadr);
if (g_rgbled != nullptr && OK != g_rgbled->init()) {
delete g_rgbled;
g_rgbled = nullptr;
}
if (g_rgbled == nullptr) {
// fall back to default bus
if (PX4_I2C_BUS_LED == PX4_I2C_BUS_EXPANSION) {
errx(1, "init failed");
}
i2cdevice = PX4_I2C_BUS_LED;
}
}
if (g_rgbled == nullptr) {
g_rgbled = new RGBLED(i2cdevice, rgbledadr);
if (g_rgbled == nullptr)
errx(1, "new failed");
if (OK != g_rgbled->init()) {
delete g_rgbled;
g_rgbled = nullptr;
errx(1, "init failed");
}
}
exit(0);
}
/* need the driver past this point */
if (g_rgbled == nullptr) {
warnx("not started");
rgbled_usage();
exit(1);
}
if (!strcmp(verb, "test")) {
fd = open(RGBLED_DEVICE_PATH, 0);
if (fd == -1) {
errx(1, "Unable to open " RGBLED_DEVICE_PATH);
}
rgbled_pattern_t pattern = { {RGBLED_COLOR_RED, RGBLED_COLOR_GREEN, RGBLED_COLOR_BLUE, RGBLED_COLOR_WHITE, RGBLED_COLOR_OFF, RGBLED_COLOR_OFF},
{500, 500, 500, 500, 1000, 0 } // "0" indicates end of pattern
};
ret = ioctl(fd, RGBLED_SET_PATTERN, (unsigned long)&pattern);
ret = ioctl(fd, RGBLED_SET_MODE, (unsignedlong)RGBLED_MODE_PATTERN);
close(fd);
exit(ret);
}
if (!strcmp(verb, "info")) {
g_rgbled->info();
exit(0);
}
if (!strcmp(verb, "off") || !strcmp(verb, "stop")) {
fd = open(RGBLED_DEVICE_PATH, 0);
if (fd == -1) {
errx(1, "Unable to open " RGBLED_DEVICE_PATH);
}
ret = ioctl(fd, RGBLED_SET_MODE, (unsignedlong)RGBLED_MODE_OFF);
close(fd);
exit(ret);
}
if (!strcmp(verb, "stop")) {
delete g_rgbled;
g_rgbled = nullptr;
exit(0);
}
if (!strcmp(verb, "rgb")) {
if (argc < 5) {
errx(1, "Usage: rgbled rgb <red> <green> <blue>");
}
fd = open(RGBLED_DEVICE_PATH, 0);
if (fd == -1) {
errx(1, "Unable to open " RGBLED_DEVICE_PATH);
}
rgbled_rgbset_t v;
v.red = strtol(argv[2], NULL, 0);
v.green = strtol(argv[3], NULL, 0);
v.blue = strtol(argv[4], NULL, 0);
ret = ioctl(fd, RGBLED_SET_RGB, (unsigned long)&v);
ret = ioctl(fd, RGBLED_SET_MODE, (unsigned long)RGBLED_MODE_ON);
close(fd);
exit(ret);
}
rgbled_usage();
exit(0);
}
我们看到,在 start中跟 MPU6050的不同在于这里没有单独做一个 start接口。这也说明 rgbled的 start过程要比 MPU6050要简单得多。主要是创建了 RGBLED对象,并调用其 init函数。然后是 rgb,rgb呢主要是获取我们命令中的 RGB值,因为我们知道这是用来控制板子上的那个 RGBLED的。发送数据使用的是 ioctl接口。当然,在 Linux设备驱动中通常是用 read接口来读数据,在 MPU6050中就是这样。
RGBLED::RGBLED(int bus, int rgbled) :
I2C("rgbled", RGBLED_DEVICE_PATH, bus, rgbled, 100000),
_mode(RGBLED_MODE_OFF),
_r(0),
_g(0),
_b(0),
_brightness(1.0f),
_running(false),
_led_interval(0),
_should_run(false),
_counter(0)
{
memset(&_work, 0, sizeof(_work));
memset(&_pattern, 0, sizeof(_pattern));
}
I2C::I2C(const char *name,
const char *devname,
int bus,
uint16_t address,
uint32_t frequency,
int irq) :
// base class
CDev(name, devname, irq),
// public
// protected
_retries(0),
// private
_bus(bus),
_address(address),
_frequency(frequency),
_dev(nullptr)
{
// fill in _device_id fields for a I2C device
_device_id.devid_s.bus_type = DeviceBusType_I2C;
_device_id.devid_s.bus = bus;
_device_id.devid_s.address = address;
// devtype needs to be filled in by the driver
_device_id.devid_s.devtype = 0;
}
I2C跟 SPI一样都继承自 CDev,所以它们之间很多东西都是相通的。对 C++不太了解的可能会觉得在给 I2C传递参数的过程中有点莫名其妙,会觉得少了一个参数。其实你去看构造函数的定义你就会知道, irq有一个初值,即 0。当我们调用的时候不提供该参数的时候 irq即为 0。其它参数我们主要看两个: devname跟 address。前者是设备文件名,后者是设备地址。其值为:
/* more devices will be 1, 2, etc */
#define RGBLED_DEVICE_PATH "/dev/rgbled0"
#define ADDR PX4_I2C_OBDEV_LED /**< I2C adress of TCA62724FMG */
#define PX4_I2C_OBDEV_LED 0x55
如果你有兴趣你可以去查看 PX4所使用的驱动芯片的 I2C地址。我没去研究硬件,就不深究了。
其实构造函数中还有一个参数:bus。在这里, bus应该也指的是总线。那么它是指什么总线呢?
我们会在 init函数中看到 bus被使用了:
int I2C::init()
{
int ret = OK;
/* attach to the i2c bus */
_dev = up_i2cinitialize(_bus);
if (_dev == nullptr) {
debug("failed to init I2C");
ret = -ENOENT;
goto out;
}
// call the probe function to check whether the device is present
ret = probe();
if (ret != OK) {
debug("probe failed");
goto out;
}
// do base class init, which will create device node, etc
ret = CDev::init();
if (ret != OK) {
debug("cdev init failed");
goto out;
}
// tell the world where we are
log("on I2C bus %d at 0x%02x", _bus, _address);
out:
return ret;
}
在 SPI中我们也遇到个 up函数,返回的也是 dev,后面就是通过这个 dev跟真正的硬件打交道。
bitcraze@bitcraze-vm:~/apm$ grep -nr up_i2cinitialize ./PX4NuttX/nuttx/arch/arm/src/stm32
./PX4NuttX/nuttx/arch/arm/src/stm32/stm32_i2c.c:1888: * Name: up_i2cinitialize
./PX4NuttX/nuttx/arch/arm/src/stm32/stm32_i2c.c:1895:FAR struct i2c_dev_s *up_i2cinitialize(int port)
bitcraze@bitcraze-vm:~/apm$
FAR struct i2c_dev_s *up_i2cinitialize(int port)
{
struct stm32_i2c_priv_s * priv = NULL; /* private data of device with multiple instances */
struct stm32_i2c_inst_s * inst = NULL; /* device, single instance */
int irqs;
#if STM32_PCLK1_FREQUENCY < 4000000
# warning STM32_I2C_INIT: Peripheral clock must be at least 4 MHz to support 400 kHz operation.
#endif
#if STM32_PCLK1_FREQUENCY < 2000000
# warning STM32_I2C_INIT: Peripheral clock must be at least 2 MHz to support 100 kHz operation.
return NULL;
#endif
/* Get I2C private structure */
switch (port)
{
#ifdef CONFIG_STM32_I2C1
case 1:
priv = (struct stm32_i2c_priv_s *)&stm32_i2c1_priv;
break;
#endif
#ifdef CONFIG_STM32_I2C2
case 2:
priv = (struct stm32_i2c_priv_s *)&stm32_i2c2_priv;
break;
#endif
#ifdef CONFIG_STM32_I2C3
case 3:
priv = (struct stm32_i2c_priv_s *)&stm32_i2c3_priv;
break;
#endif
default:
return NULL;
}
/* Allocate instance */
if (!(inst = kmalloc( sizeof(struct stm32_i2c_inst_s))))
{
return NULL;
}
/* Initialize instance */
inst->ops = &stm32_i2c_ops;
inst->priv = priv;
inst->frequency = 100000;
inst->address = 0;
inst->flags = 0;
/* Init private data for the first time, increment refs count,
* power-up hardware and configure GPIOs.
*/
irqs = irqsave();
if ((volatile int)priv->refs++ == 0)
{
stm32_i2c_sem_init( (struct i2c_dev_s *)inst );
stm32_i2c_init( priv );
}
irqrestore(irqs);
return (struct i2c_dev_s *)inst;
}
跟 SPI是类似的。从代码来看,bus指的应该是所外部设备所挂载的 I2C控制器,因为 STM32存在多个 I2C控制器,而上层获取操作接口都是通过同一个函数,这样就需要区分不同的硬件接口,这就是 bus的作用。在 SPI中其实也一样。其实我们也很容易想到 priv不是用来操作硬件的, ops才是。只要稍微看下相关源码就清楚了。
下面我们来看看 init。
int RGBLED::init()
{
int ret;
ret = I2C::init();
if (ret != OK) {
return ret;
}
/* switch off LED on start */
send_led_enable(false);
send_led_rgb();
return OK;
}
通常来讲 init都是用来初始化的。这里其实也就是初始化。先是初始化 I2C,然后就把 LED给关了。在 I2C初始化过程中我们前面看到,它调用了一个 probe函数。在 Linux中写 platform驱动的时候我们经常碰到这个接口,在设备跟驱动匹配成功时会调用这个驱动,用来做一些初始化工作。在这里其实都差不多。
int RGBLED::probe()
{
int ret;
bool on, powersave;
uint8_t r, g, b;
/**
this may look strange, but is needed. There is a serial
EEPROM (Microchip-24aa01) on the PX4FMU-v1 that responds to
a bunch of I2C addresses, including the 0x55 used by this
LED device. So we need to do enough operations to be sure
we are talking to the right device. These 3 operations seem
to be enough, as the 3rd one consistently fails if no
RGBLED is on the bus.
*/
if ((ret=get(on, powersave, r, g, b)) != OK ||
(ret=send_led_enable(false) != OK) ||
(ret=send_led_enable(false) != OK)) {
return ret;
}
return ret;
}
单从代码上看,这里有点让人费解。而从注释来看是为了确保我们操作对的设备。可能要详细理解这段代码还需要专门花点时间,毕竟我们现在只是从软件方面解读 PX4。
2. 控制LED
test其实已经提供了控制 LED的示例,即通过 ioctl进行控制。那么在应用层又是如何控制 LED的呢?
bitcraze@bitcraze-vm:~/apm$ grep -nr RGBLED_DEVICE_PATH ./ardupilot/
./ardupilot/libraries/AP_Notify/ToshibaLED_PX4.cpp:38: _rgbled_fd = open(RGBLED_DEVICE_PATH, 0);
./ardupilot/libraries/AP_Notify/ToshibaLED_PX4.cpp:40: hal.console->printf("Unable to open " RGBLED_DEVICE_PATH);
bitcraze@bitcraze-vm:~/apm$
bool ToshibaLED_PX4::hw_init()
{
// open the rgb led device
_rgbled_fd = open(RGBLED_DEVICE_PATH, 0);
if (_rgbled_fd == -1) {
hal.console->printf("Unable to open " RGBLED_DEVICE_PATH);
return false;
}
ioctl(_rgbled_fd, RGBLED_SET_MODE, (unsigned long)RGBLED_MODE_ON);
last.zero();
next.zero();
hal.scheduler->register_io_process(AP_HAL_MEMBERPROC(&ToshibaLED_PX4::update_timer));
return true;
}
这里打开了我们的 LED设备,而且通过 ioctl对设备进行了一次操作。那么可以想象,后面肯定都是通过 _rgbled_fd对设备进行操作的。
// set_rgb - set color as a combination of red, green and blue values
bool ToshibaLED_PX4::hw_set_rgb(uint8_t red, uint8_t green, uint8_t blue)
{
hal.scheduler->suspend_timer_procs();
next[0] = red;
next[1] = green;
next[2] = blue;
hal.scheduler->resume_timer_procs();
return true;
}
void ToshibaLED_PX4::update_timer(void)
{
if (last == next) {
return;
}
rgbled_rgbset_t v;
v.red = next[0];
v.green = next[1];
v.blue = next[2];
ioctl(_rgbled_fd, RGBLED_SET_RGB, (unsigned long)&v);
last = next;
}
真实情况也正是这样。但是这个时候如果继续跟踪代码我相信你会跟我一样抓狂。
radiolink@ubuntu:~/px4$ grep -nr ToshibaLED_PX4 ./ardupilot/
./ardupilot/libraries/AP_Notify/AP_Notify.h:25:#include <ToshibaLED_PX4.h>
./ardupilot/libraries/AP_Notify/AP_Notify.h:66: ToshibaLED_PX4 toshibaled;
./ardupilot/libraries/AP_Notify/ToshibaLED_PX4.h:25:class ToshibaLED_PX4 : public ToshibaLED
./ardupilot/libraries/AP_Notify/examples/ToshibaLED_test/ToshibaLED_test.pde:35:static ToshibaLED_PX4 toshiba_led;
./ardupilot/libraries/AP_Notify/ToshibaLED_PX4.cpp:22:#include"ToshibaLED_PX4.h"
./ardupilot/libraries/AP_Notify/ToshibaLED_PX4.cpp:35:boolToshibaLED_PX4::hw_init()
./ardupilot/libraries/AP_Notify/ToshibaLED_PX4.cpp:46: hal.scheduler->register_io_process(AP_HAL_MEMBERPROC(&ToshibaLED_PX4::update_timer));
./ardupilot/libraries/AP_Notify/ToshibaLED_PX4.cpp:51:boolToshibaLED_PX4::hw_set_rgb(uint8_t red, uint8_t green, uint8_t blue)
./ardupilot/libraries/AP_Notify/ToshibaLED_PX4.cpp:61:voidToshibaLED_PX4::update_timer(void)
./ardupilot/module.mk:3:SRCS =
radiolink@ubuntu:~/px4$
SRCS内容特别长,因为 SRCS记录了 ardupilot这个应用程序所用到的所有源文件。从这段匹配结果来看,我们很容易知道我们的 LED是由 "ToshibaLED_test.pde"进行控制的,但你可别忘了它的路径中可是有个 "examples"!你再取看它的源码:
void setup(void)
{
}
void loop(void)
{
}
AP_HAL_MAIN();
从这里看我们似乎已经找错方向了,却不知错误是从什么时候开始的。于是我开始怀疑 PX4运行的时候到底是不是通过 rgbled来进行控制的。于是我想了一招,执行 "rgbled stop"命令,结构灯真的就不亮了。然后我在执行 "rgbled rgb 16 16 16"命令,灯又开始闪烁了。那么也许 rgb就会是我们救命的稻草了。
3.rgb
在 rgbled的入口函数中 rgb的内容如下:
if (!strcmp(verb, "rgb")) {
if (argc < 5) {
errx(1, "Usage: rgbled rgb <red> <green> <blue>");
}
fd = open(RGBLED_DEVICE_PATH, 0);
if (fd == -1) {
errx(1, "Unable to open " RGBLED_DEVICE_PATH);
}
rgbled_rgbset_t v;
v.red = strtol(argv[2], NULL, 0);
v.green = strtol(argv[3], NULL, 0);
v.blue = strtol(argv[4], NULL, 0);
ret = ioctl(fd, RGBLED_SET_RGB, (unsigned long)&v);
ret = ioctl(fd, RGBLED_SET_MODE, (unsigned long)RGBLED_MODE_ON);
close(fd);
exit(ret);
}
貌似,好像这段代码除了调用了几个接口也没有什么特别之处,但根据前面的判断秘密肯定就藏在这里,除非说这一次我又判断失误了。既然已经肯定这里有秘密,用排除法 ioctl是最有嫌疑的。
int RGBLED::ioctl(struct file *filp, int cmd, unsigned long arg)
{
int ret = ENOTTY;
switch (cmd) {
case RGBLED_SET_RGB:
/* set the specified color */
_r = ((rgbled_rgbset_t *) arg)->red;
_g = ((rgbled_rgbset_t *) arg)->green;
_b = ((rgbled_rgbset_t *) arg)->blue;
send_led_rgb();
return OK;
case RGBLED_SET_COLOR:
/* set the specified color name */
set_color((rgbled_color_t)arg);
send_led_rgb();
return OK;
case RGBLED_SET_MODE:
/* set the specified mode */
set_mode((rgbled_mode_t)arg);
return OK;
case RGBLED_SET_PATTERN:
/* set a special pattern */
set_pattern((rgbled_pattern_t *)arg);
return OK;
default:
/* see if the parent class can make any use of it */
ret = CDev::ioctl(filp, cmd, arg);
break;
}
return ret;
}
所以我们看到,这里又引出了另外两个函数: send_led_rgb和 set_mode。其源码如下:
int RGBLED::send_led_rgb()
{
/* To scale from 0..255 -> 0..15 shift right by 4 bits */
const uint8_t msg[6] = {
SUB_ADDR_PWM0, (uint8_t)((int)(_b * _brightness) >> 4),
SUB_ADDR_PWM1, (uint8_t)((int)(_g * _brightness) >> 4),
SUB_ADDR_PWM2, (uint8_t)((int)(_r * _brightness) >> 4)
};
return transfer(msg, sizeof(msg), nullptr, 0);
}
void RGBLED::set_mode(rgbled_mode_t mode)
{
if (mode != _mode) {
_mode = mode;
switch (mode) {
default:
warnx("mode unknown");
break;
}
/* if it should run now, start the workq */
if (_should_run && !_running) {
_running = true;
work_queue(LPWORK, &_work, (worker_t)&RGBLED::led_trampoline, this, 1);
}
}
}
mode部分源码我没有全部粘出来。
可能你会觉得这里除了调用了几个函数似乎也并没有什么特别,但是请记住,这里调用了一个 work_queue函数,也就是说用到了工作队列,其定义如下:
int work_queue(int qid, FAR struct work_s *work, worker_t worker,
FAR void *arg, uint32_t delay)
所以,在延时 delay之后会调用 RGBLED::led_trampoline这个函数。那么它又干了些啥事呢?
void RGBLED::led_trampoline(void *arg)
{
RGBLED *rgbl = reinterpret_cast<RGBLED *>(arg);
rgbl->led();
}
/**
* Main loop function
*/
void RGBLED::led()
{
if (!_should_run) {
_running = false;
return;
}
switch (_mode) {
case RGBLED_MODE_BLINK_SLOW:
case RGBLED_MODE_BLINK_NORMAL:
case RGBLED_MODE_BLINK_FAST:
if (_counter >= 2)
_counter = 0;
send_led_enable(_counter == 0);
break;
case RGBLED_MODE_BREATHE:
if (_counter >= 62)
_counter = 0;
int n;
if (_counter < 32) {
n = _counter;
} else {
n = 62 - _counter;
}
_brightness = n * n / (31.0f * 31.0f);
send_led_rgb();
break;
case RGBLED_MODE_PATTERN:
/* don't run out of the pattern array and stop if the next frame is 0 */
if (_counter >= RGBLED_PATTERN_LENGTH || _pattern.duration[_counter] <= 0)
_counter = 0;
set_color(_pattern.color[_counter]);
send_led_rgb();
_led_interval = _pattern.duration[_counter];
break;
default:
break;
}
_counter++;
/* re-queue ourselves to run again later */
work_queue(LPWORK, &_work, (worker_t)&RGBLED::led_trampoline, this, _led_interval);
}
由于 rgb中设置 _mode为 RGBLED_MODE_ON,所以必须有谁去修改它的值,否则 LED将一直处于亮的状态,而不是闪烁。
当然,看到这里我们知道 LED如何能够保持闪烁,但是如何改变 LED状态,如颜色呢?从上面的代码看是跟 _mode这个变量有关,但继续跟踪代码你就会发现你只能通过 set_mode函数来设置 _mode,但是这个操作又只有通过 ioctl的 RGBLED_SET_MODE命令来调用。
radiolink@ubuntu:~/apm$ grep -nr RGBLED_SET_MODE ./
./PX4Firmware/src/modules/commander/commander_helper.cpp:272: ioctl(rgbleds, RGBLED_SET_MODE, (unsigned long)mode);
./PX4Firmware/src/drivers/drv_rgbled.h:76:#defineRGBLED_SET_MODE _RGBLEDIOC(6)
./PX4Firmware/src/drivers/drv_rgbled.h:112:/* enum passed to RGBLED_SET_MODE ioctl()*/
./PX4Firmware/src/drivers/drv_io_expander.h:66:/* enum passed to RGBLED_SET_MODE ioctl()*/
./PX4Firmware/src/drivers/rgbled/rgbled.cpp:234: case RGBLED_SET_MODE:
./PX4Firmware/src/drivers/rgbled/rgbled.cpp:663: ret = ioctl(fd, RGBLED_SET_MODE, (unsigned long)RGBLED_MODE_PATTERN);
./PX4Firmware/src/drivers/rgbled/rgbled.cpp:681: ret = ioctl(fd, RGBLED_SET_MODE, (unsigned long)RGBLED_MODE_OFF);
./PX4Firmware/src/drivers/rgbled/rgbled.cpp:708: ret = ioctl(fd, RGBLED_SET_MODE, (unsigned long)RGBLED_MODE_ON);
./ardupilot/libraries/AP_Notify/ToshibaLED_PX4.cpp:43: ioctl(_rgbled_fd, RGBLED_SET_MODE, (unsigned long)RGBLED_MODE_ON);
radiolink@ubuntu:~/apm$
从匹配结果来判断这个操作是在 commander_helper.cpp中完成的。首先排除 rgbled本身, ToshibaLED_PX4.cpp前面我们分析的就是它,确定是死路一条,况且它设置的是固定的模式。于是剩下的就只有 commander_helper.cpp了,从匹配结果来看,它可以设置多种模式。
但是问题也随之而来,当你继续跟踪代码的时候,你会最终找到一个函数:commander_main。从函数名我们很容易想到它对应了一个命令: commander。但是很遗憾,脚本中根本就没有该命令。而且,当你尝试进行代码匹配你会发现:
radiolink@ubuntu:~/apm$ grep -nr commander_main ./
./PX4Firmware/src/modules/commander/commander.cpp:200:extern "C" __EXPORT int commander_main(int argc, char *argv[]);
./PX4Firmware/src/modules/commander/commander.cpp:246:intcommander_main(int argc, char *argv[])
radiolink@ubuntu:~/apm$
于是我也只能无语了。那么是否就到此为止了呢?事情还远远没有结束!
既然这条路已经走不通了,那么只能回过头来再考虑 ToshibaLED_PX4.cpp了。那么我们前面到底忽略了什么呢?
当我回过头去看前面的笔记时,我惊讶地发现:
radiolink@ubuntu:~/px4$ grep -nr ToshibaLED_PX4 ./ardupilot/
./ardupilot/libraries/AP_Notify/AP_Notify.h:25:#include <ToshibaLED_PX4.h>
./ardupilot/libraries/AP_Notify/AP_Notify.h:66: ToshibaLED_PX4 toshibaled;
./ardupilot/libraries/AP_Notify/ToshibaLED_PX4.h:25:class ToshibaLED_PX4 : public ToshibaLED
./ardupilot/libraries/AP_Notify/examples/ToshibaLED_test/ToshibaLED_test.pde:35:static ToshibaLED_PX4 toshiba_led;
./ardupilot/libraries/AP_Notify/ToshibaLED_PX4.cpp:22:#include"ToshibaLED_PX4.h"
./ardupilot/libraries/AP_Notify/ToshibaLED_PX4.cpp:35:boolToshibaLED_PX4::hw_init()
./ardupilot/libraries/AP_Notify/ToshibaLED_PX4.cpp:46: hal.scheduler->register_io_process(AP_HAL_MEMBERPROC(&ToshibaLED_PX4::update_timer));
./ardupilot/libraries/AP_Notify/ToshibaLED_PX4.cpp:51:boolToshibaLED_PX4::hw_set_rgb(uint8_t red, uint8_t green, uint8_t blue)
./ardupilot/libraries/AP_Notify/ToshibaLED_PX4.cpp:61:voidToshibaLED_PX4::update_timer(void)
./ardupilot/module.mk:3:SRCS =
radiolink@ubuntu:~/px4$
=前面我看到这段信息就很直接地以为是在 ToshibaLED_test.pde中对 LED进行控制,但是我们忽略了一个头文件: AP_Notify.h。因为我一直都是用 C的,所以很自然地以为这里只是普通的声明。所以我忘了一点:这是 PX4源码,用的是 C++!而事情也正是在这里峰回路转了。
class AP_Notify
{
public:
/// notify_type - bitmask of notification types
struct notify_type {
uint16_t initialising : 1; // 1 if initialising and copter should not be moved
uint16_t gps_status : 3; // 0 = no gps, 1 = no lock, 2 = 2d lock, 3 = 3d lock, 4 = dgps lock, 5 = rtk lock
uint16_t gps_glitching : 1; // 1 if gps position is not good
uint16_t armed : 1; // 0 = disarmed, 1 = armed
uint16_t pre_arm_check : 1; // 0 = failing checks, 1 = passed
uint16_t save_trim : 1; // 1 if gathering trim data
uint16_t esc_calibration : 1; // 1 if calibrating escs
uint16_t failsafe_radio : 1; // 1 if radio failsafe
uint16_t failsafe_battery : 1; // 1 if battery failsafe
uint16_t failsafe_gps : 1; // 1 if gps failsafe
uint16_t arming_failed : 1; // 1 if copter failed to arm after user input
uint16_t parachute_release : 1; // 1 if parachute is being released
// additional flags
uint16_t external_leds : 1; // 1 if external LEDs are enabled (normally only used for copter)
};
// the notify flags are static to allow direct class access
// without declaring the object
static struct notify_type flags;
// initialisation
void init(bool enable_external_leds);
/// update - allow updates of leds that cannot be updated during a timed interrupt
void update(void);
private:
// individual drivers
AP_BoardLED boardled;
#if CONFIG_HAL_BOARD == HAL_BOARD_PX4
ToshibaLED_PX4 toshibaled;
ToneAlarm_PX4 tonealarm;
#elif CONFIG_HAL_BOARD == HAL_BOARD_APM1 || CONFIG_HAL_BOARD == HAL_BOARD_APM2
ToshibaLED_I2C toshibaled;
ExternalLED externalled;
Buzzer buzzer;
#elif CONFIG_HAL_BOARD == HAL_BOARD_VRBRAIN
ToshibaLED_I2C toshibaled;
ExternalLED externalled;
Buzzer buzzer;
#else
ToshibaLED_I2C toshibaled;
#endif
};
所以我们看到,这里对应的是一个类: AP_Notify。如果你去查 "notify"这个单词,你会看到它是 “通告,通知;公布”的意思。这样一来就说得通了。而这里的 flags请注意,它前面可有个 "static",在 C++中表示 flags属于整个类。所以我们很容易匹配到这样的一些结果:
./ardupilot/ArduCopter/motors.pde:40: AP_Notify::flags.arming_failed = true;
./ardupilot/ArduCopter/motors.pde:45: AP_Notify::flags.arming_failed = true;
./ardupilot/ArduCopter/motors.pde:75: AP_Notify::flags.arming_failed = false;
./ardupilot/ArduCopter/motors.pde:123: AP_Notify::flags.armed = true;
./ardupilot/ArduCopter/motors.pde:151: AP_Notify::flags.armed = false;
即直接通过类名称修改该变量的值。
radiolink@ubuntu:~/apm$ grep -nr AP_Notify ./ardupilot/
./ardupilot/ArduCopter/ArduCopter.pde:145:#include <AP_Notify.h> // Notify library
./ardupilot/ArduCopter/ArduCopter.pde:203:// AP_Notify instance
./ardupilot/ArduCopter/ArduCopter.pde:204:static AP_Notify notify;
./ardupilot/ArduCopter/ArduCopter.pde:1207: // run glitch protection and update AP_Notify if home has been initialised
./ardupilot/ArduCopter/ArduCopter.pde:1211: if (AP_Notify::flags.gps_glitching != report_gps_glitch) {
./ardupilot/ArduCopter/ArduCopter.pde:1217: AP_Notify::flags.gps_glitching = report_gps_glitch;
radiolink@ubuntu:~/apm$
那么 "ArduCopter.pde"这个源文件我们应该不陌生了,在 PX4中这是很重要的一个文件,因为如果是在 APM中我们认为我们的程序是从这里开始的。在 PX4中,如果抛开操作系统个人觉得我们也应该这样认为。那么分析到这里,我觉得这条路已经走通了,剩下的就是去看下 LED具体是如何控制的。这将顺着 notify继续讨论下去。
4. Notify
前面我们看到在 AP_Notify中只有两个函数,我们现在就先来看下这两个函数都做了哪些事情。
// initialisation
void AP_Notify::init(bool enable_external_leds)
{
AP_Notify::flags.external_leds = enable_external_leds;
boardled.init();
toshibaled.init();
#if CONFIG_HAL_BOARD == HAL_BOARD_PX4
tonealarm.init();
#endif
#if CONFIG_HAL_BOARD == HAL_BOARD_APM1 || CONFIG_HAL_BOARD == HAL_BOARD_APM2
externalled.init();
buzzer.init();
#endif
#if CONFIG_HAL_BOARD == HAL_BOARD_VRBRAIN
externalled.init();
buzzer.init();
#endif
}
// main update function, called at 50Hz
void AP_Notify::update(void)
{
boardled.update();
toshibaled.update();
#if CONFIG_HAL_BOARD == HAL_BOARD_PX4
tonealarm.update();
#endif
#if CONFIG_HAL_BOARD == HAL_BOARD_APM1 || CONFIG_HAL_BOARD == HAL_BOARD_APM2
externalled.update();
buzzer.update();
#endif
#if CONFIG_HAL_BOARD == HAL_BOARD_VRBRAIN
externalled.update();
buzzer.update();
#endif
}
其实就是调用别人的 init跟 update函数,而且注释还告诉了我们 update函数调用的频率为 50Hz。这里我们关系的只是 toshibaled,其它的我们现在就不去看了。其实你在这里把 toshibaled搞通了,其它的也一样理顺了。
radiolink@ubuntu:~/apm$ grep -nr notify.init ./ardupilot/
./ardupilot/APMrover2/APMrover2.pde:553: notify.init(false);
./ardupilot/Build.ArduCopter/ArduCopter.cpp:15657: notify.init(true);
./ardupilot/ArduPlane/ArduPlane.pde:840: notify.init(false);
./ardupilot/AntennaTracker/AntennaTracker.pde:268: notify.init(false);
./ardupilot/ArduCopter/system.pde:141: notify.init(true);
radiolink@ubuntu:~/apm$
static void init_ardupilot()
{
bool enable_external_leds = true;
// init EPM cargo gripper
#if EPM_ENABLED == ENABLED
epm.init();
enable_external_leds = !epm.enabled();
#endif
// initialise notify system
// disable external leds if epm is enabled because of pin conflict on the APM
notify.init(enable_external_leds);
}
前面我们分析 "ArduCopter"遇到过 "init_ardupilot"这个函数,当时我因为说不清楚这里的每个东西都是做什么用的,所以就略过了。而现在,我们终于又搞懂了一个。
radiolink@ubuntu:~/apm$ grep -nr notify.update ./ardupilot/
./ardupilot/APMrover2/system.pde:424: notify.update();
./ardupilot/APMrover2/GCS_Mavlink.pde:1167: notify.update();
./ardupilot/Build.ArduCopter/ArduCopter.cpp:3971: notify.update();
./ardupilot/Build.ArduCopter/ArduCopter.cpp:13066: notify.update();
./ardupilot/ArduPlane/system.pde:554: notify.update();
./ardupilot/ArduPlane/GCS_Mavlink.pde:1605: notify.update();
./ardupilot/AntennaTracker/system.pde:118: notify.update();
./ardupilot/AntennaTracker/GCS_Mavlink.pde:772: notify.update();
./ardupilot/ArduCopter/leds.pde:7: notify.update();
./ardupilot/ArduCopter/GCS_Mavlink.pde:1632: notify.update();
radiolink@ubuntu:~/apm$
// updates the status of notify
// should be called at 50hz
static void update_notify()
{
notify.update();
}
static const AP_Scheduler::Task scheduler_tasks[] PROGMEM = {
{ rc_loop, 4, 10 },
{ throttle_loop, 8, 45 },
{ update_GPS, 8, 90 },
{ update_notify, 8, 10 },
前面我们已经知道了 PX4控制速率是 400Hz,所以不必去看那个 100Hz的了。而 scheduler_tasks里面存放的是 ArduCopter中的任务,这是在线程内部实现的任务调度,在 APM中就已经存在,所以跟 Nuttx没关。
那么我们现在要做的就是沿着 toshibaled.init()跟 toshibaled.update()这两个函数往下走。
5. toshibaled
首先呢我们当然是应该看看 ToshibaLED_PX4的定义。
class ToshibaLED_PX4 : public ToshibaLED
{
public:
bool hw_init(void);
bool hw_set_rgb(uint8_t r, uint8_t g, uint8_t b);
private:
int _rgbled_fd;
void update_timer(void);
VectorN<uint8_t,3> last, next;
};
从定义中我们看到, init跟 update并没有被重写,也就是继承了父类中的方法。那么:
void ToshibaLED::init()
{
_healthy = hw_init();
}
// update - updates led according to timed_updated. Should be called
// at 50Hz
void ToshibaLED::update()
{
// return immediately if not enabled
if (!_healthy) {
return;
}
update_colours();
set_rgb(_red_des, _green_des, _blue_des);
}
如果你手上的是最新版源码,你会看到一个新的类: "class ToshibaLED: public RGBLed"。多少个类都无所谓,都差不多。 hw_init这个函数我们前面也看了,就是做一些初始化工作,包括打开设备。
bool ToshibaLED_PX4::hw_init()
{
// open the rgb led device
_rgbled_fd = open(RGBLED_DEVICE_PATH, 0);
if (_rgbled_fd == -1) {
hal.console->printf("Unable to open " RGBLED_DEVICE_PATH);
return false;
}
ioctl(_rgbled_fd, RGBLED_SET_MODE, (unsignedlong)RGBLED_MODE_ON);
last.zero();
next.zero();
hal.scheduler->register_io_process(AP_HAL_MEMBERPROC(&ToshibaLED_PX4::update_timer));
return true;
}
其中稍微要注意的就是 hal这条语句。这里又注册了一个调度函数。因为这里是通过 hal注册的,所以我们知道这里又是由 ArduCopter提供任务调度。如果忽略了这一点你看 update又会觉得糊涂,因为你去看源码就会发现 set_rgb最终调用了 hw_set_rgb函数,但是你去看该函数:
// set_rgb - set color as a combination of red, green and blue values
bool ToshibaLED_PX4::hw_set_rgb(uint8_t red, uint8_t green, uint8_t blue)
{
hal.scheduler->suspend_timer_procs();
next[0] = red;
next[1] = green;
next[2] = blue;
hal.scheduler->resume_timer_procs();
return true;
}
你会觉得它好像并没有实际操作硬件。但其实我们刚看到的只是更新了数据,真正把数据写入硬件的正是 ToshibaLED_PX4::update_timer这个函数的工作。具体颜色是如何更新的去看 update_colours这个函数最清楚。
void ToshibaLED_PX4::update_timer(void)
{
if (last == next) {
return;
}
rgbled_rgbset_t v;
v.red = next[0];
v.green = next[1];
v.blue = next[2];
ioctl(_rgbled_fd, RGBLED_SET_RGB, (unsigned long)&v);
last = next;
}
知道这里我才知道前面我认为调用 ioctl应该使用 "RGBLED_SET_MODE"错得有多么离谱。但我们如果去看 rgbled的代码你会发现:
int
RGBLED::ioctl(struct file *filp, int cmd, unsigned long arg)
{
int ret = ENOTTY;
switch (cmd) {
case RGBLED_SET_RGB:
/* set the specified color */
_r = ((rgbled_rgbset_t *) arg)->red;
_g = ((rgbled_rgbset_t *) arg)->green;
_b = ((rgbled_rgbset_t *) arg)->blue;
send_led_rgb();
return OK;
/**
* Send RGB PWM settings to LED driver according to current color and brightness
*/
int
RGBLED::send_led_rgb()
{
/* To scale from 0..255 -> 0..15 shift right by 4 bits */
const uint8_t msg[6] = {
SUB_ADDR_PWM0, (uint8_t)((int)(_b * _brightness) >> 4),
SUB_ADDR_PWM1