在前几篇文章中,我介绍了如何在Rockchip RK3568平台上使用LED状态灯,以及如何通过设备树和内核驱动来控制它们。在本文中,将进一步介绍如何在内核驱动中添加一些功能,使得LED状态灯能够根据串口的收发数据的情况来变化,从而显示串口的活动状态。
逻辑分析
要实现这个功能,需要修改内核驱动中的两个函数:serial8250_rx_chars和serial8250_tx_chars。这两个函数分别负责处理串口的接收和发送数据的操作。需要在这两个函数中添加一些代码,来记录每次发送或接收数据的设备名,并根据设备名来控制LED状态灯的亮灭。还需要引入一个外部函数led_control_set_leds_by_str,来方便地设置LED状态灯的状态。
以kernel/drivers/tty/serial/8250/8250_port.c为例,看看如何修改这两个函数:
+++ b/kernel/drivers/tty/serial/8250/8250_port.c @@ -61,7 +61,7 @@ #endif #define BOTH_EMPTY (UART_LSR_TEMT | UART_LSR_THRE) - +extern int led_control_set_leds_by_str(const char *buf); /* * Here we define the default xmit fifo size used for each type of UART. */ @@ -1761,18 +1761,62 @@ EXPORT_SYMBOL_GPL(serial8250_read_char); */ unsigned char serial8250_rx_chars(struct uart_8250_port *up, unsigned char lsr) { - struct uart_port *port = &up->port; - int max_count = 256; - - do { - serial8250_read_char(up, lsr); - if (--max_count == 0) - break; - lsr = serial_in(up, UART_LSR); - } while (lsr & (UART_LSR_DR | UART_LSR_BI)); - - tty_flip_buffer_push(&port->state->port); - return lsr; + struct uart_port *port = &up->port; + int max_count = 256; + char led_ctrl_str[16]; + // 添加一个变量来保存当前的设备名 + const char *dev_name = up->port.name; + // 打印接收到的数据 + //printk("Received data: %s:%s\n", dev_name, up->port.state->xmit.buf); + printk("Received data: %s\n", dev_name); + // 根据设备名来执行不同的操作 + switch (dev_name[4]) { // 取设备名的第五个字符,即 ttySx 中的 x + case '3': + snprintf(led_ctrl_str, sizeof(led_ctrl_str), "%d %d %d", 5, 1, 0); + led_control_set_leds_by_str(led_ctrl_str); + mdelay(10);//msleep(100); // 延时 1 秒 + snprintf(led_ctrl_str, sizeof(led_ctrl_str), "%d %d %d", 5, 1, 1); + led_control_set_leds_by_str(led_ctrl_str); + break; + case '4': + snprintf(led_ctrl_str, sizeof(led_ctrl_str), "%d %d %d", 6, 1, 0); + led_control_set_leds_by_str(led_ctrl_str); + mdelay(10);//msleep(100); // 延时 1 秒 + snprintf(led_ctrl_str, sizeof(led_ctrl_str), "%d %d %d", 6, 1, 1); + led_control_set_leds_by_str(led_ctrl_str); + break; + case '5': + snprintf(led_ctrl_str, sizeof(led_ctrl_str), "%d %d %d", 7, 1, 0); + led_control_set_leds_by_str(led_ctrl_str); + mdelay(10);//msleep(100); // 延时 1 秒 + snprintf(led_ctrl_str, sizeof(led_ctrl_str), "%d %d %d", 7, 1, 1); + led_control_set_leds_by_str(led_ctrl_str); + break; + case '7': + snprintf(led_ctrl_str, sizeof(led_ctrl_str), "%d %d %d", 8, 1, 0); + led_control_set_leds_by_str(led_ctrl_str); + mdelay(10);//msleep(100); // 延时 1 秒 + snprintf(led_ctrl_str, sizeof(led_ctrl_str), "%d %d %d", 8, 1, 1); + led_control_set_leds_by_str(led_ctrl_str); + break; + default: + // 其他设备名不做任何操作 + break; + } + + do { + serial8250_read_char(up, lsr); + if (--max_count == 0) + break; + lsr = serial_in(up, UART_LSR); + } while (lsr & (UART_LSR_DR | UART_LSR_BI)); + + tty_flip_buffer_push(&port->state->port); + + + + + return lsr; } EXPORT_SYMBOL_GPL(serial8250_rx_chars); @@ -1781,6 +1825,45 @@ void serial8250_tx_chars(struct uart_8250_port *up) struct uart_port *port = &up->port; struct circ_buf *xmit = &port->state->xmit; int count; + char led_ctrl_str[16]; + // 添加一个变量来保存当前的设备名 + const char *dev_name = up->port.name; + // 打印接发送的数据 + printk("Send data: %s\n", dev_name); + // 根据设备名来执行不同的操作 + switch (dev_name[4]) { // 取设备名的第五个字符,即 ttySx 中的 x + case '3': + snprintf(led_ctrl_str, sizeof(led_ctrl_str), "%d %d %d", 5, 0, 0); + led_control_set_leds_by_str(led_ctrl_str); + mdelay(10);//msleep(100); // 延时 1 秒 + snprintf(led_ctrl_str, sizeof(led_ctrl_str), "%d %d %d", 5, 0, 1); + led_control_set_leds_by_str(led_ctrl_str); + break; + case '4': + snprintf(led_ctrl_str, sizeof(led_ctrl_str), "%d %d %d", 6, 0, 0); + led_control_set_leds_by_str(led_ctrl_str); + mdelay(10);//msleep(100); // 延时 1 秒 + snprintf(led_ctrl_str, sizeof(led_ctrl_str), "%d %d %d", 6, 0, 1); + led_control_set_leds_by_str(led_ctrl_str); + break; + case '5': + snprintf(led_ctrl_str, sizeof(led_ctrl_str), "%d %d %d", 7, 0, 0); + led_control_set_leds_by_str(led_ctrl_str); + mdelay(10);//msleep(100); // 延时 1 秒 + snprintf(led_ctrl_str, sizeof(led_ctrl_str), "%d %d %d", 7, 0, 1); + led_control_set_leds_by_str(led_ctrl_str); + break; + case '7': + snprintf(led_ctrl_str, sizeof(led_ctrl_str), "%d %d %d", 8, 0, 0); + led_control_set_leds_by_str(led_ctrl_str); + mdelay(10);//msleep(100); // 延时 1 秒 + snprintf(led_ctrl_str, sizeof(led_ctrl_str), "%d %d %d", 8, 0, 1); + led_control_set_leds_by_str(led_ctrl_str); + break; + default: + // 其他设备名不做任何操作 + break; + } if (port->x_char) { serial_out(up, UART_TX, port->x_char);
这里需要注意以下几点:
- 在文件开头引入了一个外部函数led_control_set_leds_by_str,用来设置LED状态灯的状态,该函数的定义在kernel/drivers/leds/led_control.c中。
- (重点重点)在serial8250_rx_chars函数中,在接收数据之前,定义一个字符数组led_ctrl_str,用来存放LED状态灯的控制字符串。
- 获取当前的设备名,并打印一条日志信息。
- 根据设备名的第五个字符(即ttySx中的x)来执行不同的操作。如果是'3',表示是ttyS3串口,那么就设置第5组LED的绿色为关闭(即亮起),延时10毫秒,再设置为打开(即灭掉)。如果是'4',表示是ttyS4串口,那么就设置第6组LED的绿色为关闭(即亮起),延时10毫秒,再设置为打开(即灭掉)。如果是'5',表示是ttyS5串口,那么就设置第7组LED的绿色为关闭(即亮起),延时10毫秒,再设置为打开(即灭掉)。如果是'7',表示是ttyS7串口,那么就设置第8组LED的绿色为关闭(即亮起),延时10毫秒,再设置为打开(即灭掉)。如果是其他字符,那么就不做任何操作。这样就实现了每次接收数据时,对应的LED会闪烁一下的效果。
- (重点重点)在serial8250_tx_chars函数中,在发送数据之前,定义一个字符数组led_ctrl_str,用来存放LED状态灯的控制字符串。
- 获取当前的设备名,并打印一条日志信息。
- 根据设备名的第五个字符(即ttySx中的x)来执行不同的操作。如果是'3',表示是ttyS3串口,那么就设置第5组LED的红色为关闭(即亮起),延时10毫秒,再设置为打开(即灭掉)。如果是'4',表示是ttyS4串口,那么就设置第6组LED的红色为关闭(即亮起),延时10毫秒,再设置为打开(即灭掉)。如果是'5',表示是ttyS5串口,那么就设置第7组LED的红色为关闭(即亮起),延时10毫秒,再设置为打开(即灭掉)。如果是'7',表示是ttyS7串口,那么就设置第8组LED的红色为关闭(即亮起),延时10毫秒,再设置为打开(即灭掉)。如果是其他字符,那么就不做任何操作。这样就实现了每次发送数据时,对应的LED会闪烁一下的效果。(前面一样的)
测试效果
要测试这个功能,需要在RK3568平台上编译并加载修改后的内核驱动。然后可以使用minicom或其他串口工具来发送或接收数据,观察LED状态灯的变化。例如,可以在RK3568平台上执行以下命令:
minicom -D /dev/ttyS3 #linux推荐这么搞 # 或者 echo / cat # 都ok,android比较方便怎么测都可以。我用我自己写的接口测试
这样就会打开ttyS3串口,并与另一台设备进行通信。可以看到,每次发送或接收数据时,对应的LED状态灯会闪烁一下,表示串口的活动状态,同时查看内核日志可以看到收发数据的。
总结
本文介绍了如何在内核驱动中添加一些功能,使得LED状态灯能够根据串口的收发数据的情况来变化,从而显示串口的活动状态。修改了内核驱动中的serial8250_rx_chars和serial8250_tx_chars函数,添加了一些代码,来记录每次发送或接收数据的设备名,并根据设备名来控制LED状态灯的亮灭。还引入了一个外部函数led_control_set_leds_by_str,来方便地设置LED状态灯的状态。
希望本文对你有所帮助。如果你有任何问题或建议,请在评论区留言。谢谢!