0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

嵌入式Linux的驱动编写入门资料详细说明

Wildesbeast 来源:今日头条 作者:TopSemic 2020-02-20 15:06 次阅读

1.引言

很早之前就有网友建议写一篇关于Linux驱动的文章。之所以拖到现在才写,原因之一是我之前没有在工作中遇到需要自己手动去写驱动的需求,主要是现在Linux内核驱动的支持已经比较完善了,另外一个原因是自己水平实在有限,不敢写驱动这个话题,Linux驱动里涉及到的东西太多了,很多年前专门买过驱动相关的书籍,厚厚的,看的云里雾里。借此机会,在这里给大家做个非常非常入门级的介绍,希望对大家有所帮助。

2.环境介绍

2.1.硬件

网上的一个第三方做的NUC972开发板,这里会用到板子上的MPU6050传感器芯片,相关部分原理图如下:

2.2.软件

1) Uboot不需要改动

2) Kernel不需要改动

3) Rootfs不需要重新编译

3.最简单的驱动例子

第1步:编写hello.c

#include #include static int __init hello_init(void) { printk(KERN_INFO "module init success\n"); return 0;} static void __exit hello_exit(void) { printk(KERN_INFO "module exit success\n");} module_init(hello_init);module_exit(hello_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("wuya");MODULE_DESCRIPTION("driver example");

这是一个简单的内核模块程序,可以动态加载和卸载。模块加载的时候系统会打印module init success,模块卸载的时候系统会打印module exit success。

开头的两个头文件,init.h 定义了驱动的初始化和退出相关的函数,module.h 定义了内核模块相关的函数、变量及宏。然后module_init和module_exit是模组加载和卸载相关的两个函数,

第2步:编写Makefile

obj-m := hello.oPWD := $(shell pwd)KDIR :=/home/topsemic/nuc972/kernel/NUC970_Linux_Kernel-master/all: $(MAKE) -C $(KDIR) M=$(PWD)clean: rm -rf *.o *.mod.c *.mod.o *.ko *.symvers *.order *.a

注意:KDIR 取决于你自己Linux内核安装的位置,一定要设置正确,否则编译会报错。

第3步:编译

将hello.c和Makefile放在同一路径下进行编译,输入make即可。编译成功后,会在当前路径下生成hello.ko,这就是我们将要加载到内核的模块。

第4步:将生成的hello.ko放到板子上,然后登录板子输入:

insmod hello.ko

如果模块加载成功的话,可以查看模块加载情况,使用lsmod命令

并且可以查看内核打印的消息,使用dmesg命令,

rmmod hello.ko,用来卸载模块,使用dmesg命令可以看到相关输出信息

4.MPU6050驱动

本章以板子上的MPU6050 传感器为例,来介绍驱动的编写。由于板子上使用的是PE10和PE11,它们不是真正的I2C引脚,所以这里我们使用GPIO来模拟I2C时序。编写驱动前,首先需要下载控制器件的datasheet,在官网 可以下载。

第1步:写驱动文件,我们这里在驱动文件里放了三个文件,分别为mpu6050.c、mpu6050bsp.c和mpu6050bsp.h

其中mpu6050.c代码如下:

#include"mpu6050bsp.h"int MPU6050_MAJOR = 0;int MPU6050_MINOR = 0;int NUMBER_OF_DEVICES = 2; struct class *my_class;struct cdev cdev;dev_t devno;/*************************************************************************************/ #define DRIVER_NAME "mpu6050"int mpu6050_open(struct inode *inode,struct file *filp){u8 reg;reg=InitMPU6050();printk("mpu6050:%d\n",reg);return nonseekable_open(inode,filp);}long mpu6050_ioctl(struct file *filp,unsigned int cmd,unsigned long arg){switch(cmd){default:return -2;}return 0;}int mpu6050_read(struct file *filp, char *buffer,size_t count, loff_t *ppos){mpu_get_data();return copy_to_user(buffer, mpu_data, 14);}int mpu6050_write(struct file *filp, char *buffer, size_t count, loff_t *ppos){return 0;}struct file_operations mpu6050_fops = {.owner = THIS_MODULE,.read = mpu6050_read,.write = mpu6050_write,.open = mpu6050_open,.unlocked_ioctl = mpu6050_ioctl,};/**************************************************************************************/static int __init mpu6050_init(void){ int result; devno = MKDEV(MPU6050_MAJOR, MPU6050_MINOR); if (MPU6050_MAJOR) result = register_chrdev_region(devno, 2, "mpu6050"); else { result = alloc_chrdev_region(&devno, 0, 2, "mpu6050"); MPU6050_MAJOR = MAJOR(devno); } printk("MAJOR IS %d\n",MPU6050_MAJOR); my_class = class_create(THIS_MODULE,"mpu6050_class"); //类名为 if(IS_ERR(my_class)) { printk("Err: failed in creating class.\n"); return -1; } device_create(my_class,NULL,devno,NULL,"mpu6050"); //设备名为mpu6050 if (result<0) { printk (KERN_WARNING "hello: can't get major number %d\n", MPU6050_MAJOR); return result; } cdev_init(&cdev, &mpu6050_fops); cdev.owner = THIS_MODULE; cdev_add(&cdev, devno, NUMBER_OF_DEVICES); printk (KERN_INFO "mpu6050 driver Registered\n"); return 0;} static void __exit mpu6050_exit (void){ cdev_del (&cdev); device_destroy(my_class, devno); //delete device node under /dev//必须先删除设备,再删除class类 class_destroy(my_class); //delete class created by us unregister_chrdev_region (devno,NUMBER_OF_DEVICES); printk (KERN_INFO "char driver cleaned up\n");} module_init (mpu6050_init );module_exit (mpu6050_exit ); MODULE_LICENSE ("GPL");

上述代码整体结构和第3章介绍的hello.c类似,不过为了支持对字符设备的操作,多了open/write/read的几个函数实现。

mpu6050bsp.c由于内容较多,不把代码贴到这里了,大家一看就明白了,它就是用gpio来模拟i2c功能,实现寄存器操作功能。mpu6050bsp.h主要是相关寄存器定义。

第2步:编译,然后把ko文件放到板子,insmod mpu6050d.ko 。模块如果加载成功,在/dev目录下可以看到mpu6050的设备名出现。

第3步:写个应用程序mpu6050app.c,

#include #include #include #include short x_accel, y_accel, z_accel;short x_gyro, y_gyro, z_gyro;short temp;int main(){ char buffer[128]; short *value; int in, out; int nread; in = open("/dev/mpu6050", O_RDONLY); if (!in) { printf("ERROR: %d, Open /dev/mpu6050 failed.\n", -1); return -1; } nread = read(in, buffer, 12); close(in); if (nread < 0) { printf("ERROR: %d, A read error has occurred\n", nread); return -1; } value = (short*)buffer; x_accel = *(value); y_accel = *(value + 1); z_accel = *(value + 2); temp = *(value + 3); x_gyro = *(value + 4); y_gyro = *(value + 5); z_gyro = *(value + 6); printf("x accel is: %d \n", x_accel); printf("y accel is: %d \n", y_accel); printf("z accel is: %d \n", z_accel); printf("x gyro is: %d \n", x_gyro); printf("y gyro is: %d \n", y_gyro); printf("z gyro is: %d \n", z_gyro);printf("temperature is: %d \n", temp); exit(0);}

编译arm-linux-gcc mpu6050app.c -o mpu6050app

第4步:将板子水平摆放朝上,运行例子结果如下,

我们来计算下z轴加速度和温度的实际数值。

因为驱动里AFS_SEL寄存器设置的值是2,所以对应量程8g。数字-32767对应-8g,32767对应8g。把32767除以8,就可以得到4096,即1g对应的数值。把从加速度计读出的数字除以4096,就可以换算成加速度的数值。上面我们从加速度计z轴读到的数字是3723,那么对应的加速度数据是3723/4096≈0.91g。g为加速度的单位,重力加速度定义为1g, 等于9.8米每平方秒。由于桌上不是很平,加上传感器自身误差,所以这个值是合理的。

再看看温度计算,从手册中可以看到如下的计算公式

上述的-2352计算后得到温度为29.6℃,注意这个温度不是环境温度,是芯片内部的温度,环境温度会比这个值略低。

由于我是在北京,冬天屋里有暖气,所以这个值也是合理的。

5.结束语

本期给大家介绍关于Linux驱动最简单的使用,可以看到驱动开发和应用开发还是有很大的差异,驱动需要关注底层,需要深入的阅读芯片的数据手册,同时也得具备内核的相关知识。市场上Linux应用开发人员相对更多,真正懂驱动的人相对较少,大部分集中在芯片原厂公司。推荐大家在实际做产品时尽量选择官方推荐的元器件,或者选择可以提供Linux驱动的元器件,以降低开发难度。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 传感器
    +关注

    关注

    2524

    文章

    48036

    浏览量

    739902
  • 嵌入式
    +关注

    关注

    4981

    文章

    18265

    浏览量

    288207
  • Linux
    +关注

    关注

    87

    文章

    10986

    浏览量

    206711
收藏 人收藏

    评论

    相关推荐

    详细说明如何配置嵌入式linux 的nfs开发环境

    对于嵌入式开发者而言开发效率非常重要,nfs的是嵌入式linux 开发的一个重要的组成部分,本部分内容将详细说明如何配置嵌入式
    发表于 07-28 16:14 2206次阅读

    嵌入式LINUX入门知识

    嵌入式系统linux入门资料感觉不错就顶下哦!
    发表于 12-24 18:21

    新手必看--嵌入式Linux学习步骤,教你怎么入门

    文件I/O 编写串口通信程序 编写多串口通信程序7、嵌入式系统中多进程程序设计Linux系统进程概述 嵌入式系统的进程特点 进程操作 守护进
    发表于 06-30 11:23

    嵌入式Linux开发学习如何入门、如何深入?

    编写带有一个循环的程序调试一个有问题的程序4、嵌入式系统开发基础嵌入式系统概述交叉编译配置TFTP服务配置NFS服务下载Bootloader和内核嵌入式
    发表于 07-08 11:03

    [分享资料]嵌入式Linux应用程序开发详解

    11章 嵌入式linux设备驱动开发11.1 设备驱动概述11.2 字符设备驱动编写11.3 l
    发表于 09-11 23:25

    嵌入式linux入门学习书籍推荐

    ,由浅入深地讲解,使读者最终可以配置、移植、裁剪内核,编写驱动程序,移植gui系统,掌握整个嵌入式linux系统的开发方法。这本书是韦东山写的。配套这本书有一个开发板的视频。讲的很好。
    发表于 10-08 09:17

    嵌入式linux入门学习书籍推荐 

    嵌入式linux入门学习书籍推荐  第一:《鸟哥的Linux私房菜 基础学习篇(第三版)》主要讲解Linux一些命令,
    发表于 10-18 10:13

    嵌入式Linux开发学习如何入门、如何深入?

    编译程序编写带有一个循环的程序调试一个有问题的程序 4、嵌入式系统开发基础嵌入式系统概述交叉编译配置TFTP服务配置NFS服务下载Bootloader和内核嵌入式
    发表于 10-14 14:37

    嵌入式Linux驱动编写入门

    一些嵌入式硬件工程师的工作内容可能几乎不涉及到驱动方面的知识,他们所要做的内容就是把客户对设备的请求拆分成一个一个的接口,调用驱动的设置进行配置就可以了。至于驱动下面是怎么实现那就要根
    发表于 02-10 16:16

    嵌入式Linux系统的键盘驱动程序怎么编写

    Linux由于其具有内核强大且稳定,易于扩展和裁减,丰富的硬件支持等诸多优点,在嵌入式系统中得到了广泛的应用。很多嵌入式Linux系统,特别是一些具有与用户强交互的
    发表于 03-31 07:08

    嵌入式Linux开发的基本概念

    嵌入式Linux开发前,你可能需要知道这些基本概念 作者之前编写了一系列嵌入式Linux的开发文档: 关注公众号【微联智控】,并回复【
    发表于 11-04 08:05

    Linux嵌入式驱动开发

    嵌入式驱动开发04——应用层和内核层数据传输Linux嵌入式驱动开发05——物理地址到虚拟地址映射Lin
    发表于 12-17 06:22

    嵌入式Linux学习路线

    想学习Linux驱动但是无从下手的同学,学习Linux驱动但是一直不能入门的同学,学习了很多视频和资料
    发表于 12-23 07:52

    嵌入式教程之Linux的基础命令详细资料说明

    本文档的主要内容详细介绍的是嵌入式教程之Linux的基础命令详细资料说明主要内容包括了:1.Linux
    发表于 12-06 17:31 8次下载
    <b class='flag-5'>嵌入式</b>教程之<b class='flag-5'>Linux</b>的基础命令<b class='flag-5'>详细资料</b><b class='flag-5'>说明</b>

    如何吧SQLite移植到嵌入式Linux系统的详细资料说明

    本文档的主要内容详细介绍的是如何吧SQLite移植到嵌入式Linux系统的详细资料说明
    发表于 01-18 08:00 8次下载
    如何吧SQLite移植到<b class='flag-5'>嵌入式</b><b class='flag-5'>Linux</b>系统的<b class='flag-5'>详细资料</b><b class='flag-5'>说明</b>