Driving LED Displays With Arduino And MAX7219

The Arduino platform is great for making LEDs blink and driving small displays, but what happens when you need to display more complex information? For Project ColorTyme, I need to drive 6x 14-segment “starburst” style alphanumeric LED displays, but pins are at a premium. To solve the problem, we’ll incorporate the MAX7219 LED display driver; however, it’s an imperfect solution: The displays I’m using are common-anode while the MAX7219 is a common-cathode driver. In this video, we’ll walk through wiring a 14-segment display to the Arduino, then wiring the MAX7219 to drive in common-anode mode while adjusting the code so everything comes out correctly.


(Most of these can be found in the Arduino Starter Kit available here)

Arduino UNO

Solderless breadboard

Jumper (Dupont) Wire

MAX7219 LED Driver

14 Segment LED Display


MAX7219 Datasheet

LedControl Arduino Library (patched for common-anode displays)


The main thing to remember when wiring in this inverse method is that the segment pins correspond to individual digits while the digit pins correspond to individual segments. This illustration is also limited to a single 14-segment digit, but the pinout for a dual display is what is wired.


//Project ColorTyme
//MAX7219x14-segment Display Proof of Concept
//CC-BY-SA Matthew Eargle
//element14 Presents http://element14.com/presents
//AirborneSurfer Productions http://airbornesurfer.com

#include "LedControl.h"
pin 12 is connected to the DataIn
pin 11 is connected to the CLK
pin 10 is connected to LOAD
We have only a single MAX72XX.
LedControl lc=LedControl(12,11,10,1);

unsigned long delaytime=100;
byte numbers[16] = {
B11111100, B01100000, B11011010, B11110010,
B01100110, B10110110, B10111110, B11100000,
B11111110, B11110110, B11101110, B11110011,
B10011100, B11110001, B10011110, B10001110
int ones = 0;
int twos = 0;

void setup() {
MAX72XX wakeup call
set the brightness /
clear the display */
int counter = 0;

void loop() {
while(ones <= 15) {
ones = 0;

How To Set Up A TFT Screen On Arduino With The RA8875

In a previous video, we looked at how to attach a TFT LCD screen to a Raspberry Pi to build a small-form-factor project. But what if your project doesn’t need quite as much horsepower? What if you just need to display shapes and colors on a screen with no operating system and nearly instant boot times? This is where a TFT LCD screen on Arduino comes in handy. In this video, we’ll look at how to set up a TFT LCD screen on Arduino with the RA8875 breakout board from Adafruit then walk through how to display various shapes and colors in the sketch.


(Most of these can be found in the Arduino Starter Kit available here)

Arduino UNO

Solderless breadboard

Jumper (Dupont) Wire

TFT LCD Display

Adafruit RA8875 Breakout Board


RA8875 Datasheet

Adafruit RA8875 Arduino Library


It’s a fairly simple wiring job here using the hardware SPI interface on the Arduino. CLK, MISO, and MOSI connect to 13, 12, 11 respectively. CS, RST, and INT are defaulted to 10, 9, and 3, but can be reassigned in the sketch. Of course, the display is not illustrated, but it is connected to the 40-pin ribbon connector on the left.


 This is an example for the Adafruit RA8875 Driver board for TFT displays
 ---------------&gt; http://www.adafruit.com/products/1590
 The RA8875 is a TFT driver for up to 800x480 dotclock'd displays
 It is tested to work with displays in the Adafruit shop. Other displays
 may need timing adjustments and are not guanteed to work.

 Adafruit invests time and resources providing this open
 source code, please support Adafruit and open-source hardware
 by purchasing products from Adafruit!

 Written by Limor Fried/Ladyada for Adafruit Industries.
 BSD license, check license.txt for more information.
 All text above must be included in any redistribution.

#include "Adafruit_GFX.h"
#include "Adafruit_RA8875.h"

// Library only supports hardware SPI at this time
// Connect SCLK to UNO Digital #13 (Hardware SPI clock)
// Connect MISO to UNO Digital #12 (Hardware SPI MISO)
// Connect MOSI to UNO Digital #11 (Hardware SPI MOSI)
#define RA8875_INT 3
#define RA8875_CS 10
#define RA8875_RESET 9

Adafruit_RA8875 tft = Adafruit_RA8875(RA8875_CS, RA8875_RESET);
uint16_t tx, ty;

void setup()
  Serial.println("RA8875 start");

  /* Initialize the display using 'RA8875_480x80', 'RA8875_480x128', 'RA8875_480x272' or 'RA8875_800x480' */
  if (!tft.begin(RA8875_480x272)) {
    Serial.println("RA8875 Not Found!");
    while (1);

  Serial.println("Found RA8875");

  tft.GPIOX(true);      // Enable TFT - display enable tied to GPIOX
  tft.PWM1config(true, RA8875_PWM_CLK_DIV1024); // PWM output for backlight

  // With hardware accelleration this is instant

  // Play with PWM
  for (uint8_t i=255; i!=0; i-=5 )
  for (uint8_t i=0; i!=255; i+=5 )


  // Try some GFX acceleration!
  tft.drawCircle(100, 100, 50, RA8875_BLACK);
  tft.fillCircle(100, 100, 49, RA8875_GREEN);

  tft.fillRect(11, 11, 398, 198, RA8875_BLUE);
  tft.drawRect(10, 10, 400, 200, RA8875_GREEN);
  tft.fillRoundRect(200, 10, 200, 100, 10, RA8875_RED);
  tft.drawLine(10, 10, 200, 100, RA8875_RED);
  tft.drawTriangle(200, 15, 250, 100, 150, 125, RA8875_BLACK);
  tft.fillTriangle(200, 16, 249, 99, 151, 124, RA8875_YELLOW);
  tft.drawEllipse(300, 100, 100, 40, RA8875_BLACK);
  tft.fillEllipse(300, 100, 98, 38, RA8875_GREEN);
  // Argument 5 (curvePart) is a 2-bit value to control each corner (select 0, 1, 2, or 3)
  tft.drawCurve(50, 100, 80, 40, 2, RA8875_BLACK);
  tft.fillCurve(50, 100, 78, 38, 2, RA8875_WHITE);

  pinMode(RA8875_INT, INPUT);
  digitalWrite(RA8875_INT, HIGH);


  Serial.print("Status: "); Serial.println(tft.readStatus(), HEX);
  Serial.println("Waiting for touch events ...");

void loop()
  float xScale = 1024.0F/tft.width();
  float yScale = 1024.0F/tft.height();

  /* Wait around for touch events */
  if (! digitalRead(RA8875_INT))
    if (tft.touched())
      Serial.print("Touch: ");
      tft.touchRead(&amp;tx, &amp;ty);
      Serial.print(tx); Serial.print(", "); Serial.println(ty);
      /* Draw a circle */
      tft.fillCircle((uint16_t)(tx/xScale), (uint16_t)(ty/yScale), 4, RA8875_WHITE);


Using An Arduino To Drive A Geiger Counter

It's a moving coil-style analog meter
Moving coil-style analog meter from a CDV-700 (jonshobbies.com)

Traditionally, a Geiger counter like the CDV-700 series is a completely analog device–the device output is driven entirely by the analog electronics. Electrical pulses passing through the Geiger-Müller tube during ionizing events are run through a speaker cone, generating the characteristic clicks of a Geiger counter. That voltage is also directed into an electromechanical meter that displays the average clicks over a given amount of time. For Project Pripyat, I wanted to have the option to drive several different kinds of output with minimal rewiring and I want to be able to save data gathered and send it to a computer. The easiest way to accomplish all of these objectives was to pipe the GM tube output into an Arduino, and I just so happened to have a bunch of Nanos in my parts bin!

Arduino code for a Geiger counter

The Nano has a pretty low tolerance for excessive voltage on its digital pins, so I had to incorporate a pretty hefty voltage divider into my circuit design (from 400V down to <5V) to prevent frying the thing. The code itself was a work of trial and error, mostly playing around with various ways to drive the analog meter. Since the Arduino does not have true analog output (only pulse-width modulation), I decided to let digital pulses “kick” the needle into the appropriate position on the meter. The more frequent the pulses, the more the needle will be displaced. It’s basically PWM, but there’s no averaging being done in software. The v1.0 code, therefore, is rock-basic simple. It is almost entirely pin definitions, based on the “Blink” example sketch, but it’s snappy and serves its purpose as a “minimum viable” solution.

const byte interruptPin = 2;
attachInterrupt(digitalPinToInterrupt(interruptPin), blink, LOW);

In the sketch, we define several pins on the Arduino and how they’ll be used. Pin 2 is going to serve as an interrupt and is attached to the pulse generator in the circuit. In my Geiger counter circuit, the pulse generator is actually reverse-biased to become an interrupt generator. Every ionizing event detected by the GM tube causes the 2N2222 transistor to ground the signal line connected to the Arduino which will be picked up as the interrupt signal. Upon detecting the interrupt, the Arduino will jump to the blink interrupt service routine which changes quickly changes pin states for the LED, meter, and piezo buzzer. The counter sketch has a resolution of about 3 milliseconds which is significantly lower than the theoretical dead time on the SBM-20 tube, but this project is more of a concept demonstration and exploratory toy than anything else, so I’m not worried about it.

One other item to note: When simply given a high/low signal on one pin, the piezo buzzer’s clicks are extremely soft. Connecting the ground terminal of the buzzer to another digital pin held low and swapping the high/low pins during the ISR effectively doubles the deflection of the buzzer and results in a much more satisfying click!

/* Project Pripyat v1.0
 *  by Matthew Eargle https://airbornesurfer.com
 *  for element14 Presents
 *  CC-BY-SA 2018 AirborneSurfer
const byte ledPin = 13;
const byte interruptPin = 2;
const byte speakerPin = 5;
volatile byte state = LOW;
volatile byte speaker = LOW;

void setup() {
  pinMode(ledPin, OUTPUT);
  pinMode(speakerPin, OUTPUT);
  pinMode(interruptPin, INPUT);
  pinMode(6, OUTPUT);
  pinMode(12, OUTPUT);
  pinMode(9, OUTPUT);
  attachInterrupt(digitalPinToInterrupt(interruptPin), blink, LOW);

void loop() {
  digitalWrite(ledPin, LOW);
  digitalWrite(speakerPin, LOW);
  digitalWrite(6, HIGH);
  digitalWrite(12, LOW);

void blink() {
  digitalWrite(ledPin, HIGH);
  digitalWrite(speakerPin, HIGH);
  digitalWrite(6, LOW);
  digitalWrite(12, HIGH);