Arquivo da categoria: Eletrônica

Obtendo o ARM GNU Toolchain para desenvolvimento embarcado

Já é a segunda vez que inicio a jornada para configurar um ambiente de desenvolvimento de sistema embarcado baseado na arquitetura ARM Cortex.

Primeiro iniciei com a placa de avaliação STM32 VL Discovery (ARM Cortex M3, 24Mhz, 8kB RAM e 128 kB FLASH), agora que mudei para a STM32 “Bluepill” (STM32F103C8T6) que possui um ARM Cortex-M3 de 72 MHz, 20k de RAM e 128 kB de memória Flash.

Nessa empreitada, o passo inicial da máquina que será utilizada para desenvolvimento é a configuração do cross-compiler que tem o objetivo de compilar um código que é desenvolvido no meu computador pessoal (arquitetura x86) e gerar um arquivo que será carregado em outra arquitetura (no caso Cortex-M), para isso inicia-se a obtenção do GNU Arm Embedded Toolchain que possui um longo caminho no site (developer.arm.com) para chegar até ele, para facilitar a vida segue o link direto do local: aqui

Após fazer o download realize os seguintes passos para configurar de acordo com sua plataforma (no meu caso Ubuntu Linux):

$ sudo -i
# cd /opt
# tar xjf ~user/Downloads/gcc-arm-none-eabi-9-2020-q2-update-x86_64-linux.tar.bz2
# mv gcc-arm-none-eabi-9-2020-q2-update gcc-arm

(retorne para o usuário comum)

$ export PATH="/opt/gcc-arm/bin:$PATH"

(abra um novo terminal)

$ arm-none-eabi-gcc --version
arm-none-eabi-gcc (GNU Arm Embedded Toolchain 9-2020-q2-update) 9.3.1 20200408 (release)
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Para salvar a configuração do PATH para ele sempre enxergar os binários do toolchain o recomendado é incluir a seguinte linha no seu arquivo .bashrc

export PATH="/opt/gcc-arm/bin:$PATH"

Agora seu computador está configurado com o GNU Arm Embedded Toolchain!

Recompilando o kernel na Raspberry Pi 4

Adquiri a Raspberry Pi 4 com o objetivo de realizar alguns experimentos com eBPF/XDP para filtragem de pacotes e este modelo em específico por ter uma interface Ethernet Gigabit disponível.

O Problema

O Raspberry Pi 4 vem nativo com o Kernel 4.19.75-v7l+ e um primeiro passo é checar se os módulos que eu preciso, relativos ao eBPF/XDP, estão habilitados no kernel.

Isto é feito verificando-se o arquivo config:

$sudo modprobe configs
$ zgrep -E "(BPF|XDP)" /proc/config.gz

CONFIG_BPF=y
# CONFIG_BPF_SYSCALL is not set
CONFIG_NETFILTER_XT_MATCH_BPF=m
# CONFIG_BPFILTER is not set
# CONFIG_NET_CLS_BPF is not set
# CONFIG_NET_ACT_BPF is not set
# CONFIG_BPF_JIT is not set
CONFIG_HAVE_EBPF_JIT=y
# CONFIG_NBPFAXI_DMA is not set
# CONFIG_TEST_BPF is not set

O comando acima já permitiu verificar que alguns módulos do kernel relativos ao eBPF não estão ativos. O módulo do XDP nem apareceu na listagem.

Para ativar esses módulos no kernel é necessario realizar uma recompilação do kernel, objetivo desse texto.

Obtendo o kernel

A fonte oficial do Raspberry sobre recompilação do Kernel (em inglês chamado de build) demonstra um passo a passo que vou realizar neste texto e mostra também a possibilidade de realizar uma compilação cruzada (cross-compiling).

A compilação cruzada é muito útil quando sua plataforma embarcada não tem muita capacidade de processamento. Esta restrição em processamento pode resultar em muito tempo para compilar o kernel  ou mesmo não ter espaço suficiente para baixar o código fonte do kernel. Neste texto, farei uma compilação local.

Um primeiro passo é obter todas as ferramentas necessárias via apt:

$ sudo apt-get install git bc bison flex libssl-dev

Em seguida, deve-se baixar o código fonte do kernel no github do Raspberry Pi.

Como estou com o kernel na versão mais atual, não preciso me preocupar em qual branch do github vou pegar o kernel, o atual corresponde ao mesmo kernel que o meu (Branch: rpi-4.19.y), caso o branch atual seja uma versão mais a frente da sua e você não quer atualizar o kernel mas apenas recompilar sua versão atual, atente-se a isso.

Para quem já está acostumado ao ambiente Linux é comum pensar que todo o código do kernel deve ser baixado e compilado no diretório tradicional /usr/src/linux mas fazer isso nesse diretório envolve aspectos de segurança e por isso a boa prática é trabalhar no diretório do usuário sem privilégio root:

$ cd
$ mkdir kernel
$ cd kernel
$ git clone --depth=1 https://github.com/raspberrypi/linux

Configurando o kernel

Configurar o kernel, no nosso caso significa definir os módulos que serão compilados em conjunto com o kernel para resolver nosso problema.

Uma forma muito amigável de realizar essa tarefa é utilizar uma ferramenta chamada “menuconfig” ela apresenta uma interface gráfica que facilita a procura e escolha dos módulos mas para isso é necessário instalar a biblioteca do ncurses:

$ sudo apt-get install libncurses5-dev

Em seguida fazemos (para a Raspberry 4):

$ cd linux
$ KERNEL=kernel7l
$ make bcm2711_defconfig

E finalmente rodamos o menuconfig para configurar os módulos que desejo para essa recompilação:

$ make menuconfig

A navegação no menuconfig e intuitiva e possui as instruções logo no início (setas, barra de espaco, enter, teclas Y/N/M/*), uma dica legal no uso do menuconfig é que você pode pressionar a tecla / que irá abrir uma caixa de busca e assim você pode localizar onde estão os módulos que você deseja ativar.

Por exemplo, digitei xdp na busca e confirmei que o XDP_SOCKETS está em Networking Support -> Network options, ele também mostrou que para ativar esse módulo preciso (“depends on”) ativar o suporte a Network (NET) e a BPF_SYSCALL.

Dentro do menuconfig, faça a seguinte navegação para configurar o XDP/eBPF:

1) General setup <ENTER>
1.1) Enable bpf() system call <SPACE>
1.2) Permanently enable BPF JIT and remove BPF interpreter <SPACE>
1.3) Selecione <SAVE>

O SAVE irá gravar as modificações no arquivo .config

Em seguida, volte para o início do menuconfig e selecione:

1) Networking support <ENTER>
2) Networking options <ENTER>
2.1) XDP sockets <SPACE>
2.2) BPF based packet filtering framework <SPACE>
2.3) enable BPF Just In Time compiler <SPACE>
1.3) Selecione <SAVE>

Você ainda pode teclar / para abrir o menu de pesquisa do menuconfig e buscar por bpf para verificar os módulos que não estão selecionados (=n) e seguir o Location para incluí-los no objetivo da recompilação do kernel.

Após ativar os módulos referentes ao BPF e ao XDP, você pode sair do menuconfig… Como confirmação das modificações você pode executar os seguintes comandos:

$ cat .config | grep XDP
CONFIG_XDP_SOCKETS=y
$ cat .config | grep BPF
CONFIG_CGROUP_BPF=y
CONFIG_BPF=y
CONFIG_BPF_SYSCALL=y
CONFIG_BPF_JIT_ALWAYS_ON=y
CONFIG_NETFILTER_XT_MATCH_BPF=m
CONFIG_BPFILTER=y
CONFIG_BPFILTER_UMH=y
CONFIG_NET_CLS_BPF=m
CONFIG_NET_ACT_BPF=m
CONFIG_BPF_JIT=y
CONFIG_BPF_STREAM_PARSER=y
CONFIG_LWTUNNEL_BPF=y
CONFIG_HAVE_EBPF_JIT=y
# CONFIG_BPF_LIRC_MODE2 is not set
# CONFIG_NBPFAXI_DMA is not set
CONFIG_BPF_EVENTS=y
CONFIG_TEST_BPF=m

Recompilando o kernel

Com o arquivo .config montado corretamente via menuconfig podemos seguir com a recompilação do kernel (build):

$ make -j4 zImage modules dtbs
$ sudo make modules_install
$ sudo cp arch/arm/boot/dts/*.dtb /boot/
$ sudo cp arch/arm/boot/dts/overlays/*.dtb* /boot/overlays/
$ sudo cp arch/arm/boot/dts/overlays/README /boot/overlays/
$ sudo cp arch/arm/boot/zImage /boot/$KERNEL.img

Detalhe que o argumento -j4 do make permite que a compilação utilize os quatro núcleos do processador da Raspberry Pi 4 acelerando este processo (vale também para a RPi 2 e 3).

Este processo leva um tempo razoável de execução e também aumenta consideravelmente a temperatura da Raspberry Pi, por isso e recomendável deixar a placa em um ambiente propício e se possível com uma ventoinha.

Verificando a recompilação

Conforme inicialmente apresentado como problema, esta etapa busca demonstrar o funcionamento do eBPF/XDP após recompilação do kernel, para isso farei uso de um script de exemplo disponível em meu github:

$ git clone https://github.com/gubertoli/ids_ml.git
$ cd ids_ml/examples/bpf
$ sudo apt-get install clang-7
$ make

Carregando na interface eth0:

$ sudo ip -force link set dev eth0 xdp obj portfilter.o sec filter

Verificando:

$ ip link show eth0

Removendo:

$ sudo ip link set dev eth0 xdp off

ITG-3200 (Gyroscope) & ADXL345 (Accelerometer) – Arduino Example

Recently I got an IMU (Inertial Measurement Unit) from Sparkfun, called SparkFun 6 Degrees of Freedom IMU Digital Combo Board.

IMU Breakout Doard - 6 Degree of Freedom

This IMU has embedded a gyroscope (ITG-3200) and a accelerometer (ADXL345) in a breakout board being useful for application that requires knowledge about orientation, position, and velocity.

The gyroscope is responsible to measure angular velocity on body axes:

  • X Axis – Roll
  • Y Axis – Pitch
  • Z Axis – Yaw

Some examples:

Angular Movements - Gyro Examples

The accelerometer is responsible to measure acceleration on the body axes (x, y and z) that is useful to determine how fast a body is speeding up or slowing down.

Another point is the use of I2C (Inter-Integrated Circuit) bus that is an intra-board serial bus that uses only two “wires” for comunication: one responsible for synchronizing the devices on bus (CLOCK – SCL)  and another one responsible for data transmission (DATA – SDA).

This article aims to present a arduino implementation that simply shows output data from sensors. The references on source code are highly recommended for study before deploy this code in real application.

// SparkFun 6 Degrees of Freedom IMU Digital Combo Board
// ITG-3200 (GYRO) & ADXL345 (ACCEL) - Arduino Example Algorithm
// 
// Author: Gustavo Bertoli
//
// References:
// https://www.sparkfun.com/datasheets/Sensors/Accelerometer/ADXL345.pdf
// http://bildr.org/2011/03/adxl345-arduino/
// https://learn.sparkfun.com/tutorials/itg-3200-hookup-guide
// https://www.sparkfun.com/datasheets/Sensors/Gyro/PS-ITG-3200-00-01.4.pdf
// https://www.sparkfun.com/products/10121

#include <Wire.h>

// I2C Devices Address
#define GYRO 0x68    // ITG-3200 I2C Address - 0x68
#define ACCEL 0x53   // ADXL345  I2C Address - 0x53

// ITG-3200 (GYRO) REGISTERS
#define GYRO_X_H 0x1D
#define GYRO_X_L 0x1E
#define GYRO_Y_H 0x1F
#define GYRO_Y_L 0x20
#define GYRO_Z_H 0x21
#define GYRO_Z_L 0x22
#define DLPF_FS 0x16
#define SMPLRT_DIV 0x15

// ADXL345 (ACCEL) REGISTERS
#define ACCEL_X_L 0x32 
#define ACCEL_X_H 0x33
#define ACCEL_Y_L 0x34
#define ACCEL_Y_H 0x35
#define ACCEL_Z_L 0x36
#define ACCEL_Z_H 0x37
#define POWER_CTL 0x2D
#define DATA_FORMAT 0x31

//ITG-3200
//DLPF, Full Scale Register Bits
//FS_SEL must be set to 3 for proper operation
//Set DLPF_CFG to 3 for 1kHz Fint and 42 Hz Low Pass Filter
char DLPF_CFG_0 = 1<<0;
char DLPF_CFG_1 = 1<<1;
char DLPF_CFG_2 = 1<<2;
char DLPF_FS_SEL_0 = 1<<3;
char DLPF_FS_SEL_1 = 1<<4;

byte adxl345[6];

void setup()
{
  //Set serial communication at 9600 baudrate
  Serial.begin(9600);
  
  //Initialize the I2C communication.
  Wire.begin();

  //Set the gyroscope scale for the outputs to +/-2000 degrees per second
  writeI2C(GYRO, DLPF_FS, (DLPF_FS_SEL_0|DLPF_FS_SEL_1|DLPF_CFG_0));
  //Set the sample rate to 100 hz  
  writeI2C(GYRO, SMPLRT_DIV, 9);

  //to enable measurement mode in ADXL345 (measure bit)
  writeI2C(ACCEL, POWER_CTL, 0b00001000);
  //The DATA_FORMAT register controls the presentation of data to Register 0x32 through Register 0x37
  //writeI2C(ACCEL, DATA_FORMAT, 0b0000);
}

void loop()
{
   int data = 0;
   int x, y ,z; //acceleration - ADXL345
   Serial.println("");
   Serial.println("==============================");
   Serial.println("=ACCELEROMETER DATA (ADXL345)=");
   Serial.println("==============================");
   readI2C(ACCEL,ACCEL_X_L,6);    //ADXL345 multiple-byte read to prevent a change in data between reads of sequential registers
   // each axis reading comes in 10 bit resolution, ie 2 bytes.  Least Significat Byte first!!
   // thus we are converting both bytes in to one int
   x = (((int)adxl345[1]) << 8) | adxl345[0];   
   y = (((int)adxl345[3]) << 8) | adxl345[2];
   z = (((int)adxl345[5]) << 8) | adxl345[4];
   Serial.print("X-axis: ");
   Serial.print(x);
   Serial.print(" Y-axis: ");
   Serial.print(y);
   Serial.print(" Z-axis: ");
   Serial.print(z);
   Serial.println("");
   Serial.println("==============================");
   Serial.println("= GYROSCOPE DATA (ITG-3200)  =");
   Serial.println("==============================");
   //Angular velocity on x axis
   data = readRoll();
   Serial.print("Roll: ");
   Serial.print(data);
   
   //Angular velocity on y axis
   data = readPitch();
   Serial.print(" Pitch: ");
   Serial.print(data);

   //Angular velocity on z axis
   data = readYaw();
   Serial.print(" Yaw: ");
   Serial.print(data);
   delay(10000);
}

int readRoll (void){
  int data = 0;
  data = readI2C(GYRO, GYRO_X_H,1)<<8;
  data |= readI2C(GYRO, GYRO_X_L,1);
  return data;
}
int readPitch (void){
  int data = 0;
  data = readI2C(GYRO, GYRO_Y_H,1)<<8;
  data |= readI2C(GYRO, GYRO_Y_L,1);
  return data;
}
int readYaw (void){
  int data = 0;
  data = readI2C(GYRO, GYRO_Z_H,1)<<8;
  data |= readI2C(GYRO, GYRO_Z_L,1);
  return data;
}
// function to read from a device on I2C bus
byte readI2C(byte devAddress, byte devRegister, int numBytes){
  byte data = 0; 
  int aux = 0;
  
  Wire.beginTransmission(devAddress);   //Set device address on I2C bus
  Wire.write(devRegister);              //Set register to read from device
  Wire.endTransmission();               //Stop

  Wire.beginTransmission(devAddress);   //Set device address on I2C bus
  Wire.requestFrom(devAddress, numBytes);      //read data

  if (numBytes == 1) {
    if (Wire.available()){
      data = Wire.read();
    }
  } else {    //ADXL345 multiple-byte read to prevent a change in data between reads of sequential registers
    while (Wire.available()){
      adxl345[aux] = Wire.read();
      aux++;
    }
  }
  Wire.endTransmission();
  return data;
}

// function to write in a device on I2C bus
void writeI2C(byte devAddress, byte devRegister, byte devData){
  Wire.beginTransmission(devAddress);    //Start tx - set device address on I2C bus
  Wire.write(devRegister);               //Set register from I2C device
  Wire.write(devData);                  //Set value to register 
  Wire.endTransmission();                //Stop tx
}

Result:

IMU Output - Arduino Example
IMU Output – Arduino Example

Module GP-20U7 / GP20U7 – GPS Example with arduino

GP-20U7 is a low-cost GPS module available in SparkFun (check it here).

The GP-20U7 is useful for mobile application, autonomous vehicles and many other applications that requires geographical positioning data.

It requires a 3.3V power supply and a UART port to communicate with your processor (more details on datasheet).

This example was implemented with arduino UNO and the following pins were assigned:

  • Vcc pin from GP-20U7 to 3.3V on arduino
  • GND from GP-20U7 to GND on arduino
  • TX from GP-20U7 to PIN10 on arduino

Below is the code implemented:

// GPS Module - GP-20U7 - Arduino Example Algorithm
//
// Author: Gustavo Bertoli
//
// References:
// https://cdn.sparkfun.com/datasheets/GPS/GP-20U7.pdf
// https://www.arduino.cc/en/Reference/SoftwareSerial
// http://forum.arduino.cc/index.php?topic=288234.0
// 

#include <SoftwareSerial.h> 

SoftwareSerial GPS_Serial(10, 11); // RX, TX

void setup() {
  Serial.begin(9600);
  GPS_Serial.begin(9600); 
}

void loop() {
   char rc;

   if (GPS_Serial.available()){
        rc = GPS_Serial.read();
        Serial.write(rc);
   }
}

And the output:

GPS GP-20U7 - arduino output
GPS GP-20U7 – arduino output