0.文件编译及烧录
- SDK包在文件夹/home/tao/linux/luckfox/luckfox-pico-spi
- 应用程序样例在文件夹/home/tao/linux/luckfox-pico-spi/demo
- 编译:sudo ./build.sh生成的镜像文件在./luckfox-pico-spi/output/image中,将所有文件复制到windows电脑文件夹I:\BaiduNetdiskDownload\Luckfox_Pico_Pro_Max_Flash中
- 打开桌面SocTool文件夹,启动soctool烧录软件,开发板按住boot键再上电,此时usb设备处弹出maskdown 134 搜索路径按钮找到镜像所在文件夹,点击下载
- 若有时一直烧录完一直重启可能是由于rkipc命令默认执行,将摄像头撤掉即可
1.编写IIC-MPU6050驱动
1.1buildroot中添加命令
- 我们需要使用 depmod、modprobe 或 insmod 这三个命令来加载驱动模块,所以根文件系统要存在这两个命令。卸载驱动模块的时候需要用到 rmmod 命令,所以需要确保 depmod、modprobe、insmod 和 rmmod 这四个命令都存在。因此需要在buildroot中进行添加
- 在busybox目录下,使用make menuconfig命令之后弹出编译菜单选项,确保 kmod 或者depmod被选中,勾选 kmod 后,depmod 会自动包含在根文件系统中。配置好后连续按esc,保存后重新进行编译:
make savedefconfig
make
#(可选)export LD_LIBRARY_PATH= #若make时出现You seem to have the current working directory in your LD_LIBRARY_PATH environment variable.使用LD_LIBRARY_PATH=命令使变量为空
-
如果使用的是buildroot文件系统,则直接修改config文件,sysdrv/source/buildroot/buildroot-2023.02.6/package/busybox/busybox.config 将#CONFIG_DEPMOD is not set修改为CONFIG_DEPMOD=y
-
重新烧录固件:编译选择分支,指定开发板型号
./build.sh lunch
./build.sh
如果报错权限不足,使用su切换超级用户;固件在output/img文件夹中
1.2 编写驱动程序
- 设备树文件
在dts文件中对应接口i2c3下添加设备
&i2c3 {
status = "okay";
pinctrl-0 = <&i2c3m1_xfer>;
clock-frequency = <100000>;
status = "okay";
mpu6050@68 {//添加设备,0x68是iic7位地址,
compatible = "taojian,mpu6050";//用于匹配
reg = <0x68>;
};
};
- mpu6050.h
#ifndef MPU6050_H
#define MPU6050_H
#define MPU6050_ADDR 0X68 /* AP3216C 器件地址 */
#define MPU6050_ADDRESS 0x68
#define WHO_AM_I 0x75 //IIC地址寄存器(默认数值0x68,只读)
#define PWR_MGMT_1 0x6B //电源管理,典型值:0x00(正常启用)
#define SMPLRT_DIV 0x19 //陀螺仪采样率,典型值:0x07(125Hz)
#define CONFIG 0x1A //低通滤波频率,典型值:0x06(5Hz)
#define GYRO_CONFIG 0x1B //陀螺仪自检及测量范围,典型值:0x18(不自检,2000deg/s)
#define ACCEL_CONFIG 0x1C //加速计自检、测量范围及高通滤波频率,典型值:0x01(不自检,2G,5Hz)
#define INT_PIN_CFG 0x37 //旁路设置
#define USER_CTRL 0x6A //用户控制寄存器
/* mpu6050读取数据的寄存器地址 */
#define ACCEL_XOUT_H 0x3B
#define ACCEL_XOUT_L 0x3C
#define ACCEL_YOUT_H 0x3D
#define ACCEL_YOUT_L 0x3E
#define ACCEL_ZOUT_H 0x3F
#define ACCEL_ZOUT_L 0x40
#define TEMP_OUT_H 0x41
#define TEMP_OUT_L 0x42
#define GYRO_XOUT_H 0x43
#define GYRO_XOUT_L 0x44
#define GYRO_YOUT_H 0x45
#define GYRO_YOUT_L 0x46
#define GYRO_ZOUT_H 0x47
#define GYRO_ZOUT_L 0x48
#endif
- mpu6050.c
/***************************************************************
文件名 : mpu6050.c
作者 : tj
版本 : V1.0
描述 : mpu6050 驱动程序
其他 : 无
***************************************************************/
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
//#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "mpu6050.h"
#define mpu6050_CNT 1
#define mpu6050_NAME "mpu6050"//驱动加载后节点为/dev/mpu6050
struct mpu6050_dev {
struct i2c_client *client; /* i2c 设备 */
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
struct device_node *nd; /* 设备节点 */
unsigned short xyzData[7];//存放数据,分别是xyz角加速度,温度,xyz偏移量
};
/*
* @description : 从 mpu6050 读取多个寄存器数据
* @param – dev : mpu6050 设备
* @param – reg : 要读取的寄存器首地址
* @param – val : 读取到的数据
* @param – len : 要读取的数据长度
* @return : 操作结果
*/
static int mpu6050_read_regs(struct mpu6050_dev *dev, u8 reg,void *val, int len)
{
int ret;
struct i2c_msg msg[2];
struct i2c_client *client = (struct i2c_client *)dev->client;
/* msg[0]为发送要读取的首地址 */
msg[0].addr = client->addr; /* mpu6050 地址 */
msg[0].flags = 0; /* 标记为发送数据 */
msg[0].buf = ® /* 读取的首地址 */
msg[0].len = 1; /* reg 长度 */
/* msg[1]读取数据 */
msg[1].addr = client->addr; /* mpu6050 地址 */
msg[1].flags = I2C_M_RD; /* 标记为读取数据 */
msg[1].buf = val; /* 读取数据缓冲区 */
msg[1].len = len; /* 要读取的数据长度 */
ret = i2c_transfer(client->adapter, msg, 2);//调用i2c_transfer进行发送数据
if(ret == 2) {
ret = 0;
} else {
printk("i2c rd failed=%d reg=%06x len=%d\n",ret, reg, len);
ret = -EREMOTEIO;
}
return ret;
}
/*
* @description: 向 mpu6050 多个寄存器写入数据
* @param - dev: mpu6050 设备
* @param - reg: 要写入的寄存器首地址
* @param - val: 要写入的数据缓冲区
* @param - len: 要写入的数据长度
* @return : 操作结果
*/
static s32 mpu6050_write_regs(struct mpu6050_dev *dev, u8 reg,u8 *buf, u8 len)
{
u8 b[256];
struct i2c_msg msg;
struct i2c_client *client = (struct i2c_client *)dev->client;
b[0] = reg; /* 寄存器首地址 */
memcpy(&b[1],buf,len); /* 将要写入的数据拷贝到数组 b 里面 */
msg.addr = client->addr; /* mpu6050 地址 */
msg.flags = 0; /* 标记为写数据 */
msg.buf = b; /* 要写入的数据缓冲区 */
msg.len = len + 1; /* 要写入的数据长度 + 寄存器长度*/
return i2c_transfer(client->adapter, &msg, 1);//写数据就只有一个msg
}
static void mpu6050_write_reg(struct mpu6050_dev *dev, u8 reg,u8 data)//向 mpu6050 1个寄存器写入数据
{
u8 buf = 0;
buf = data;
mpu6050_write_regs(dev, reg, &buf, 1);
}
/** @description : 读取 mpu6050 的数据*/
void mpu6050_readdata(struct mpu6050_dev *dev)
{
unsigned char buf[14];//7组数据每组两字节
/* 循环读取所有传感器数据 */
mpu6050_read_regs(dev, ACCEL_XOUT_H,buf,14);
for(int i=0;i<7;i++)
dev->xyzData[i]=((unsigned short )buf[i*2] << 8) | buf[i*2+1];
dev->xyzData[3] = (365 + ( (unsigned short )(dev->xyzData[3]) /34)) ;//温度数据转换
}
/*
* @description : 打开设备
* @param – inode : 传递给驱动的 inode
* @param - filp : 设备文件,file 结构体有个叫做 private_data 的成员变量
* 一般在 open 的时候将 private_data 指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int mpu6050_open(struct inode *inode, struct file *filp)//c程序使用open调用时执行的函数
{
/* 从 file 结构体获取 cdev 指针,再根据 cdev 获取 mpu6050_dev 首地址 */
struct cdev *cdev = filp->f_path.dentry->d_inode->i_cdev;
struct mpu6050_dev *mpu6050dev = container_of(cdev,struct mpu6050_dev, cdev);
/* 初始化 mpu6050 */
printk("Init mpu6050 start\r\n");
mpu6050_write_reg(mpu6050dev,PWR_MGMT_1 , 0x00);
mpu6050_write_reg(mpu6050dev,SMPLRT_DIV, 0x07);
mpu6050_write_reg(mpu6050dev,CONFIG , 0x06);
mpu6050_write_reg(mpu6050dev, INT_PIN_CFG,0x42 );
mpu6050_write_reg(mpu6050dev,USER_CTRL ,0x40 );
mpu6050_write_reg(mpu6050dev, ACCEL_CONFIG,0x00 );
mpu6050_write_reg(mpu6050dev, GYRO_CONFIG,0x18 );
mdelay(50);
printk("Init mpu6050 over!!\r\n");
return 0;
}
/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t mpu6050_read(struct file *filp, char __user *buf,size_t cnt, loff_t *off)
{
unsigned short data[7];
long err = 0;
struct cdev *cdev = filp->f_path.dentry->d_inode->i_cdev;
struct mpu6050_dev *dev = container_of(cdev, struct mpu6050_dev,cdev);
mpu6050_readdata(dev);
for(int i=0;i<7;i++)
data[i] = dev->xyzData[i];
err = copy_to_user(buf, data, sizeof(data));
return 0;
}
/*
* @description : 关闭/释放设备
* @param - filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int mpu6050_release(struct inode *inode, struct file *filp)
{
return 0;
}
/* mpu6050 操作函数 */
static const struct file_operations mpu6050_ops = {
.owner = THIS_MODULE,
.open = mpu6050_open,
.read = mpu6050_read,
.release = mpu6050_release,
};
/*
* @description : i2c 驱动的 probe 函数,当驱动与设备匹配以后此函数就会执行
* @param – client : i2c 设备
* @param - id : i2c 设备 ID
* @return : 0,成功;其他负值,失败
*/
static int mpu6050_probe(struct i2c_client *client,const struct i2c_device_id *id)
{
int ret;
struct mpu6050_dev *mpu6050dev;
mpu6050dev = devm_kzalloc(&client->dev, sizeof(*mpu6050dev),GFP_KERNEL);
if(!mpu6050dev)
return -ENOMEM;
/* 注册字符设备驱动 */
/* 1、创建设备号 */
ret = alloc_chrdev_region(&mpu6050dev->devid, 0, mpu6050_CNT,mpu6050_NAME);
if(ret < 0) {
pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n",mpu6050_NAME, ret);
return -ENOMEM;
}
/* 2、初始化 cdev */
mpu6050dev->cdev.owner = THIS_MODULE;
cdev_init(&mpu6050dev->cdev, &mpu6050_ops);
/* 3、添加一个 cdev */
ret = cdev_add(&mpu6050dev->cdev, mpu6050dev->devid,mpu6050_CNT);
if(ret < 0) {
goto del_unregister;
}
/* 4、创建类 */
mpu6050dev->class = class_create(THIS_MODULE, mpu6050_NAME);
if (IS_ERR(mpu6050dev->class))
goto del_cdev;
/* 5、创建设备 */
mpu6050dev->device = device_create(mpu6050dev->class, NULL,mpu6050dev->devid, NULL, mpu6050_NAME);
if (IS_ERR(mpu6050dev->device))
goto destroy_class;
mpu6050dev->client = client;
/* 保存 mpu6050dev 结构体 */
i2c_set_clientdata(client,mpu6050dev);
return 0;
destroy_class:
device_destroy(mpu6050dev->class, mpu6050dev->devid);
del_cdev:
cdev_del(&mpu6050dev->cdev);
del_unregister:
unregister_chrdev_region(mpu6050dev->devid, mpu6050_CNT);
return -EIO;
}
/*
* @description : i2c 驱动的 remove 函数,移除 i2c 驱动的时候此函数会执行
* @param - client : i2c 设备
* @return : 0,成功;其他负值,失败
*/
static int mpu6050_remove(struct i2c_client *client)
{
struct mpu6050_dev *mpu6050dev = i2c_get_clientdata(client);
/* 注销字符设备驱动 */
/* 1、删除 cdev */
cdev_del(&mpu6050dev->cdev);
/* 2、注销设备号 */
unregister_chrdev_region(mpu6050dev->devid, mpu6050_CNT);
/* 3、注销设备 */
device_destroy(mpu6050dev->class, mpu6050dev->devid);
/* 4、注销类 */
class_destroy(mpu6050dev->class);
return 0;
}
/* 传统匹配方式 ID 列表 */
static const struct i2c_device_id mpu6050_id[] = {
{"taojian,mpu6050", 0}, //字符串和设备树文件中一致
{}
};
/* 设备树匹配列表 */
static const struct of_device_id mpu6050_of_match[] = {
{ .compatible = "taojian,mpu6050" },
{ /* Sentinel */ }
};
/* i2c 驱动结构体 */
static struct i2c_driver mpu6050_driver = {
.probe = mpu6050_probe,
.remove = mpu6050_remove,
.driver = {
.owner = THIS_MODULE,
.name = "mpu6050",
.of_match_table = mpu6050_of_match,
},
.id_table = mpu6050_id,
};
/*
* @description : 驱动入口函数
* @param : 无
* @return : 无
*/
static int __init mpu6050_init(void)
{
int ret = 0;
ret = i2c_add_driver(&mpu6050_driver);
return ret;
}
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit mpu6050_exit(void)
{
i2c_del_driver(&mpu6050_driver);
}
/* module_i2c_driver(mpu6050_driver) */
module_init(mpu6050_init);
module_exit(mpu6050_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("TAOJIAN");
MODULE_INFO(intree, "Y");
- Makefile文件
KERNELDIR := /home/tao/linux/luckfox/luckfox-pico-spi/sysdrv/source/kernel#内核文件所在目录
CURRENT_PATH := $(shell pwd)
obj-m := mpu6050.o
ccflags-y += -std=gnu11
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
- 编译生成
make ARCH=arm CROSS_COMPILE=arm-rockchip830-linux-uclibcgnueabihf-
此时在文件夹内生成mpu6050.ko驱动文件
1.3编写测试应用程序
- mpu6050App.c
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>
/*
* @description : main 主程序
* @param - argc : argv 数组元素个数
* @param - argv : 具体参数
* @return : 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{
int fd;
char *filename;
unsigned short databuf[7];
unsigned short ir, als, ps;
int ret = 0;
if (argc != 2) {
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if(fd < 0) {
printf("can't open file %s\r\n", filename);
return -1;
}
while (1) {
ret = read(fd, databuf, sizeof(databuf));
if(ret == 0) { /* 数据读取成功 */
for(int i=0;i<7;i++)
printf(" %d ", databuf[i]);
printf("\r\n");
usleep(200000); /*100ms */
}
close(fd); /* 关闭文件 */
return 0;
}
- 交叉编译生成可执行程序mpu
./mpu /dev/mpu6050运行
1.4加载驱动
- 运行depmod命令:
depmod: can’t change directory to ‘lib/modules/5.10.110’: No such file or directory
mkdir -p /lib/modules/5.10.110
cd /lib/modules/5.10.110#将ko驱动复制到此文件夹
depmod
modprobe mpu6050.ko
2.LCD2Inch4
- 连接示意:
- 连接后记得重启上电:
cat /dev/urandom > /dev/fb0#测试命令屏幕花屏
cat /dev/zero > /dev/fb0 #清屏