基于pixhawk2.4.6硬件和NUTTX系统的ardupilot启动流程:从上电到ArduCopter应用层代码

基于pixhawk2.4.6硬件和NUTTX系统的ardupilot启动流程:从上电到ArduCopter应用层代码

原文: http://www.amovauto.com/portal.php?mod=view&aid=132 ,在此基础上进行了细化分析

px4从上电到执行rcS脚本的过程两块控制板(即fmu和io)上电后分别独立启动并运行各自的code。运行过程为:
(1) 2套stm32_start()分别初始化fmu和io


  • fmu链接脚本: /ardupilot/modules/PX4Firmware/nuttx-configs/px4fmu-v2/scripts/ld.script,中由ENTRY_POINT(__start)指定入口方法为符号 __start;
  • io链接脚本: /ardupilot/modules/PX4Firmware/nuttx-configs/px4io-v2/scripts/ld.script,中由ENTRY_POINT(__start)指定入口方法为符号 __start;

px4使用的是stm32f100(IO)和stm32f427(fmu),该入口函数在stm32_start.c中定义:
stm32_start.c位于ardupilot/modules/PX4NuttX/nuttx/arch/arm/src/stm32/
这里两个soc公用同一个 __start 函数;

__start--      #处理器执行的第一条指令   
          |    
          v    
     stm32_clockconfig()------              #初始化时钟 80003d2:    f000 f827     bl    8000424   
                             |    
                             v    
                   rcc_reset()              #复位rcc    
                   stm32_stdclockconfig()   #初始化标准时钟    
                   rcc_enableperipherals()  #使能外设时钟    
                            |    
         --------------------    
         |    
         v    
    stm32_fpuconfig()                      #配置fpu,    
    stm32_lowsetup()                       #基本初始化串口,之后可以使用up_lowputc()    
    stm32_gpioinit()                       #初始化gpio,只是调用stm32_gpioremap()设置重映射   
    clear bss                                 # 清bss段 
    up_earlyserialinit()                   #初始化串口,之后可以使用up_putc()    
    stm32_boardinitialize()--              #板级初始化    
                            |    
                            v    
                  stm32_spiinitialize()    #初始化spi,只是调用stm32_configgpio()设置gpio    
                  stm32_usbinitialize()    #初始化usb,只是调用stm32_configgpio()设置gpio    
                  board_led_initialize()   #初始化led,只是调用stm32_configgpio()设置gpio    
                            |    
         --------------------    
         |    
         v    
         

上面__start 中调用的这些方法,在具体的芯片系列中有不同的定义,比如这里stm32f100 和stm32f427, 均在其芯片具体配置代码中实现了 stm32_clockconfig(),其称之为public方法,
stm32_start.c 的头部通过#include *.c 包含进来,如下所示,在编译时会按当前的芯片类型,走具体芯片的 stm32_clockconfig()方法;

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/* Include chip-specific clocking initialization logic */

#if defined(CONFIG_STM32_STM32L15XX)
#  include "stm32l15xxx_rcc.c"
#elif defined(CONFIG_STM32_STM32F10XX)
#  include "stm32f10xxx_rcc.c"
#elif defined(CONFIG_STM32_STM32F20XX)
#  include "stm32f20xxx_rcc.c"
#elif defined(CONFIG_STM32_STM32F30XX)
#  include "stm32f30xxx_rcc.c"
#elif defined(CONFIG_STM32_STM32F40XX)
#  include "stm32f40xxx_rcc.c"
#else
#  error "Unsupported STM32 chip"
#endif

(2) 2套os_start()分别初始化nuttx系统

28.    os_start()-------------#初始化操作系统  ardupilot\modules\PX4NuttX\nuttx\sched\os_start.c  
29.                            |    
30.                            v    
31.                  dq_init()                #初始化各种状态的任务列表(置为null)    
32.                  g_pidhash[i]=            #初始化唯一可以确定的元素--进程ID    
33.                  g_pidhash[PIDHASH(0)]=   #分配空闲任务的进程ID为0    
34.                  g_idletcb=               #初始化空闲任务的任务控制块    
35.                  sem_initialize()--       #初始化信号量    
36.                                   |    
37.                                   v    
38.                        dq_init()          #将信号量队列置为null    
39.                        sem_initholders()  #初始化持有者结构以支持优先级继承,   
40.                                   |    
41.                            --------    
42.                            |    
43.                            v    
44.                  up_allocate_heap()       #分配用户模式的堆(设置堆的起点和大小)    
45.                  kumm_initialize()        #初始化用户模式的堆    
46.                  up_allocate_kheap()      #分配内核模式的堆,   
47.                  kmm_initialize()         #初始化内核模式的堆,   
48.                  task_initialize()        #初始化任务数据结构,   
49.                  irq_initialize()         #将所有中断向量都指向同一个异常中断处理程序    
50.                  wd_initialize()          #初始化看门狗数据结构    
51.                  clock_initialize()       #初始化rtc    
52.                  timer_initialize()       #配置POSIX定时器    
53.                  sig_initialize()         #初始化信号    
54.                  mq_initialize()          #初始化命名消息队列    
55.                  pthread_initialize()     #初始化线程特定的数据,空函数    
56.                  fs_initialize()---       #初始化文件系统    
57.                                   |    
58.                                   v    
59.                        sem_init()         #初始化节点信号量为1    
60.                        files_initialize() #初始化文件数组,空函数    
61.                                   |    
62.                            --------    
63.                            |    
64.                            v    
65.                  net_initialize()--       #初始化网络    
66.                                   |    
67.                                   v    
68.                        uip_initialize()   #初始化uIP层    
69.                        net_initroute()    #初始化路由表,    
70.                        netdev_seminit()   #初始化网络设备信号量    
71.                        arptimer_init()    #初始化ARP定时器    
72.                                   |    
73.                            --------    
74.                            |    
75.                            v    
76.                  up_initialize()---       #处理器特定的初始化    
77.                                   |    
78.                                   v    
79.                        up_calibratedelay()#校准定时器    
80.                        up_addregion()     #增加额外的内存段    
81.                        up_irqinitialize() #设置中断优先级,关联硬件异常处理函数    
82.                        up_pminitialize()  #初始化电源管理,   
83.                        up_dmainitialize() #初始化DMA,    
84.                        up_timerinit()     #初始化定时器中断    
85.                        devnull_register() #注册/dev/null    
86.                        devzero_register() #注册/dev/zero,    
87.                        up_serialinit()    #注册串口控制台/dev/console和串口/dev/ttyS0    
88.                        up_rnginitialize() #初始化并注册随机数生成器    
89.                        up_netinitialize() #初始化网络,是arch/arm/src/chip/stm32_eth.c中的    
90.                        up_usbinitialize() #初始化usb驱动,   
91.                        board_led_on()   #打开中断使能led,  
92.                                   |    
93.                            --------    
94.                            |    
95.                            v  
96.                  lib_initialize()         #初始化c库,空函数    
97.                  group_allocate()         #分配空闲组    
98.                  group_setupidlefiles()   #在空闲任务上创建stdout、stderr、stdin    
99.                  group_initialize()       #完全初始化空闲组    
100.                     os_bringup()------       #创建初始任务  os_bringup.c  
101.                                      |    
102.                                      v    
103.                           KEKERNEL_THREAD()  #启动内核工作者线程    
104.                           board_initialize() #最后一刻的板级初始化,nsh未调用    
105.                           TASK_CREATE()      #启动默认应用程序  os_bringup.c  
106.                                      |    
107.                               --------    
108.                               |    
109.                               v    
110.                     for up_idle()            #空闲任务循环    
111.                               |    
112.            --------------------    
113.            |    
114.            v    
115.       for(;;)     

(3) 2套os_bringup()函数分别创建进程并传入进程指针CONFIG_USER_ENTRYPOINT

#ifdef CONFIG_NUTTX_KERNEL
  DEBUGASSERT(USERSPACE->us_entrypoint != NULL);
  taskid = TASK_CREATE("init", SCHED_PRIORITY_DEFAULT,
                       CONFIG_USERMAIN_STACKSIZE, USERSPACE->us_entrypoint,
                       (FAR char * const *)NULL);
#else
  taskid = TASK_CREATE("init", SCHED_PRIORITY_DEFAULT,
                       CONFIG_USERMAIN_STACKSIZE,
                       (main_t)CONFIG_USER_ENTRYPOINT,
                       (FAR char * const *)NULL);

(4) fmu中的config.h文件宏定义

#define CONFIG_USER_ENTRYPOINT nsh_main

io中的config.h文件宏定义

#define CONFIG_USER_ENTRYPOINT user_start

(5) fmu开启nshshell

int nsh_main(int argc, char *argv[])
{
 int exitval = 0;
 int ret;

 /* Call all C++ static constructors */

#if defined(CONFIG_HAVE_CXX) && defined(CONFIG_HAVE_CXXINITIALIZE)
 up_cxxinitialize();
#endif

 /* Make sure that we are using our symbol take */

#if defined(CONFIG_LIBC_EXECFUNCS) && defined(CONFIG_EXECFUNCS_SYMTAB)
 exec_setsymtab(CONFIG_EXECFUNCS_SYMTAB, 0);
#endif

 /* Register the BINFS file system */

#if defined(CONFIG_FS_BINFS) && (CONFIG_BUILTIN)
 ret = builtin_initialize();
 if (ret < 0)
   {
    fprintf(stderr, "ERROR: builtin_initialize failed: %d\n", ret);
    exitval = 1;
  }
#endif

 /* Initialize the NSH library */

 nsh_initialize();

 /* If the Telnet console is selected as a front-end, then start the
  * Telnet daemon.
  */

#ifdef CONFIG_NSH_TELNET
 ret = nsh_telnetstart();
 if (ret < 0)
   {
    /* The daemon is NOT running.  Report the the error then fail...
     * either with the serial console up or just exiting.
     */

    fprintf(stderr, "ERROR: Failed to start TELNET daemon: %d\n", ret);
    exitval = 1;
  }
#endif

 /* If the serial console front end is selected, then run it on this thread */

#ifdef CONFIG_NSH_CONSOLE
 ret = nsh_consolemain(0, NULL);

 /* nsh_consolemain() should not return.  So if we get here, something
  * is wrong.
  */

 fprintf(stderr, "ERROR: nsh_consolemain() returned: %d\n", ret);
 exitval = 1;
#endif

 return exitval;
}

io
user_start 负责px4io 基础环境的初始化,包括PWM,串口,ADC 等资源的初始化,最后运行一个死循环,用于处理遥控器输入,与PX4FMU 通信的内容
controls_tick 负责处理遥控器的输入内容,包括SBUS 的处理sbus_input、 SPKT/DSM 的处理dsm_port_input、 PPM 的处理ppm_input

int
user_start(int argc, char *argv[])
{
   /* configure the first 8 PWM outputs (i.e. all of them) */
   up_pwm_servo_init(0xff);

   /* run C++ ctors before we go any further */
   up_cxxinitialize();

   /* reset all to zero */
   memset(&system_state, 0, sizeof(system_state));

   /* configure the high-resolution time/callout interface */
   hrt_init();

   /* calculate our fw CRC so FMU can decide if we need to update */
   calculate_fw_crc();

   /*
    * Poll at 1ms intervals for received bytes that have not triggered
    * a DMA event.
    */
#ifdef CONFIG_ARCH_DMA
   hrt_call_every(&serial_dma_call, 1000, 1000, (hrt_callout)stm32_serial_dma_poll, NULL);
#endif

   /* print some startup info */
   lowsyslog("\nPX4IO: starting\n");

   /* default all the LEDs to off while we start */
   LED_AMBER(false);
   LED_BLUE(false);
   LED_SAFETY(false);
#ifdef GPIO_LED4
   LED_RING(false);
#endif

   /* turn on servo power (if supported) */
#ifdef POWER_SERVO
   POWER_SERVO(true);
#endif

   /* turn off S.Bus out (if supported) */
#ifdef ENABLE_SBUS_OUT
   ENABLE_SBUS_OUT(false);
#endif

   /* start the safety switch handler */
   safety_init();
   /* initialise the control inputs */
   controls_init();

   /* set up the ADC */
   adc_init();

   /* start the FMU interface */
   interface_init();

   /* add a performance counter for mixing */
   perf_counter_t mixer_perf = perf_alloc(PC_ELAPSED, "mix");

   /* add a performance counter for controls */
   perf_counter_t controls_perf = perf_alloc(PC_ELAPSED, "controls");

   /* and one for measuring the loop rate */
   perf_counter_t loop_perf = perf_alloc(PC_INTERVAL, "loop");

   struct mallinfo minfo = mallinfo();
   lowsyslog("MEM: free %u, largest %u\n", minfo.mxordblk, minfo.fordblks);

   /* initialize PWM limit lib */
   pwm_limit_init(&pwm_limit);

   /*
    *    P O L I C E    L I G H T S
    *
    * Not enough memory, lock down.
    *
    * We might need to allocate mixers later, and this will
    * ensure that a developer doing a change will notice
    * that he just burned the remaining RAM with static
    * allocations. We don't want him to be able to
    * get past that point. This needs to be clearly
    * documented in the dev guide.
    *
    */
   if (minfo.mxordblk < 600) {

       lowsyslog("ERR: not enough MEM");
       bool phase = false;

       while (true) {

           if (phase) {
               LED_AMBER(true);
               LED_BLUE(false);

           } else {
               LED_AMBER(false);
               LED_BLUE(true);
           }

           up_udelay(250000);

           phase = !phase;
       }
   }

   /* Start the failsafe led init */
   failsafe_led_init();

   /*
    * Run everything in a tight loop.
    */

   uint64_t last_debug_time = 0;

   for (;;) {

       /* track the rate at which the loop is running */
       perf_count(loop_perf);

       /* kick the mixer */
       perf_begin(mixer_perf);

       if (!(r_setup_features & PX4IO_P_SETUP_FEATURES_ONESHOT)) {
           /*
             when in oneshot we don't trigger output
             until we get new data from the FMU
            */
           mixer_tick();
       }

       perf_end(mixer_perf);

       /* kick the control inputs */
       perf_begin(controls_perf);
       controls_tick();
       perf_end(controls_perf);

       check_reboot();

       /* check for debug activity (default: none) */
       show_debug_messages();

       /* post debug state at ~1Hz - this is via an auxiliary serial port
        * DEFAULTS TO OFF!
        */
       if (hrt_absolute_time() - last_debug_time > (1000 * 1000)) {

           isr_debug(1, "d:%u s=0x%x a=0x%x f=0x%x m=%u",
                 (unsigned)r_page_setup[PX4IO_P_SETUP_SET_DEBUG],
                 (unsigned)r_status_flags,
                 (unsigned)r_setup_arming,
                 (unsigned)r_setup_features,
                 (unsigned)mallinfo().mxordblk);
           last_debug_time = hrt_absolute_time();
       }

       if (r_page_setup[PX4IO_P_SETUP_HEATER_DUTY_CYCLE] <= PX4IO_HEATER_MAX) {
           control_IMU_heater(r_page_setup[PX4IO_P_SETUP_HEATER_DUTY_CYCLE]);

       } else {
           control_heartbeat_LED();
       }
   }
}

至此io板启动分析完了,fmu继续分析
(6)nsh_main中
nsh_initialize()初始化nshshell,
nsh_consolemain()执行nshshell
进入nsh_consolemain()

int nsh_consolemain(int argc, char *argv[])
{
 FAR struct console_stdio_s *pstate = nsh_newconsole();
 int ret;

 DEBUGASSERT(pstate);

 /* Execute the start-up script */

#ifdef CONFIG_NSH_ROMFSETC
 (void)nsh_initscript(&pstate->cn_vtbl);
#endif

 /* Initialize any USB tracing options that were requested */

#ifdef CONFIG_NSH_USBDEV_TRACE
 usbtrace_enable(TRACE_BITSET);
#endif

 /* Execute the session */

 ret = nsh_session(pstate);

 /* Exit upon return */

 nsh_exit(&pstate->cn_vtbl, ret);
 return ret;
}

(void)nsh_initscript(&pstate->cn_vtbl);初始化nsh脚本
ret = nsh_session(pstate);执行nsh脚本
至此nsh脚本还没有引入,所以需要去寻找nsh脚本是什么,进入nsh_initscript()

#ifdef CONFIG_NSH_ROMFSETC
int nsh_initscript(FAR struct nsh_vtbl_s *vtbl)
{
 static bool initialized;
 bool already;
 int ret = OK;

 /* Atomic test and set of the initialized flag */

 sched_lock();
 already     = initialized;
 initialized = true;
 sched_unlock();

 /* If we have not already executed the init script, then do so now */

 if (!already)
   {
     ret = nsh_script(vtbl, "init", NSH_INITPATH);
   }

 return ret;
}

ret = nsh_script(vtbl, “init”, NSH_INITPATH); //Execute the NSH script at path.

#define NSH_INITPATH CONFIG_NSH_ROMFSMOUNTPT “/” CONFIG_NSH_INITSCRIPT
#define CONFIG_NSH_ROMFSMOUNTPT “/etc”
#define CONFIG_NSH_INITSCRIPT “init.d/rcS”
“/etc”是挂载目录
“init.d/rcS”是rcS路径
因此将”/etc”和”init.d/rcS”路径传入nsh_script()

int nsh_script(FAR struct nsh_vtbl_s *vtbl, FAR const char *cmd,
              FAR const char *path)
{
 FAR char *fullpath;
 FAR FILE *savestream;
 FAR char *buffer;
 FAR char *pret;
 int ret = ERROR;

 /* The path to the script may be relative to the current working directory */

 fullpath = nsh_getfullpath(vtbl, path);
 if (!fullpath)
   {
     return ERROR;
   }

 /* Get a reference to the common input buffer */

 buffer = nsh_linebuffer(vtbl);
 if (buffer)
   {
     /* Save the parent stream in case of nested script processing */

     savestream = vtbl->np.np_stream;

     /* Open the file containing the script */

     vtbl->np.np_stream = fopen(fullpath, "r");
     if (!vtbl->np.np_stream)
       {
         nsh_output(vtbl, g_fmtcmdfailed, cmd, "fopen", NSH_ERRNO);

         /* Free the allocated path */

         nsh_freefullpath(fullpath);

         /* Restore the parent script stream */

         vtbl->np.np_stream = savestream;
         return ERROR;
       }

     /* Loop, processing each command line in the script file (or
      * until an error occurs)
      */

     do
       {
         /* Flush any output generated by the previous line */

         fflush(stdout);
#ifndef CONFIG_NSH_DISABLE_LOOPS
         /* Get the current file position.  This is used to control
          * looping.  If a loop begins in the next line, then this file
          * offset will be needed to locate the top of the loop in the
          * script file.  Note that ftell will return -1 on failure.
          */

         vtbl->np.np_foffs = ftell(vtbl->np.np_stream);
         vtbl->np.np_loffs = 0;

         if (vtbl->np.np_foffs < 0)
           {
             nsh_output(vtbl, g_fmtcmdfailed, "loop", "ftell", NSH_ERRNO);
           }
#endif

         /* Now read the next line from the script file */

         pret = fgets(buffer, CONFIG_NSH_LINELEN, vtbl->np.np_stream);
         if (pret)
           {
             /* Parse process the command.  NOTE:  this is recursive...
              * we got to cmd_sh via a call to nsh_parse.  So some
              * considerable amount of stack may be used.
              */

             if ((vtbl->np.np_flags & NSH_PFLAG_SILENT) == 0)
               {
                 nsh_output(vtbl,"%s", buffer);
               }

             ret = nsh_parse(vtbl, buffer);
           }
       }
     while (pret && (ret == OK || (vtbl->np.np_flags & NSH_PFLAG_IGNORE)));

     /* Close the script file */

     fclose(vtbl->np.np_stream);

     /* Restore the parent script stream */

     vtbl->np.np_stream = savestream;
   }

 /* Free the allocated path */

 nsh_freefullpath(fullpath);
 return ret;
}

其中nsh_output(vtbl, g_fmtcmdfailed, cmd, “fopen”, NSH_ERRNO);是将脚本打开
ret = nsh_parse(vtbl, buffer);解析脚本
至此分析完了fmu从上电到执行rcS脚本的过程。

从rcS启动到ArduCopter执行setup()、loop()
rcS的命令是nshshell的命令,比如

if rgbled start

先执行rgbled start,再作判断。
rgbled 可以对应到modules/PX4Firmware/Build/px4fmu-v2_APM.build/builtin_commands.c中
该文件在固件编译过程中自动生成,记录着一个名为g_builtin[]的数组,数组所有成员即为飞控上电之后,后台运行的所有应用程序,每个成员均有4个参数,分别为程序名、程序调度优先级、堆栈大小以及程序入口函数。

/* builtin command list - automatically generated, do not edit */
#include <nuttx/config.h>
#include <nuttx/binfmt/builtin.h>
extern int sercon_main(int argc, char *argv[]);
extern int serdis_main(int argc, char *argv[]);
extern int px4io_main(int argc, char *argv[]);
extern int mtd_main(int argc, char *argv[]);
extern int adc_main(int argc, char *argv[]);
extern int perf_main(int argc, char *argv[]);
extern int fmu_main(int argc, char *argv[]);
extern int tone_alarm_main(int argc, char *argv[]);
extern int ArduPilot_main(int argc, char *argv[]);    ///< --------------------
extern int pwm_input_main(int argc, char *argv[]);
extern int nshterm_main(int argc, char *argv[]);
extern int mixer_main(int argc, char *argv[]);
extern int uorb_main(int argc, char *argv[]);
extern int ver_main(int argc, char *argv[]);
extern int usb_connected_main(int argc, char *argv[]);
extern int bl_update_main(int argc, char *argv[]);
extern int top_main(int argc, char *argv[]);
extern int reboot_main(int argc, char *argv[]);
const struct builtin_s g_builtins[] = {
    {"sercon", SCHED_PRIORITY_DEFAULT, 2048, sercon_main},
    {"serdis", SCHED_PRIORITY_DEFAULT, 2048, serdis_main},
    {"px4io", SCHED_PRIORITY_DEFAULT, 1200, px4io_main},
    {"mtd", SCHED_PRIORITY_DEFAULT, CONFIG_PTHREAD_STACK_DEFAULT, mtd_main},
    {"adc", SCHED_PRIORITY_DEFAULT, CONFIG_PTHREAD_STACK_DEFAULT, adc_main},
    {"perf", SCHED_PRIORITY_DEFAULT, 1800, perf_main},
    {"fmu", SCHED_PRIORITY_DEFAULT, 1200, fmu_main},
    {"tone_alarm", SCHED_PRIORITY_DEFAULT, CONFIG_PTHREAD_STACK_DEFAULT, tone_alarm_main},
    ///< ----------------------------------------------------------- >///
    {"ArduPilot", SCHED_PRIORITY_DEFAULT, 4096, ArduPilot_main}, 
    ///< ----------------------------------------------------------- >///
    {"pwm_input", SCHED_PRIORITY_DEFAULT, CONFIG_PTHREAD_STACK_DEFAULT, pwm_input_main},
    {"nshterm", SCHED_PRIORITY_DEFAULT-30, 1500, nshterm_main},
    {"mixer", SCHED_PRIORITY_DEFAULT, 4096, mixer_main},
    {"uorb", SCHED_PRIORITY_DEFAULT, 2048, uorb_main},
    {"ver", SCHED_PRIORITY_DEFAULT, 1024, ver_main},
    {"usb_connected", SCHED_PRIORITY_DEFAULT, CONFIG_PTHREAD_STACK_DEFAULT, usb_connected_main},
    {"bl_update", SCHED_PRIORITY_DEFAULT, 4096, bl_update_main},
    {"top", SCHED_PRIORITY_DEFAULT, 1700, top_main},
    {"reboot", SCHED_PRIORITY_DEFAULT, 800, reboot_main},
    {NULL, 0, 0, NULL}
};
const int g_builtin_count = 18;

送些应用程序可分为系统应用、驱动程序、飞行控制程序等几类。系统应用如top可以显示当前运行的任务及CPU占用率;uORB在应用么间分发数据;nshterm处理nsh终端数据;pref用于打印任务状态信息等。驱动程序如gps,接收并处理gps数据;pwm,可启动、停止各PWM通道,显示并设置各通道的工作频率及最小、最大pwm值;px4io用于处理与协处理器的通信等;飞行控制程序如ArduPilot,这便是飞行控制找的入口。

{"rgbled", SCHED_PRIORITY_DEFAULT, CONFIG_PTHREAD_STACK_DEFAULT, rgbled_main}

也就对应到rgbled _main,所以就可以跳转到rgbled.cpp文件中的rgbled _main()函数
然后argc是参数数量,*argv[]是参数数组。对应到rcS

if rgbled start

rgbled是第一个参数,start是第二个参数,所以在rgbled _main()函数中,执行

if (!strcmp(verb, "start")) {
......
}

其他命令也是如此,于是跳过中间步骤,直接运行

sh /etc/init.d/rc.APM
---------------------------->>>
#!nsh

# APM startup script for NuttX on PX4

# To disable APM startup add a /fs/microsd/APM/nostart file

# check for an old file called APM, caused by 
# a bug in an earlier firmware release
if [ -f /fs/microsd/APM ]
then
    echo "APM file found - renaming"
    mv /fs/microsd/APM /fs/microsd/APM.old
fi

if [ -f /fs/microsd/APM/nostart ]
then
    echo "APM/nostart found - skipping APM startup"
    sh /etc/init.d/rc.error
fi

set sketch NONE
if rm /fs/microsd/APM/boot.log
then
    echo "removed old boot.log"
fi
set logfile /fs/microsd/APM/BOOT.LOG

if mkdir /fs/microsd/APM > /dev/null
then
    echo "Created APM directory"
fi

if uorb start
then
    echo "uorb started OK"
else
    sh /etc/init.d/rc.error
fi

echo Starting ArduPilot
if ArduPilot start
then
    echo ArduPilot started OK
else
    sh /etc/init.d/rc.error
fi

echo "rc.APM finished"

再跳到rc.APM最后,执行

if ArduPilot start

那么一定会有ArduPilot_main和ArduPilot对应

extern int ArduPilot_main(int argc, char *argv[]);
{"ArduPilot", SCHED_PRIORITY_DEFAULT, 4096, ArduPilot_main},

ArduPilot_main则为APM飞行控制找的入口函数,但整个飞行控制找中并没有这个入口函数的实现。这是由于APM飞行控制软件可以应用在多种平台(如固定翼、多旋翼、地面车等)和不同的飞控板(如APM、Pixhawk、Navio+等)上,而对于不同的平台和飞控板,固件编译和启动有对应的流程。针对不同的飞控板,APM飞行控制找使用硬件抽象层隔离不同飞控板的差异化部分,将不同飞控板的入口函数(ArduPilot_main)统一封装到AP_MAIN()。对于不同的控制平台,APM飞行控制巧将AP_MAIN()再次封装为AP_HAL_MAIN_CALLBACKS(CALLBACKS),最后将hal.run(argc, argv, CALLBACKS);函数解析到相应硬件抽象层类方法,如HAL_Linux.run()、HAL_PX4.run()等。

#pragma once

#include "HAL.h"

#if CONFIG_HAL_BOARD == HAL_BOARD_PX4 || CONFIG_HAL_BOARD == HAL_BOARD_VRBRAIN
//将AP_MAIN替换成ArduPilot_main
#define AP_MAIN __EXPORT ArduPilot_main 
#endif

#ifndef AP_MAIN
#define AP_MAIN main
#endif

#define AP_HAL_MAIN() \
    AP_HAL::HAL::FunCallbacks callbacks(setup, loop); \
    extern "C" {                               \
    int AP_MAIN(int argc, char* const argv[]); \
    int AP_MAIN(int argc, char* const argv[]) { \
        hal.run(argc, argv, &callbacks); \
        return 0; \
    } \
    }

//针对不同的机型,AP_MAIN再次封装为AP_HAL_MAIN_CALLBACKS(CALLBACKS)
#define AP_HAL_MAIN_CALLBACKS(CALLBACKS) extern "C" { \
    int AP_MAIN(int argc, char* const argv[]); \
    int AP_MAIN(int argc, char* const argv[]) { \
        hal.run(argc, argv, CALLBACKS); \
        return 0; \
    } \
    }

这样,将不同机型、不同控制板的飞控程序入口函数统一到AP_HAL_MAIN_CALLBACKS(CALLBACKS),只需改变机型参数就可以,如固定翼机调用AP_HAL_MAIN_CALLBACKS(&plane),地面车调用AP_HAL_MAIN_CALLBACKS(&rover),多旋翼则调用AP_HAL_MAIN_CALLBACKS(&copter)。 (AuduPilot.cpp末尾,copter对象在Copter.cpp末尾定义)
确定Pixhawk飞行控制栈的入口函数后,可看到函数体为hal.run(),最终会解析成Pixhawk的硬件抽象层类的方法HAL_PX4.run()。首先,在Copter.cpp中定义hal为硬件抽象层类AP_HAL::HAL的引用,

const AP_HAL::HAL& hal = AP_HAL::get_HAL();
并且在Pixhawk固件的Makefile文件px4_target.mk中有

SKETCHFLAGS=$(SKETCHLIBINCLUDES) -DUAVCAN_CPP_VERSION=UAVCAN_CPP03 -DUAVCAN_NO_ASSERTIONS -DUAVCAN_NULLPTR=nullptr -DARDUPILOT_BUILD -DTESTS_MATHLIB_DISABLE -DCONFIG_HAL_BOARD=HAL_BOARD_PX4 -DSKETCHNAME="\\\"$(SKETCH)\\\"" -DSKETCH_MAIN=ArduPilot_main -DAPM_BUILD_DIRECTORY=APM_BUILD_$(SKETCH)

继而,将hal解析为HAL_PX4。HAL_PX4_Class.cpp中代码如下:

#if CONFIG_HAL_BOARD == HAL_BOARD_PX4
......
const AP_HAL::HAL& AP_HAL::get_HAL() {
    static const HAL_PX4 hal_px4;
    return hal_px4;
}
#endif // CONFIG_HAL_BOARD == HAL_BOARD_PX4
}

void HAL_PX4::run(int argc, char * const argv[], Callbacks* callbacks)实际上是一个守护任务,可W启动、停止飞行控制栈。Pixhawk飞控板在上电后,Nuttx操作系统经由rc.APM启动ArduPilot_main ,即这里的HAL_PX4::run(),继而由

static AP_HAL::HAL::Callbacks* g_callbacks; 
void HAL_PX4::run(int argc, char * const argv[], Callbacks* callbacks) const
{
......
g_callbacks = callbacks;
......
daemon_task = px4_task_spawn_cmd(SKETCHNAME,
                                 SCHED_FIFO,
                                 APM_MAIN_PRIORITY,
                                 APM_MAIN_THREAD_STACK_SIZE,
                                 main_loop,
                                 nullptr);
......
}

创建main_loop()线程(libraries/AP_HAL_PX4/HAL_PX4_Class.cpp)


static int main_loop(int argc, char **argv)
{
    hal.uartA->begin(115200);
    hal.uartB->begin(38400);
    hal.uartC->begin(57600);
    hal.uartD->begin(57600);
    hal.uartE->begin(57600);
    hal.scheduler->init();

    // init the I2C wrapper class
    PX4_I2C::init_lock();
    
    /*
      run setup() at low priority to ensure CLI doesn't hang the
      system, and to allow initial sensor read loops to run
     */
    hal_px4_set_priority(APM_STARTUP_PRIORITY);

    schedulerInstance.hal_initialized();

    g_callbacks->setup(); ///> copter->setup()
    hal.scheduler->system_initialized();

    perf_counter_t perf_loop = perf_alloc(PC_ELAPSED, "APM_loop");
    perf_counter_t perf_overrun = perf_alloc(PC_COUNT, "APM_overrun");
    struct hrt_call loop_overtime_call;

    thread_running = true;

    /*
      switch to high priority for main loop
     */
    hal_px4_set_priority(APM_MAIN_PRIORITY);

    while (!_px4_thread_should_exit) {
        perf_begin(perf_loop);
        
        /*
          this ensures a tight loop waiting on a lower priority driver
          will eventually give up some time for the driver to run. It
          will only ever be called if a loop() call runs for more than
          0.1 second
         */
        hrt_call_after(&loop_overtime_call, 100000, (hrt_callout)loop_overtime, nullptr);

        g_callbacks->loop(); ///> copter->loop()

        if (px4_ran_overtime) {
            /*
              we ran over 1s in loop(), and our priority was lowered
              to let a driver run. Set it back to high priority now.
             */
            hal_px4_set_priority(APM_MAIN_PRIORITY);
            perf_count(perf_overrun);
            px4_ran_overtime = false;
        }

        perf_end(perf_loop);

        /*
          give up 250 microseconds of time, to ensure drivers get a
          chance to run. This relies on the accurate semaphore wait
          using hrt in semaphore.cpp
         */
        hal.scheduler->delay_microseconds(250);
    }
    thread_running = false;
    return 0;
}

copter对象的setup和loop方法:

void Copter::setup()
{
    // Load the default values of variables listed in var_info[]s
    AP_Param::setup_sketch_defaults();

    // setup storage layout for copter
    StorageManager::set_layout_copter();

    init_ardupilot();

    // initialise the main loop scheduler
    scheduler.init(&scheduler_tasks[0], ARRAY_SIZE(scheduler_tasks), MASK_LOG_PM);
}

void Copter::loop()
{
    scheduler.loop();
    G_Dt = scheduler.get_last_loop_time_s();
}

scheduler对象的init和loop方法:

// initialise the scheduler
void AP_Scheduler::init(const AP_Scheduler::Task *tasks, uint8_t num_tasks, uint32_t log_performance_bit)
{
    _tasks = tasks;
    _num_tasks = num_tasks;
    _last_run = new uint16_t[_num_tasks];
    memset(_last_run, 0, sizeof(_last_run[0]) * _num_tasks);
    _tick_counter = 0;

    // setup initial performance counters
    perf_info.set_loop_rate(get_loop_rate_hz());
    perf_info.reset();

    _log_performance_bit = log_performance_bit;
}
void AP_Scheduler::loop()
{
    // wait for an INS sample
    AP::ins().wait_for_sample();

    const uint32_t sample_time_us = AP_HAL::micros();
    
    if (_loop_timer_start_us == 0) {
        _loop_timer_start_us = sample_time_us;
        _last_loop_time_s = get_loop_period_s();
    } else {
        _last_loop_time_s = (sample_time_us - _loop_timer_start_us) * 1.0e-6;
    }

    // Execute the fast loop
    // ---------------------
    if (_fastloop_fn) {
        _fastloop_fn();
    }

    // tell the scheduler one tick has passed
    tick();

    // run all the tasks that are due to run. Note that we only
    // have to call this once per loop, as the tasks are scheduled
    // in multiples of the main loop tick. So if they don't run on
    // the first call to the scheduler they won't run on a later
    // call until scheduler.tick() is called again
    const uint32_t loop_us = get_loop_period_us();
    const uint32_t time_available = (sample_time_us + loop_us) - AP_HAL::micros();
    run(time_available > loop_us ? 0u : time_available);

#if CONFIG_HAL_BOARD == HAL_BOARD_SITL
    // move result of AP_HAL::micros() forward:
    hal.scheduler->delay_microseconds(1);
#endif

    // check loop time
    perf_info.check_loop_time(sample_time_us - _loop_timer_start_us);
        
    _loop_timer_start_us = sample_time_us;
}

scheduler对象的run方法:

/*
  run one tick
  this will run as many scheduler tasks as we can in the specified time
 */
void AP_Scheduler::run(uint32_t time_available)
{
    uint32_t run_started_usec = AP_HAL::micros();
    uint32_t now = run_started_usec;

    if (_debug > 1 && _perf_counters == nullptr) {
        _perf_counters = new AP_HAL::Util::perf_counter_t[_num_tasks];
        if (_perf_counters != nullptr) {
            for (uint8_t i=0; i<_num_tasks; i++) {
                _perf_counters[i] = hal.util->perf_alloc(AP_HAL::Util::PC_ELAPSED, _tasks[i].name);
            }
        }
    }
    
    for (uint8_t i=0; i<_num_tasks; i++) {
        uint16_t dt = _tick_counter - _last_run[i];
        uint16_t interval_ticks = _loop_rate_hz / _tasks[i].rate_hz;
        if (interval_ticks < 1) {
            interval_ticks = 1;
        }
        if (dt < interval_ticks) {
            // this task is not yet scheduled to run again
            continue;
        }
        // this task is due to run. Do we have enough time to run it?
        _task_time_allowed = _tasks[i].max_time_micros;

        if (dt >= interval_ticks*2) {
            // we've slipped a whole run of this task!
            debug(2, "Scheduler slip task[%u-%s] (%u/%u/%u)\n",
                  (unsigned)i,
                  _tasks[i].name,
                  (unsigned)dt,
                  (unsigned)interval_ticks,
                  (unsigned)_task_time_allowed);
        }

        if (_task_time_allowed > time_available) {
            // not enough time to run this task.  Continue loop -
            // maybe another task will fit into time remaining
            continue;
        }

        // run it
        _task_time_started = now;
        current_task = i;
        if (_debug > 1 && _perf_counters && _perf_counters[i]) {
            hal.util->perf_begin(_perf_counters[i]);
        }
        _tasks[i].function();
        if (_debug > 1 && _perf_counters && _perf_counters[i]) {
            hal.util->perf_end(_perf_counters[i]);
        }
        current_task = -1;

        // record the tick counter when we ran. This drives
        // when we next run the event
        _last_run[i] = _tick_counter;

        // work out how long the event actually took
        now = AP_HAL::micros();
        uint32_t time_taken = now - _task_time_started;

        if (time_taken > _task_time_allowed) {
            // the event overran!
            debug(3, "Scheduler overrun task[%u-%s] (%u/%u)\n",
                  (unsigned)i,
                  _tasks[i].name,
                  (unsigned)time_taken,
                  (unsigned)_task_time_allowed);
        }
        if (time_taken >= time_available) {
            time_available = 0;
            break;
        }
        time_available -= time_taken;
    }

    // update number of spare microseconds
    _spare_micros += time_available;

    _spare_ticks++;
    if (_spare_ticks == 32) {
        _spare_ticks /= 2;
        _spare_micros /= 2;
    }
}
发布了134 篇原创文章 · 获赞 20 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/u011583798/article/details/82971207
今日推荐