PX4-6-串口设备驱动

上一篇我们讲了PX4的SPI/IIC设备驱动,我们现在讲一下PX4的串口设备。

PX4的串口设备驱动框架比SPI/IIC设备简单不少,使用了两种底层实现方式:

一种是系统自带的标准字符设备接口,一种是直接使用mcu的底层资源通过自行配置中断和DMA的方式实现的串口收发。

使用系统接口

Nuttx的串口使用的是标准的字符设备接口,通过标准接口函数open、close、read、write、ioctl等函数操作。

熟悉linux的同学对这个应该不陌生,通过看linux的驱动编程书籍对Nuttx的串口编程也同样有参考意义。

大多数搞飞控的都没有系统性的学习过系统编程,这里分享一个linux的设备驱动编程参考书,里面的知识在Nuttx中也非常适用,可以参考学习一下,对于了解Nuttx非常有参考意义。可以在公众号中回复 驱动编程 获取。

PX4的大多数串口使用的是系统接口,如GPS、数传、以及所有的串口外设等等。

PX4的串口驱动在任务调度上分两种情况,一种是使用task、一种是使用工作队列,那么都在什么情况下使用呢。

我们回顾前面分享的 PX4-4-任务调度 里面讲到的task与工作队列的区别,具体到串口驱动上。

  • 使用task

使用task的典型应用是gps驱动,gps驱动独立开启了一个线程,串口采用poll阻塞方式读取。 以下是gps读取的关键代码:

int GPS::pollOrRead(uint8_t *buf, size_t buf_length, int timeout)
{
    pollfd fds[1];
	fds[0].fd = _serial_fd;
	fds[0].events = POLLIN;

	int ret = poll(fds, sizeof(fds) / sizeof(fds[0]), math::min(max_timeout, timeout));

	if (ret > 0) {
		/* if we have new data from GPS, go handle it */
		if (fds[0].revents & POLLIN) {
            ...
        }
    }
}

这样的读取的方式的优点是在串口没有数据时任务可以自动挂起,在收到数据时有系统唤醒直接读取数据,这样的方式数据延时可以做到很小。

  • 使用工作队列

大多数的串口任务会使用工作队列的方式,比如距离传感器distance_sensor下的各种串口传感器,这些驱动的串口都采用轮训的方式读取,即非阻塞的定时获取。

它的串口读取的关键代码为:

int
XXX::collect()
{
	// Check the number of bytes available in the buffer
	int bytes_available = 0;
	::ioctl(_fd, FIONREAD, (unsigned long)&bytes_available);

	do {
		// read from the sensor (uart buffer)
		ret = ::read(_fd, &readbuf[0], readlen);

		// bytes left to parse
		bytes_available -= ret;

	} while (bytes_available > 0);
}

可以发现串口只有在周期调度函数collect执行到时尝试读取一次,没有获取到数据则直接返回,这样做的读取延时最大为一个调度周期,如果以100hz调度的话即为10ms。

为什么采用这种方式呢?PX4同时支持非常多的串口设备,而大多数的串口设备的实时性要求不高,采用工作队列非阻塞的方式,可以降低系统的内存、堆栈、调度的消耗,同时也能满足应用的需要。

值得一提的时使用工作队列的方式不能使用阻塞方式的串口读取,否则会影响队列中的其它任务的运行导致严重的故障!

自定义串口驱动

只有一种情况PX4采用的自定义的串口驱动,即板载fmu芯片与io芯片间的串口,这个串口用于两个单片机间的高速数据通信,波特率达到921600,用于传输输出控制信号,遥控输入信号。

这个串口的要求是高带宽、低延时,因此PX4选择自己实现这个串口驱动。

我们以pixhawk为例,它的串口驱动底层代码在 platforms/nuttx/src/px4/stm/stm32f4/px4io_serial/px4io_serial.cpp 和 src/drivers/px4io/px4io_serial.cpp 中

这个驱动主要实现了:

  • 使用Nuttx的底层接口(irq_attach()),配置串口中断为自定义的串口中断函数 ArchPX4IOSerial::_interrupt(int irq, void *context, void *arg)

  • 配置了发送和接收数据的dma stm32_dmasetup()

  • 大多操作直接对寄存器操作,效率非常高

这个驱动这里不做详细的分析,感兴趣的同学可以深入的去看看

PX4在使用Nuttx为RTOS的时候,大多接口使用了系统上的API,但在一些高效率的场合还是会直接面向硬件直接编写驱动,比如这里的px4io_serial以及任务调度中提到的hrt。

我的微信公众号,文章同步更新,欢迎关注。

微信公众号