基于华邦W90P710的嵌入式Linux串口驱动的实现方法
扫描二维码
随时随地手机看文章
摘 要: 基于华邦W90P710处理器的Linux内核应用,详细介绍了Linux串口驱动的实现方法。同时对Linux文件系统操作入口函数及内核的编译做了详细的说明。
关键词: ARM;Linux;UART;文件系统;串口驱动程序
嵌入式Linux是一种很受欢迎的操作系统,具有开放源码、不存在黑箱技术、内核小、功能强大、运行稳定、效率高、易于定制裁减等特点[1],广泛应用于工控产品。很多工控产品需要和外部设备进行信息交换,而串口通信是最简单快捷的实现方法。在不同的工控产品中,由于对所选用的串口元件或者串口通信的数据格式、波特率等有不同的需求,需要对串口驱动进行开发。华邦W90P710采用ARM的ARM7TDMI微处理器核心,采用?滋CLinux-2.4.20内核,支持4组通用异步接收发送口(UART),下面基于华邦W90P710的串口驱动详细分析串口驱动的实现方法,实现嵌入式设备通过串口对外通信。
1 华邦W90P710 UART介绍
华邦W90P710支持4组UART,串口的控制主要通过以下寄存器实现[2]:
(1)行寄存器(UART_LCR):设置数据位长度、奇偶校验、停止位数。
(2)波特率除数寄存器(UART_DLL、UART_DLM):波特率发生器的公式为:BaudOut=crystal clock/16×[Divisor +2],Divisor为当前波特率。
(3)Modem控制寄存器(UART_MCR):控制RTS、CTS等信号。
(4)FIFO控制寄存器(UART_FCR):设置FIFO的长度,复位FIFO等控制。
(5)接收超时寄存器(UART_TOR):收到首个字节后接收器启动本超时,之后每收到一个字节后都会重置该值,在此超时时间内不再收到数据时,接收器会产生一个接收中断。
(6)中断控制器(UART_IER):设置接收、发送、行中断等。
在使用RXDn、TXDn前必须对GPIO进行配置,使能RXDn、TXDn,串口才可正常运行。GPIO配置对应表如表1所示。
2 Linux系统驱动介绍
设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以像操作普通文件一样对硬件设备进行操作。同时,设备驱动程序是内核的一部分[3]。图1所示为设备驱动程序接口流程图。
Linux系统的设备分为字符设备、块设备和网络设备三种。字符设备是指存取时没有缓存的设备,只能顺序读写。典型的字符设备包括鼠标、键盘、串行口等;块设备一般都有缓存来支持,并且块设备必须能够支持随机存取。块设备主要包括硬盘设备、CD-ROM等;网络设备在Linux系统中用做专门的处理,Linux的网络系统主要是基于BSD Unix的socket机制[4]。
3 串口驱动程序详细介绍
一般来说,Linux的设备驱动程序包括驱动程序的注册和注销、设备的打开和释放、设备的读写操作、设备的控制操作、设备的中断和轮询处理等功能。下面就这些功能对串口驱动进行详细说明。
(1)串口设备的数据结构包括串口参数接收发送缓冲区等。串口参数包括波特率、数据位、数据起始位、奇偶校验、串口类型、发送缓冲区、接收缓冲区等,每个串口对应一个如下的数据结构:
typedef struct{
int bps;
int databits;
int stopbits;
int parity;
int siotype; //串口参数
int openflag;
int recvTrigTimeout;
SIO_D_SEND_BUFFER *pSendBuf;//发送缓冲区
SIO_D_RECV_BUFFER *pRecvBuf;//接收缓冲区
struct fasync_struct *fasync_queue;
wait_queue_head_t read_wait;
}serial_dev;
static serial_dev serial_device;
(2)文件系统操作入口函数对应文件操作函数read ()、write()、ioctl()、open()、close()。
struct file_operations serial_fops = {
owner: THIS_MODULE,
poll: serial_poll,
read: serial_read,
write: serial_write,
ioctl: serial_ioctl,
open: serial_open,
release: serial_release,
};
(3)驱动程序注册和注销。驱动程序在应用前,需要在模块初始化时将设备注册到系统设备表中;不再使用时,将设备从系统中卸除。注册包括初始化定时器、初始化串口数据结构serial_device和字符设备注册。注销时直接调用设备注销函数[5]。
int __init topbandserial1_init(void)
{
init_timer(&timer);//初始化定时器结构
memset(&serial_device, 0, sizeof(serial_device));
result=register_chrdev(SERIAL1_MAJOR, "serial1",
&serial_fops);
…
}
(4)串口设备打开包括分配串口的接收发送缓冲区及中断注册[5]。
static int serial_open(struct inode *inode, struct file *filp)
{
dev->pRecvBuf = kmalloc(sizeof(SIO_D_RECV_BUFFER), GFP_KERNEL);
request_irq(INT_UART1,serial_interrupt,SA_SHIRQ,
"TopbandSerial1",&serial_device);
…
}
(5)串口设备释放包括释放内存空间、注销中断和删除定时器[5]。
static int serial_release(struct inode *inode, struct file *flip)
{
serial_dev *dev = flip->private_data;//释放内存空间
kfree(dev->fasync_queue);
CSR_WRITE(COM_IER_1, 0x00); /* 中断禁止 */
free_irq(INT_UART1, dev); //注销中断
del_timer(&timer);//删除定时器
MOD_DEC_USE_COUNT;
dev->openflag = 0;
…
}
(6)串口读数据是指返回接收缓冲区中已收到的数据。读取数据有两种方式,阻塞方式和非阻塞方式。阻塞方式[6]中用户程序执行读操作时如果没有数据可读,即让read()操作等待直到数据可读;非阻塞方式中当用户执行读操作时,不论串口是否接收到数据,设备驱动xxx_read()函数会立刻返回,read()函数系统调用也随即返回。
static int serial_read(struct file *filp, char *buf, size_t
count, loff_t *f_pos)
{
if(filp->f_flags & O_NONBLOCK)/非阻塞方式读取
retsts = serial_nonblock_read(dev,buf,count);
else /*阻塞方式读取*/
retsts = serial_block_read(dev,buf,count);
…
}
(7)串口写数据包括把数据存放在发送缓冲区、启动硬件发送及发送中断。当发送第一个字节后,硬件会产生发送中断,剩下的数据将在中断处理程序中发送。
static int serial_write(struct file *filp, const char *buf,
size_t count, loff_t *f_pos)
{
copy_from_user(&pSendBuf->frameData[pSendBuf->
bufWritex].data[0],buf, count);
CSR_WRITE(CMBOARD_GPIO_DATAOUT1,status1);
enable_tx_interrupt_1();
…
}
(8)串口控制包括设置串口波特率、奇偶校、停止位等,还可以定义其他特殊的控制。应用程序通过ioctl()调用把串口的参数传递给驱动程序,驱动程序再通过对硬件串口控制寄存器进行设置,来满足应用层用户要求。
static int serial_ioctl(struct inode *inode, struct file *flip,
unsigned int cmd, unsigned long arg)
{
switch(cmd){
case SERIAL_IOC_BPS:
…
break;
case SERIAL_IOC_SENDBUF:
…
break;
}
}
(9)中断处理包括对接收中断、发送中断、异常中断的处理。读取中断寄存器的状态,根据不同的中断类型分别处理。当收到数据时,硬件会产生接收中断,驱动程序把串口的数据读取出来,放在接收缓冲区中,直到所有数据读取完成;当发送数据时,硬件会产生发送中断,驱动程序把发送缓冲区的数据发送出去,直到所有数据发送完成;当串口接收或发送发生异常时,会产生异常中断,驱动程序根据情况把串口重新初始化,以便串口恢复正常。
static void serial_interrupt(int irq, void * dev_id,
struct pt_regs *regs)
{
status = CSR_READ(COM_IIR_1);
while(status & UART_IIR_STATUS_NO) == 0)
{
switch(status)
{
case UART_IIR_STATUS_RDA:
case UART_IIR_STATUS_TOUT:
receive_chars(dev,status);
break;
case UART_IIR_THRE:
transmit_chars(dev);
break;
}
status = CSR_READ(COM_IIR_1);
}
}
(10)定时器处理。中断接收程序只负责把数据读取到缓冲区,并没有指示缓冲区的数据可被用户使用,这时需要在超时程序中把可用标志置上,当用户调用read()函数时就可把接收缓冲区的数据返回。
static void serial_timer(unsigned long dummy)
{
…
serial_device.pRecvBuf->frameData
[serial_device.pRecvBuf->bufWritex].finished = 1;
mod_timer(&timer,jiffies+2);/* 20 ms 进一次 */
}