RaspberryPi:ProgrammingInC

From UCT EE Wiki
Jump to navigation Jump to search

Overview[edit]

There are a few C and C++ libraries available for programming the Raspberry Pi's GPIO pins. This article will only cover use of WiringPi. Please note that this guide is by no means meant to serve as a comprehensive reference. Rather, it is included to show what WiringPi offers.

WiringPi is a C library (that can be used in C or C++) developed by Gordon Henderson (@drogon). Despite the fact that the library has recently been deprecated, it remains an easy way of interfacing with the Raspberry Pi and its peripherals.

Documentation for the library can be found here: [1].

WiringPi on the Command Line - The gpio Utility[edit]

Before jumping in to WiringPi and using it in a C/C++ application, it’s useful to note that WiringPi has a command line tool that can be used to control the pins. More information on this can be found here.

Working with WiringPi[edit]

To use WiringPi in your code, you need to include it as you would any library. It generally comes installed on Raspbian, so you can just include it as follows:

#include <wiringPi.h>

You also need to link it when compiling to make use of some of the more advanced functionality. See Toolchains, Compilers And Makefiles to better understand this.

-lwiringPi

You need to specify which board mode you’re using. For more information on board modes, see Section [sec:BoardModes]. Board modes are specified when the library is initialized as shown below. These functions need to be run with root privileges, so make sure to use sudo ./your_program.

//Initialises WiringPi and assumes that the calling program is going to be using the wiringPi pin numbering scheme.
wiringPiSetup();

//Initialises WiringPi and allows the calling programs to use the Broadcom GPIO pin numbers directly with no re-mapping.
wiringPiSetupGpio();

//Initialises WiringPi and allows the calling programs to use the physical pin numbers on the P1 connector only.
wiringPiSetupPhys();

You also need to perform “cleanup” on GPIOs when your application exists. By performing cleanup and resetting all pins to their default modes, you can prevent possible damage to your Raspberry Pi. There is not default cleanup function in WiringPi, so you need to write your own.

Core Functions[edit]

This section covers the core functions available in the WiringPi library. Read the documentation, available here.

  • Set the Pin Mode

    void pinMode (int pin, int mode) ;

    This sets the mode of a pin to either INPUT, OUTPUT, PWM_OUTPUT or GPIO_CLOCK. Note that only wiringPi pin 1 (BCM_GPIO 18) supports PWM output and only wiringPi pin 7 (BCM_GPIO 4) supports CLOCK output modes.

  • Set the Internal Resistors

    void pullUpDnControl (int pin, int pud) ;

    This sets the pull-up or pull-down resistor mode on the given pin, which should be set as an input. The parameter pud can be one of the following:

    • PUD_OFF - no pull up/down

    • PUD_DOWN - pull to ground

    • PUD_UP - pull to 3.3v

    The internal pull up/down resistors have a value of roughly 50KΩ on the Raspberry Pi.

  • Write a Digital Value

    void digitalWrite (int pin, int value) ;

    Writes the value HIGH or LOW (1 or 0) to the given pin which must have been previously set as an output. WiringPi treats any non-zero number as HIGH, however 0 is the only representation of LOW.

  • Read a Digital Value

    int digitalRead (int pin) ;

    This function returns the value read at the given pin. It will be HIGH or LOW (1 or 0) depending on the logic level at the pin.

Analog[edit]

The Raspberry Pi does not have any analog input pins. You will need to use something like the MCP3008 to read analog voltages.

PWM[edit]

Hardware PWM[edit]

Read the documentation here. To see which pins support hardware PWM, consult pinout.xyz. The following functions are available:

  • Set the PWM mode

     pwmSetMode (int mode);

    The PWM generator can run in 2 modes – “balanced” and “mark:space”. The mark:space mode is traditional, however the default mode in the Pi is “balanced”. You can switch modes by supplying the parameter: PWM_MODE_BAL or PWM_MODE_MS.

  • Set the range register

    pwmSetRange (unsigned int range) ;

    The default is 1024.

  • Set the divisor register

    pwmSetClock (int divisor) ;

Software PWM[edit]

Read the documentation here. If you’re using PWM and it’s not on the PWM pin on the Pi, you must include softPwm:

#include <softPwm.h>

When compiling, you will alos need to link the PThreads library:

-lpthread

The following functions are available:

  • Create a software PWM signal:

     int softPwmCreate (int pin, int initialValue, int pwmRange) ;

    You can use any GPIO pin and the pin numbering will be that of the wiringPiSetup() function you used. Use 100 for the pwmRange, then the value can be anything from 0 (off) to 100 (fully on) for the given pin.

    The return value is 0 for success. Anything else and you should check the global errno variable to see what went wrong.

  • Create a software PWM signal:

    void softPwmWrite (int pin, int value) ;

    This updates the PWM value on the given pin. The value is checked to be in-range and pins that haven’t previously been initialised via softPwmCreate will be silently ignored.

Notes on software PWM:

  • Each “cycle” of PWM output takes 10mS with the default range value of 100, so trying to change the PWM value more than 100 times a second will be futile.
  • Each pin activated in softPWM mode uses approximately 0.5
  • There is currently no way to disable softPWM on a pin while the program in running.
  • You need to keep your program running to maintain the PWM output!

Interrupts[edit]

Very often it is useful to set up an interrupt, for example to trigger an event when a button is pressed. Callback functions are executed on a different thread. If you have multiple callbacks, know that they will be executed sequentially as only one thread exists for interrupts. Interrupts can be implemented as follows:

 int wiringPiISR (int pin, int edgeType,  void (*function)(void)) ;

An example of how to set up a button connected between ground and pin 23 to be used as an interrupt could be as follows:

pinMode(23, INPUT); 
pullUpDnControl(23, PUD_UP);
wiringPiISR(23,INT_EDGE_RISING,&callback_function);

Software Debounce[edit]

Deboucing is important to prevent multiple triggers of an interrupt. There are two options, hardware and software debounce. Software debounce in C could be implemented as follows:

void callback_function(void){
    long interruptTime = millis();
    
    if (interruptTime - lastInterruptTime>200){
        // Perform your logic here      
    }
    lastInterruptTime = interruptTime;
}

Communication Protocols[edit]

WiringPi, unlike RPI.GPIO described at RaspberryPi:ProgrammingInPython, offers methods for communication.

I2C[edit]

Read the documentation at WiringPi I2C, and be sure to link WiringPi when compiling.

Ensure you enable I2C in raspi-config.

Once you connect a device, you can run $gpio i2cdetect to determine if a device is detected on the I2C bus. In the example below, there is an electronic compass (GY-271) connected to the Pi.

An i2cdetect example

To initialize an I2C device, do the following:

#include <wiringPiI2C.h>

And call the function:

int wiringPiI2CSetup (int devId) ;

The function returns a negative 1 if an error occurs. If we want to make use of this functionality, we can write code similar to the following:

if (wiringPiI2CSetup (0x1e) == -1) {
    printf("I2C init failed \n");
    return;
}

Note: For all the following functions, if the return value is negative then an error has happened and you should consult errno.

To read a byte from the I2C device, you can use the following function:

int wiringPiI2CReadReg8 (int fd, int reg) ;

To write a byte to the I2C device:

int wiringPiI2CWriteReg8 (int fd, int reg, int data) ;

Tying all these together, we can create an example. Imagine an I2C device that holds a simple count value, which we want to increase by 1. We can do the following:

int devcheck= wiringPiI2CSetup (0x1e);
if (devcheck== -1) {
    printf("I2C init failed \n");
    return;
}

int value = wiringPiI2CReadReg8(i2cdevice, 0x01);
value = value +1;
wiringPiI2CWriteReg8(i2cdevice, 0x01, value) ;

SPI[edit]

Read the documentation at WiringPi SPI, and be sure to link WiringPi when compiling. Ensure you enable SPI in raspi-config.

To initialize SPI, include the header:

#include <wiringPiSPI.h>

You also need to call the Setup function. channel refers to whichever SPI channel you use (0 or 1, based on the pins you use). Speed is the speed in Hz, and is an integer in the range 500,000 through 32,000,000.

int wiringPiSPISetup (int channel, int speed) ;

To write a value through SPI, the following function is used:

int wiringPiSPIDataRW (int channel, unsigned char *data, int len) ;

This performs a simultaneous write/read transaction over the selected SPI bus. Data that was in your buffer is overwritten by data returned from the SPI bus.

Tying this together, we can develop the following example:

spidevice = wiringPiSPISetup(0, 1000000) ; // Channel 0 at 1MHz
if (spidevice == -1) {
    printf("SPI init failed \n");
    return;
}

unsigned char data = {0x01, 0x02, 0x03};

wiringPiSPIDataRW(0, data, 3);

printf{"Obtained these values off spidevice:\n"};
for(int i = 0; i< 3; i++){
    printf("%u", data[i]);
}

Timing[edit]

The WiringPi library has some other useful timing functions. Read the documentation here. These include:

  • unsigned int millis (void);

This returns a number representing the number of milliseconds since your program called one of the wiringPiSetup functions. It returns an unsigned 32-bit number which wraps after 49 days.

  • unsigned int micros (void);

This returns a number representing the number of microseconds since your program called one of the wiringPiSetup functions. It returns an unsigned 32-bit number which wraps after approximately 71 minutes.

  • void delay (unsigned int howLong);

This causes program execution to pause for at least howLong milliseconds. Due to the multi-tasking nature of Linux it could be longer.

  • void delayMicroseconds (unsigned int howLong);

This causes program execution to pause for at least howLong microseconds. Due to the multi-tasking nature of Linux it could be longer.

Delays under 100 microseconds are timed using a hard-coded loop continually polling the system time. Delays over 100 microseconds are done using the system nanosleep() function. You may need to consider the implications of very short delays on the overall performance of the system, especially if using threads.

Using Threads[edit]

While WiringPi does offer wrapping for PThreads (you can read the WiringPi documentation on that here), we’re going to make use of the PThreads library for threads.

A full guide for using PThreads can be found at this link. You need to include PThreads:

#include <pthread.h>

and link it when compiling:

-lpthread

The following example code creates a high priority thread, and is taken (almost verbatim) from this reference.

void *threaded_function(void *threadargs){
    // Do some threaded task
    pthread_exit(NULL);
}


int main(void) {
    pthread_attr_t tattr;
    pthread_t thread_id;
    int newprio = 99; // set highest priority
    sched_param param;
    
    pthread_attr_init (&tattr);
    pthread_attr_getschedparam (&tattr, &param); /* safe to get existing scheduling param */
    param.sched_priority = newprio; /* set the priority; others are unchanged */
    pthread_attr_setschedparam (&tattr, &param); /* setting the new scheduling param */
    pthread_create(&thread_id, &tattr, threaded_function, (void *)1); /* with new priority specified *
    
    //Perform some other menial tasks
    
    // Wait for the thread to finish
    pthread_join(thread_id, NULL); 
    return;
}