一、理解串口驱动
我们所谓串口驱动,先说串口,其实就是异步的半/全双工串行通信,TX RX 还有地 这是本质,然后区分不同串口的其实是根据电平
比如:
- UART TTL电平(逻辑1: 2.4V–5V 逻辑0: 0V-- 0.5V)
- RS232 (逻辑1:-15V – -3V 逻辑0:+3V-- +15V)
- RS485(逻辑1:+2V-- +6V 逻辑0:-6V-- -2V)这里的电平指AB 两线间的电压差。
然后不同种类的有不同优点与缺点,比如RS485抗干扰能力强,距离长。
二、单片机本质驱动UART
我们要在单片机上驱动串口,其实本质是驱动uart,如果要实现其它电平那么会在单片机外围再加上对应电平转换电路。(因为单片机是TTL电平的。)
三、linux内核下的UART驱动
说明:
linux的uart驱动一般都被soc厂商写好了,且只有一个驱动程序,但是可以匹配多个串口设备,这就需要我们更改设备树,添加串口节点了(这就是我们驱动工程师要做的)相比于I2C/SPI驱动,UART驱动没有自己的总线,因为驱动程序就一个而且还是soc厂商写好的,但是
UART驱动和设备是挂在Platform下的。UART设备一旦挂上就会执行驱动中probe
驱动实现:
uart设备驱动本质上是一个基于platform的字符设备驱动,它的major与min_start和次设备数量都是由各个soc确定下来的。
1.设备树讲解
不是讲驱动吗?为啥要讲设备树呢?
因为我们要清楚一点就是设备树头文件imx6ull.dtsi与我们自己要修改的设备树文件如:imx6ull_14x14_dts,
一般SOC厂商写好的程序匹配的都是imx6ull.dtsi中的compitable,然后将节点匿名如uart1 = &uart1,那么在我们自己的dts中就直接可以用&uart1了,如果在&uart1节点下的节点不写compitable的话那么默认就是父节点&uart1的compitable了。
2.回到uart驱动
soc在驱动与设备树匹配这块是这样实现的,所有uart1/2/3/4/5/6的父节点都在imx6ull.dtsi中实现,然后compitable都一样。
那么也就是会有的效果就是,如果在自己的dts下不论是那个&uart如果添加了没有自己compible的节点都会匹配到soc厂商写的那个驱动,也是唯一的一个uart驱动。
3.先上结构体
struct uart_driver {
struct module *owner;
const char *driver_name;
const char *dev_name;
int major;
int minor;
int nr;
struct console *cons;
struct uart_state *state;
struct tty_driver *tty_driver;
};
struct uart_port {
unsigned int type; /* port type */
const struct uart_ops *ops;//里面是底层的与uart交互的函数
.....
void *private_data; /* generic platform data pointer */
};
4.编写步骤
1.在init中
static int __init imx_serial_init(void)
{
int ret = uart_register_driver(&imx_reg);//在这里面注册了字符设备,但是还没有与uart_ops绑定
if (ret)
return ret;
ret = platform_driver_register(&serial_imx_driver);//当有设备时,调用serial_imx_driver的probe函数,调用uart_add_one_port函数将cdev与uart_ops绑定.
if (ret != 0)
uart_unregister_driver(&imx_reg);
return ret;
}
2.probe函数
static int serial_imx_probe(struct platform_device *pdev)
{
struct imx_port *sport;
void __iomem *base;
int ret = 0;
struct resource *res;
int txirq, rxirq, rtsirq;
....
....
sport->port.dev = &pdev->dev;
sport->port.mapbase = res->start;
sport->port.membase = base;
sport->port.type = PORT_IMX,
sport->port.iotype = UPIO_MEM;
sport->port.irq = rxirq;
sport->port.fifosize = 32;
sport->port.ops = &imx_pops;//实现ops函数
return uart_add_one_port(&imx_reg, &sport->port);//添加到cdev下
}
我们要做的事,添加设备节点:
注意:
由于串口驱动只能一对一,所以设备有多少个串口就只能设多少个节点,比如imx的imx6ull.dtsi下只有&uart1-8,那么我们只能在自己的dts文件下的每个&uart下最多创建一个节点。不能一个uart有多个子节点。
步骤:
1.pinctrl
pinctrl_uart3: uart3grp {
fsl,pins = <
MX6UL_PAD_UART3_TX_DATA__UART3_DCE_TX 0X1b0b1
MX6UL_PAD_UART3_RX_DATA__UART3_DCE_RX 0X1b0b1
>;
};
2.uart
&uart3 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart3>;
status = "okay";
};
应用:
在更改完设备树并重新启动内核后,就会在/dev下多出对应节点,其名称由soc写的驱动中的dev_name决定。
我们可以在开发板上minicom进行与电脑的串口通信,具体如何用test.c打开设备与外界通信还不知道。