Interrupt driven display driver for the Arduino nano

CircuitPicture

There are a number of 7 segment display available on the Internet. Some of them use 74595 shift registers to control them. These require the display to be refreshed continuously as the LED’s are multiplexed across a number of digital output pins. I bought one from dx.com (link) though I see they are now out of stock. With a bit of fiddling I came up with the following interrupt driven code to drive it.

The PWM system is used to generate a periodic interrupt on digital output 3. Arduino allows you to attach an interrupt to this pin which works even if the pin is a PWM output. This is done in the setup function. The interrupt handler is set to RefreshDisplay.

The RefreshDisplay function copies the contents of the global array DisplayMemory to the 74595 shift registers in the the display module. One digit is written for each interrupt. After 8 interrupts, all digits have been updated and the process starts over.

The main loop calls on the DisplayNumber function whose job is to take a apart the supplied number into each of its individual digits. The digits are converted to 7-segment LED codes which are stored in DisplayMemory.
The display is connected to the ICSP header of an Arduino nano as shown. The clock and data pins are driven by software.

int SCKPin = 13;
int DIOPin = 11;
int RCKPin = 12;
int DisplayMemory[8];
void setup()
{
  pinMode(RCKPin,OUTPUT);
  pinMode(DIOPin,OUTPUT);
  pinMode(SCKPin,OUTPUT);
// Establish periodic interrupt on digital pin 3 so that RefreshDisplay
// is called regularly
  analogWrite(3,100);
  attachInterrupt(1,RefreshDisplay,RISING);
}
long Count=0;
void loop()
{ 
  DisplayNumber(Count++);
}
const char LED_Codes[]={ 0b11000000,0b11111001,0b10100100,0b10110000,0b10011001,0b10010010,0b10000010,0b11111000,0b10000000,0b10010000 };

void DisplayNumber(long Number)
{
  int Digit;
  for (Digit=0;Digit<8;Digit++)
  {
    DisplayDigit(7-Digit,Number % 10);
    Number = Number / 10;
  }
}
void DisplayDigit(int DigitNumber,int Digit)
{
  DisplayMemory[DigitNumber]=LED_Codes[Digit];
}
void RefreshDisplay()
{
  static  int Digit=1;
  static int Index=0;
  SendByte(Digit);
  SendByte(DisplayMemory[Index]);
  digitalWrite(RCKPin,LOW);
  digitalWrite(RCKPin,HIGH);  
  Digit = Digit << 1;  
  Index++;
  if (Index > 7)
  {
    Index = 0;
    Digit = 1;
  }

}
void SendByte(int Byte)
{
  int Bit;
  for (Bit = 0; Bit < 8; Bit++)
  {
    if (Byte & 0x80)
      digitalWrite(DIOPin,HIGH);
    else
      digitalWrite(DIOPin,LOW);
    Byte = Byte << 1;
    digitalWrite(SCKPin,LOW);
    digitalWrite(SCKPin,HIGH);
  }
}

A very low cost STM32F030 dev board

Aliexpress have begun shipping a low cost breakout/development board for the STM32F030. The board can be programmed using ISP and a USB/UART interface as shown below. The boards cost varies but I got mine for 1.58 Euro.
stm32f030board
I had previously worked on some suitable examples which can be found over here.
These examples are built using a Makefile however I have found that the following script is a lot easier to work with (just make sure your PATH environment variable includes the directory where the arm compiler programs and utilities are stored).

arm-none-eabi-gcc -static -mthumb -g -mcpu=cortex-m0 *.c -T linker_script.ld -o main.elf -nostartfiles 
arm-none-eabi-objcopy -g -O binary main.elf main.bin
arm-none-eabi-objcopy -g -O ihex main.elf main.hex

You then program the chip by linking the Boot0 pin and 3v3 with the jumper, hit reset and enter the following:

stm32flash -w main.hex /dev/ttyUSB0

To run your program, move the jumper so that it links Boot0 to GND and hit reset.
The USB/UART interface appeared as /dev/ttyUSB0 on my system, yours may vary (on Windows it will be something like COM3)
stm32flash can be downloaded from a number of sources. On Ubuntu it can be installed with

sudo apt-get install stm32flash

The ARM cross compiler suite can be downloaded from launchpad.net

Aliexpress link to the board.

Interactive SPI

The SPI protocol can be tricky enough to get working especially if you are unsure of the MCU you are using and/or the peripheral.  Logic analyzers can help but can also be expensive.  With the help of the following Energia MSP430G2553 code and a dumb terminal serial application program (on your PC) you can interact live with an SPI peripheral and hopefully come to grips with its operation.

The peripheral can be wired as follows:
LaunchPads-MSP430G2-—-Pins-Maps-13-42

Launchpad                   Peripheral
MOSI------------------------MOSI
MISO------------------------MISO
P1_0------------------------SS (slave select or CE)
GND-------------------------GND
Vcc-------------------------Vcc

Check the peripheral power requirements first and don’t connect a 5V peripheral directly to a 3.3V MSP430

The program presents the user with a simple menu:

Please select from one of the following:
0: SS Low
1: SS High
2: Write byte
3: Read byte

If you choose 0 or 1, SS is raised or lowered as appropriate and the menu recycles. If you choose 2 you see this (I entered the value ‘a9’ (not case sensitive))

Please select from one of the following:
0: SS Low
1: SS High
2: Write byte
3: Read byte
Enter a 2 character hex value: a9
Out : A9
In : 0

If you choose 3 you will see something like this:

Please select from one of the following:
0: SS Low
1: SS High
2: Write byte
3: Read byte
In : 0

The code is shown below. You will probably need to check out which SPI modes and byte ordering suit you. Also, the SPI interface is running at the very low speed of 125kHz. This was deliberate as it reduces the risk of data errors on shaky test leads and may help debugging. You can of course change this. The divider is divided into 16MHz to give an SPI data rate. This is very definitely version 0.1 and changes are likely in the future when I do some real testing.

/*
 * SPI protocol tester using the MSP430G2553 G2 Launchpad
 * This program allows you manage an SPI bus,write and read data
 * using a serial dumb terminal application
 * The program makes use of the Energia Serial and SPI libraries
 * Serial interface : 9600,n,8,1
 * SPI library reference : http://energia.nu/reference/spi/
 * 
 */
#include <SPI.h>

// Will use P1_0 as SS pin as there is a handy LED there on the launchpad
#define SS_Pin  P1_0


int getUserCommand();
int getInteger(String Prompt);
void setup() {
  // put your setup code here, to run once:
  // Default SPI configuration : feel free to change!
  // Set up the SS Pin and make it HIGH initially (low wakes up a slave)
  pinMode(SS_Pin,OUTPUT);  
  digitalWrite(SS_Pin,HIGH);
  SPI.begin();
  SPI.setDataMode(SPI_MODE0); // can choose modes 0,1,2,3
  SPI.setBitOrder(MSBFIRST);  // can be MSBFIRST or LSBFIRST
  SPI.setClockDivider(128);   // assuming a system clock of 16MHz this gives an 
                              // SPI speed of 125kHz - deliberately slow to be more forgiving and
                              // to make signals easier to see with a scope of logic analyser
  // Serial communications to host setup
  Serial.begin(9600);
}
int TXByte,RXByte;
void loop() {
  
  // put your main code here, to run repeatedly: 
  switch (getUserCommand())
  {
    case 0 : {
      // Command 0 : drop SS pin down
      digitalWrite(SS_Pin,LOW);    
      break;
    }
    case 1 : {
      // Command 1 : raise SS pin up
      digitalWrite(SS_Pin,HIGH);
      break;
    }
    case 2 : {
      // Command 2 : send a byte
      TXByte = getInteger("Enter a value to transmit: ");
      RXByte = SPI.transfer(TXByte);
      Serial.print("Out : ");
      Serial.println(TXByte,HEX);
      Serial.print("In : ");
      Serial.println(RXByte,HEX);
      break;
    }
    case 3 : {
      // Command 3 : read a byte (send a dummy byte out)
      RXByte = SPI.transfer(0x00);
      Serial.print("In : ");
      Serial.println(RXByte,HEX);
      break;      
    }
    default : {
      Serial.println("Invalid choice");
    }
  }
  delay(100);
}
int showMenu(String Menu[],int MenuItemCount)
{
  Serial.flush();
  Serial.println("Please select from one of the following:");
  for (int item=0; item < MenuItemCount; item++)
  {
    Serial.print(item);
    Serial.print(": ");
    Serial.println(Menu[item]);
  }
  while(Serial.available()==0);
    
  return Serial.read() - '0'; // assuming a numeric choice is made - convert to decimal from ascii
}

int getUserCommand()
{
  String Menu[4];
  Menu[0]="SS Low";
  Menu[1]="SS High";
  Menu[2]="Write byte";
  Menu[3]="Read byte";
  return showMenu(Menu,4);
  
}
int HexDigitToDecimal(char Digit)
{
  if ( (Digit >= '0') && (Digit <= '9') )
  {
    return Digit - '0';
  }
  Digit = Digit | 32; // enforce lower case
  if ( (Digit >= 'a') && (Digit <= 'f') )
  {
    return Digit - 'a' + 10;
  }
  return 0;
}
int getInteger(String Prompt)
{
  char HexString[3];
  int ReturnValue = 0;
  HexString[2]=0;
  Serial.flush();
  Serial.print("Enter a 2 character hex value: ");
  while(Serial.available()==0);  
  HexString[0]=Serial.read();Serial.print(HexString[0]);
  while(Serial.available()==0);  
  HexString[1]=Serial.read();Serial.println(HexString[1]);
  ReturnValue = HexDigitToDecimal(HexString[0]);
  ReturnValue = ReturnValue << 4;
  ReturnValue += HexDigitToDecimal(HexString[1]);
  return ReturnValue;
}