筆者今天來講講Linux下UART的使用,串口的發送與接收。
來看看Beagle Bone的串口引腳設置。總共有5個串口,其中4個串口收發均可以使用,有一個串口3只可發不可收。
然後可以查看當前哪些串口設備可以使用。 (下圖中串口1,2,4可以直接使用 即通過 打開文件,寫和讀操作)
ls -l /dev/ttyO*
1. 串口初始化
打開串口設備:
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;
}
2. 串口發送
串口發送比較簡單,直接調用write函數即可發送。
參數:串口設備文件句柄,發送緩存,發送的字節數。
write(Uart_fd,Buf,ByteNum);
3. 串口接收
串口接收比較複雜一點,需要調用一個函數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;
}
}
}
接收顯示和串口助手打印分別如下。