Web Banner

Introduction

This is the third part of this project. The aim is to make a custom 7-segment display controlled through the I2C interface. The development will be broken down into several parts to show how I evolved the design. The display module will be controlled by an ATTiny85 and this will then drive the 7-segment displays using the 74HC595 8-bit shift register.

In this part I am going to add a simple command interface which will allow me to change the modules I2C address (via EEPROM).

If you would like to see the video, please feel free to click the link below, otherwise just read on.

Before getting started, If you want to see the how to program the ATTiny (Part 1) or how I started the writing the code for the ATTiny (Part 2) or more information on the 74HC595 then click on the links below:

  • ATTiny ISP Programmer - Part 1
  • ATTiny I2C Display Module - Part 2
  • 74HC595 Overview
  • Adding a Command Structure

    In the last part, we got the basic display module working. Now I want to expand the capabilites of the display. This requires adding a simple command interface.

    Updating the Sketch

    Most of the work is done in the recieve section. I've added two extra bytes that must be sent to the module. These extra bytes must always be sent. The first is going to be used as a 'Command', the second byte will be used as a 'Value'. With these two extra bytes we can have over 65,000 features, I think this should be enough....

    The first step was to keep the existing functionality working, to do this we can define a command byte for 'normal' operation. The second byte for 'value' can just be ignored as we don't have a use for it yet.

    When the data is recieved from the I2C master, the first byte is processed by a switch statment. This gives us 255 possible commands. Just allocate one for the 'Normal' operation as we currently have it working. The remaining 6-bytes of data can then be processed exactly the same as we did in the last section.

    We now can define a 'Command' for setting the I2C address (See line number 108). The second byte can now be used as the 'Value' for the I2C address. We also need to add some sanity checks to make sure that the value is a valid I2C address.

    The only problem is where to store it. We can not assign it to a variable since this will be lost when power is removed from the module. The solution is to write it to EEPROM. Data stored in the EEPROM does not get lost when the power is removed. The only limitation is the number of write cycles to EEPROM. Most EEPROMs have a limited number of write cycles typically about ~10,000. This will not be a problem for this application, but you would not want to store data that changes frequently in an EEPROM. Since the EEPROM we are using is in the microcontroller, once this fails you would have to replace the microcontroller.

    The final task is how to read this from the EEPROM. If we go to line number 33, you can see that the first byte of the EEPROM is read on start up and assigned to 'i2c_address'. The only problem we have is what happens the first time the display module is powered if the EEPROM has not been programmed? The good news is that EEPROMS when blank have '0x00' or '0xff' in all of the memory locations. A simple sanity check allows us to use a default value if the data in the EEPROM is blank.

    Note: Reading from the EEPROM does not decrease its life expectancy. The EEPROM is only limited in the number of WRITE cycles.

    ATTiny85 .INO File

    // Please see credits and usage for usiTwiSlave and TinyWireS in the .h files of // those libraries. #include <avr/sleep.h> #include <avr/wdt.h> #include <EEPROM.h> #include "TinyWireS.h" #define I2C_DEFAULT_ADDR 0x04 // i2c default address (4, 0x04) #define DISPLAY_NUMBER 0x00 #define SET_I2C_ADDR 0x50 uint8_t i2c_address = I2C_DEFAULT_ADDR; // Pin 1 - Reserved for Reset uint8_t SER = 3; // Pin 2 - SER to 74HC595 uint8_t SRCLK = 4; // Pin 3 - SRCLK to 74HC595 // Pin 4 - Gnd // Pin 5 - SDA uint8_t RCLK = 1; // Pin 6 - RCLK to 74HC595 // Pin 7 - SCL // Pin 8 - Vcc void setup() { pinMode(SER,OUTPUT); //Configure PB3 as output pinMode(RCLK,OUTPUT); //Configure PB1 as output pinMode(SRCLK,OUTPUT); //Configure PB4 as output i2c_address = EEPROM.read(0); if ((i2c_address== 0x00)||(i2c_address== 0xff)){ i2c_address = I2C_DEFAULT_ADDR; } clear_display(); TinyWireS.begin(i2c_address); // Initiazlize the I2C Slave mode TinyWireS.onReceive(receiveEvent); // Register the onReceive() callback function // disable the watchdog timer so that it doesn't cause power-up, code is from datasheet // Clear WDRF in MCUSR – MCU Status Register // MCUSR provides information on which reset source caused an MCU Reset. MCUSR = 0x00; // WDTCR - Watchdog Timer Control Register WDTCR |= ( _BV(WDCE) | _BV(WDE) ); // Write logical one to WDCE and WDE (must be done before disabling) WDTCR = 0x00; // Turn off WDT set_sleep_mode(SLEEP_MODE_PWR_DOWN); // Enable power down sleep mode sleep_enable(); sei(); // Enable interrupts } void loop() { } void clear_display(){ int numberOfShiftRegisters = 6; digitalWrite(SER, 0); //Set SER line LOW digitalWrite(RCLK, 0); //Set RCLK line LOW digitalWrite(SRCLK, 0); //Set SRCLK line LOW // Since we don't have a free pin to use to clear the data from the shift-registers, simply // clock a '0' through all registers for(int x = 0; x<(8*numberOfShiftRegisters);x++){ // For each settable bit in the Shift-registers digitalWrite(SRCLK, 1); // Clock the Serial Clock line High digitalWrite(SRCLK, 0); // Clock the Serial Clock line Low } digitalWrite(RCLK, 1); // Pulse the RLCK to clear any junk data on the output pins. digitalWrite(RCLK, 0); //Note: If we add a small delay (1ms) to enabling the OE pin, then 74HC595 will be initialized with all '0' preventing any unwanted data from being //visible on the 7-segment displays. Even with OE and SCLR lines not being used. } // Gets called when the ATtiny receives an i2c write slave request // This routine runs from the usiTwiSlave interrupt service routine (ISR) // so interrupts are disabled while it runs. void receiveEvent(uint8_t num_bytes) { uint8_t display_byte[16]; uint8_t master_bytes = num_bytes - 2; uint8_t command = TinyWireS.receive(); uint8_t value = TinyWireS.receive(); switch (command){ case DISPLAY_NUMBER: for (uint8_t i = 0; i < master_bytes; i++){ // Process each byte of data from the master display_byte[i] = TinyWireS.receive(); for (uint8_t x = 0; x < 8; x++){ //Loop for each bit within data byte digitalWrite(SER, bitRead(display_byte[i], x)); //Set the SER pin based on the bit of data digitalWrite(SRCLK, 1); //Set SRCLK High digitalWrite(SRCLK, 0); //Set SRCLK Low } } digitalWrite(RCLK, 1); // Pulse the RLCK to clear any junk data on the output pins. digitalWrite(RCLK, 0); break case SET_I2C_ADDR: if ((value > 0) && (value < 128)){ EEPROM.write(0,value); } break } }

    Upload this sketch using the Arduino ISP programmer and then place the ATTiny85 back into the development board. Try changing the I2C address. Note: After chaning the I2C address, you will need to power cycle the display module before the change takes effect.

    Driving the I2C Display Module

    Below is the simple sketch that I used to test the display module. To keep this working all I needed to add was the extra two bytes (line number 18 & 19) to be sent, when sending the display command.

    #include <Wire.h> #include <Arduino.h> uint8_t i2c_address = 0x04; void setup() { Wire.begin(); display("654321"); } void loop() { } void display (String data){ Wire.beginTransmission(i2c_address); //Start the I2C Session Wire.write(0x00); //Command Display Number Wire.write(0x00); //Value does not matter... for (int x=data.length() ; x>=0 ; x--){ //Process the char array one byte at a time char aByte=data.charAt(x); char value = 0; char dp = 0; if ((x+1)//Check if we are setting the decimal place if (data.charAt(x+1) == '.'){ dp = 0x01; } } switch (aByte){ //Send the correct bit sequence for each char case '-': // '-' sign value = 0x04|dp; // Add the deciamal place if set Wire.write(value); //Send the encoded value to the display break case '0': // '0' value = 0xfa|dp; // Add the deciamal place if set Wire.write(value); //Send the encoded value to the display break case '1': // '1' value = 0x60|dp; // Add the deciamal place if set Wire.write(value); //Send the encoded value to the display break case '2': // '2' value = 0xdc|dp; // Add the deciamal place if set Wire.write(value); //Send the encoded value to the display break case '3': // '3' value = 0xf4|dp; // Add the deciamal place if set Wire.write(value); //Send the encoded value to the display break case '4': // '4' value = 0x66|dp; // Add the deciamal place if set Wire.write(value); //Send the encoded value to the display break case '5': // '5' value = 0xb6|dp; // Add the deciamal place if set Wire.write(value); //Send the encoded value to the display break case '6': // '6' value = 0xbe|dp; // Add the deciamal place if set Wire.write(value); //Send the encoded value to the display break case '7': // '7' value = 0xe0|dp; // Add the deciamal place if set Wire.write(value); //Send the encoded value to the display break case '8': // '8' value = 0xff|dp; // Add the deciamal place if set Wire.write(value); //Send the encoded value to the display break case '9': // '9' value = 0xe6|dp; // Add the deciamal place if set Wire.write(value); //Send the encoded value to the display break case 'C': // 'C' value = 0x9a|dp; // Add the deciamal place if set Wire.write(value); //Send the encoded value to the display break case 'F': // 'F' value = 0x8e|dp; // Add the deciamal place if set Wire.write(value); //Send the encoded value to the display break case 'o': // 'o' value = 0x3c|dp; // Add the deciamal place if set Wire.write(value); //Send the encoded value to the display break } } Wire.endTransmission(); //End the transmission }

    Download

    ATTiny85 Project (3)

    The files shown on this page.

    Zip file containing source code