About: PLC, Arduino - Do it yourself project

The Attiny85 does not support specific hardware for SPI and I2C comunications as on ATmega series, instead there is a hardware module called UNIVERSAL SERIAL INTERFACE (USI) that can be only configured to perform SPI or I2C. In this small project, I'd like to share how to perform both SPI and I2C protocols simultaneously on the ATTiny85 by:

  • Using USI for I2C with TinyWireM library to read MPU-6050.
  • Using remaining pins for Software SPI to control one LED MATRIX 13x15.

Let’s getting started by video below.

Step 1: Bill of Materials and Tools

Main components:


Step 2: Idea and Schematic

My idea is to use only one ATTiny85 to make an auto-rotating message led matrix - size 13x15. It uses an MPU-6050 Accelerometer + Gyro to calculate the angle and detect whenever led matrix is in upside-down position, then it will carry out the correction by rotating the message on led matrix and its direction as well.

You can download schematic with high resolution PDF file HERE.

Step 3: Protoboards Soldering

I soldered the led matrix shield and control circuit on 2 prototype boards 7x9cm as diagram shown on STEP 2.

1. Soldering led matrix shield 13x15

I cut led pins short enough and ensure all led anodes/ cathodes to be soldered together without any extension wires. Led matrix shield have all 15 rows and each row has 13 leds. At the bottom, male headers for led anodes (rows) and cathodes (columns) were soldered to plug on the control board.

  • Top view

  • Bottom view

2. Soldering control circuit

I soldered the control circuit to another DIY protoboard 7x9cm. All the wires were arranged on PCB top and they will be hidden when led matrix shield is plugged.

  • Top view

  • Bottom view

  • Plugging ATTiny85 and MPU6050 into their female headers.

  • Connecting the wires from ATTiny85 male header to SPI and MPU-6050 male headers. Control board is DONE. The ATTiny85 USB port is protruded from the protoboard so that it can be plugged into a computer or power bank.

3. Connecting protoboards together

  • Plugging matrix shield to control board.

  • DigiSpark ATTiny85 was glued into the control protoboard to avoid wobbling.

Step 4: Eagle Design

Because the protoboard 7x9cm has limited space so I took my times to design this project in Eagle software. With the same hardware configuration, Eagle PCB can control led matrix 16x16 with auto-rotating function.

I have just finished designing and did not send to fabrication company. I think it should work as same as my protoboards.

And here is my design.

  • PCB - Top view

  • PCB - Bottom view

  • PDF print file for Clothes Iron Toner Transfer

To make a circuit board at home, we can use Clothes Iron Toner Transfer method that simply print below circuit designs out on a laser printer, iron it onto the copper, and etch by Ferric Chloride.



Step 5: Programming

The Arduino code for this project is available at my GitHub.

The first scrolling text testing is shown in below video and led board is powered from the laptop.

Step 6: ATTinyCore & TinyWireM Library

The following core, library and driver need to be installed in this project:

1. ATTinyCore at

This core allows sketches to be uploaded directly to DigiSpark ATTiny85 via USB and it can be installed using the Boards Manager in Arduino IDE. The Boards Manager URL is:

- File Preferences on Arduino IDE, enter the above URL in "Additional Boards Manager URLs"

- Tools Boards Boards Manager...

- Select "ATTinyCore by Spence Konde" and click "Install".

2. TinyWireM (I2C library) at

- Open Aduino IDE

- Sketch Include Library Manage Libraries... Search "TinyWireM"

3. Digistump Driver:

Step 7: How It Works

1. DigiSpark ATTiny Pin Usage

  • SPI connection

- Serial data (DATA_PIN) - ATTiny85 PIN P1

- Shift register clock (CLOCK_PIN) - ATTiny85 PIN P3

- Storage register clock (LATCH_PIN) - ATTiny85 PIN P4

- Output enable (BLANK_PIN): For this version, it is connected to GND.

  • I2C connection

- SCL - ATTiny85 PIN P2

- SDA - ATTiny85 PIN P0

2. Program explanations:

  • Compare Match Interrupt setup for SPI protocol, take note that in CTC mode, the timer clears the counter after it reaches OCR1C.
  // Clear registers
  TCNT1 = 0;
  TCCR1 = 0;
  // Reset to $00 in the CPU clock cycle after a compare match with OCR1C register value
  // 50 x 3.636 = 181.8us
  OCR1C = 50;
  // A compare match does only occur if Timer/Counter1 counts to the OCR1A value
  OCR1A = OCR1C;    
  // Clear Timer/Counter on Compare Match A
  TCCR1 |= (1 << CTC1);
  // Prescaler 64 - 16.5MHz/64 = 275Kz or 3,636us
  TCCR1 |= (1 << CS12) | (1 << CS11) | (1 << CS10);
  // Output Compare Match A Interrupt Enable
  TIMSK |= (1 << OCIE1A);
  • I2C and MPU-6050 setup
  • Led matrix can be adjusted brightness via Bit Angle Modulation (BAM) mode, or constant brightness mode. This option can be set by parameter "BAM_USE".
  // If we use BAM 4 bit method
  #if BAM_USE == 1  
  if(BAM_Counter==8)    // Bit weight 2^0 of BAM_Bit, lasting time = 8 ticks x interrupt interval time
  if(BAM_Counter==24)   // Bit weight 2^1 of BAM_Bit, lasting time = 24 ticks x interrupt interval time
  if(BAM_Counter==56)   // Bit weight 2^3 of BAM_Bit, lasting time = 56 ticks x interrupt interval time
  // Cathodes scanning
  switch (BAM_Bit)
    case 0:
        DIY_SPI(matrixBuffer[0][level + 0]);
        DIY_SPI(matrixBuffer[0][level + 1]);
    case 1:      
        DIY_SPI(matrixBuffer[1][level + 0]);
        DIY_SPI(matrixBuffer[1][level + 1]);
    case 2:     
        DIY_SPI(matrixBuffer[2][level + 0]);
        DIY_SPI(matrixBuffer[2][level + 1]);
    case 3:
        DIY_SPI(matrixBuffer[3][level + 0]);
        DIY_SPI(matrixBuffer[3][level + 1]);      
    if(BAM_Counter==120)    //Bit weight 2^3 of BAM_Bit, lasting time = 120 ticks x interrupt interval time
  // If we don't use BAM method
  // Cathodes scanning
  // Uncomment to adjust your constant brightness level
  // Bit weight 2^0 of BAM_Bit
  // DIY_SPI(matrixBuffer[0][level + 0]);
  // DIY_SPI(matrixBuffer[0][level + 1]);
  // Bit weight 2^1 of BAM_Bit
   DIY_SPI(matrixBuffer[1][level + 0]);
   DIY_SPI(matrixBuffer[1][level + 1]);

  // Bit weight 2^2 of BAM_Bit
  // DIY_SPI(matrixBuffer[2][level + 0]);
  // DIY_SPI(matrixBuffer[2][level + 1]);
  // Bit weight 2^3 of BAM_Bit  
  // DIY_SPI(matrixBuffer[3][level + 0]);
  // DIY_SPI(matrixBuffer[3][level + 1]);
  // Anode scanning
  DIY_SPI(anode[row][0]);   // Send out the anode level low byte
  DIY_SPI(anode[row][1]);  // Send out the anode level high byte
  PORTB &= ~(1<<LATCH_PIN);
  level = row * 2; 
  if (row == 13) row=0;
  if (level == 26) level=0;  
  • In constant brightness mode, we can choose 4 options brightness level, for example:
// Bit weight 2^1 of BAM_Bit
DIY_SPI(matrixBuffer[1][level + 0]);
DIY_SPI(matrixBuffer[1][level + 1]);
  • There are 4 set of fonts for scrolling texts as follows: FONT 3x5, FONT 5x7, FONT 8x8 and FONT 8x16. We can check auto-rotating function in a specific case, like FONT8x16:
if (font == FONT8x16)
  for ((dir) ? offset=0 : offset=((lenString(mystring)-6)*9-1); (dir) ? offset <((lenString(mystring)-6)*9-1) : offset >0; (dir) ? offset++ : offset--)
      for (byte xx=0; xx<15; xx++)
        for (byte yy=0; yy<13; yy++)
              pos = (AccX >0 ? 1:0);
              if (pos)
                if (getPixelHString(xx+offset, yy, mystring, FONT8x16)) 
                setcolor = For_color; 
                else setcolor = Bk_color;                 

                if (flipbyte(getPixelHString((xx+offset),yy, mystring, FONT8x16)))             
                setcolor = For_color; 
                else setcolor=Bk_color;
                LED(xx, yy, setcolor);


ATTiny85 read the value "AccX" from MPU-6050 via I2C and compare with "0" (pos = (AccX >0 ? 1:0)) to determine auto-rotating function.

void READ_MPU6050()
TinyWireM.send(0x3B);                               // Starting with register 0x3B (ACCEL_XOUT_H)
TinyWireM.requestFrom(MPU_ADDR, 14);                // Request a total of 14 registers
AccX=TinyWireM.receive()<<8|TinyWireM.receive();    // 0x3B (ACCEL_XOUT_H) & 0x3C (ACCEL_XOUT_L)
AccY=TinyWireM.receive()<<8|TinyWireM.receive();    // 0x3D (ACCEL_YOUT_H) & 0x3E (ACCEL_YOUT_L)
AccZ=TinyWireM.receive()<<8|TinyWireM.receive();    // 0x3F (ACCEL_ZOUT_H) & 0x40 (ACCEL_ZOUT_L)
Temp=TinyWireM.receive()<<8|TinyWireM.receive();    // 0x41 (TEMP_OUT_H) & 0x42 (TEMP_OUT_L)
GygroX=TinyWireM.receive()<<8|TinyWireM.receive();  // 0x43 (GYRO_XOUT_H) & 0x44 (GYRO_XOUT_L)
GygroY=TinyWireM.receive()<<8|TinyWireM.receive();  // 0x45 (GYRO_YOUT_H) & 0x46 (GYRO_YOUT_L)
GygroZ=TinyWireM.receive()<<8|TinyWireM.receive();  // 0x47 (GYRO_ZOUT_H) & 0x48 (GYRO_ZOUT_L)

Step 8: Finish

Thank you for reading my work!!!

PCB Challenge

Runner Up in the
PCB Challenge

Be the First to Share


    • Build a Tool Contest

      Build a Tool Contest
    • Eggs Challenge

      Eggs Challenge
    • Backyard Contest

      Backyard Contest