首页 » 智能 » 嵌入式Linux系列第20篇:驱动编写入门_模块_相干

嵌入式Linux系列第20篇:驱动编写入门_模块_相干

雨夜梧桐 2025-01-15 02:33:21 0

扫一扫用手机浏览

文章目录 [+]

2.2.软件

1) Uboot不须要改动

2) Kernel不须要改动

嵌入式Linux系列第20篇:驱动编写入门_模块_相干 嵌入式Linux系列第20篇:驱动编写入门_模块_相干 智能

3) Rootfs不须要重新编译

嵌入式Linux系列第20篇:驱动编写入门_模块_相干 嵌入式Linux系列第20篇:驱动编写入门_模块_相干 智能
(图片来自网络侵删)
3.最大略的驱动例子

第1步:编写hello.c

#include <linux/init.h>#include <linux/module.h> 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 <unistd.h>#include <stdlib.h>#include <sys/stat.h>#include <fcntl.h>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驱动的元器件,以降落开拓难度。

标签:

相关文章