Linux简单设备驱动(3):UART驱动之在寄存器层面驱动开发及Android程序验证

前言

在上一篇《Linux简单设备驱动(2): file_operations的write、read、ioctl驱动及Android应用层开发验证》已经打通了驱动层和应用层数据读写的任督二脉,因此本文从这里开始对具体设备驱动进行开发。
还是要提一下,linux内核中已有相应的模块对UART进行开发,比如uart_driver结构体->tty_driver结构体,里面嵌套了一两层,本人对该这方面的源代码看得非常头疼也没找到有关uart寄存器的操作内容,只看到一个resource结构体,然后赋给他相关寄存器的物理地址什么的。。。
本人还有一个疑问,即便通过uart_driver什么的能够简化开发,那么我换一种处理器芯片呢?它的寄存器操作不一样,这样同样的结构代码也能够兼容这样的开发么?还没想明白。。。
对此,我们在这里暂时先摒弃这些什么uart_driver啊tty_driver啊,从寄存器层面直接驱动一个UART驱动先

硬件方面

本文依旧使用的是迅为电子的三星exynos4412处理器开发板。

实验效果

先说一下代码的效果把,下面是电脑界面上的串口调试助手以及开发板上的Android应用程序界面。这几个button就不说了,Android发送的数据在电脑中显示,反之依然。Android界面上最上面的两个EditView分别代表ioctl函数的cmd和arg参数,在这里cmd=0时发送“123”字符串至电脑中,并显示UFSTAT状态寄存器的值;cmd=1时只显示UFSTAT状态寄存器的值;其他值没有任何用处UART驱动串口助手界面
这里写图片描述

Linux驱动层开发

本文用得是exynos4412的uart3外设,要对这方面进行开发的话,有可能在开发板中已经有这个外设的驱动了,这样的话是无法再申请IO内存的。因此,在进行驱动开发的时候,必须在kernel代码中,将相应的UART驱动模块先不编译到内核中,每种内核版本不一样把,这里给出的是本人的开发板的kernel内核,用make menuconfig指令,将下图所示的模块设置为[M]。为什么是[M]而不是[ ]完全去掉呢?我也不懂,完全去掉的话,重新烧写内核后连Android系统都进不去,原因不详。。。这里写图片描述
这里要注意的是,这么设置的话串口的控制台就暂时用不了了,不要紧,要调试的话转移到cat /proc/kmsg中就行了。

模块加载

这里不多做介绍了,主要是kmalloc、注册杂项设备等等

GPIO引脚复用

#define UART3_TX EXYNOS4_GPA1(4)
#define UART3_RX EXYNOS4_GPA1(5)
/*自定义函数 UART3 GPIO引脚复用*/
static int uart_gpio_init(void)
{
int ret;
gpio_free(UART3_TX); gpio_free(UART3_RX);
ret=gpio_request(UART3_TX, DRIVER_NAME);ret=gpio_request(UART3_RX, DRIVER_NAME);
s3c_gpio_cfgpin(UART3_TX, S3C_GPIO_SFN(0x2)); s3c_gpio_cfgpin(UART3_RX, S3C_GPIO_SFN(0x2));
return ret;
}

UART初始化

先把代码放在这里,后面慢慢解释。

/*自定义函数 UART3 寄存器配置*/
static int uart_reg_init(void)
{
    int ret;
    unsigned int state=0;
    set_uart_clk();//查询uart3时钟
    //UART3寄存器向IO内存的映射                  if(request_mem_region(S5P_PA_UART3,UART3_PA_LEN,DRIVER_NAME)==NULL) {
    goto UART3_MEM_FAIL;
}
else{
    printk(KERN_EMERG "request_mem_region succeed!\n");
}
//UART3寄存器 IO内存的重映射
p=ioremap(S5P_PA_UART3,UART3_PA_LEN);
if(p==NULL){
    goto UART3_REMAP_FAIL;
}
else {      
    UART3=(UART_TypeDef *)p;
    printk(KERN_EMERG "ioremap succeed!\n");
}
/*开始配置UART3寄存器*/
//普通操作模式,校验位,1个停止位,8位数据
state=ioread32(&UART3->ULCON);
printk(KERN_EMERG "Previous: UART3->ULCON = %d\n",state);
iowrite32(0x03,&UART3->ULCON);
state=ioread32(&UART3->ULCON);
printk(KERN_EMERG "UART3->ULCON = %d\n",state);
//发送中断类型为Level,接收中断类型为Pulse,先不管接收异常
state=ioread32(&UART3->UCON);
printk(KERN_EMERG "Previous UART3->UCON = %d\n",state);
iowrite32(0x205|0x80|0x3000,&UART3->UCON);
state=ioread32(&UART3->UCON);
printk(KERN_EMERG "UART3->UCON = %d\n",state);
//FIFO模式,接收超过2字节时发生中断
iowrite32(0x01,&UART3->UFCON);
//固定波特率115200
iowrite32(53,&UART3->UBRDIV);
iowrite32(4,&UART3->UFRACVAL);

printk(KERN_EMERG "uart send message test:\n");
uart_send("debug",5);
printk(KERN_EMERG "uart send message test end!\n");
/*申请UART接收中断*/
ret = request_irq(IRQ_S3CUART_RX3, uart3_rx_interrupt, IRQF_DISABLED , "UART3_RXINT", NULL);//"UART3_RXINT"中断名称 可在cat /proc/interrupts 查询到
if (ret < 0) {
    printk(KERN_EMERG "Request IRQ1 %d failed, %d\n", IRQ_S3CUART_RX3, ret);
    return ret;
}
else{
    //只设置接收中断,其他屏蔽
    iowrite32(0x0e,&UART3->UINTM);
    printk(KERN_EMERG "Request IRQ_S3CUART_RX3 succeed!\n");
}
return ret;
UART3_REMAP_FAIL:
printk(KERN_EMERG "ioremap failed!\n");
release_mem_region(S5P_PA_UART3,UART3_PA_LEN);
UART3_MEM_FAIL:
printk(KERN_EMERG "request_mem_region failed!\n");
return ret;
}
1. ULCON寄存器

这个寄存器主要是配置停止位、校验位、数据位啊什么的,如下图所示,很简单。在这里配置为正常模式、无校验位、1位停止位,数据长度为8bit,即iowrite32(0x03,&UART3->ULCON)。如下图所示,这里就不翻译了,应该很容易理解。这里写图片描述

2.UCON寄存器

这个寄存器很多东西,但对本文有用的不算很多,比如说这个DMA(直接寄存器读取)还用不上,本人现在还不知道怎么用,能用的话当然对后面的数据读取速率有很大帮助,相信编过STM32单片机的童鞋都有体会,由于这里换了内核配上linux操作系统,本人还未来得及研究,因此这里遇到DMA的一律不管或者DISABLE就是。
下面结合以下几个从exynos4412芯片文档,对该寄存器做简要说明。前面DMA的不管,默认就行。
[15:12]Rx接收超时中断,采用默认值3,意思就是超过8*(3+1)的frame时间,若再没有数据进来就进入接收中断,以便将Rx FIFO的剩余数据全部读出来。
[11]Rx FIFO内容为空的时候进入接收中断,个人认为好像没什么用,disable。
[9:8]Tx发送/Rx接收中断模式,我也是看别人的配置的,实在看不出这两种有什么区别。。。。
[7]Rx超时中断,enable,用于上述[15:12]的用途。
[6]接收错误中断,Disable
[5]这个是在硬件端将Tx引脚与Rx引脚自己接在一起,用于测试的,置0.
[4]一般用不上,我也不知道这拿来干嘛。。。置0.
[3:0]发送/接收模式,选择01,中断要求或轮询模式。
iowrite32(0x205|0x80|0x3000,&UART3->UCON)
这里写图片描述这里写图片描述这里写图片描述

3.UFCON寄存器

这个寄存器也很简单,主要用于设置FIFO模式,和接收/发送多少数据进入中断的。本文使用的UART3,因此这里看channel 3就是了。很简单,FIFO模式,接收超过2字节时发生中断,这里不一一说明了。要注意的是,UART3的FIFO只有16byte缓存大小,至少要接收2字节的数据才能进入接收中断。对于后者,结合上面的[15:12]Rx接收超时中断,就能把Rx FIFO剩余的数据全部给读出来。
iowrite32(0x01,&UART3->UFCON)。
这里写图片描述这里写图片描述

4.波特率设置寄存器UBRDIV和UFRACVAL

举个例子就很清楚了:
由于本人对这个板子的时钟配置很不了解,与相应时钟寄存器有关,也和外部晶振有关,因此连蒙带猜,准确猜出了UART3的SCLK_UART=100MHz。
若我们要设置波特率为115200,要这么计算:
DIV_VAL = (SCLK_UART/(bps * 16)) - 1
DIV_VAL = (100000000/(115200 * 16)) – 1=53.25347222222……
取整数部分UBRDIV=53
小数部分UFRACVAL=0.253472222…..*16=4.05去整数部分4
因此有iowrite32(53,&UART3->UBRDIV);iowrite32(4,&UART3->UFRACVAL);
有点误差,但这是在波特率误差所允许的范围。

5.申请UART3接收中断

中断这个环节,在单片机上很容易理解,但弄到linux上,什么中断线的不是很了解,暂时搁置这个,先调通再说。
在mach/map-exynos4.h这个文件中,我们可以看到UART3的接收中断线为IRQ_S3CUART_RX3,因此有下面代码:其中IRQF_DISABLED为快速中断模式,禁止其他中断的嵌套(这里也没有其他中断。。)

/*申请UART接收中断*/
ret = request_irq(IRQ_S3CUART_RX3, uart3_rx_interrupt, IRQF_DISABLED , "UART3_RXINT", NULL);//"UART3_RXINT"中断名称 可在cat /proc/interrupts 查询到
if (ret < 0) {
    printk(KERN_EMERG "Request IRQ1 %d failed, %d\n", IRQ_S3CUART_RX3, ret);
    return ret;
}
else{
    //只设置接收中断,其他屏蔽
    iowrite32(0x0e,&UART3->UINTM);
    printk(KERN_EMERG "Request IRQ_S3CUART_RX3 succeed!\n");
}

这里涉及到UART的UINTM寄存器,主要是用来屏蔽不相关的中断的,如下图所示。这里只用上了接收中断,其他一律屏蔽。
这里写图片描述
下面是接收中断服务函数:

static irqreturn_t uart3_rx_interrupt(int irq, void *dev_id) {
    //printk(KERN_EMERG "uart3 rx interrupt!\n");
    tasklet_schedule(&mtasklet);//接收数据放在tasklet中处理
    return IRQ_HANDLED;
}

很简单,这个是上层中断处理函数,然后就调用下层中断处理函数:

struct tasklet_struct mtasklet;
/* 初始化tasklet */
void tasklet_func(unsigned long arg);
DECLARE_TASKLET(mtasklet,tasklet_func,0);

/* 中断下层处理函数 */
void tasklet_func(unsigned long arg)
{
int len,i;
unsigned int state;
write_lock_irq(&myuart_dev->rwlock);
do{
    state=ioread32(&UART3->UFSTAT)&0x1ff;//读取FIFO中有多少byte要接收,最多16byte
    if(state>0xff){//FIFO接收区已满
        len=UART3_FIFO_MAX;
    }
    else{
        len=state&0xff;
    }
    for(i=0;(i<len)&&((myuart_dev->index)<(myuart_dev->size));i++){
        myuart_dev->data[myuart_dev->index]=ioread32(&UART3->URXH)&0xff;
        myuart_dev->index++;
    }
    if((myuart_dev->index)>=(myuart_dev->size)){//缓存已装满,为放防止溢出异常,直接先空读把
        state=ioread32(&UART3->URXH)&0xff;
    }
}while(state!=0);
write_unlock_irq(&myuart_dev->rwlock);
}

一开始too young了一回,将DECLARE_TASKLET(mtasklet,tasklet_func,0);这个语句放在初始化语句里面进行初始化,后面才知道这个不是函数,好像是个宏定义把,要放在外面定义。
读取Rx FIFO的内容,用到了读写自旋锁,以及UFSTAT寄存器判断有多少数据可以接收,寄存器说明如下图,很简单,不翻译了。
这里写图片描述
上面的接收数据很简单,主要是判断用于接收数据的缓存是否已满,已满的话就直接空读取吧,为了防止溢出错误,若发送溢出错误的话好像要清理相应的标志位,麻烦,这里就直接这么防止溢出就行了。

驱动层与应用层的交互file_oprations

直接贴代码了,大家直接看代码注释即可。这里ioctl用于在用户层看到UFSTAT寄存器的内容,用于调试。write是一个阻滞函数,在Android应用中最好不要再UI线程中调用。read函数将驱动层缓存内容赋给应用层,然后清空相应的数据。

/* 自定义函数 UART发送 */
static void uart_send(char *buf,int len)
{
int i;
for(i=0;i<len;i++){
    iowrite32(buf[i],&UART3->UTXH);
}
}

static long myuart_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
{
long ret;
if((cmd&0xff)==0){
    uart_send("123",3);
    ret=ioread32(&UART3->UFSTAT);
}
else if((cmd&0xff)==1){//读取UFSTAT
    ret=ioread32(&UART3->UFSTAT);
}
else{
    ret=1;
}
printk(KERN_EMERG "ioctl: cmd=%d, arg=%d\n, ret=%d",cmd,arg,ret);
return ret;
}

static int myuart_open(struct inode *node, struct file *f)
{
int ret=0;
uart_send("open\n",5);
printk(KERN_EMERG "myuart_open!");
return ret;
}

static ssize_t myuart_write(struct file *f, const char __user *buf, size_t len, loff_t *loff)
{
int ret,i=0;
unsigned int state;
char sendBuf[len];
ret=copy_from_user(sendBuf,buf,len);
if(ret!=0){
    return -1;
}
while(i<len){
    state=(ioread32(&UART3->UFSTAT)>>16)&0x1ff;//读取FIFO中还有多少byte要发送,最多16byte
    if(state<0x0ff){//FIFO没有满
        iowrite32(buf[i],&UART3->UTXH);
        i++;
    }
}
do{//等待全部数据发送完毕
    state=(ioread32(&UART3->UFSTAT)>>16)&0x1ff;//读取FIFO中还有多少byte要发送,最多16byte
}while(state!=0);
return len;
}

/* 需要返回读取了多长的数据,读取失败返回-1 */
static ssize_t myuart_read(struct file *f, char __user *buf, size_t len, loff_t *loff)
{
int ret=0,i=0,temp;
read_lock_irq(&myuart_dev->rwlock);//读取锁
if(myuart_dev->index<=0){//没有数据直接返回
    goto nodata;
}
if((myuart_dev->index)<=len){
    ret=copy_to_user(buf,myuart_dev->data,myuart_dev->index);
    if(ret!=0){//读取失败
        goto nodata;
    }
    else{
        ret=myuart_dev->index;//要返回的数据长度
        myuart_dev->index=0;
    }
}
else{//index>=len
    ret=copy_to_user(buf,myuart_dev->data,len);
    if(ret!=0){//读取失败
        goto nodata;
    }
    else{
        ret=len;//要返回的数据长度
        myuart_dev->index-=len;
    }
}
read_unlock_irq(&myuart_dev->rwlock);//解锁
//==============================================================================
return ret;
nodata:
read_unlock_irq(&myuart_dev->rwlock);//解锁
return -1;
}

static int myuart_release(struct inode *node, struct file *f)
{
int ret=0;
printk(KERN_EMERG "myuart_close!");
return ret;
}

static struct file_operations myuart_fops = {
.owner          = THIS_MODULE,
.open           = myuart_open,
.unlocked_ioctl = myuart_ioctl,
.write          = myuart_write,
.read           = myuart_read,
.release        = myuart_release,
};

好了,驱动层的东西就这么多,下面进入Android开发!!!!

Android应用层开发

JNI层

JNI层的一些分析,已经在上一篇Linux简单设备驱动(2)中说过了,这里只是补充一点东西。
读取函数中,jbyte *pBuffer=env->GetByteArrayElements(buffer,NULL);这一句应该是开辟了一段pBuffer存储空间,因此在函数返回直线,把该指针的内存释放一下。

JNIEXPORT jint JNICALL Java_ling_tarkelc_linux_Uart_read(JNIEnv *env, jobject obj, jbyteArray buffer, jint len)
{
    int ret=0;
    jbyte *pBuffer=env->GetByteArrayElements(buffer,NULL);
    ret=read(fd,pBuffer,len);
    env->ReleaseByteArrayElements(buffer,pBuffer,0);//复制回内容,释放pBuffer空间
    return ret;
}

而在write中,定义数组实在栈中,由编译器自己去释放吧:

JNIEXPORT jint JNICALL Java_ling_tarkelc_linux_Uart_write(JNIEnv *env, jobject obj, jbyteArray data, jint len)
{
    int ret=0;
    jbyte pBuffer[len];
    env->GetByteArrayRegion(data,0,len,pBuffer);
    ret=write(fd,pBuffer,len);
    return ret;

Android程序java

读取数据线程,每隔0.5秒读取一次数据,本人用线程习惯了,大家也可以在Timer加TimerTask中使用。若能读取数据,则经过handler交给UI线程进行显示。

public class ReadThread extends Thread{

    private boolean flagSwitch;
    private int max;
    private byte[] buf,msgBuf;

    public ReadThread(int max) {
        flagSwitch=true;
        this.max=max;
        buf=new byte[max];
    }

    @Override
    public void run() {
        int ret;
        while(flagSwitch){
            ret=uart.read(buf, max);
            if(ret>0){
                msgBuf=new byte[ret];
                System.arraycopy(buf, 0, msgBuf, 0, ret);
                Message msg=new Message();
                Bundle bundle=new Bundle();
                bundle.putByteArray("readBuf", msgBuf.clone());
                bundle.putInt("readLength", ret);
                msg.setData(bundle);
                msg.what=Constant.GET_READ_MESSAGE;
                handler.sendMessage(msg);
            }
            /*每隔0.5秒读一次数据*/
            try {
                sleep(500);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

    public void close(){
        flagSwitch=false;
    }

}

写入函数直接在一个写入Button空间的点击回调函数中写就行了。

writeBtn.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            final String text=writeText.getText().toString();
            if(text.equals("")){
                consoleText.append("请先输入所要发送的数据\n");
                return;
            }
            writeBtn.setEnabled(false);
            new Thread(){
                public void run() {
                    writeRet=uart.write(text.getBytes(), text.length());
                        handler.sendEmptyMessage(Constant.WRITE_FINISHED);
                }
            }.start();
        }
    });

结语

至此,UART在寄存器层面的任督二脉已成功打开,具体代码已经上传,还在审核,后面再把源代码连接放在这,欢迎大家下载参考吧。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值