Submitted by admin on Sat, 01/09/2010 - 01:42
ก่อนที่เราจะไปถึงจุดที่จะพัฒนาโปรแกรมเพื่อควบคุม I2C Devices เราจำเป็นต้องทำความเข้าใจการเชื่อมต่อระหว่างฮาร์แวร์กับLinux กันสักนิด ในงาน Embedded System ทั่วๆไปที่ไม่ได้มีความซับซ้อนเราสามารถที่จะพัฒนาโคดไปสั่งฮาร์แวร์ได้โดยตรง แต่ในEmbedded Systemที่รันภายใต้ Operating System การใช้Resourcesต่างๆของระบบจำเป็นต้องมีกฏกติกา การเชื่อมต่อฮาร์แวร์จะไม่ทำโดยตรงจากโปรแกรมที่อยู่ใน User Space แต่จะทำผ่านโปรแกรมพิเศษที่วิ่งภายใต้การควบคุมของ Kernel ที่เรียกกันว่า Device Driver ซึ่งมีข้อดีต่างๆมากมายอาทิเช่น
- User Application จะแยกออกจาก HardWare ทำให้โปรแกรมไม่ยึดติดกับฮาร์แวร์ใดๆเพียงเจ้าเดียว เช่นโปรแกรมที่พัฒนาบน ARM ก็สามารถเอาไปใช้บน MIPS หรือ PC ได้เมื่อCross Compile เพียงแต่เปลี่ยน Device Driver ให้เหมาะกับฮาร์แวร์นั้นๆ
- ระบบมีความยืดหยุ่น โคดที่เกี่ยวข้องกับฮาร์แวร์ไม่ได้ถูกฝังลงใน Kernel จะใช้ก็โหลด ไม่ใช้ก็ไม่โหลด
- ฮาร์แวร์สามารถถูกใช้ได้จากผู้ใช้หลายๆคน
การใช้งาน I2C Bus บน Linux ก็เช่นกัน จะกระทำผ่าน Device Driver ซึ่งมีโครงสร้างดังนี้

i2c บน Linux จะประกอบด้วย Device Drivers หลายตัวทำงานเชื่อมต่อกันเป็นชั้นๆ โดยแต่ละตัวมีหน้าที่ต่างๆกันดังนี้
i2c-dev
i2c-dev จะเป็นตัวที่สร้างไฟล์เทียมขึ้นมาภายในไดเร็คทอรี่ /dev/i2c/0 เพื่อให้ User Application ติดต่อผ่านไปยัง i2c bus โดยหลักการแล้ว Linux OS จะมองฮาร์ดแวร์ทุกอย่างเปรียบเสมือนไฟล์ตัวหนึ่ง ตัวอย่างเช่น หากเราจะส่งค่าไปยัง i2c bus ก็สามารถทำได้โดยการเปิดไฟล์ /dev/i2c/0 แล้ว write ค่าไปยังไฟล์ที่เป็นตัวแทนของฮาร์แวร์นั้นๆ
ตัวอย่างโปรแกรมการควบคุม i2c bus ผ่าน i2c-dev
int file;
int adapter_nr = 0; /* probably dynamically determined */
char filename[20];
snprintf(filename, 19, "/dev/i2c-%d", adapter_nr);
file = open(filename, O_RDWR); เปิดไฟล์ตัวแทน i2c bus
if (file < 0) {
/* ERROR HANDLING; you can check errno to see what went wrong */
exit(1);
}
int adapter_nr = 0; /* probably dynamically determined */
char filename[20];
snprintf(filename, 19, "/dev/i2c-%d", adapter_nr);
file = open(filename, O_RDWR); เปิดไฟล์ตัวแทน i2c bus
if (file < 0) {
/* ERROR HANDLING; you can check errno to see what went wrong */
exit(1);
}
int addr = 0x40; /* The I2C address */ กำหนด Slave Address ของอุปกรณ์ที่จะติดต่อ
if (ioctl(file, I2C_SLAVE, addr) < 0) {
/* ERROR HANDLING; you can check errno to see what went wrong */
exit(1);
}
/* ERROR HANDLING; you can check errno to see what went wrong */
exit(1);
}
/* Using I2C Write, equivalent of
i2c_smbus_write_word_data(file, register, 0x6543) */
i2c_smbus_write_word_data(file, register, 0x6543) */
buf[0] = register;
buf[1] = 0x43;
buf[2] = 0x65;
if (write(file, buf, 3) ! =3) { ส่งค่าไปยัง i2c Bus
/* ERROR HANDLING: i2c transaction failed */
}
buf[1] = 0x43;
buf[2] = 0x65;
if (write(file, buf, 3) ! =3) { ส่งค่าไปยัง i2c Bus
/* ERROR HANDLING: i2c transaction failed */
}
i2c-core
i2c-core จะเป็นตัวที่เชื่อมต่อ i2c-dev และ i2c-algo-bit เข้าด้วยกัน โดย i2c-dev กับ i2c-algo-bit จะลงทะเบียนกับ i2c-core เอาไว้ว่าตัวเองมี functions อะไรบ้าง
i2c-algo-bit
i2c-algo-bit จะเป็น Device Driver ที่จะรู้ว่าจะเซ็ท SDA/SCL ให้ High หรือ Low ยังไงถึงจะเป็น start bit, stop bit , read data, write data และอื่นๆ แต่จะไม่รู้ว่าการทำให้ ฮาร์แวร์จริงๆ High หรือ Low เป็นยังไง ต้องอาศัย Function ที่อยู่ใน i2c-mips-gpio จึงได้ชื่อว่า Algorithm-bit คือรู้แค่หลักการ
i2c-mips-gpio
i2c-mips-gpio จะเป็น Driver ที่จะรู้ว่าถ้าจะทำให้ SDA/SCL High หรือ Low จะต้องทำยังไง โดยตัวมันจะทำการลงทะเบียน Function กับ i2c-algo-bit ให้เรียกใช้งาน
จะเห็นว่า Device Drivers ทั้งสี่ตัวนี้จะทำงานประสานกัน โปรดสังเกตุว่า i2c-dev, i2c-core, i2c-algo-bit จะไม่เปลี่ยนแปลงไปตามฮาร์แวร์ จะมีเพียงตัวเดียวเท่านั้นที่ขึ้นตรงกับว่าฮาร์แวร์เป็นอะไรนั่นคือตัว i2c-mips-gpio ดังนั้นสมมติว่าถ้ามีการPortการใช้งาน i2c busไปยัง Platform อื่นเช่น ARM เราก็เพียงแต่ต้องเขียน ตัวสุดท้ายใหม่เท่านั้น แต่ที่เหลือจะคงเดิม นี่คือข้อได้เปรียบของการใช้ Device Driversในการควบคุมฮาร์แวร์
ดังนั้นจึงไม่แปลกใจที่ i2c-dev, i2c-core และ i2c-algo-bit จะเป็นส่วนหนึ่งที่ติดมากับซอสโคดมาตราฐานของ Kernel ในขณะที่ตัว i2c-mips-gpio นั้นจะต้องเพิ่มเติมเข้ามาในระบบ
Tips
สำหรับผู้ที่สนใจจะเจาะลึกมากขึ้นกับ i2c Device Drivers ให้ดูซอสโคดจาก ~/openwrt/kamikaze/build_dir/linux-brcm-2.4/linux-2.4.37.5/drivers/i2c
มาถึงจุดนี้ เราก็ต้องหาทางเพิ่ม Device Drivers สี่ตัวที่กล่าวข้างต้นให้กับระบบของเรา หลังจากมองหาก็ไปเจอลิงค์นี้เข้า
ปรากฏว่าลิงค์นี้อ้างอิง kamikaze 7.09 ซึ่งเมื่อทำตามแล้วใช้ไม่ได้กับ kamikaze 8.09.1 ที่เราใช้อยู่ในปัจจุบัน จำเป็นต้องมีการแก้ไขดัดแปลง Make fileให้เหมาะสมเสียก่อน หลังจากปลุกปล้ำกันพักใหญ่ก็ได้สูตรสำเร็จดังนี้ครับ
1. เปิดโปรแกรม VirtualBox เพื่อวิ่ง Ubuntu นะครับ หลังจากนั้นก็เปิดโปรแกรม Terminal แล้วพิมพ์
$ cd ~/openwrt/kamikaze เพื่อไปยัง Directory ที่ใช้สร้าง Image
$ svn update ทำการ update source code
$ make menuconfig ทำการ Config kernel เพื่อเพิ่ม i2c drivers

โปรดสังเกตุว่า Target System ของเราจะเป็น BCM947xx/953xx [2.4] คือเราจะใช้ kernel version 2.4 (เนื่องจาก kernel version 2.6 มีการเปลี่ยน driver ของ wifi ที่ไม่มีเสถียรภาพและมีปัญหาในการใช้งาน) หลังจากนั้นเลื่อนปุ่มหัวลูกศรลงไปที่ Kernel modules แล้วกด Enter

2. เลื่อนปุ่มหัวลูกศรลงไปยัง I2C support แล้วกด Enter
อ้าว!!! ทำไมมันไม่ไปไหนล่ะครับ!!! นี่คือปัญหาแรกที่เราต้องแก้ครับ
ให้ exit ออกจาก make menuconfig ก่อนนะครับโดยไม่ต้องเซฟไฟล์ครับ
หลังจากกลับไปอ่านเอกสารประกอบจากเวบ openwrt.org ก็ได้ใจความตรงนี้ครับ http://kamikaze.openwrt.org/docs/openwrt.html#x1-470002.1.3
Openwrt ประกอบด้วย package สองชนิดอันแรกเป็นโปรเจ็คแยกต่างหาก และอีกอันมากับ Mainline Kernel ซึ่งจะอยู่ใน Directory package/kernel/modules/*.mk
ว่าแล้วก็นี่เลยครับ
$ ls package/kernel/modules

โปรดสังเกตุครับว่ามีไฟล์ที่ชื่อว่า i2c.mk อยู่
ว่าแล้วก็ลองดูเลยครับว่ามีอะไรอยู่ในไฟล์นี้
$ vi package/kernel/modules/i2c.mk

scroll down จะเจอ DEPENDS:=LINUX_2_6 ให้แก้เป็น DEPENDS:=LINUX_2_4 ครับ ด้วยการเลื่อนCursor ไปยังหมายเลข 6 กดปุ่ม r แล้ว 4 จากนั้นกดปุ่ม :x แล้วปุ่ม enter เพื่อ save ครับ
จากนั้นก็
$ make menuconfig
แล้วกลับไปยัง Kernel modules/I2C support คราวนี้จะเห็นว่ามี kmod-i2c-core ให้เลือก ให้กดปุ่ม space bar ให้เป็นเครื่องหมายดอกจัน เพื่อเลือก kmod-i2c-core และ kmod-i2c-algo-bit ดังรูปครับ

Tips
ในการเลือก Device Driver หากช่องข้างหน้าเลือกเป็น M แสดงว่าเราต้องการสร้าง driver ให้เป็น file ที่นี่นามสกุล .ipk แยกจาก Kernel ซึ่งจะต้อง Load เข้าไปทีหลังเวลาใช้ แต่ถ้าเลือกช่องข้างหน้าเป็นเครื่องหมาย * แสดงว่าเราต้องการให้รวมอยู่กับ Kernel เลย ซึ่งในกรณีของเรา เราจะใช้ I2C แน่นอนจึงเลือกให้รวมอยู่ใน Kernel
3. คราวนี้ออกจากโปรแกรมพร้อมทั้ง save config นะครับแล้วลองสร้าง Image กันเลยครับด้วยคำสั่ง
$ make
ปรากฏว่ามันจะค้างเติ่งเลยครับ ฮ่าๆๆ อยากรู้ว่าทำไมค้าง กด Ctrl-C เพื่อยกเลิกการ make ก่อนครับ
สำหรับ make มันจะมีตัวช่วยในการแก้ปัญหาครับ คราวนี้ลองใหม่ด้วยคำสั่ง
$ make V=99
คราวนี้มันทำอะไรมันจะรายงานเราหมดเลยครับ

สาเหตุที่ค้างเนื่องจากมันรอให้เรายืนยันข้อมูลการแก้ไข .config file ใหม่ ให้เรากด Enter เพื่อยืนยันค่า Default ไปเรื่อยๆครับ

หลังจากนั้นก็จะมีการ Compile พักใหญ่ๆแล้วจบลงด้วย Error 2
มาลองแก้ปัญหาการ Compile Kernel กันดูนะครับ มองย้อนขึ้นไปจะเห็นสาเหตุของ Error ลองตรวจสอบด้วยการ

จะเห็นว่า ในระหว่างการ make มีการพยายามอ้างอิงถึง directory algos ในขณะที่ของจริงไม่มี ลองกลับไปดู ไฟล์ i2c.mk อีกครั้ง

จะเห็นว่า make ไฟล์อ้างอิงถึง algos ให้ตัดออก(ถ้าใช้ vi ให้กดปุ่ม x เพื่อลบทีละตัวอักษร)แล้วแก้ไขเป็น
CONFIG_I2C_ALGOBIT:drivers/i2c/i2c-algo-bit
เซฟไฟล์ i2c.mk แล้วสั่งให้ make ใหม่ด้วย
$ make V=99
เราก็จะ Compile สำเร็จทุกประการ!!!!
4. ภายใต้ Directory ./staging_dir/target-mipsel_uClibc-0.9.30.1/root-brcm-2.4/ ซึ่งจะกลายไปเป็น Root Directory ของระบบ เมื่อดู lib/modules/2.4.37.5/ จะเห็น Device Drivers สามตัวแรกที่เราต้องการ

ต่อไปจะเป็นการเพิ่ม package ภายนอกที่ไม่ได้มาจาก mainline kernel นะครับเพื่อสร้าง Device Driver i2c-mips-gpio
5. Copy source files จากเน็ทไปยังตำแหน่งต่างๆดังนี้
ภายใต้ Directory ~/openwrt/kamikaze
$ mkdir package/broadcom-i2c
$ mkdir package/broadcom-i2c/src
$ cd package/broadcom-i2c
$ cd src
$ wget http://www.ratnet.stw.uni-erlangen.de/~simischo/openwrt/package/broadcom-i2c/src/i2c-mips-gpio.c
$ cd ~/openwrt/kamikaze
สรุปคือเราเพิ่มไฟล์สามไฟล์ตามตำแหน่งดังนี้
package/broadcom-i2c/Makefile
package/broadcom-i2c/src/Makefile
package/broadcom-i2c/src/i2c-mips-gpio.c
package/broadcom-i2c/src/Makefile
package/broadcom-i2c/src/i2c-mips-gpio.c
6. เนื่องจากได้สาธิตการแก้ปัญหาของการ Compile Kernel ไปแล้ว จึงจะขอเฉลยเลยว่า package/broadcom-i2c/Makefile มีปัญหาและต้องแก้ไขดังนี้

- แก้จาก DEPENDS:=@LINUX_2_4_BRCM ไปเป็น DEPENDS:=@LINUX_2_4 (จะทำให้ I2C Bus ปรากฏเป็นตัวเลือกใน Kernel Modules เมื่อทำ make menuconfig)
- ตัดข้อความที่เกี่ยวข้องกับ DESCRIPTION ทิ้งไปเนื่องจาก Obsolete แล้วทำให้ Compile ไม่ผ่าน

หลังจากนั้นก็ให้
$ make menuconfig
แล้วไปยัง Kernel Modules/I2C Bus แล้วกด space bar เพื่อให้ข้างหน้า kmod-broadcom-i2c เป็นเครื่องหมาย *
ออกจากmake menuconfig แล้ว Save Configuration
เนื่องจาก i2c-mips-gpio จะเป็น Driver ที่ต่อกับฮาร์ดแวร์จริง ดังนั้นเราจึงจำเป็นต้องแก้ไข GPIO ของ SDA/SCL ให้ตรงกับที่เรากำหนดไว้
SDA = White LED = GPIO 2
SCL = DMZ LED = GPIO 7
ดังนั้นแก้ไขไฟล์ package/broadcom-i2c/src/i2c-mips-gpio.c โดยให้มีข้อความตรงดังนี้
#define DEFAULT_GPIO_SCL 7
#define DEFAULT_GPIO_SDA 2
#define DEFAULT_GPIO_SDA 2
Save file แล้วทำการ Make ด้วยคำสั่ง
$ make V=99

ในที่สุดเราก็ได้ Device Driver i2c-mips-gpio ตัวสุดท้ายครบตามต้องการ
เหนื่อยไม๊ครับ ฮ่าๆๆ พักก่อนนะครับ ตอนต่อไปจะมาว่าด้วยการสร้าง i2ctools เพื่อทดสอบ i2c Bus ของเราโดยที่ไม่ต้องเขียนโปรแกรมเองครับ...