玩转Linux串口编程(RS485详解) - 1. 简介" 在Linux中,所有串口都被映射成TTY终端模式,因此进行串口编程时,关键在于找到并启用对应平台的TTY。以Nuclei平台的轩辕91030M芯片为例,在设备树配置文件中: ```markdown uart0: - compatible: "sifive,uart0" address: 0x10013000 interrupt: plic0 (2) clock: hfclk2 status: okay uart1: - compatible: "sifive,uart0" address: 0x10012000 interrupt: plic0 (3) clock: hfclk2 status: okay ``` 确保上述 UART 设备的 `status` 都设为 "okay" 后,在 "/dev/" 目录下会出现 ttySIF0 和 ttySIF1 两个串
最编程
2024-07-20 11:30:37
...
查看代码
static int io_rs485_to_send(void)
{
int ret;
#if KERNEL_RS485_CTRL
int fd;
char *tl485_ctl = "/dev/tl485_ctl_pin";
fd = open(tl485_ctl, O_RDWR);
if(fd < 0)
{
printf("Open %s failed\n", tl485_ctl);
close(fd);
return -1;
}
ret = ioctl(fd, 1, 0);
if(ret<0)
{
printf("tl485 set ctl to high failed!\r\n");
close(fd);
return -1;
}
close(fd);
return 0;
#else
if(devGpioSet(8, 1) < 0) /* 用户态驱动接口 */
return -1;
return 0;
#endif
}
static int io_rs485_to_recv(void)
{
int ret;
#if KERNEL_RS485_CTRL
int fd;
char *tl485_ctl = "/dev/tl485_ctl_pin";
fd=open(tl485_ctl, O_RDWR);
if(fd < 0)
{
printf("Open %s failed\n", tl485_ctl);
close(fd);
exit(1);
}
ret = ioctl(fd, 0, 0);
if(ret<0)
{
close(fd);
printf("tl485 set ctl to low failed!\r\n");
return -1;
}
close(fd);
return 0;
#else
if(devGpioSet(8, 0) < 0) /* 用户态驱动接口 */
return -1;
return 0;
#endif
}
3、串口初始化
查看代码
int devRS485InitPort(int com)
{
int fd;
int rv;
int flags = 0;
/*USART_RS485_DEV就是 /dev/ 下的tty设备,如本例中就是"/dev/ttySIF1" */
fd = open(USART_RS485_DEV, O_RDWR | O_NOCTTY | O_NDELAY);
if (fd < 0) {
perror("Open error:\n");
exit(1);
}
/* 恢复串口为阻塞状态 */
if (fcntl(fd, F_SETFL, 0) < 0) {
printf("fcntl failed.\n");
return -1;
}
/* 测试该设备是否为tty设备 */
if (isatty(fd) == 0) {
printf("not tty device.\n");
return -1;
}
rv = io_uart_set_opt(fd, 9600, 8, 'N', 1);
if (rv < 0) {
printf("Set uart faild\n");
return -1;
}
return fd;
}
4、串口发送函数
查看代码
STATUS devRS485SendDatas(int ttyfd, const char *buf, int len)
{
int rv = 0;
ssize_t wlen = 0;
int sendflag = 0;
rv = io_rs485_to_send();
if(rv < 0)
{
printf("set 485 to send failed\n");
return -1;
}
wlen = write(ttyfd, buf, len);
if (wlen != len) {
tcflush(ttyfd, TCOFLUSH);
printf("write 485 failed\n");
return -1;
}
/* write只是将数据从文件写到了发送缓存区,
* tcdrain是等待发送缓存区发送完成,完成之前阻塞。
* 从很多教程都说这个函数会在这里阻塞,但是我测试波形时发现:
收发控制管脚总是在RS485数据线刚出波形就置为接收状态了
!!!!!!!这里没搞懂,所以在后面加了延时。
*/
rv = tcdrain(ttyfd);
if(rv < 0)
{
printf("Wite 485 send failed\n");
return -1;
}
usleep(len * 1000); /*9600发送1BYTE数据大约1ms*/
rv = io_rs485_to_recv();
if(rv < 0)
{
printf("Set 485 recv failed\n");
return -1;
}
return 0;
}
5、接收函数
查看代码
int g485fd;
int open485(void)
{
int retval;
fd_set rfds;
struct timeval tv;
int nread;
char gRcvBuf[256];
int gRcvLen = 0;
g485fd = devRS485InitPort(0);
if (g485fd < 0) {
printf("error: open console error.\r\n");
close(g485fd);
return ERROR;
}
// wait 2.5s ,select最后一个参数
tv.tv_sec = 2; //阻塞时间(秒)
tv.tv_usec = 500; //阻塞时间(毫秒)
while (1)
{
FD_ZERO(&rfds);
FD_SET(g485fd, &rfds);
retval = select(g485fd + 1 , &rfds, NULL, NULL, NULL); /*最后一个参数为NULL表示有数据前一直阻塞*/
if (retval == -1) {
perror("select()");
break;
}
else if (retval) { // pan duan shi fou hai you shu ju
if(!FD_ISSET(g485fd,&rfds)) /*判断是不是这个串口触发的*/
continue;
/*测试时发现这里每次调用read()只收到一个byte*/
nread = read(g485fd, gRcvBuf + gRcvLen, 256);
gRcvLen += nread;
/*缓存区越界处理,这里只是随便写的,需要修改*/
if (gRcvLen >= sizeof(gRcvBuf))
gRcvLen = 0;
//printf("gRcvLen = %d ", gRcvLen);
if (gRcvBuf[gRcvLen - 2] == '\r' && gRcvBuf[gRcvLen-1] == '\n') {
FD_ZERO(&rfds);
FD_SET(g485fd, &rfds);
retval = select(g485fd + 1 , &rfds, NULL, NULL, NULL);
if (!retval) continue;// no datas, break
}
/*
*包解析流程
......
*/
//for (int i = 0; i < 19; i++) {
// printf("%02x ", gRcvBuf[i]);
//}
//printf("gRcvLen=%d\r\n", gRcvLen);
}
else {
continue;
};
}
创建一个任务处理串口的接收即可。
参考:
https://blog.****.net/weixin_45003868/article/details/130263090
https://zhuanlan.zhihu.com/p/521283753?utm_id=0&wd=&eqid=b51aeee60009016800000003647efd5b
上一篇: 不同的进程间通讯方式探索
下一篇: 无人机有前途吗?