跨平台移植
将基于PSDK 开发的负载设备控制程序移植到不同版本的软硬件平台上时,需要先初始化Hal 和Osal 层,注册关键的配置信息。通过加载静态库、引用指定的资源文件并声明结构体,设置负载设备控制程序跨平台移植所需的配置信息。最后使用指定的接口将Platform 模块注册到负载设备的控制程序中,获取硬件资源和操作系统资源,实现负载设备控制程序的跨平台移植。
示例代码
- Linux
- Hal 层适配:
samples/sample_c/platform/linux/manifold2/hal
- Osal 层适配:
samples/sample_c/platform/linux/common/osal/
- Hal 层适配:
- FreeRTOS
- Hal 层适配:
samples/sample_c/platform/rtos_freertos/stm32f4_discovery/hal
- Osal 层适配:
samples/sample_c/platform/rtos_freertos/common/osal/
- Hal 层适配:
说明: PSDK Platform 模块的API 接口,在
psdk_lib/include/dji_platform.h
文件中。
概述
为能使基于PSDK 开发的负载设备控制程序移植到不同的软硬件平台,需要通过Hal(Hardware Abstraction Layer,硬件接口层)适配不同的硬件平台,通过Osal(Operating System Abstraction Layer,操作系统抽象层)实现与不同操作系统的兼容,如 图1.代码移植 所示。
图1.代码移植
基础概念
Hal 层
Hal(Hardware Abstraction Layer,硬件接口层)是PSDK 硬件接口抽象层,位于操作系统、负载设备控制程序和硬件接口间。开发者需要按照DjiPlatform_RegHalUartHandler()
, DjiPlatform_RegHalUsbBulkHandler
与DjiPlatform_RegHalNetworkHandler()
接口中的函数原型,实现并将适配Hal 层的函数注册到负载设备控制程序中,使基于PSDK 开发的负载设备控制程序,通过Hal 层即可直接访问负载设备硬件的底层资源,控制负载设备执行相应的动作,使负载设备控制程序能够适配不同的硬件平台,如STM32F407IGH6-EVAL 或Manifold 2-C 等。
网口设备
需要使用网口的设备适配Hal 层函数需要执行如下操作:
实现适配Hal 层网口操作函数
- 本地网卡初始化:
T_DjiReturnCode (*NetworkInit)(const char *ipAddr, const char *netMask, T_DjiNetworkHandle *networkHandle)
- 本地网卡反初始化:
T_DjiReturnCode (*NetworkDeInit)(T_DjiNetworkHandle networkHandle)
- 获取网卡信息:
T_DjiReturnCode (*NetworkGetDeviceInfo)(T_DjiHalNetworkDeviceInfo *deviceInfo)
- 本地网卡初始化:
使用
DjiPlatform_RegHalNetworkHandler()
接口注册网口操作函数
串口设备
使用串口通信的设备适配Hal 层函数需要执行如下操作:
- 实现适配Hal 层UART 操作函数
- 串口初始化:
T_DjiReturnCode (*UartInit)(E_DjiHalUartNum uartNum, uint32_t baudRate, T_DjiUartHandle *uartHandle)
- 串口反初始化:
T_DjiReturnCode (*UartDeInit)(T_DjiUartHandle uartHandle)
- 发送数据:
T_DjiReturnCode (*UartWriteData)(T_DjiUartHandle uartHandle, const uint8_t *buf, uint32_t len, uint32_t *realLen)
- 接收数据:
T_DjiReturnCode (*UartReadData)(T_DjiUartHandle uartHandle, uint8_t *buf, uint32_t len, uint32_t *realLen)
- 获取串口状态:
T_DjiReturnCode (*UartGetStatus)(E_DjiHalUartNum uartNum, T_DjiUartStatus *status)
- 串口初始化:
- 使用
DjiPlatform_RegHalUartHandler()
接口注册串口操作函数
说明
- 负载设备串口的参数
- 波特率:460800(SkyPort V2/X-Port 固件版本低于且包含V01.01.0100)或者DJI Assistant 2 软件设置值(SkyPort V2/X-Port 固件版本高于V01.01.0100)
- 停止位:1
- 数据位:8
- 奇偶校验:无
- 可通过DJI Assistant 2 软件设置SkyPort V2/X-Port 与PSDK 负载设备的通信波特率(PSDK 设备通信参数须与SkyPort V2/X-Port 保持一致),完美适配不同的硬件平台(某些硬件平台不支持默认通信参数或存在功能缺陷)且适应不同的应用场景(例如订阅大量数据时需适当提高通信波特率)。
图2. 设置通信波特率
USB设备
使用USB Bulk通信的设备适配Hal 层函数需要执行如下操作:
实现适配Hal 层USB Bulk 操作函数
- USB Bulk初始化:
T_DjiReturnCode (*UsbBulkInit)(T_DjiHalUsbBulkInfo usbBulkInfo, T_DjiUsbBulkHandle *usbBulkHandle)
- USB Bulk反初始化:
T_DjiReturnCode (*UsbBulkDeInit)(T_DjiUsbBulkHandle usbBulkHandle)
- USB Bulk写数据:
T_DjiReturnCode (*UsbBulkWriteData)(T_DjiUsbBulkHandle usbBulkHandle, const uint8_t *buf, uint32_t len, uint32_t *realLen)
- USB Bulk读数据:
T_DjiReturnCode (*UsbBulkReadData)(T_DjiUsbBulkHandle usbBulkHandle, uint8_t *buf, uint32_t len, uint32_t *realLen)
- 获取USB Bulk信息(仅针对SDK设备端作为USB Device的情况):
T_DjiReturnCode (*UsbBulkGetDeviceInfo)(T_DjiHalUsbBulkDeviceInfo *deviceInfo)
- USB Bulk初始化:
使用
DjiPlatform_RegHalUsbBulkHandler()
接口注册 USB Bulk 操作函数
在M300 RTK, M30/M30T, M3E/M3T上,USB的主从模式存在差异:
- M300 RTK:SDK设备端为USB Host,飞机端为USB Device
- M30/M30T:SDK设备端为USB Device,飞机端为USB Host
- M3E/M3T:SDK设备端为USB Device,飞机端为USB Host
M300 RTK
USB功能限制如下表所示
USB功能 | 功能限制 |
---|---|
虚拟串口 | 云台管理、相机管理、部分基础通信 |
USB Bulk | 获取码流、获取感知灰度图、MOP功能、媒体文件管理 |
虚拟串口:
- Linux平台:无需额外配置,驱动默认支持。可以通过
ls /dev/ttyACMx
查询到,当作标准串口设备进行操作。 - RTOS平台:需要移植USB Host的库进行读写操作,详情请参考 samples/sample_c/platform/rtos_freertos/stm32f4_discovery/middlewares/ST/STM32_USB_Host_Library/中的USB_Host移植实现。
USB-Bulk:
Linux平台:用户在Type-C拓展口开发程序,可以通过安装并调用libusb静态库的方式进行开发。
安装方法如下:
sudo apt-get install libusb-1.0-0-dev
RTOS平台:不支持USB Bulk相关驱动。
M30/M30T
USB功能限制如下表所示
USB功能 | 功能限制 |
---|---|
RNDIS虚拟网卡/USB转以太网卡 | 推送相机码流、订阅FPV/主相机码流 |
USB Bulk通道1 | 推送第三方相机码流、获取码流 |
USB Bulk通道2 | 获取感知灰度图、媒体文件管理 |
说明 当SDK设备端作为USB Device时,可以使用推荐型号的USB网卡进行连接,如AX88179A、RTL8152等网卡型号, 同时需要通过注册
NetworkGetDeviceInfo
接口传入对应网卡的VID/PID信息, 保证飞机端可以正常识别USB网卡。
M3E/M3T
USB功能限制如下表所示
USB功能 | 功能限制 |
---|---|
RNDIS虚拟网卡/USB转以太网卡 | 推送相机码流、订阅主相机码流 |
USB Bulk通道1 | 推送第三方相机码流、获取码流 |
USB Bulk通道2 | 获取感知灰度图 |
将开发平台的USB配置为从模式
配置USB为从模式(USB-Device):配置开发平台的USB管脚复用,并且设置为USB-OTG或者USB-Device模式。当配置成功后,可以将USB口接到另外一个设备的USB Host上,可以查询对应的vid和pid信息,并确认是否符合预期。如我们将DJI Manifold 2-G的vid和pid配置为0x0955和0x7020,将USB接到另外一台Ubuntu上,通过
lsusb
命令方便地查询到。配置RNDIS网卡:首先确认平台是否支持并且打开 USB Gadget驱动,详细请参考链接。编写RNDIS的配置,以管理员身份运行后,可以将USB口接到另外一个设备的USB Host上,通过
ifconfig
命令查询到对应的网卡是否枚举出来,并确认是否符合预期,并且可以通过ping
命令来检查是否通信成功。配置USB Bulk:首先确认是否平台是否支持并且打开 USB Gadget驱动,详细请参考链接。编写USB Bulk的配置配置并运行Bulk初始化程序,以管理员身份运行后,可以将USB口接到另外一个设备的USB Host上,先使用
sudo su
切换root账户再通过cat /sys/kernel/debug/usb/devices
命令查询到对应的Bulk端口,并确认是否符合预期。对bulk设备的读写操作详情请参考hal_usb_bulk.c中的接口实现。
I2C设备
使用I2C 认证芯片的设备适配Hal 层函数需要执行如下操作:
实现适配Hal 层 I2C 设备操作函数
- I2C设备初始化:
T_DjiReturnCode (*I2cInit)(T_DjiHalI2cConfig i2cConfig, T_DjiI2cHandle *i2cHandle)
- I2C设备反初始化:
T_DjiReturnCode (*I2cDeInit)(T_DjiI2cHandle i2cHandle)
- I2C设备写数据:
T_DjiReturnCode (*I2cWriteData)(T_DjiI2cHandle i2cHandle, uint16_t devAddress, const uint8_t *buf, uint32_t len, uint32_t *realLen)
- I2C设备读数据:
T_DjiReturnCode (*I2cReadData)(T_DjiI2cHandle i2cHandle, uint16_t devAddress, uint8_t *buf, uint32_t len, uint32_t *realLen)
- I2C设备初始化:
使用
DjiPlatform_RegHalI2cHandler()
接口注册I2C设备操作函数。
Osal 层
Osal(Operating System Abstraction Layer,操作系统抽象层)是PSDK 的操作系统抽象层,位于负载设备控制程序和操作系统间。开发者需要按照DjiPlatform_RegOsalHandler()
接口中的函数原型,实现并将适配不同操作系统的函数注册到负载设备控制程序中,使用PSDK 开发的负载设备控制程序即可直接访问操作系统以及操作系统内核的资源,将负载设备控制程序移植到不同的操作系统上。
线程函数
使用线程机制管理负载设备控制程序执行相应的任务,开发者需要实现创建线程、销毁线程和线程睡眠的函数。
- 创建线程:
T_DjiReturnCode (*TaskCreate)(const char *name, void *(*taskFunc)(void *), uint32_t stackSize, void *arg, T_DjiTaskHandle *task)
- 销毁线程:
T_DjiReturnCode (*TaskDestroy)(T_DjiTaskHandle task)
- 线程睡眠:
T_DjiReturnCode (*TaskSleepMs)(uint32_t timeMs)
互斥锁
互斥锁是一种用于防止多个线程同时对同一队列、计数器和中断处理程序等公共资源(如共享内存等)执行读写操作的机制,能够有效避免进程死锁或长时间的等待。使用互斥锁机制,需要开发者实现创建互斥锁、销毁互斥锁、互斥锁上锁和互斥锁解锁。
创建互斥锁:
T_DjiReturnCode (*MutexCreate)(T_DjiMutexHandle *mutex)
销毁互斥锁:
T_DjiReturnCode (*MutexDestroy)(T_DjiMutexHandle mutex)
互斥锁上锁:
T_DjiReturnCode (*MutexLock)(T_DjiMutexHandle mutex)
互斥锁解锁:
T_DjiReturnCode (*MutexUnlock)(T_DjiMutexHandle mutex)
信号量
信号量是一种用于防止多线程同时操作相同代码段的机制。开发者使用该机制时,需要实现创建信号量、销毁信号量、等待信号量、释放信号量和等待超时信号量函数。
- 创建信号量:
T_DjiReturnCode (*SemaphoreCreate)(uint32_t initValue, T_DjiSemaHandle *semaphore)
说明: 使用该接口时,请设置
initValue
信号量的初始值。
- 销毁信号量:
T_DjiReturnCode (*SemaphoreDestroy)(T_DjiSemaHandle semaphore)
- 等待信号量:
T_DjiReturnCode (*SemaphoreWait)(T_DjiSemaHandle semaphore)
说明: 等待信号量接口等待时间的最大值为32767 ms。
- 等待超时信号量:
T_DjiReturnCode (*SemaphoreTimedWait)(T_DjiSemaHandle semaphore, uint32_t waitTimeMs)
- 释放信号量:
T_DjiReturnCode (*SemaphorePost)(T_DjiSemaHandle semaphore)
时间接口
- 获取当前系统的时间(ms):
T_DjiReturnCode (*GetTimeMs)(uint32_t *ms)
- 获取当前系统的时间(us):
T_DjiReturnCode (*GetTimeUs)(uint64_t *us)
内存管理接口
- 申请内存:
void *(*Malloc)(uint32_t size)
- 释放内存:
void (*Free)(void *ptr)
实现跨平台移植
1. 跨平台接口适配
跨平台接口适配 | 适配方案 | ||
---|---|---|---|
Hal 层适配 | 串口 | Linux | 请根据硬件连接,配置对应的串口设备名称,如`ttyUSB0`, 并实现串口初始化、串口读数据和串口写数据的回调函数。详细实现方法请参见:/samples/sample_c/platform/linux/manifold2/hal/hal_uart.c |
RTOS | 请根据MCU 的型号配置对应的串口管脚,并实现串口初始化、串口读数据和串口写数据的回调函数。 详细实现方法请参见:samples/sample_c/platform/rtos_freertos/stm32f4_discovery/hal/hal_uart.c | ||
网口 | Linux | 使用网口将第三方开发平台连接至DJI无人机后,需要实现并注册配置网络的回调函数,当系统初始化时,会自动完成负载网络参数的配置,配置完成后,可以使用网口相关的功能。 详细实现方法请参见:/samples/sample_c/platform/linux/manifold2/hal/hal_network.c | |
RTOS | 对于RTOS系统,可以通过注册配置网络参数的回调函数,获取到当前负载应配置的网络参数,根据实际需要告知其他子系统模块,完成网口相关的功能。 详细实现方法请参见:/samples/sample_c/platform/rtos_freertos/stm32f4_discovery/application/main.c | ||
I2C 设备 | Linux | 使用I2C将DJI SDK认证芯片(DJI SDK CC)连接至第三方开发平台后,需要实现并注册配置I2C的回调函数,当系统初始化时,会自动完成I2C器件的配置,配置完成后,可以和SDK认证芯片(DJI SDK CC)正常通讯。 详细实现方法请参见:/samples/sample_c/platform/linux/raspberry_pi/hal/hal_i2c.c | |
RTOS | 对于RTOS系统,使用I2C将SDK认证芯片(DJI SDK CC)连接至第三方开发平台后,需要实现并注册配置I2C的回调函数,当系统初始化时,会自动完成I2C器件的配置,配置完成后,可以和SDK认证芯片(DJI SDK CC)正常通讯。 详细实现方法请参见:/samples/sample_c/platform/rtos_freertos/stm32f4_discovery/hal/hal_i2c.c | ||
Osal 层适配 | Linux | 使用标准库 `pthread` 封装 `T_DjiOsalHandler`中的线程函数、互斥锁、信号量以及时间接口等接口。详细实现方法请参见:samples/sample_c/platform/linux/common/osal/osal.c | |
RTOS | 使用 CMSIS 封装的`thread`接口,封装 `T_DjiOsalHandler`中的线程函数、互斥锁、信号量以及时间接口等接口。 详细实现方法请参见:samples/sample_c/platform/rtos_freertos/common/osal/osal.c |
2. 注册跨平台适配接口
结构体声明
请完整地填充 T_DjiHalUartHandler
、T_DjiHalNetworkHandler
、T_DjiHalUsbBulkHandler
和 T_DjiOsalHandler
中的接口内容,确保所注册的接口能够正常使用。
若对应的机型有使用SDK认证芯片(SDK CC),需要额外填充 T_DjiHalI2cHandler
中的接口内容,确保所注册的接口能够正常使用。
- T_DjiHalUartHandler uartHandler
T_DjiHalUartHandler uartHandler = {
.UartInit = HalUart_Init,
.UartDeInit = HalUart_DeInit,
.UartWriteData = HalUart_WriteData,
.UartReadData = HalUart_ReadData,
.UartGetStatus = HalUart_GetStatus,
};
- T_DjiHalNetworkHandler networkHandler
T_DjiHalNetworkHandler networkHandler = {
.NetworkInit = HalNetWork_Init,
.NetworkDeInit = HalNetWork_DeInit,
.NetworkGetDeviceInfo = HalNetWork_GetDeviceInfo,
};
- T_DjiOsalHandler osalHandler
T_DjiOsalHandler osalHandler = {
.TaskCreate = Osal_TaskCreate,
.TaskDestroy = Osal_TaskDestroy,
.TaskSleepMs = Osal_TaskSleepMs,
.MutexCreate= Osal_MutexCreate,
.MutexDestroy = Osal_MutexDestroy,
.MutexLock = Osal_MutexLock,
.MutexUnlock = Osal_MutexUnlock,
.SemaphoreCreate = Osal_SemaphoreCreate,
.SemaphoreDestroy = Osal_SemaphoreDestroy,
.SemaphoreWait = Osal_SemaphoreWait,
.SemaphoreTimedWait = Osal_SemaphoreTimedWait,
.SemaphorePost = Osal_SemaphorePost,
.Malloc = Osal_Malloc,
.Free = Osal_Free,
.GetTimeMs = Osal_GetTimeMs,
.GetTimeUs = Osal_GetTimeUs,
};
- T_DjiHalI2cHandler i2CHandler
T_DjiHalI2cHandler i2CHandler = {
.I2cInit = HalI2c_Init,
.I2cDeInit = HalI2c_DeInit,
.I2cWriteData = HalI2c_WriteData,
.I2cReadData = HalI2c_ReadData,
};
请依次调用 DjiPlatform_RegOsalHandler()
、DjiPlatform_RegHalUartHandler()
函数注册基础Hal 层和Osal 层,若接口注册不成功,请根据返回码和日志信息排查错误问题。若使用依赖USB或者网卡的功能,还需要根据实际情况调用DjiPlatform_RegHalUsbBulkHandler
函数注册USB Bulk设备的相关方法或者调用DjiPlatform_RegHalNetworkHandler
函数注册网卡的相关方法。若对应的机型有使用SDK认证芯片(SDK CC),需要调用 DjiPlatform_RegHalI2cHandler
中的接口内容,确保所注册的接口能够正常使用。
说明: 跨平台移植模块必须在其他PSDK 功能模块前被注册,若Platform 模块注册失败或未注册,开发者将无法使用基于PSDK 开发的负载设备。
returnCode = DjiPlatform_RegOsalHandler(&osalHandler);
if (returnCode != DJI_ERROR_SYSTEM_MODULE_CODE_SUCCESS) {
printf("register osal handler error");
return DJI_ERROR_SYSTEM_MODULE_CODE_SYSTEM_ERROR;
}
returnCode = DjiPlatform_RegHalUartHandler(&uartHandler);
if (returnCode != DJI_ERROR_SYSTEM_MODULE_CODE_SUCCESS) {
printf("register hal uart handler error");
return DJI_ERROR_SYSTEM_MODULE_CODE_SYSTEM_ERROR;
}
#if (CONFIG_HARDWARE_CONNECTION == DJI_USE_UART_AND_USB_BULK_DEVICE)
returnCode = DjiPlatform_RegHalUsbBulkHandler(&usbBulkHandler);
if (returnCode != DJI_ERROR_SYSTEM_MODULE_CODE_SUCCESS) {
printf("register hal usb bulk handler error");
return DJI_ERROR_SYSTEM_MODULE_CODE_SYSTEM_ERROR;
}
#elif (CONFIG_HARDWARE_CONNECTION == DJI_USE_UART_AND_NETWORK_DEVICE)
returnCode = DjiPlatform_RegHalNetworkHandler(&networkHandler);
if (returnCode != DJI_ERROR_SYSTEM_MODULE_CODE_SUCCESS) {
printf("register hal network handler error");
return DJI_ERROR_SYSTEM_MODULE_CODE_SYSTEM_ERROR;
}
//Attention: if you want to use camera stream view function, please uncomment it.
returnCode = DjiPlatform_RegSocketHandler(&socketHandler);
if (returnCode != DJI_ERROR_SYSTEM_MODULE_CODE_SUCCESS) {
printf("register osal socket handler error");
return DJI_ERROR_SYSTEM_MODULE_CODE_SYSTEM_ERROR;
}
#elif (CONFIG_HARDWARE_CONNECTION == DJI_USE_ONLY_UART)
/*!< Attention: Only use uart hardware connection.
*/
#endif
//Optional: it is necessary to call this interface when using the encryption chip.
returnCode = DjiPlatform_RegHalI2cHandler(&i2CHandler);
if (returnCode != DJI_ERROR_SYSTEM_MODULE_CODE_SUCCESS) {
printf("register hal i2c handler error");
return DJI_ERROR_SYSTEM_MODULE_CODE_SYSTEM_ERROR;
}