Introduction: ATTINY85 – SIMULTANEOUSLY USING I2C AND SPI PROTOCOLS
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:
- 200pcs x LED 3mm, optional color, blue in my case.
- 2pcs x Double Sided DIY Protoboard Circuit 7x9cm.
- 1pcs x DigiSpark ATTiny85.
- 1pcs x MPU-6050 Accelerometer + Gyro.
- 2pcs x Shift Register 74HC595N.
- 2pcs x Power Logic 8-Bit Shift Register TPIC6B595N.
- 16pcs x Transistor A1013.
- 300pcs x R100.
- 16pcs x R1K.
- 4pcs x Capacitor 0.1uF.
- 4pcs x Copper Standoff Spacers 15mm.
- 2pcs x Female 40pin 2.54mm Header.
- 2pcs x Male 40pin 2.54mm Header.
- 1pcs x XH2.54mm – 3P 10cm Wire Cable Double Connector.
- 1pcs x XH2.54mm – 4P 10cm Wire Cable Double Connector.
- 1pcs x XH2.54mm – 6P 10cm Wire Cable Double Connector.
- 1pcs x Power Bank 5V, minimum 2A.
- 1meter x 8P Rainbow Ribbon Cable.
- Some small bolts and nuts.
Tools:
- Intelligent Digital Oscilloscope Multimeter. This is an useful multimeter tool with reasonable price and it is integrated waveform signal display within 20KHz.
- Hot glue gun.
- Soldering machine.
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.
https://github.com/tuenhidiy/ATTINY85-SIMULTANEOUS...
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 https://github.com/SpenceKonde/ATTinyCore
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:
http://drazzy.com/package_drazzy.com_index.json
- 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 https://github.com/adafruit/TinyWireM
- 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
TinyWireM.begin(); MPU_INIT();
- 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".
ISR(TIMER1_COMPA_vect) { // 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 BAM_Bit++; else if(BAM_Counter==24) // Bit weight 2^1 of BAM_Bit, lasting time = 24 ticks x interrupt interval time BAM_Bit++; else if(BAM_Counter==56) // Bit weight 2^3 of BAM_Bit, lasting time = 56 ticks x interrupt interval time BAM_Bit++; BAM_Counter++; // Cathodes scanning switch (BAM_Bit) { case 0: // DIY_SPI(matrixBuffer[0][level + 0]); DIY_SPI(matrixBuffer[0][level + 1]); break; case 1: // DIY_SPI(matrixBuffer[1][level + 0]); DIY_SPI(matrixBuffer[1][level + 1]); break; case 2: // DIY_SPI(matrixBuffer[2][level + 0]); DIY_SPI(matrixBuffer[2][level + 1]); break; 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 { BAM_Counter=0; BAM_Bit=0; } break; } // If we don't use BAM method #else // 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]); #endif // 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; delayMicroseconds(2); PORTB &= ~(1<<LATCH_PIN); delayMicroseconds(2); row++; 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--) { READ_MPU6050(); 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; LED(14-xx,12-yy,setcolor); } else { if (flipbyte(getPixelHString((xx+offset),yy, mystring, FONT8x16))) setcolor = For_color; else setcolor=Bk_color; LED(xx, yy, setcolor); } } } delay(delaytime); } }
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.beginTransmission(MPU_ADDR); TinyWireM.send(0x3B); // Starting with register 0x3B (ACCEL_XOUT_H) TinyWireM.endTransmission(); 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!!!