LoRa串口透传驱动开发—【万物智能之开源鸿蒙OpenHarmony系统实战开发系列教程】
蓝牙透传会了。今天换 LoRa。一颗模组只能证明口通、模块活着;真要验证无线,得两套对打。
同一根 UART 排针,上午可以挂 GPS、下午可以挂 BLE。再换一颗 LoRa 透传模组,软件上还是 /dev/ttyS7,差别只有三件:
- 波特率是 9600,不是 115200。
- 数据按 十六进制裸字节 走,不要当文本行,也不要发明一套 AT。
- 一颗模组只能证明串口活着。 无线电要两套:一边发、一边收。
我见过的空转几乎全落在这三件上。有人把 BLE 的 115200 原样拿来,write 返回 3,模组 AUX 灯不闪,对端什么都没有。有人对着单颗模组发 FE 05 90,等无线回显,等不到就开始改驱动。有人在串口里敲 AT+ADDR?,期望 OK——透传模式下那六个字符会当射频载荷打出去,模组不会回答你。
两根模式脚都接到地上(低电平),上电就是透传。串口进、射频出,字节不解析。不要进配置态,不要发 AT。手册里若画了 M0/M1 拨码,看的是「两根都低」,不是「发一条指令切模式」。
产品代号仍是 rk3568_evb。节点、termios、poll、32 位 musl,和 BLE 那篇是同一套 C。这篇只把发送改成十六进制,并把「单模块口径」写死,免得验收时扯皮。



1. 它在链路上的位置
板 A 用户态
│ write(FE 05 90) @ ttyS7 9600 8N1
▼
uart7
│ TX/RX
▼
LoRa 透传模组 A ← 两根模式脚接地,透传
│ 射频
▼
LoRa 透传模组 B
│ TX/RX
▼
uart7 / ttyS7
│ poll + read
▼
板 B 用户态 打印 FE0590
单板、单模组时,图里的「射频」那一跳不存在。你能验证的只有虚线以上:open 成功、write 返回正数、模组活动灯闪一下。灯闪说明芯片收到了 UART 字节并开始干活。灯不闪,字节没进模组,查波特率、TX/RX 交叉、供电。灯闪了却没有第二套设备,到此为止,不要再往下骂协议。
和 BLE 的对比,省得混:
| BLE 透传 | LoRa 透传 | |
|---|---|---|
| 口 | 同组排针,常见 `ttyS7` | 同左 |
| 波特率 | 115200 | **9600** |
| 载荷 | ASCII 文本即可 | **十六进制裸字节** |
| 对端 | 手机 nRF Connect | **另一颗 LoRa 模组** |
| 单套能证明什么 | GATT 双向五个字母 | **只有 UART** |
排针一次一颗。LoRa 和 BLE、GPS 的 TX 并在一起,谁都不说话。测完拔掉。
不要占用 /dev/ttyS2。 那是调试口,1500000。往那里写十六进制,你的串口窗口会开花。
2. 设备树还是 uart7
device/board/rk/rk3568_evb/kernel/rk3568-evb-linux.dts
&uart7 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&uart7m1_xfer>;
};
现场核对:
hdc shell "ls -l /dev/ttyS7"
hdc shell "cat /proc/tty/driver/serial"
期望字符设备:
crw-rw-rw- 1 root root 4, 71 ... /dev/ttyS7
serial 表要有 7: 行,类型常见 16550A。第一位是 - 不是 c,是有人 echo > /dev/ttyS7 在 tmpfs 上建的普通文件,tcgetattr 报 Not a tty。删掉假文件,查 DTS status 和 pinctrl。
HAP 不是 root。临时:
hdc shell "chmod 0666 /dev/ttyS7"
重启会丢。开机脚本:
device/board/rk/rk3568_evb/board_perm/board-perm.sh
由 *.rk30board.cfg 里 once = 1 的服务拉起来。ohos.boot.hardware 必须是 rk30board。
模式脚:两根都接地。有的板子出厂已经用电阻拉低,上电即透传;有的露出两个焊盘或拨码。量一下,低就是透传。一根悬空,模组可能进配置态,UART 上的字节被当成命令吃掉,射频侧什么都不发。这不是驱动 bug。
3. 完整 C:十六进制收发
dw-apb-uart 上两条假路不要走:纯阻塞 read + VTIME 会永远不返回;select 在 DMA 回退中断模式时,内核 rx 涨、用户态却说没数据。稳的是 O_NONBLOCK + poll(POLLIN)。CRTSCTS 关掉,CTS 悬空会卡死收。
十六进制发送:字符串 FE0590 或 FE 05 90 都收,写成三个字节 0xFE 0x05 0x90。打印接收时用大写连续 hex,中间不插空格,对端好对。
uart_lora.c:
/* uart_lora.c — OpenHarmony 4.1 用户态,32 位 musl
* uart_lora /dev/ttyS7 9600 交互:一行当 hex 发出去
* uart_lora /dev/ttyS7 9600 send FE0590 发 3 字节,再听 1 秒
* uart_lora /dev/ttyS7 9600 recv 5000 只听
*/
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
static speed_t baud_const(int b)
{
switch (b) {
case 4800: return B4800;
case 9600: return B9600;
case 19200: return B19200;
case 38400: return B38400;
case 57600: return B57600;
case 115200: return B115200;
default: return B0;
}
}
static int hexval(int c)
{
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
return -1;
}
/* 把 "FE 05 90" / "FE0590" 收成字节。返回字节数,-1 格式错。 */
static int parse_hex(const char *s, unsigned char *out, int max)
{
int n = 0;
const char *p = s;
while (*p) {
if (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') {
p++;
continue;
}
int hi = hexval(*p++);
if (hi < 0 || !*p)
return -1;
int lo = hexval(*p++);
if (lo < 0)
return -1;
if (n >= max)
return -1;
out[n++] = (unsigned char)((hi << 4) | lo);
}
return n;
}
static void hexdump_line(const unsigned char *b, int n)
{
int i;
for (i = 0; i < n; i++)
printf("%02X", b[i]);
printf("\n");
}
static int uart_open(const char *path, int baud)
{
speed_t sp = baud_const(baud);
if (sp == B0) {
fprintf(stderr, "波特率 %d 不在表里\n", baud);
return -1;
}
int fd = open(path, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (fd < 0) {
fprintf(stderr, "open %s: %s\n", path, strerror(errno));
return -1;
}
struct termios t;
memset(&t, 0, sizeof(t));
if (tcgetattr(fd, &t) != 0) {
fprintf(stderr, "tcgetattr: %s(节点是普通文件?)\n", strerror(errno));
close(fd);
return -1;
}
cfsetispeed(&t, sp);
cfsetospeed(&t, sp);
t.c_cflag &= ~CSIZE;
t.c_cflag |= CS8 | CLOCAL | CREAD;
t.c_cflag &= ~PARENB;
t.c_cflag &= ~CSTOPB;
t.c_cflag &= ~CRTSCTS;
t.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
t.c_iflag &= ~(IXON | IXOFF | IXANY | INLCR | ICRNL);
t.c_oflag &= ~OPOST;
t.c_cc[VMIN] = 0;
t.c_cc[VTIME] = 0;
tcflush(fd, TCIOFLUSH);
if (tcsetattr(fd, TCSANOW, &t) != 0) {
fprintf(stderr, "tcsetattr: %s\n", strerror(errno));
close(fd);
return -1;
}
return fd;
}
static int uart_write_bytes(int fd, const unsigned char *b, int n)
{
ssize_t w = write(fd, b, (size_t)n);
if (w < 0) {
fprintf(stderr, "write: %s\n", strerror(errno));
return -1;
}
return (int)w;
}
static int uart_read_bytes(int fd, unsigned char *out, int outsz, int timeout_ms)
{
int total = 0, idle = 0;
while (idle < timeout_ms && total < outsz) {
struct pollfd p;
p.fd = fd;
p.events = POLLIN;
p.revents = 0;
int pr = poll(&p, 1, 100);
if (pr > 0 && (p.revents & POLLIN)) {
int r = (int)read(fd, out + total, (size_t)(outsz - total));
if (r > 0) {
total += r;
idle = 0;
continue;
}
}
idle += 100;
}
return total;
}
static void interactive(int fd)
{
fprintf(stderr, "一行当 hex 发出去,例如 FE0590。Ctrl-C 退出。\n");
for (;;) {
struct pollfd ps[2];
ps[0].fd = fd;
ps[0].events = POLLIN;
ps[1].fd = STDIN_FILENO;
ps[1].events = POLLIN;
if (poll(ps, 2, 500) <= 0)
continue;
if (ps[0].revents & POLLIN) {
unsigned char rx[512];
int n = uart_read_bytes(fd, rx, sizeof(rx), 200);
if (n > 0) {
printf("RX %d: ", n);
hexdump_line(rx, n);
}
}
if (ps[1].revents & POLLIN) {
char line[512];
if (!fgets(line, sizeof(line), stdin))
break;
unsigned char tx[256];
int n = parse_hex(line, tx, (int)sizeof(tx));
if (n <= 0) {
fprintf(stderr, "hex 格式不对\n");
continue;
}
int w = uart_write_bytes(fd, tx, n);
printf("TX %d: ", w);
hexdump_line(tx, n);
}
}
}
int main(int argc, char **argv)
{
if (argc < 3) {
fprintf(stderr, "用法: %s <设备> <波特率> [send <hex>|recv <毫秒>]\n",
argv[0]);
return 1;
}
int baud = atoi(argv[2]);
int fd = uart_open(argv[1], baud);
if (fd < 0)
return 1;
if (argc >= 5 && strcmp(argv[3], "send") == 0) {
unsigned char tx[256];
int n = parse_hex(argv[4], tx, (int)sizeof(tx));
if (n <= 0) {
fprintf(stderr, "hex 格式不对: %s\n", argv[4]);
close(fd);
return 1;
}
int w = uart_write_bytes(fd, tx, n);
printf("TX %d 字节: ", w);
hexdump_line(tx, n);
unsigned char rx[512];
int r = uart_read_bytes(fd, rx, sizeof(rx), 1000);
if (r > 0) {
printf("RX %d: ", r);
hexdump_line(rx, r);
} else {
printf("RX: 1 秒内无数据(单模块正常;对端要另一套)\n");
}
close(fd);
return 0;
}
if (argc >= 5 && strcmp(argv[3], "recv") == 0) {
int ms = atoi(argv[4]);
unsigned char rx[1024];
int r = uart_read_bytes(fd, rx, sizeof(rx), ms);
if (r > 0) {
printf("RX %d: ", r);
hexdump_line(rx, r);
} else {
printf("RX: %d ms 内无数据\n", ms);
}
close(fd);
return 0;
}
interactive(fd);
close(fd);
return 0;
}
32 位 musl,板上只有 /system/lib/ld-musl-arm.so.1:
OHOS=$HOME/code/opcode/OpenHarmony
CLANG=$OHOS/prebuilts/clang/ohos/linux-x86_64/llvm/bin/clang
SYSROOT=$OHOS/prebuilts/ohos-sdk/linux/11/native/sysroot
$CLANG --target=arm-linux-ohos --sysroot=$SYSROOT \
-O2 -Wall \
-Wl,--dynamic-linker=/system/lib/ld-musl-arm.so.1 \
-o uart_lora uart_lora.c
file uart_lora
# ELF 32-bit LSB executable, ARM, interpreter /system/lib/ld-musl-arm.so.1
hdc file send uart_lora /data/local/tmp/uart_lora
hdc shell "chmod 0755 /data/local/tmp/uart_lora"
file 若是 aarch64,exec 失败。那是 target 写错。
FE0590 没有特殊协议含义,三个任意非空字节都行。选它只因为肉眼好认,活动灯也好对照:发下去灯该闪。不要把这三字节当成某家模组的「查询 ID」命令——透传模式不认命令。
4. 单模块:只验 UART
插一颗。模式脚都低。旁边不要第二颗 UART 模块。
hdc shell "/data/local/tmp/uart_lora /dev/ttyS7 9600 send FE0590"
期望:
TX 3 字节: FE0590
RX: 1 秒内无数据(单模块正常;对端要另一套)
TX 3 是这步的判据。write 返回 3,内核 serial 表 7: 的 tx 计数加 3,模组活动灯闪一下:UART 通路成立。
RX 空是正常的。没有第二颗模组在空中接,本地 UART 也不会环回——TX 和 RX 是两根线,除非你在排针上短接,否则自己发的字节不会从 RX 爬回来。有人把「RX 空」当成故障,开始改 termios,改一天。
打开失败对照:
open /dev/ttyS7: No such file or directory
DTS 没 okay。
tcgetattr: Not a tty
假节点。
open /dev/ttyS7: Permission denied
chmod 0666。hdc 是 root,通常碰不到;HAP 会。
TX 3 了灯不闪:
- 波特率不是 9600。115200 时控制器吐字节,模组按 9600 采样,全是噪声,芯片不认为收到完整帧,灯可能完全不动。
- TX/RX 没交叉。SoC TX 接模组 RX,SoC RX 接模组 TX。两根都接 TX,灯不闪。
- 模组没供电,或 3.3 V / 5 V 接反。先量 VCC。
- 模式脚不是双低,字节被吃进配置态。
tx 计数都不涨:write 没进控制器。查 fd、termios、是不是写到了 ttyS2。
单模块验收到此结束。不要在这一步要求「空中有回包」。
交互模式也可以用来盯灯:
hdc shell "/data/local/tmp/uart_lora /dev/ttyS7 9600"
敲 FE0590 回车,看灯。Ctrl-C 退出。
5. 两套:空中那一跳
两块板,或一块板加一台带同一频段透传模组的 USB 转串口。两边都 9600 8N1,都是透传(模式脚双低)。信道、地址、空速出厂通常一致;若一边被人改过配置,透传对不上,那是射频参数问题,不是开源鸿蒙的 UART 问题。先保证两边都是出厂透传,再谈参数。
板 B 先听:
hdc shell "/data/local/tmp/uart_lora /dev/ttyS7 9600 recv 20000"
板 A 发:
hdc shell "/data/local/tmp/uart_lora /dev/ttyS7 9600 send FE0590"
板 B 期望:
RX 3: FE0590
板 A 的 RX: 1 秒内无数据 仍正常,除非 B 也在发。
对不上时,先把两套都退化成「单模块 UART 自检」。两边灯都闪,再把它们放到几米内、天线拧紧、远离金属。一边灯闪一边 recv 空,才是射频:信道、空速、天线、距离、模式脚。不要先改 poll。
短接回环(SoC TX 短接 RX、模组拔掉)只能证明 控制器 活着,证明不了模组。LoRa 的单模块口径是「写成功 + 灯闪」,不是短接回环。
两套对发时注意:透传模组从收到 UART 到射频出去有几十毫秒级的缓冲。A send 之后立刻 recv 100 可能空,B 侧 recv 20000 更稳。不要用过短超时当「射频丢包」。
应用侧 uartWrite(..., isHex=1)、uartRead(..., asHex=1),默认 9600、/dev/ttyS7。不要在透传里发 AT。排障顺序:波特率 9600 → 灯闪 → 单模块到此为止 → 两套才谈射频。
系列第 45 篇 · 芯片:瑞芯微 RK3568 · OpenHarmony 4.1(API 11) · Linux 5.10
更多推荐

所有评论(0)