Hi, new here, been stuck with this project for 4 months already.
I'm building a miniature Bridge Crane, and i'm having a lot of trouble implementing the control.
The pieces i'm using:
Arduino Uno Smd
Infrared reciever KY-022, with a remote controller.
H Bridge module L9110S, controlling 2 DC 12-24v motors (working on 12v)
4 EndStop limit switch
My objectives are:
Each motor moves the crane left-right and back-forward, by 4 separate buttons in the remote controller. The motors can only move when the button is hold
To prevent the bridge from falling (and to not burn the motors) i want to use the EndStop limit switches to block one direction each one.
I have tested the pieces and they all work.
Here is the code i made so far, i'm trying to implement the Ir control part first, before adding the limit switches:
#include <IRremote.hpp>
//Receptor Ir
int pinReceptor = 7;
//Motors
int AIA = 8;
int AIB = 9;
int BIA = 10;
int BIB = 11;
void setup()
{
Serial.begin(115200);
IrReceiver.begin(pinReceptor, ENABLE_LED_FEEDBACK);
Serial.println("Listo para recibir");
pinMode(AIA, OUTPUT);
pinMode(AIB, OUTPUT);
pinMode(BIA, OUTPUT);
pinMode(BIB, OUTPUT);
}
void loop() {
if (IrReceiver.decode()) {
IrReceiver.printIRResultShort(&Serial);
if (IrReceiver.decodedIRData.command == 0x18){
forward();
}
else if (IrReceiver.decodedIRData.command == 0x52){
back();
}
else if (IrReceiver.decodedIRData.command == 0x8){
left();
}
else if (IrReceiver.decodedIRData.command == 0x5A){
right();
} else {
stopX();
stopY();
}
}
IrReceiver.resume();
}
The issues i have encountered:
I'm trying to implement a switch case table for the buttons, but so far it didn't work at all, the if chain that its in does make the motors spin, which is why i'm using it here... hopefully for now. (SOLVED by user hallowed31)
The signals are NEC 32 bits, and i'm using the command part of the signal, but sometimes the same type of command is applied to a different button, normally at a relatively fast speed. ¿Is there any way to use both the command read and the Raw Data read? (SOLVED)
Sometimes the buttons get mixed up. Example: up and down don't work, but the left button activates up, and then unlocks up and down, until it stops, probably because of the issue i mentioned before. I think it probably has to do with the remote controller as well. (SOLVED by alto777)
Completly lost in figuring out how to add the holding button function for the control.
Thats everything so far. The project seemed approachable as i tested the diferent pieces... and now its 4 months later and not a lot of progress has been made in the coding part.
Thanks for the help!
IR is a bit tricky, maybe just work on that part of the code and instead of motors just turn on LEDs. Once that works for you, get a 10 yo kid to test it. Once you are satisfied, replace the LEDs with motors. Add the limit switches in any time, that is the easiest part.
A thing this doesn't mean you've necessarily done is to fake the control.
In the part where you expect to be able to receive IR commands one day, write code that instead takes single characters from the serial monitor as input, and does the same thing that will later be done because a specific IR code was sent.
You can leave it in the code even as you add the IR stuff. Just two ways to do everything.
You can come at this from the other direction: get the IR receiver working to the point where it reliably spits out characters one each for every button.
I hope you see that in one hand you have code producing characters (IR Receive) and in the other you have code for consuming characters (Motor Control).
Just connect them and control the motors with IR commands.
I'm not sure i understand all your issues correctly but here's how I'd approach a sketch like this. Tested ok for the info and equipment I had and I had to make up my own functions but anyway...hope it helps.
#include <IRremote.hpp>
//Receptor Ir
int pinReceptor = 7;
//Motors
//int AIA = 8;
//int AIB = 9;
//int BIA = 10;
int BIB = 11;
// you could have up to 256 states in your machine with a single driving byte
byte state = 0;
bool printX = false;
bool printY = false;
void setup() {
Serial.begin(115200);
IrReceiver.begin(pinReceptor, ENABLE_LED_FEEDBACK);
Serial.println("Listo para recibir");
// pinMode(AIA, OUTPUT);
// pinMode(AIB, OUTPUT);
// pinMode(BIA, OUTPUT);
pinMode(BIB, OUTPUT);
}
void loop() {
if (IrReceiver.decode()) {
if (IrReceiver.decodedIRData.command == 0x45) { // power
state = 0;
} else if (IrReceiver.decodedIRData.command == 0x09) { // forward
state = 1;
} else if (IrReceiver.decodedIRData.command == 0x07) { // back
state = 2;
} else if (IrReceiver.decodedIRData.command == 0x44) { // left
state = 3;
} else if (IrReceiver.decodedIRData.command == 0x43) { // right
state = 4;
} else if (IrReceiver.decodedIRData.command == 0x47) { // func/stop
state = 9;
} else if (IrReceiver.decodedIRData.command == 0x16) { // 0
state = 10;
}
delay(20);
}
IrReceiver.resume();
switch (state) {
case 0:
printX = false;
printY = false;
stopAll();
break;
case 1:
forward();
break;
case 2:
back();
break;
case 3:
left();
break;
case 4:
right();
break;
// fill in 5-8 here if you like
case 9:
printX = true;
stopX();
break;
case 10:
printY = true;
stopY();
break;
default:
stopAll();
break;
} // end of switch
}
void forward() {
analogWrite(BIB, 255);
Serial.println("F");
Serial.println();
}
void back() {
analogWrite(BIB, 50);
Serial.println("B");
Serial.println();
}
void left() {
analogWrite(BIB, 180);
Serial.println("L");
Serial.println();
}
void right() {
analogWrite(BIB, 90);
Serial.println("R");
Serial.println();
}
void stopX() {
analogWrite(BIB, 0);
if (printX == true) {
Serial.println("stopY");
Serial.println();
} else {
}
}
void stopY() {
analogWrite(BIB, 0);
if (printY == true) {
Serial.println("stopX");
Serial.println();
} else {
}
}
void stopAll() {
stopX();
stopY();
Serial.println("stopAll");
Serial.println();
}
Note the commented out stuff was just to avoid setting those pins I wasn't using for the test to outputs in case I forget and plug something in later
@hallowed31 - I did a little work on presenting (elsewhere, one day in the future) an examlke of the divide and conquer.
This function (written by chatGPT and as yet untested) should be able to slot into your if/else decoding tree:
// Define separate arrays for IR codes and mapped characters (parallel arrays)
const unsigned long irCodes[] = {
0xFF30CF, // IR code for Button A
0xFF18E7, // IR code for Button B
0xFF7A85, // IR code for Button C
// Add more IR codes as needed
};
const char mappedChars[] = {
'A', // Character for Button A
'B', // Character for Button B
'C', // Character for Button C
// Add more characters as needed
};
// Function to decode IR input and return mapped character using parallel arrays
char getIRInput() {
if (irrecv.decode(&results)) {
long int irCode = results.value;
irrecv.resume(); // Prepare for the next IR signal
// Map the IR code to a character using parallel arrays
for (int i = 0; i < sizeof(irCodes) / sizeof(irCodes[0]); i++) {
if (irCode == irCodes[i]) {
return mappedChars[i]; // Return the corresponding character from the parallel array
}
}
}
return '\0'; // Return null character if no IR signal or unrecognized button
}
which will be the separate part where we get the characters from the IR.
It would show up in the loop() like this
void loop() {
char command = '\0';
// First, check if we have serial input:
command = getSerialInput();
if (command != '\0') {
printCharacter(command); // Print and process serial input
processCommand(command); // Process the command
}
// Second, check if we have IR input:
command = getIRInput();
if (command != '\0') {
printCharacter(command); // Print and process IR input
processCommand(command); // Process the command
}
}
where getSerialInput() is the other way to get command characters and is left as an exercise for the reader.
The getIRInput() function uses parallel one dimensional arrays. A step up would be a two dimensional array or an array of struct variables each hold a command and matching character.
I didn't think about that approach.
It works, which means the case problem is solved!
The remote controller also works nicely now as well, the button layer got loose and messed up the detection of the signal. Thats another issue solved.
What remains is the holding button function.
Update on the buttons: The motors only start working when i press the button of the opposite motor first:
To start the Up-Down motor, i need to press Right, and then it starts spinning Up, or Left to start spinning Down.
Same situation with the Left-Right motor.
After that "start" button, it controls as intended
Here is the code at the moment:
#include <IRremote.hpp>
//Receptor Ir
int pinReceptor = 6;
//Motores
int AIA = 8;
int AIB = 9;
int BIA = 10;
int BIB = 11;
byte estado = 0;
//Limites de Carrera
void setup()
{
Serial.begin(115200);
IrReceiver.begin(pinReceptor, ENABLE_LED_FEEDBACK);
Serial.println("Listo para recibir");
pinMode(AIA, OUTPUT);
pinMode(AIB, OUTPUT);
pinMode(BIA, OUTPUT);
pinMode(BIB, OUTPUT);
}
void loop() {
if (IrReceiver.decode()) {
IrReceiver.printIRResultShort(&Serial);
if (IrReceiver.decodedIRData.command == 0x18){ //Adelante, arranca motor X
estado = 1;
}
else if (IrReceiver.decodedIRData.command == 0x52){ //Atras
estado = 2;
}
else if (IrReceiver.decodedIRData.command == 0x8){ //Izquierda
estado = 3;
}
else if (IrReceiver.decodedIRData.command == 0x5A){ //Derecha, arranca motor Y
estado = 4;
}
else if (IrReceiver.decodedIRData.command == 0x19){ //Parar
estado = 5;
}
delay(20);
}
IrReceiver.resume();
switch (estado) {
case 1:
adelante();
break;
case 2:
atras();
break;
case 3:
izquierda();
break;
case 4:
derecha();
break;
case 5:
parar();
break;
default:
parar();
break;
}
}
//Direcciones Motores
void izquierda() {
digitalWrite(AIA, HIGH);
digitalWrite(AIB, LOW);
}
void derecha() {
digitalWrite(AIA, LOW);
digitalWrite(AIB, HIGH);
}
void adelante() {
digitalWrite(BIA, HIGH);
digitalWrite(BIB, LOW);
}
void atras() {
digitalWrite(BIA, LOW);
digitalWrite(BIB, HIGH);
}
void pararX() {
digitalWrite(AIA, LOW);
digitalWrite(AIB, LOW);
}
void pararY() {
digitalWrite(BIA, LOW);
digitalWrite(BIB, LOW);
}
void parar() {
pararX();
pararY();
}
I just added a call to pararX() or pararY() to each function as appropriate. This makes the remote able to turn on one of the four LEDS all others go off or (#5) turn them all off.
This control works! the motors no longer have the weird button problem, so that problem is also solved!
It appears that is not necessary to add the digitalWrite control for the other motor, they can be controlled separately, which is the original goal.
All that's left is the holding button function, and add the endstop switch.
Here is the full sketch for now.
I (and my dad) made some changes, mainly putting blocks of code into functions for readability.
Also i started adding the pins for the endstop switches, they aren't in the loop in here.
#include <IRremote.hpp> //Libreria control remoto
//Receptor Ir
int pinReceptor = 6;
long lecturaIr;
//Señales del control remoto
uint8_t mXAd1 = 0x18;
uint8_t mXAt2 = 0x52;
uint8_t mYIz1 = 0x8;
uint8_t mYDe2 = 0x5A;
uint8_t freno = 0x19;
//Pines Motores
int AIA = 8;
int AIB = 9;
int BIA = 10;
int BIB = 11;
uint8_t estado = 0;
//Estados del sistema
const byte EstadoMovX1 = 1; // Movimiento Motor 1 Izquierda
const byte EstadoMovX2 = 2; // Movimiento Motor 1 Derecha
const byte EstadoMovY1 = 3; // Movimiento Motor 2 Adelante
const byte EstadoMovY2 = 4; // Movimiento Motor 2 Atras
const byte EstadoParar = 5; // Detiene ambos motores
const byte EstadoInicial = 0; // Posición inicial de grua
//Limites de Carrera
int limiteCarreraX1 = 1;
int limiteCarreraX2 = 2;
int limiteCarreraY1 = 3;
int limiteCarreraY2 = 4;
bool limiteCarreraActivoX1;
bool limiteCarreraActivoX2;
bool limiteCarreraActivoY1;
bool limiteCarreraActivoY2;
void setup() { //Configuración
configuracion();
Serial.println("Listo para recibir");
}
void loop() { //Bucle
lecturaLimitesCarrera();
if (IrReceiver.decode()) { //Si recibe codigo, entonces
lecturaCodigoIr();
cambioEstado();
//Tabla de estados grua
switch (estado) {
case EstadoMovX1:
adelante();
break;
case EstadoMovX2:
atras();
break;
case EstadoMovY1:
izquierda();
break;
case EstadoMovY2:
derecha();
break;
case EstadoParar:
parar();
break;
default:
parar();
break;
}
revisaCambioIr();
}
}
//Funciones
void configuracion() {
Serial.begin(115200);
IrReceiver.begin(pinReceptor, ENABLE_LED_FEEDBACK);
pinMode(AIA, OUTPUT);
pinMode(AIB, OUTPUT);
pinMode(BIA, OUTPUT);
pinMode(BIB, OUTPUT);
pinMode(limiteCarreraX1, OUTPUT);
pinMode(limiteCarreraX2, OUTPUT);
pinMode(limiteCarreraY1, OUTPUT);
pinMode(limiteCarreraY2, OUTPUT);
limiteCarreraActivoX1 = false;
limiteCarreraActivoX2 = false;
limiteCarreraActivoY1 = false;
limiteCarreraActivoY2 = false;
}
void lecturaCodigoIr() {
IrReceiver.printIRResultShort(&Serial); //Imprime información recibida. Los datos son Address, Command(el que usamos para el control) y Raw Data
lecturaIr = IrReceiver.decodedIRData.command; //Decodifica el comando de la señal recibida.
IrReceiver.resume();
}
void lecturaLimitesCarrera() {
}
void revisaCambioIr(){
static byte estadoIr;
if (estadoIr != estado) {
Serial.print("now state: "); Serial.println(estado);
estadoIr = estado;
}
}
//Direcciones Motores
void cambioEstado() {
//Motor Y
if (lecturaIr == mXAd1){ //Adelante, arranca motor X
estado = 1;
}
else if (lecturaIr == mXAt2){ //Atras
estado = 2;
}
//Motor X
else if (lecturaIr == mYIz1){ //Izquierda
estado = 3;
}
else if (lecturaIr == mYDe2){ //Derecha, arranca motor Y
estado = 4;
}
else if (lecturaIr == freno){ //Parar
estado = 5;
}
delay(20);
}
//Motor X
void izquierda() {
digitalWrite(AIA, HIGH);
digitalWrite(AIB, LOW);
}
void derecha() {
digitalWrite(AIA, LOW);
digitalWrite(AIB, HIGH);
}
//Motor Y
void adelante() {
digitalWrite(BIA, HIGH);
digitalWrite(BIB, LOW);
}
void atras() {
digitalWrite(BIA, LOW);
digitalWrite(BIB, HIGH);
}
//Frenos
void pararX() {
digitalWrite(AIA, LOW);
digitalWrite(AIB, LOW);
}
void pararY() {
digitalWrite(BIA, LOW);
digitalWrite(BIB, LOW);
}
void parar() {
pararX();
pararY();
}
It also wasn't necesary to turn off the other motor from the control blocks, which allows to control the bridge crane from both directions at the same time.