zoukankan      html  css  js  c++  java
  • PX4 FMU [7] rgbled [转载]

    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

  • 相关阅读:
    C++-struct类的新特性当class用
    rbenv、fish 與 VSCode 設置之路
    angularJS进阶阶段(4)
    插入排序
    Vimium
    Design Patterns 25
    Mysql(或者sqlite), Mongo中update Column + 1
    Hexo
    继承
    Gradle的依赖方式——Lombok在Gradle中的正确配置姿势
  • 原文地址:https://www.cnblogs.com/eastgeneral/p/10879636.html
Copyright © 2011-2022 走看看