Les 4: Libraries en C/C++

Libraries

  • Welke libraries zijn er beschikbaar?
  • Hoe te gebruiken?
  • Hoe maak je een library?

Beschikbare libraries

  • Standaard libraries
    • SPI

  • Recommended libraries
    • Adafruit NeoPixel

  • Contributed libraries
    • Adafruit NeoPixel

  • Eigen libraries
    • ProjectLib

Libraries gebruiken

  • Via de IDE
    • Sketch > Include Library > Librarie Selecteren

  • Manueel
    • #include <LibraryName.h>
    • Bovenaan file

Library Manager

  • Sketch > Include Library > Manage Libraries ...
  • Makkelijk toevoegen van Libraries
    • Libraries downloaden
    • Een gezipte librarie toevoegen
      • Sketch > Include Library > Add .ZIP Library ...

  • Locatie
    • $USER_DIR/Arduino/libraries
    • /home/luytsm/Arduino/libraries
    • c:/Users/luytsm/Arduino/libraries

Zelf een library maken

  • Preprocessor
  • Header Files
  • Classes

Preprocessor

  • Beginnend met #
  • One-Liners
  • Geen ;
  • Een stap voor compilatie
    • Substitutie
  • Gebruik
    • Directives
    • Macros

      #define THIS_EXISTS
      #ifdef THIS_EXIST
        //THEN DO THIS
      #endif

      #include <file>

      #define PI 3.14

      #define EDIT_BIT(bit) (1 << BIT )
    

Directives

Directive Description
#define Substitutes a preprocessor macro.
#include Inserts a particular header from another file.
#undef Undefines a preprocessor macro.
#ifdef Returns true if this macro is defined.
#ifndef Returns true if this macro is not defined.
#if Tests if a compile time condition is true.
#else The alternative for #if.
#elif #else and #if in one statement.
#endif Ends preprocessor conditional.
#error Prints error message on stderr.

Macros

  • #define
    • Object-Like Macro
    • Function-Like Macro
  • Substitutie

      #define THIS_EXISTS

      #define PI 3.14

      #define EDIT_BIT(bit) (1 << BIT )
    

Verschill Macro en Functies

Macro Functie
Macro is Preprocessed Function is Compiled
No Type Checking Type Checking is Done
Code Length Increases Code Length remains Same
Use of macro can lead to side effect No side Effect
Speed of Execution is Faster Speed of Execution is Slower
Before Compilation macro name is replaced by macro value During function call , Transfer of Control takes place
Useful where small code appears many time Useful where large code appears many time
Generally Macros do not extend beyond one line Function can be of any number of lines
Macro does not Check Compile Errors Function Checks Compile Errors

Conditionele Macros


#define DEBUG

#ifdef DEBUG
#define DEBUG_INIT() char sDebug[128];
#define DEBUG_PRINTHEX(T, v) Serial.print(T); sprintf(sDebug, "%x\n\r", v); Serial.print(sDebug);
#define DEBUG_PRINTDEC(T, v) Serial.print(T); sprintf(sDebug, "%d\n\r", v); Serial.print(sDebug);
#define DEBUG_HALT() (Serial.available() == 0); Serial.setTimeout(1); Serial.readBytes(sDebug, 1);
#else
#define DEBUG_INIT() 
#define DEBUG_PRINTHEX(T, v) 
#define DEBUG_PRINTDEC(T, v)
#define DEBUG_HALT() 
#endif
  

Optimalisatie

Test Code met Variabele


#include <stdio.h>
static const  foo =  6;

int  main() {
  printf("%d", foo);
  return 0;
}
      

Test Code met Macro


#include <stdio.h>
#define foo 6

int  main() {
  printf("%d",foo);
  return 0;
}
      

Gecompileerde ASM Code met Variabele


push rbp
mov rbp,rsp
sub rsp,0x20
call d 
mov eax,0x6
mov edx,eax
lea rcx,[rip+Ox4]
call 20 
mov eax,0x0
add rsp,0x20
pop rbp
ret
nop
      

Gecompileerde ASM Code met Macro


push rbp
mov rbp,rsp
sub rsp,0x20
call d 
mov edx,0x6
lea rcx,[rip+Ox0]
call le 
mov eax,0x0
add rsp,0x20
pop rbp
ret
nop
      

Gecompileerde Code


sub rsp,Ox28
call 9 
lea rcx,[rip+Ox0]
mov edx,0x6
call 1a 
xor eax,eax
add rsp,Ox28
ret
nop
      

Vooruitgang in compiler technologie heeft er voor gezorgd dat de performatie tussen van preprocessors verdwenen is

Header Files

  • Source file: .cpp
  • Header file: .h
  • Gebruik:
    • definiëren van:
      • Functie prototypes
      • Variabelen
      • Macros
    • Bevat geen logica
    • "#include" in source file

Header file gebruiken

  • Door preprocessor te gebruiken
  • Declareer dit in het begin van de source file
    • #include "TestHeader.h"
      • "Zoekt in folder"
    • #include <TestHeader.h>
      • "Zoekt in PATH"
      
        #include "mylib.h"
        #include <mylib.h>
        int main(){
        }
      
    

Werking include

test_header.h

        int ledPin = 7;
    
myproject.ino

        #include "test_header.h"
        int state = HIGH;

        void setup(){
          //DO SOMETHING
        }

        void loop(){
          //DO SOMETHING
        }
    
myproject.ino

        int ledPin = 7;
        int state = HIGH;

        void setup(){
          //DO SOMETHING
        }

        void loop(){
          //DO SOMETHING
        }
    

Include Guard

  • Geen ambiguïteit
  • Geen 2 dezelfde includes
  • Kan file size verkleinen

      #ifndef _TEST_LIB_H_
      #define _TEST_LIB_H_

        /***************/
        /*             */ 
        /*    YOUR     */ 
        /*    CODE     */ 
        /*    HERE     */ 
        /*             */ 
        /***************/

      #endif
    

Werking Include Guard

mystruct.h


      struct myStruct{
        int x;
        double y;
        char z;
      };
    

myproject.ino


      #include "mystruct.h"
      #include "mystruct.h"

      void setup(){
        //DO SOMETHING
      }

      void loop(){
        //DO SOMETHING
      }
    

myproject.ino


      struct myStruct{
        int x;
        double y;
        char z;
      };
      // Dubbele declaratie 
      // Compile Error
      struct myStruct{ 
        int x;         
        double y;
        char z;
      };

      void setup(){
        //DO SOMETHING
      }

      void loop(){
        //DO SOMETHING
      }
    

Werking Include Guard

mystruct.h


      #ifndef _TEST_LIB_H_
      #define _TEST_LIB_H_

      struct myStruct{
        int x;
        double y;
        char z;
      };
      #endif
    

myproject.ino


      #include "mystruct.h"
      #include "mystruct.h"

      void setup(){
        //DO SOMETHING
      }

      void loop(){
        //DO SOMETHING
      }
    

myproject.ino


      struct myStruct{
        int x;
        double y;
        char z;
      };
      // Geen compile error

      void setup(){
        //DO SOMETHING
      }

      void loop(){
        //DO SOMETHING
      }
    

Conditional Include

  • Selectief Include
  • Specifieke output afhankelijk van compile opties
    • Cross Compatibility
    • Code verkleinen

#ifndef _AVR_IO_H_
#define _AVR_IO_H_

#include 

#if defined (__AVR_AT94K__)
#  include 
#elif defined (__AVR_AT43USB320__)
#  include 
#elif defined (__AVR_AT43USB355__)
#endif

#endif /* _AVR_IO_H_ */

    

Classes

  • Class Definitions
  • Class Accces Modifiers
  • Class Member Functions

Class Definitions


    class Box {
      public:
        double length;         // Length of a box
        double breadth;        // Breadth of a box
        double height;         // Height of a box
        double getVolume(void);// Returns box volume
    };
  

Class Accces Modifiers

  • public
    • Members toegankelijk buiten de class
    • Geen set/get
  • protected
    • Members toegangelijk voor child classes
    • Overerving niet vaak gebruik op het Arduino platform
  • private
    • Members alleen toegankelijk voor class members
    • Set / Get nodig
    • Default optie

      class Base {

        public:

        // public members go here

        protected:

        // protected members go here

        private:

        // private members go here
    }
    

Class Members Function

Inline Functie

        class Box {
           public:
              double length;      // Length of a box
              double breadth;     // Breadth of a box
              double height;      // Height of a box

              double getVolume(void) {
                 return length * breadth * height;
              }
        };
    
Functie met Scope Resolution Operator

        class Box {
           public:
              double length;      // Length of a box
              double breadth;     // Breadth of a box
              double height;      // Height of a box
        };

        double Box::getVolume(void) {
          return length * breadth * height;
        }
    

Example

Morse.h

      #ifndef Morse_h
      #define Morse_h

      #include "Arduino.h"

      class Morse
      {
        public:
          Morse(int pin);
          void dot();
          void dash();
        private:
          int _pin;
      };

      #endif
    
Morse.cpp

      Morse::Morse(int pin)
      {
        pinMode(pin, OUTPUT);
        _pin = pin;
      }

      void Morse::dot()
      {
        digitalWrite(_pin, HIGH);
        delay(250);
        digitalWrite(_pin, LOW);
        delay(250);  
      }

      void Morse::dash()
      {
        digitalWrite(_pin, HIGH);
        delay(1000);
        digitalWrite(_pin, LOW);
        delay(250);
      }
    

Eigen library maken

  • Quick
  • Full

Quick

  • In project folder
  • Niet meer dan een class declaratie
    • myLib.h
    • myLib.cpp
  • #include "myLib.h"
  • Wordt automatisch mee geopend bij project

Project Folder Structuur


      labo3_guacamole
      ├── Guacamole.cpp
      ├── Guacamole.h
      └── labo3_guacamole.ino
    

Full

  • $USER_DIR/Arduino/libraries
  • Wat voor het meer dan Quick
    • Syntax Highlighting
    • Examples
    • Toegankelijk voor elk nieuw project

Library Folder Structuur


    TestLib
    ├── examples
    │   └── SimpleExample.ino
    ├── keywords.txt
    ├── library.properties
    └── src
        ├── TestLib.cpp
        └── TestLib.h
    

Keywords

  • Syntax Highlighting in de Arduino IDE
  • Reflectie van header file
  • KEYWORD1
    • Datatypes
    • Class Definities

  • KEYWORD2
    • Methoden
    • Functies

  • LITERAL1
    • Constanten

      #######################################
      # Syntax Coloring Map SPI
      #######################################

      #######################################
      # Datatypes (KEYWORD1)
      #######################################

      SPI	KEYWORD1

      #######################################
      # Methods and Functions (KEYWORD2)
      #######################################
      begin	KEYWORD2
      end	KEYWORD2
      transfer	KEYWORD2
      setBitOrder	KEYWORD2
      setDataMode	KEYWORD2
      setClockDivider	KEYWORD2


      #######################################
      # Constants (LITERAL1)
      #######################################
      SPI_CLOCK_DIV4	LITERAL1
      SPI_CLOCK_DIV16	LITERAL1
      SPI_CLOCK_DIV64	LITERAL1
      SPI_CLOCK_DIV128	LITERAL1
      SPI_CLOCK_DIV2	LITERAL1
      SPI_CLOCK_DIV8	LITERAL1
      SPI_CLOCK_DIV32	LITERAL1
      SPI_CLOCK_DIV64	LITERAL1
      SPI_MODE0	LITERAL1
      SPI_MODE1	LITERAL1
      SPI_MODE2	LITERAL1
      SPI_MODE3	LITERAL1
    

Properties File

Metadata voor de Library Manager


    name=SPI
    version=1.0
    author=Arduino
    maintainer=Arduino 
    sentence=Enables the communication with devices that use the Serial Peripheral Interface (SPI) Bus.  
    paragraph=SPI is a synchronous serial data protocol used by microcontrollers for communicating with one or more peripheral devices quickly over short distances. It uses three lines common to all devices (MISO, MOSI and SCK) and one specific for each device.
    category=Communication
    url=http://www.arduino.cc/en/Reference/SPI
    architectures=avr
  

Oefening

  1. Quick Library
    • Maak van debug.txt (te vinden op digitap) een quick library
    • Gebruik een include guard
    • Laat volgend stukje code werken

#ifdef DEBUG
#define DEBUG_INIT() char sDebug[128];
#define DEBUG_PRINTHEX(T, v) Serial.print(T); sprintf(sDebug, "%x\n\r", v); Serial.print(sDebug);
#define DEBUG_PRINTDEC(T, v) Serial.print(T); sprintf(sDebug, "%d\n\r", v); Serial.print(sDebug);
#define DEBUG_HALT() (Serial.available() == 0); Serial.setTimeout(1); Serial.readBytes(sDebug, 1);
#else
#define DEBUG_INIT()
#define DEBUG_PRINTHEX(T, v) 
#define DEBUG_PRINTDEC(T, v)
#define DEBUG_HALT() 
#endif

    

#include "debug.h"

void setup() {
  Serial.begin(9600);
  DEBUG_INIT()
  DEBUG_PRINTDEC("De waarde van X is: ", 89)
  
}

void loop() {
  // put your main code here, to run repeatedly:

} 
    

Oefening

  1. Full Library
    • Conveert de Morse Class naar een full library (te vinden op digitap)
      • Beschikbaar voor elke project
      • Code Highlighting
Morse.h

#define DOT 300
#define DASH  900

class Morse
{
  public:
    Morse(int pin);
    const String MORSE_LUT[26] = { ".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "_._", ".-..", "--", "-.", "---", ".--.", "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--.."};
    void char_to_morse(char c);
  private:
    int _pin;
    void signal(int duration);
};
    
Morse.cpp

Morse::Morse(int pin)
{
  pinMode(pin, OUTPUT);
  _pin = pin;
}

void Morse::signal(int duration)
{
  digitalWrite(_pin, HIGH);
  delay(duration);
  digitalWrite(_pin, LOW);
  delay(duration);  
}

void Morse::char_to_morse(char c){
  String morse_code = MORSE_LUT[c-'a'];
  for (int i = 0; i < morse_code.length(); i++){
    if (morse_code[i] == '.') signal(DOT);
    else signal(DASH);
  }
}
    

Pointers

Wat is een pointer

Een pointer is een variabele dat een geheugen locatie bevat van andere elementen in de code. Een pointer kan de adressen van volgende elementen bevatten

  • Een adres
  • Variabelen
  • Functies
  • Pointers

Geheugen is adresseerbare blok bits!

Wat is een pointer

  • Zorgt voor efficiëntere code
  • Verhoogt de complexiteit
  • Pointers hangen nauw samen met arrays
  • Pointer hangt niet vast aan een datatype (void *pointer)

Wat is een pointer

Pointers Declaren


  int number = 10;
  int *pointer = &number;
    
Je wijst een adres toe aan een pointer

Pointer Operators

  • Adres &
    • Met & vraag je het adres op van een variable
  • Dereferencing *
    • Met * vraag je de achterliggende data op, * ook de indirection operator genoemd.

Pointer Voorbeelden

Declaratie Value Adres
int Val = 2 2 54428
int *pVal = &Val 54428 97880
int secVal = *pVal 2 97932
*pVal = 5 5 54428
pVal = pVal + 1 54444 (+16) 97880

Dus ...

  • int *p = &c
    • De pointer p wijst naar het locatie van c
  • int k = *p
    • k is gelijk aan de achterliggende data van p
  • *p = 0
    • De achterliggende data = 0
  • *j = *p
    • De achterliggende data van j is gelijk aan de achterliggende data van p

Oefening

  • Declareer 3 integers x, y en z met de respectievelijke waardes 1, 5, 155
  • Declareer 3 pointers p_x, p_y, p_z die verwijzen naar de geheugen locaties van x,y,z
  • Print de waardes af van de achterliggende data van de pointers als ook de locaties

Swap Functie

  • Het volgende stuk code swap de variabelen niet
  • Er word een lokale kopie gemaakt
  • Dit noemt Pass By Value
  • De originele data word beschermt

void swap(int x, int y){
int tmp;
tmp = x
x = y;
y = tmp;
}
void main(void){
int a = 10;
int b = 13;

swap (a, b);
}
/*y == ? && z == ?*/
      

Pass By Reference

  • De data wisselt tussen de variabelen
  • Toegang tot de variabele is mogelijk door indirecte toegang
  • Data moet niet gedupliceerd worden
  • Pass By Reference
  • Concept bestaat ook in andere programmeer talen

void swap(int *x, int *y){
int tmp;
tmp = *x
*x = *y;
*y = tmp;
}
void main(void){
int a = 10;
int b = 13;

swap (&a, &b);
}
/*y == ? && z == ?*/
      

Oefening

  • Declareer 3 integers x, y en z met de respectievelijke waardes 1, 5, 155
  • Schuif vanuit een functie de waarde x naar y, y naar z en z naar x

Volgorde van bewerkingen

  • * en & hebben voorrang op rekenkundige operators
  • *, &, ++ en -- hebben dezelfde priorieit
  • * en & worden van rechts naar links geëvalueerd
  • *p++ en (*p)++ zijn verschillend
    • *p++ verhoogt het adres opgeslagen in p en haalt daarna de achterliggende data van het nieuwe adres op
    • (*p)++ verhoogte de achterliggende data op adres p

Volgorde van bewerkingen

Veronderstel dat

  • char c = 5
  • char *p
  • p = &c

Pointers & Arrays

  • Arrays is een blok van dezelfde data types
  • Het eerste element van een array kan beschouwd worden als een pointer van hetzelfde type
  • Door het adres opgeslagen in de pointer te veranderen kunnen we volgende elementen aanspreken
  • Elk array element wordt in opeenvolgende volgorde in het geheugen opgeslagen

int *p
int a[1O]

p = &(a[2]);

/*Dan klopt het volgende*/
*p = a[2];
*(p+ 1) = a[3];
    

Gevaren van pointers

  • Rechtstreekse manipulatie van het geheugen
  • Kent geen out of bounds
    • 
      int a[1O]
      int *p = &(a[0]);
      int i = a[11] //error
      int j = *(p + 15) //mogelijk
            
  • Datatypes zijn niet belangrijk (void *pointer)

Null Terminated C String

  • In C worden alle char arrays getermineerd door '\0'
    • char *x = "hello" == "hello\0"
    • char x[4] = "hello" == "hello\0"
  • Dit maakt het mogelijk om simpels char arrays te kunnen manipuleren

int strlen(char *s) /* added by RJH; source: K&R p99 */
{
  int n;

  for(n = 0; *s != '\0'; s++)
  {
    n++;
  }
  return n;
}
  

Lengte van een object

  • Pointer is een adres
  • Pointers geven niet altijd een lengte mee
  • Functies mbt tot buffers hebben meestal beide nodig
  • 
    void RF24::read(void *buf, uint8_t  len)
    bool RF24::write(const void *buf, uint8_t len)
        
  • De groote van een element - sizeof()
    • sizeof returnt de grote in het aantal bytes
    • sizeof(int) = 4

char data[10]="Hello World"
for (int i = 0; i < sizeof(data)/sizof(int); i++){
//dosomething
}
  

char data[10]="Hello World"
write(data, sizeof(data));
  

Strings

  • Op een micrcontroller werkt men niet graag met string maar null terminated arrays
  • Kijk zeker hier is: Werken met buffers

#include 

void *memcpy (void *dest, const void *src, size_t len)
{
  char *d = dest;
  const char *s = src;
  while (len--)
    *d++ = *s++;
  return dest;
}
    

Oefening

Schrijf een functie met pointers waar je een 2 arrays aan elkaar rijgt (concat)

Union

A union is a variable that may hold (at different) times objects of different types and sizes, with the compiler keeping track of size and alignment requirements. Unions provide a way to manipulate different kinds of data in a single area of storage.

  • acces
    • union_name.member
    • union-name.member
  • Maar een datatype per moment mogelijk
  • Alle datatypes gedeclareerd starten op dezelfde geheugen locatie

union my_data {
  int value;
  char bytes[sizeof(int)];
} this_data;    

union my_data other_data
    

Union Voorbeeld


void setup(){

  union  {
  unsigned short int vals_ints[3];
  unsigned char vals_chars[sizeof(unsigned short int)*3+1];
  } vals;
  
  vals.vals_ints[0] = 1;
  vals.vals_ints[1] = 2;
  vals.vals_ints[2] = 0xccAA;

  printf("Loc struct: %p\n",(void*) &vals);
  printf("Loc 1: %p\n",(void*) &vals.vals_ints[0]);
  printf("Loc 2: %p\n",(void*) &vals.vals_ints[1]);
  printf("Loc 3:%p\n",(void*) &vals.vals_ints[2]);
  printf("Loc Array: %p\n",(void*) &vals.vals_chars);

  printf("Val 1: %d\n",vals.vals_ints[0]);
  printf("Val 2: %d\n",vals.vals_ints[1]);
  printf("Val 3: %d\n",vals.vals_ints[2]);
  printf("Byte 1: %u\n",vals.vals_chars[0]); //unsigned short int is 2 bytes lang  
  printf("Byte 2: %u\n",vals.vals_chars[2]); //daarom 0 2 4 als index
  printf("Byte 3: %x\n",vals.vals_chars[4]);
  printf("Val array: %d\n",vals.vals_chars);
  /*
   *Let wel op de endianness, de integer word met litlle endianness opgeslagen
   in het geheugen. Dit kan je zien met print van byte 3.
   * */

}

void loop(){

}

/* OUTPUT
Loc struct: 0x7ffc295e2bd0
Loc 1: 0x7ffc295e2bd0
Loc 2: 0x7ffc295e2bd2
Loc 3:0x7ffc295e2bd4
Loc Array: 0x7ffc295e2bd0
Val 1: 1
Val 2: 2
Val 3: 52394
Byte 1: 1
Byte 2: 2
Byte 3: aa
Val array: 694037456
      

Union Gebruik

In het Arduino I2C Master Reader/Slave Writer Voorbeeld

Master Reader


#include <Wire.h>

const int MEMBERS = 3;
const int SIZE = sizeof(unsigned short int) * 3 + 1; 

union  {
  unsigned short int vals_ints[MEMBERS];
  unsigned char vals_chars[SIZE];
} vals;

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

void loop() {
  Wire.requestFrom(8,sizeof(unsigned short int)*3+1);    
  if (Wire.available() == SIZE) {
    Wire.readBytes(vals.vals_chars, SIZE);
    for (int i = 0; i < MEMBERS; i++) Serial.println(vals.vals_ints[i]);
  }

  delay(500);
}
      

Slave Sender


#include 

const int MEMBERS = 3;
const int SIZE = sizeof(unsigned short int) * 3 + 1; 

union  {
  unsigned short int vals_ints[MEMBERS];
  unsigned char vals_chars[SIZE];
} vals;

vals.vals_ints[0] = 0;
vals.vals_ints[1] = 1;
vals.vals_ints[2] = 2;


void setup() {
  Wire.begin(8);

  Wire.onRequest(requestEvent); 
}

void loop() {
  delay(100);
}

void requestEvent() {
  Wire.write(vals.vals_chars); 
}
      

Typedef

  • Creeren van nieuwe datatypes
  • Makkelijke toegang voor structs, unions
  • Creeren van architectuur afhankelijke code

Zonder Typedef


union my_data {
  int value;
  char bytes[sizeof(int)];
} this_data;    

union my_data other_data
      

Met Typedef


type_def union my_unions {
  int value;
  char bytes[sizeof(int)];
} my_union;    

my_union this_data
my_union other_data
      

Typedef - Architectuur afhankelijke code

Standaard Int Definities AVR (8bit)

Source

Standaard Int Definities Raspberry Pi (32bit)

Source