[Elixir Nerves] Seven-segment LED, shift register and SPI interface

I enjoy Elixir programming for my Raspberry Pis using Nerves IoT framework. This is my study note about controlling a seven-segment LED with a shift register through SPI interface from my Nerves-powered Raspberry Pi.

When I was doing experiments with a character LCD display with serial interface, I learned many boards like Adafruit i2c/SPI LCD backpack has shift registers or I/O expanders built in so that we do not do the tedius work of physically wiring many I/O pins. All I need was just connect a few wires for i2c/SPI.

Today, I want to wire an 8-bit shift register by myself. I use one digit seven-segment LED display, which has eight LEDs internally. From my Raspberry Pi, I will control the seven-segment display though the SPI interface.

Seven-segment LED display

According to Wikipedia:

A seven-segment display is a form of electronic display device for displaying decimal numerals that is an alternative to the more complex dot matrix displays.

There are two types of seven-segment LED display:

  • Common-Cathode
  • Common-Anode

We need to use a resistor for GND or VCC in accordance with the forward current value in the product's data sheet just like we would for basic LEDs.

Common-Cathode (CC) display

Common-Anode (CA) display


SN74HC595 is an 8-bit shift register, which can be used as a serial-to-parallel converter to send signals to the display. From our controller device, we serially send eight bits of data through one signal pin over to the shift register. Then the shift register can output it from eight pins at once.


QA-QH8-bit parallel data output pins
QH'serial output that can be connected to SER of another 74HC595
SRCLR (Shift Register Clear)negative logic pin; should be kept at HIGH.
SRCLK (Shift Register Clock)For each clock pulse, data in the shift register moves by one bit.
RCLK (Register Clock)For each clock pulse. data in the shift register moves into memory register.
OE (Output Enable Input)negative logic pin; should be kept at LOW.
SER (Serial Data Input)The data is entered serially through this pin, one bit a time.

The Arduino website a nice tutorial on 74HC595 Shift Register.


According to Wikipedia:

The Serial Peripheral Interface (SPI) is a synchronous serial communication interface specification used for short-distance communication, primarily in embedded systems.

I use Circuits.SPI library, which allows me to send a byte at a time

To me, the advantage of using SPI for a shift register is that SPI abstracts away the connection between the controller device and the shift register. All I need is just to send a byte using SPI library. Without SPI, I would have to write code to send one bit after another and then send a signal to trigger the output for each iteration.





74HC595Raspberry PiSeven-segment display
QB (Output)-B
QC (Output)-C
QD (Output)-D
QE (Output)-E
QF (Output)-F
QG (Output)-G
QH (Output)-DP
GND (Ground)GND (Ground)-
QH' (Output)--
SRCLR (Shift Register Clear Input)3.3V-
SRCLK (Serial Clock Input)SPI SCLK (Clock)-
RCLK (Register Clock Input)SPI CE (Chip Enable)-
OE (Output Enable Input)GND (Ground)-
SER (Serial Data Input)SPI COPI (Controller Out Peripheral In)-
QA (Output)-A
VCC (Power)3.3V-

SN74HC595 top view




The seven-segment display has seven segments plus dot, a total of eight LEDs. We will specify which segment we want to be on/off by sending a byte (8 bits). Depending on the display type, a high bit (1) can mean "on" or "off" state. In my case, since I use a common-cathode display, a high bit represents the "on" state. Assuming the pin assignment is as stated earlier, the following list maps numbers to eight bits for a common-cathode display.


With a common-anode display, the on/off state above would be inverted. For example, 11000000 would represent the "0" shape.

Here is the Elixir code that I wrote. I ssh into my Raspberry Pi and run that program from the Interactive Elixir shell (iex).

# ["spidev0.0", "spidev0.1"]

{:ok, ref} = Circuits.SPI.open("spidev0.0")
# {:ok, #Reference<0.50134155.268828678.71837>}

# Show "0" on the display.
Circuits.SPI.transfer(ref, <<0x3f>>)

# A function that repeats displaying from "0" to "9".
count_fn = fn(count_fn) ->
  digits = [0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f]
  digits |> Enum.each(fn x ->
    Circuits.SPI.transfer(ref, <<x>>)


# Call the function.

One mistake I made

I forgot to use a 220ohm resistor for the common cathode. I re-wire with a 220ohm resistor without turning off the device, which broke the LED closest to the pin I was working on.

That's it.