Powered by Drupal, an open source content management system

การใช้งาน GPIO บน Embedded Linux

การใช้งาน GPIO บน Embedded Linux

 
 ในโลกของ Embedded System ทั่วไป การควบคุม GPIO ทำได้ง่ายดายเพียงแค่อ่านหรือเขียนลง Control Register แต่สำหรับผู้ที่เริ่มใช้ Embedded Linux แล้วมักจะมีคำถามเสมอว่าการใช้งาน GPIO ทำได้อย่างไร บทความนี้จะสาธิตการใช้งาน GPIO กับ Embedded Linux บนบอร์ด MINI2440  ซึ่งสามารถนำไปประยุกต์ใช้งานได้กับบอร์ด Embedded Linux อื่นๆ

Sysfs

 ในระบบ Embedded System การติดต่อฮาร์แวร์จะไม่กระทำโดยตรง แต่จะทำผ่านส่วนที่เรียกว่า Device Driver โดยผู้ใช้จะมองฮาร์ดแวร์ทุกอย่างเป็นเสมือนไฟล์ เพียงแต่ไฟล์นี้จะเป็นไฟล์ชนิดพิเศษที่เมื่อ Read/Write ข้อมูลลงไปในไฟล์นี้แล้ว จะเหมือนการ read/write ข้อมูลไปยังฮาร์แวร์
  ใน Linux kernel version 2.4 ไฟล์พิเศษนี้จะอยู่ภายใต้ไดเร็คทอรี่ /dev และจะต้องถูกสร้างขึ้นมาในระบบโดยผู้ใช้ก่อนการใช้งาน ซึ่งถ้าหากฮาร์แวร์ถูกถอดออกไปจากระบบ ไฟล์นี้ก็ยังดำรงอยู่ยกเว้นผู้ใช้จะไปทำการลบออกจากระบบเอง สำหรับ Linux kernel version 2.6 ได้มีการคิดค้นระบบไฟล์สำหรับฮาร์แวร์ใหม่โดยไฟล์ที่ว่านี้เรียกว่า Sysfs ซึ่งเป็น virtual file system และจะถูกสร้างขึ้นมาในลักษณะเดียวกับ ram drive ระบบจะสร้างไฟล์ตามฮาร์แวร์ที่มีอยู่จริงโดยอัตโนมัติ  ทำให้ผู้ใช้ไม่ต้องสร้างหรือลบไฟล์ด้วยตัวเอง สำหรับ Sysfs นี้จะปรากฏอยู่ในระบบภายใต้ไดเร็คทอรี่ /sys
 รายละเอียดเพิ่มเติมของ sysfs สามารถหาอ่านได้จาก http://www.kernel.org/pub/linux/kernel/people/mochel/doc/papers/ols-2005/mochel.pdf

GPIO ภายใต้ sysfs

 

        เมื่อบูทระบบแล้ว Sysfs จะสร้างลิงค์ /sys/class/gpio/gpiochipN เพื่อใช้ติดต่อกับ IO PORT ของระบบดังนี้

 

โดย gpiochip หนึ่งตัวก็จะแทน port หนึ่งตัว ลองดูตัวอย่างโครงสร้างของไดเร็คทอรี่ภายใต้ gpiochip32 จะเป็นดังนี้

 

โดยไฟล์ label, base และ ngpio จะให้รายละเอียดของ GPIO ที่มันควบคุมดังนี้

 

ซึ่งจากข้อมูลเบื้องต้น
- label แสดงว่า gpiochip32 จะควบคุม  Port B ของชิพ s3c2440
- base แสดงว่าหมายเลขที่ใช้ในการติดต่อกับ bit 0 ของ Port B นี้คือ 32
- ngpio แสดงว่าจำนวนบิททั้งหมดที่สามารถสั่งงานได้ใน port นี้มี 16 ตัว
ดังนั้น สมมติเราต้องการจะควบคุม bit 6 ของ Port B หรือ GPB6 หมายเลขประจำบิทนี้ก็คือ 32 + 6 = 38 นั่นเอง

การควบคุม GPIO

 

เราลองมาเปิดปิด LED2 บนบอร์ด mini2440 กันดูนะครับ จากวงจร จะเห็นว่า LED2 ต่ออยู่กับ GPB6 ซึ่งจากการคำนวณข้างต้นมีหมายเลขประจำตัวคือ 38

เริ่มต้นการใช้งานด้วยการส่งตัวเลขประจำบิทที่เราจะควบคุมไปยังไฟล์พิเศษที่ชื่อ export ด้วยคำสั่ง echo
โปรดสังเกตุว่าจะมี link ใหม่ถูกสร้างขึ้นที่ชื่อว่า gpio38

 

ลองตรวจสอบดูจะเห็นโครงสร้างไฟล์ภายใต้ link ดังนี้

 

ไฟล์ที่น่าสนใจคือ direction และ value โดย
- direction เมื่ออ่านค่าจากไฟล์นี้ จะได้ค่า in หรือ out เพื่อบอกทิศทางว่าเป็น input หรือ output และเมื่อเขียนสามารถกำหนดทิศทางของบิทได้ โดยค่าที่เขียนสามารถเป็น in หรือ out  โดยปกติแล้วเมื่อเขียนค่าด้วย out บิทจะถูก initialize ให้เป็น low ไปพร้อมๆกัน  แต่ถ้าต้องการเปลี่ยนทิศทางให้เป็น out ในขณะเดียวกัน initialize เป็น high ก็สามารถส่งคำสั่ง high ไปแทนได้
- value อ่านค่าได้ 1 หรือ 0 สำหรับ input และ ส่งค่า 1 หรือ 0 สำหรับควบคุม output

ลองคำสั่งดังต่อไปนี้
 

cat gpio38/direction แสดงให้เห็นว่าเริ่มต้น IO port GPB6 จะถูกกำหนดไว้เป็น  input
echo out > gpio38/direction จะทำการเซ็ท IO port GPB6 ให้เป็น ouput ในขณะเดียวกันจะ Initialize ค่าให้เป็น low โปรดสังเกตุว่า LED2 บนบอร์ดจะติด

ทดลองส่งค่า 1 ไปยัง gpio38/value จะทำให้ LED2 บนบอร์ดดับ จากนั้นยกเลิกการใช้งานบิท GPB6 ด้วยคำสั่ง echo 38 > unexport ตรวจสอบดูจะเห็นว่า link gpio38 ที่ใช้ควบคุมบิท GPB6 จะหายไป

 

ลองมาทดสอบ input ดูบ้างนะครับ
 

เราจะมาทดสอบการใช้งาน ปุ่มกด K6 ซึ่งจากวงจรจะเท่ากับ GPG11
 

จากการตรวจสอบจะเห็นว่าบิท 0 ของ GPIOG เท่ากับ 192 ดังนั้น GPG11 ก็จะมีหมายเลขประจำบิทเท่ากับ 192 + 11 = 203

เริ่มต้นการใช้งานบิท 203 ด้วยการ export จะเห็นว่า link gpio203 จะถูกสร้างขึ้น

 

ตรวจสอบสถานะของบิทดูจะเห็นว่าเป็น input และ มีค่าเท่ากับ 1 ซึ่งสอดคล้องกับวงจรที่มีการ pull high ผ่าน R22

 

ทำการกดปุ่ม K6 ค้างไว้แล้วตรวจสอบค่าจะเห็นว่าเปลี่ยนไปเป็น 0

 

เมื่อเสร็จสิ้นการใช้งานแล้วเราสามารถที่จะยกเลิกการใช้งานบิท GPG11 ด้วยคำสั่ง echo 203 > unexport จะสังเกตุได้ว่า link gpio203 จะหายไป

 

การควบคุม GPIO จากโปรแกรม

ติดตั้ง cross tool chain ตามลิงค์ http://project4fun.com/node/10
ทำการคอมไพล์โปรแกรมด้วยคำสั่ง 
arm-angstrom-linux-gnueabi-gcc gpio.c -o gpio
จะได้เอาท์พุทไฟล์ที่ชื่อว่า gpio หลังจาก copy ไปยัง mini2440 แล้วให้ execute ด้วยคำสั่ง ./gpio
จะสังเกตุเห็น LED2 กระพริบด้วยความถี่ 1Hz และถ้ากดปุ่ม  K6 ค้างไว้เป็นเวลาสามวินาทีก็จะออกจากโปรแกรม

 

/********************************
 *           gpio.c             *
 ********************************/

#include <string.h>
#include <stdio.h>
#include <stdlib.h>

FILE *fp;
char read_value[4];

int write(char *fname,char *mode,char *value)
{
 if ((fp = fopen(fname, mode)) == NULL)
  {
   printf("Cannot open %s.\n",fname);
   return 1;
  }
  fwrite(value, sizeof(char), strlen(value), fp);
  fclose(fp);
  return 0;
}

int read(char *fname,char *mode,char *value)
{
 int nStrLen;
 if ((fp = fopen(fname, mode)) == NULL)
  {
   printf("Cannot open %s.\n",fname);
   return 1;
  }
  nStrLen = fread(value, sizeof(value), 1, fp);
  value[nStrLen+1] = 0;
  fclose(fp);
  return 0;
}
int main(int argc, char** argv)
{

 printf("\n**********************************\n"
   "*  Welcome to PIN Blink program  *\n"
   "*  ..blinking pin GPB6 (LED2)..  *\n"
   "*  .......rate of 1 Hz.........  *\n"
   "**********************************\n");
 
 //Integer to keep track of whether we want on or off
 int toggle = 1;
 
 //Integer to keep track of switch k6 hold time
 int k6counter = 0;
 
 //Using sysfs we need to write "38" to /sys/class/gpio/export
 //This will create the folder /sys/class/gpio/gpio38 to control LED2
 if (write("/sys/class/gpio/export","ab","38"))
  return 1;
 printf("...export file accessed, new pin now accessible\n");
 
 //SET DIRECTION
 //Open the LED's sysfs file in binary for reading and writing, store file pointer in fp
 if (write("/sys/class/gpio/gpio38/direction","rb+","out"))
  return 1;
 printf("...direction set to output\n");
 
 //SET VALUE
 //Open the LED's sysfs file in binary for reading and writing, store file pointer in fp
 if (write("/sys/class/gpio/gpio38/value","rb+","1"))
  return 1;
 printf("...LED2 = 1...\n");
   

 //Using sysfs we need to write "203" to /sys/class/gpio/export
 //This will create the folder /sys/class/gpio/gpio203 to control switch K6
 if (write("/sys/class/gpio/export","ab","203"))
  return 1;


 //Run an infinite loop - Press and hold switch K6 for 3 minutes to exit this program
 while(k6counter < 3)
 {
  read("/sys/class/gpio/gpio203/value","rb+",read_value);
  printf("...K6 = %s...\n",read_value);
    
  toggle = !toggle;
  if(toggle)
  {
   //Write our value of "1" to the file
   if (write("/sys/class/gpio/gpio38/value","rb+","1"))
    return 1;
   printf("...LED2 = 1...\n");
  }
  else
  {
   //Write our value of "0" to the file
   if (write("/sys/class/gpio/gpio38/value","rb+","0"))
    return 1;
   printf("...LED2 = 0...\n");
  }

  if( strcmp( read_value, "0" ) == 0 )
   k6counter++;
  else
   k6counter = 0;

  //Pause for one second
  sleep(1);
 }
 
 // Delete no longer used gpios
 if (write("/sys/class/gpio/unexport","ab","38"))
  return 1;
 if (write("/sys/class/gpio/unexport","ab","203"))
  return 1;
 return 0;
}

หวังว่าตัวอย่างนี้คงเป็นแนวทางในการใช้งาน GPIO ได้เป็นอย่างดีนะครับ เนื่องจากการควบคุมแบบนี้อิงมาตราฐานของLinux ดังนั้นจึงสามารถนำไปประยุกต์ใช้กับบอร์ดอื่นๆที่รองรับ Kernel 2.6 ได้ด้วยครับ

เอกสารอ้างอิง
1. http://www.mjmwired.net/kernel/Documentation/gpio.txt
2. http://www.avrfreaks.net/wiki/index.php/Documentation:Linux/GPIO
3. http://www.kernel.org/pub/linux/kernel/people/mochel/doc/papers/ols-2005/mochel.pdf