armsom-W3单板计算机之小白学习笔记(二)

前言

本篇内容主要是学习关于RK3588开发板(W3)UART的使用和调试方法,包括UART作为普通串口和控制台两种不同使用场景。


一.UART的功能特点

Rockchip UART (Universal Asynchronous Receiver/Transmitter) 基于16550A串口标准,完整模块支持以下功能

1.支持5、6、7、8 bits数据位

其中最常用的是8bits数据位,我们探讨一下

下面这个例子中,模拟了一个UART发送函数uart_send,然后发送一个字符串"Hello, World!"。每个字符使用8位数据位进行传输

#include <stdio.h>
#include <stdint.h>

// 模拟UART发送函数
void uart_send(uint8_t data) {
    // 实际的UART发送代码
    printf("Sending data: 0x%X\n", data);
}

int main() {
    // 8位数据位示例
    char message[] = "Hello, World!";
    for (int i = 0; i < sizeof(message) - 1; i++) {
        uart_send(message[i]);
    }
    
    return 0;
}

嵌入式系统间的数据通信

假设有两个嵌入式系统,通过UART进行通信,一个系统发送数据,另一个系统接收并处理数据

发送端:

#include <stdio.h>
#include <stdint.h>

// 模拟UART发送函数
void uart_send(uint8_t data) {
    // 实际的UART发送代码
    printf("Sending data: 0x%X\n", data);
}

int main() {
    uint8_t data_to_send = 0x7F; // 假设要发送的数据

    // 发送数据
    uart_send(data_to_send);

    return 0;
}

接收端:

#include <stdio.h>
#include <stdint.h>

// 模拟UART接收函数
uint8_t uart_receive() {
    // 实际的UART接收代码
    uint8_t received_data = 0x7F; // 假设接收到的数据
    return received_data;
}

int main() {
    uint8_t received_data = uart_receive();

    // 处理接收到的数据
    printf("Received data: 0x%X\n", received_data);

    return 0;
}

2.支持1、1.5、2 bits停止位

停止位用于数据同步和数据分隔,同样的 1bits停止位是比较常见的,常用于计算机与外设之间的通信,如GPS模块、传感器等。1bits停止位是最常见的配置,表示在每个字符之后插入一个停止位。这种配置传输效率高,但对接收方的时序要求较高

配置1位停止位:

#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>

int main() {
    int uart_fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY);

    struct termios uart_config;
    tcgetattr(uart_fd, &uart_config);

    // 配置1位停止位
    uart_config.c_cflag &= ~CSTOPB; // 清除CSTOPB位(1位停止位)

    tcsetattr(uart_fd, TCSANOW, &uart_config);

    // 发送和接收数据代码...

    close(uart_fd);
    return 0;
}


3.支持奇校验和偶校验,不支持mark校验和space校验

奇校验和偶校验  

  • 奇校验(Odd Parity):校验位设置为1或0,使得数据帧中所有1的总数为奇数。
  • 偶校验(Even Parity):校验位设置为1或0,使得数据帧中所有1的总数为偶数。

示例  

假设要发送的数据帧是1011:

  • 使用奇校验:数据帧中有3个1(奇数),因此校验位设为0,保持总数为奇数。最终发送的帧为10110。
  • 使用偶校验:数据帧中有3个1(奇数),因此校验位设为1,使总数变为偶数。最终发送的帧为10111。

4.支持接收FIFO和发送FIFO,一般为32字节或者64字节

在串行通信中,FIFO(First In, First Out)缓冲区用于临时存储接收和发送的数据。接收FIFO和发送FIFO分别用于存储从串行接口接收到的数据和准备通过串行接口发送的数据。这些缓冲区的容量通常为32字节或64字节。

接收FIFO和发送FIFO缓冲区用于提高串行通信的效率和可靠性。它们可以缓冲数据,防止数据丢失,并确保数据按顺序传输

详细解释

  • FIFO(First In, First Out):FIFO缓冲区是一种先进先出数据结构,确保数据按顺序处理。第一个进入缓冲区的数据是第一个被处理和移除的数据。
  • 接收FIFO:存储从串行接口接收到的数据,等待处理。如果接收的数据量超过了处理速度,接收FIFO可以防止数据丢失。
  • 发送FIFO:存储准备通过串行接口发送的数据。如果发送的数据量超过了传输速度,发送FIFO可以确保数据按顺序发送。

 下面这段代码是一个基本的UART(通用异步收发传输器)通信示例。它展示了如何在Linux环境中通过串口设备进行数据发送和接收

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>

int main() {
    // 打开UART设备文件
    int uart_fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY);
    if (uart_fd < 0) {
        perror("Open UART failed");
        return -1;
    }

    // 获取当前UART配置
    struct termios uart_config;
    tcgetattr(uart_fd, &uart_config);

    // 设置波特率、数据位、停止位和校验
    cfsetispeed(&uart_config, B115200); // 设置输入波特率
    cfsetospeed(&uart_config, B115200); // 设置输出波特率
    uart_config.c_cflag &= ~CSIZE;
    uart_config.c_cflag |= CS8;         // 设置数据位为8位
    uart_config.c_cflag &= ~PARENB;     // 禁用校验
    uart_config.c_cflag &= ~CSTOPB;     // 设置停止位为1位

    // 应用新的UART配置
    tcsetattr(uart_fd, TCSANOW, &uart_config);

    // 要发送的数据
    char send_data[] = "Hello, UART!";
    char recv_data[32]; // 接收FIFO为32字节

    // 发送数据
    write(uart_fd, send_data, sizeof(send_data));

    // 接收数据
    int len = read(uart_fd, recv_data, sizeof(recv_data));
    if (len > 0) {
        recv_data[len] = '\0'; // 确保字符串以NULL结尾
        printf("Received: %s\n", recv_data);
    }

    // 关闭UART设备文件
    close(uart_fd);
    return 0;
}


5.支持最高4M波特率,实际支持波特率需要芯片时钟分频策略配合

该设备的串行通信接口(UART)最高支持4Mbps的波特率(每秒传输4百万比特)。然而,实际可用的波特率取决于芯片的时钟分频策略。也就是说,要达到特定的波特率,需要将芯片的时钟频率适当分频,以匹配所需的通信速度。

详细解释:

  • 最高4M波特率

    • 这是指设备的UART接口在理想情况下可以达到的最大传输速度。
    • 波特率(Baud rate)是指每秒钟传输的符号(或比特)数量。
  • 实际支持波特率

    • 实际上,设备能够稳定工作的波特率可能会低于这个最大值,具体取决于其他硬件和系统配置。
  • 芯片时钟分频策略

    • 通常,UART接口的波特率是通过将芯片的时钟频率进行分频得到的。例如,如果芯片的时钟频率是48MHz,要实现4Mbps的波特率,需要将时钟频率除以12。
    • 不同的芯片有不同的时钟分频机制,需要在软件或硬件上进行相应的配置,以实现所需的波特率。

6.支持中断传输模式和DMA传输模式。 支持硬件自动流控,RTS+CTS

  • 支持中断传输模式:UART接口可以配置为在数据传输时触发中断,这样CPU可以在接收到数据或发送完成时进行处理,而不需要不断地轮询UART状态。
  • 支持DMA传输模式:UART接口可以利用DMA(直接内存访问)控制器直接将数据从内存传输到UART接口或从UART接口传输到内存,这减少了CPU的负担,提高了数据传输效率。
  • 支持硬件自动流控,RTS+CTS:UART接口支持硬件流控制,通过RTS(请求发送)和CTS(清除发送)信号来控制数据流,防止数据溢出或丢失。

 二.驱动代码&文件

 在Linux的kernel(内核) 中,使用8250串口通用驱动(8250芯片为UART专用),以下为主要驱动文件

1.驱动文件

  •  8250_core.c

    • 包含主要的驱动函数,包括初始化、读取、写入和中断处理等。
  • 8250_port.c

    • 处理串口端口的配置和操作,例如设置波特率、数据位、停止位和校验位等。
  • 8250_pci.c

    • 针对PCI接口的8250串口设备进行专门处理,负责设备的探测、初始化和配置。
  • 8250_pnp.c

    • 支持即插即用串口设备的发现和配置,利用系统提供的即插即用机制。
  • 8250_of.c

    • 处理设备树中定义的8250串口设备,主要用于嵌入式系统中。
  • 8250_acpi.c

    • 通过ACPI机制发现和配置8250串口设备,主要用于现代计算机系统。
  • 8250_dma.c

    • 提供DMA支持,以提高串口数据传输的效率,减少CPU的参与。
  • 8250_early.c

    • 在系统启动早期阶段初始化串口控制台,便于调试和日志记录。
  • serial_8250.h

    • 包含驱动所需的数据结构和函数原型,供其他驱动文件引用。

2.关键函数

  • serial8250_init:初始化 8250 串口驱动。
  • serial8250_register_port:注册一个 8250 串口端口。
  • serial8250_startup:启动 8250 串口端口。
  • serial8250_shutdown:关闭 8250 串口端口。
  • serial8250_interrupt:处理 8250 串口中断。
  • serial8250_console_write:处理控制台输出。

 SDK中提供的UART默认配置已经使用了8250驱动我们就不需要修改

 三.设备树配置

1.设备树文件路径

在Rockchip 平台上,设备树文件通常位于 kernel/arch/arm64/boot/dts/rockchip 目录下。具体文件路径取决于所使用的开发板。每个开发板都有一个对应的 .dts 文件,该文件描述了开发板的硬件配置。

2.设备树文件结构

设备树文件分为两种:.dts 文件和 .dtsi 文件。

  • .dts 文件(Device Tree Source):这是设备树的主文件,通常与特定的开发板相关。例如,rk3588-armsom-w3.dts
  • .dtsi 文件(Device Tree Include):这是设备树的包含文件,用于定义一系列通用硬件配置,通常与特定的芯片相关。例如,rk3588s.dtsi

3.修改设备树

当需要修改设备树时,首先确定目标开发板的 .dts 文件,然后根据需要在 .dts.dtsi 文件中进行修改。通常,具体的硬件配置会在 .dtsi 文件中定义,特定开发板的配置会在 .dts 文件中进行。

定位设备树文件—>打开并编辑文件—>查找并修改配置—>保存并退出编辑器—>编译设备树—>验证和提交修改  

 3.1作为普通串口 

 假如使用w3开发板上40PIN上的uart7(11和15引脚),需要在dts可以使用如下配置打开

&uart7 {
    // 启用 uart7 接口
    status = "okay";
    
    // 设置引脚控制名称
    pinctrl-names = "default";
    
    // 引脚控制配置,对应 uart7 的传输引脚
    pinctrl-0 = <&uart7m1_xfer>;
};

 3.2作为调试串口

Rockchip UART作为控制台,要使用fiq_debugger流程。在dts中fiq_debugger节点配置如下。由于fiq_debugger和普通串口互斥,在使能fiq_debugger节点后必须禁用对应的普通串口uart节点。

/ {
    chosen {
        /*
         * 设置内核启动参数。
         * "earlycon" 参数指定早期控制台使用的UART设备,这里为uart8250,使用MMIO32地址0xfe660000。
         * "console" 参数指定普通控制台使用的UART设备,这里为ttyFIQ0。
         */
        bootargs = "earlycon=uart8250,mmio32,0xfe660000 console=ttyFIQ0";
    };

    fiq-debugger {
        compatible = "rockchip,fiq-debugger";  /* 兼容字符串,标识设备为Rockchip的FIQ调试器 */
        rockchip,serial-id = <2>;              /* 使用的UART设备ID,这里是UART2 */
        rockchip,wake-irq = <0>;               /* 唤醒中断号,这里设置为0 */
        rockchip,irq-mode-enable = <1>;        /* 启用IRQ模式(而不是FIQ模式) */
        rockchip,baudrate = <1500000>;         /* 波特率设置,仅支持115200和1500000 */
        interrupts = <GIC_SPI 252 IRQ_TYPE_LEVEL_LOW>; /* 中断配置,GIC_SPI中断号为252,低电平触发 */
        pinctrl-names = "default";             /* 引脚控制名称 */
        pinctrl-0 = <&uart2m0_xfer>;           /* 引脚控制设置,使用uart2m0_xfer配置 */
        status = "okay";                       /* 启用该设备 */
    };
};

&uart2 {
    status = "disabled";  /* 禁用普通UART2节点,因为它与FIQ调试器互斥 */
};

rockchip,serial-id:使用的UART编号。修改serial-id到不同UART,fiq_debugger设备也会注册成
ttyFIQ0设备。

rockchip,irq-mode-enable:配置为1使用irq中断,配置为0使用fiq中断。
interrupts:配置的辅助中断,保持默认即可。
pinctrl-0:使用的串口引脚
rockchip,baudrate:波特率配置

 普通串口设备将会根据dts中的aliase来对串口进行编号,对应注册成ttySx设备。注册的节点为/dev/ttyS4,命名规则是通过dts中的aliases来的。

aliases {
    /* 定义UART别名,使设备树中可以使用别名引用UART设备,便于管理和维护 */
    serial0 = &uart0;  /* 将别名 serial0 关联到 UART0 */
    serial1 = &uart1;  /* 将别名 serial1 关联到 UART1 */
    serial2 = &uart2;  /* 将别名 serial2 关联到 UART2 */
    serial3 = &uart3;  /* 将别名 serial3 关联到 UART3 */
}

3.3设备注册


普通串口设备将会根据dts中的aliase来对串口进行编号,对应注册成ttySx设备。注册的节点为/dev/ttyS4,命名规则是通过dts中的aliases来的。

aliases {
serial0 = &uart0;
serial1 = &uart1;
serial2 = &uart2;
serial3 = &uart3;
}



对应uart0注册为ttyS0,uart1注册为ttyS1,如果需要把uart3注册成ttyS1,可以进行以下修改

serial1 = &uart3;  
serial3 = &uart1;
3.3.1什么是设备注册

设备注册是将硬件设备映射到操作系统中的一个过程,使得操作系统能够识别、访问和管理这些设备。具体而言,它包括将硬件资源分配给特定的设备驱动程序,并通过创建设备文件来提供访问接口。

3.3.2为什么需要设备注册

设备注册的主要目的是:

  1. 统一管理:操作系统可以统一管理和调度设备资源。
  2. 简化访问:用户和应用程序可以通过设备文件简化地访问硬件设备,而无需直接操作硬件。
  3. 驱动匹配:确保硬件设备能够与合适的设备驱动程序绑定,以正确执行设备操作。
  4. 方便调试和开发:开发人员可以通过设备文件轻松调试和开发硬件驱动程序。
3.3.3具体的设备注册过程

在嵌入式系统中,设备树(Device Tree)描述了系统中的硬件配置,包括各个外设的地址、属性等。通过设备树,内核可以识别并注册这些设备。

解释示例中的设备注册 Aliases节点

aliases { serial0 = &uart0;
          serial1 = &uart1; 
          serial2 = &uart2; 
          serial3 = &uart3; 
}

解释

  • aliases 节点提供了一种方便的方法,将设备树中的节点映射为更容易记忆和访问的别名。
  • 在这个示例中,将 uart0uart1uart2uart3 分别映射为 serial0serial1serial2serial3
  • 这样,当系统启动时,内核会根据这些别名来识别和注册相应的串口设备。
3.3.4设备文件命名规则

根据 aliases 节点,内核会将串口设备注册为 /dev/ttySx 文件。

  • serial0 对应 uart0,注册为 /dev/ttyS0
  • serial1 对应 uart1,注册为 /dev/ttyS1
  • serial2 对应 uart2,注册为 /dev/ttyS2
  • serial3 对应 uart3,注册为 /dev/ttyS3
修改设备注册映射

如果需要更改串口设备的注册顺序,例如将 uart3 注册为 ttyS1,可以通过修改 aliases 节点实现:

aliases { serial1 = &uart3; // 将 uart3 注册为 ttyS1 
          serial3 = &uart1; // 将 uart1 注册为 ttyS3 
}

解释

  • 原本 serial1 对应 uart1,现在改为 uart3,所以 uart3 将被注册为 /dev/ttyS1
  • 原本 serial3 对应 uart3,现在改为 uart1,所以 uart1 将被注册为 /dev/ttyS3

通过这样的映射,可以灵活调整设备注册顺序,满足特定需求。例如,某些应用程序可能对特定的串口设备有固定的需求,通过调整映射,可以方便地适配这些需求。

3.4控制台打印相关


Rockchip UART打印通常包括DDR阶段、Miniloader阶段、TF-A (Trusted Firmware-A)阶段、OP-TEE阶段、Uboot阶段和Kernel阶段,我们平时主要关注的是uboot阶段和kernel阶段的打印,在这两个阶段我们可以尝试关闭所有打印或切换所有打印到其他UART,RK平台默认的调试串口是uart2_m0这一组引脚,假如现在我将打印换成其他串口,可以尝试以下做法。

3.4.1DDR Loader修改方法


DDR Loader中关闭或切换打印,需要修改DDR Loader中的UART打印配置,修改文件rkbin/tools/ddrbin_param.txt中的以下参数:

uart id= # UART控制器id,配置为0xf为关闭打印
uart iomux= # 复用的IOMUX引脚 uart
baudrate= # 115200 or 1500000

修改完成后,使用命令重新生成ddr.bin固件。 

./ddrbin_tool ddrbin_param.txt rk3588_ddr_lp4_2112MHz_lp5_2736MHz_v1.09.bin

4.测试

代码

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <termio.h>
#include <time.h>
#include <pthread.h>

int read_data(int fd, void *buf, int len);
int write_data(int fd, void *buf, int len);
int setup_port(int fd, int baud, int databits, int parity, int stopbits);
void print_usage(char *program_name);

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t data_ready = PTHREAD_COND_INITIALIZER;
int data_available = 0;

void *read_thread(void *arg) {
    int fd = *(int *)arg;
    char buffer[1024]; // 存储读取的数据

    while (1) {
        int bytes_read = read_data(fd, buffer, sizeof(buffer));
        if (bytes_read > 0) {
            printf("Read Thread: Read %d bytes: %s\n", bytes_read, buffer);
        } else {
            // 处理读取错误或设备关闭的情况
            break;
        }
    }
    
    pthread_exit(NULL);
}

void *write_thread(void *arg) {
    int fd = *(int *)arg;
    char input[1024]; // 存储用户输入的数据

    while (1) {
        printf("Enter data to write (or 'q' to quit): ");
        fgets(input, sizeof(input), stdin);

        if (strcmp(input, "q\n") == 0 || strcmp(input, "Q\n") == 0) {
            // 用户输入 'q' 或 'Q',退出循环
            break;
        }

        int len = strlen(input);
        int bytes_written = write_data(fd, input, len);
        if (bytes_written > 0) {
            printf("Write Thread: Wrote %d bytes: %s\n", bytes_written, input);
        }
    }
    
    pthread_exit(NULL);
}

int main(int argc, char *argv[]) //./a.out /dev/ttyS4 115200 8 0 1
{
    int fd;
    int baud;
    int len;
    int count;
    int i;
    int databits;
    int stopbits;
    int parity;

    if (argc != 6) {
        print_usage(argv[0]);
        return 1;
    }
 
    baud = atoi(argv[2]);
    if ((baud < 0) || (baud > 921600)) {
        fprintf(stderr, "Invalid baudrate!\n");
        return 1;
    }
 
    databits = atoi(argv[3]);
    if ((databits < 5) || (databits > 8)) {
        fprintf(stderr, "Invalid databits!\n");
        return 1;
    }
 
    parity = atoi(argv[4]);
    if ((parity < 0) || (parity > 2)) {
        fprintf(stderr, "Invalid parity!\n");
        return 1;
    }
 
    stopbits = atoi(argv[5]);
    if ((stopbits < 1) || (stopbits > 2)) {
        fprintf(stderr, "Invalid stopbits!\n");
        return 1;
    }
 
 
    fd = open(argv[1], O_RDWR, 0);
    if (fd < 0) {
        fprintf(stderr, "open <%s> error %s\n", argv[1], strerror(errno));
        return 1;
    }
 
    if (setup_port(fd, baud, databits, parity, stopbits)) {
        fprintf(stderr, "setup_port error %s\n", strerror(errno));
        close(fd);
        return 1;
    }
    pthread_t read_tid, write_tid;
    int ret;

    // 创建读取线程
    ret = pthread_create(&read_tid, NULL, read_thread, &fd);
    if (ret != 0) {
        fprintf(stderr, "Failed to create read thread\n");
        return 1;
    }

    // 创建写入线程
    ret = pthread_create(&write_tid, NULL, write_thread, &fd);
    if (ret != 0) {
        fprintf(stderr, "Failed to create write thread\n");
        return 1;
    }

    // 等待读取线程和写入线程结束
    pthread_join(read_tid, NULL);
    pthread_join(write_tid, NULL);
    
    close(fd);
 
    return 0;
}

static int baudflag_arr[] = {
    B921600, B460800, B230400, B115200, B57600, B38400,
    B19200,  B9600,   B4800,   B2400,   B1800,  B1200,
    B600,    B300,    B150,    B110,    B75,    B50
};
static int speed_arr[] = {
    921600,  460800,  230400,  115200,  57600,  38400,
    19200,   9600,    4800,    2400,    1800,   1200,
    600,     300,     150,     110,     75,     50
};

int speed_to_flag(int speed)
{
    int i;
 
    for (i = 0;  i < sizeof(speed_arr)/sizeof(int);  i++) {
        if (speed == speed_arr[i]) {
            return baudflag_arr[i];
        }
    }
 
    fprintf(stderr, "Unsupported baudrate, use 9600 instead!\n");
    return B9600;
}

static struct termio oterm_attr;

int setup_port(int fd, int baud, int databits, int parity, int stopbits)
{
    struct termio term_attr;
 
    
    if (ioctl(fd, TCGETA, &term_attr) < 0) {
        return -1;
    }
 
    
    memcpy(&oterm_attr, &term_attr, sizeof(struct termio));
 
    term_attr.c_iflag &= ~(INLCR | IGNCR | ICRNL | ISTRIP);
    term_attr.c_oflag &= ~(OPOST | ONLCR | OCRNL);
    term_attr.c_lflag &= ~(ISIG | ECHO | ICANON | NOFLSH);
    term_attr.c_cflag &= ~CBAUD;
    term_attr.c_cflag |= CREAD | speed_to_flag(baud);
 
    
    term_attr.c_cflag &= ~(CSIZE);
    switch (databits) {
        case 5:
            term_attr.c_cflag |= CS5;
            break;
 
        case 6:
            term_attr.c_cflag |= CS6;
            break;
 
        case 7:
            term_attr.c_cflag |= CS7;
            break;
 
        case 8:
        default:
            term_attr.c_cflag |= CS8;
            break;
    }
 
    
    switch (parity) {
        case 1:  
            term_attr.c_cflag |= (PARENB | PARODD);
            break;
 
        case 2:  
            term_attr.c_cflag |= PARENB;
            term_attr.c_cflag &= ~(PARODD);
            break;
 
        case 0:  
        default:
            term_attr.c_cflag &= ~(PARENB);
            break;
    }
 
 
    
    switch (stopbits) {
        case 2:  
            term_attr.c_cflag |= CSTOPB;
            break;
 
        case 1:  
        default:
            term_attr.c_cflag &= ~CSTOPB;
            break;
    }
 
    term_attr.c_cc[VMIN] = 1;
    term_attr.c_cc[VTIME] = 0;
 
    if (ioctl(fd, TCSETAW, &term_attr) < 0) {
        return -1;
    }
 
    if (ioctl(fd, TCFLSH, 2) < 0) {
        return -1;
    }
 
    return 0;
}
 
 
int read_data(int fd, void *buf, int len)
{
    int count;
    int ret;
 
    ret = 0;
    count = 0;
 
    //while (len > 0) {
 
    ret = read(fd, (char*)buf + count, len);
    if (ret < 1) {
        fprintf(stderr, "Read error %s\n", strerror(errno));
        //break;
    }
 
    count += ret;
    len = len - ret;
 
    //}
 
    *((char*)buf + count) = 0;
    return count;
}
 
 
int write_data(int fd, void *buf, int len)
{
    int count;
    int ret;
 
    ret = 0;
    count = 0;
 
    while (len > 0) {
 
        ret = write(fd, (char*)buf + count, len);
        if (ret < 1) {
            fprintf(stderr, "Write error %s\n", strerror(errno));
            break;
        }
 
        count += ret;
        len = len - ret;
    }
 
    return count;
}

void print_usage(char *program_name)
{
    fprintf(stderr,
            "*************************************\n"
            "  A Simple Serial Port Test Utility\n"
            "*************************************\n\n"
            "Usage:\n  %s <device> <baud> <databits> <parity> <stopbits> \n"
            "       databits: 5, 6, 7, 8\n"
            "       parity: 0(None), 1(Odd), 2(Even)\n"
            "       stopbits: 1, 2\n"
            "Example:\n  %s /dev/ttyS4 115200 8 0 1\n\n",
            program_name, program_name
           );
}

4.1测试会遇到的问题

4.1.1需要连接远程服务器

配置 VS Code 使用 Remote-SSH 进行串口通信

  1. 安装 VS Code Remote-SSH 插件

    • 打开 VS Code。
    • 在左侧活动栏中,点击扩展图标(四个方块的图标)。
    • 在搜索栏中输入 “Remote - SSH”,找到并安装 “Remote - SSH” 插件。
  2. 配置 SSH 主机

    • 在 VS Code 中按 Ctrl+Shift+P 打开命令面板。
    • 输入 “Remote-SSH: Open SSH Configuration File”,并选择 ~/.ssh/config 文件进行编辑。
    • 添加以下内容以配置 SSH 连接:
       
      ​
      ​
      Host <your-host-alias>
      HostName <your-host-ip> 
      User <your-username> 
      Port <your-ssh-port> 
      # 默认为 22,若是不同的端口,请修改为相应的端口
      
      ​
      
      ​

    • <your-host-alias> 替换为你的主机别名,<your-host-ip> 替换为主机的 IP 地址,<your-username> 替换为你的用户名。
  3. 连接到远程主机

    • 在 VS Code 中按 Ctrl+Shift+P 打开命令面板。
    • 输入 “Remote-SSH: Connect to Host”,然后选择刚刚配置的主机别名进行连接。
    • 输入密码进行验证后,VS Code 将在远程主机上打开一个新的窗口。
  4. 配置串口通信

    • 在远程主机上打开终端(可以通过 VS Code 内置终端或直接通过 SSH 终端进行操作)。

    • 确认串口设备是否被识别:

      ls /dev/ttyS*

      这将列出所有可用的串口设备,例如 /dev/ttyS0, /dev/ttyS1 等。

    • 假设你的串口设备为 /dev/ttyS4,可以通过以下命令配置串口参数(例如波特率、数据位、停止位等):

      stty -F /dev/ttyS4 115200 cs8 -cstopb -parenb

      这条命令配置 /dev/ttyS4 的波特率为 115200,数据位为 8 位,1 个停止位,无奇偶校验。

    • 使用以下命令打开串口,并查看串口通信内容:

      cat /dev/ttyS4

  5. 发送和接收数据

    • 现在可以在远程主机上通过 VS Code 终端进行串口通信。你可以发送数据到串口或从串口接收数据。例如,可以使用 echo 命令发送数据:

      echo "Hello, Serial Port" > /dev/ttyS4

    • 在另一端使用 cat 命令查看接收到的数据:

      cat /dev/ttyS4

通过以上步骤,你可以在 VS Code 中通过 Remote-SSH 插件配置和调试串口通信。

 5.总结

测试UART串口就四个步骤

准备硬件——>配置硬件——>编译——>运行测试 

  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

羊羽快去写代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值