Introduction: I2C Blynk Car With Attiny85 and M5StickC

About: PLC, Arduino - Do it yourself project

This project shows you how to build your own I2C DC motor drive using a DigiSpark Attiny85 plus Arduino motor shield. To test its operation, I made a small RC car which used an M5StickC & connected to Blynk App. to communicate with this drive via the I2C protocol.

Please check my introduction video before getting started.

Step 1: Supplies

First of all, I would like to thank the JLCPCB for supporting me on this project. If you have a PCB project, please visit the JLCPCB website to get exciting discounts and coupons as follows:

a. Main materials:

⦾ 1pcs x M5StickC.

⦾ 1pcs x DigiSpark ATTiny85.

⦾ 1pcs x Quad DC Motor Driver Shield for Arduino.

⦾ 4pcs x Small DC Gear Motor with Wheel. I've reused 4pcs x Micro DC Motor in the CD/DVD players and 4 wheels from toy cars, but it's better to go with geared motors with wheel.

⦾ 1pcs x LM2596S 3A Adjustable Step-down DC-DC Power Supply Module.

⦾ 2pcs x Double Sided DIY Protoboard Circuit 7x9cm.

⦾ 1pcs x Arduino Uno Protoshield.

⦾ 1pcs x 2 Slot Battery 18650 Holder.

⦾ 2pcs x Rechargeable Li-ion Battery 18650.

⦾ 4pcs x Male & Female Header.

⦾ 4pcs x Copper Standoff Spacers 20mm.

⦾ 1 meter x 8P/16P Rainbow Ribbon Cable.

⦾ 2 pcs x R10K.

⦾ 1pcs x XH2.54mm – 4P 10cm Wire Cable Double Connector.

⦾ 1pcs x Grove Connector.

⦾ 1 meter x Two Core Power Cable.

⦾ Cable spiral wrap, bolts and nuts.

b. Tools:

⦾ Hot glue gun.

⦾ Soldering machine.

Step 2: Schematic

Main control components include: M5StickC, DigiSpark Attiny85, DFRobot Quad DC Motor Driver Shield for Arduino. And their connection is as below:

I used two communications in this project to control four DC motors of a RC car:

  • M5StickC gets Blynk joystick command from smartphone via WIFI and converts it to car direction and speed.
  • After that M5StickC transmits these converted commands to Attiny85 via I2C protocol. Finally, after receiving command, Attiny85 send its output signals to Quad motor shield to control direction and speed of car DC motors.

Notes:

  • In the schematic, I used 2 pull-up resistors R10K on the SDA and SCL line.
  • For the quad motor shield, I connected each group consisting of 2 DC motors in parallel at the M1 & M2 screw terminals. DIR signal of motor M1 & M2 has been connected together at motor shield pin 4 & 12. So I have controlled four DC motors with only 3 signals.

Step 3: Car Assembly

After using stepper motors for mini CNC projects, there are still some DC motors left in the CD/DVD player. They are used to insert or eject CD/ DVD plastic tray.

I disassembled four DC motors, soldered them on the double sided protoboard 7x9cm.

I found some wheels from small toy cars that have a center bore equal to the 2mm motor shaft diameter.

I used another protoboard 7x9cm then soldered on it a step-down power supply module LM2596, a power switch, a 2-pin screw header and 4 male header groups that match correspondingly to all the pins of the Quad DC Motor Shield for Arduino.

The LM2596 output terminals were connected to male headers at pin 5V and GND. It is used to power all the control circuits after I stack all the boards on these headers.

I connected wires for battery holder, module LM2596, motor shield power supply and 4 DC motors.

The main control circuit was soldered on an Arduino protoshield following the schematic on previous step, as follow:

⦾ Protoshield bottom includes: Attiny85 header (black), 4-pins I2C header (white) and motor shield headers (4 group - black).

⦾ Protoshield top includes: M5StickC 8 pin-header.

Solder an I2C Grove conversion connector for Attiny85 and M5StickC.

Plug DigiSpark Attiny85 and I2C connecter on their headers.

Plug M5StickC on its header and connect I2C Grove header.

Insert 2pcs x battery 18650 into their slots and stack all the boards together.

DONE.

Step 4: Blynk Control Screen

The Blynk's control screen includes a Joystick and a E-Stop button.

The joystick working range is: -255 to 255 in both x and y coordinates.

Step 5: Blynk Joystick

After doing some projects related RC cars and polar CNC machines, I think the most intuitive way to work with a Blynk joystick is to convert its Cartesian coordinates into Polar coordinates. It allows us to control a RC car in a very natural and friendly way.

The Blynk joystick cartesian coordinates (x, y) can be converted to polar coordinates (r, φ) with r ≥ 0 and φ in the interval [−π, π] by:

r = √ (x² + y²)

φ = atan2(y, x)

Where specific angle range (φ) indicates the car direction and radius (r) is the desired car speed.

Picture below shows how I can control my RC car in 4 directions with the speed varying according to the x, y joystick coordinates.

Step 6: M5StickC Programing

In my project, M5StickC has the following tasks:

  • Receive car control commands from the Blynk joystick, calculates and converts them to car direction and speed, represented by 2 separate bytes. This Cartesian-Polar coordinates conversion was explained on the previous step.
  • Transmit these command values to the Attiny85 over the I2C protocol.

The M5StickC program is as follows:

# define BLYNK_PRINT Serial
# include <M5StickC.h>
# include <Wire.h>
# include <WiFi.h>
# include <WiFiClient.h>
# include <BlynkSimpleEsp32.h>

# define SLAVE_ADDR 0x50                      // Attiny85 I2C address

// I2C direction code
const uint8_t FWD_DATA = 0x01;
const uint8_t RHT_DATA = 0x02;
const uint8_t BWD_DATA = 0x03;
const uint8_t LFT_DATA = 0x04;
const uint8_t STP_DATA = 0x00;

// I2C speed data
uint8_t SPEED_DATA;

// Blynk command
uint16_t Speed_V7;
int Stopcmd_V8;
double Angle;
int Joy_X;
int Joy_Y;

// You should get Auth Token in the Blynk App.
char AUTH[] = "Auth Token";
  
// Your WiFi credentials.
char SSID[] = "Your SSID";
char PASS[] = "Your PASS";

void sendI2C(uint8_t I2C_ADD, uint8_t I2C_DIR, uint8_t I2C_SPD)
{
  Wire.beginTransmission(I2C_ADD); 
  Wire.write(I2C_SPD);            // Send speed data to Attiny85
  Wire.write(I2C_DIR);            // Send direction to Attiny85
  Wire.endTransmission();
}

//Joystick data
BLYNK_WRITE(V7)
{
  Joy_X = param[0].asInt();
  Joy_Y = param[1].asInt();
  
  // Set speed
  SPEED_DATA = (uint8_t)sqrt(Joy_X*Joy_X + Joy_Y*Joy_Y);
  
  // Theta angle in degree
  Angle = atan2(Joy_Y, Joy_X)* 57.295779;
  
  if ((Angle > 45) && (Angle <=135))
  {
    sendI2C(SLAVE_ADDR, FWD_DATA, SPEED_DATA);
  }
  else if ((Angle > -135) && (Angle <=-45))
  {
    sendI2C(SLAVE_ADDR, BWD_DATA, SPEED_DATA);
  }
  else if ((Angle <= -135) || (Angle >135))
  {
    sendI2C(SLAVE_ADDR, LFT_DATA, SPEED_DATA);
  }
  else
  {
    sendI2C(SLAVE_ADDR, RHT_DATA, SPEED_DATA);
  }
}

// Stop
BLYNK_WRITE(V8)
{   
  Stopcmd_V8 = param.asInt();              // Get stop command from Blynk button
  if(Stopcmd_V8)
  {
    sendI2C(SLAVE_ADDR, STP_DATA, 0x00);   // Send stop command to Attiny85 via I2C
  }
}

void setup()
{
  Blynk.begin(AUTH, SSID, PASS);
  M5.begin();                               //Init M5StickC
  Wire.begin();                             //Init wire and join the I2C network.
  M5.Lcd.fillScreen(BLACK);                 //Make the screen full of black een).
  M5.Lcd.setTextColor(YELLOW);              //Set the font color to yellow.
  M5.Lcd.setRotation(3);                    //Rotate the screen.
  M5.Lcd.setTextFont(4);                    //Set text size
  M5.Lcd.setCursor(0, 24);                  //Set (x, y) to show text
  M5.Lcd.println("I2C-Blynk Car");          //Print a string on the screen.  
}

void loop()
{
  M5.update();
  Blynk.run();
}

Step 7: Attiny85 Programing

I used TinyWireS library to receive I2C data for Digispark ATTiny85. Every time we move the joystick on Blynk App., M5StickC transmits 2 bytes to Attiny85:

⦾ The first byte: its value is the car speed, range: 0...255.

⦾ The second byte: its value is the car direction and it is encoded as follows:

  • Forward: 0x01.
  • Turn right: 0x02.
  • Backward: 0x03.
  • Turn left: 0x04.
  • Stop: 0x00.

Based on the received command, Attiny85 will control my car with a direction and speed corresponding to the finger movement on the Blynk joystick.

Attiny85's code is as follows:

#include <TinyWireS.h>
#define SLAVE_ADDR 0x50

uint8_t receiveCommand[2];

// I2C - Direction code
const uint8_t FWD_DATA = 0x01;
const uint8_t RHT_DATA = 0x02;
const uint8_t BWD_DATA = 0x03;
const uint8_t LFT_DATA = 0x04;
const uint8_t STP_DATA = 0x00;

// I2C - Speed value
uint8_t SET_SPEED = 0;
uint8_t INIT_SPEED = 0;

// Attiny85 - Pin setup
const int
    DIR_PIN(3),                 // Motor 1 & 2 Direction Pin PB3.  
    SPD1_PIN(1),                // Motor 1 Speed Pin PB1.
    SPD2_PIN(4);                // Motor 2 Speed Pin PB4

// Motors properties
typedef struct
{
  int SPD_PIN;    // Speed Pin
  int DIR_PIN;    // Direction Pin
  int SET_SPEED;  // Speed to be set
} Motor;

Motor MotorLeft  {SPD1_PIN, DIR_PIN, INIT_SPEED}; // Motor 1 - M1 Terminal on the Quad Motor Driver Shield
Motor MotorRight {SPD2_PIN, DIR_PIN, INIT_SPEED}; // Motor 2 - M2 Terminal on the Quad Motor Driver Shield

void receiveEvent(uint8_t howMany)  
{
   memset(receiveCommand,0 ,sizeof(receiveCommand));
   for ( int i = 0 ; i <howMany; i ++)
   {
    receiveCommand[i] = TinyWireS.receive();
    }

// Speed control
SET_SPEED = receiveCommand[0];     // Speed data

// Direction control
switch (receiveCommand[1])
  {
      case FWD_DATA:               // Forward code - 0x01
        RC_Car_Forward();
        break;
      case RHT_DATA:               // Right code - 0x02
        RC_Car_Right();
        break;
      case BWD_DATA:               // Backward code - 0x03
        RC_Car_Backward();
        break;
      case LFT_DATA:               // Left code - 0x04
        RC_Car_Left();
        break;  
      case STP_DATA:               // Stop code - 0x00
        RC_Car_Stop();
        break; 
      default:
        RC_Car_Stop();
        break;
  }
}

void setup()
{ 
  pinMode(DIR_PIN, OUTPUT);
  pinMode(SPD1_PIN, OUTPUT);
  pinMode(SPD2_PIN, OUTPUT);

  TinyWireS.begin(SLAVE_ADDR);
  TinyWireS.onReceive(receiveEvent); 
}

void loop()
{
  TinyWireS_stop_check ();
}

void RC_Car_Forward()
{
  // Set car direction
  digitalWrite(MotorLeft.DIR_PIN, 0);  
  // Set car speed
  analogWrite(MotorLeft.SPD_PIN, SET_SPEED);
  analogWrite(MotorRight.SPD_PIN, SET_SPEED);
}

void RC_Car_Backward()
{
  digitalWrite(MotorLeft.DIR_PIN, 1);   
  analogWrite(MotorLeft.SPD_PIN, SET_SPEED);
  analogWrite(MotorRight.SPD_PIN, SET_SPEED);  
}

void RC_Car_Left()
{
  digitalWrite(MotorLeft.DIR_PIN, 0);   
  analogWrite(MotorLeft.SPD_PIN, SET_SPEED);
  analogWrite(MotorRight.SPD_PIN, 0); 
}

void RC_Car_Right()
{
  digitalWrite(MotorLeft.DIR_PIN, 0);  
  analogWrite(MotorLeft.SPD_PIN, 0);
  analogWrite(MotorRight.SPD_PIN, SET_SPEED); 
}

void RC_Car_Stop()
{
  digitalWrite(MotorLeft.DIR_PIN, 0);  
  analogWrite(MotorLeft.SPD_PIN, 0);
  analogWrite(MotorRight.SPD_PIN, 0);
}

Step 8: Conclusion

Thank you for reading my works!!!

Made with Math Contest

Participated in the
Made with Math Contest