Nuttx Driver Development Manual

Table of contents

  1. Nuttx code gets compiled

  2. Nuttx startup process

  3. Source Code Analysis of Nuttx BootLoader Development

  4. gpio driver analysis

  5. I2c driver analysis

  6. PX4 Framework Analysis

  7. UORB inter-process communication analysis

  8. PX4 application layer driver analysis and implementation routine

  9. Serial port driver GPS driver analysis

  10. Mavlink flight control protocol analysis


1. Nuttx code gets compiled

You can refer to the official website installation process

Installing — NuttX latest documentation

1.1 ubuntu download dependencies

#sudo apt install \
bison flex gettext texinfo libncurses5-dev libncursesw5-dev \
gperf automake libtool pkg-config build-essential gperf genromfs \
libgmp-dev libmpc-dev libmpfr-dev libisl-dev binutils-dev libelf-dev \
libexpat-dev gcc-multilib g++-multilib picocom u-boot-tools util-linux
bison flex gettext texinfo libncurses5-dev libncursesw5-dev \
gperf automake libtool pkg-config build-essential gperf genromfs \
libgmp-dev libmpc-dev libmpfr-dev libisl-dev binutils-dev libelf-dev \
libexpat-dev gcc-multilib g++-multilib picocom u-boot-tools util-linux

1.2 KConfig frontend

#sudo apt install kconfig-frontends

1.3 Toolchain

#sudo apt install gcc-arm-none-eabi binutils-arm-none-eabi

1.4 Download NuttX


 

#mkdir nuttxspace
#cd nuttxspace
#git clone https://github.com/apache/incubator-nuttx.git nuttx
#git clone https://github.com/apache/incubator-nuttx-apps apps
#cd nuttxspace
#git clone https://github.com/apache/incubator-nuttx.git nuttx
#git clone https://github.com/apache/incubator-nuttx-apps apps

1.5 View the configuration of all hardware platforms supported by nuttx , you can first view the configuration of stm32


 

#./tools/configure.sh -L | less | grep stm32

<img data-cke-saved-src="file:///tmp/lu50995x3qlr8.tmp/lu50995x3qq99_tmp_3f6d84a23f302f52.png" src="file:///tmp/lu50995x3qlr8.tmp/lu50995x3qq99_tmp_3f6d84a23f302f52.png" data-cke-saved-name="图像1" name="图像1" width="643" height="361" />

1.6 Select a stm32 configuration, you can choose other models of configuration

#cd nuttx
#./tools/configure.sh -l stm32f4discovery:nsh
#./tools/configure.sh -l stm32f4discovery:nsh

1.7 You can then customize this configuration by using the menu-based configuration system


 

#cd nuttx
#make menuconfig
#make menuconfig

1.8 compile nuttx

#cd nuttx
#make -j4
1.10 Use openocdfirmware to burn
#sudo apt install openocd
Connect your USB cable for programming
#cd nuttx/
#openocd -f interface/stlink-v2.cfg -f target/stm32f1x.cfg -c 'init' \
  -c 'program nuttx/nuttx.bin verify reset' -c 'shutdown'
1.11 Interacting with nuttshell
#picocom -b 115200 /dev/ttyUSB0
You may have to add yourself to the dialout group on Linux to have access to the serial port.
#gpasswd -a <user> dialout
1.12 NuttX build.
#cd nuttx
#./tools/configure.sh -l sim:nsh
  Copy files
  Select CONFIG_HOST_LINUX=y
  Refreshing...

1.13 Build & run

#make clean; make
#./nuttx
login:
登录用户名称和密码
用户名: admin 
密码: Administrator
1.14 从另一个终端窗口,杀死模拟器。
#sudo pkill nuttx
#./nuttx
login:
Login username and password
Username: admin
Password: Administrator


1.14 From another terminal window, kill the emulator. 
#sudo pkill nuttx

2. Nuttx startup process


 

2.1 The startup process of nuttx has five stages


 

<span style="color:#000000"><strong>1 OSINIT_POWERUP</strong><strong>() </strong></span>
<span style="color:#000000"><strong>2 OSINIT_BOOT</strong><strong>()</strong></span>
<span style="color:#000000"><strong>3 OSINIT_TASKLISTS</strong><strong>()</strong></span>
<span style="color:#000000"><strong>4 OSINIT_MEMORY</strong><strong>()</strong></span>
<span style="color:#000000"><strong>5 OSINIT_HARDWARE</strong><strong>()</strong></span>
<span style="color:#000000"><strong>6 OSINIT_HARDWARE</strong><strong>()</strong></span>


 

 
 

2.2 The following analysis is based on the source code


 

vi ./include/nuttx/init.h +53

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
enum nx_initstate_e
{
  OSINIT_POWERUP   = 0,   上电初始化BSS段

  OSINIT_BOOT      = 1,   bootloader 初始化完成,但是系统中的服务还未启动
  OSINIT_TASKLISTS = 2,   准备分配任务列表
  OSINIT_MEMORY    = 3,   内存管理初始化
  OSINIT_HARDWARE  = 4,   MCU 初始化完成,并初始化了驱动
  OSINIT_OSREADY   = 5    操作系统完全初始化完成
};
{
  OSINIT_POWERUP = 0, power on to initialize the BSS segment

  OSINIT_BOOT = 1, bootloader initialization is complete, but the services in the system have not started yet
  OSINIT_TASKLISTS = 2, ready to assign task list
  OSINIT_MEMORY = 3, memory management initialization
  OSINIT_HARDWARE = ​​4, the MCU is initialized and the driver is initialized
  OSINIT_OSREADY = 5 OS full initialization complete
};

2.3 The stage of completion is when the MCU executes until nx_startthe system starts up OSINIT_BOOT.

2.4 In OSINIT_TASKLISTSthe stage the task list will be initialized.

2.5 In OSINIT_MEMORYthe stage , the semaphore needs to be initialized first (because many system components need to use the semaphore), and then the memory management is initialized.

2.6在 OSINIT_HARDWARE 阶段需要先初始化文件系统(因为后面的设备驱动需要使用到文件系统,这涉及到了 nuttx 的驱动管理方式,后面我会再讲。),然后配置 中断向量表、看门狗、时钟、系统 tick 、系统信号、系统消息队列、网络、线程栈内容初始化、注册标准的设备(如:/dev/null,/dev/zero,/dev/loop./dev/random)。

2.7在 OSINIT_OSREADY 阶段需要完成多系统相关的初始化(如启动 IDLE 线程)。

2.8下面是 启动过程的分析 vi ./sched/init/nx_start.c +260


 


 


 


 


 

2.9除了上面的分析,网上还有其他博主对启动流程分析图,这个更为详细。如下所示:

按照stm32 启动例程来说

vi arch/arm/src/stm32/stm32_start.c +185


 

vi boards/arm/stm32/axoloti/src/stm32_boot.c +53


 


 

对上面的流程加以分析


 

 
__start--                                 #处理器执行的第一条指令
        |  
      vi arch/arm/src/stm32/stm32_start.c +185  硬件开始初始化位置
        v  
   stm32_clockconfig()------              #初始化时钟
                           |  
                           v  
   rcc_reset()              #复位rcc
   stm32_stdclockconfig()   #初始化标准时钟
                 rcc_enableperipherals()  #使能外设时钟
                           |  
        --------------------  
        |  
        v  
   stm32_fpuconfig()                      #配置fpu,shenzhou/nsh未调用
   stm32_lowsetup()                       #基本初始化串口,之后可以使用up_lowputc()
   stm32_gpioinit()                       #初始化gpio,只是调用stm32_gpioremap()设置重映射
   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
                           |  
        --------------------  
        


   os_start()---------------              #初始化操作系统
                  

                vi sched/init/nx_start.c +313  开始初始化操作系统位置
    |  
                            
                 dq_init()                #初始化各种状态的任务列表(置为null)
                 g_pidhash[i]=            #初始化唯一可以确定的元素--进程ID
                 g_pidhash[PIDHASH(0)]=   #分配空闲任务的进程ID为0
                 g_idletcb=               #初始化空闲任务的任务控制块
                 sem_initialize()--       #初始化信号量
                                  |  
                                  v  
                       dq_init()          #将信号量队列置为null
                       sem_initholders()  #初始化持有者结构以支持优先级继承,shenzhou/nsh未调用
                                  |  
                           --------  
                           |  
                           v  
                 up_allocate_heap()       #分配用户模式的堆(设置堆的起点和大小)
                 kumm_initialize()        #初始化用户模式的堆
                 up_allocate_kheap()      #分配内核模式的堆,shenzhou/nsh未调用
                 kmm_initialize()         #初始化内核模式的堆,shenzhou/nsh未调用
                 task_initialize()        #初始化任务数据结构,shenzhou/nsh未调用
                 irq_initialize()         #将所有中断向量都指向同一个异常中断处理程序
                 wd_initialize()          #初始化看门狗数据结构
                 clock_initialize()       #初始化rtc
                 timer_initialize()       #配置POSIX定时器
                 sig_initialize()         #初始化信号
                 mq_initialize()          #初始化命名消息队列
                 pthread_initialize()     #初始化线程特定的数据,空函数
                 fs_initialize()---       #初始化文件系统
                                  |  
                                  v  
                       sem_init()         #初始化节点信号量为1
                       files_initialize() #初始化文件数组,空函数
                                  |  
                           --------  
                           |  
                           v  
                 net_initialize()--       #初始化网络
                                  |  
                                  v  
                       uip_initialize()   #初始化uIP层
                       net_initroute()    #初始化路由表,shenzhou/nsh未调用
                       netdev_seminit()   #初始化网络设备信号量
                       arptimer_init()    #初始化ARP定时器
                                  |  
                           --------  
                           |  
                           v  
                 up_initialize()---       #处理器特定的初始化
                                  |  
                                  v  
                       up_calibratedelay()#校准定时器
                       up_addregion()     #增加额外的内存段
                       up_irqinitialize() #设置中断优先级,关联硬件异常处理函数
                       up_pminitialize()  #初始化电源管理,shenzhou/nsh未调用
                       up_dmainitialize() #初始化DMA,shenzhou/nsh未调用
                       up_timerinit()     #初始化定时器中断
                       devnull_register() #注册/dev/null
                       devzero_register() #注册/dev/zero,shenzhou/nsh未调用
                       up_serialinit()    #注册串口控制台/dev/console和串口/dev/ttyS0
                       up_rnginitialize() #初始化并注册随机数生成器,shenzhou/nsh未调用
                       up_netinitialize() #初始化网络,是arch/arm/src/chip/stm32_eth.c中的
                       up_usbinitialize() #初始化usb驱动,shenzhou/nsh未调用
                       board_led_on()     #打开中断使能led,但很快会被其它地方的led操作改变状态
                                  |  
                           --------  
                           |  
                           v  
                 lib_initialize()         #初始化c库,空函数
                 group_allocate()         #分配空闲组
                 group_setupidlefiles()   #在空闲任务上创建stdout、stderr、stdin
                 group_initialize()       #完全初始化空闲组
                 os_bringup()------       #创建初始任务
                                  |  
                                  v  
                       KEKERNEL_THREAD()  #启动内核工作者线程
                       board_initialize() #最后一刻的板级初始化,shenzhou/nsh未调用
                       TASK_CREATE()      #启动默认应用程序
                                  |  
                           --------  
                           |  
                           v  
for up_idle()            #空闲任务循环
                           |  
        --------------------  
        |  
        v  
for(;;)                                #不应该到达这里处理器执行的第一条指令
        |  
      vi arch/arm/src/stm32/stm32_start.c +185  硬件开始初始化位置
        v  
   stm32_clockconfig()------              #初始化时钟
                           |  
                           v  
   rcc_reset()              #复位rcc
   stm32_stdclockconfig()   #初始化标准时钟
                 rcc_enableperipherals()  #使能外设时钟
                           |  
        --------------------  
        |  
        v  
   stm32_fpuconfig()                      #配置fpu,shenzhou/nsh未调用
   stm32_lowsetup()                       #基本初始化串口,之后可以使用up_lowputc()
   stm32_gpioinit()                       #初始化gpio,只是调用stm32_gpioremap()设置重映射
   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
                           |  
        --------------------  
        


   os_start()---------------              #初始化操作系统
                  

                vi sched/init/nx_start.c +313  开始初始化操作系统位置
    |  
                            
                 dq_init()                #初始化各种状态的任务列表(置为null)
                 g_pidhash[i]=            #初始化唯一可以确定的元素--进程ID
                 g_pidhash[PIDHASH(0)]=   #分配空闲任务的进程ID为0
                 g_idletcb=               #初始化空闲任务的任务控制块
                 sem_initialize()--       #初始化信号量
                                  |  
                                  v  
                       dq_init()          #将信号量队列置为null
                       sem_initholders()  #初始化持有者结构以支持优先级继承,shenzhou/nsh未调用
                                  |  
                           --------  
                           |  
                           v  
                 up_allocate_heap()       #分配用户模式的堆(设置堆的起点和大小)
                 kumm_initialize()        #初始化用户模式的堆
                 up_allocate_kheap()      #分配内核模式的堆,shenzhou/nsh未调用
                 kmm_initialize()         #初始化内核模式的堆,shenzhou/nsh未调用
                 task_initialize()        #初始化任务数据结构,shenzhou/nsh未调用
                 irq_initialize()         #将所有中断向量都指向同一个异常中断处理程序
                 wd_initialize()          #初始化看门狗数据结构
                 clock_initialize()       #初始化rtc
                 timer_initialize()       #配置POSIX定时器
                 sig_initialize()         #初始化信号
                 mq_initialize()          #初始化命名消息队列
                 pthread_initialize()     #初始化线程特定的数据,空函数
                 fs_initialize()---       #初始化文件系统
                                  |  
                                  v  
                       sem_init()         #初始化节点信号量为1
                       files_initialize() #初始化文件数组,空函数
                                  |  
                           --------  
                           |  
                           v  
                 net_initialize()--       #初始化网络
                                  |  
                                  v  
                       uip_initialize()   #初始化uIP层
                       net_initroute()    #初始化路由表,shenzhou/nsh未调用
                       netdev_seminit()   #初始化网络设备信号量
                       arptimer_init()    #初始化ARP定时器
                                  |  
                           --------  
                           |  
                           v  
                 up_initialize()---       #处理器特定的初始化
                                  |  
                                  v  
                       up_calibratedelay()#校准定时器
                       up_addregion()     #增加额外的内存段
                       up_irqinitialize() #设置中断优先级,关联硬件异常处理函数
                       up_pminitialize()  #初始化电源管理,shenzhou/nsh未调用
                       up_dmainitialize() #初始化DMA,shenzhou/nsh未调用
                       up_timerinit()     #初始化定时器中断
                       devnull_register() #注册/dev/null
                       devzero_register() #注册/dev/zero,shenzhou/nsh未调用
                       up_serialinit()    #注册串口控制台/dev/console和串口/dev/ttyS0
                       up_rnginitialize() #初始化并注册随机数生成器,shenzhou/nsh未调用
                       up_netinitialize() #初始化网络,是arch/arm/src/chip/stm32_eth.c中的
                       up_usbinitialize() #初始化usb驱动,shenzhou/nsh未调用
                       board_led_on()     #打开中断使能led,但很快会被其它地方的led操作改变状态
                                  |  
                           --------  
                           |  
                           v  
                 lib_initialize()         #初始化c库,空函数
                 group_allocate()         #分配空闲组
                 group_setupidlefiles()   #在空闲任务上创建stdout、stderr、stdin
                 group_initialize()       #完全初始化空闲组
                 os_bringup()------       #创建初始任务
                                  |  
                                  v  
                       KEKERNEL_THREAD()  #启动内核工作者线程
                       board_initialize() #最后一刻的板级初始化,shenzhou/nsh未调用
                       TASK_CREATE()      #启动默认应用程序
                                  |  
                           --------  
                           |  
                           v  
for up_idle()            #空闲任务循环
                           |  
        --------------------  
        |  
        v  
for(;;)                                #不应该到达这里
 
 

3.Nuttx BootLoader 开发之源码分析


 

3.1下面主要是以STM32为例子,首先分析一下stm32 3种启动方式启动方式

Cortex M3的内核有三种启动方式,其分别是:

3.1.1.通过boot引脚设置可以将中断向量表定位于SRAM区,即起始地址为0x2000000,同时复位后PC指针位于0x2000000处;

3.1.2.通过boot引脚设置可以将中断向量表定位于FLASH区,即起始地址为0x8000000,同时复位后PC指针位于0x8000000处;

3.1.3.通过boot引脚设置可以将中断向量表定位于内置Bootloader区,M3单片机复位后,从0x00000000取栈指针(SP),从0x00000004取复位向量(PC),有了栈指针和复位向量后,单片机就按照正常流程运行了,单片机启动默认先运行BootLoader,所以默认的中断向量表位置是BootLoader的中断向量表。

3.1.4 Cortex-M3单片机中断向量表的重要性

Cortex-M3单片机有一个管理中断向量表的寄存器,叫做向量表偏移量寄存器(VTOR)(地址:0xE000_ED08)。

STM32的BootLoader程序一般是在0x08000000,不是在0x00000000是因为STM32的重映射技术(不符合Cortex-M3),所以BootLoader的中断向量表在0x08000000那里。如果我们新下载的程序在0x08060000,并且新程序的中断向量表在起始地址,那么下载完程序,为了新程序能正确运行,我们需要把中断向量表重定位到0x08060000那里,再跳转到新程序运行

Cortex-M3内核规定,起始地址必须存放堆顶指针,而第二个地址则必须存放复位中断入口向量地址,这样在Cortex-M3内核复位后,会自动从起始地址的下一个32位空间取出复位中断入口向量,跳转执行复位中断服务程序。Cortex-M3内核是固定了中断向量表的位置而起始地址是可变化的.


 

3.2 Bootloader源码:CPU启动过程


 

3.2.1上电启动:EXTERN (vector_table)


 

1)初始化堆栈指针 .initial_sp_value = &_stack,

2)硬件错误为阻塞 .hard_fault = hard_fault_handler,

3)中断控制器 .irq = { IRQ_HANDLERS }

4)系统的复位入口函数 .reset = reset_handler,


 

3.2.2入口函数:ENTRY(reset_handler)


 

1)定义数据段 .data和.bss

2)pre_main()(开启协处理器)

3)main() //分为main_f1.c和main_f4.c


 

3.2.3.main函数:main(void)


 

1)board_init()(开发板的初始化)

2)bootloader()(nuttx系统的设置)

3)jump_to_app()(测试引导nuttx系统)


 

3.2.4.bootloader引导进入.vectors向量表:stm32_vectors.S


 

1)定义堆栈的大小

2)定义STM32的中断向量表

3)入口函数是ENTRY(__start)


 

3.2.5.入口函数是ENTRY(__start)


 

1)stm32的配置和初始化

2)nuttx系统的入口函数os_start() //\Firmware\NuttX\nuttx\sched\os_start.c启动Nuttx系统


 

3.2.6.系统入口函数os_start()


 

1)nuttx系统的初始化

2)nuttx系统的启动进程os_bringup()


 

3.2.7.系统的启动进程os_bringup()


 

1)创建内核进程

2)创建用户进程


 

3.2.8创建init进程(main_t)CONFIG_USER_ENTRYPOINT

IO板 : CONFIG_USER_ENTRYPOINT =user_start

Fmu板:CONFIG_USER_ENTRYPOINT = nsh_main


 


 

3.2.9.下面以 stm32 F1 为例子,从main_f1.c 启动初始化中断向量表,在bl.c中 启动nuttx,剩下的可以主要看一下串口虚拟代码路径kinetis os烧写拷贝实现过程。

├── bl.c

├── bl.h

├── board_types.txt

├── Bootloader.sublime-project

├── cdcacm.h

├── crypto_hal

│   ├── crypto.h

│   ├── image_toc.c

│   ├── image_toc.h

│   ├── monocypher

│   │   ├── crypto.c

│   │   ├── Makefile.include

│   │   └── public_key.h

│   ├── README.txt

│   └── test_key

│   └── key0.pub

├── hw_config.h

├── Jenkinsfile

├── jig_px4fmu.cfg

├── kinetis //这个路径里面是USB CDC 串口虚拟代码

│   ├── cdcacm.c

│   ├── usart.c

│   ├── usb_device_descriptor.c

│   ├── usb_device_descriptor.h

│   └── virtual_com.h

├── kinetis.c

├── kinetis.h

├── kinetisk66.ld

├── lib

│   └── kinetis

│   └── NXP_Kinetis_Bootloader_2_0_0

├── libopencm3

├── LICENSE.md

├── main_f1.c

├── main_f3.c

├── main_f4.c

├── main_f7.c

├── main_k66.c

├── Makefile

├── Makefile.f1

├── Makefile.f3

├── Makefile.f4

├── Makefile.f7

├── Makefile.k66

├── monocypher

├── px_mkfw.py

├── px_uploader.py

├── README.md

├── rules.mk

├── stm32

│   ├── cdcacm.c

│   └── usart.c

├── stm32f102.cfg

├── stm32f1.ld

├── stm32f1x.cfg

├── stm32f3.ld

├── stm32f3x.cfg

├── stm32f4.ld

├── stm32f4x.cfg

├── stm32f7.ld

├── Tools

│   ├── astylerc

│   ├── check_code_style_all.sh

│   ├── check_code_style.sh

│   ├── check_submodules.sh

│   ├── docker_run.sh

│   ├── files_to_check_code_style.sh

│   ├── fix_code_style.sh

│   └── pre-commit

└── uart.h


 

4.gpio 驱动分析


 

gpio分析以drivers/ioexpander/gpio.c为例子


 

4.1首先要实现文件读写的结构体,实现基本的 read open close 接口函数

static const struct file_operations g_gpio_drvrops =

{

NULL, /* open */

NULL, /* close */

gpio_read, /* read */

gpio_write, /* write */

gpio_seek, /* seek */

gpio_ioctl, /* ioctl */

NULL /* poll */

#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS

, NULL /* unlink */

#endif

};

4.2.下面的结构体是实现是硬件寄存器直接操作,是给上面 g_gpio_drvrops 使用

struct gpio_operations_s

{

/* Interface methods */

CODE int (*go_read)(FAR struct gpio_dev_s *dev, FAR bool *value);

CODE int (*go_write)(FAR struct gpio_dev_s *dev, bool value);

CODE int (*go_attach)(FAR struct gpio_dev_s *dev,

pin_interrupt_t callback);

CODE int (*go_enable)(FAR struct gpio_dev_s *dev, bool enable);

CODE int (*go_setpintype)(FAR struct gpio_dev_s *dev, enum gpio_pintype_e pintype);

};

static const struct gpio_operations_s gpin_ops =

{

.go_read = gpin_read,

.go_write = NULL,

.go_attach = NULL,

.go_enable = NULL,

};

static const struct gpio_operations_s gpout_ops =

{

.go_read = gpout_read,

.go_write = gpout_write,

.go_attach = NULL,

.go_enable = NULL,

};

static const struct gpio_operations_s gpint_ops =

{

.go_read = gpint_read,

.go_write = NULL,

.go_attach = gpint_attach,

.go_enable = gpint_enable,

};

4.3 这是驱动的入口函数,用来获取

int gpio_pin_register(FAR struct gpio_dev_s *dev, int minor)

4.4.通过register_driver函数申请设备描述符,”/dev/gpiox”,方便应用程序的调用

register_driver(devname, &g_gpio_drvrops, 0666, dev);

→inode_reserve(path, mode, &node);

5.I2c驱动分析


 

gpio分析以drivers/i2c/i2c_driver.c为例子,主要实现以下接口


 

1.入口函数

i2c_register(FAR struct i2c_master_s *i2c, int bus)

2.实现 file_operations的基本接口函数

static const struct file_operations i2cdrvr_fops

3.申请设备描述符

register_driver(devname, &i2cdrvr_fops, 0666, priv);

4.跟硬件直接交互的接口函数

I2C_TRANSFER(priv->i2c, transfer->msgv, transfer->msgc);

6.PX4 框架分析

6.1 PX4自动驾驶仪软件可分为三大部分:实时操作系统、中间件和飞行控制栈。

6.2 NuttX实时操作系统,上文对nuttx 已经做了深析,它提供POSIX-style的用户操作环境(printf(), pthreads,/dev/ttyS1,open(),write(),poll(),ioctl()),进行底层的任务调度。

6.3 PX4中间件,PX4中间件运行于操作系统之上,提供设备驱动和一个微对象请求代理(micro object request brokeruORB)用于驾驶仪上运行的单个任务之间的异步通信。Px43DR开源后,整个代码结构被⼤改,原先的系统被摒弃,进而采用Nuttx,但是核心思想没变-为了简化开发而采用牺牲部分效率的消息传递机制,这是Px4 ArduPilot 最本质的差别。

6.4 PX4飞行控制栈。飞行控制栈可以使用PX4的控制软件栈,也可以使用其他的控制软APM:PlaneAPM:Copter,但必须运行于PX4中间件之上。


 

6.4.1决策导航部分:根据飞行器自身安全状态和接收到的命令,决定工作于什么模式,下一步应该怎么做。

6.4.2位置姿态估计部分:根据传感器得到自身的位置和姿态信息,此部分算法含金量最高,算法也相当多。

6.4.3位置姿态控制部分:根据期望位置和姿态设计控制结构,尽可能快、稳的达到期望位置和姿态。


 


 


 


 

7.UORB 进程间通讯分析

7.1 uORB(Micro Object Request Broker,微对象请求代理器)是PX4/Pixhawk系统中非常重要且关键的一个模块,它肩负了整个系统的数据传输任务,所有的传感器数据、GPS、PPM信号等都要从芯片获取后通过uORB进行传输到各个模块进行计算处理。实际上uORB是一套跨「进程」 的IPC通讯模块。在Pixhawk中, 所有的功能被独立以进程模块为单位进行实现并工作。而进程间的数据交互就由为重要,必须要能够符合实时、有序的特点。

 7.2 Pixhawk使用的是NuttX实时ARM系统,uORB实际上是多个进程打开同一个设备文件,进程间通过此文件节点进行数据交互和共享。进程通过命名的「总线」交换的消息称之为「主题」(topic),在Pixhawk 中,一个主题仅包含一种消息类型,通俗点就是数据类型。每个进程可以「订阅」或者「发布」主题,可以存在多个发布者,或者一个进程可以订阅多个主题,但是一条总线上始终只有一条消息。


 


 

7.3 应用层中操作基础飞行的应用之间都是隔离的,这样提供了一种安保模式,以确保基础操作独立的高级别系统状态的稳定性。而沟通它们的就是uORB。

7.4 uORB文件夹结构说明


 

topics : 系统通用接口定义的标准主题,比如电池电量转态、GPS的位置参数等

module.mk : uORB模块makefile文件

objects_common.cpp: 通用接口标准主题定义集合,如添加新主题在这里定义

ORBMap.hpp : 对象请求器节点链表管理(驱动节点)

ORBSet.hpp : 对象请求器节点管理(非驱动节点)

Publication.cpp : 在不同的发布中遍历使用

Publication.hpp : 在不同的发布中遍历使用

Subscription.cpp : 在不同的订阅中遍历使用

Subscription.hpp : 在不同的订阅中遍历使用

uORB.cpp : uORB的实现

uORB.h : uORB头文件

uORBCommon.hpp : uORB公共部分变量定义实现

uORBCommunicator.hpp : 远程订阅的接口实现,实现了对不同的通信通道管理,如添加/移除订阅者,可以基于TCP/IP或fastRPC;传递给通信链路的实现,以提供在信道上接收消息的回调。

uORBDevices.hpp :

uORBDevices_nuttx.cpp : 节点操作,close,open,read,write

uORBDevices_nuttx.hpp :

uORBDevices_posix.cpp :

uORBDevices_posix.hpp :

uORBMain.cpp : uORB入口

uORBManager.hpp : uORB功能函数实现头文件

uORBManager_nuttx.cpp : uORB功能函数实现(Nuttx)

uORBManager_posix.cpp : uORB功能函数实现(Posix)

uORBTest_UnitTest.cpp : uORB测试

uORBTest_UnitTest.hpp : uORB测试头文件,包括主题定义和声明等


 


 


 


 


 


 


 

8.PX4应用层驱动分析并实现例程


 

8.1下面例子是一个发布订阅的例子,可供参考和使用

sensor_combined主题是官方提供的通用接口标准主题。
vehicle_attitude主题是官方提供的通用接口标准主题。

8.2 程序流程图如下:


 


 


 


 


 


 


 


 


 


 


 


 


 


 


 


 

/**

* @file px4_simple_app.c

* Minimal application example for PX4 autopilot

*/


 

#include <nuttx/config.h>

#include <unistd.h>

#include <stdio.h>

#include <poll.h>


 

#include <uORB/uORB.h>

#include <uORB/topics/sensor_combined.h>

#include <uORB/topics/vehicle_attitude.h>


 

__EXPORT int px4_simple_app_main(int argc, char *argv[]);


 

int px4_simple_app_main(int argc, char *argv[])

{

printf("Hello Sky!\n");


 

/* 订阅 sensor_combined 主题 */

int sensor_sub_fd = orb_subscribe(ORB_ID(sensor_combined));

orb_set_interval(sensor_sub_fd, 1000);


 

/* 公告 attitude 主题 */

struct vehicle_attitude_s att;

memset(&att, 0, sizeof(att));

int att_pub_fd = orb_advertise(ORB_ID(vehicle_attitude), &att);


 

/*一个应用可以等待多个主题,在这里只等待一个主题*/

struct pollfd fds[] = {

{ .fd = sensor_sub_fd, .events = POLLIN },

/* there could be more file descriptors here, in the form like:

* { .fd = other_sub_fd, .events = POLLIN },

*/

};


 

int error_counter = 0;


 

while (true) {

/* wait for sensor update of 1 file descriptor for 1000 ms (1 second) */

int poll_ret = poll(fds, 1, 1000);


 

/* handle the poll result */

if (poll_ret == 0) {

/* this means none of our providers is giving us data */

printf("[px4_simple_app] Got no data within a second\n");

} else if (poll_ret < 0) {

/* this is seriously bad - should be an emergency */

if (error_counter < 10 || error_counter % 50 == 0) {

/* use a counter to prevent flooding (and slowing us down) */

printf("[px4_simple_app] ERROR return value from poll(): %d\n"

, poll_ret);

}

error_counter++;

} else {


 

if (fds[0].revents & POLLIN) {

/* obtained data for the first file descriptor */

struct sensor_combined_s raw;

/* copy sensors raw data into local buffer */

orb_copy(ORB_ID(sensor_combined), sensor_sub_fd, &raw);

printf("[px4_simple_app] Accelerometer:\t%8.4f\t%8.4f\t%8.4f\n",

(double)raw.accelerometer_m_s2[0],

(double)raw.accelerometer_m_s2[1],

(double)raw.accelerometer_m_s2[2]);


 

/* 赋值 att 并且发布这些数据给其他的应用 */

att.roll = raw.accelerometer_m_s2[0];

att.pitch = raw.accelerometer_m_s2[1];

att.yaw = raw.accelerometer_m_s2[2];

orb_publish(ORB_ID(vehicle_attitude), att_pub_fd, &att);

}

/* there could be more file descriptors here, in the form like:

* if (fds[1..n].revents & POLLIN) {}

*/

}

}


 

return 0;

}


 

8.3 sensor_combined主题是官方提供的通用接口标准主题。


 

/**

* @file px4_simple_app.c

* Minimal application example for PX4 autopilot

*/


 

#include <nuttx/config.h>

#include <unistd.h>

#include <stdio.h>

#include <poll.h>


 

#include <uORB/uORB.h>

#include <uORB/topics/sensor_combined.h>


 

__EXPORT int px4_simple_app_main(int argc, char *argv[]);


 

int px4_simple_app_main(int argc, char *argv[])

{

printf("Hello Sky!\n");


 

/*订阅sensor_combined 主题*/

int sensor_sub_fd = orb_subscribe(ORB_ID(sensor_combined));


 

/*一个应用可以等待多个主题,在这里只等待一个主题*/

struct pollfd fds[] = {

{ .fd = sensor_sub_fd, .events = POLLIN },

/* 这里可以添加更多的文件描述符;

* { .fd = other_sub_fd, .events = POLLIN },

*/

};


 

int error_counter = 0;


 

while (true) {

/*poll函数调用阻塞的时间为1s*/

int poll_ret = poll(fds, 1, 1000);


 

/*处理poll返回的结果 */

if (poll_ret == 0) {

/* 这表示时间溢出了,在1s内没有获取到发布者的数据 */

printf("[px4_simple_app] Got no data within a second\n");

} else if (poll_ret < 0) {

/* 出现问题 */

if (error_counter < 10 || error_counter % 50 == 0) {

/* use a counter to prevent flooding (and slowing us down) */

printf("[px4_simple_app] ERROR return value from poll(): %d\n"

, poll_ret);

}

error_counter++;

} else {


 

if (fds[0].revents & POLLIN) {

/*从文件描述符中获取订阅的数据*/

struct sensor_combined_s raw;

/* copy sensors raw data into local buffer */

orb_copy(ORB_ID(sensor_combined), sensor_sub_fd, &raw);

printf("[px4_simple_app] Accelerometer:\t%8.4f\t%8.4f\t%8.4f\n",

(double)raw.accelerometer_m_s2[0],

(double)raw.accelerometer_m_s2[1],

(double)raw.accelerometer_m_s2[2]);

}

/* 如果有更多的文件描述符,可以这样:

* if (fds[1..n].revents & POLLIN) {}

*/

}

}


 

return 0;

}

9.串口驱动GPS 驱动分析

9.1 GPS 通过串口获取数据,可以选择不同数据协议,我这边选用NEMA协议,不断轮训获取新数据,并且通过UORB将读到的数据放到队列中,被其他进程获取到。


 


 

10.mavlink 飞行控制协议分析


 


 

Mavlink是目前最常见的无人机飞控协议之一。PX4对Mavlink协议提供了良好的原生支持。该协议既可以用于地面站(GCS)对无人机(UAV)的控制,也可用于UAV对GCS的信息反馈。其飞控场景一般是这样的:

a) 手工飞控:GCS -> (MavLink) -> UAV
b) 信息采集:GCS <- (Mavlink) <- UAV
c) 自治飞控:User App -> (MavLink) -> UAV

也就是说,如果你想实现地面站控制飞行,那么由你的地面站使用Mavlink协议,通过射频信道(或 wifi etc.)给无人机发送控制指令就可以了。如果你想实现无人机自主飞行,那么就由你自己写的应用(运行在无人机系统上)使用Mavlink协议给无人机发送本地的控制指令就可以了。

然而,为实现飞控架构的灵活性,避免对底层实现细节的依赖,在PX4中,并不鼓励开发者在自定义飞控程序中直接使用Mavlink,而是鼓励开发者使用一种名为uORB((Micro Object Request Broker,微对象请求代理)的消息机制。其实uORB在概念上等同于posix里面的命名管道(named pipe),它本质上是一种进程间通信机制。由于PX4实际使用的是NuttX实时ARM系统,因此uORB实际上相当于是多个进程(驱动级模块)打开同一个设备文件,多个进程(驱动级模块)通过此文件节点进行数据交互和共享。

在uORB机制中,交换的消息被称之为topic,一个topic仅包含一种message类型(即数据结构)。每个进程(或驱动模块)均可“订阅”或“发布”多个topic,一个topic可以存在多个发布者,而且一个订阅者可也订阅多个topic。而正因为有了uORB机制的存在,上述飞控场景变成了:

a) 手工飞控:GCS -> (MavLink) -> (uORB topic) -> UAV
b) 信息采集:GCS <- (Mavlink) <- (uORB topic) <- UAV
c) 自治飞控:User App -> (uORB topic) -> (MavLink) -> UAV

有了以上背景基础,便可以自写飞控逻辑了,仅需在PX4源码中,添加一个自定义module,然后使用uORB订阅相关信息(如传感器消息等),并发布相关控制信息(如飞行模式控制消息等)即可。具体的uORB API、uORB消息定义可参考PX4文档与源码,所有控制命令都在firmware代码的msg里面,不再敷述。

最后值得一提的是,在PX4系统中,还提供了一个名为mavlink的专用module,源码在firmware的src/modules/mavlink中,这货与linux的控制台命令工具集相当相似,其既可以作为ntt控制台下的命令使用,又可作为系统模块加载后台运行。其所实现的功能包括:1)uORB消息解析,将uORB消息实际翻译为具体的Mavlink底层指令,或反之。2)通过serial/射频通信接口获取或发送Mavlink消息,既考虑到了用户自写程序的开发模式,也适用于类似linux的脚本工具链开发模式,使用起来很灵活,有兴趣的可以看看。


 


 


 

→GPSDriverNMEA::receive//此时传递超时时间去解析下面的数据,超时时间200ms 5HZ

-> read ( buf , sizeof ( buf ), timeout );// Pass parameters here to the callback

- > _callback ( GPSCallbackType :: readDeviceData , buf , buf_length , _callback_user ); // callback receiving

→pollOrRead // read data

→parseChar(buf[i]); // check protocol comma

→handleMessage(l); // Start parsing


 


 


 


 


 


 

Guess you like

Origin blog.csdn.net/qq_31057589/article/details/128398712
Recommended