【米尔MYD-YA15XC-T评测】+ epoll串口通信
<div class='showpostmsg'> 本帖最后由 dql2016 于 2021-12-15 20:09 编辑<p>串口是嵌入式系统中最常用的接口之一,系统终端通常都是串口。除了终端功能之外,实际应用中,Linux系统也经常通过串口与其它设备进行通信和数据传递,如232、485等接口传感器通常底层都是串口。在Linux下的串口编程不像微控制器上那么简单。本帖以米尔MYD-YA15XC-T为例,实现串口基本操作、串口属性设置、如串口数据读写。米尔MYD-YA15XC-T开发板扩展排针接口引出了串口3供我们使用。位置如下图所示:</p>
<p></p>
<p>终端使用<span style="color:#2ecc71;"><strong>ls /dev/tty*</strong></span>命令查看设备文件:</p>
<p>Linux的串口表现为设备文件,Linux的串口设备文件命名一般为<strong><em><span style="color:#2ecc71;">/dev/tty*</span></em></strong>,米尔MYD-YA15XC-T开发板串口设备命名为<strong><span style="color:#2ecc71;"><em>/dev/ttySTM*</em></span></strong>。</p>
<p>可以看到串口3对应的设备文件是<strong><span style="color:#2ecc71;"><em>/dev/ttySTM3</em></span></strong>,使用一个TTL串口调试助手连接到串口3:<br />
</p>
<p>首先使用echo命令测试发送数据,可以看到串口3收到了数据:<br />
</p>
<p>接下来通过编写代码的方式实现串口的数据收发。</p>
<p> </p>
<p>首先配置网络,因为后面拷贝文件到开发板需要用到。<br />
</p>
<p> </p>
<p>在编写Linux串口的C程序代码时,需要包含termios.h头文件。主要流程为:</p>
<h4 id="h4-u6253u5F00u4E32u53E3">打开串口:用open()函数打开它所对应的设备文件。</h4>
<h4 id="h4-u5173u95EDu4E32u53E3">关闭串口:用close()函数关闭串口。</h4>
<h4 id="h4-u53D1u9001u6570u636E">发送数据:使用write()函数可以发送数据。</h4>
<h4 id="h4-u8BFBu53D6u6570u636E">读取数据:使用read()函数可以读取接收到的数据。</h4>
<p>此外,如果对串口属性进行设置需要包含<termios.h>头文件,该文件包含了POSIX终端属性描述结构struct termios。</p>
<p>完整的代码如下所示:</p>
<pre>
<code class="language-cpp">#include <iostream>
using namespace std;
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <string.h>
#include <chrono>
#include <thread>
class MySerial
{
private:
int fd = -1;
string serial_dev;
int serial_baudrate;
string serial_parity;
int serial_databits;
int serial_stopbits;
void* (*rx_cb_fun)(void*);
int epfd;
struct epoll_event event; // 告诉内核要监听什么事件
struct epoll_event wait_event;
public:
MySerial(string serial_dev, int serial_baudrate, string serial_parity, int serial_databits, int serial_stopbits,void* (*rx_cb_fun)(void*))
{
this-> serial_dev= serial_dev;
this-> serial_baudrate= serial_baudrate;
this-> serial_parity= serial_parity;
this-> serial_databits= serial_databits;
this-> serial_stopbits= serial_stopbits;
this->rx_cb_fun = rx_cb_fun;
epfd = epoll_create(10); // 创建一个 epoll 的句柄,参数要大于 0, 没有太大意义
if( -1 == epfd )
{
perror ("epoll_create");
}
};
int OpenSerial()
{
struct termios tios;
int speed;
fd = open(serial_dev.c_str(), O_RDWR | O_NOCTTY | O_NDELAY | O_EXCL);
if (fd < 0)
{
perror("open");
}
memset(&tios, 0, sizeof(struct termios));
switch (serial_baudrate)
{
case 50:
speed = B50;
break;
case 75:
speed = B75;
break;
case 110:
speed = B110;
break;
case 134:
speed = B134;
break;
case 150:
speed = B150;
break;
case 200:
speed = B200;
break;
case 300:
speed = B300;
break;
case 600:
speed = B600;
break;
case 1200:
speed = B1200;
break;
case 1800:
speed = B1800;
break;
case 2400:
speed = B2400;
break;
case 4800:
speed = B4800;
break;
case 9600:
speed = B9600;
break;
case 19200:
speed = B19200;
break;
case 38400:
speed = B38400;
break;
case 57600:
speed = B57600;
break;
case 115200:
speed = B115200;
break;
case 230400:
speed = B230400;
break;
case 460800:
speed = B460800;
break;
case 500000:
speed = B500000;
break;
case 576000:
speed = B576000;
break;
case 921600:
speed = B921600;
break;
case 1000000:
speed = B1000000;
break;
case 1152000:
speed = B1152000;
break;
case 1500000:
speed = B1500000;
break;
case 2000000:
speed = B2000000;
break;
case 2500000:
speed = B2500000;
break;
case 3000000:
speed = B3000000;
break;
case 3500000:
speed = B3500000;
break;
case 4000000:
speed = B4000000;
break;
default:
speed = B9600;
break;
}
if ((cfsetispeed(&tios, speed) < 0) || (cfsetospeed(&tios, speed) < 0))
{
close(fd);
fd = -1;
perror("cfsetispeed or cfsetospeed");
}
tios.c_cflag |= (CREAD | CLOCAL);
tios.c_cflag &= ~CSIZE;
switch (serial_databits)
{
case 5:
tios.c_cflag |= CS5;
break;
case 6:
tios.c_cflag |= CS6;
break;
case 7:
tios.c_cflag |= CS7;
break;
case 8:
default:
tios.c_cflag |= CS8;
break;
}
if (serial_stopbits == 1)
{
tios.c_cflag &= ~CSTOPB;
}
else
{
tios.c_cflag |= CSTOPB;
}
if (serial_parity == "none")
{
tios.c_cflag &= ~PARENB;
}
else if (serial_parity == "even")
{
tios.c_cflag |= PARENB;
tios.c_cflag &= ~PARODD;
}
else if (serial_parity == "odd")
{
tios.c_cflag |= PARENB;
tios.c_cflag |= PARODD;
}
tios.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
if (serial_parity == "none")
{
tios.c_iflag &= ~INPCK;
}
else
{
tios.c_iflag |= INPCK;
}
tios.c_iflag &= ~(IXON | IXOFF | IXANY);
tios.c_oflag &= ~OPOST;
tios.c_cc = 0;
tios.c_cc = 0;
if (tcsetattr(fd, TCSANOW, &tios) < 0)
{
close(fd);
fd = -1;
perror("tcsetattr");
}
event.data.fd = fd; // 串口描述符
event.events = EPOLLIN; // 表示对应的文件描述符可以读
// 事件注册函数,将描述符fd加入监听事件
int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
if(-1 == ret)
{
perror("epoll_ctl");
}
return fd;
}
void loop()
{
int ret;
// 监视并等待多文件(串口)描述符的属性变化(是否可读)
// 没有属性变化,这个函数会阻塞,直到有变化才往下执行,这里没有设置超时
ret = epoll_wait(epfd, &wait_event, 2, -1);
if(ret == -1)// 出错
{
close(epfd);
perror("epoll");
}
else if(ret > 0)// 准备就绪的文件描述符
{
//char buf = {0};
if((fd == wait_event.data.fd) && (EPOLLIN == wait_event.events & EPOLLIN))
{
rx_cb_fun(&fd);
}
}
else if(0 == ret)
{
printf("time out\n");
}
}
~MySerial()
{
close(fd);
close(epfd);
}
};
void *SerialRxCB(void* arg)
{
int fd = *(int*)arg;
char buf;
read(fd, &buf, 1);
printf("%c\n", buf);
return NULL;
}
void thread_task(void* arg)
{
int fd = *(int*)arg;
while(1)
{
char tx_buf[]="hello eeworld & mier";
this_thread::sleep_for(chrono::seconds(1));
write (fd, tx_buf, sizeof(tx_buf));
}
}
int main(int argc,char* argv[])
{
MySerial *ps = new MySerial("/dev/ttySTM3",9600,"none",8,1,SerialRxCB);
int ret = ps->OpenSerial();
if(ret < 0)
{
perror("open serial");
return -1;
}
thread t(thread_task,&ret);
t.detach();
while(1)
{
ps->loop();
}
return 0;
}
</code></pre>
<p>程序中使用到了epoll,类似单片机里面的中断,可以实现异步数据的接收,程序中还使用到了c++11的线程库,米尔提供的交叉编译器版本很新,完全支持c++11的各种特性。首先在虚拟机终端执行命令,这样交叉编译环境变量就在该终端生效了:</p>
<pre>
<code class="language-bash">. /opt/st/myir/3.1-snapshot/environment-setup-cortexa7t2hf-neon-vfpv4-ostl-linux-gnueabi</code></pre>
<p>可以看到CXX环境变量:</p>
<p></p>
<p>然后执行命令进行程序的编译:</p>
<pre>
<code class="language-bash">$CXX stm32mp1_uart_test.cpp -o uart_test -lpthread</code></pre>
<p>c++11的线程库底层使用了pthread实现,因此需要加链接参数<span style="color:#2ecc71;"><em>-lpthread</em></span></p>
<p>交叉编译完成后,使用scp命令将程序拷贝到开发板上执行:</p>
<pre>
<code class="language-bash">scp qldeng@192.168.1.131:/home/qldeng/stm32mp1_uart_test/uart_test ./</code></pre>
<p>复制到板子上的效果:<br />
</p>
<p>最后看看实际效果吧:</p>
<p></p>
</div><script> var loginstr = '<div class="locked">查看本帖全部内容,请<a href="javascript:;" style="color:#e60000" class="loginf">登录</a>或者<a href="https://bbs.eeworld.com.cn/member.php?mod=register_eeworld.php&action=wechat" style="color:#e60000" target="_blank">注册</a></div>';
if(parseInt(discuz_uid)==0){
(function($){
var postHeight = getTextHeight(400);
$(".showpostmsg").html($(".showpostmsg").html());
$(".showpostmsg").after(loginstr);
$(".showpostmsg").css({height:postHeight,overflow:"hidden"});
})(jQuery);
} </script><script type="text/javascript">(function(d,c){var a=d.createElement("script"),m=d.getElementsByTagName("script"),eewurl="//counter.eeworld.com.cn/pv/count/";a.src=eewurl+c;m.parentNode.insertBefore(a,m)})(document,523)</script> <p>串口读取等待休眠得驱动支持吧</p>
<p>感觉还不错,代码给的比较全,是例程吗?</p>
页:
[1]