Nuttx系统启动是由ardupilot/mk/PX4/ROMFS/init.d里的rcS和rc.APM完成的。笔者阅读了rcS和rc.APM,该脚本类似C语言,并做了相关注释。主要是一些设备自检,启动各模块,最后启动进入ArduPilot_main,开始运行程序。本节只介绍启动脚本,程序的启动与整体运行下节再分析。
rcS
rcS #!nsh由nsh进行解析的脚本 # # PX4FMU startup script. # # This script is responsible for:这个脚本是为了回应 # # - mounting the microSD card (if present)安装SD卡 # - running the user startup script from the microSDcard (if present)从SD卡中运行用户启动脚本 # - detecting the configuration of the systemand picking a suitable查找系统配置并选择一个合适的启动脚 # startup script to continue with 本继续运行 # # Note: DO NOT add configuration-specificcommands to this script; # add them to the per-configuration scripts instead. # # # Default to auto-start mode. An initscript on the microSD card # can change this to prevent automatic startupof the flight script. # set MODE autostart 模式设置为自动开始 MODE跟USB应该是环境变量 set USB autoconnect USB自动连接 在Linux中也是用set去给一个环境变量赋值的 # # if rgbled start 亮灯的设置,有就亮白灯 then set HAVE_RGBLED 1 #show startup white rgbled rgb 16 16 16 else set HAVE_RGBLED 0 fi # # Try to mount the microSD card.尝试挂载SD卡 # echo "[init]looking for microSD..."echo是打印的命令 if mount -t vfat/dev/mmcsd0 /fs/microsdmount [-t vfstype] [-ooptions] device dir. then -t vfstype指定文件系统的类型,通常不必指定,fat32文件系统:vfat echo"[init] card mounted at /fs/microsd" /dev/mmcsd0指SD卡 /fs/microsd指挂载目录 set HAVE_MICROSD 1 # Startplaying the startup tune 产生启动语调,表示启动成功 tone_alarm1 else set HAVE_MICROSD 0 echo"Trying format of microSD" tone_alarm MBAGP ifmkfatfs /dev/mmcsd0 格式化SD卡 then echo "microSD card formatted" if mount -t vfat /dev/mmcsd0 /fs/microsd 挂载SD卡 then echo "formatsucceeded" set HAVE_MICROSD 1 tone_alarm 1 else echo "mountfailed" tone_alarm MNBG 挂载失败的语调 if [ $HAVE_RGBLED == 1] then rgbled rgb 16 0 0 挂载失败的灯 fi fi else 格式化失败 echo"format failed" tone_alarm MNBGG 挂载失败的语调 if [ $HAVE_RGBLED == 1 ] then rgbled rgb 16 0 0 挂载失败的灯 fi fi fi # # Look for an initscript on the microSD card. 寻找初始化脚本 # # To prevent automatic startup in the currentflight mode,为了避免自动启动当前飞行模式,脚本应该设置成 # the script should set MODE to some othervalue. 其他值 # if [ -f /fs/microsd/etc/rc ] 查找/fs/microsd/etc/rc这个路径的rc文件 then echo"[init] reading /fs/microsd/etc/rc" sh /fs/microsd/etc/rc 执行rc文件 sh是用来执行nsh脚本的一个命令 fi # Also consider rc.txt files if [ -f /fs/microsd/etc/rc.txt ]查找/fs/microsd/etc/rc.txt这个路径的rc文件 then echo"[init] reading /fs/microsd/etc/rc.txt" sh /fs/microsd/etc/rc.txt 执行rc.txt文件 fi # # Check for USB host # if [ $USB != autoconnect] 判断USB不是自动连接 then echo"[init] not connecting USB" else if sercon 初始化USB串口 then echo"[init] USB interface connected" else echo"[init] No USB connected" fi fi if [ $HAVE_MICROSD == 0 ] 判断是否有SD卡 then ifusb_connected then echo "Opening USB nsh" else echo "booting with no microSD" set HAVE_MICROSD 1 fi fi # if this is an APM build then there will be arc.APM script如果这是APM build,那么会有rc.APM脚本文件 # from an EXTERNAL_SCRIPTS build option rc.APM脚本文件来自EXTERNAL_SCRIPTS(外部脚本)build选项中 if [ -f /etc/init.d/rc.APM -a $HAVE_MICROSD == 1-a ! -f /fs/microsd/APM/nostart ] then echoRunning rc.APM # ifAPM startup is successful then nsh will exit sh /etc/init.d/rc.APM 执行/etc/init.d/rc.APM文件 else nshterm /dev/ttyACM0& fi
其实我们完全把它当作一个 Linux脚本来阅读就可以了。
MODE跟 USB应该是环境变量,在Linux中也是用 set去给一个环境变量赋值的。然后去启动 “rgbled”,这是由 “Firmware”提供的。我们很容易猜到这是用来控制那个高亮的 LED灯的。然后才是挂载 SD卡,文件系统为 vfat,挂载目录为 /fs/microsd。 SD卡挂载上来之后就去执行 SD卡中的脚本 “/fs/microsd/etc/rc”,所以我们就知道 sh是用来执行 nsh脚本的一个命令。应该来说 “/fs/microsd/etc/rc.txt”也是一个脚本,要不然也不会使用 sh命令了。 sercon你需要去看下源码才知道这是用来初始化 USB串口的。而最后则是通过调用 /etc/init.d/rc.APM脚本来真正初始化飞控。
rc.APM
#!nsh 由nsh进行解析的脚本 # APM startup script for NuttX on PX4 APM的Nuttx系统启动脚本 # To disable APM startup add a /fs/microsd/APM/nostart file # To enable mkblctrlstartup add a /fs/microsd/APM/mkblctrl file # To enable mkblctrl_+ startup add a /fs/microsd/APM/mkblctrl_+file # To enable mkblctrl_x startup add a /fs/microsd/APM/mkblctrl_xfile # To enable PWM on FMUv1 on ttyS1 add a /fs/microsd/APM/AUXPWM.enfile set deviceA /dev/ttyACM0 # check for an old file called APM, caused by 核查一个叫APM旧的文件 # a bug in an earlier firmware release 它由firmware发布,为了应对之前的一个bug if [ -f /fs/microsd/APM ] then echo"APM file found - renaming" mv /fs/microsd/APM /fs/microsd/APM.old 将/fs/microsd/APM命名为/fs/microsd/APM.old fi if [ -f /fs/microsd/APM/nostart]是否找到/fs/microsd/APM/nostart文件 then echo"APM/nostart found - skipping APMstartup" sh /etc/init.d/rc.error 执行/etc/init.d/rc.error fi # mount binfsso we can find the built-in apps挂载binfs(built_in_files)文件,这样就可以找到built-in应 if [ -f /bin/reboot ] 查找/bin/reboot文件 用程序 then echo"binfs already mounted" else echo"Mounting binfs" ifmount -t binfs /dev/null/bin 将binfs挂载到/bin目录下 then echo "binfs mounted OK" else sh /etc/init.d/rc.error 执行/etc/init.d/rc.error fi fi set sketch NONE 设置环境变量sketch为NONE if rm /fs/microsd/APM/boot.log 删除(remove)/fs/microsd/APM/boot.log then echo"removed old boot.log" fi set logfile /fs/microsd/APM/BOOT.LOG 将logfile文件设置成/fs/microsd/APM/BOOT.LOG文件 if [ ! -f /bin/ArduPilot ] 查找/bin/ArduPilot文件 then echo"/bin/ardupilot not found" sh /etc/init.d/rc.error fi if mkdir /fs/microsd/APM >/dev/null 生成/fs/microsd/APM/dev/null目录(猜测) then echo"Created APM directory" fi
这部分是我划出来的第一部分。先设置串口,然后一堆跟SD卡相关的命令。其实我们将一张空的SD卡放进去也是可以正常工作的。可能这里最让人感兴趣的就是“ArduPilot”了,因为APM工程就叫“ArduPilot”。在 g_builtins数组中也有这样一行信息:
{"ArduPilot",SCHED_PRIORITY_DEFAULT, 4096, ArduPilot_main},
也就是说 PX4将 APM原来的主程序设计成了 Nuttx中的一个命令。但其实我们在使用 Linux系统的时候,可执行文件其实就等同命令。这个命令跟其他命令还是有很大不同的,这个我们后面再讨论。
if [ -f /bin/px4io ] 查找/bin/px4io文件 then if [ -f/bin/lsm303d ] 查找/bin/lsm303d then echo"Detected FMUv2 board" setBOARD FMUv2 将BOARD设置成FMUv2 else echo"Detected FMUv1 board" setBOARD FMUv1 将BOARD设置成FMUv1 fi else echo"Detected FMUv4 board" setBOARD FMUv4 将BOARD设置成FMUv4 fi if [ $BOARD == FMUv1 ] 从这开始是对deviceC和D的设置 then setdeviceC /dev/ttyS2 if [ -f/fs/microsd/APM/AUXPWM.en] then To enablePWM on FMUv1 on ttyS1 add a /fs/microsd/APM/AUXPWM.en file setdeviceD /dev/null else setdeviceD /dev/ttyS1 fi else setdeviceC /dev/ttyS1 setdeviceD /dev/ttyS2 fi if uorb start uorb是否被设置为start then echo"uorb started OK" else sh /etc/init.d/rc.error fi
从这里我们可以看到FMUv1跟 FMUv2的差别在于 lsm303d这颗传感器。通过这颗传感器我们可以用肉眼进行区分。当然它们所使用的串口不同这个可能就无法直接看到了。
我看了下 uorb的源码,源码中有这样一条注释:“Start/load the driver”,我没太搞懂这里 “driver”是什么,只是它用到了一个变量:PX4IO *g_dev = nullptr;,由此也只能确定这是跟 IO板相关的,更具体的就不知道了。
# start mkblctrldriver if configured 启动mkblctrl驱动,如果mkblctrl被配置了 if [ -f /fs/microsd/APM/mkblctrl] then echo"Setting up mkblctrl driver" echo"Setting up mkblctrl driver">> $logfile 写入logfile mkblctrl -d /dev/pwm_output fi if [ -f /fs/microsd/APM/mkblctrl_+ ] then Toenable mkblctrl_+ startup add a /fs/microsd/APM/mkblctrl_+ file echo"Setting up mkblctrl driver +" echo"Setting up mkblctrl driver +">> $logfile mkblctrl -mkmode +-d /dev/pwm_output fi if [ -f /fs/microsd/APM/mkblctrl_x ] then Toenable mkblctrl_x startup add a /fs/microsd/APM/mkblctrl_x file echo"Setting up mkblctrl driver x" echo"Setting up mkblctrl driver x">> $logfile mkblctrl -mkmode x-d /dev/pwm_output fi 这一段我想应该是比较好理解的,就算我们不去看这个命令,因为这里是在设置机型。 if [ -f /bin/px4io ] then echo"Trying PX4IO board" # trythe px4io start twice. Some FMUv2 board don't再次启动px4io,因为有些FMUv2板第一次启动会不成功 # comeup the first time setHAVE_PX4IO false if px4iostart norc px4是否启动正常 then setHAVE_PX4IO true 将HAVE_PX4IO设置为true else # itmay be in bootloader mode 此时应该在bootloader模式 echoLoading /etc/px4io/px4io.bin 下载/etc/px4io/px4io.bin文件到板中 tone_alarm MBABGP 发出MBABGP的提示语调 ifpx4io update /etc/px4io/px4io.bin 判断px4是否跟新成/etc/px4io/px4io.bin then echo"upgraded PX4IO firmware OK" tone_alarm MSPAA 发出MSPAA的提示语调 else echo"Failed to upgrade PX4IO firmware" tone_alarm MNGGG 发出MNGGG的提示语调 fi sleep1 ifpx4io start norc px4是否启动正常 then set HAVE_PX4IO true 将HAVE_PX4IO设置为true #play happy tune again tone_alarm1 发出1号提示语调 fi fi else 找不到/bin/px4io setHAVE_PX4IO false echo"No PX4IO support" fi if [ $HAVE_PX4IO == true ] then echo"PX4IO board OK" ifpx4io checkcrc /etc/px4io/px4io.bin 校验/etc/px4io/px4io.bin通过CRC校验判断是否需要更新的 then echo "PX4IO CRC OK" else echo "PX4IO CRC failure" echo "PX4IO CRC failure" >> $logfile tone_alarm MBABGP 发出MBABGP的提示语调 ifpx4io safety_on then echo "PX4IO disarm OK" else echo "PX4IO disarm failed" fi sleep 1 ifpx4io forceupdate 14662 /etc/px4io/px4io.bin 强制跟新/etc/px4io/px4io.bin then sleep 1 if px4io start norc px4是否启动正常 then echo "PX4IO restart OK" echo "PX4IO restart OK" >> $logfile 写入logfile tone_alarm MSPAA else echo "PX4IO restart failed" echo "PX4IO restart failed" >> $logfile tone_alarm MNGGG sh /etc/init.d/rc.error 执行/etc/init.d/rc.error fi else echo "PX4IO update failed" echo "PX4IO update failed" >> $logfile tone_alarm MNGGG fi fi else HAVE_PX4IO=false echo"No PX4IO board found" echo "No PX4IO board found">> $logfile if [$BOARD == FMUv2 ] FMUv4不需要PX4IO,通过/bin/lsm303d传感器文件,判断是FMUv2还是FMUv1 then sh/etc/init.d/rc.error fi fi
这里看似很长的一段脚本,其实质做了一件事情:更新IO板的固件。这么长的脚本处理了两种情况:一、IO板没有固件;二,IO板有固件但不是最新的。更新固件是通过 px4io命令来完成的,如果我们想知道固件是如何更新的可以去阅读该命令的源码。固件更新其实我们在分析 IO板的启动的时候已经知道是通过 CRC校验判断是否需要更新的。
if [ $BOARD == FMUv1 -a $deviceD == /dev/ttyS1 ] 判断板子是否是FMUV1,deviceD是否对应/dev/ttyS1 then echo "Setting FMU mode_serial" fmu mode_serial 将fmu设置为串口模式 else iffmu mode_pwm4 将fmu设置为pwm模式 then echo "Set FMU mode_pwm4" fi fi
我粗略看了下 fmu的源码,没看明白这一段是干嘛的。
if mtd start /fs/mtd MTD(memory technology device内存技术设备)用于访问memory设备(ROM,Flash)的子系统 then linux中MTD的主要目的是为了使新的memory设备的驱动更加简单,为此它在硬件和上层之间提供了一个 echo"started mtd driver OK" 抽象的接口,MTD的所有源代码在/driver/mtd子目录中 else echo"failed to start mtd driver" echo"failed to start mtd driver" >> $logfile sh/etc/init.d/rc.error fi if mtd readtest /fs/mtd then echo"mtd readtest OK" 读mtd测试 else echo"failed to read mtd" echo"failed to read mtd" >> $logfile sh/etc/init.d/rc.error fi if [ $BOARD == FMUv2 -o $BOARD == FMUv4 ] then # theramtron on FMUv2 is very fast and can handle trillions of 之前有个设备读写失败了,所以进行读 #writes. This full rw test on each boot ensures it is working 写测试,确保读写没问题 #properly. We have one board that failed this, so # thetest is arguably worth having if mtdrwtest /fs/mtd mtd读写测试 then echo"mtd rwtest OK" else echo"failed to test mtd" echo"failed to test mtd" >> $logfile sh/etc/init.d/rc.error fi fi
对mtd进行读写测试,确保这方面没问题。这部分脚本是对mtd的操作,根据对Linux的了解,我觉得这里应该跟存储设备相关。虽然没有mount操作,但我仍然觉得这里可能是挂载存储设备,具体的仍然要到源码中去找寻答案。
echo "Starting APM sensors" if ms5611 start启动ms5611 then echo"ms5611 started OK" else echo"no ms5611 found" echo"No ms5611 found" >> $logfile sh/etc/init.d/rc.error fi if adc start 启动adc then echo"adc started OK" else echo"No adc" >> $logfile sh/etc/init.d/rc.error fi if [ $BOARD == FMUv1 ] 板子为FMUv1 then echo"Starting FMUv1 sensors" ifhmc5883 -C -T -X start 启动hmc5883 then echo "Have external hmc5883" else echo "No external hmc5883" fi ifhmc5883 -C -T -I start then echo "Have internal hmc5883" else echo "No internal hmc5883" fi ifmpu6000 start 启动mpu6000 then echo "mpu6000 startedOK" else sh/etc/init.d/rc.error fi ifl3gd20 start 启动l3gd20 then echo "l3gd20 started OK" else echo "No l3gd20" echo "No l3gd20" >> $logfile fi fi if [ $BOARD == FMUv2 ] 板子为FMUv2 then echo"Starting FMUv2 sensors" ifhmc5883 -C -T -X start then echo "Have external hmc5883" else echo "No external hmc5883" fi ifhmc5883 -C -T -I -R 4 start then echo "Have internal hmc5883" else echo "No internal hmc5883" fi #external MPU6000 is rotated YAW_180 from standard ifmpu6000 -X -R 4 start then echo "Found MPU6000 external" setHAVE_FMUV3 true else ifmpu9250 -X -R 4 start then echo "Found MPU9250 external" set HAVE_FMUV3 true else echo "No MPU6000 or MPU9250 external" set HAVE_FMUV3 false fi fi if [$HAVE_FMUV3 == true ] then #external L3GD20 is rotated YAW_180 from standard ifl3gd20 -X -R 4 start then echo "l3gd20 external started OK" else echo "No l3gd20" sh /etc/init.d/rc.error fi #external LSM303D is rotated YAW_270 from standard iflsm303d -a 16 -X -R 6 start then echo "lsm303d external started OK" else echo "No lsm303d" sh /etc/init.d/rc.error fi #internal MPU6000 is rotated ROLL_180_YAW_270 from standard ifmpu6000 -R 14 start then echo "Found MPU6000 internal" else if mpu9250 -R 14 start then echo "Found MPU9250 internal" else echo "No MPU6000 or MPU9250" echo "No MPU6000 or MPU9250" >> $logfile sh /etc/init.d/rc.error fi fi ifhmc5883 -C -T -S -R 8 start then echo "Found SPI hmc5883" fi else ifmpu6000 start then echo "Found MPU6000" else if mpu9250 start then echo "FoundMPU9250" else echo "No MPU6000 orMPU9250" echo "No MPU9250">> $logfile fi fi ifl3gd20 start then echo "l3gd20 started OK" else sh /etc/init.d/rc.error fi iflsm303d -a 16 start then echo "lsm303d started OK" else sh /etc/init.d/rc.error fi fi fi if [ $BOARD == FMUv4 ] 板子为FMUv4 then echo"Starting FMUv4 sensors" ifhmc5883 -C -T -X start then echo "Have external hmc5883" else echo "No external hmc5883" fi ifhmc5883 -C -T -S -R 2 start then echo "Have SPI hmc5883" else echo "No SPI hmc5883" fi ifmpu6000 -R 2 -T 20608 start then echo "Found ICM-20608 internal" fi ifmpu9250 -R 2 start then echo "Found mpu9250 internal" fi fi
从 echo那一句很容易看出这里是在启动传感器。当然,我暂时是不会去研究这些传感器是干嘛用的,只以看流程为主。从这段脚本我们也可以看出 V1跟 V2的一些区别,即 V1是内置罗盘,并且上电校准。但是 V2可以使用外置罗盘,而且上电过程是不校准的。至于陀螺为什么会有内部和外部的区别这个我没想通,也没有找到相关资料,这或许就要到源码中去寻找答案了。而源码肯定是要去看的。
# optional ETS airspeed sensor 空速计 if ets_airspeed start then echo"Found ETS airspeed sensor" fi if meas_airspeed start then echo"Found MEAS airspeed sensor" else ifmeas_airspeed start -b 2 then echo "Found MEAS airspeed sensor (bus2)" fi fi # optional Range Finder sensor 测距仪 if ll40ls -X start then echo"Found external ll40ls sensor" fi if ll40ls -I start then echo"Found internal ll40ls sensor" fi if trone start then echo"Found trone sensor" fi if mb12xx start then echo"Found mb12xx sensor" fi # optional PX4Flow sensor 光流 if [ -f /bin/px4flow ] then ifpx4flow start then echo"Found px4flow sensor" fi fi # optional PWM input driver pwm输入驱动 if pwm_input start then echo"started pwm_input driver" fi # optional oreo leds oled屏 if [ -f /bin/oreoled ] then iforeoled start autoupdate then echo "oreoledstarted OK" fi fi # optional smbus battery monitor 电池总线监视器 if batt_smbus -b 2 start then echo"Found batt_smbus" fi # optional irlock irlock暂时不知道是什么 if irlock start then echo"irlock started" fi echo Starting ArduPilot $deviceA $deviceC$deviceD if ArduPilot -d $deviceA -d2 $deviceC -d3$deviceD start 启动deviceA、deviceC、deviceD then echo ArduPilot started OK else sh/etc/init.d/rc.error fi echo "rc.APM finished"
最后还有一个人脚本: /etc/init.d/rc.error,在出错的时候我们经常看到这个脚本。
echo "Error in startup" tone_alarm MNCC if [ $HAVE_RGBLED == 1 ] then rgbled rgb16 0 0 fi nshterm/dev/ttyACM0 & 启动/dev/ttyACM0 sleep 1 nshterm/dev/ttyS0 & 启动/dev/ttyS0 sleep 1 exit