TM1638 and SPI Interrupts

This is a follow up on my previous re: Tom Almy's TM1638 library.

I've attached his code, and I replaced his test loop() code (which works) with a simple test that should cycle the indicated LED's on and off. However, it doesn't work. After the second updateDisplay(), the LEDs are never turned off. Strangely, if I insert a Serial.println(), it works as expected. I don't understand the indexing in his ISR(), which is where I think the problem lies. Anybody willing to investigate ?

Thanks

/* TM1638_test -- Test program for LED&KEY board with TM1638. Interrupt driven state machine.
 *  This code works for Arduino boards with AVR microcontrollers. 16MHz clock.
  Copyright (C) 2020  Thomas Almy

  This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public License
  as published by the Free Software Foundation; either version 2
  of the License, or (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

  Contact the author at tom@almy.us */
#include "Pins.h"  // Use the new Pin.h library

// SPI pin assignments depend on board.
// Board                 MOSI    MISO    SCK
// Uno/Nano/Nano Every   11      12      13
// Mega                  51      50      52
// Leonardo              ICSP-4  ICSP-1  ICSP-3  (only on ICSP header)
// Micro                 16/MOSI 14/MISO 15/SCK  (see arduino.cc for locations!)
// Pro Micro             16       14     15  

// You can connect to the ICSP header on all boards if you wish.

// IMPORTANT: MISO and MOSI must be connected together.

// For the TM1638 and LED&KEY board, connect DIO to MISO&MOSI, CLK to SCK,
// and STB to digital pin 2 on the Arduino board.

#define TM1638_STB digital_2  // put STB on pin 2

#if defined (__AVR_ATmega4809__)
// Different SPI module
#define SPDR SPI0.DATA
#endif

static const uint8_t PROGMEM SEGMENT_MAP[] = {
  0b00111111, // 0
  0b00000110, // 1
  0b01011011, // 2
  0b01001111, // 3
  0b01100110, // 4
  0b01101101, // 5
  0b01111101, // 6
  0b00000111, // 7
  0b01111111, // 8
  0b01101111, // 9
  0b01110111, // A
  0b01111100, // b
  0b00111001, // C
  0b01011110, // d
  0b01111001, // E
  0b01110001, // F

  0b10111111, // 0.
  0b10000110, // 1.
  0b11011011, // 2.
  0b11001111, // 3.
  0b11100110, // 4.
  0b11101101, // 5.
  0b11111101, // 6.
  0b10000111, // 7.
  0b11111111, // 8.
  0b11101111, // 9.
  0b11110111, // A.
  0b11111100, // b.
  0b10111001, // C.
  0b11011110, // d.
  0b11111001, // E.
  0b11110001, // F.
  0b01000000, // - (index 32)
  0x0 // blank (index 33)
};

void delay1us(void) { // Gives a 1us delay between driving edges
  // trimmed for AVR. Seems still OK for 20MHz clock in Nano Every
  _NOP();
  _NOP();
  _NOP();
  _NOP();
  _NOP();
  _NOP();
};

void setup() {
  ddr.digital_SS = 1; // This pin must be output
  ddr.TM1638_STB = 1;
  port.TM1638_STB = 1;
  ddr.digital_MOSI = 1; // Output for now
  ddr.digital_SCK = 1;
  // Enable all but interrupt, LSB first, 1MHz clock
  // Negative clock (CPOL=1) and reversed phase (CPHA=1)
  // so that we can do the direction change.

  #if defined (__AVR_ATmega4809__)
// Different SPI module
  PORTMUX.TWISPIROUTEA |= PORTMUX_SPI0_ALT2_gc;
#if F_CPU >= 20000000
  // We need /32 divider with 20MHz clock otherwise timing is at hairy edge
  // Use the /16 divider at your own risk!
  SPI0.CTRLA = 0x75;  // DORD MASTER CLK2X PRESC1 (/32) ENABLE
#else
  SPI0.CTRLA = 0x63; // DORD MASTER PRESC0 (/16) ENABLE
#endif
  SPI0.CTRLB = 3; // Buffering off, CPOL=1, CPHA=1
    SPI0.INTCTRL = 1; // Enable interrupt

#else
  SPCR = (1 << SPE) + (1 << SPIE) + (1 << MSTR) + (1 << CPOL) + (1 << CPHA) + (1 << DORD) + 1;
#endif

  // Do initialization ISR will reset the SPI interface
  port.TM1638_STB = 0;
  SPDR = 0x89; // Activate display, low (level 1) brightness
  delay(1); // Time to execute above
}

// Values for the row of LEDs and the 7-segment display are stored in
// these variables prior to updating with updateDisplay()
uint8_t buffer_LED = 0; // LSB is leftmost LED
uint8_t buffer_display[8] = {10, 11, 12, 13+16, 5, 6, 7, 8};

// The push button values are returned here with readButtons()
volatile uint8_t button_values = 0;

// These are private variables for the SPI state machine
volatile uint8_t spi_index = 0;
volatile uint8_t tm1638_command = 0;


// We can update the entire display (8 digits and 8 LEDs) with 18 bytes
// transferred. If we use the command to write a specific byte, it takes
// 3 bytes transferred. So this way is only slower if need to update 5 or
// fewer digits/LEDs. But we have the advantage that the entire display is
// updated in the background, which takes about 250us. The call to updateDisplay
// takes under a microsecond if not blocked. The conventional 
// TM1638 libraries will block during the update, which takes about 3.4 ms for
// the digits alone.

// The functions updateDisplay and readButtons block only if the SPI is
// still shifting from a previous command. To prevent blocking, check
// for SPI running by calling spi_idle first.

// Call this function to see if the SPI is idle and ready for a command
inline bool spi_idle(void) {
  return (tm1638_command == 0);
}

// Starts the SPI state machine to update the LED and 7-segment displays
// Do not update the buffer_LED and buffer_display variables until after call
// and spi_idle() returns true.
void updateDisplay(void) {
  while (!spi_idle()); // block if SPI still running
  port.TM1638_STB = 0; // start command
  tm1638_command = 0x40;
  SPDR = 0x40; // command to write data incrementing address
}


// Starts the SPI state machine to update the button_values variable.
// Values are updated when spi_idle() returns true.
void readButtons(void) {
  while (!spi_idle()); // block if SPI still running
  port.TM1638_STB = 0; // start command
  tm1638_command = 0x42;
  SPDR = 0x42;
}

// The SPI state machine is implemented in its ISR
#if defined (__AVR_ATmega4809__)
ISR(SPI0_INT_vect) { // Invoked when transfer complete
  SPI0.INTFLAGS = 0x80; // clear flag because it doesn't happen automatically in 4809

#else
ISR(SPI_STC_vect) { // Invoked when transfer completes
#endif
  if (tm1638_command == 0x42) { // Read switches/buttons
    if (spi_index == 0) { // start
      // switch direction and only read
      ddr.digital_MOSI = 0;
      spi_index = 1;
      button_values = 0;
      SPDR = 0; // start read
    } else if (spi_index < 4) { // read first three button bytes
      button_values |= SPDR << (spi_index - 1);
      SPDR = 0; // start next read
      spi_index++;
    } else { // last button byte
      button_values |= SPDR << 3;
      delay1us();
      port.TM1638_STB = 1;
      delay1us();
      ddr.digital_MOSI = 1; // Do last, after SS is high
      tm1638_command = 0;
      spi_index = 0;
    }
  }
  else if (tm1638_command == 0x40) { // write all display
    if (spi_index == 0) { // start
      // End command byte and set the address
      port.TM1638_STB = 1;
      delay1us();
      port.TM1638_STB = 0;
      SPDR = 0xc0;
      spi_index = 2; // index plus 2 when writing
    } else if (spi_index == 18) { // we are done
      delay1us();
      port.TM1638_STB = 1;
      delay1us();
      spi_index = 0;
      tm1638_command = 0;
    } else {
      uint8_t index = (spi_index >> 1) - 1;
      if (spi_index & 1) { // LED
        SPDR = (buffer_LED >> index) & 1;
      } else { // 7-segment display
        SPDR = pgm_read_byte(&SEGMENT_MAP[buffer_display[index]]);
      }
      spi_index++;
    }
  }
  else { // Single byte command (like RESET) or an error condition
         // Just reset the SPI
    tm1638_command = 0;
    spi_index = 0;
    port.TM1638_STB = 1;
  }
}


// Test program is in loop()
int counter = 0;
uint8_t last_button_values;
void loop_old() 
{
  int temp = counter++;
  last_button_values = button_values;
  readButtons();
  buffer_display[7] = temp % 10;
  temp /= 10;
  buffer_display[6] = temp % 10;
  temp /= 10;
  buffer_display[5] = temp % 10;
  temp /= 10;
  buffer_display[4] = temp % 10;
  while (!spi_idle()) ; // switch read must complete
  // before we use the values
  // Toggle LED on button press
  buffer_LED ^= button_values & ~last_button_values;
  updateDisplay();  
  delay(100);
}

void loop()
{
  delay(1000);
  buffer_LED = 0b00000000;  
  //Serial.println();  
  updateDisplay();  // do this after every change in display data

  delay(1000);
  buffer_LED = 0b00100100;
  updateDisplay();
  
}

My interpretation of the code is that spi_index is used to implement a state machine (driving the SPI data register: SPDR) and to index bit positions in buffers.

For reading buttons the TM1638 uses 4 bytes containing button state of max 8x3 keys. This code is specific to the LED&KEY module and only reads 8 keys).

For displaying segments the TM1638 has 8x2 bytes of display memory that can address 10segments x 8 positions. Here spi_index is used to index bits in the buffers used to light the leds.

(Btw I didn't see your previous posted as you didn't link to it)

The problem is that the buffer_LED variable is not declared volatile. It is accessed in the ISR(). Changing this make the library work as expected.

To be safe, I changed the buffer_display (the digits) to volatile also.

K

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.