Embedded Linux - Lab Exercise 3: Revision History Rev Date Author Notes
Embedded Linux - Lab Exercise 3: Revision History Rev Date Author Notes
Embedded Linux - Lab Exercise 3: Revision History Rev Date Author Notes
Revision history
Rev Date Author Notes
0.1 24.9.2019 Jarno Tuominen Initial revision – work in progress!
1
1 Introduction
In this lab we will study SPI interface and continue programming of RasPI HW resources.
In a most common timing-mode (CPOL=0, CPHA = 0), new data is written to the bus (MOSI) on a
trailing edge (falling edge) of the clock signal, and reading of the data value (MISO) takes places on
the leading (rising edge) of the clock signal.
https://en.wikipedia.org/wiki/Serial_Peripheral_Interface
While it is possible to make a SPI controller purely with software and GPIOs (this is called “bit
banging”), it is very inefficient, because the CPU must actively drive GPIO-pins to create clock pulses
and data lines, with a correct timing. Luckily, most microcontrollers have small peripheral blocks,
which take care of the data transmission and clocking of the SPI bus, leaving CPU the tasks to write
data to SPI Tx-buffer and read data from SPI-Rx-buffer. In a way, these peripheral block can be
considered as HW accelerators – they even often DMA-capabilities to maximize the CPU off-loading.
The microcontroller used in RasPi, Broadcom BCM2711, has several SPI controllers, in fact 6 in total.
5 of those can be connected to pins, which are routed to 40-pin RasPi expansions connector. Note:
Microcontrollers have typically much more integrated peripheral devices (I2Cs, PWMs, SPIs, UARTs)
than available I/O-pins. This means that the physical I/O-pins can be GPIO’s or can have alternative
functions. Mapping these peripherals to I/O-pins can be really complicated task. Recall the RasPi pin-
out:
2
Physical pins 19, 21 and 23 are typically mapped SPI0 MOSI, MISO and SCLK respectively. Pins 24 and
26 are used as SPI0 bus Chip Selects. This is true also for PiFace Digital 2 –board, which we will use in
this lab exercise.
More info on SPI implementation on RasPi:
https://www.raspberrypi.org/documentation/hardware/raspberrypi/spi/README.md
PiFace Digital 2 is an expansion board to RasPi 8´(a “HAT”), which includes an I/O-expander chip with
SPI interface – Microchip MCP23S17. (There is also a sister model of this IC with I2C interface,
MCP23017). It provides 16 I/Os with programmable direction, and 2 interrupt input lines.
3
Look at the datasheet of MCP23S17 (You will find it in Redmine) and answer the following questions:
1. What is the maximum SPI clock frequency? Assume Vcc = 3.3V.
2. How much earlier the CS needs to be pulled down before the first rising edge of the clock?
Assume mode 0,0.
3. What is the data set-up time, i.e. how much earlier the data must be stable prior to rising
edge of the clock?
4. What is the maximum time you can expect from succesful serial data write to change in
GPIO outputs?
5. How many configurable registers MCP23S17 has?
6. What is the required pattern you need to write over SPI in order to set GPB0 to logic ‘1’? Use
hexadecimal notation and don’t forget the control/address word(s)
a. Assume that slave address[3:1] is “000”
b. Use IOCON.Bank = ‘0’. This means: 3-3 and 3-5 has correct register address
mappings
c. TIP: You don’t need to write to every register in the chip – have a look at the power-
on-reset (POR) values of the registers – if they are ok, just leave them. Focus only on
the ones which needs to be changed.
http://www.piface.org.uk/products/piface_digital_2/
http://piface.github.io/pifacedigitalio/
https://github.com/ibexuk/C_RaspberryPi_PiFace/blob/master/piface.cpp
4
2 Programming MCP23S17 over SPI
As you may have noticed earlier, it can be quite tedious to program/control even a very
simple component, like an I/O-expander in this case. Majority of the peripheral devices and
sensors used nowadays have similar serial communication-based programming interface,
and they can contain DSP’s, in-built (additional) microcontrollers etc, and their datasheets
can be easily hundreds of pages.
Luckily, most chip vendors provide drivers for their chips. Some of them are really simply
ones, just containing the low-level functions and register mappings, whereas some drivers
provide extensive APIs. In all cases, these drivers needs to ported in your system.
A simple C “library” for MCP23S17 I/O expander is available in github. Let’s study it for a
while.
https://github.com/piface/libmcp23s17
Get it from github (git clone and so on…)
Create a new project in Eclipse, this time do not select “Hello World”-project, take the
“Empty Project” instead
Give you project some meaningful name (this will be the name of the executable)
Drag and drop the “src”-directory of libmcp23s17-directory (created when you cloned it
from the git) to your newly created project. Select “Copy files and folders”. You should now
have mcp23s17.c & .h in /src –directory under your project directory (which is in your Eclipse
Workspace-directory)
Drag in example.c from libmcp23s17 in a similar way, also under “src” directory. We want to
make it flat at this point to avoid include path issues.
Drag in also interrupt_example.c, under src. At this point modify it so that you wrap main()-
function inside #if 0 .. #end if. Otherwise you would have two main()-functions in your
project, which would throw an error.
TIP: “#if 0” is a really handy way to comment out big blocks of your code quickly. When you
need to enable that part of code again, just change it to “#if 1”. It’s the C-preprocessor,
which handles these directives. This approach is widely used if you have several build
configurations of your project (To learn more, see:
https://en.wikibooks.org/wiki/C_Programming/Preprocessor_directives_and_macros )
Build the project- it should compile and link without any errors or warnings
5
From pull-down menu, select Run -> Debug Configurations… Right-click the debug-configuration you
created for Lab 2 and select “duplicate”
In “Project”, click “browse” and select your new project. (This will rename the debug configuration).
Replace the Name, C/C++ application, Remote absolute file path and “Commands to execute…” with
the corect application name (i.e. your project name). Now you should have a working debug
configuration for your newly created project. (You can also start making the debug configuration
from scratch by double-clicking C/C++ Remote Application – the copy-method is a bit faster.)
Launch the debugger, start stepping your code and observe what happens when calling function
“mcp23s17_open”.
The reason: SPI interface is not enabled in RasPi by default. In more detail: The spi kernel driver
module was not loaded (into kernel). Furthermore, the SPI device node – a character device
representing the SPI driver, is missing.
Go to shell and look in /dev/ and see it yourself: no SPI-devices there.
Type: “lsmod” (or “lsmod |grep spi”). This shows you all the loaded kernel modules – SPI is not
there.
You can add the SPI drivers to kernel on the fly with mobprobe. Modprobe is a smarter version of
insmod, able to handle dependencies between kernel modules.
(Note: there seems to some bug in Raspbian Buster, loading spidev module should automatically
create/enumerate the spi nodes to the device tree, but it does not do so always… )
spidev0 refers to SPI bus number 0 (there are two of those in RasPi connector), whereas .0 refers to
CS0 and .1 to CS1. See the link to hardware here?
To load SPI modules automatically at start-up, launch raspi-config and enable SPI in Interfaces-
section.
6
Now, restart your program, everything should work just fine.
Edit the main loop so that it has an infinite loop, inside which you print out the input state of the
GPIO-pins. Add a 1 second sleep in the loop as well.
Recompile, restart and still it should work just fine – you just don’t have any hardware connected yet
to the SPI-bus. None of the initilization routines are actually checking if there is an SPI device
connected to a bus, so we are just “yelling into a forest” without expecting any answer. Similarly,
when reading the GPIO-input, we are listening but no one is talking, so we get zero. Often the SPI-
devices contain some kind of Device_ID-register, which is being read during start-up to verify that
the hardware and SPI-bus is functional.
Next, connect the PiFace Digital 2 –boadr to RasPi:
1. Shutdown RasPi (sudo shutdown –h now)
2. Wait for the red LED to light up
3. Detach power cable
4. Attach the board to RasPi. Be careful – this is still electronics, not heavy machinery. If you
need to use excessive force, you are doing something wrong! Note: There seems to be
mechanical issue with the HAT-board and RasPi 4 – the board hits the ethernet connector
and does not go all the way to bottom of the connector. Still, it makes contact and it works.
5. Power-up RasPi and let it boot
Now run/debug your application and see a LED show. Press the 4 buttons all in turn (do not apply
too much force! There’s a risk of short circuit if the HAT board bends to touch RasPi), even
simultaneously and observe the values that are being read in.
Can you explain why the value is 0xff when no buttons are pressed?
Examples how to access GPIO in RasPi – several approaches and several languages
https://elinux.org/RPi_GPIO_Code_Samples
7
3 Using the interrupt coming from GPIO-expander
Next, wrap the main()-function of example.c inside #if 0 - #end if – thing and take the main()-
function in interrupt_example.c into use (change #if 0 to #if 1 – see how Eclipse shows these code
blocks nicely)
Compile and run it. What happens?
Follow the call path all the way to init_epoll(). Put a breakpoint there and step forward – in which
line does the error happen?
In this implementation interrupts are handled by using epoll() (a POSIX function) – which actually is
not an interrupt handler at all, but a function, which can be used to poll for changes in files.
In sysfs, gpio-pins are presented as virtual (or pseudo) files – writing to these files will not cause any
disk activity, but instead gpio-pins configuration or output state will be change. Similarly, when
reading these pseudo files, we get the state of gpio-pins configuration or it’s input state.
Normally, accessing gpio requires root priviledges, which can be really problematic when running
user applications. When a gpio is exported, the ownership of these pseudo files is changed to the
user running the program. This means that you can have a small script of gpio exports to setup the
gpio pins as your program requires without the need to run anything as root, or with the sudo
command. Similarly, once exported, you can use these virtual files to access all the exported I/Os
from your C-code.
These virtual files (also called device interfaces) are located in /sys/class/gpio/
FILE *fd;
fd = fopen("/sys/class/gpio/export", "w");
2. Write the pin name to export “file” (and close the file handler for export)
Once the gpio has been exported this is how it looks like in /sys/class/gpio/:
(the export was done in this case with gpio export – command, part of wiringPi -library)
8
4 A closer look at WiringPi library
WiringPi comes with a handy tool to manipulate GPIOs – the tools is gpio
gpio can be called from shell scripts – and also for example from C and Python programs by issuing
functions, which call (linux) system functions.
NOTE: The Raspbian Buster release seems to have an old WiringPi installed, which does not
recognize Raspi 4. Luckily, an updated version is available.
Check the version of wiringPi you have installed:
If it’s older than 2.52, you need to update it. It’s not available in apt (at least, not yet).
wget https://project-downloads.drogon.net/wiringpi-latest.deb
(And if you are in a mood for sad stories, read this: http://wiringpi.com/wiringpi-deprecated/
…sigh… )
type: gpio readall to see status of all the GPIOs + a pin map
Note especially, how GPIO numbers seen by Broadcom’s chip are different that the pin numbers
used by wiringPi.
9
5 Compiling and debugging
10
6 Eclipse tips
To perform string search on all files of the project: ctrl-H
11