1000字范文,内容丰富有趣,学习的好帮手!
1000字范文 > 野火i.MX6ULL Pro开发板mpu6050驱动的调试编写

野火i.MX6ULL Pro开发板mpu6050驱动的调试编写

时间:2022-09-28 11:52:33

相关推荐

野火i.MX6ULL Pro开发板mpu6050驱动的调试编写

本文将详细描述本人编写该驱动遇到的问题已经相关调试经验

一、设备树部分的编写

这里有第一个坑,如果在野火开发板自带的固件中编写,需要在终端中将/boot/uEnv.txt文件中的一些开机自带的驱动手动关闭,并重启,比如注释掉开机启动的mpu6050驱动,否则驱动挂载出错或不起作用。

设备树可以直接使用系统自带的,不需要自己改动。上方为mpu6050与芯片连接的引脚配置。下方为i2c1控制器对应的配置,内部有对应的硬件设备,其中68为mpu6050在i2c1总线上的地址,此地址可由mpu6050芯片手册获得。

为了易于区分,我给设备的compatible属性重新设值。

pinctrl_i2c1: i2c1grp {fsl,pins = <MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0>;};/*=====================*/&i2c1{clock-frequency = <100000>;pinctrl-names = "default";pinctrl-0 = <&pinctrl_i2c1>;status = "okay";i2c_mpu6050@68 {/*compatible = "fire,i2c_mpu6050";*/compatible = "ldysl,i2c_mpu6050_ldysl";reg = <0x68>;status = "okay";};};

我的板子是mmc的,所以使用的指令如下:

sudo cp /mnt/imx6ull-mmc-npi.dtb /usr/lib/linux-image-4.19.35-imx6/imx6ull-mmc-npi.dtb

修改设备树拷贝到到指定的位置并重启以后。在/sys/bus/devices/0-0068/name是否为你自己修改的compatible值。有的话,说明设备树加载成功。

二、驱动部分编写

驱动的编写并不复杂,根据给的例程对照着修改即可。

1、驱动的初始化与注销

主要用到i2c_add_driver()和i2c_del_driver(),然后围绕这两个函数的参数构建i2c_driver结构体中的几个成员参数。

/*驱动初始化函数*/static int __init mpu6050_driver_init(void){int ret;pr_info("mpu6050_driver_init\n");ret = i2c_add_driver(&mpu6050_driver);return ret;}/*驱动注销函数*/static void __exit mpu6050_driver_exit(void){pr_info("mpu6050_driver_exit\n");i2c_del_driver(&mpu6050_driver);}module_init(mpu6050_driver_init);module_exit(mpu6050_driver_exit);MODULE_LICENSE("GPL");

2、在i2c_driver结构体中构建probe和remove函数和几个用于匹配的结构体。

/*定义ID 匹配表*/static const struct i2c_device_id gtp_device_id[] = {{"ldysl,i2c_mpu6050_ldysl", 0},{}};/*定义设备树匹配表*/static const struct of_device_id mpu6050_of_match_table[] = {{.compatible = "ldysl,i2c_mpu6050_ldysl"},{/*预留*/}};/*定义i2c总线设备结构体*/struct i2c_driver mpu6050_driver = {.probe = mpu6050_probe,.remove = mpu6050_remove,.id_table = gtp_device_id,.driver = {.name = "ldysl,i2c_mpu6050_ldysl",.owner= THIS_MODULE,.of_match_table = mpu6050_of_match_table,},};

3、在probe函数中注册字符设备并将其与文件操作结构体相关联

static int mpu6050_probe(struct i2c_client *client, const struct i2c_device_id *id){int ret = -1;printk(KERN_EMERG "\t match success !!\n");//动态分配来获取设备编号, 次设备号为0.可通过cat /proc/devices的方式查看//DEV_CNT为1,当前只申请一个设备编号ret = alloc_chrdev_region(&mpu6050_devno, 0, DEV_CNT, DEV_NAME);if (ret < 0) {printk("fail to alloc mpu6050_devno\n");goto alloc_err;}//关联字符设备结构体cdev与文件操作结构体mpu6050_chr_dev_fops.owner = THIS_MODULE;cdev_init(&mpu6050_chr_dev, &mpu6050_chr_dev_fops);//将设备添加进cdev_map列表中ret = cdev_add(&mpu6050_chr_dev, mpu6050_devno, DEV_CNT);if (ret < 0) {printk("fail to add cdev\n");goto add_err;}class_mpu6050 = class_create(THIS_MODULE, DEV_NAME);device_mpu6050 = device_create(class_mpu6050, NULL, mpu6050_devno, NULL, DEV_NAME);mpu6050_client = client;return 0;add_err:// 添加设备失败时,需要注销设备号unregister_chrdev_region(mpu6050_devno, DEV_CNT);printk("\n error! \n");alloc_err:return -1;}static int mpu6050_remove(struct i2c_client *client){device_destroy(class_mpu6050, mpu6050_devno);class_destroy(class_mpu6050);cdev_del(&mpu6050_chr_dev);//清除设备号unregister_chrdev_region(mpu6050_devno, DEV_CNT);//取消注册字符设备return 0;}

4、构建文件操作结构体

在mpu6050中主要是构建open和read函数,因为这个设备我们一般只读取数据所有无需构建write。

野火将mpu6050的初始化放在open函数中,它的初始化就是使用i2c提供的接口来进行寄存器的配置,详细配置参阅手册即可,这里就再叙述。

read函数也是使用i2c提供的接口获取到相应的参数并组合成一个short型数组,再使用copy_to_user()从内核态映射到用户态,完成用户态数据的获取。

static struct file_operations mpu6050_chr_dev_fops = {.owner = THIS_MODULE,.open = mpu6050_open,.read = mpu6050_read,.release = mpu6050_release,};static int mpu6050_open(struct inode *inode, struct file *filp){mpu6050_init();return 0;}static int mpu6050_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off){char data_H;char data_L;int error;//保存mpu6050转换得到的原始数据short mpu6050_result[6] = {0};/*读取3轴加速度原始值*/i2c_read_mpu6050(mpu6050_client, ACCEL_XOUT_H, &data_H, 1);i2c_read_mpu6050(mpu6050_client, ACCEL_XOUT_L, &data_L, 1);mpu6050_result[0] = data_H << 8;mpu6050_result[0] += data_L;i2c_read_mpu6050(mpu6050_client, ACCEL_YOUT_H, &data_H, 1);i2c_read_mpu6050(mpu6050_client, ACCEL_YOUT_L, &data_L, 1);mpu6050_result[1] = data_H << 8;mpu6050_result[1] += data_L;i2c_read_mpu6050(mpu6050_client, ACCEL_ZOUT_H, &data_H, 1);i2c_read_mpu6050(mpu6050_client, ACCEL_ZOUT_L, &data_L, 1);mpu6050_result[2] = data_H << 8;mpu6050_result[2] += data_L;/*读取3轴角速度原始值*/i2c_read_mpu6050(mpu6050_client, GYRO_XOUT_H, &data_H, 1);i2c_read_mpu6050(mpu6050_client, GYRO_XOUT_L, &data_L, 1);mpu6050_result[3] = data_H << 8;mpu6050_result[3] += data_L;i2c_read_mpu6050(mpu6050_client, GYRO_YOUT_H, &data_H, 1);i2c_read_mpu6050(mpu6050_client, GYRO_YOUT_L, &data_L, 1);mpu6050_result[4] = data_H << 8;mpu6050_result[4] += data_L;i2c_read_mpu6050(mpu6050_client, GYRO_ZOUT_H, &data_H, 1);i2c_read_mpu6050(mpu6050_client, GYRO_ZOUT_L, &data_L, 1);mpu6050_result[5] = data_H << 8;mpu6050_result[5] += data_L;printk("kernel:AX=%d, AY=%d, AZ=%d \n",(int)mpu6050_result[0],(int)mpu6050_result[1],(int)mpu6050_result[2]);printk("kernel:GX=%d, GY=%d, GZ=%d \n \n",(int)mpu6050_result[3],(int)mpu6050_result[4],(int)mpu6050_result[5]);/*将读取得到的数据拷贝到用户空间*///error = copy_to_user(buf, mpu6050_result, sizeof(mpu6050_result));printk("cnt:%d\n", cnt);error = copy_to_user(buf, mpu6050_result, cnt);if(error != 0){printk("copy_to_user error:%x!", error);return -1;}return 0;}static int mpu6050_release(struct inode *inode, struct file *filp){printk("\nmpu6050_release \n");return 0;}static struct file_operations mpu6050_chr_dev_fops = {.owner = THIS_MODULE,.open = mpu6050_open,.read = mpu6050_read,.release = mpu6050_release,};

5、使用i2c接口进行寄存器的读写

使用i2c进行读写都需要用i2c_transfer()来执行,不同的是需要构建i2c_msg结构体来传入这个函数。

写寄存器操作时,构建一个i2c_msg结构体,并将需要写入的寄存器地址以及值制作为数组传入i2c_msg.buf中即可。

读寄存器操作有一些复杂,不过需要构建一个i2c_msg结构体数组。第一个结构数组的i2c_msg[1].buf成员中放入寄存器地址。第二个结构体数组先置i2c_msg[2].flag为I2C_M_RD,并将缓存的指针存入i2c_msg[2].buf即可。

static int i2c_write_mpu6050(struct i2c_client *mpu6050_client, u8 address, u8 data){int error = 0;u8 write_data[2];struct i2c_msg send_msg;//待发送的数据结构体write_data[0] = address;//设置要发送的数据write_data[1] = data;/*发送数据要写入地址reg*/send_msg.addr = mpu6050_client->addr;//mpu6050在i2c总线上的地址send_msg.flags = 0;send_msg.buf = write_data;//写入的首地址send_msg.len = 2;//msg长度error = i2c_transfer(mpu6050_client->adapter, &send_msg, 1);if (error != 1){printk(KERN_DEBUG "i2c transfer error\n");return -1;}return 0;}static int i2c_read_mpu6050(struct i2c_client *mpu6050_client, u8 address, void *data, u32 length){int error = 0;u8 address_data = address;struct i2c_msg mpu6050_msg[2];/*设置读取位置msg*/mpu6050_msg[0].addr = mpu6050_client->addr; //mpu6050在 iic 总线上的地址mpu6050_msg[0].flags = 0;//标记为发送数据mpu6050_msg[0].buf = &address_data;//写入的首地址mpu6050_msg[0].len = 1;//写入长度/*设置读取位置msg*/mpu6050_msg[1].addr = mpu6050_client->addr; //mpu6050在 iic 总线上的地址mpu6050_msg[1].flags = I2C_M_RD;//标记为读取数据mpu6050_msg[1].buf = data;//读取得到的数据保存位置mpu6050_msg[1].len = length;//读取长度error = i2c_transfer(mpu6050_client->adapter, mpu6050_msg, 2);if (error != 2) {printk(KERN_DEBUG "\n i2c read mpu6050 error\n");return -1;}return 0;}

三、应用的编写

应用的编写倒没啥,就是一个open然后read,最后一个close。

需要注意的有两个点:

第一个,open()函数对应的属性文件是否存在、即是否匹配成功。

第二个,属性文件的名称是否正确,可能会出现二者对不上导致的open失败。

第三个,路径是否正确,我一开始调的时候就是有驱动文件但是就是打不开,直到我使用perror发现文件不存在,我去看发现我的路径写成了open("dev/XXX")少了一个‘/’,就很难顶。

int main(int argc, char *argv[]){short resive_data[6]; //保存收到的 mpu6050转换结果数据,依次为 AX(x轴角度), AY, AZ 。GX(x轴加速度), GY ,GZ/*打开文件*/int fd = open("/dev/I2C1_mpu6050", O_RDWR);if(fd < 0){printf("open file : %s failed !\n", argv[0]);perror("why error:");return -1;}/*读取数据*/int error = read(fd,resive_data,12);if(error < 0){printf("write file error! \n");close(fd);/*判断是否关闭成功*/}/*打印数据*/printf("AX=%d, AY=%d, AZ=%d ",(int)resive_data[0],(int)resive_data[1],(int)resive_data[2]);printf(" GX=%d, GY=%d, GZ=%d \n \n",(int)resive_data[3],(int)resive_data[4],(int)resive_data[5]);/*关闭文件*/error = close(fd);if(error < 0){printf("close file error! \n");}return 0;}

四、代码调试

·我觉得敲代码最重要的就是知道如何调试,调试的方法可以让我们更快的锁定问题,解决问题。

1、probe匹配。

匹配应该没啥问题,只要名称一样就能匹配上,如果匹配失败对照着检查一下代码。

之前失败是因为我使用cdev_add()函数,将字符设备结构体struct cdev和设备号传入其中时,手抖了,传设备号,传入了指针即&devno,导致设备probe成功,但是应用层获取不到那个设备。

匹配成功后,在/sys/bus/i2c/drivers/下看到相关设备

2、调用app读取数据

最开始调试时,我不知道的open()路径有误,导致我的觉得我驱动写的有问题,

但我调试发现路径下有,我直接cat确实报了错。仔细看会发现是copy_to_user的问题,因为我是直接cat所以读取数据到用户态的这个长度出问题导致的出错。只要屏蔽这个,我直接cat就没问题。后面反过来看app,最终找到了问题,驱动也就大功告成。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。