韦东山嵌入式Linux应用开发实验班-学习笔记(三)——使用文件IO操作硬件(驱动程序方式)

写在前面:本系列文章用于记录个人学习过程中的心得体会,可能存在理解偏差或不到位的情况,欢迎指出,互相交流,共同进步!

        在上一节(指路:学习笔记(二)--使用文件IO操作硬件(SYSFS方式))编写了一个使用SYSFS系统访问GPIO从而点灯的应用程序,可以在没有驱动程序的情况下访问硬件,但更为常用的方式是编写驱动程序,提供接口给应用层,在应用层通过Linux系统的文件IO操作,调用驱动层从而访问硬件,本节就是使用驱动程序接口(在实际开发中由驱动开发人员提供)编写一个应用程序,实现在触摸屏上点击按钮实现开关灯操作。

1.了解驱动程序接口并编写应用程序

        首先需要了解应用层和驱动层的各个函数调用关系,如下图(截取自《韦东山Linux应用开发完全手册V5.2_IMX6ULL_PRO开发板》文档)所示,APP层使用的Linux系统调用与驱动层的一一对应,APP调用open,对应地就会最终调用到驱动层的drv_open。

        由上图可知,我们点灯需要:open打开设备文件、write写入命令、close关闭等操作,因此重点关注驱动层的对应函数使用方法即可。

        首先,对于open,我们需要指定open哪个设备文件,查看驱动层,因为设备文件是由驱动层注册并命名的,发现在入口函数中有如下代码:

/* 在入口函数 */
static int __init gpio_drv_init(void)
{
    /* 省略其他代码 */

	device_create(gpio_class, NULL, MKDEV(major, 0), NULL, "100ask_led"); /* /dev/100ask_gpio */
	
	/* 省略其他代码 */
}

        device_create()就是创建设备节点,可已看出设备命名为100ask_led,因此在应用程序中我们就需要打开该文件,一般驱动层注册的设备文件都会在 /dev/目录下,因此APP中的初始化函数如下:

static int fd; //led_device file

void led_init()
{
    /* 1.open the device file */
    fd = open("/dev/100ask_led", O_RDWR);

    if (fd < 0)
    {
        qDebug()<<"/dev/100ask_led open error.";
        perror("fopen error:");
        return;
    }
    qDebug()<<"/dev/100ask_led open success.";
}

        其次,对于点灯write,查看驱动层gpio_drv_write()函数,如下所示,该函数主要关注buf和ker_buf这两个参数,buf前面的__user表示这是用户空间的变量,ker_buf是内核空间变量,copy_from_user()函数将用户空间数据拷贝到内核空间数据,然后根据buf内容使用gpio_set_value()对相应的GPIO进行写值操作。

        由上图可以看出,buf[]是一个含有2个元素的数组,buf[0]指定的是gpio,buf[1]指定的是需要写入的值,从led_test.c(驱动开发者提供的测试应用程序)的使用说明也可以作证这一点。因此,我们在自己编写应用程序时提供驱动程序所需要的这个buf[]即可,指定buf[0]为0(开发板上只有这一盏灯),buf[1]为写入值,应用程序如下:

void led_control(int on)
{
    char buf[2];

    buf[0] = 0; //LED_PIN, depend on the led_drv API

    if(on)
    {
        buf[1] = 0; //LED_Command, depend on the led_drv API
    }
    else
    {
        buf[1] = 1;
    }

    /* write command to the file */
    int ret = write(fd, buf, 2);
    if(ret == 2)
    {
        qDebug()<<"LED_toggled success.";
    }
    else
    {
        qDebug()<<"LED_toggled failed.";
    }

    return;
}

        到此,本节就已经完成代码修改工作,主要是将原来的SYSFS访问硬件的方式改为了驱动程序访问方式。

2.上机测试

        将新的可执行程序LED_and_Temp以及驱动程序编译出来的led_drv.ko文件下载至开发板后,先ps命令查看是否存在之前运行的LED_and_Temp程序,如果存在需要先kill掉,因为在上一节设置了开机自启动该程序,它占用了LED的GPIO口会导致insmod加载驱动模块失败。然后设置QT运行的环境变量,接着使用如下命令手动加载驱动程序模块。

insmod /root/led_drv.ko //注意路径根据自己的实际情况设置

        使用lsmod命令可以查看驱动模块是否加载成功,如下图所示。

        使用cat /proc/devices命令可以查看设备文件是否注册成功,如下图所示。开发板中有很多设备文件,LED、LCD等都属于字符设备,所以在Character devices中查找即可,名字就是驱动程序中设定的名字,如下图所示。

        加载驱动模块完毕之后即可运行可执行程序LED_and_Temp,点击按钮可以控制灯的开关,如果有错误可以修改应用程序,添加Qdebug在各个节点打印结果,主要排查思路就是:是否成功打开文件?打开的设备文件是否正确?写操作是否正确?

3.程序执行方式的思考

        应用开发要站在为用户着想的角度,上面我们为了方便测试可以手动用insmod加载驱动模块,但是不能作为产品,因此需要让其自动加载。有各种方法,可以从驱动角度添加(这需要驱动开发的知识),也可以将该命令写进开发板的启动脚本文件中。/etc/init.d/rcS 是一个启动脚本文件,其作用是在Linux系统引导时执行系统初始化操作。 它是Linux系统启动过程中最早执行的脚本之一,通常在所有其他脚本之前运行。将上面的命令写入该文件第一行即可(这里写入命令的行数是否有关我也不清楚,可以百度该文件的功能解析),如下图所示。

        重启开发板即可,至此基本实现“用户上电,就可以通过按钮开关灯”的效果。

4.总结与思考

        本节内容较简单,主要就是体验使用别人编写好的驱动程序接口来编写自己的应用层程序这个过程。

4.1 指定的buf[0]为什么一定是0呢?从何得知呢?其他行不行?

        这里需要查看驱动程序,buf[0]在驱动程序中表示的是GPIO编号,由gpios[]数组存储,这个编号是由驱动程序使用gpio_request()申请的,查看gpios的定义发现gpios[0]是LED,由此确定灯的编号是0。

疑问:驱动程序中没看到有关open的操作,应用程序调用open之后,是如何打开设备文件的?(视频中没有讲,欢迎各位懂驱动的大佬指点!)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值