在嵌入式Linux系統中,實現串口重定向可將標準輸入輸出(stdin/stdout)重映射到指定UART,實現遠程控制台、日志透傳、調試命令下發。本文解析基於 termios 的串口配置、雙線程雙向透傳、標準流重定向、退出序列檢測的完整實現代碼。
setOption()封裝 termios 結構體,支持波特率、數據位、校驗位、停止位、模式(規範/非規範)靈活配置:
int setOption(int fd, int nSpeed, int nBits, char mode, char nEvent, int nStop) {
struct termios newtio;
bzero(&newtio, sizeof(newtio));
newtio.c_cflag |= CLOCAL | CREAD; // 忽略調制解調器狀態、使能接收
newtio.c_cflag &= ~CSIZE;
// 數據位
switch(nBits) { case 7: newtio.c_cflag|=CS7; break; case 8: newtio.c_cflag|=CS8; break; }
// 模式:S=規範/回顯, N/M=非規範/無回顯/原始
switch(mode) {
case 'S': newtio.c_lflag|=ICANON|ECHO; newtio.c_cc[VMIN]=0; newtio.c_cc[VTIME]=0; break;
case 'N': case 'M': newtio.c_lflag&=~(ICANON|ECHO); newtio.c_lflag|=ECHOE;
newtio.c_oflag|=ONLCR|OPOST; newtio.c_cc[VMIN]=1; newtio.c_cc[VTIME]=0; break;
}
// 校驗位
switch(nEvent) {
case 'O': newtio.c_cflag|=PARENB|PARODD; newtio.c_iflag|=INPCK|ISTRIP; break;
case 'E': newtio.c_cflag|=PARENB; newtio.c_cflag&=~PARODD; newtio.c_iflag|=INPCK|ISTRIP; break;
case 'N': newtio.c_cflag&=~PARENB; break;
}
// 波特率
speed_t spd = B9600;
switch(nSpeed) { case 2400:spd=B2400;break; case 4800:spd=B4800;break; case 9600:spd=B9600;break; case 115200:spd=B115200;break; }
cfsetispeed(&newtio, spd); cfsetospeed(&newtio, spd);
// 停止位
if(nStop==1) newtio.c_cflag&=~CSTOPB; else if(nStop==2) newtio.c_cflag|=CSTOPB;
tcflush(fd, TCIFLUSH);
return tcsetattr(fd, TCSANOW, &newtio);
}
關鍵點: 非規範模式(VMIN=1, VTIME=0)下 read() 阻塞直到至少1字節,適合實時命令交互。
exitBmconsole()void exitBmconsole(int signum) {
tcflush(console_fd, TCIFLUSH); tcflush(uart3_fd, TCIFLUSH);
resetConsoleConfig(); // 還原控制台屬性
dup2(oldstdin, 0); // stdin 還原
dup2(oldstdout, 1); // stdout 還原
close(uart3_fd); close(console_fd);
muxClearAll(i2cfd_mux, MUX_SLAVE_ADDR); close(i2cfd_mux);
char muxName[5]; sprintf(muxName,"MUX%d",gMuxId); setInUseFlag(muxName,0);
exit(0);
}
保存原始 stdin/stdout 文件描述符(oldstdin/oldstdout),退出時用 dup2 還原,確保不影響父進程/其他終端。
MuxOutputToCom5()void *MuxOutputToCom5(void) {
setOption(uart3_fd, 115200, 8, 'N', 'N', 1);
char buff[MAX_BUFF_LEN+1];
while(1) {
memset(buff,0,sizeof(buff));
int nread = read(uart3_fd, buff, MAX_BUFF_LEN);
if(nread > 0) {
if(buff[0]==10 && buff[1]==10) // 雙換行符特殊處理
write(console_fd, &buff[1], nread);
else
write(console_fd, buff, nread);
}
}
}
雙換行符(0x0A 0x0A)作為特殊協議標記,去除首個換行符再轉發,可能用於幀同步。
Com5InputToMux()void *Com5InputToMux(void) {
freopen("/usr/std.in","w+",stdin); // 重定向 stdin 到文件/設備
freopen("/usr/std.out","w+",stdout); // 重定向 stdout
setOption(console_fd, 115200, 8, 'M', 'N', 1); // 非規範模式
char buff[MAX_BUFF_LEN+1], inputBuff[TEMP_ST_LEN+1];
int inputNumber=0, recievExitFlag=0;
while(1) {
memset(buff,0,sizeof(buff));
int nread = read(console_fd, buff, MAX_BUFF_LEN);
if(nread > 0) {
// 退出序列檢測:\q + 回車
if(recievExitFlag) {
if(!(nread==1 && ((inputNumber==1 && buff[0]!=LOWER_Q_ASCII) || (inputNumber==2 && buff[0]!=CR_ASCII)))) {
inputNumber=0; recievExitFlag=0; memset(inputBuff,0,sizeof(inputBuff));
}
}
if(nread == 1) {
if(buff[0] == CR_ASCII) { // 回車
if(inputNumber==2 && inputBuff[0]==QUIT_IDENTIFY_ASCII && inputBuff[1]==LOWER_Q_ASCII)
exitBmconsole(0); // 觸發退出
inputNumber=0; recievExitFlag=0; memset(inputBuff,0,sizeof(inputBuff));
}
if(inputNumber > MAX_BUFF_LEN) { inputNumber=0; memset(inputBuff,0,sizeof(inputBuff)); }
if(buff[0] == QUIT_IDENTIFY_ASCII) recievExitFlag=1; // 檢測到 \q
if(recievExitFlag) inputBuff[inputNumber++] = buff[0];
}
if(recievExitFlag != 1) write(uart3_fd, buff, nread); // 非退出序列字符透傳
}
}
}
退出協議: 輸入 \q (0x5C 0x71) 後跟回車 (0x0D) 觸發安全退出。狀態機 recievExitFlag + inputBuff 實現序列檢測,防止誤觸發。
MuxOutputToCom5 (UART→Console) + Com5InputToMux (Console→UART) 並行運行。freopen 將 stdin/stdout 重定向,使 printf/scanf 直接走串口。此代碼可直接移植到基於STM32/ARM Linux的網關、DTU、工控機項目,實現遠程串口調試、固件升級、AT命令透傳等功能。