开篇分割线:我们再写驱动程序的目的是能够注册到系统的框架之中,那么就在创建设备之初你的设备结构体(C++中叫类)必须从系统提供的结构中进行派生出新的结构体,根据自己的设备类型定义私有数据域,
MCU一般会有多个串口,所以串口驱动也需要支持多个串口的配置,设备结构体更应该以数组的形式出现,config信息就代表了真实的硬件有多少个固定的串口,并通过数组一次性默认配置好,至于是不是要启用,可以通过预定义宏的方式进行开关:
有了uart设备对象以后,我们还有需要能够操作对象的方法(C++中的类就集成了这一部分),C语言中可以通过函数指针的方式来实现操作方法的结构体存储:
/**
* uart operators
*/
struct rt_uart_ops
{
rt_err_t (*configure)(struct rt_serial_device *serial, struct serial_configure *cfg);
rt_err_t (*control)(struct rt_serial_device *serial, int cmd, void *arg);
int (*putc)(struct rt_serial_device *serial, char c);
int (*getc)(struct rt_serial_device *serial);
rt_size_t (*dma_transmit)(struct rt_serial_device *serial, rt_uint8_t *buf, rt_size_t size, int direction);
};
上面定义的是函数原型的指针:后续需要我们根据stm32实现具体的方法来赋值给对应的原形,这里先说下每个函数的作用该实现怎样的功能,后续你才好去写这部分功能。
configure方法:用于配置串口的波特率、数据位、校验位、停止位等参数。
control方法:用于控制串口。
putc方法:用于串口向外发送字符数据。
getc方法:用于串口获取接收外部的字符数据。
transmit方法:用于数据发送侧重于多个字节的数据发送。
你是否发现了一个很奇怪的参数,就是这些操作方法的第一个输入参数是系统提供的serial的结构体,按理说这里的ops需要进行最底层的硬件操作及数据收发,那为什么会是serial,而不是uart呢,其实这源于这些操作函数的调用方,假如应用和驱动不是分离的,那么应用可以很简单的知道底层的驱动是哪个uart,但实际上应用和驱动是隔离开的,应用需要通过一个名称来获取串口的句柄,而串口的句柄只能来自于系统的定义,也就是serial对象,但是我们实际上需要的是uart,那么这里提前引入一个转换,由成员对象找到派生对象的操作,不得不说C语言的强大,详细的分析会放到configure函数的实现上来讲:
/**
* rt_container_of - return the member address of ptr, if the type of ptr is the
* struct type.
*/
#define rt_container_of(ptr, type, member) \
((type *)((char *)(ptr) - (unsigned long)(&((type *)0)->member)))