使用USB线连接板子的OTG口和PC的USB口。
然后在板子加载驱动程序后,可以看到新的设备节点/dev/ttyGS0:
# modprobe g_serial
g_serial gadget: Gadget Serial v2.4
g_serial gadget: g_serial ready
g_serial gadget: high-speed config #2: CDC ACM config
# ls /dev/ttyGS0 -l
crw-rw---- 1 root dialout 246, 0 Jan 1 00:30 /dev/ttyGS0
在PC上,如果是Windows系统,可以在设备管理器里看到新的USB串口:
在PC上,如果是VMware上的Linux系统,按下图操作,先把USB串口连接到VMware:
然后在PC Linux中可以看到新的设备节点:
[email protected]:~$ dmesg
[ 286.903239] usb 1-1: new high-speed USB device number 2 using ehci-pci
[ 287.254549] usb 1-1: New USB device found, idVendor=0525, idProduct=a4a7, bcdDevice= 4.09
[ 287.254550] usb 1-1: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[ 287.254551] usb 1-1: Product: Gadget Serial v2.4
[ 287.254552] usb 1-1: Manufacturer: Linux 4.9.88 with 2184000.usb
[ 287.342786] cdc_acm 1-1:2.0: ttyACM0: USB ACM device
[ 287.343202] usbcore: registered new interface driver cdc_acm
[ 287.343202] cdc_acm: USB Abstract Control Model driver for USB modems and ISDN adapters
[email protected]:~$ ls /dev/ttyACM0 -l
crw-rw---- 1 root dialout 166, 0 Mar 5 22:38 /dev/ttyACM0
Gadget串口的框架如下:
u_serial提供了有2种方法来使用Gadget串口:
u_serial.c里注册console结构体gserial_cons。启动Linux内核时传入commandline参数"console=ttyGS0"后,内核的printk的信息通过Gadget串口打印出来(Host要打开USB串口):
注册TTY和console的过程:
gs_bind // drivers\usb\gadget\legacy\serial.c
status = serial_register_ports(cdev, &serial_config_driver,
"acm");
fi_serial[i] = usb_get_function_instance(f_name);
acm_alloc_instance // drivers\usb\gadget\function\f_acm.c
ret = gserial_alloc_line(&opts->port_num); // drivers\usb\gadget\function\u_serial.c
// 注册TTY
tty_dev = tty_port_register_device(&ports[port_num].port->port,
gs_tty_driver, port_num, NULL);
// 注册console
gserial_console_init();
register_console(&gserial_cons);
注意,在USB中数据传输总是由Host发起,所以:
板子上的APP访问/dev/ttyGS0时,就会导致gs_tty_ops结构体的对应函数被调用:
APP调用open函数时,会导致如下调用:
gs_open
gs_start_io(port);
// 取出out端点(对应Host来说是out, 对于板子来说就是输入)
struct usb_ep *ep = port->port_usb->out;
// 给out端点分配usb_request
status = gs_alloc_requests(ep, head, gs_read_complete,
&port->read_allocated);
// 给in端点分配usb_request, 但是在open时并没有把in方向的usb_request放入队列
status = gs_alloc_requests(port->port_usb->in, &port->write_pool,
gs_write_complete, &port->write_allocated);
// 把usb_request放入队列, 如果Host发来数据, 这个usb_request的complete函数被调用
started = gs_start_rx(port);
status = usb_ep_queue(out, req, GFP_ATOMIC);
APP调用write函数时,会导致如下调用:
gs_write
gs_start_tx(port);
// 把usb_request放入队列, Host读取数据时就可以从中得到数据
status = usb_ep_queue(in, req, GFP_ATOMIC);
启动Linux内核时传入commandline参数"console=ttyGS0"后,内核的printk的信息通过Gadget串口打印出来(Host要打开USB串口)。
内核的printk函数会导致gserial_cons结构体中的write指针即gs_console_write
函数被调用:
gs_console_write函数的调用关系如下:
gs_console_write
// 把要打印的数据放入环形buffer
gs_buf_put(&info->con_buf, buf, count);
// 唤醒内核线程
wake_up_process(info->console_thread);
// 内核线程
gs_console_thread
// 被唤醒后
// 取出输入端点和它的usb_request
req = info->console_req;
ep = port->port_usb->in;
// 从环形buffer得到数据、设置usb_request
xfer = gs_buf_get(&info->con_buf, req->buf, size);
req->length = xfer;
// 把usb_request放入队列,以便Host读取
ret = usb_ep_queue(ep, req, GFP_ATOMIC);
PC: open/read/write /dev/ttyACM0
板子: open/read/write /dev/ttyGS0
参考资料:https://stackoverflow.com/questions/7469139/what-is-the-equivalent-to-getch-getche-in-linux
源码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static struct termios old, current;
/* Initialize new terminal i/o settings */
void initTermios(int echo)
{
tcgetattr(0, &old); /* grab old terminal i/o settings */
current = old; /* make new settings same as old settings */
current.c_lflag &= ~ICANON; /* disable buffered i/o */
if (echo) {
current.c_lflag |= ECHO; /* set echo mode */
} else {
current.c_lflag &= ~ECHO; /* set no echo mode */
}
tcsetattr(0, TCSANOW, ¤t); /* use these new terminal i/o settings now */
}
/* Restore old terminal i/o settings */
void resetTermios(void)
{
tcsetattr(0, TCSANOW, &old);
}
/* set_opt(fd,115200,8,'N',1) */
int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop)
{
struct termios newtio,oldtio;
if ( tcgetattr( fd,&oldtio) != 0) {
perror("SetupSerial 1");
return -1;
}
bzero( &newtio, sizeof( newtio ) );
newtio.c_cflag |= CLOCAL | CREAD;
newtio.c_cflag &= ~CSIZE;
newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); /*Input*/
newtio.c_oflag &= ~OPOST; /*Output*/
switch( nBits )
{
case 7:
newtio.c_cflag |= CS7;
break;
case 8:
newtio.c_cflag |= CS8;
break;
}
switch( nEvent )
{
case 'O':
newtio.c_cflag |= PARENB;
newtio.c_cflag |= PARODD;
newtio.c_iflag |= (INPCK | ISTRIP);
break;
case 'E':
newtio.c_iflag |= (INPCK | ISTRIP);
newtio.c_cflag |= PARENB;
newtio.c_cflag &= ~PARODD;
break;
case 'N':
newtio.c_cflag &= ~PARENB;
break;
}
switch( nSpeed )
{
case 2400:
cfsetispeed(&newtio, B2400);
cfsetospeed(&newtio, B2400);
break;
case 4800:
cfsetispeed(&newtio, B4800);
cfsetospeed(&newtio, B4800);
break;
case 9600:
cfsetispeed(&newtio, B9600);
cfsetospeed(&newtio, B9600);
break;
case 115200:
cfsetispeed(&newtio, B115200);
cfsetospeed(&newtio, B115200);
break;
default:
cfsetispeed(&newtio, B9600);
cfsetospeed(&newtio, B9600);
break;
}
if( nStop == 1 )
newtio.c_cflag &= ~CSTOPB;
else if ( nStop == 2 )
newtio.c_cflag |= CSTOPB;
newtio.c_cc[VMIN] = 1; /* 读数据时的最小字节数: 没读到这些数据我就不返回! */
newtio.c_cc[VTIME] = 0; /* 等待第1个数据的时间:
* 比如VMIN设为10表示至少读到10个数据才返回,
* 但是没有数据总不能一直等吧? 可以设置VTIME(单位是10秒)
* 假设VTIME=1,表示:
* 10秒内一个数据都没有的话就返回
* 如果10秒内至少读到了1个字节,那就继续等待,完全读到VMIN个数据再返回
*/
tcflush(fd,TCIFLUSH);
if((tcsetattr(fd,TCSANOW,&newtio))!=0)
{
perror("com set error");
return -1;
}
//printf("set done!\n");
return 0;
}
int open_port(char *com)
{
int fd;
//fd = open(com, O_RDWR|O_NOCTTY|O_NDELAY);
fd = open(com, O_RDWR|O_NOCTTY);
if (-1 == fd){
return(-1);
}
if(fcntl(fd, F_SETFL, 0)<0) /* 设置串口为阻塞状态*/
{
printf("fcntl failed!\n");
return -1;
}
return fd;
}
static void *my_read_thread_func (void *data)
{
int fd = (int)data;
int iRet;
char c;
while (1)
{
iRet = read(fd, &c, 1);
printf("%c", c);
fflush(stdout);
}
}
/*
* ./serial_send_recv
*/
int main(int argc, char **argv)
{
int fd;
int iRet;
char c;
pthread_t tid;
/* 1. open */
/* 2. setup
* 115200,8N1
* RAW mode
* return data immediately
*/
/* 3. write and read */
if (argc != 2)
{
printf("Usage: \n");
printf("%s \n", argv[0]);
return -1;
}
fd = open_port(argv[1]);
if (fd < 0)
{
printf("open %s err!\n", argv[1]);
return -1;
}
iRet = set_opt(fd, 115200, 8, 'N', 1);
if (iRet)
{
printf("set port err!\n");
return -1;
}
/* 创建一个读线程 */
iRet = pthread_create(&tid, NULL, my_read_thread_func, (void *)fd);
if (iRet)
{
printf("pthread_create err!\n");
return -1;
}
printf("Enter a char: ");
initTermios(1);
// 写线程
while (1)
{
c = getchar();
iRet = write(fd, &c, 1);
if (iRet != 1)
printf("can not write data\n");
}
resetTermios();
return 0;
}
编译2个版本:PC、ARM
gcc -o serial_send_recv_pc serial_send_recv.c -lpthread
arm-buildroot-linux-gnueabihf-gcc -o serial_send_recv_arm serial_send_recv.c -lpthread
使用USB线连接板子的OTG口、PC的USB口,PC上监测到USB串口后把它连接到VMWare,确定:
测试:
sudo ./serial_send_recv_pc /dev/ttyACM0
sudo ./serial_send_recv_arm /dev/ttyGS0
PC上监测到USB串口后把它连接到VMWare,确定:
测试:
sudo ./serial_send_recv_pc /dev/ttyACM0
sudo ./serial_send_recv_arm /dev/ttyGS0
以上笔记源自
韦东山
老师的视频课程,感谢韦老师,韦老师是嵌入式培训界一股清流,为嵌入式linux开发点起的星星之火,也愿韦老师桃李满园。聚是一团火,散是满天星!
在这样一个速食的时代,坚持做自己,慢下来,潜心琢磨,心怀敬畏,领悟知识,才能向下扎到根,向上捅破天,背着世界往前行!
仅此向嵌入行业里的每一个认真做技术的从业者致敬!