I am working on building an RC car/robot with Raspberry Pi and Arduino. I connected Arduino to Raspberry Pi using USB and send serial commands with python. I haven't done much programming with Arduino nor Python.
This just the beginning of this project and would like to get feedback on the code. Is it a good practice to use byte, bit masks and bitwise operators to send and decode commands? Is it better to send string commands? Also would I would like to get feedback on general structure and code logic for this type of program.
The car that I am using has two motors that control each side of the car. I think I have to slowdown the right side to go right and left side to go left.
I am using pygames to capture key presses. Some of the code was taken from GitHub.
Arduino
//Pin Functions
#define motorLeftSpeedPin 6 //pwm
#define motorLeftForwardPin A0
#define motorLeftBackwardPin A1
#define motorRightSpeedPin 5 //pwm
#define motorRightForwardPin A2
#define motorRightBackwardPin A3
#define speakerPin A5
#define ledPin 2
//define commands
#define STOP 0x00 //0000
#define FORWARD_DIRECTION 0x01 //0001
#define BACKWARD_DIRECTION 0x02 //0010
#define LEFT_DIRECTION 0x04 //0100
#define RIGHT_DIRECTION 0x08 //1000
#define MOTORLEFT 0x10 //0001 0000
#define MOTORRIGHT 0x20 //0010 0000
#define SET_SPEED 0x40 //0100 0000
#define TURN_SPEED_OFFSET 20
#define MINIMUM_MOTOR_SPEED 100
#define MAXIMUM_MOTOR_SPEED 250
struct Motor
{
byte mSide;
byte mSpeed;
}motorLeft, motorRight;
struct Command
{
byte cmdID;
byte data1;
byte data2;
byte checkSum;
};
enum COMMAND_IDS
{
INVALID_CMD = 0,
DRIVE = 10
};
byte currentDirection = 0x00;
void dbg_print(const char * s)
{
//#if DEBUG
Serial.print(s);
//#endif
}
void processCommand(struct Command &command)
{
//prcess recieved command
switch(command.cmdID)
{
case DRIVE:
dbg_print("Drive ...");
driveCar(command);
break;
default:
//unknown command and do nothing
dbg_print("Invalid cmd received...");
break;
}
}
void driveCar(struct Command &command)
{
if(command.data1 & STOP){
stopAllMotors();
dbg_print("Stop ...");
return;
}
if (!(command.data1 & LEFT_DIRECTION) && !(command.data1 & RIGHT_DIRECTION)){
//if not turning sync the motor speeds
setAllMotorsSpeed(command.data2);
}
if (command.data1 & FORWARD_DIRECTION){
goForward();
dbg_print("Drive Forward ...");
}else if (command.data1 & BACKWARD_DIRECTION){
goBackward();
dbg_print("Drive Backward ...");
}
if (command.data1 & LEFT_DIRECTION){
turnLeft();
dbg_print("Turn Left ...");
}else if (command.data1 & RIGHT_DIRECTION){
turnRight();
dbg_print("Turn Right ...");
}else{
//reset the direction bits
currentDirection &= (RIGHT_DIRECTION | LEFT_DIRECTION);
}
if (command.data1 & SET_SPEED){
setAllMotorsSpeed(command.data2);
dbg_print("Set Speed ...");
}
}
void turnLeft()
{
//slow down the left motor to turn right
if (!(currentDirection & LEFT_DIRECTION)){
motorLeft.mSpeed-=TURN_SPEED_OFFSET;
setMotorSpeed(motorLeft);
currentDirection |= LEFT_DIRECTION;
}
}
void turnRight()
{
//slow down the right motor to turn right
if (!(currentDirection & RIGHT_DIRECTION)){
motorRight.mSpeed-=TURN_SPEED_OFFSET;
setMotorSpeed(motorRight);
currentDirection |= RIGHT_DIRECTION;
}
}
void goForward()
{
//if going backwards then stop motors and then go forward
if(!(currentDirection & FORWARD_DIRECTION))
{
stopAllMotors();
digitalWrite(motorLeftForwardPin,1);
digitalWrite(motorRightForwardPin,1);
setMotorSpeed(motorLeft);
setMotorSpeed(motorRight);
currentDirection |= FORWARD_DIRECTION; // set forward direction bit
currentDirection &= BACKWARD_DIRECTION; // reset backward direction bit
}
}
void goBackward()
{
if(!(currentDirection & BACKWARD_DIRECTION))
{
//if not going backwards then stop motors and start going backward
stopAllMotors();
digitalWrite(motorLeftBackwardPin,1);
digitalWrite(motorRightBackwardPin,1);
setMotorSpeed(motorLeft);
setMotorSpeed(motorRight);
currentDirection |= BACKWARD_DIRECTION; // set backward direction bit
currentDirection &= FORWARD_DIRECTION; // reset forward direction bit
}
}
void stopAllMotors()
{
setAllMotorsSpeed(0);
digitalWrite(motorRightBackwardPin,0);
digitalWrite(motorLeftBackwardPin,0);
digitalWrite(motorRightForwardPin,0);
digitalWrite(motorLeftForwardPin,0);
delay(200);
}
void setAllMotorsSpeed(byte speedValue)
{
if(speedValue < MAXIMUM_MOTOR_SPEED && speedValue > MINIMUM_MOTOR_SPEED){
motorLeft.mSpeed = speedValue;
motorRight.mSpeed = speedValue;
setMotorSpeed(motorLeft);
setMotorSpeed(motorRight);
}else{
dbg_print("Motor speed is two high:");
}
}
void setMotorSpeed(struct Motor &motor)
{
if(motor.mSide == MOTORLEFT){
analogWrite(motorLeftSpeedPin, motor.mSpeed);
}else if (motor.mSide == MOTORRIGHT){
analogWrite(motorRightSpeedPin, motor.mSpeed);
}else{
dbg_print("Error Setting Motor Speed");
}
}
void setup()
{
Serial.begin(9600);
pinMode(speakerPin, OUTPUT);
pinMode(ledPin, OUTPUT);
pinMode(motorLeftSpeedPin, OUTPUT);
pinMode(motorLeftForwardPin, OUTPUT);
pinMode(motorLeftBackwardPin, OUTPUT);
pinMode(motorRightSpeedPin, OUTPUT);
pinMode(motorRightForwardPin, OUTPUT);
pinMode(motorRightBackwardPin, OUTPUT);
motorLeft.mSpeed = MAXIMUM_MOTOR_SPEED;
motorRight.mSpeed = MAXIMUM_MOTOR_SPEED;
motorLeft.mSide = MOTORLEFT;
motorRight.mSide = MOTORRIGHT;
}
void loop()
{
Command incomingCmd;
if(Serial.available() >= sizeof(Command)){
//read the incoming data
Command *mem = &incomingCmd;
unsigned char *p = (unsigned char *)mem;
for(int i=0;i<sizeof(Command);i++)
{
unsigned int data = Serial.read();
Serial.println(data);
p[i] = data;
}
//verify checksum
byte received_sum = incomingCmd.cmdID + incomingCmd.data1 + incomingCmd.data2;
if (incomingCmd.cmdID != INVALID_CMD && received_sum == incomingCmd.checkSum) {
driveCar(incomingCmd);
dbg_print("Good Cmd - checksum matched");
} else {
//Checksum didn't match, don't process the command
dbg_print("Bad Cmd - invalid cmd or checksum didn't match");
}
}
}
Python
import pygame
from pygame.locals import *
pygame.init()
#define commands
STOP = 0x00
FORWARD_DIRECTION = 0x01
BACKWARD_DIRECTION = 0x02
LEFT_DIRECTION = 0x04
RIGHT_DIRECTION = 0x08
MOTORLEFT = 0x10
MOTORRIGHT = 0x20
SET_SPEED = 0x40
currentDirection = 0x00
carSpeed = 200
done = False
title = "Hello!"
width = 640
height = 400
screen = pygame.display.set_mode((width, height))
screen.fill((255, 255, 255))
clock = pygame.time.Clock()
pygame.display.set_caption(title)
#load images
down_off = pygame.image.load('images/down_off.gif')
down_on = pygame.image.load('images/down_on.gif')
left_on = pygame.image.load('images/left_on.gif')
left_off = pygame.image.load('images/left_off.gif')
right_on = pygame.image.load('images/right_on.gif')
right_off = pygame.image.load('images/right_off.gif')
up_on = pygame.image.load('images/up_on.gif')
up_off = pygame.image.load('images/up_off.gif')
import serial
ser = serial.Serial('/dev/ttyACM0', 9600)
#show initally on the screen
def arrowsOff():
screen.blit(down_off,(300,250))
screen.blit(left_off,(230,180))
screen.blit(right_off,(370,180))
screen.blit(up_off,(300,110))
arrowsOff()
font=pygame.font.SysFont("monospace", 30)
def sendCommandToArduino():
#send command drive with motor speed 0xA0
checkSum = 0x01+currentDirection+0xA0
ser.write(chr(0x01)+chr(currentDirection)+chr(0xA0)+chr(checkSum))
def showSpeed():
screen.fill(pygame.Color("white"), (0, 0, 110, 40))
scoretext=font.render("Speed:"+str(carSpeed), 1,(0,0,0))
screen.blit(scoretext, (0, 0))
while not done:
for event in pygame.event.get():
if (event.type == QUIT):
done = True
elif(event.type == KEYDOWN):
if (event.key == K_ESCAPE):
done = True
keys = pygame.key.get_pressed()
if keys[K_LEFT]:
screen.blit(left_on,(230,180))
print("Left");
currentDirection|=LEFT_DIRECTION
sendCommandToArduino()
if keys[K_RIGHT]:
screen.blit(right_on,(370,180))
print("Right");
currentDirection|=RIGHT_DIRECTION
sendCommandToArduino()
if keys[K_UP]:
screen.blit(up_on,(300,110))
print("Up");
currentDirection|=FORWARD_DIRECTION
sendCommandToArduino()
if keys[K_DOWN]:
screen.blit(down_on,(300,250))
print("Down");
currentDirection|=BACKWARD_DIRECTION
sendCommandToArduino()
if keys[K_LEFTBRACKET]:
if(carSpeed>0):
carSpeed-=5
print("Slowdown");
showSpeed()
if keys[K_RIGHTBRACKET]:
carSpeed+=5
showSpeed()
print("faster")
elif(event.type == KEYUP):
if keys[K_LEFT]:
screen.blit(left_off,(230,180))
print("Left");
currentDirection&=LEFT_DIRECTION
sendCommandToArduino()
if keys[K_RIGHT]:
screen.blit(right_off,(370,180))
print("Right");
currentDirection&=RIGHT_DIRECTION
sendCommandToArduino()
if keys[K_UP]:
screen.blit(up_off,(300,110))
print("Up");
currentDirection&=FORWARD_DIRECTION
sendCommandToArduino()
if keys[K_DOWN]:
screen.blit(down_off,(300,250))
print("Down");
currentDirection&=BACKWARD_DIRECTION
sendCommandToArduino()
pygame.display.update()
clock.tick(60)