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

    1 : // Please see credits and usage for usiTwiSlave and TinyWireS in the .h files of 2 : // those libraries. 3 : 4 : #include <avr/sleep.h> 5 : #include <avr/wdt.h> 6 : #include <EEPROM.h> 7 : #include "TinyWireS.h" 8 : 9 : #define I2C_DEFAULT_ADDR 0x04 // i2c default address (4, 0x04) 10 : 11 : #define DISPLAY_NUMBER 0x00 12 : #define SET_I2C_ADDR 0x50 13 : 14 : 15 : uint8_t i2c_address = I2C_DEFAULT_ADDR; 16 : 17 : // Pin 1 - Reserved for Reset 18 : uint8_t SER = 3; // Pin 2 - SER to 74HC595 19 : uint8_t SRCLK = 4; // Pin 3 - SRCLK to 74HC595 20 : // Pin 4 - Gnd 21 : // Pin 5 - SDA 22 : uint8_t RCLK = 1; // Pin 6 - RCLK to 74HC595 23 : // Pin 7 - SCL 24 : // Pin 8 - Vcc 25 : 26 : 27 : void setup() 28 : { 29 : pinMode(SER,OUTPUT); //Configure PB3 as output 30 : pinMode(RCLK,OUTPUT); //Configure PB1 as output 31 : pinMode(SRCLK,OUTPUT); //Configure PB4 as output 32 : 33 : i2c_address = EEPROM.read(0); 34 : if ((i2c_address== 0x00)||(i2c_address== 0xff)){ 35 : i2c_address = I2C_DEFAULT_ADDR; 36 : } 37 : 38 : clear_display(); 39 : 40 : TinyWireS.begin(i2c_address); // Initiazlize the I2C Slave mode 41 : TinyWireS.onReceive(receiveEvent); // Register the onReceive() callback function 42 : 43 : // disable the watchdog timer so that it doesn't cause power-up, code is from datasheet 44 : // Clear WDRF in MCUSR – MCU Status Register 45 : // MCUSR provides information on which reset source caused an MCU Reset. 46 : MCUSR = 0x00; 47 : // WDTCR - Watchdog Timer Control Register 48 : WDTCR |= ( _BV(WDCE) | _BV(WDE) ); // Write logical one to WDCE and WDE (must be done before disabling) 49 : WDTCR = 0x00; // Turn off WDT 50 : 51 : 52 : set_sleep_mode(SLEEP_MODE_PWR_DOWN); // Enable power down sleep mode 53 : sleep_enable(); 54 : sei(); // Enable interrupts 55 : } 56 : 57 : void loop() 58 : { 59 : } 60 : 61 : 62 : void clear_display(){ 63 : int numberOfShiftRegisters = 6; 64 : 65 : digitalWrite(SER, 0); //Set SER line LOW 66 : digitalWrite(RCLK, 0); //Set RCLK line LOW 67 : digitalWrite(SRCLK, 0); //Set SRCLK line LOW 68 : 69 : // Since we don't have a free pin to use to clear the data from the shift-registers, simply 70 : // clock a '0' through all registers 71 : for(int x = 0; x<(8*numberOfShiftRegisters);x++){ // For each settable bit in the Shift-registers 72 : digitalWrite(SRCLK, 1); // Clock the Serial Clock line High 73 : digitalWrite(SRCLK, 0); // Clock the Serial Clock line Low 74 : } 75 : digitalWrite(RCLK, 1); // Pulse the RLCK to clear any junk data on the output pins. 76 : digitalWrite(RCLK, 0); 77 : 78 : //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 79 : //visible on the 7-segment displays. Even with OE and SCLR lines not being used. 80 : } 81 : 82 : 83 : // Gets called when the ATtiny receives an i2c write slave request 84 : // This routine runs from the usiTwiSlave interrupt service routine (ISR) 85 : // so interrupts are disabled while it runs. 86 : void receiveEvent(uint8_t num_bytes) 87 : { 88 : uint8_t display_byte[16]; 89 : uint8_t master_bytes = num_bytes - 2; 90 : 91 : uint8_t command = TinyWireS.receive(); 92 : uint8_t value = TinyWireS.receive(); 93 : 94 : switch (command){ 95 : case DISPLAY_NUMBER: 96 : for (uint8_t i = 0; i < master_bytes; i++){ // Process each byte of data from the master 97 : display_byte[i] = TinyWireS.receive(); 98 : for (uint8_t x = 0; x < 8; x++){ //Loop for each bit within data byte 99 : digitalWrite(SER, bitRead(display_byte[i], x)); //Set the SER pin based on the bit of data 100 : digitalWrite(SRCLK, 1); //Set SRCLK High 101 : digitalWrite(SRCLK, 0); //Set SRCLK Low 102 : } 103 : } 104 : digitalWrite(RCLK, 1); // Pulse the RLCK to clear any junk data on the output pins. 105 : digitalWrite(RCLK, 0); 106 : break 107 : 108 : case SET_I2C_ADDR: 109 : if ((value > 0) && (value < 128)){ 110 : EEPROM.write(0,value); 111 : } 112 : break 113 : } 114 : 115 : } 116 : 117 :

    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.

    1 : #include <Wire.h> 2 : #include <Arduino.h> 3 : 4 : uint8_t i2c_address = 0x04; 5 : 6 : void setup() { 7 : Wire.begin(); 8 : display("654321"); 9 : } 10 : 11 : void loop() { 12 : } 13 : 14 : void display (String data){ 15 : 16 : Wire.beginTransmission(i2c_address); //Start the I2C Session 17 : 18 : Wire.write(0x00); //Command Display Number 19 : Wire.write(0x00); //Value does not matter... 20 : 21 : for (int x=data.length() ; x>=0 ; x--){ //Process the char array one byte at a time 22 : char aByte=data.charAt(x); 23 : char value = 0; 24 : char dp = 0; 25 : 26 : if ((x+1)//Check if we are setting the decimal place 27 : if (data.charAt(x+1) == '.'){ 28 : dp = 0x01; 29 : } 30 : } 31 : 32 : switch (aByte){ //Send the correct bit sequence for each char 33 : case '-': // '-' sign 34 : value = 0x04|dp; // Add the deciamal place if set 35 : Wire.write(value); //Send the encoded value to the display 36 : break 37 : 38 : case '0': // '0' 39 : value = 0xfa|dp; // Add the deciamal place if set 40 : Wire.write(value); //Send the encoded value to the display 41 : break 42 : 43 : case '1': // '1' 44 : value = 0x60|dp; // Add the deciamal place if set 45 : Wire.write(value); //Send the encoded value to the display 46 : break 47 : 48 : case '2': // '2' 49 : value = 0xdc|dp; // Add the deciamal place if set 50 : Wire.write(value); //Send the encoded value to the display 51 : break 52 : 53 : case '3': // '3' 54 : value = 0xf4|dp; // Add the deciamal place if set 55 : Wire.write(value); //Send the encoded value to the display 56 : break 57 : 58 : case '4': // '4' 59 : value = 0x66|dp; // Add the deciamal place if set 60 : Wire.write(value); //Send the encoded value to the display 61 : break 62 : 63 : case '5': // '5' 64 : value = 0xb6|dp; // Add the deciamal place if set 65 : Wire.write(value); //Send the encoded value to the display 66 : break 67 : 68 : case '6': // '6' 69 : value = 0xbe|dp; // Add the deciamal place if set 70 : Wire.write(value); //Send the encoded value to the display 71 : break 72 : 73 : case '7': // '7' 74 : value = 0xe0|dp; // Add the deciamal place if set 75 : Wire.write(value); //Send the encoded value to the display 76 : break 77 : 78 : case '8': // '8' 79 : value = 0xff|dp; // Add the deciamal place if set 80 : Wire.write(value); //Send the encoded value to the display 81 : break 82 : 83 : case '9': // '9' 84 : value = 0xe6|dp; // Add the deciamal place if set 85 : Wire.write(value); //Send the encoded value to the display 86 : break 87 : 88 : case 'C': // 'C' 89 : value = 0x9a|dp; // Add the deciamal place if set 90 : Wire.write(value); //Send the encoded value to the display 91 : break 92 : 93 : case 'F': // 'F' 94 : value = 0x8e|dp; // Add the deciamal place if set 95 : Wire.write(value); //Send the encoded value to the display 96 : break 97 : 98 : case 'o': // 'o' 99 : value = 0x3c|dp; // Add the deciamal place if set 100 : Wire.write(value); //Send the encoded value to the display 101 : break 102 : } 103 : } 104 : Wire.endTransmission(); //End the transmission 105 : } 106 :

    Download

    ATTiny85 Project (3)

    The files shown on this page.

    Zip file containing source code