[LAB] Bibliotecas e Hardware Handling

Neste projeto vamos aprofundar um pouco mais na manipulaĆ§Ć£o dos hardwares e perifĆ©ricos dos sistemas embarcados utilizando Linux. Vamos desenvolver uma aplicaĆ§Ć£o bĆ”sica que realize algumas tarefas que sĆ£o comumente utilizadas em aplicaƧƵes embarcadas, como: manipulaĆ§Ć£o de GPIO, geraĆ§Ć£o de PWM, trigggers e callbacks.

Para desenvolver aplicaƧƵes em sistemas Linux embarcados, geralmente utilizamos bibliotĆ©cas in-house ou de terceiros para desempenhar determinados papeis na aplicaĆ§Ć£o, por exemplo, comunicaĆ§Ć£o com hardware, protocolos, criptografias e etc.

Esta Ć© uma escolha usualmente simples, pois muitas vezes nĆ£o Ć© desejĆ”vel re-implementar este tipo de funĆ§Ć£o, ou estĆ” fora do escopo do projeto ou simplesmente pois existe uma gama tĆ£o grande de bibliotecas que desempenham este papel com maestria e jĆ” foram massivamente testadas e possuem um bom suporte que nĆ£o convĆ©m desenvolvelas novamente.

Para a manipulaĆ§Ć£o de hardware nĆ£o Ć© diferente, existem diversas bibliotecas e APIs na comunidade para este fim. Cada uma com sua especificidade, sua aplicaĆ§Ć£o, sua interface e sua linguagem de implementaĆ§Ć£o, tais como: WiringPi, PIGPIO e RPi.GPIO.

Por alguns motivos em particular, vamos utilizar a biblioteca PIGPIO para as implementaƧƵes desta atividade. ElĆ” possui um interface em linguagem C, Ć© suportada para todas as versƵes das Raspberrys e possui uma boa gama de funƧƵes, tais como: manipulaĆ§Ć£o bĆ”sica de GPIOs, timers, comunicaĆ§Ć£o serial e etc.

Cross-compilando e Instalando o PIGPIO no host

cd ~/dsle20/dl
mkdir libs
cd libs

A compilaĆ§Ć£o deve ser feita utilizando cross-compiling, uma vez que o cĆ³digo Ć© feito para rodar no embarcado. Portando, altere a variĆ”vel CROSS_COMPILE dentro do Makefile para receber o prefixo da ferramenta de cross compilaĆ§Ć£o.


wget abyz.me.uk/rpi/pigpio/pigpio.zip
unzip pigpio.zip
cd PIGPIO
make
sudo make install

ApĆ³s a compilaĆ§Ć£o observe os arquivos gerados dentro do diretĆ³rio PIGPIO. Estamos especialmente interessados nas bibliotecas compartilhadas (.so) e nos cabeƧalhos.

Instalando o PIGPIO no target (Buildroot)

Para instalar a PIGPIO podemos utilizar o Buildroot, uma vez que o Busybox tem esta biblioteca em seu repositĆ³rio.

Abra o menu de configuraĆ§Ć£o dos pacotes do Buildroot:

cd ~/dsle20/rootfs/buildroot-2019.02.8
make menuconfig 

Na seĆ§Ć£o Target packages entre na subseĆ§Ć£o Hardware Handling, onde se encontram diversos pacotes para a manipulaĆ§Ć£o e perifĆ©ricos e entĆ£o selecione o pacote pigpio. Save a configuraĆ§Ć£o e recompile o nosso RootFS. NĆ£o se preocupe, o processo de compilaĆ§Ć£o ignora os arquivos jĆ” compilado, desta forma o processo se torna simples e rĆ”pido.

EntĆ£o, grave a nova imagem do RootFS no cartĆ£o SD e realize a configuraĆ§Ć£o novamente. Para que tenhamos acesso agora, a essa nova biblioteca no nosso sistema embarcado.

Por padrĆ£o, as bibliotecas sĆ£o instaladas dentro de /usr/lib no nosso target. NĆ£o diferente, se listarmos os arquivos disponĆ­veis dentro deste diretĆ³rio encontraremos os referidos arquivos.

ls -ll /usr/lib | grep *pig*

-rwxr-xr-x    1 root     root        193672 Feb  2  2020 libpigpio.so
-rwxr-xr-x    1 root     root         45108 Feb  2  2020 libpigpiod_if.so
-rwxr-xr-x    1 root     root         45108 Feb  2  2020 libpigpiod_if2.so

Utilizando a PIGPIO:

Primeiro passo que devemos fazer Ʃ importar esta nossa biblioteca atravƩs da primitiva de prƩ-processamento:

#include "pigpio.h"

EntĆ£o, ao compilar o cĆ³digo percebemos que nĆ£o Ć© possĆ­vel realizar a compilaĆ§Ć£o pois o linker nĆ£o encontrou a referida biblica, pois a mesma nĆ£o se encontra no diretĆ³rio de execuĆ§Ć£o. Portanto, faz-se necessĆ”rio informar tanto o caminho do diretĆ³rio que deve ser incluĆ­do no PATH do compilador como o nome da biblioteca, assim:

Clique com o botĆ£o direito sobre o projeto e em Properies > C/C++ Build > Settings.

EntĆ£o, na aba central, escolhemos a subseĆ§Ć£o Cross GCC Compiler > Includes.

Em "includes paths (-l)" adicionamos o caminho atƩ a biblioteca compilada: /home/gbs/dsle20/libs/PIGPIO.

Na subseĆ§Ć£o Cross GCC Linker > Libraries em "Libraries (-l)" adicionamos o nome da biblioteca: pigpio (sem o prefixo lib e sem a extensĆ£o .so).

Por fim, em "Library search path (-L)" adicionamos novamente o caminho para a biblioteca compilada: /home/gbs/dsle20/libs/PIGPIO

Finalmente apĆ³s a compilaĆ§Ć£o iremos copiar os binĆ”rios gerados para dentro da RPi e entĆ£o executar o nosso cĆ³digo de teste.

cd exs/
sudo ./ex02

./ex02: error while loading shared libraries: libpigpio.so.1: cannot open shared object file: No such file or directory

Este erro Ć© ocasionado pois o executĆ”vel nĆ£o conseguiu encontrar o arquivo "libpigpio.so.1". De fato, podemos listar as bibliotecas instaladas e procurar pela libpigpio no target:

ls -ll /usr/lib/ | grep libpigpio.so
-rwxr-xr-x    1 root     root        193672 Feb  2  2020 libpigpio.so

Ao passo que no host:

gbs@core:~$ ls -ll ~/dsle20/libs/PIGPIO/ | grep libpigpio.so

lrwxrwxrwx 1 gbs  gbs      14 Jan 10 14:26 libpigpio.so -> libpigpio.so.1
-rwxrwxr-x 1 gbs  gbs  260000 Jan 10 14:26 libpigpio.so.1

Nota-se que hĆ” um link simbĆ³lico libpigpio.so -> libpigpio.so.1, ou seja, no processo de compilaĆ§Ć£o a biblioteca a libpigpio.so Ć© substituĆ­da por libpigpio.so.1. Normalmente, este nĆŗmero no final da biblioteca indica o versionamento atual da biblioteca, Ć© muito comum realizar este tipo de link. PorĆ©m, na versĆ£o instalada na plataforma target, nĆ£o hĆ” o arquivo *.so.1 desta forma, precisamos criar este link no target para conseguir realizar a execuĆ§Ć£o corretamente.

sudo ln -s /usr/lib/libpigpio.so /usr/lib/libpigpio.so.1
$ ls -ll /usr/lib/ | grep pig
-rwxr-xr-x    1 root     root        193672 Feb  2  2020 libpigpio.so
lrwxrwxrwx    1 root     root            21 Jan  1 00:32 libpigpio.so.1 -> /usr/lib/libpigpio.so

Agora basta compilar o cĆ³digo e tudo deve funcionar como o esperado.

Toda as funƧƵes utilizadas nesta (e nas seguintes) prĆ”tica podem ser consultadas atravĆ©s da documentaĆ§Ć£o da biblioteca PIGPIO ou mesmo atravĆ©s do comando man.

GeraĆ§Ć£o de Sinal PWM utilizando a Biblioteca PIGPIO

Com a biblioteca devidamente instalada em ambas as plataformas podemos dar inĆ­cio a prĆ”tica deste laboratĆ³rio.

O primeiro objetivo Ć© gerar um sinal PWM em um dos pinos de uso geral da RPi3, de acordo com a especificaĆ§Ć£o mostrada no diagrama de forma de onda abaixo, isto Ć©, PWM de 50Hz e 50% de duty-cycle.

Primeiramente, devemos inicializar a biblioteca PIGPIO, esta inicializaĆ§Ć£o Ć© feita atravĆ©s da funĆ§Ć£o gpioInitialise.

	if (gpioInitialise() < 0)
	{
		fprintf(stderr, "pigpio initialization failed\n");
		return 1;
	}

Depois de inicializada devemos configurar o modo de operaĆ§Ć£o do pino que produzirĆ” o sinal PWM. Nesta prĆ”tica iremos utilizar o GPIO 23 (observe o pinout da RPi para verificar a localizaĆ§Ć£o fĆ­sica do mesmo). Para configurar o GPIO 23 como um pino de saĆ­da, basta utilizar a funĆ§Ć£o gpioSetMode.

if (gpioSetMode(23, PI_OUTPUT) != 0)
{
    printf("Error: Failed to set GPIO %d \n\n", 23);
}

Em seguida, utilizamos a funĆ§Ć£o gpioSetPWMfrequency para definir a frequĆŖncia de geraĆ§Ć£o do pulso.

int freq_ret;
freq_ret = gpioSetPWMfrequency(23, 50);

printf("Freq: %d \n\n", freq_ret);

Em seguida devemos definir o atravĆ©s da funĆ§Ć£o gpioSetPWMrange a resoluĆ§Ć£o do pulso gerado, bem como o duty-cycle do PWM atravĆ©s da funĆ§Ć£o gpioPWM. O duty-cycle Ć© definido pela razĆ£o entre o range e o parĆ¢metro passado a funĆ§Ć£o gpioPWM.

gpioSetPWMrange(23, 1000);
gpioPWM(23, 500);

ApĆ³s a execuĆ§Ć£o do cĆ³digo Ć© necessĆ”rio encerar as configuraƧƵes dos pinos, similarmente como realizado no processo de inicializaĆ§Ć£o.

gpioTerminate();

ISR e Callbacks utilizando a Biblioteca PIGPIO

Na segunda parte da atividade iremos realizar a configuraĆ§Ć£o de uma ISR (interrupĆ§Ć£o) em um dos pinos. Esta interrupĆ§Ć£o serĆ” vinculada a uma funĆ§Ć£o signal_handler_io que tem como objetivo executar uma determinada tarefa, como por exemplo a manipulaĆ§Ć£o de um outro pino ou mesmo a execuĆ§Ć£o de uma outra funĆ§Ć£o qualquer.

Primeiramente vamos realizar a configuraĆ§Ć£o de mais duas GPIOs. A GPIO 20 serĆ” configurada como uma porta de entrada e estarĆ” vinculada Ć  ISR. AlĆ©m disso, iremos inializar o GPIO 16 como saĆ­da para que a mesma seja manipulada pela callback da interrupĆ§Ć£o.

if (gpioSetMode(20, PI_INPUT) != 0)
{
    printf("Error: Failed to set GPIO %d \n\n", 20);
}

if (gpioSetMode(16, PI_OUTPUT) != 0)
{
    printf("Error: Failed to set GPIO %d \n\n", 16);
}

Para atrelar um GPIO a uma ISR devemos utilizar a funĆ§Ć£o gpioSetISRFunc da biblioteca PIGPIO. Como parĆ¢metros podemos passar a GPIO utilizada, a borda Ć  ser detectada, um timeout e tambĆ©m a funĆ§Ć£o (callback) a ser chamada quando o evento acontecer.

gpioSetISRFunc(20, FALLING_EDGE, 300, signal_handler_IO);

Assim, devemos criar a funĆ§Ć£o signal_handler_IO para ser utilizada como callback.

void signal_handler_IO(int gpio, int level, uint32_t tick) 
{
    int gpio_val = gpioRead(16);
    gpio_val = !gpio_val;
    gpioWrite(16, gpio_val);
    printf("Interrupt Occurred! Level-> %d\t tick-> %d\n",level, tick-tick_spent);
    tick_spent = tick;
}

Populamos a funĆ§Ć£o com alguma tarefa qualquer, neste caso, foi proposto calcular o tempo entre duas interrupƧƵes e tambĆ©m alterar o estado do GPIO 16, ligando-o e desligando conforme ocorrem as interrupƧƵes.

Ao executar o cĆ³digo podemos observar o seguinte diagrama:

A primeira forma de onda Ć© o sinal PWM gerado pelo GPIO 23 ao passo que a segunda Ć© a forma de onda gerada pelo GPIO 16.

Realizando um contato elƩtrico entre os GPIO 23 e GPIO 20, ou seja, conectando o PWM ao pino ISR Ʃ possƭvel obter o diagrama a seguir.

Desta forma, foi possĆ­vel visualizar a operaĆ§Ć£o da ISR dos dois modos: timeout e detecĆ§Ć£o de borda. Estes tĆ³picos serĆ£o abordados no projeto final.

/*
 ============================================================================
 Name        : ex02.c
 Author      : gbs|jms
 Version     :
 Copyright   : Your copyright notice
 Description : A simple C program for demonstrating the PIGPIO interface.
 ============================================================================
 */

//==============================================================================
//                              USED INTERFACES
//==============================================================================

#include <fcntl.h>				/* Required by open / write system calls.     */
#include <pthread.h>			/* POSIX threads Library. Required by PIGPIO. */
#include <stdint.h>				/* Standard Int Library.(uint32_t, uint8_t...)*/
#include <stdio.h>				/* Standard Input/Ouput Library. (printf...)  */
#include <stdlib.h>				/* General (standard) Library. (EXIT_SUCCESS. */
#include <termios.h>			/* Terminal Input/Output interfaces.          */
#include <unistd.h>				/* Complement (flags, constants, definitions..)
									for the POSIX API. */

#include "pigpio.h"				/* PIGPIO Library Header */

//==============================================================================
//                        MACROS AND DATATYPE DEFINITIONS
//==============================================================================

/* HW and Interfaces */


//==============================================================================
//                     STATIC (PRIVATE) FUNCTION PROTOTYPES
//==============================================================================

/**
 * @brief A function to verify if any key was pressed on terminal.
 *
 * @return TRUE     If a key was pressed.
 * @return FALSE    Otherwise.
 */
static int kbhit(void);

/**
 * @brief The callback function attached to the GPIO pin interrupt. The bellow
 * 		  parameters are provided by the RPiGPIO Library.
 *
 * @param[in] gpio     0-53     The GPIO which has changed state.
 * @param[in] level    0-2      0 = change to low (a falling edge)
 *		                        1 = change to high (a rising edge)
 *           		            2 = no level change (interrupt timeout)
 * @param[in] tick     32 bit   The number of microseconds since boot
 *
 */
static void signal_handler_IO(
		int gpio,
		int level,
		uint32_t tick);

//==============================================================================
//                          STATIC GLOBAL VARIABLES
//==============================================================================

uint32_t tick_spent;		/* A variable used to store the number of occurred
								ticks between each GPIO interrupt. */


//==============================================================================
//                      IMPLEMENTATION OF PUBLIC FUNCTIONS
//==============================================================================

int main(void)
{
	tick_spent = 0;


	if (gpioInitialise() < 0) /* Such function must be called before using the
									 PIGPIO library.*/
	{
		fprintf(stderr, "pigpio initialization failed\n");
		return 1;
	}

    if (gpioSetMode(23, PI_OUTPUT) != 0)
    {
    	printf("Error: Failed to set GPIO %d \n\n", 23);
    }

    if (gpioSetMode(16, PI_OUTPUT) != 0)
    {
		printf("Error: Failed to set GPIO %d \n\n", 16);
	}

    if (gpioSetMode(20, PI_INPUT) != 0)
	{
		printf("Error: Failed to set GPIO %d \n\n", 20);
	}

    /**
     * Registers the signal_handler function to be called
     * (a callback) whenever an interrupt occurs on
     * the specified GPIO (20). The EITHER_EDGE parameter
     * specifies an interrupt for both: Rising or Falling
     * edges. The parameter 100 indicates a 100ms timeout.
     */
    gpioSetISRFunc(20, EITHER_EDGE, 300, signal_handler_IO);
    /* PS: If you want to see a result different from the timeout,
     * i.e., a detected RISING or FALLING edge, you should connect
     * the GPIO pins (BCM) 20(input)-23(pwm). Which physically are:
     * the pins 38 and 16, respectively. */


    int freq_ret;

	/* Sets a 50Hz PWM Frequency for the GPIO  23 pin.
	 * It returns the numerically closest frequency if OK.
	 * The frequency depends on other factors, please for
	 * further details check:
	 * http://abyz.me.uk/rpi/pigpio/cif.html#gpioSetPWMfrequency*/
	freq_ret = gpioSetPWMfrequency(23, 50);

	printf("Freq: %d \n\n", freq_ret);

	/* Selects the duty-cycle range to be used for the GPIO.
	 * Subsequent calls to gpioPWM will use a duty-cycle
	 * between 0 (off) and range (fully on).]
	 * the range value can be: 25-40000 */
	gpioSetPWMrange(23, 1000);

	/* According to the above specified range (1000), sets a duty-cicle
	 * of 50%. */
	gpioPWM(23, 500);

	while(!kbhit()) /* While a key from the keyboard isn't pressed... */
	{

		printf("Running..\n");
		sleep(1);
	}

	/* Similar to the gpioInitialise function, this one must be called at the end of your program.*/
	gpioTerminate();


	return EXIT_SUCCESS;
}

//==============================================================================
//                IMPLEMENTATION OF STATIC (PRIVATE) FUNCTIONS
//==============================================================================

void signal_handler_IO(int gpio, int level, uint32_t tick)
{
	/* Reads the current state of the GPIO pin 16. */
	int gpio_val = gpioRead(16);

	/* Inverts such state. */
	gpio_val = !gpio_val;

	/* Updates the gpio pin's state. */
	gpioWrite(16, gpio_val);

	/* Computes and shows the number of spent ticks between the last and the current interrupt. */
	printf("Interrupt Occurred! Level-> %d\t tick-> %d\n",level, tick-tick_spent);
	tick_spent = tick;
}

static int kbhit(void)
{
    struct termios oldt, newt;

    int ch;
    int oldf;

    /** Gets the attributes from the standard input file descriptor. */
    tcgetattr(STDIN_FILENO, &oldt);

    /** Enables raw input. (unprocessed). */
    newt = oldt;
    newt.c_lflag &= ~(ICANON | ECHO);

    /** Sets the new attributes. */
    tcsetattr(STDIN_FILENO, TCSANOW, &newt);

    /** Gets the file access mode and status flags. */
    oldf = fcntl(STDIN_FILENO, F_GETFL, 0);

    /** Sets the same above read values + the O_NONBLOCK flag. */
    fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);

    /** Checks if there is a char on the standard terminal. (nonblock). */
    ch = getchar();

    /** Make changes now, without waiting for data to complete. */
    tcsetattr(STDIN_FILENO, TCSANOW, &oldt);

    /** Removes the O_NONBLOCK flag. */
    fcntl(STDIN_FILENO, F_SETFL, oldf);

    /** If any key was hitted, return true. And puts back the pressed key onto
     *  the terminal. */
    if(ch != EOF)
    {
        ungetc(ch, stdin);
        return 1;
    }

    return 0;
}

Last updated