树莓派5串口调试
在完成一个物联网项目时,尝试以stm32作为数据采集设备,通过树莓派与stm32通信
配置
我们需要通过raspi_config禁止串口登陆以释放串口设备给程序使用,同时保留硬件串口
运行 sudo raspi_config
,选择 Interface Options
选择 Serial Port
随后在提示符 Would you like a login shell to be accessible over serial?
下选择 No
在提示符 Would you like the serial port hardware to be enabled?
下选择Yes
保存退出并重启树莓派
打开并编辑 /boot/firmeare/config.txt
,将文件末尾修改如下:
[all]
dtoverlay=uart0-pi5
若要使用其他次UART也可考虑禁用蓝牙控制器
问题
根据树莓派官网介绍:树莓派5包含5个PL011 UART,不包含mini UART,除 UART0
外其他默认情况下禁用。
ID | 类型 |
---|---|
UART0 | PL011 |
UART1 | PL011 |
UART2 | PL011 |
UART3 | PL011 |
UART4 | PL011 |
在树莓派上,一个 UART 通过 GPIO 14(发送)和 15(接收),即主UART。默认情况下,这也是 Linux 控制台可能存在的 UART
文档这里将树莓派5的主UART标为UART10,而后在介绍Linux设备时说:
Linux设备 | 描述 |
---|---|
/dev/ttyAMA0 | UART0 |
/dev/serial0 | 主UART0 |
/dev/ttyAMA10 | 调试UART |
同时又在下面指出 /dev/serial0
会通过符号链接指向 /dev/ttyAMA0
(因为树莓派5不包含mini UART)
实际上,查看 /dev/
可以看到:
$ ll | grep ttyAMA
lrwxrwxrwx 1 root root 8 May 6 23:03 serial0 -> ttyAMA10
crw-rw---- 1 root dialout 204, 64 May 6 23:15 ttyAMA0
crw-rw---- 1 root dialout 204, 74 May 6 23:07 ttyAMA10
使用 /dev/serial0
通常是一个安全的选择,但它似乎被错误指向了 /dev/ttyAMA10
,这是板上SH1.0的3p端口。因此在调试UART时,若希望不禁用蓝牙连接而使用三针UART,应直接使用 /dev/ttyAMA0
这个设备来访问UART0
程序
串口配置:
int configure_serial(int fd) {
struct termios options{};
if (tcgetattr(fd, &options) < 0) {
perror("获取串口属性失败");
return -1;
}
cfmakeraw(&options);
cfsetispeed(&options, B115200);
cfsetospeed(&options, B115200);
options.c_cflag |= (CLOCAL | CREAD); // 启用接收
options.c_cflag &= ~CSTOPB; // 1位停止位
options.c_cc[VMIN] = 0; // 接收最小字节数
options.c_cc[VTIME] = 10; // 超时 1 秒(单位0.1s)
if (tcsetattr(fd, TCSANOW, &options) < 0) {
perror("配置串口失败");
return -1;
}
return 0;
}
接收线程,包含字节检测:
void* recv_thread(void* arg) {
(void)arg;
while (1) {
fd_set read_fds;
struct timeval timeout;
FD_ZERO(&read_fds);
FD_SET(serial_fd, &read_fds);
timeout.tv_sec = 1;
timeout.tv_usec = 0;
int retval = select(serial_fd + 1, &read_fds, NULL, NULL, &timeout);
if (retval == -1) {
perror("[recv] select error");
continue;
} else if (retval == 0) {
continue;
}
ssize_t read_bytes = read(serial_fd, recv_struct, sizeof(Recv));
if (read_bytes < 0) {
if (errno == EINTR) continue;
perror("[recv] read error");
continue;
} else if (read_bytes != sizeof(Recv)) {
fprintf(stderr, "[recv] 不完整数据 (%zd/%zu 字节)\n",
read_bytes, sizeof(Recv));
tcflush(serial_fd, TCIFLUSH);
continue;
}
printf("[recv] 收到%zd字节反馈数据\n", read_bytes);
}
return NULL;
}
发送线程:
void* send_thread(void* arg) {
(void)arg;
while (1) {
// 从服务器获取数据
sync_from_remote();
if (control_container->mode == 1) {
code_light_groups_normal_mode(light_struct);
} else if (control_container->mode == 2) {
code_light_groups_smart_mode(car_mgr, light_struct);
} else {
printf("[send] Invalid mode\n");
sleep(1);
continue;
}
ssize_t written = write(serial_fd, light_struct, sizeof(Light));
if (written < 0) {
perror("[send] 写入失败");
} else {
printf("[send] 写入%zd字节\n", written);
}
tcflush(serial_fd, TCOFLUSH);
sleep(1); // 控制发送间隔
}
return NULL;
}
主程序:
#include <pthread.h>
// ...
int serial_fd = -1;
int main()
{
// ...
// 打开串口0
if ((serial_fd = open("/dev/ttyAMA0", O_RDWR | O_NOCTTY)) < 0) {
perror("打开串口失败");
return -1;
}
if (configure_serial(serial_fd) < 0) {
close(serial_fd);
return -1;
}
pthread_t send_tid, recv_tid;
pthread_create(&send_tid, NULL, send_thread, NULL);
pthread_create(&recv_tid, NULL, recv_thread, NULL);
pthread_join(send_tid, NULL);
pthread_join(recv_tid, NULL);
close(serial_fd);
// ...
return 0;
}