2017年8月18日 星期五

Linux GPIO Manipulation via System File System, sysfs


     To access GPIO, the most intuitive way is to write a driver for this purpose. This approach is most generic,  but the Linux driver model is complicated, It is requisite to spend time to learn that. Besides,  in the modern operation systems, the drivers have been treated as the external parts of operation system.  Applying a driver to the operation system means the operation system has been patched. If Somebody writes an inappropriate driver, something might be wrong but the system would keep working, it is very difficult to seek out the bug.
    The goal is to access GPIO only, It is not necessary to be in anguish in coding a driver.
    To eschew to  write a driver but the accessing GPIO is possible, the most simple way is to exploit  System file system, sysfs. That concept of sysfs is that everything is file under Linux, the accessing behaviors would correspond to the manipulations respectively.

My development board is openWRT system, which is  :

root@GL-AR150:/tmp# cat /proc/cpuinfo 
system type  : Atheros AR9330 rev 1
machine   : TP-LINK TL-WR720N v3
processor  : 0
cpu model  : MIPS 24Kc V7.4


    零.  Control Existing LED.
     In my development board, the folder, /sys is where sysfs existing. The LEDs under the path :


root@GL-AR150:/tmp# ls /sys/class/leds/
ath9k-phy0           tp-link:blue:eth1    tp-link:blue:wlan
tp-link:blue:eth0    tp-link:blue:system


There are file LEDs under the path, but for my board, the physical LEDs are only four. the ath9k-phy0 does not exist.

Adopt the follow command to turn on the specific LED :

root@GL-AR150:/tmp# echo 1 > /sys/class/leds/tp-link\:blue\:wlan/brightness 

    The the line to turn off the LED:

root@GL-AR150:/tmp# echo 0 > /sys/class/leds/tp-link\:blue\:wlan/brightness

  Query the LED current status:


root@GL-AR150:/tmp# cat /sys/class/leds/tp-link\:blue\:wlan/brightness 
1

1 represents bright, 0 is dim, it is very natural.


the C code for corresponding to above action is :


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

#include <fcntl.h>

#define BUFFER_MAX       (8)

int SetSysfsLEDStatus(char *p_led_sysfs_path, int status)
{
 char status_str[BUFFER_MAX];
 char led_sysfs_location[PATH_MAX];
 int sysfs_led_fd;

 memset(&led_sysfs_location[0], 0, PATH_MAX);

 strncpy(&led_sysfs_location[0], p_led_sysfs_path, PATH_MAX);

 {
  int path_len;
  path_len = strlen(led_sysfs_location);

  if( '/'== led_sysfs_location[path_len - 1])
   strncat(&led_sysfs_location[path_len - 1], "/brightness", PATH_MAX);
  else
   strncat(&led_sysfs_location[0], "/brightness", PATH_MAX);
 }

 sysfs_led_fd = open(&led_sysfs_location[0], O_RDWR);

 if (-1 == sysfs_led_fd)
 {
  printf("Failed to open %s for writing!\n", &led_sysfs_location[0]);
  return -1;
 }/*file description*/


 {
  char current_status[BUFFER_MAX];
  if(0 > read(sysfs_led_fd, &current_status[0], BUFFER_MAX))
  {
   printf("read current status fail\r\n");
   close(sysfs_led_fd);
   return -2;
  }

  printf("current status = %s\r\n", &current_status[0]);
 }


 switch(status)
 {
 case 0:
  snprintf(&status_str[0], BUFFER_MAX, "0");
  break;
 case 1:
  snprintf(&status_str[0], BUFFER_MAX, "1");
  break;
 default:
  return -2;
 }/*switch value*/


 if (1 != write(sysfs_led_fd, &status_str[0], strlen(status_str)))
 {
  printf("Failed to write value!\n");
  close(sysfs_led_fd);
  return -3;
 }/*if write*/


 close(sysfs_led_fd);
 printf("status %d has been set \r\n", status);

 return 0;

}/*SetSysfsLEDStatus*/


int main(int argc, char *argv[])
{
 int k;

 if(3 > argc)
 {
  printf("not enough argments\r\n"
   "\tit should be %s sysfs_led_path status\r\n",
   argv[0]);

  return -1;
 }

 SetSysfsLEDStatus(argv[1], '0'== argv[2][0] ? 0 : 1);

 return 0;

}/*main*/

And the Makefile be :


OPENWRT_ROOT=/home/gaiger/openwrt-cc/staging_dir
OPENWRT_TOOLCHAIN_PATH = $(OPENWRT_ROOT)/toolchain-mips_34kc_gcc-4.8-linaro_uClibc-0.9.33.2


CC = $(OPENWRT_TOOLCHAIN_PATH)/bin/mips-openwrt-linux-gcc



CFLAGS := -g
all:
        $(CC)  $(CFLAGS)    main.c  -o sysfs_led_control
clean:
        rm sysfs_led_control -f

The Makefile is just to be Specified the cross-compiler  location only.

After Cross-compiling (It should be called parasitic compiling) and run the binary :


root@GL-AR150:/tmp# ./sysfs_led_control  /sys/class/leds/tp-link\:blue\:wlan/ 1
current status = 0

status 1 has been set

root@GL-AR150:/tmp# ./sysfs_led_control  /sys/class/leds/tp-link\:blue\:wlan/ 0
current status = 1

status 0 has been set


To here, You should know how to control the existing LEDs.




一. Connect a  External LED in the preserved GPIO interface and control it.
 It is demonstrate how to output a signal via GPIO.

Inquire the circuit diagram, there are GPIOs preserved for the user :

The pin consponding to the diagram is near ethernet lan port :


I choose  GPIO20  to connect a LED ( with a protective resistor in 470 Ohm to avoid burning out).

  that is :
            
the resistor value is not confined as 470 Ohm only,  Any resistor between 200 to 2000k Ohm could be adopted to protect the LED.

After the connection has been set,  type the following command in the terminal:


root@GL-AR150:/tmp# echo 20 > /sys/class/gpio/export 
root@GL-AR150:/tmp# echo out > /sys/class/gpio/gpio20/direction 
root@GL-AR150:/tmp# echo 1 > /sys/class/gpio/gpio20/value 
root@GL-AR150:/tmp# echo 0 > /sys/class/gpio/gpio20/value 
root@GL-AR150:/tmp# echo 20 > /sys/class/gpio/unexport

Theoretically, the external LED would be bright then dim.

The C code achieves the same goal be :


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

#include <fcntl.h>

#include <signal.h>

#define IN         (0)
#define OUT         (1)

#define BUFFER_MAX       (8)

int GPIOExport(int pin)
{
 char buffer[BUFFER_MAX];
 ssize_t bytes_written;
 int fd;

 fd = open("/sys/class/gpio/export", O_WRONLY);

 if (-1 == fd)
 {
  fprintf(stderr, "Failed to open export for writing!\n");
  return -1;
 }/*file description*/

 bytes_written = snprintf(buffer, BUFFER_MAX, "%d", pin);
 bytes_written = write(fd, buffer, bytes_written);

 close(fd);
 return 0;

}/*GPIOExport*/


int GPIOUnexport(int pin)
{
 char buffer[BUFFER_MAX];
 ssize_t bytes_written;
 int fd;

 fd = open("/sys/class/gpio/unexport", O_WRONLY);
 if (-1 == fd)
 {
  printf("Failed to open unexport for writing!\n");
  return -1;
 }/*file description*/

 bytes_written = snprintf(buffer, BUFFER_MAX, "%d", pin);
 write(fd, buffer, bytes_written);
 close(fd);

 return(0);
}/*GPIOUnexport*/


int GPIODirection(int pin, int dir)
{
 char file_location[PATH_MAX];
 char direction_str[BUFFER_MAX];
 int fd;

 snprintf(&file_location[0], PATH_MAX, "/sys/class/gpio/gpio%d/direction", pin);

 fd = open(&file_location[0], O_WRONLY);

 if (-1 == fd)
 {
  printf("Failed to open gpio direction for writing!\n");
  return -1;
 }/**/

 switch(dir)
 {
 case IN:
  snprintf(&direction_str[0], BUFFER_MAX, "in");
  break;

 case OUT:
  snprintf(&direction_str[0], BUFFER_MAX, "out");
  break;

 default:
  return -2;
 }/*switch dir*/

 if(-1 == write(fd, &direction_str[0], strlen(direction_str)))
 {
  close(fd);
  printf("Failed to set direction!\n");
  return -3;
 }/*if write*/

 close(fd);
 return 0;

}/*GPIODirection*/


int GPIOWrite(int pin, int value)
{
 char values_str[BUFFER_MAX];
 char file_location[PATH_MAX];
 int fd;

 snprintf(&file_location[0], PATH_MAX, "/sys/class/gpio/gpio%d/value", pin);
 fd = open(&file_location[0], O_WRONLY);

 if (-1 == fd)
 {
  printf("Failed to open gpio value for writing!\n");
  return -1;
 }/*file description*/


 switch(value)
 {
 case 0:
  snprintf(&values_str[0], BUFFER_MAX, "0");
  break;
 case 1:
  snprintf(&values_str[0], BUFFER_MAX, "1");
  break;
 default:
  return -2;
 }/*switch value*/


 if (1 != write(fd, &values_str[0], strlen(values_str)))
 {
  printf("Failed to write value!\n");
  close(fd);
  return -2;
 }/*if write*/

 close(fd);

 return 0;

}/*GPIOWrite*/



int g_gpio_number;

void InterruptSignalHandlingRoutine(int sig)
{
 GPIOWrite(g_gpio_number, 0);
 GPIOUnexport(g_gpio_number);

 exit(0);
}/*InterruptSignalHandlingRoutine*/


int main(int argc, char *argv[])
{
 int k;

 signal(SIGINT, InterruptSignalHandlingRoutine);

 if(2 > argc)
 {
  printf("%s should be followed by led_gpio_pin_number\r\n", argv[0]);
  return -1;
 }

 {
  char *p_temp;
  g_gpio_number = strtol(argv[1], &p_temp, 10);
  if(argv[1]== p_temp)
  {
   printf("%s is not a number for specifying gpio pin number\r\n", argv[1]);
   return -2;
  }/*not a number*/

 }/*local variable*/


 GPIOExport(g_gpio_number);
 GPIODirection(g_gpio_number, OUT);

 k = 0;

 while(k < 100*2)
 {
  GPIOWrite(g_gpio_number, (k + 1)&0x01);
  usleep(500*1000);
  k++;
 }

 GPIOUnexport(g_gpio_number);
 return 0;

}/*main*/



The Makefile is almost the same, replace the sysfs_led_control as gpio_led_control.
Build and Execute the binary,  you will find the external LED blinking per 0.5 seconds.


     二. Connect a  External push button in the preserved GPIO interface and  read the pressed/released signal.
   
      I adopt the GPIO 19  for connecting the push button.
     It is to explain how to read d input signal.

you need to know what is pull-up  and pull-down.

   Pull-down  : when the GPIO is dangling (does not be made as a circuit loop), The voltage in it should be low as ground and the value is 0; once the GPIO has been connected to Vcc, the voltage would be high( of course), and  the value become 1.

   Pull-up : contrast to Pull-down, the GPIO voltage should be high, and its value is 0; While the GPIO is been connected to ground,  the value be 1.

  Summary : Pull-down : 0 is low, 1 is high; Pull-up : 0 is high, 1 is low.


You could check the default value for trigger:


root@GL-AR150:/tmp# echo 19 > /sys/class/gpio/export 
root@GL-AR150:/tmp# echo in > /sys/class/gpio/gpio19/direction
root@GL-AR150:/tmp# cat /sys/class/gpio/gpio19/active_low 
0

It is pull-up mechanism for triggering . (active low false, means high is actived, it is high is 1 )
The value could be set, But I do not change that.

The circuit for me be :
I use the right one (pulldown), and the resistor in here is 10k Ohm.
You could use the command to enquire value :


root@GL-AR150:/tmp# cat /sys/class/gpio/gpio19/value #button released
0
root@GL-AR150:/tmp# cat /sys/class/gpio/gpio19/value #button pressed
1


The C code  be :


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

#include <linux/limits.h>

#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

#include <signal.h>

#define LOCAL        static

#define IN          (0)
#define OUT         (1)


#define BUFFER_MAX       (8)


LOCAL int GPIOExport(int pin)
{
 char buffer[BUFFER_MAX];
 ssize_t bytes_written;
 int fd;

 fd = open("/sys/class/gpio/export", O_WRONLY);

 if (-1 == fd)
 {
  fprintf(stderr, "Failed to open export for writing!\n");
  return -1;
 }/*file description*/

 bytes_written = snprintf(buffer, BUFFER_MAX, "%d", pin);
 bytes_written = write(fd, buffer, bytes_written);

 close(fd);
 return 0;

}/*GPIOExport*/


LOCAL int GPIOUnexport(int pin)
{
 char buffer[BUFFER_MAX];
 ssize_t bytes_written;
 int fd;

 fd = open("/sys/class/gpio/unexport", O_WRONLY);
 if (-1 == fd)
 {
  printf("Failed to open unexport for writing!\n");
  return -1;
 }/*file description*/

 bytes_written = snprintf(buffer, BUFFER_MAX, "%d", pin);
 write(fd, buffer, bytes_written);
 close(fd);

 return(0);
}/*GPIOUnexport*/


LOCAL int GPIODirection(int pin, int dir)
{
 char file_location[PATH_MAX];
 char direction_str[BUFFER_MAX];
 int fd;

 snprintf(&file_location[0], PATH_MAX, "/sys/class/gpio/gpio%d/direction", pin);

 fd = open(&file_location[0], O_WRONLY);

 if (-1 == fd)
 {
  printf("Failed to open gpio direction for writing!\n");
  return -1;
 }/**/

 switch(dir)
 {
 case IN:
  snprintf(&direction_str[0], BUFFER_MAX, "in");
  break;

 case OUT:
  snprintf(&direction_str[0], BUFFER_MAX, "out");
  break;

 default:
  return -2;
 }/*switch dir*/

 if(-1 == write(fd, &direction_str[0], strlen(direction_str)))
 {
  close(fd);
  printf("Failed to set direction!\n");
  return -3;
 }/*if write*/

 close(fd);
 return 0;

}/*GPIODirection*/

#include <errno.h>


LOCAL int GPIORead(int pin, int *p_value)
{
 char buffer[BUFFER_MAX];
 char file_location[PATH_MAX];
 int read_size;
 int gpio_fd;
 int previous_button_status, current_button_status;
 char *p_tmp;

 snprintf(&file_location[0], PATH_MAX, "/sys/class/gpio/gpio%d/value", pin);
 gpio_fd = open(&file_location[0], O_RDONLY);

 if (-1 == gpio_fd)
 {
  printf("Failed to open gpio value for writing!\n");
  return -1;
 }/*file description*/

 lseek(gpio_fd, 0, SEEK_SET);
 read_size = read(gpio_fd, &buffer[0], 1);

 if(0 > read_size)
 {
  printf("Failed to read value!\n");

  printf("error message = %s\r\n", strerror(errno));
  close(gpio_fd);
  return -2;
 }/*if write*/

 previous_button_status = strtol(&buffer[0], &p_tmp, 10);

 while(1)
 {
  usleep(15*1000);

  lseek(gpio_fd, 0, SEEK_SET);
  read_size = read(gpio_fd, &buffer[0], 1);

  if(0 > read_size)
  {
   printf("Failed to read value!\n");
   printf("error message = %s\r\n", strerror(errno));
   close(gpio_fd);
   return -2;
  }/*if write*/

  current_button_status = strtol(&buffer[0], &p_tmp, 10);

  if(previous_button_status == current_button_status)
   continue;

  break;
 };

 close(gpio_fd);

 *p_value = current_button_status;
 return 0;

}/*GPIOWrite*/


int g_gpio_number;

void InterruptSignalHandlingRoutine(int sig)
{
 GPIOUnexport(g_gpio_number);
 exit(0);
}/*InterruptSignalHandlingRoutine*/


int main(int argc, char *argv[])
{

 signal(SIGINT, InterruptSignalHandlingRoutine);

 if(2 > argc)
 {
  printf("%s should be followed by led_gpio_pin_number\r\n", argv[0]);
  return -1;
 }

 {
  char *p_temp;
  g_gpio_number = strtol(argv[1], &p_temp, 10);
  if(argv[1]== p_temp)
  {
   printf("%s is not a number for specifying gpio pin number\r\n", argv[1]);
   return -2;
  }/*not a number*/

 }/*local variable*/


 GPIOExport(g_gpio_number);
 GPIODirection(g_gpio_number, IN);

 while(1)
 {
  int is_pressed;

  GPIORead(g_gpio_number, &is_pressed);
  if(0 == is_pressed)
   printf("button released\r\n");
  else
   printf("button pressed\r\n");
 }

 GPIOUnexport(g_gpio_number);

 return 0;
}/*main*/


I name the binary as gpio_button_status, build and run it :


root@GL-AR150:/tmp# ./gpio_button_status 19
button pressed
button released
button pressed
button released

the print would follow the button status.


Now you know the sysfs is very useful for consigning/receiving signal to/from GPIO. But the sysfs is suited for low speed signal only. LOW SPEED means the speed in the order of KHz, or in the order of milli-second. The sysfs is not able to deal with signal speed in order of micro-second. it is pity that the most sensors ( besides the buttons) signal speed are in the scale of micro-second.

Reference :

http://blog.sina.com.cn/s/blog_7880d3350102w2um.html
https://developer.ridgerun.com/wiki/index.php/How_to_use_GPIO_signals 

沒有留言:

張貼留言