笔者今天来讲讲Linux下UART的使用,串口的发送与接收。
来看看Beagle Bone的串口引脚设置。总共有5个串口,其中4个串口收发均可以使用,有一个串口3只可发不可收。
然后可以查看当前哪些串口设备可以使用。 (下图中串口1,2,4可以直接使用 即通过 打开文件,写和读操作)
ls -l /dev/ttyO*
打开串口设备:
Uart_fd=open(buf,O_RDWR | O_NOCTTY | O_NONBLOCK| O_NDELAY);
设置串口参数:波特率、8位数据等等,具体参数可以查询结构体 struct termios
newtio.c_cflag = BAUDRATE |CS8|CLOCAL|CREAD;
newtio.c_iflag = IGNPAR| ICRNL;
newtio.c_oflag = 0;
newtio.c_lflag=~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
newtio.c_cc[VTIME] = 0;
newtio.c_cc[VMIN] = 0;
typedef enum
{
BBIO_OK, // No error
BBIO_ACCESS, // Error accessing a file
BBIO_SYSFS, // Some error with Sysfs files
BBIO_CAPE, // some error with capes
BBIO_INVARG, // Invalid argument
BBIO_MEM,
BBIO_GEN // General error
} BBIO_err;
typedef struct
{
const char *name;
const char *path;
const char * dt;
const char *rx;
const char *tx;
} uart_t;
typedef struct
{
const char *name;
const char *path;
const char * dt;
const char *pin;
} adc_t;
//引脚设置
uart_t uart_table[] =
{
{ "UART1", "/dev/ttyO1","BB-UART1 ","P9_26", "P9_24"},
{ "UART2", "/dev/ttyO2","BB-UART2 ","P9_22", "P9_21"},
{ "UART3", "/dev/ttyO3","BB-UART3 ","P9_42", ""},
{ "UART4", "/dev/ttyO4","BB-UART4 ","P9_11", "P9_13"},
{ "UART5", "/dev/ttyO5","BB-UART5 ","P8_38", "P8_37"},
{ NULL, NULL, NULL, 0, 0 }
};
int UartInit(int uartindex)
{
struct termios newtio;
int Uart_fd;
printf("start...\r\n");
BBIO_err bt;
bt=uart_setup(uart_table[uartindex].dt);
printf("UART Tree load %d\r\n",bt);
set_pin_mode(uart_table[uartindex].rx,"uart");
set_pin_mode(uart_table[uartindex].tx,"uart");
char buf[30] = "/dev/ttyO";
char port_nr[2];
sprintf(port_nr, "%d", uartindex+1);
strcat(buf,port_nr);
Uart_fd=open(buf,O_RDWR | O_NOCTTY | O_NONBLOCK| O_NDELAY);
if(Uart_fd<0)
{
perror(uart_table[uartindex].name);
return -1;
}
printf("open ... baudrate %d\r\n",115200);
bzero(&newtio,sizeof(newtio));
newtio.c_cflag = BAUDRATE |CS8|CLOCAL|CREAD;
newtio.c_iflag = IGNPAR| ICRNL;
newtio.c_oflag = 0;
newtio.c_lflag=~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
newtio.c_cc[VTIME] = 0;
newtio.c_cc[VMIN] = 0;
tcflush(Uart_fd,TCIFLUSH);
tcsetattr(Uart_fd,TCSANOW,&newtio);
printf("reading...\n\r");
return Uart_fd;
}
BBIO_err load_device_tree(const char *name)
{
char line[256];
FILE *file = NULL;
char slots[41];
snprintf(ctrl_dir, sizeof(ctrl_dir), "/sys/devices/platform/bone_capemgr");
/* Check if the Linux kernel booted with u-boot overlays enabled.
If enabled, do not load overlays via slots file as the write
will hang due to kernel bug in cape manager driver. Just return
BBIO_OK in order to avoid cape manager bug. */
if(uboot_overlay_enabled()) {
return BBIO_OK;
}
if(pocketbeagle()) {
return BBIO_OK;
}
snprintf(slots, sizeof(slots), "%s/slots", ctrl_dir);
file = fopen(slots, "r+");
if (!file) {
return BBIO_CAPE;
}
while (fgets(line, sizeof(line), file))
{
//the device is already loaded, return 1
if (strstr(line, name)) {
fclose(file);
return BBIO_OK;
}
}
//if the device isn't already loaded, load it, and return
fprintf(file, "%s", name);
fclose(file);
//0.2 second delay
nanosleep((struct timespec[]){{0, 200000000}}, NULL);
return BBIO_OK;
}
int set_pin_mode(const char *key, const char *mode)
{
// char ocp_dir[] defined in common.h
char ocp_dir[50];
char path[60]; // "/sys/devices/platform/ocp/ocp:P#_##_pinmux/state"
char pinmux_dir[20]; // "ocp:P#_##_pinmux"
char pin[6]; //"P#_##"
FILE *f = NULL;
if (strlen(key) == 4) // Key P#_# format, must inject '0' to be P#_0#
snprintf(pin, sizeof(pin), "%.3s0%c", key,key[3]);
else //copy string
snprintf(pin, sizeof(pin), "%s", key);
strncpy(ocp_dir, "/sys/devices/platform/ocp", sizeof(ocp_dir));
snprintf(pinmux_dir, sizeof(pinmux_dir), "ocp:%s_pinmux", pin);
snprintf(path, sizeof(path), "%s/%s/state", ocp_dir, pinmux_dir);
f = fopen(path, "w");
if (NULL == f) {
return -1;
}
syslog(LOG_DEBUG, "Pinmux file %s access OK", path);
fprintf(f, "%s", mode);
fclose(f);
syslog(LOG_DEBUG, " set_pin_mode() :: Set pinmux mode to %s for %s", mode, pin);
return 0;
}
串口发送比较简单,直接调用write函数即可发送。
参数:串口设备文件句柄,发送缓存,发送的字节数。
write(Uart_fd,Buf,ByteNum);
串口接收比较复杂一点,需要调用一个函数select,来触发接收,Linux下面中断不太好用,所以需要调用这样一个函数。
int select(int fd_max, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
作用:监视一个或多个文件句柄的状态变化的,可阻塞也可不阻塞。
fd_max:传入的监视文件描述符集合中最大的文件描述符数值 + 1,因为select是从0开始一直遍历到数值最大的标识符。
readfds:文件描述符集合,检查该组文件描述符的可读性。
writefds:文件描述符集合,检查该组文件描述符的可写性。
exceptfds:文件描述符集合,检查该组文件描述符的异常条件。
struct timeval {
time_t tv_sec; /*秒 /
time_t tv_usec; /微秒/
};
int Uart_fd = UartInit(UART2);
int retval;
int RecByteNum=0;
fd_set rfds_Uart;
FD_ZERO(&rfds_Uart);
FD_SET(Uart_fd, &rfds_Uart);
//printf("--%d\n",rfds);
retval=select(Uart_fd + 1, &rfds_Uart, NULL, NULL, NULL);
//tv控制选择的时间,若规定时间内没有收到数据
//则不监控该rfds。则若为NULL,一直等到接收到数据
//再继续程序执行。
printf("--%d\n",rfds_Uart);
printf("--%d\n",retval);
了解这个函数之后,我们就可以调用这个函数监控串口设备是否有接收字符,如果存在字符,则调用read函数接收。
read函数参数:串口设备句柄,接收字符的地址,缓存区大小。
函数返回接收的字符数。
int ReadCom(int Uart_fd,char Readbuff[],fd_set rfds)
{
int index=0;
if(FD_ISSET(Uart_fd, &rfds))
{
int rc=0;
index=0;
do
{
usleep(2000);
rc=read(Uart_fd, &Readbuff[index], R_BUF_LEN-index);
index+=rc;
//printf("--%d\n",rc);
}
while(rc!=0 && (index<R_BUF_LEN));
}
return index;
}
最后笔者开启一个线程来接收串口数据并把接收到的数据发送出去。
pthread_t UsartId;
int UsartArg = 1;
int temp;
temp = MyThreadStart(pthread_UsartFun,&UsartId,&UsartArg);
if(temp == 0)
{
printf("UsartFun successfully!\n");
}
void *pthread_UsartFun(void *arg)
{
int Uart_fd = UartInit(UART2);
int retval;
int RecByteNum=0;
fd_set rfds_Uart;
FD_ZERO(&rfds_Uart);
FD_SET(Uart_fd, &rfds_Uart);
//printf("--%d\n",rfds);
retval=select(Uart_fd + 1, &rfds_Uart, NULL, NULL, NULL);
//tv控制选择的时间,若规定时间内没有收到数据
//则不监控该rfds。则若为NULL,一直等到接收到数据
//再继续程序执行。
printf("--%d\n",rfds_Uart);
printf("--%d\n",retval);
while(*(int*)arg)
{
memset(RXBuf,0x00,sizeof(RXBuf));
RecByteNum=ReadCom(Uart_fd,RXBuf,rfds_Uart);
if(RecByteNum>0)
{
printf("UART:%s--%d**%d\n",RXBuf,strlen(RXBuf),RecByteNum);
write(Uart_fd,RXBuf,RecByteNum);
memset(RXBuf,0x00,sizeof(RXBuf));
RecByteNum=0;
}
}
}
接收显示和串口助手打印分别如下。