页面标题
GitHub Gitee

在Hi3519DV500上使用其他串口

笔者这次需要使用 Hi3519DV500 上的其他串口,以此来达到与其他设备通信的目的。
然而,麻烦的是,海思的官方 SDK 默认是不开启除 UART0 以外的其他串口的,那就只好自己来整了。
此外,这次的文章严格来说跟 IPC 开发没有半毛钱关系,也就不加相关的 tag 了。

1. 设备树文件

按照《外围设备驱动 操作指南》的说法,咱们在设备树文件里把其他串口的对应配置打开,以下均以 UART2 为例:

进入内核源码的 arch/arm64/boot/dts/vendor/hi3519dv500-demb.dts 文件:

将 uart2 配置为 “okey” ,然后重新编译内核文件即可。

串口默认工作模式为中断模式,如果需要配置为DMA模式,
需要自行修改 hi3519dv500.dtsi 文件,具体可以查阅《外围设备驱动 操作指南》

2. 寄存器配置(引脚功能复用)

更要命的地方在于,海思没有预先配置串口对应引脚的寄存器配置,那就只能自己查手册了,好在也不算多费劲。

查询 Hi3519DV500_PINOUT_CN.xlsx,在“管脚功能寄存器”中,查找 UART2 相关的内容:

上面这个是 UART2_RXD,TXD 在下面:

首先,我们需要在两个寄存器的“功能选择”里,选择 UART2_RXD 和 UART2_TXD,
然后,根据用户手册里 UART 一章的说法,我们需要将 RXD 管脚配置为上拉使能。

这样,这两个寄存器的值就应该分别配置成:

0x010260000 -> 0x1301
0x010260004 -> 0x1101  # 这里我们姑且把TXD也配置成上拉

那么,问题来了,我们如何配置这两个寄存器的值呢?
在老款的海思多媒体芯片里可以直接用 himm 这个工具,不过我这边是没找着类似的工具(手册里好像提过一个叫 bspmm 的东西,但是我没找着...)
那就自己写一个自己的能读取/写入寄存器值的工具:

以下是读取:

// read_reg_val.c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>

#define PAGE_SIZE 4096

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        fprintf(stderr, "Usage: %s <register_address>\n", argv[0]);
        fprintf(stderr, "Example: %s 0x010260000\n", argv[0]);
        return EXIT_FAILURE;
    }

    // 解析输入的地址
    uint64_t reg_addr;
    char *endptr;
    reg_addr = strtoul(argv[1], &endptr, 0);
    if (*endptr != '\0' || reg_addr == 0)
    {
        fprintf(stderr, "Invalid address format. Use decimal or 0xhex format.\n");
        return EXIT_FAILURE;
    }

    // 打开/dev/mem设备
    int fd = open("/dev/mem", O_RDONLY | O_SYNC);
    if (fd == -1)
    {
        perror("Failed to open /dev/mem (need root?)");
        return EXIT_FAILURE;
    }

    // 计算映射参数
    uint64_t map_base = reg_addr & ~(PAGE_SIZE - 1);  // 页对齐基地址
    uint64_t map_offset = reg_addr & (PAGE_SIZE - 1); // 页内偏移量

    // 映射内存页
    void *mapped_base = mmap(
        NULL,
        PAGE_SIZE,
        PROT_READ,
        MAP_SHARED,
        fd,
        map_base);

    if (mapped_base == MAP_FAILED)
    {
        perror("Memory mapping failed");
        close(fd);
        return EXIT_FAILURE;
    }

    // 获取寄存器指针(volatile防止编译器优化)
    volatile uint32_t *reg_ptr = (uint32_t *)((uint8_t *)mapped_base + map_offset);

    // 读取寄存器值
    uint32_t reg_value = *reg_ptr;

    // 输出结果
    printf("Register [0x%lX] value:\n", reg_addr);
    printf("  Hex: 0x%08X\n", reg_value);
    printf("  Decimal: %u\n", reg_value);
    printf("  Binary: 0b");
    for (int i = 31; i >= 0; i--)
    {
        printf("%d", (reg_value >> i) & 1);
        if (i % 4 == 0 && i != 0)
            printf("_");
    }
    printf("\n");

    // 清理资源
    if (munmap(mapped_base, PAGE_SIZE) == -1)
        perror("munmap failed");
    close(fd);
    return EXIT_SUCCESS;
}

以下是写入:

// write_reg_val.c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>

#define PAGE_SIZE 4096

int main(int argc, char *argv[]) {
    if (argc != 3) {
        fprintf(stderr, "Usage: %s <register_address> <value>\n", argv[0]);
        fprintf(stderr, "Example: %s 0x010260000 0x1A00\n", argv[0]);
        return EXIT_FAILURE;
    }

    // 解析地址和写入值
    uint64_t reg_addr = strtoul(argv[1], NULL, 0);
    uint32_t write_val = strtoul(argv[2], NULL, 0);

    // 打开/dev/mem设备(需要读写权限)
    int fd = open("/dev/mem", O_RDWR | O_SYNC);
    if (fd == -1) {
        perror("Failed to open /dev/mem (need root?)");
        return EXIT_FAILURE;
    }

    // 计算内存映射参数
    uint64_t map_base = reg_addr & ~(PAGE_SIZE - 1);   // 页对齐基地址
    uint64_t map_offset = reg_addr & (PAGE_SIZE - 1);  // 页内偏移量

    // 映射物理内存到用户空间
    void *mapped_base = mmap(
        NULL,
        PAGE_SIZE,
        PROT_READ | PROT_WRITE,
        MAP_SHARED,
        fd,
        map_base
    );

    if (mapped_base == MAP_FAILED) {
        perror("Memory mapping failed");
        close(fd);
        return EXIT_FAILURE;
    }

    // 获取寄存器指针(volatile确保实际硬件访问)
    volatile uint32_t *reg_ptr = (uint32_t *)((uint8_t *)mapped_base + map_offset);

    // 执行写操作
    printf("Writing 0x%08X to address 0x%08lX\n", write_val, reg_addr);
    *reg_ptr = write_val;

    // 内存屏障(确保写操作完成)
    __sync_synchronize();

    // 清理资源
    if (munmap(mapped_base, PAGE_SIZE) == -1)
        perror("munmap failed");
    close(fd);
    return EXIT_SUCCESS;
}

各自编译它们,然后执行:

./write_reg_val 0x010260000 0x1301
./write_reg_val 0x010260004 0x1101

就配置完成了。

你也可以将编译出来的这两个小工具放在 /usr/sbin 文件夹下,配置好 profilePATH 路径,
你就可以把它们当作命令来使用了,或者在开机启动脚本里用它们来提前配置也无不可。

3. 测试

使用 cat /dev/ttyAMA2 命令,然后将开发板的对应串口连接电脑,注意,用 CH340 时别忘了接上 GND 引脚。
通过电脑端的串口工具向开发板发送信息,别忘了提前设置波特率:

你也可以写一段 C 语言的程序:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
#include <sys/types.h>

const char *STR = "Hello Hisilicon!";
int uart_set(int fd)
{
    struct termios options;
    if (tcgetattr(fd, &options) < 0)
    {
        printf("tcgetattr error\n");
        return -1;
    }
    // 设置波特率
    cfsetispeed(&options, B115200);
    cfsetospeed(&options, B115200);
    // 关闭流控
    options.c_cflag &= ~CRTSCTS;
    // 设置数据位
    options.c_cflag &= ~CSIZE;
    options.c_cflag |= CS8;
    // 设置校验位
    options.c_cflag &= ~PARENB;
    options.c_cflag &= ~INPCK;
    // 1停止位
    options.c_cflag &= ~CSTOPB; 
    if (tcsetattr(fd, TCSANOW, &options) < 0)
    {
        printf("tcsetattr failed\n");
        return -1;
    }
    return 0;
}
int uart_read(int fd, char *buf, int len)
{
    int ret;
    int read_num, left_num;
    fd_set rfds;
    char *ptr;
    FD_ZERO(&rfds);
    FD_SET(fd, &rfds);
    left_num = len;
    ret = select(fd + 1, &rfds, NULL, NULL, NULL);
    if (ret > 0)
    {
        while (left_num > 0)
        {
            read_num = read(fd, buf, left_num);
            if (read_num > 0)
            {
                left_num -= read_num;
                ptr += read_num;
            }
            else
            {
                printf("read fail!\n");
                return -1;
            }
        }
    }
    return 0;
}
int uart_write(int fd, char *buf, int len)
{
    int ret;
    int write_num, left_num;
    char *ptr;
    left_num = len;
    while (left_num > 0)
    {
        write_num = write(fd, buf, left_num);
        if (write_num > 0)
        {
            left_num -= write_num;
            ptr += write_num;
        }
        else
        {
            printf("write fail!\n");
            return -1;
        }
    }
    return 0;
}
int main(int argc, char *argv[])
{
    int ret = 0;
    int serial_port = open("/dev/ttyAMA2", O_RDWR | O_NOCTTY | O_NDELAY);
    if (serial_port == -1)
    {
        perror("open serial port");
        exit(EXIT_FAILURE);
    }
    ret = uart_set(serial_port);
    if (ret == -1)
    {
        perror("uart set");
        exit(EXIT_FAILURE);
    }
    while (getchar() != 'e')
        uart_write(serial_port, STR, strlen(STR));
    return 0;
}
posted @ 2025-05-04 22:01  Wintoki  阅读(413)  评论(0)    收藏  举报