2.3. Accessing the Gateway’s User Button and LED from C Code

2.3.1. Introduction

This tutorial will introduce you to a C application for Vesta Gateways that changes the output of the Gateway’s LED when the user button is pressed. It provides a useful reference for developers who wish to utilize the button and/or LEDs in their own Gateway applications.

The complete code for this tutorial is available as part of the Vesta Examples repository.

In code samples in this document, each command will be preceded by the command prompt, in this case root@080030717-00055:~/c-button-led#.

2.3.2. Setup

  1. First, log in to the Vesta Gateway. This will put you in the home directory.

  2. Create a directory for this project, and navigate into it.

    root@080030717-00055:~# mkdir c-button-led
    root@080030717-00055:~# cd c-button-led
    
  3. Use wget to download the project’s source code to c-button-led, as follows:

    root@080030717-00055:~/c-button-led# wget https://git.rigado.com/VestaExamples/c-button-led/raw/master/gpio-utils.c
    root@080030717-00055:~/c-button-led# wget https://git.rigado.com/VestaExamples/c-button-led/raw/master/gpio-utils.h
    root@080030717-00055:~/c-button-led# wget https://git.rigado.com/VestaExamples/c-button-led/raw/master/led_test.c
    root@080030717-00055:~/c-button-led# wget https://git.rigado.com/VestaExamples/c-button-led/raw/master/Makefile
    

    Note

    The Vesta image comes pre-installed with wget, a utility to download files from the network. An alternative to downloading the files directly to the Gateway would be to download them to your host computer, and then copy them to the Gateway using a utility like scp.

2.3.3. led_test.c code

The following code block shows the source code contained in the led_test.c file.

  • The main() function in this file initializes the sys/class/gpio files for the GPIO lead connected to the Gateway’s user button, and then polls the file containing that GPIO lead’s current state for changes indicating that a button push has occurred.

  • Each time a button press is detected, the main() function calls next_led_pattern(), which re-configures the Pulse Width Modulator circuits controlling the Gateway’s LED, which results in a change to the LED’s output.

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <poll.h>
    #include "gpio-utils.h"
    #include <errno.h>
    
    #define USER_BUTTON 138
    #define TIMEOUT -1 //forever
    
    #define redled_dc "/sys/class/pwm/pwmchip5/pwm0/duty_cycle"
    #define redled_period "/sys/class/pwm/pwmchip5/pwm0/period"
    #define greenled_dc "/sys/class/pwm/pwmchip1/pwm0/duty_cycle"
    
    //duty cycle in nanoseconds
    const char period_1sec[] = "1000000000";
    const char led_flash_1[] = "500000000";   // .5sec
    const char led_flash_2[] = "950000000";   // .05sec
    const char led_off[] = "1000000000";    // 1 sec
    const char led_on[] = "0000000000";    // 0 sec
    
    const char period_tenth_sec[] = "100000000"; // .1 sec
    const char led_flash_rapid[] = "95000000";   // .005 sec
    
    void next_led_pattern()
    {
      int fd_red_dc;
      int fd_green_dc;
      int fd_red_period;
      static int state = 0;
    
      fd_red_dc = open(redled_dc, O_WRONLY);
      fd_red_period = open(redled_period, O_WRONLY);
      fd_green_dc = open(greenled_dc, O_WRONLY);
    
      if( fd_red_dc == -1 || fd_green_dc == -1 || fd_red_period == -1)
      {
              perror("Cannot open output file\n");
              close(fd_red_dc);
              close(fd_red_period);
              close(fd_green_dc);
              exit(1);
      }
    
      if(state == 0)
      {
        printf("Button pressed.\n pwm1 period=1 sec, duty cycle=100%\n pwm5 period=1 sec, duty cycle=50%\n");
              write(fd_green_dc, led_off, sizeof(led_off));
              write(fd_red_dc, led_flash_1, sizeof(led_flash_1));
              state = 1;
      } else if(state == 1)
      {
        printf("Button pressed.\n pwm1 period=1 sec, duty cycle=50%\n pwm5 period=1 sec, duty cycle=100%\n");
              write(fd_red_dc, led_off, sizeof(led_off));
              write(fd_green_dc, led_flash_1, sizeof(led_flash_1));
              state = 2;
      } else if(state == 2)
      {
        printf("Button pressed.\n pwm1 period=1 sec, duty cycle=100%\n pwm5 period=1 sec, duty cycle=95%\n");
              write(fd_green_dc, led_off, sizeof(led_off));
              write(fd_red_dc, led_flash_2, sizeof(led_flash_2));
              state = 3;
      } else if(state == 3)
      {
        printf("Button pressed.\n pwm1 period=1 sec, duty cycle=100%\n pwm5 period=.1 sec, duty cycle=95%\n");
              write(fd_red_dc, led_flash_rapid, sizeof(led_flash_rapid));
              write(fd_red_period, period_tenth_sec, sizeof(period_tenth_sec));
              state = 4;
      } else if(state==4) {
        printf("Button pressed.\n pwm1 period=1 sec, duty cycle=0%\n pwm5 period=1 sec, duty cycle=50%\n");
              write(fd_red_period, period_1sec, sizeof(period_1sec));
              write(fd_red_dc, led_flash_1, sizeof(led_flash_1));
              write(fd_green_dc, led_on, sizeof(led_on));
              state = 5;
      } else if(state == 5)
      {
        printf("Button pressed.\n pwm1 period=1 sec, duty cycle=0%\n pwm5 period=1 sec, duty cycle=100%\n");
              write(fd_red_dc, led_off, sizeof(led_off));
              write(fd_green_dc, led_on, sizeof(led_on));
              state = 0;
      }
      close(fd_red_dc);
      close(fd_green_dc);
      close(fd_red_period);
    }
    
    int main()
    {
      struct pollfd fd_poll;
      int gpio_fd, rc;
      char buf[MAX_BUF];
      unsigned int gpio;
      int len;
      int count = 0;
    
      gpio_export(USER_BUTTON);
      gpio_set_dir(USER_BUTTON, 0);
      gpio_set_edge(USER_BUTTON, "rising");  // Can be rising, falling or both
      gpio_fd = gpio_fd_open(USER_BUTTON);
    
      printf("\nLED test.  Pressing gateway's button will change LED pattern\n\n");
    
      while ( count < 6 ) {
              fd_poll.fd = gpio_fd;
              fd_poll.events = POLLPRI;
    
              rc = poll(&fd_poll, 1, TIMEOUT);
    
              if (rc < 0) {
                      printf("\npoll() failed!\n");
                      return -1;
              }
    
              if (fd_poll.revents & POLLPRI) {
                      lseek(fd_poll.fd, 0, SEEK_SET);  // Read from the start of the file
                      len = read(fd_poll.fd, buf, MAX_BUF);
                      if(buf[0] == '1') {
                              next_led_pattern();
                              count++;
                      }
    
              }
    
              fflush(stdout);
      }
    
      printf("\nLED test done\n");
      gpio_fd_close(gpio_fd);
      return 0;
    }
    

2.3.4. Make and Run

  1. Build the led_test binary by executing the makefile:

    root@080030717-00055:~/c-button-led# make
    
  2. Run the newly-compiled led_test binary:

    root@080030717-00055:~/c-button-led# ./led_test
    

When run, led_test will switch to a new LED pattern each time the Gateway’s user button is pressed. After each button press, the program also prints out the new Pulse Width Modulator settings for both the Green (PWM 1) and Red (PWM 5) components of the Gateway LED. After the 6th press, the program will restore the Gateway LED to steady green, and exit.

2.3.5. Understanding how Pulse Width Modulator settings affect the Gateway LED’s output

Although the Vesta Gateway has a single LED, it is a multi-color unit with red and green components. These components are each connected to individual pulse width modulator (PWM) circuits, and can thus be controlled independently. When the PWM associated with a particular LED components outputs a one, that LED component (i.e. color) will turn off, and when that PWM outputs a zero, that LED component will illuminate.

Both LED PWMs possess a period and a duty cycle register. Each PWM is clocked internally once per nanosecond, and the period register specifies the number of clocks per PWM period. To set the PWM period to 1 second, for example, you’d write 1000000000 into the period register. The duty cycle register specifies the number of clock cycles that the PWM will output a 1 at the start of each period. There is no gap between periods, so if the duty cycle register is set to 0, the PWM will output a steady string of zeros, and the LED in question will remain steadily lit. If the duty cycle register is set to the same value as the period register, the PWM will output a steady stream of 1s, and the LED will remain steadily off. If the duty cycle register is set to 50% of the period register, the LED will be off for the first half of the period, and on for the second half, and will toggle off/on at an even rate. Setting the duty cycle to 95% of the period register will cause the LED to flash for a brief instant at the very end of each period. Reducing the period register, and adjusting the duty cycle register to retain a constant ratio with respect to the period register, will retain the basic flash sequence, but will cause it to occur more rapidly.

Be aware that the PWM driver code will reject any register change that attempts to make the duty cycle setting larger than the period setting for a particular PWM. Since these values are set independently, using write() system calls to manipulate different sysfs files, you must sequence your changes carefully. For example, if a PWM currently has a period setting of 1000000000 (1 second), and a duty cycle setting of 500000000 (.5 seconds), then the following update will complete successfully:

write(fd_red_dc, 50000000, 8);
write(fd_red_period, 100000000, 9);

However, attempting the same changes in reverse order:

write(fd_red_period, 100000000, 9);
write(fd_red_dc, 50000000, 8);

results in the first write returning a -1 return code, with Linux’s errno set to -EINVAL (invalid argument), since it attempts to set the period register to .1 second when the duty cycle register is set to a larger value (.5 seconds).

Illuminating both the green and red LED components concurrently makes the Gateway’s LED appear yellow. The periods of the red and green PWMs are not synchronized however, so when using yellow, it is best to leave both the green and red LED components either steadily on, or blink only one of the two components at any one time (for example, setting the green component’s PWM to a 0% duty cycle, and setting the red component’s PWM to a 50% duty cycle, when both the green and red PWMs have the same period, results in an alternating yellow/green pattern).

2.3.6. Usage of the LED and the User Button by the Gateway OS

2.3.6.1. The Gateway’s LED

When the Gateway is reset, both PWM’s are reset, and output 0. As such, the Gateway LED turns yellow on reset, and remains yellow while the Gateway is booting. At completion of the boot process, the Gateway operating system sets the LED to steady green. It performs no further update of the LED PWM registers once boot is complete. As such, you can configure the Gateway LED however you wish, and be confident that the Gateway’s OS will not overwrite your settings.

2.3.6.2. The Gateway’s User Button

Similarly, the Gateway’s button is not monitored by the Gateway OS. As such, you may use the button any way you see fit, with no concerns that you use of the button will cause an unexpected response from the Gateway OS.