页面加载中,请稍候
来源:嵌入式Linux系统开发发布时间:2023-07-013658浏览
询问 AI本文写应用程序,与 Linux 自带的 USB Gadget 驱动 serial 通信。
/drivers/usb/gadget/legacy/serial.c
参考资料:
韦东山老师驱动大全
*https://blog.csdn.net/embededswordman/article/details/6689593
* Linux 文档:Documentation\usb\gadget_serial.txt
*UART教程
*回顾TTY的概念
*回顾console的概念
使用 USB 线,连接板子的 OTG 口和 PC 的 USB 口。
然后在板子加载驱动程序后,可以看到新的设备节点 /dev/ttyGS0:
#modprobeg_serial
g_serialgadget:GadgetSerialv2.4
g_serialgadget:g_serialready
g_serialgadget:high-speedconfig#2:CDCACMconfig
#ls/dev/ttyGS0-l
crw-rw----1rootdialout246,0Jan100:30/dev/ttyGS0
在 PC 上,如果是 Windows 系统,可以在设备管理器里看到新的 USB 串口:

在 PC 上,如果是 VMware 上的 Linux 系统,按下图操作,先把 USB 串口连接到 VMware:

然后在 PC Linux 中可以看到新的设备节点:
book@100ask:~$dmesg
[286.903239]usb1-1:newhigh-speedUSBdevicenumber2usingehci-pci
[287.254549]usb1-1:NewUSBdevicefound,idVendor=0525,idProduct=a4a7,bcdDevice=4.09
[287.254550]usb1-1:NewUSBdevicestrings:Mfr=1,Product=2,SerialNumber=0
[287.254551]usb1-1:Product:GadgetSerialv2.4
[287.254552]usb1-1:Manufacturer:Linux4.9.88with2184000.usb
[287.342786]cdc_acm1-1:2.0:ttyACM0:USBACMdevice
[287.343202]usbcore:registerednewinterfacedrivercdc_acm
[287.343202]cdc_acm:USBAbstractControlModeldriverforUSBmodemsandISDNadapters
book@100ask:~$ls/dev/ttyACM0-l
crw-rw----1rootdialout166,0Mar522:38/dev/ttyACM0
Gadget 串口的框架如下:

u_serial 提供了有 2 种方法来使用 Gadget 串口:


注册 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,对于板子来说就是输入)
structusb_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_write2.2.2 printk
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<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<errno.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<termios.h>
#include<stdlib.h>
#include<pthread.h>
staticstructtermiosold,current;
/*Initializenewterminali/osettings*/
voidinitTermios(intecho)
{
tcgetattr(0,old);/*graboldterminali/osettings*/
current=old;/*makenewsettingssameasoldsettings*/
current.c_lflag=~ICANON;/*disablebufferedi/o*/
if(echo){
current.c_lflag|=ECHO;/*setechomode*/
}else{
current.c_lflag=~ECHO;/*setnoechomode*/
}
tcsetattr(0,TCSANOW,current);/*usethesenewterminali/osettingsnow*/
}
/*Restoreoldterminali/osettings*/
voidresetTermios(void)
{
tcsetattr(0,TCSANOW,old);
}
/*set_opt(fd,115200,8,'N',1)*/
intset_opt(intfd,intnSpeed,intnBits,charnEvent,intnStop)
{
structtermiosnewtio,oldtio;
if(tcgetattr(fd,oldtio)!=0){
perror("SetupSerial1");
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)
{
case7:
newtio.c_cflag|=CS7;
break;
case8:
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)
{
case2400:
cfsetispeed(newtio,B2400);
cfsetospeed(newtio,B2400);
break;
case4800:
cfsetispeed(newtio,B4800);
cfsetospeed(newtio,B4800);
break;
case9600:
cfsetispeed(newtio,B9600);
cfsetospeed(newtio,B9600);
break;
case115200:
cfsetispeed(newtio,B115200);
cfsetospeed(newtio,B115200);
break;
default:
cfsetispeed(newtio,B9600);
cfsetospeed(newtio,B9600);
break;
}
if(nStop==1)
newtio.c_cflag=~CSTOPB;
elseif(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("comseterror");
return-1;
}
//printf("setdone!\n");
return0;
}
intopen_port(char*com)
{
intfd;
//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("fcntlfailed!\n");
return-1;
}
returnfd;
}
staticvoid*my_read_thread_func(void*data)
{
intfd=(int)data;
intiRet;
charc;
while(1)
{
iRet=read(fd,c,1);
printf("%c",c);
fflush(stdout);
}
}
/*
*./serial_send_recv<dev>
*/
intmain(intargc,char**argv)
{
intfd;
intiRet;
charc;
pthread_ttid;
/*1.open*/
/*2.setup
*115200,8N1
*RAWmode
*returndataimmediately
*/
/*3.writeandread*/
if(argc!=2)
{
printf("Usage:\n");
printf("%s</dev/ttySAC1orother>\n",argv[0]);
return-1;
}
fd=open_port(argv[1]);
if(fd<0)
{
printf("open%serr!\n",argv[1]);
return-1;
}
iRet=set_opt(fd,115200,8,'N',1);
if(iRet)
{
printf("setporterr!\n");
return-1;
}
/*创建一个读线程*/
iRet=pthread_create(tid,NULL,my_read_thread_func,(void*)fd);
if(iRet)
{
printf("pthread_createerr!\n");
return-1;
}
printf("Enterachar:");
initTermios(1);
//写线程
while(1)
{
c=getchar();
iRet=write(fd,c,1);
if(iRet!=1)
printf("cannotwritedata\n");
}
resetTermios();
return0;
}
编译 2 个版本,有两条命令:PC、ARM
gcc-oserial_send_recv_pcserial_send_recv.c-lpthread
arm-buildroot-linux-gnueabihf-gcc-oserial_send_recv_armserial_send_recv.c-lpthread
使用 USB 线连接板子的 OTG 口、PC 的 USB 口,PC 上监测到 USB 串口后把它连接到 VMWare,确定:
测试:
新闻来源:嵌入式Linux系统开发,文中所述为作者独立观点,不代表icspec立场。更多精彩资讯请下载icspec App。如对本稿件有异议,请联系微信客服specltkj。
暂无评论哦,快来评论一下吧!
2026-07-22
2026-06-08

2026-07-22