linux下編寫I2C驅動與stm32通信(一)

最近做一個IPC的項目,其中用了海思的一套解決方案,用Hi3518e作爲主芯片,上面搭載嵌入式linux系統。由於可行性驗證階段,沒有做芯片級,而是先從系統級做起,用了一塊已經移植好linux系統,帶有網絡文件系統服務的板子,該板子是專用於rtsp視頻傳輸的,預留的引腳是在太少,只有兩個用於IRCUT的引腳,而我們不僅僅需要rtsp服務,還需要在rtsp視頻流中加入九軸陀螺儀的數據一起提供給上位機解析,只得再加一塊stm32板子,用IRCUT的兩個GPIO口模擬I2C與stm32通信,將與九軸陀螺儀通信以及控制LED亮度和電機控制等工作留給stm32,Hi3518從上位機接收命令傳遞給stm32,stm32獲取九軸陀螺儀數據給Hi3518

由於I2C對時序的嚴格要求,而linux是一個多任務操作系統,在應用層無法滿足嚴格的時序要求,只得在驅動層做。

1.將寄存器做相應的宏定義,包含相應的頭文件,定義用戶的數據結構體

#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>
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;
#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驅動開發的一般流程:定義相應的數據結構

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)

static int __init gpio_i2c_init(void)
{
    int ret;
//把內存映射到CPU空間,可直接操作GPIO寄存器組,返回的線性地址指向,<span style="font-family: Arial, Helvetica, sans-serif;">由於Hi3518e的GPIO寄存器佔用64K字節,故申請映射時使用0x10000作爲參數</span>
<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>
<span style="font-family: Arial, Helvetica, sans-serif;"><span style="white-space:pre">	</span> if(0 != ret)</span>
<span style="font-family: Arial, Helvetica, sans-serif;"><span style="white-space:pre">		</span> return -1;</span>


    i2c_set(SCL | SDA);
    return 0;
}

static void __exit gpio_i2c_exit(void)
{
    iounmap((void*)reg_gpio0_base_va); //取消映射
    misc_deregister(&gpioi2c_dev);  //將註冊時申請的資源釋放
}
module_init(gpio_i2c_init); //聲明入口函數,模塊被insmod時會自動調用gpio_i2c_init
module_exit(gpio_i2c_exit); //聲明出口函數,模塊被rmmod時會自動調用gpio_i2c_exit


4.編寫unlock_ioctl,open,close函數

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從機配置和兩者之間的通信協議。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章