《ARM Linux开发-warewin 2G/3G无线传输(DTU)和路由器—笔记》

1、串口定义

串行接口简称串口,也称串行通信接口(UART),是采用串行通信方式的扩展接口。

2、Linux下的使用

在Linux下操作、控制串口是通过操作设备文件进行的,可在/dev目录下看到串口设备文件,如ttyS0、ttyS1等。在应该程序中操作串口可进行以下步骤:

l 打开串口

int comfd;
comfd = open("/dev/ttyS2", O_RDWR | O_NOCTTY | O_NONBLOCK);

l 设置串口波特率

set_baud(comfd , 9600); //设置波特率为9600

set_baud()函数定义如下:

int baud_arr[] = { B38400, B19200, B9600, B4800, B2400, B1200, B300,
                    B38400, B19200, B9600, B4800, B2400, B1200, B300, };
int name_arr[] = {38400, 19200, 9600, 4800, 2400, 1200, 300, 38400, 
                                 19200, 9600, 4800, 2400, 1200, 300, };
void set_baud(int fd, int baud){
      int  i;
      int  status;
      struct termios  Opt;
      tcgetattr(fd, &Opt);
      for ( i= 0; i < sizeof(speed_arr) / sizeof(int); i++) {
             if (baud== name_arr[i]) {    
                    tcflush(fd, TCIOFLUSH);    
                    cfsetispeed(&Opt, baud_arr[i]); 
                    cfsetospeed(&Opt, baud_arr[i]);  
                    status = tcsetattr(fd, TCSANOW, &Opt); 
                    if (status != 0) {       
                           perror("tcsetattr fd"); 
                           return;    
                    }   
                    tcflush(fd,TCIOFLUSH);  
             } 
      }
}
l 设置串口信息
struct termios tio;
if ( tcgetattr( fd,&options) != 0) {
perror("SetupSerial 1");    
return 0; 
}
set_Parity(&tio,8,1,'N'); //设置数据位为8,停止位为1,无校验
set_Flag(&tio,10,0,0);//设置VTIME时间为10,VMIN大小为0,无流控
set_Parity函数如下:
void set_Parity(struct termios *tio,uint32_t databit,uint32_t stopbit,char parity)
{
tio->c_cflag &= ~CSIZE;
switch (databit) /*设置数据位数*/
{
case 7:
   tio->c_cflag |= CS7;
   break;
case 8:
   tio->c_cflag |= CS8;
   break;
default:
   fprintf(stderr,"Unsupported data size%d\n",databit);
   return;
}
switch (parity)
{
case 'n':
case 'N':
   tio->c_cflag &= ~PARENB;   /* Clear parity enable */
   tio->c_iflag &= ~INPCK;     /* Enable parity checking */
   break;
case 'o':
case 'O':
设置为奇效验*/ 
   tio->c_iflag |= INPCK;             /* Disnable parity checking */
   break;
case 'e':
case 'E':
   tio->c_cflag |= PARENB;     /* Enable parity */
转换为偶效验*/ 
   tio->c_iflag |= INPCK;       /* Disnable parity checking */
   break;
case 's':
case 'S': /*as no parity*/
   tio->c_cflag &= ~PARENB;
   tio->c_cflag &= ~CSTOPB;
   break;
default:
   fprintf(stderr,"Unsupported parity\n");
   return;
   }
/* 设置停止位*/   
switch (stopbit)
{
case 1:
   tio->c_cflag &= ~CSTOPB;
   break;
case 2:
   tio->c_cflag |= CSTOPB;
   break;
default:
   fprintf(stderr,"Unsupported stop bits\n");
   return;
}
}

set_Flag()函数如下:

void set_Flag(struct termios *tio,uint32_t timeout,uint32_t bufout,uint32_t fcon)
{
switch (fcon) /*设置数据流控*/
{
case 0:
   tio->c_cflag&=~CRTSCTS;
   break;
case 1:
   tio->c_cflag |= CRTSCTS;
   break;
default:
   fprintf(stderr,"Unsupported FlowControl\n");
   return;
}  
tio->c_lflag=0;
tio->c_cflag|=CREAD|CLOCAL;
tio->c_iflag|=IGNPAR;
tio->c_cc[VTIME]=timeout;
tio->c_cc[VMIN]=bufout;
}

注:函数中的VTIME指定了等待的时间,VMIN指定了读取字符的最小数量。它们不同组合地取值会得到不同的结果,分别如下:

(1)当VTIME>0,VMIN>0时。read调用将保持阻塞直到读取到第一个字符,读到了第一个字符之后开始计时,此后若时间到了VTIME或者时间未到但已读够了VMIN个字符则会返回;若在时间未到之前又读到了一个字符(但此时读到的总数仍不够VMIN)则计时重新开始。

(2)当VTIME>0,VMIN=0时。read调用读到数据则立即返回,否则将为每个字符最多等待VTIME时间。

(3) 当VTIME=0,VMIN>0时。read调用一直阻塞,直到读到VMIN个字符后立即返回。

(4)若在open或fcntl设置了O_NDELALY或O_NONBLOCK标志,read调用不会阻塞而是立即返回,那么VTIME和VMIN就没有意义,效果等同于与把VTIME和VMIN都设为了0。

l 使用串口读写数据:

char iobuf[100];
read(comfd, iobuf, 50);//读50个子节数据
write(comfd, iobuf, 50);//写50个子节数据
l 关闭串口
close(comfd);
3、注册多个串口
以内核版本为Linux-2.6.36、AT91SAM9260平台为例,内核默认注册了3个串口,一个终端调试串口ttyS0,两个232串口ttyS1、ttyS2,现在再注册ttyS3、ttyS4这两个串口,在内核源码/linux-2.6.36/arch/arm/mach-at91/board-sam9260ek.c中的__init ek_map_io()函数中添加以下红色字体:
static void __init ek_map_io(void)
{
/* Initialize processor: 18.432 MHz crystal */
at91sam9260_initialize(18432000);
/* DGBU on ttyS0. (Rx & Tx only) */
at91_register_uart(0, 0, 0);
/* USART0 on ttyS1. (Rx, Tx, CTS, RTS, DTR, DSR, DCD, RI) */
at91_register_uart(AT91SAM9260_ID_US0,1,ATMEL_UART_CTS| ATMEL_UART_RTS | ATMEL_UART_DTR | ATMEL_UART_DSR | ATMEL_UART_DCD | ATMEL_UART_RI);
/* USART1 on ttyS2. (Rx, Tx, RTS, CTS) */
at91_register_uart(AT91SAM9260_ID_US1,2,ATMEL_UART_CTS| ATMEL_UART_RTS);
/* USART2 on ttyS3. (Rx, Tx) */
at91_register_uart(AT91SAM9260_ID_US2, 3, 0);
/* USART3 on ttyS4. (Rx, Tx, RTS) */
at91_register_uart(AT91SAM9260_ID_US3, 4, ATMEL_UART_RTS);
/* set serial console to ttyS0 (ie, DBGU) */
at91_set_serial_console(0);
}

/dev目录下会看到新生成的ttyS3、ttyS4这两个设备节点,在应用程序中便可使用这两个串口。

4、设置485串口

ttyS4为485串口为例,在内核源码/linux-2.6.36/drivers/serial/atmel_serial.c的atmel_set_termios()函数中添加以下红色字体:
static void atmel_set_termios(struct uart_port *port, struct ktermios *termios,
      struct ktermios *old)
{
unsigned long flags;
unsigned int mode, imr, quot, baud;
/* Get current mode register */
mode = UART_GET_MR(port) & ~(ATMEL_US_USCLKS | ATMEL_US_CHRL
| ATMEL_US_NBSTOP | ATMEL_US_PAR);
if (port->line==4){
mode = UART_GET_MR(port) & ~(ATMEL_US_USMODE);
mode |= ATMEL_US_USMODE_RS485;
}
baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk / 16);
quot = uart_get_divisor(port, baud);
.........................................................................
.........................................................................
}

485串口传输数据中,测试发现有乱码的出现,需要把485串口DMA模式去掉,在/linux-2.6.36/arch/arm/mach-at91/at91sam9260_devices.c文件中把atmel_uart_data uart3_data结构中use_dma_tx、use_dma_rx的值由1改为0,另外添加以下红色字体代码、在发送数据时增加延时处理,如以下代码所示:

static struct atmel_uart_data uart3_data = {
.use_dma_tx = 0,
.use_dma_rx = 0,
//20120301add
.rs485= {
.flags = SER_RS485_ENABLED|SER_RS485_RTS_AFTER_SEND|SER_RS485_RTS_BEFORE_SEND,
.delay_rts_before_send=5,
.delay_rts_after_send= 5,
},
};