之前一直在用I2C接口,由於老是有線程的例子就一直沒有去深刻的瞭解,今天分析了一下在linux下通用GPIO模擬I2C的程序。
I2C的驅動是用雜項設備實現的,這也是一種比較簡單的實現方式。經過 misc_register(&mygpioi2c_dev);來註冊本身的雜項設備,此函數中會自動建立設備節點,即設備文件。無需mknod指令建立設備文件。由於misc_register()會調用class_device_creat或者device_creat。主設備號也不用管,是最簡單的一種驅動了。註冊後經過miscdevice結構體關聯的file_operations的操做來實現驅動程序的open,read,write接口。 node
- static struct file_operations gpioi2c_fops = {
- .owner = THIS_MODULE,
- .ioctl = gpioi2c_ioctl,
- .open = gpioi2c_open,
- .release = gpioi2c_close
- };
具體的操做在IOCTL中實現
- int gpioi2c_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
- {
- unsigned int val;
-
- char device_addr, reg_addr;
- short reg_val;
-
-
- switch(cmd)
- {
- case GPIO_I2C_READ:
- val = *(unsigned int *)arg;
- device_addr = (val&0xff000000)>>24;//設備地址
- reg_addr = (val&0xff0000)>>16;//設備中讀寫寄存器地址。對於兩字節的地址須要本身去定義數據傳送的方法。
-
- reg_val = gpio_i2c_read(device_addr, reg_addr);
- *(unsigned int *)arg = (val&0xffff0000)|reg_val;
- break;
-
- case GPIO_I2C_WRITE:
- val = *(unsigned int *)arg;
- device_addr = (val&0xff000000)>>24;
- reg_addr = (val&0xff0000)>>16;
-
- reg_val = val&0xffff;
- gpio_i2c_write(device_addr, reg_addr, reg_val);
- break;
-
- default:
- return -1;
- }
- return 0;
- }
- <pre name="code" class="cpp" style="font-family: 宋體, Arial; line-height: 1.5; ">unsigned char gpio_i2c_read(unsigned char devaddress, unsigned char address)
- {
- int mydata;
- i2c_start_bit();
- i2c_send_byte((unsigned char)(devaddress));
- i2c_receive_ack();
- i2c_send_byte(address);
- i2c_receive_ack();
- i2c_start_bit();
- i2c_send_byte((unsigned char)(devaddress) | 1);
- i2c_receive_ack();
- mydata = i2c_receive_byte();
- i2c_stop_bit();
- </pre><span style="font-family:宋體,Arial"><span style="line-height:1.5">}<br>
- 其中的規則就是I2C協議的規定.下面是開始信號的模擬</span></span>
- <pre style="font-family:宋體,Arial; line-height:1.5"></pre>
- <pre name="code" class="cpp" style="font-family: 宋體, Arial; line-height: 1.5; ">static void i2c_start_bit(void)
- {
- DELAY(1);//這裏的時序須要本身模擬。用一個for循環就能夠
- i2c_set(SDA | SCL);//sda.scl設爲1
- DELAY(1);
- i2c_clr(SDA);//在SCL爲1時拉低SDA,表示發送開始信號
- DELAY(2);
- }</pre>
- <pre></pre>
delay具體多久須要計算,感受能夠用定時器實現,以前就寫過一個定時1ms的驅動,由於經過linux應用層單步操做時間間隔最好也是10ms,當時寫過selest和一些別的方式作過測試,過幾天把這部分整理出來。。回到這個驅動上來。。。。
在作模擬時,總結出了一些要點:
1.本身模擬時序時I2C的佔空比並非必定的,只要符合其數據在上升沿讀取這一規律以及高電平不變數據就行
2.釋放SDA就是把SDA拉高
3.應答位誰接受誰產生
4.數據是在上升沿鎖存,因此在須要讀數據前最好把模擬成CL的GPIO作一下拉低再拉高處理,不要直接拉高。
爲何隨機讀要產生兩個開始信號呢,由於隨機讀須要CPU發送deviceaddress和regaddress,數據流向是cpu->e2prom 而肯定了地址後須要改變數據流向。所以須要重新發送開始信號,爲何呢,由於I2C裏面的deviceaddress裏面包含有讀寫信息,
即若是寫地址是deviceaddress,
則讀地址是deviceaddress+1 所以能夠這樣講一個開始信號肯定當前是讀仍是寫,要改變讀寫,對不起,只能從新開始。下面是IO口模擬寫的時序
- void gpio_i2c_write(unsigned char devaddress, unsigned char address, unsigned char data)
- {
- i2c_start_bit();
- i2c_send_byte((unsigned char)(devaddress));
- i2c_receive_ack();
- i2c_send_byte(address);
- i2c_receive_ack();
- i2c_send_byte(data);
- i2c_stop_bit();
- }
能夠看到這裏只有一次起始,模擬的時候須要注意。以上就是linux模擬I2C驅動的一點心得。