在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 文件夹下,配置好 profile 内 PATH 路径,
你就可以把它们当作命令来使用了,或者在开机启动脚本里用它们来提前配置也无不可。
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;
}

浙公网安备 33010602011771号