Off the shelf and open the storage container.
This project is 4-5 years old, it has had many spec changes over the years. It features a Raspberry Pi, a GPIO expander to solderless breadboards, I2C servo hat, and an Arduino Nano wired on the I2C bus, with other I2C sensors. This competition gave the project hope, and eventually life !
Here it the completed project in action ! https://youtu.be/eMoC8VlGKAU
Original Goal:
A DC brushless rotor motor’s speed is to be controlled by a Raspberry Pi using a I2C servo hat, receiving throttle data from the Arduino I2C.
The project goal is to send the joystick position from the Arduino to the Raspberry Pi using I2C for main rotor speed control.
A 3D helicopter printed helicopter has a DC brushless outboard motor mounted on it as main rotor (with a model airplane prop on it mounted upside down.)
The tail rotor is a WS2812 neopixel ring was wired to the Arduino Nano, also using the joystick positions for color and speed of LED flashing.
The tail rotor neopixel ring will change cycling speeds with the joystick position FWD-Back.
The tail rotor neopixel ring will change colors with the joystick position, center, left , and right.
Workscope Creep - the overly demanding and ambitious project leader seizes the opportunity to add features to enhance the safety and wow factor of the original design.
- using the integrated joystick switch, create a rotor motor enable signal which resides in the Arduino Nano.
The enable signal will toggle by pressing down on the joystick.
When rotor motor is enabled, LED13 on the Arduino will be lit as visual feedback of a possible danger condition.
When the rotor motor is disabled, LED13 will be out.
The rotor motor enable bit is sent over I2C to cause the rotor motor speed reference to be set to zero when LOW.
When the rotor motor enable bit is HIGH, the rotor motor servo speed reference will follow the FwdBack joystick position. Full Back = top speed ( 90 degrees reference = 1.5 ms on the 20ms duty cycle) stick full back slows it to zero speed.
- YAW CONTROL The 3D printed copter will be mounted on a 2 axis servo bracket. The bottom servo position will be controlled by the left/right joystick so full left points the copter left, full right points the copter right. The servo position reference will have reference rate clamps so reduce mechanical stress on the mechanicals.
- PITCH (TILT) CONTROL – on the 2 axis servo bracket, the upper servo position will be controlled by the Fwd/Back joystick. The angle and rate will be limited to reduce mechanical stresses.
Here it the completed project in action ! https://youtu.be/eMoC8VlGKAU
In the body of this submission is Arduino code, Raspberry Pi Python code, and the wiring schematics !
slave_sender_w_NeoPixel_JoystickWORKS_.ino
// Wire Slave Sender
// by Nicholas Zambetti <http://www.zambetti.com>
// Demonstrates use of the Wire library
// Sends data as an I2C/TWI slave device
// Refer to the "Wire Master Reader" example for use with this
// Created 29 March 2006
// This example code is in the public domain.
#include <Wire.h>
int number = 0;
int state = 0;
#include <Adafruit_NeoPixel.h>
#define PIXEL_PIN 11 // Digital IO pin connected to the NeoPixels.
#define PIXEL_COUNT 16
// Parameter 1 = number of pixels in strip, neopixel stick has 8
// Parameter 2 = pin number (most are valid)
// Parameter 3 = pixel type flags, add together as needed:
// NEO_RGB Pixels are wired for RGB bitstream
// NEO_GRB Pixels are wired for GRB bitstream, correct for neopixel stick
// NEO_KHZ400 400 KHz bitstream (e.g. FLORA pixels)
// NEO_KHZ800 800 KHz bitstream (e.g. High Density LED strip), correct for neopixel stick
Adafruit_NeoPixel strip = Adafruit_NeoPixel(PIXEL_COUNT, PIXEL_PIN, NEO_GRB + NEO_KHZ800);
// joystick left A4=0v joystick right A4 = 4.7v nuetral position = 2.5V (512 counts)
// joystick back A5=0V joystick full fwd A5=4.7v nuetral position = 2.5V (512 counts)
// 10-bit analog to digital converter. input voltages between 0 and 5 volts correspond to integer values between 0 and 1023.
// lower byte of an analog value reads 0-255, upper byte 256-1023
int Joystick_FwdRev=0;
int Joystick_LeftRight=0;
int Red =0;
int Green=0;
byte IOBytes[5]; // setup a matrix for IO Bytes of data
int LEDcycleTime; // for WS2812 update time infuenced by throttle joystick
int JoystickSwitch; //
int EnableServos= LOW;
int EnableServosLastScan= LOW;
byte ArduinoDigitalIns=0; // a generic byte to map inputs to the Raspberry Pi
void setup() {
strip.begin();
delay (2000); // give time for the LEDs to initialize
strip.show(); // Initialize all pixels to 'off'
byte IOBytes[5]; // setup a matrix for IO Bytes of data
int Joystick_FwdRev=0;
int Joystick_LeftRight=0;
int EnableServos= LOW;
//SW (KEY or SEL) – This is the digital output from the pushbutton, normally open, will connect to GND when the button is pushed.
pinMode(12, INPUT_PULLUP); // sets the digital pin 12 as an input for the joystick switch
Wire.begin(8); // join i2c bus with address #8
// Wire.onRequest(requestEvent); // register event
// Serial.begin(9600); // setup serial monitor
Wire.onReceive(receiveData);
Wire.onRequest(sendData);
// Serial.println("Ready!");
}
void loop() {
//this code block determines color of rotor.
//Red when turning LEFT , Green when turning right, yellow at stick nuetral (255, 255, 0)
Joystick_LeftRight = analogRead(A2);
Joystick_FwdRev = analogRead(A1);
// Serial.println(Joystick_LeftRight);
if (Joystick_LeftRight >= 520 )
{Red = 0;
Green = 30;
}
if ((Joystick_LeftRight < 520 )&& (Joystick_LeftRight >= 480 ))
{
Red = 30;
Green = 30;
}
if (Joystick_LeftRight < 480)
{ Red = 30;
Green = 0;
}
for (int i = 0; i<16; i++)
{
Joystick_FwdRev = analogRead(A1); // read throttle joystick position
LEDcycleTime = 50-(Joystick_FwdRev/32); // throttle full back delay ~ 60ms, full fwd delay time is ~16ms , nuetral position ~ 34ms
strip.setPixelColor(i,Red,Green,0); // this is an LED sequence starting from LED #1
int j=(i+8);
if ((i+8)>16)
{j=(i-8);}
strip.setPixelColor((j),Red,Green,0); // // this is an LED sequence starting from LED #8
strip.show();
delay (LEDcycleTime);
strip.setPixelColor(i,0,0,0); //
strip.setPixelColor((j),0,0,0); // this is an LED sequence starting from LED #8
strip.show();
delay (3);
// each pass takes
}
// delay(100);
////////////////////////// ENABLE SERVOs - send this bit to the Raspberry Pi & light the Arduino LED13 /////////////////////////////////////////////////
JoystickSwitch = digitalRead(12); // read the input pin
if ((JoystickSwitch == LOW)&& (EnableServos==LOW)&&(EnableServosLastScan==LOW)) //LastScan value to eliminate race condition
{
EnableServos=HIGH; // this toggles enable high
delay (20);
}
if ((JoystickSwitch == LOW)&& (EnableServos==HIGH)&&(EnableServosLastScan==HIGH)) // joystick input has a pullup , reads LOW when pushed
{
EnableServos=LOW; //this is used to toggle off enable
delay (20);
}
if (EnableServos==LOW)
{
digitalWrite(13, LOW);
bitWrite(ArduinoDigitalIns, 0, 0);
}
else {
digitalWrite(13, HIGH);
bitWrite(ArduinoDigitalIns, 0, 1);
}
// Serial.print ("JoystickSwitch ="); //confirmed 5V = 1023 counts
// Serial.println (JoystickSwitch);
// Serial.print ("EnableServos ="); //confirmed 5V = 1023 counts
// Serial.println (EnableServos);
// Serial.print ("ArduinoDigitalIns="); //confirmed 5V = 1023 counts
// Serial.println (ArduinoDigitalIns);
EnableServosLastScan = EnableServos; // to eliminate race condition in logic
// for bebugging fill up the byte matrix of 5 with data, for now only put in the analogs
// IOBytes[0] = lowByte(Joystick_LeftRight);
// IOBytes[1] = highByte(Joystick_LeftRight);
// IOBytes[2] = lowByte(Joystick_FwdRev);
// IOBytes[3] = highByte(Joystick_FwdRev);
// IOBytes[4] = lowByte(16); // use for debugging, later for reading DIs
// Serial.print ("Joystick_LeftRight ="); //confirmed 5V = 1023 counts
// Serial.println (Joystick_LeftRight);
// Serial.print("Joystick_LeftRight low byte =");
// Serial.println (IOBytes[0]); // low byte value max = 255
// Serial.print("Joystick_LeftRight hi byte ="); // hi byte max value = 3
// Serial.println (IOBytes[1]);
// Serial.println (" ");
// Serial.println (" ");
// Serial.print("Joystick_FwdRev =");
// Serial.println (Joystick_FwdRev);
// Serial.print("Joystick_FwdRev lo byte =");
// Serial.println (IOBytes[2]);
// Serial.print("Joystick_FwdRev hi byte =");
// Serial.println (IOBytes[3]);
// Serial.print("constant lo byte =");
// Serial.println (IOBytes[4]);
// Serial.println (" ");
}
// function that executes whenever data is requested by master
// this function is registered as an event, see setup()
// send 5 bytes of data, 2 bytes from A1, 2 bytes from A2, 1 byte as a packed byte of bits from digital inputs
//void requestEvent() /
//{
// for (int i=0;i<6;i++)
// {
// Wire.beginTransmission(9); // transmit to device #9
// Wire.write(IOBytes[i]);
// Wire.send(IOBytes[i]); // sends x
// Wire.endTransmission(); // stop transmitting
// }
// as expected by master
// }
// callback for received data
void receiveData(int byteCount)
{
while(Wire.available())
{
number = Wire.read();
// Serial.print("data received: ");
// Serial.println(number);
}
}
// callback for sending data
//Wire.write(data, length)
// Parameters value: a value to send as a single byte
// data: an array of data to send as bytes
// length: the number of bytes to transmit
void sendData()
{
Joystick_LeftRight = analogRead(A2);
Joystick_FwdRev = analogRead(A1);
IOBytes[0] = lowByte(Joystick_LeftRight);
IOBytes[1] = highByte(Joystick_LeftRight);
IOBytes[2] = lowByte(Joystick_FwdRev);
IOBytes[3] = highByte(Joystick_FwdRev);
IOBytes[4] = lowByte (ArduinoDigitalIns); //lowByte(16); // use for debugging, later for reading DIs
Wire.write(IOBytes,5); // dont need the [] brackets on the matrix
}
# this filename CopterJoystickI2C_FINAL.py
# it is derived from CopterReadJoystickI2CMatrix.py and AdafruitServoTest2.py
# this program WORKS !!
# to work properly, the Arduino needs to be loaded with slave_sender_w_NeoPixel_JoystickWORKS_.ino
#DEBUG CODE is denoted with # to comment it out
#!! SETUP FOR I2C !!
import smbus
import time
# for RPI version 1, use "bus = smbus.SMBus(0)"
bus = smbus.SMBus(1)
#!! This is the address we setup in the Arduino Program
address = 0x08
#!! SETUP FOR 16 ch servo hat
from adafruit_servokit import ServoKit
kit = ServoKit(channels=16)
- kit.servo[0].angle=90 # yaw servo
- kit.servo[1].angle=90 # pitch- tilt servo
- kit.servo[4].angle=0 ## note prop motor only runs 0-90 for 0 spd to top speed
- time.sleep(1) # leave some time for setup
#!! application parameters
YawOffset =0 # in degrees
PitchTiltOffset=0 # in degrees
PitchTiltAngleNoseDownLimit=120 # from 90 degrees = horizontal
PitchTiltAngleNoseUpLimit=50 # from 90 degrees = horizontal
while True:
time.sleep(.1) # time is for slowing down the updates if debugging
JoystickData = bus.read_i2c_block_data(0x08, 0x00, 5)
# print (" JoystickData=", JoystickData)
LeftRightJoystick=(JoystickData[0]+(JoystickData[1]*256))
# print (" LeftRightJoystick =", LeftRightJoystick)
FwdBackJoystick=(JoystickData[2]+(JoystickData[3]*256))
# print (" FwdBackJoystick =", FwdBackJoystick)
EnableServos = JoystickData[4]
# print (" EnableServos =", EnableServos)
#!! leftRight joystick values are full left = 0, full right = 1023
#!! FwdBack joystick values are full back = 0, full fwd = 1023
#!! the servos require a reference in degrees
#!! the Yaw servo needs 180 to turn left, and 0 is full right,opposite of joystick
#!! the inverted value for Yaw so left joystick makes it turn left is called YawDereesInv
YawDegrees=(((LeftRightJoystick)*180)/1023)+YawOffset # right is zero, left is 180
YawDegreesInv= (180-YawDegrees)
#!! Ensure YawDegreesInv stays within range
if (YawDegreesInv>179):
YawDegreesInv=179
if (YawDegreesInv<1):
YawDegreesInv=1
# print (" YawDegrees=", YawDegrees)
# print (" YawDegrees=Inv", YawDegreesInv)
#!! pitchTilt servo calcs
PitchTiltDegrees=(((FwdBackJoystick)*180)/1023)+PitchTiltOffset
# print ("PitchTiltDegrees", PitchTiltDegrees)
if (PitchTiltDegrees>PitchTiltAngleNoseDownLimit):
PitchTiltDegrees=PitchTiltAngleNoseDownLimit
if (PitchTiltDegrees<PitchTiltAngleNoseUpLimit):
PitchTiltDegrees=PitchTiltAngleNoseUpLimit
#!! rotor speed calcs
RotorSpeed=(((FwdBackJoystick)*90)/1023) # rotor speed max value is 90
#!! Ensure rotor speed stays in range
if (RotorSpeed>90):
RotorSpeed=90
if (RotorSpeed<1):
RotorSpeed=1
if (EnableServos == 0):
RotorSpeed=0
# print ("RotorSpeed =", RotorSpeed)
kit.servo[0].angle=YawDegreesInv #
kit.servo[1].angle=PitchTiltDegrees
kit.servo[4].angle=RotorSpeed # where 90 degrees is top speed
Part 1 – Original workscope and photos https://www.element14.com/community/community/project14/offtheshelf/blog/2021/09/20/arduino-nano-as-raspberry-pi-io-i2c-expander-part-1-aka-copter-rotor-control-via-i2c-joystick
Part 2 – wiring, hardware, and reading drop numbers on I2C https://www.element14.com/community/community/project14/offtheshelf/blog/2021/09/20/arduino-nano-as-raspberry-pi-io-i2c-expander-part-2-the-i2c-bus-copter-rotor-control-via-i2c-joystick-
Progress videos: The neopixel tail rotor controlled by an Arduino Nano from analog joystick inputs.
The main rotor controlled the Raspberry Pi using Python and I2C servo hat.
Part 3 – I2C talking Arduino to Raspberry Pi, single data byte. https://www.element14.com/community/community/project14/offtheshelf/blog/2021/09/22/arduino-nano-as-raspberry-pi-io-i2c-expander-part-3-python-voodoo-the-i2c-bus-byte-me
Part 4 – I2C sharing 5 bytes from the Arduino Nano to the Raspberry Pi https://www.element14.com/community/community/project14/offtheshelf/blog/2021/09/28/arduino-nano-as-raspberry-pi-io-i2c-expander-part-4-humbling-persistence-scope-creep-python-voodoo-and-over-the-hump