linux下编写I2C驱动与stm32通信(一)
2017-12-15 13:52
337 查看
最近做一个IPC的项目,其中用了海思的一套解决方案,用Hi3518e作为主芯片,上面搭载嵌入式linux系统。由于可行性验证阶段,没有做芯片级,而是先从系统级做起,用了一块已经移植好linux系统,带有网络文件系统服务的板子,该板子是专用于rtsp视频传输的,预留的引脚是在太少,只有两个用于IRCUT的引脚,而我们不仅仅需要rtsp服务,还需要在rtsp视频流中加入九轴陀螺仪的数据一起提供给上位机解析,只得再加一块stm32板子,用IRCUT的两个GPIO口模拟I2C与stm32通信,将与九轴陀螺仪通信以及控制LED亮度和电机控制等工作留给stm32,Hi3518从上位机接收命令传递给stm32,stm32获取九轴陀螺仪数据给Hi3518
由于I2C对时序的严格要求,而linux是一个多任务操作系统,在应用层无法满足严格的时序要求,只得在驱动层做。
1.将寄存器做相应的宏定义,包含相应的头文件,定义用户的数据结构体
[html] view
plain copy
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/fcntl.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/proc_fs.h>
#include <linux/workqueue.h>
#include <asm/uaccess.h>
#include <asm/system.h>
#include <asm/io.h>
[html] view
plain copy
typedef struct I2C_DATA_S
{
<span style="white-space:pre"> </span>unsigned char<span style="white-space:pre"> </span>dev_addr;
<span style="white-space:pre"> </span>unsigned int <span style="white-space:pre"> </span>reg_addr;
<span style="white-space:pre"> </span>unsigned int <span style="white-space:pre"> </span>addr_byte_num;
<span style="white-space:pre"> </span>unsigned int <span style="white-space:pre"> </span>data;
<span style="white-space:pre"> </span>unsigned int <span style="white-space:pre"> </span>data_byte_num;
}I2C_DATA_S;
[html] view
plain copy
#define GPIO_0_BASE 0x20180000
#define SCL (1 << 6) /* GPIO 4_6 */
#define SDA (1 << 7) /* GPIO 4_7 */
#define GPIO_I2C_SCL_REG IO_ADDRESS(GPIO_0_BASE + 0x400)
#define GPIO_I2C_SDA_REG IO_ADDRESS(GPIO_0_BASE + 0x200
#define GPIO_I2C_SCLSDA_REG IO_ADDRESS(GPIO_0_BASE + 0x600)<strong>
</strong>
2.按照linux miscdevice驱动开发的一般流程:定义相应的数据结构
[html] view
plain copy
static struct file_operations gpioi2c_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = gpioi2c_ioctl,
.open = gpioi2c_open,
.release = gpioi2c_close
};
static struct miscdevice gpioi2c_dev = {
.minor = MISC_DYNAMIC_MINOR, //动态分配次设备号
.name = "gpioi2c_ex", //驱动名字
.fops = &gpioi2c_fops, //文件操作
};
3.编写模块的入口函数(init)和出口函数(exit)
[csharp] view
plain copy
static int __init gpio_i2c_init(void)
{
int ret;
[csharp] view
plain copy
//把内存映射到CPU空间,可直接操作GPIO寄存器组,返回的线性地址指向,<span style="font-family: Arial, Helvetica, sans-serif;">由于Hi3518e的GPIO寄存器占用64K字节,故申请映射时使用0x10000作为参数</span>
[csharp] view
plain copy
<span style="font-family: Arial, Helvetica, sans-serif;"><span style="white-space:pre"> </span>reg_gpio0_base_va = ioremap_nocache((unsigned long)GPIO_0_BASE, (unsigned long)0x10000); ret = misc_register(&gpioi2c_dev); //注册设备</span>
[csharp] view
plain copy
<span style="font-family: Arial, Helvetica, sans-serif;"><span style="white-space:pre"> </span> if(0 != ret)</span>
[csharp] view
plain copy
<span style="font-family: Arial, Helvetica, sans-serif;"><span style="white-space:pre"> </span> return -1;</span>
[csharp] view
plain copy
i2c_set(SCL | SDA);
[csharp] view
plain copy
return 0;
}
static void __exit gpio_i2c_exit(void)
{
iounmap((void*)reg_gpio0_base_va); //取消映射
misc_deregister(&gpioi2c_dev); //将注册时申请的资源释放
}
[csharp] view
plain copy
module_init(gpio_i2c_init); //声明入口函数,模块被insmod时会自动调用gpio_i2c_init
module_exit(gpio_i2c_exit); //声明出口函数,模块被rmmod时会自动调用gpio_i2c_exit
4.编写unlock_ioctl,open,close函数
[cpp] view
plain copy
long gpioi2c_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
I2C_DATA_S __user *argp = (I2C_DATA_S __user*)arg;
unsigned char device_addr;
unsigned short reg_addr;
unsigned short reg_val;
unsigned int addr_len;
unsigned int data_len;
switch(cmd)
{
case GPIO_I2C_READ:
device_addr = argp->dev_addr;
reg_addr = argp->reg_addr;
addr_len = argp->addr_byte_num;
data_len = argp->data_byte_num;
reg_val = gpio_i2c_read_ex(device_addr, reg_addr, addr_len, data_len);
argp->data = reg_val ;
break;
case GPIO_I2C_WRITE:
device_addr = argp->dev_addr;
reg_addr = argp->reg_addr;
addr_len = argp->addr_byte_num;
data_len = argp->data_byte_num;
reg_val = argp->data;
gpio_i2c_write_ex(device_addr, reg_addr, addr_len, reg_val, data_len);
break;
default:
return -1;
}
return 0;
}
int gpioi2c_open(struct inode * inode, struct file * file)
{
return 0;
}
int gpioi2c_close(struct inode * inode, struct file * file)
{
return 0;
}
至此,大概框架就搭好了,接下来就是按照I2C的时序要求编写相应的代码,在此就不全贴了,说说遇到的问题吧。
首先遇到的第一个问题就是驱动编写完成加载后,SCL和SDA的引脚均为低电平,而我在模块入口函数处将SDA和SCL均拉高了,通过在其中加printk语句,确认函数确实执行到了,但是就是没有起作用,对照datasheet检查了各种寄存器的地址和配置都没有问题,后来通过示波器发现两个引脚的电平都为0,但是不是很平稳,更像是一个未被控制的引脚,类似于三态的感觉,于是怀疑方向控制寄存器配置不对,于是在gpio_i2c_init函数中将这个寄存器的值打印出来发现是对的。排除了其他的寄存器配置后,只剩下一个管脚复用寄存器,由于复位后这个寄存器的值为0x00,即为GPIO模式,就没有对其进行配置。如下图所示:
结果将这个寄存器的值打印出来发现是0x01,不是默认的GPIO模式,于是在入口函数中重新配置该寄存器,I2C时序正常。 hi3518e这边的工作算是完成了,接下来就是完成stm32的I2C从机配置和两者之间的通信协议。
由于I2C对时序的严格要求,而linux是一个多任务操作系统,在应用层无法满足严格的时序要求,只得在驱动层做。
1.将寄存器做相应的宏定义,包含相应的头文件,定义用户的数据结构体
[html] view
plain copy
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/fcntl.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/proc_fs.h>
#include <linux/workqueue.h>
#include <asm/uaccess.h>
#include <asm/system.h>
#include <asm/io.h>
[html] view
plain copy
typedef struct I2C_DATA_S
{
<span style="white-space:pre"> </span>unsigned char<span style="white-space:pre"> </span>dev_addr;
<span style="white-space:pre"> </span>unsigned int <span style="white-space:pre"> </span>reg_addr;
<span style="white-space:pre"> </span>unsigned int <span style="white-space:pre"> </span>addr_byte_num;
<span style="white-space:pre"> </span>unsigned int <span style="white-space:pre"> </span>data;
<span style="white-space:pre"> </span>unsigned int <span style="white-space:pre"> </span>data_byte_num;
}I2C_DATA_S;
[html] view
plain copy
#define GPIO_0_BASE 0x20180000
#define SCL (1 << 6) /* GPIO 4_6 */
#define SDA (1 << 7) /* GPIO 4_7 */
#define GPIO_I2C_SCL_REG IO_ADDRESS(GPIO_0_BASE + 0x400)
#define GPIO_I2C_SDA_REG IO_ADDRESS(GPIO_0_BASE + 0x200
#define GPIO_I2C_SCLSDA_REG IO_ADDRESS(GPIO_0_BASE + 0x600)<strong>
</strong>
2.按照linux miscdevice驱动开发的一般流程:定义相应的数据结构
[html] view
plain copy
static struct file_operations gpioi2c_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = gpioi2c_ioctl,
.open = gpioi2c_open,
.release = gpioi2c_close
};
static struct miscdevice gpioi2c_dev = {
.minor = MISC_DYNAMIC_MINOR, //动态分配次设备号
.name = "gpioi2c_ex", //驱动名字
.fops = &gpioi2c_fops, //文件操作
};
3.编写模块的入口函数(init)和出口函数(exit)
[csharp] view
plain copy
static int __init gpio_i2c_init(void)
{
int ret;
[csharp] view
plain copy
//把内存映射到CPU空间,可直接操作GPIO寄存器组,返回的线性地址指向,<span style="font-family: Arial, Helvetica, sans-serif;">由于Hi3518e的GPIO寄存器占用64K字节,故申请映射时使用0x10000作为参数</span>
[csharp] view
plain copy
<span style="font-family: Arial, Helvetica, sans-serif;"><span style="white-space:pre"> </span>reg_gpio0_base_va = ioremap_nocache((unsigned long)GPIO_0_BASE, (unsigned long)0x10000); ret = misc_register(&gpioi2c_dev); //注册设备</span>
[csharp] view
plain copy
<span style="font-family: Arial, Helvetica, sans-serif;"><span style="white-space:pre"> </span> if(0 != ret)</span>
[csharp] view
plain copy
<span style="font-family: Arial, Helvetica, sans-serif;"><span style="white-space:pre"> </span> return -1;</span>
[csharp] view
plain copy
i2c_set(SCL | SDA);
[csharp] view
plain copy
return 0;
}
static void __exit gpio_i2c_exit(void)
{
iounmap((void*)reg_gpio0_base_va); //取消映射
misc_deregister(&gpioi2c_dev); //将注册时申请的资源释放
}
[csharp] view
plain copy
module_init(gpio_i2c_init); //声明入口函数,模块被insmod时会自动调用gpio_i2c_init
module_exit(gpio_i2c_exit); //声明出口函数,模块被rmmod时会自动调用gpio_i2c_exit
4.编写unlock_ioctl,open,close函数
[cpp] view
plain copy
long gpioi2c_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
I2C_DATA_S __user *argp = (I2C_DATA_S __user*)arg;
unsigned char device_addr;
unsigned short reg_addr;
unsigned short reg_val;
unsigned int addr_len;
unsigned int data_len;
switch(cmd)
{
case GPIO_I2C_READ:
device_addr = argp->dev_addr;
reg_addr = argp->reg_addr;
addr_len = argp->addr_byte_num;
data_len = argp->data_byte_num;
reg_val = gpio_i2c_read_ex(device_addr, reg_addr, addr_len, data_len);
argp->data = reg_val ;
break;
case GPIO_I2C_WRITE:
device_addr = argp->dev_addr;
reg_addr = argp->reg_addr;
addr_len = argp->addr_byte_num;
data_len = argp->data_byte_num;
reg_val = argp->data;
gpio_i2c_write_ex(device_addr, reg_addr, addr_len, reg_val, data_len);
break;
default:
return -1;
}
return 0;
}
int gpioi2c_open(struct inode * inode, struct file * file)
{
return 0;
}
int gpioi2c_close(struct inode * inode, struct file * file)
{
return 0;
}
至此,大概框架就搭好了,接下来就是按照I2C的时序要求编写相应的代码,在此就不全贴了,说说遇到的问题吧。
首先遇到的第一个问题就是驱动编写完成加载后,SCL和SDA的引脚均为低电平,而我在模块入口函数处将SDA和SCL均拉高了,通过在其中加printk语句,确认函数确实执行到了,但是就是没有起作用,对照datasheet检查了各种寄存器的地址和配置都没有问题,后来通过示波器发现两个引脚的电平都为0,但是不是很平稳,更像是一个未被控制的引脚,类似于三态的感觉,于是怀疑方向控制寄存器配置不对,于是在gpio_i2c_init函数中将这个寄存器的值打印出来发现是对的。排除了其他的寄存器配置后,只剩下一个管脚复用寄存器,由于复位后这个寄存器的值为0x00,即为GPIO模式,就没有对其进行配置。如下图所示:
结果将这个寄存器的值打印出来发现是0x01,不是默认的GPIO模式,于是在入口函数中重新配置该寄存器,I2C时序正常。 hi3518e这边的工作算是完成了,接下来就是完成stm32的I2C从机配置和两者之间的通信协议。
相关文章推荐
- linux下编写I2C驱动与stm32通信(一)
- linux下编写I2C驱动与stm32通信(二)
- linux下编写I2C驱动与stm32通信(二)
- linux I2C设备驱动编写
- 【Linux驱动】I2C驱动编写要点
- Linux I2C设备驱动编写(三)-实例分析AM3359
- Linux I2C设备驱动编写(一)
- 编写i2c驱动-基于Linux3.10
- Linux I2C设备驱动编写(一)【转】
- Linux I2C设备驱动编写(一)
- 【转】Linux I2C设备驱动编写(一)
- Linux I2C设备驱动编写(二)
- Linux i2c设备驱动编写(二)
- Linux I2C设备驱动编写(二)
- 编写i2c驱动-基于Linux3.10
- 【转】Linux I2C设备驱动编写(二)
- Linux I2C设备驱动编写(三)-实例分析AM3359
- Linux I2C设备驱动编写
- Linux I2C设备驱动编写(三)-实例分析AM3359
- Linux i2c子系统(四) _从i2c-s3c24xx.c看i2c控制器驱动的编写