-
1Interpreting an RC receiver serial payload
Honestly, I saw how relatively inexpensive these transmitter/receiver bundles were, and I saw how much functionality was built into them, and I knew they worked at a longer range than Bluetooth and standard Wi-Fi (I’m not even going near ESPNow but maybe), and I had to screw around with one.
I was doing an unrelated project using field oriented control of bushels motors, and I got the remote control to do this, which got me jazzed:
Each degree of motion (we will call them “channels,” because that is what they are called in this arena) on the transmitter is broadcast to the receiver. On the receiver side, each channel outputs a PWM signal that can directly control something, if it wants to, which we don’t want to do. Instead, we will utilize a different way to use way less wires, and it is easy and satisfying.
Instead of having a PWM line for each channel fed into the ESP32, this brand of remote has this thing called “I-Bus,” which sounds complicated, and sounds even more complicated upon learning it involves reading a serial stream of bytes from an RC receiver and turning that into meaningful information.
Luckily, as of October 2024, ChatGPT4o++^2 will be familiar with this, and upon our very detailed prompts which reflect our deep understanding of syntax and semantics of C++ (to be clear, this is sarcasm about my own proficiency), we get a pretty cool class for reading off a second UART stream to the ESP32. It gets better. We even get efficient code so as not to flood the buffer (I don’t know what that means but I’m writing it to sound knowledgeable) that reads one by every time it goes through the loop, scanning for the to start bytes, then reading the remaining 30 bytes that encode the data of every state of every doodad on the transmitter. Here is the class:
// Include necessary libraries (if any) // Note: Since we're defining the class and using it in the same file, no additional includes are necessary. // Define the IBusReceiver class class IBusReceiver { public: // Constructor IBusReceiver(HardwareSerial& serialPort, uint8_t rxPin) : serial(serialPort), rxPin(rxPin) { packetIndex = 0; packetAvailable = false; } // Initialize the serial port void begin(uint32_t baudRate) { serial.begin(baudRate, SERIAL_8N1, rxPin, -1); } // Non-blocking function to read IBUS data void readIBUSData() { while (serial.available()) { uint8_t incomingByte = serial.read(); switch (packetIndex) { case 0: if (incomingByte == HEADER1) { ibusPacket[packetIndex++] = incomingByte; } break; case 1: if (incomingByte == HEADER2) { ibusPacket[packetIndex++] = incomingByte; } else { packetIndex = 0; // Reset if header not matched } break; default: ibusPacket[packetIndex++] = incomingByte; if (packetIndex == IBUS_PACKET_SIZE) { // Full packet received packetAvailable = true; packetIndex = 0; // Reset for next packet } break; } } } // Function to process the received IBUS packet bool processIBUSPacket() { if (!packetAvailable) { return false; } // Verify checksum before processing if (!verifyChecksum()) { packetAvailable = false; return false; // Checksum failed, discard packet } // Extract channel data camX = ibusPacket[2 + 0 * 2] | (ibusPacket[3 + 0 * 2] << 8); camY = ibusPacket[2 + 1 * 2] | (ibusPacket[3 + 1 * 2] << 8); moveX = ibusPacket[2 + 2 * 2] | (ibusPacket[3 + 2 * 2] << 8); moveY = ibusPacket[2 + 3 * 2] | (ibusPacket[3 + 3 * 2] << 8); switchA = ibusPacket[2 + 4 * 2] | (ibusPacket[3 + 4 * 2] << 8); switchB = ibusPacket[2 + 5 * 2] | (ibusPacket[3 + 5 * 2] << 8); switchC = ibusPacket[2 + 6 * 2] | (ibusPacket[3 + 6 * 2] << 8); switchD = ibusPacket[2 + 7 * 2] | (ibusPacket[3 + 7 * 2] << 8); varA = ibusPacket[2 + 8 * 2] | (ibusPacket[3 + 8 * 2] << 8); varB = ibusPacket[2 + 9 * 2] | (ibusPacket[3 + 9 * 2] << 8); packetAvailable = false; return true; } // Getters for channel values int getCamX() { return camX; } int getCamY() { return camY; } int getMoveX() { return moveX; } int getMoveY() { return moveY; } int getSwitchA() { return switchA; } int getSwitchB() { return switchB; } int getSwitchC() { return switchC; } int getSwitchD() { return switchD; } int getVarA() { return varA; } int getVarB() { return varB; } private: HardwareSerial& serial; uint8_t rxPin; static const int IBUS_PACKET_SIZE = 32; static const uint8_t HEADER1 = 0x20; static const uint8_t HEADER2 = 0x40; uint8_t ibusPacket[IBUS_PACKET_SIZE]; int packetIndex; bool packetAvailable; int camX, camY, moveX, moveY, switchA, switchB, switchC, switchD, varA, varB; // Function to verify the checksum of the IBUS packet bool verifyChecksum() { uint16_t checksum = 0xFFFF; for (int i = 0; i < IBUS_PACKET_SIZE - 2; i++) { checksum -= ibusPacket[i]; } uint16_t receivedChecksum = ibusPacket[IBUS_PACKET_SIZE - 2] | (ibusPacket[IBUS_PACKET_SIZE - 1] << 8); return checksum == receivedChecksum; } }; // Create an instance of IBusReceiver // For ESP32, Serial2 is typically used for UART communication IBusReceiver ibus(Serial2, 16); // Assuming RX is on GPIO 13 void setup() { Serial.begin(115200); // Initialize Serial Monitor ibus.begin(115200); // Initialize IBUS receiver with baud rate 115200 // Optional: Give some time for serial ports to initialize delay(1000); } void loop() { // Read IBUS data ibus.readIBUSData(); // Process IBUS packet if available if (ibus.processIBUSPacket()) { // Retrieve the channel values int camX = ibus.getCamX(); int camY = ibus.getCamY(); int moveX = ibus.getMoveX(); int moveY = ibus.getMoveY(); int switchA = ibus.getSwitchA(); int switchB = ibus.getSwitchB(); int switchC = ibus.getSwitchC(); int switchD = ibus.getSwitchD(); int varA = ibus.getVarA(); int varB = ibus.getVarB(); // Print the channel values Serial.print(2000); Serial.print(" "); Serial.print(1000); Serial.print(" "); Serial.print(camX); Serial.print(" "); Serial.print(camY); Serial.print(" "); Serial.print(moveX); Serial.print(" "); Serial.print(moveY); Serial.print(" "); Serial.print(switchA); Serial.print(" "); Serial.print(switchB); Serial.print(" "); Serial.print(switchC); Serial.print(" "); Serial.print(switchD); Serial.print(" "); Serial.print(varA); Serial.print(" "); Serial.print(varB); Serial.println(""); } // Optional: Add a small delay to prevent flooding the serial monitor // delay(10); }
To be clear, I'm sure this could massively be improved upon, but I have a bunch of more steps to publish.
-
2Connect esp32 to two BTS7960 motor drivers to control wheel dc motors
We are going to use some somewhat beefy but inexpensive motor drivers, one for each wheel (each of which has its own motor). The BTS7960 is rated up to 43A, which will be more than sufficient unless we live on a steep hill (in which case we would like to hear from us).
We will assign two pins to the ESP32 to output the PWM signal using the MCPWM library (apparently it’s like, better than the LEDC library or something for reasons I don’t understand). While we are soldering, we will ask ChatGPT^ei to write up another cpp class allowing us to easily control the drivers from the main loop.
This code uses the MCPWM library to drive the BTS7960:
#include "driver/mcpwm.h" // MyMotor class for controlling the BTS7960 motor driver class MyMotor { public: // Constructor: Initializes MCPWM and sets up pins MyMotor(int forwardPin, int reversePin) { this->forwardPin = forwardPin; this->reversePin = reversePin; // Initialize MCPWM for forward and reverse pins mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, forwardPin); // Initialize forward pin for PWM mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0B, reversePin); // Initialize reverse pin for PWM // Configure MCPWM unit 0, timer 0 for both forward and reverse control mcpwm_config_t pwm_config; pwm_config.frequency = 1000; // Set frequency to 1 kHz pwm_config.cmpr_a = 0.0; // Initial duty cycle of PWMxA = 0% pwm_config.cmpr_b = 0.0; // Initial duty cycle of PWMxB = 0% pwm_config.counter_mode = MCPWM_UP_COUNTER; // Up counting mode pwm_config.duty_mode = MCPWM_DUTY_MODE_0; // Active high duty // Initialize MCPWM with the above configuration mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config); } // Method to set speed and direction: -100 to 100 void setSpeed(int speed) { if (speed < -100) speed = -100; // Limit speed to minimum -100 if (speed > 100) speed = 100; // Limit speed to maximum 100 // Calculate the absolute duty cycle percentage (0 to 100) float dutyCycle = abs(speed); // Forward direction if (speed > 0) { mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, dutyCycle); // Set duty for forward mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_B, 0.0); // Ensure reverse is 0 Serial.print("Forward Speed: "); } // Reverse direction else if (speed < 0) { mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, 0.0); // Ensure forward is 0 mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_B, dutyCycle); // Set duty for reverse Serial.print("Reverse Speed: "); } // Stop else { mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, 0.0); // Stop forward mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_B, 0.0); // Stop reverse Serial.println("Motor Stopped"); return; } // Print the set speed to Serial Monitor Serial.print(speed); Serial.println("%"); } private: int forwardPin; // GPIO for forward PWM control int reversePin; // GPIO for reverse PWM control }; // Create an instance of the MyMotor class MyMotor motor(4, 15); // Use GPIO 4 for forward, GPIO 15 for reverse void setup() { Serial.begin(115200); Serial.println("Starting Motor Test..."); // No need to initialize pins in setup as MyMotor handles this } void loop() { // ** FORWARD CYCLE: Ramp up from 0 to 100% ** for (int speed = 0; speed <= 100; speed += 10) { motor.setSpeed(speed); // Set speed in the forward direction delay(500); // Adjust delay for smoother ramp-up (lower = smoother) } delay(2000); // Pause at 100% forward // ** RAMP DOWN to 0 ** for (int speed = 100; speed >= 0; speed -= 10) { motor.setSpeed(speed); // Ramp down to 0 delay(500); } delay(2000); // Pause at 0% (stopped) // ** REVERSE CYCLE: Ramp up from 0 to -100% ** for (int speed = 0; speed >= -100; speed -= 10) { motor.setSpeed(speed); // Set speed in the reverse direction delay(500); // Adjust delay for smoother ramp-up (lower = smoother) } delay(2000); // Pause at 100% reverse // ** RAMP DOWN to 0 ** for (int speed = -100; speed <= 0; speed += 10) { motor.setSpeed(speed); // Ramp down to 0 delay(500); } delay(2000); // Pause at 0% (stopped) }
If this works correctly, it should look something like the first part of this:
-
3The steering pulley
Dear reader, you’re on your own for the mechanical part of this section; I was entirely out of my depth here. I’m not exaggerating when I say took four iterations of progressively more (poor) aluminum TIG welding to make something rigid where I could have it fully control the steering wheel. I know some smart people hang out here and might have a better idea, please let me know as this was the best that I can come up with (including with advice from people who know what they are talking about).
Long story short, we will use a 20-tooth GT2 pulley on a 35kg*cm servo to drive a 40-tooth pulley mounted to the driving shaft. It bears stating, Power Wheels do not use elegant mechanisms for their steering
Once we make our servo-drive-shaft coupling rigid (this involved more welding and fabricating it out tensioner for me, but I hope you understand this better), let's take our current existing code and add include the servo controlling the steering wheel:
#include "driver/mcpwm.h" // --- IBusReceiver Class --- class IBusReceiver { public: // Constructor IBusReceiver(HardwareSerial& serialPort, uint8_t rxPin) : serial(serialPort), rxPin(rxPin) { packetIndex = 0; packetAvailable = false; } // Initialize the serial port void begin(uint32_t baudRate) { serial.begin(baudRate, SERIAL_8N1, rxPin, -1); } // Non-blocking function to read IBUS data void readIBUSData() { while (serial.available()) { uint8_t incomingByte = serial.read(); switch (packetIndex) { case 0: if (incomingByte == HEADER1) { ibusPacket[packetIndex++] = incomingByte; } break; case 1: if (incomingByte == HEADER2) { ibusPacket[packetIndex++] = incomingByte; } else { packetIndex = 0; // Reset if header not matched } break; default: ibusPacket[packetIndex++] = incomingByte; if (packetIndex == IBUS_PACKET_SIZE) { // Full packet received packetAvailable = true; packetIndex = 0; // Reset for next packet } break; } } } // Function to process the received IBUS packet bool processIBUSPacket() { if (!packetAvailable) { return false; } // Verify checksum before processing if (!verifyChecksum()) { packetAvailable = false; return false; // Checksum failed, discard packet } // Extract channel data camX = ibusPacket[2 + 0 * 2] | (ibusPacket[3 + 0 * 2] << 8); camY = ibusPacket[2 + 1 * 2] | (ibusPacket[3 + 1 * 2] << 8); moveY = ibusPacket[2 + 2 * 2] | (ibusPacket[3 + 2 * 2] << 8); moveX = ibusPacket[2 + 3 * 2] | (ibusPacket[3 + 3 * 2] << 8); switchA = ibusPacket[2 + 4 * 2] | (ibusPacket[3 + 4 * 2] << 8); switchB = ibusPacket[2 + 5 * 2] | (ibusPacket[3 + 5 * 2] << 8); switchC = ibusPacket[2 + 6 * 2] | (ibusPacket[3 + 6 * 2] << 8); switchD = ibusPacket[2 + 7 * 2] | (ibusPacket[3 + 7 * 2] << 8); varA = ibusPacket[2 + 8 * 2] | (ibusPacket[3 + 8 * 2] << 8); varB = ibusPacket[2 + 9 * 2] | (ibusPacket[3 + 9 * 2] << 8); packetAvailable = false; return true; } // Getters for channel values int getCamX() { return camX; } int getCamY() { return camY; } int getMoveX() { return moveX; } int getMoveY() { return moveY; } int getSwitchA() { return switchA; } int getSwitchB() { return switchB; } int getSwitchC() { return switchC; } int getSwitchD() { return switchD; } int getVarA() { return varA; } int getVarB() { return varB; } private: HardwareSerial& serial; uint8_t rxPin; static const int IBUS_PACKET_SIZE = 32; static const uint8_t HEADER1 = 0x20; static const uint8_t HEADER2 = 0x40; uint8_t ibusPacket[IBUS_PACKET_SIZE]; int packetIndex; bool packetAvailable; int camX, camY, moveX, moveY, switchA, switchB, switchC, switchD, varA, varB; // Function to verify the checksum of the IBUS packet bool verifyChecksum() { uint16_t checksum = 0xFFFF; for (int i = 0; i < IBUS_PACKET_SIZE - 2; i++) { checksum -= ibusPacket[i]; } uint16_t receivedChecksum = ibusPacket[IBUS_PACKET_SIZE - 2] | (ibusPacket[IBUS_PACKET_SIZE - 1] << 8); return checksum == receivedChecksum; } }; // --- MyMotor Class --- class MyMotor { public: // Constructor: Initializes MCPWM and sets up pins MyMotor(int forwardPin, int reversePin) { this->forwardPin = forwardPin; this->reversePin = reversePin; // Initialize MCPWM for forward and reverse pins mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, forwardPin); // Initialize forward pin for PWM mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0B, reversePin); // Initialize reverse pin for PWM // Configure MCPWM unit 0, timer 0 for both forward and reverse control mcpwm_config_t pwm_config; pwm_config.frequency = 1000; // Set frequency to 1 kHz pwm_config.cmpr_a = 0.0; // Initial duty cycle of PWMxA = 0% pwm_config.cmpr_b = 0.0; // Initial duty cycle of PWMxB = 0% pwm_config.counter_mode = MCPWM_UP_COUNTER; // Up counting mode pwm_config.duty_mode = MCPWM_DUTY_MODE_0; // Active high duty // Initialize MCPWM with the above configuration mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config); } // Method to set speed and direction: -100 to 100 void setSpeed(int speed) { if (speed < -100) speed = -100; // Limit speed to minimum -100 if (speed > 100) speed = 100; // Limit speed to maximum 100 // Calculate the absolute duty cycle percentage (0 to 100) float dutyCycle = abs(speed); // Forward direction if (speed > 0) { mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, dutyCycle); // Set duty for forward mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_B, 0.0); // Ensure reverse is 0 //Serial.print("Forward Speed: "); } // Reverse direction else if (speed < 0) { mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, 0.0); // Ensure forward is 0 mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_B, dutyCycle); // Set duty for reverse //Serial.print("Reverse Speed: "); } // Stop else { mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, 0.0); // Stop forward mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_B, 0.0); // Stop reverse //Serial.println("Motor Stopped"); return; } // Print the set speed to Serial Monitor Serial.print(speed); //Serial.println("%"); } private: int forwardPin; // GPIO for forward PWM control int reversePin; // GPIO for reverse PWM control }; // --- MyServo Class --- class MyServo { public: // Constructor to initialize the servo on a given MCPWM unit, timer, operator, and GPIO pin MyServo(mcpwm_unit_t unit, mcpwm_timer_t timer, mcpwm_operator_t op, int gpio_pin) { mcpwm_num = unit; timer_num = timer; op_num = op; gpio_num = gpio_pin; // Initialize MCPWM GPIO pin mcpwm_gpio_init(mcpwm_num, getSignal(), gpio_num); // Configure MCPWM unit mcpwm_config_t pwm_config; pwm_config.frequency = 50; // Frequency = 50Hz for servo pwm_config.cmpr_a = 0; // Duty cycle for PWMxA = 0 pwm_config.cmpr_b = 0; // Duty cycle for PWMxB = 0 pwm_config.counter_mode = MCPWM_UP_COUNTER; pwm_config.duty_mode = MCPWM_DUTY_MODE_0; // Initialize MCPWM with the configuration mcpwm_init(mcpwm_num, timer_num, &pwm_config); } // Method to set the servo angle (75 to 195 degrees) void setAngle(int angle) { float duty_cycle = calculateDutyCycle(angle); mcpwm_set_duty(mcpwm_num, timer_num, op_num, duty_cycle); mcpwm_set_duty_type(mcpwm_num, timer_num, op_num, MCPWM_DUTY_MODE_0); } private: mcpwm_unit_t mcpwm_num; // MCPWM unit number mcpwm_timer_t timer_num; // MCPWM timer number mcpwm_operator_t op_num; // MCPWM operator (A or B) int gpio_num; // GPIO pin number // Function to calculate duty cycle based on the desired angle float calculateDutyCycle(int angle) { // Pulse width range from 0.5ms (0 degrees) to 2.5ms (270 degrees) float min_pulse_width = 0.5; // Minimum pulse width in milliseconds float max_pulse_width = 2.5; // Maximum pulse width in milliseconds float frequency = 50.0; // Frequency in Hz float period = 1000.0 / frequency; // Period in milliseconds (20ms) // Ensure the angle is within the specified range if (angle < 75) angle = 75; if (angle > 195) angle = 195; // Calculate pulse width for the given angle float pulse_width = min_pulse_width + ((float)angle / 270.0) * (max_pulse_width - min_pulse_width); // Calculate duty cycle percentage float duty_cycle = (pulse_width / period) * 100.0; return duty_cycle; } // Helper function to get the correct MCPWM signal for GPIO initialization mcpwm_io_signals_t getSignal() { if (op_num == MCPWM_OPR_A) { if (timer_num == MCPWM_TIMER_0) return MCPWM0A; else if (timer_num == MCPWM_TIMER_1) return MCPWM1A; else return MCPWM2A; // MCPWM_TIMER_2 } else { if (timer_num == MCPWM_TIMER_0) return MCPWM0B; else if (timer_num == MCPWM_TIMER_1) return MCPWM1B; else return MCPWM2B; // MCPWM_TIMER_2 } } }; // --- Global Instances --- // Create an instance of IBusReceiver // For ESP32, Serial2 is typically used for UART communication IBusReceiver ibus(Serial2, 16); // Assuming RX is on GPIO 16 // Create an instance of the MyMotor class MyMotor motor(4, 15); // Use GPIO 4 for forward, GPIO 15 for reverse // Create an instance of the MyServo class MyServo servo_wheel(MCPWM_UNIT_1, MCPWM_TIMER_0, MCPWM_OPR_A, 25); // Servo on GPIO25 void setup() { Serial.begin(115200); // Initialize Serial Monitor ibus.begin(115200); // Initialize IBUS receiver with baud rate 115200 Serial.println("Starting Motor and Servo Control with iBUS Receiver..."); // Optional: Give some time for serial ports to initialize delay(1000); } void loop() { // Read IBUS data ibus.readIBUSData(); // Process IBUS packet if available if (ibus.processIBUSPacket()) { // Retrieve the moveY and moveX channel values int moveY = ibus.getMoveY(); int moveX = ibus.getMoveX(); int camX = ibus.getCamX(); int camY = ibus.getCamY(); // Map moveY from 1000-2000 to -100 to 100 int motorSpeed = map(moveY, 1000, 2000, -100, 100); motor.setSpeed(motorSpeed); // Map moveX from 1000-2000 to 75-195 degrees int servo_wheel_angle = map(moveX, 1000, 2000, 80, 190); servo_wheel.setAngle(servo_wheel_angle); // Map camX from 1000-2000 to whatever int servo_pan_angle = map(camX, 1000, 2000, 45, 225); servo_wheel.setAngle(servo_pan_angle); int servo_tilt_angle = map(camX, 1000, 2000, 45, 225); servo_wheel.setAngle(servo_tilt_angle); Serial.print(motorSpeed); Serial.print(" "); Serial.print(servo_wheel_angle); Serial.print(" "); Serial.print(servo_pan_angle); Serial.print(" "); Serial.println(servo_tilt_angle); } }
Now that we have both the motor drivers and the steering servo controlled by the Esp32, we can do things like this:
-
4Pan and Tilt Skull Servos
I imagine this will be different for each of our personal skills, skill sets, and available materials. This probably could have been CAD'd and 3d printed, but I wanted to be away from the computer for a bit, so I made a weird mechanism for the pan and tilt functions of the skull.
The pan mechanism is more complicated,
In any event, here is a code with the motor driver is going, the steering wheel servo going, and both the pan and the tilt servers going (if you want to see videos on the actual wiring of stuff let you know because I definitely already have the footage):
#include "driver/mcpwm.h" // --- IBusReceiver Class --- class IBusReceiver { public: // Constructor IBusReceiver(HardwareSerial& serialPort, uint8_t rxPin) : serial(serialPort), rxPin(rxPin) { packetIndex = 0; packetAvailable = false; } // Initialize the serial port void begin(uint32_t baudRate) { serial.begin(baudRate, SERIAL_8N1, rxPin, -1); } // Non-blocking function to read IBUS data void readIBUSData() { while (serial.available()) { uint8_t incomingByte = serial.read(); switch (packetIndex) { case 0: if (incomingByte == HEADER1) { ibusPacket[packetIndex++] = incomingByte; } break; case 1: if (incomingByte == HEADER2) { ibusPacket[packetIndex++] = incomingByte; } else { packetIndex = 0; // Reset if header not matched } break; default: ibusPacket[packetIndex++] = incomingByte; if (packetIndex == IBUS_PACKET_SIZE) { // Full packet received packetAvailable = true; packetIndex = 0; // Reset for next packet } break; } } } // Function to process the received IBUS packet bool processIBUSPacket() { if (!packetAvailable) { return false; } // Verify checksum before processing if (!verifyChecksum()) { packetAvailable = false; return false; // Checksum failed, discard packet } // Extract channel data camX = ibusPacket[2 + 0 * 2] | (ibusPacket[3 + 0 * 2] << 8); camY = ibusPacket[2 + 1 * 2] | (ibusPacket[3 + 1 * 2] << 8); moveY = ibusPacket[2 + 2 * 2] | (ibusPacket[3 + 2 * 2] << 8); moveX = ibusPacket[2 + 3 * 2] | (ibusPacket[3 + 3 * 2] << 8); switchA = ibusPacket[2 + 4 * 2] | (ibusPacket[3 + 4 * 2] << 8); switchB = ibusPacket[2 + 5 * 2] | (ibusPacket[3 + 5 * 2] << 8); switchC = ibusPacket[2 + 6 * 2] | (ibusPacket[3 + 6 * 2] << 8); switchD = ibusPacket[2 + 7 * 2] | (ibusPacket[3 + 7 * 2] << 8); varA = ibusPacket[2 + 8 * 2] | (ibusPacket[3 + 8 * 2] << 8); varB = ibusPacket[2 + 9 * 2] | (ibusPacket[3 + 9 * 2] << 8); packetAvailable = false; return true; } // Getters for channel values int getCamX() { return camX; } int getCamY() { return camY; } int getMoveX() { return moveX; } int getMoveY() { return moveY; } int getSwitchA() { return switchA; } int getSwitchB() { return switchB; } int getSwitchC() { return switchC; } int getSwitchD() { return switchD; } int getVarA() { return varA; } int getVarB() { return varB; } private: HardwareSerial& serial; uint8_t rxPin; static const int IBUS_PACKET_SIZE = 32; static const uint8_t HEADER1 = 0x20; static const uint8_t HEADER2 = 0x40; uint8_t ibusPacket[IBUS_PACKET_SIZE]; int packetIndex; bool packetAvailable; int camX, camY, moveX, moveY, switchA, switchB, switchC, switchD, varA, varB; // Function to verify the checksum of the IBUS packet bool verifyChecksum() { uint16_t checksum = 0xFFFF; for (int i = 0; i < IBUS_PACKET_SIZE - 2; i++) { checksum -= ibusPacket[i]; } uint16_t receivedChecksum = ibusPacket[IBUS_PACKET_SIZE - 2] | (ibusPacket[IBUS_PACKET_SIZE - 1] << 8); return checksum == receivedChecksum; } }; // --- MyMotor Class --- class MyMotor { public: // Constructor: Initializes MCPWM and sets up pins MyMotor(int forwardPin, int reversePin) { this->forwardPin = forwardPin; this->reversePin = reversePin; // Initialize MCPWM for forward and reverse pins mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, forwardPin); // Initialize forward pin for PWM mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0B, reversePin); // Initialize reverse pin for PWM // Configure MCPWM unit 0, timer 0 for both forward and reverse control mcpwm_config_t pwm_config; pwm_config.frequency = 1000; // Set frequency to 1 kHz pwm_config.cmpr_a = 0.0; // Initial duty cycle of PWMxA = 0% pwm_config.cmpr_b = 0.0; // Initial duty cycle of PWMxB = 0% pwm_config.counter_mode = MCPWM_UP_COUNTER; // Up counting mode pwm_config.duty_mode = MCPWM_DUTY_MODE_0; // Active high duty // Initialize MCPWM with the above configuration mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config); } // Method to set speed and direction: -100 to 100 void setSpeed(int speed) { if (speed < -100) speed = -100; // Limit speed to minimum -100 if (speed > 100) speed = 100; // Limit speed to maximum 100 // Calculate the absolute duty cycle percentage (0 to 100) float dutyCycle = abs(speed); // Forward direction if (speed > 0) { mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, dutyCycle); // Set duty for forward mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_B, 0.0); // Ensure reverse is 0 //Serial.print("Forward Speed: "); } // Reverse direction else if (speed < 0) { mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, 0.0); // Ensure forward is 0 mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_B, dutyCycle); // Set duty for reverse //Serial.print("Reverse Speed: "); } // Stop else { mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, 0.0); // Stop forward mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_B, 0.0); // Stop reverse //Serial.println("Motor Stopped"); return; } // Print the set speed to Serial Monitor //Serial.print(speed); //Serial.println("%"); } private: int forwardPin; // GPIO for forward PWM control int reversePin; // GPIO for reverse PWM control }; // --- MyServo Class --- class MyServo { public: // Constructor to initialize the servo on a given MCPWM unit, timer, operator, and GPIO pin MyServo(mcpwm_unit_t unit, mcpwm_timer_t timer, mcpwm_operator_t op, int gpio_pin) { mcpwm_num = unit; timer_num = timer; op_num = op; gpio_num = gpio_pin; // Initialize MCPWM GPIO pin mcpwm_gpio_init(mcpwm_num, getSignal(), gpio_num); // Configure MCPWM unit mcpwm_config_t pwm_config; pwm_config.frequency = 50; // Frequency = 50Hz for servo pwm_config.cmpr_a = 0; // Duty cycle for PWMxA = 0 pwm_config.cmpr_b = 0; // Duty cycle for PWMxB = 0 pwm_config.counter_mode = MCPWM_UP_COUNTER; pwm_config.duty_mode = MCPWM_DUTY_MODE_0; // Initialize MCPWM with the configuration mcpwm_init(mcpwm_num, timer_num, &pwm_config); } // Method to set the servo angle (75 to 195 degrees) void setAngle(int angle) { float duty_cycle = calculateDutyCycle(angle); mcpwm_set_duty(mcpwm_num, timer_num, op_num, duty_cycle); mcpwm_set_duty_type(mcpwm_num, timer_num, op_num, MCPWM_DUTY_MODE_0); } private: mcpwm_unit_t mcpwm_num; // MCPWM unit number mcpwm_timer_t timer_num; // MCPWM timer number mcpwm_operator_t op_num; // MCPWM operator (A or B) int gpio_num; // GPIO pin number // Function to calculate duty cycle based on the desired angle float calculateDutyCycle(int angle) { // Pulse width range from 0.5ms (0 degrees) to 2.5ms (270 degrees) float min_pulse_width = 0.5; // Minimum pulse width in milliseconds float max_pulse_width = 2.5; // Maximum pulse width in milliseconds float frequency = 50.0; // Frequency in Hz float period = 1000.0 / frequency; // Period in milliseconds (20ms) // Ensure the angle is within the specified range if (angle < 75) angle = 75; if (angle > 195) angle = 195; // Calculate pulse width for the given angle float pulse_width = min_pulse_width + ((float)angle / 270.0) * (max_pulse_width - min_pulse_width); // Calculate duty cycle percentage float duty_cycle = (pulse_width / period) * 100.0; return duty_cycle; } // Helper function to get the correct MCPWM signal for GPIO initialization mcpwm_io_signals_t getSignal() { if (op_num == MCPWM_OPR_A) { if (timer_num == MCPWM_TIMER_0) return MCPWM0A; else if (timer_num == MCPWM_TIMER_1) return MCPWM1A; else return MCPWM2A; // MCPWM_TIMER_2 } else { if (timer_num == MCPWM_TIMER_0) return MCPWM0B; else if (timer_num == MCPWM_TIMER_1) return MCPWM1B; else return MCPWM2B; // MCPWM_TIMER_2 } } }; // --- Global Instances --- // Create an instance of IBusReceiver // For ESP32, Serial2 is typically used for UART communication IBusReceiver ibus(Serial2, 16); // Assuming RX is on GPIO 16 // Create an instance of the MyMotor class MyMotor motor(4, 15); // Use GPIO 4 for forward, GPIO 15 for reverse // Create instances of the MyServo class MyServo servo_wheel(MCPWM_UNIT_1, MCPWM_TIMER_0, MCPWM_OPR_A, 25); // Servo on GPIO25 MyServo servo_pan(MCPWM_UNIT_1, MCPWM_TIMER_1, MCPWM_OPR_A, 26); // Servo on GPIO26 MyServo servo_tilt(MCPWM_UNIT_1, MCPWM_TIMER_2, MCPWM_OPR_A, 27); // Servo on GPIO27 // Define GPIO pins for the relays const int propane_relay = 18; // GPIO 18 controls the propane relay const int arc_relay = 19; // GPIO 19 controls the arc relay int tiltMap(int input) { int output; // First linear relationship: from input 1060 to 1500, output 181 to 157 if (input <= 1500) { output = map(input, 1060, 1500, 181, 157); } // Second linear relationship: from input 1500 to 2000, output 157 to 100 else { output = map(input, 1500, 2000, 157, 100); } return output; } void setup() { Serial.begin(115200); // Initialize Serial Monitor ibus.begin(115200); // Initialize IBUS receiver with baud rate 115200 Serial.println("Starting Motor and Servo Control with iBUS Receiver..."); // Initialize relay control pins pinMode(propane_relay, OUTPUT); pinMode(arc_relay, OUTPUT); // Ensure both relays are OFF at startup digitalWrite(propane_relay, LOW); digitalWrite(arc_relay, LOW); // Optional: Give some time for serial ports to initialize delay(1000); } void loop() { // Read IBUS data ibus.readIBUSData(); // Process IBUS packet if available if (ibus.processIBUSPacket()) { // Retrieve the moveY, moveX, camX, camY, and switchC channel values int moveY = ibus.getMoveY(); int moveX = ibus.getMoveX(); int camX = ibus.getCamX(); int camY = ibus.getCamY(); int switchC = ibus.getSwitchC(); // Map moveY from 1000-2000 to -100 to 100 int motorSpeed = map(moveY, 1000, 2000, -100, 100); motor.setSpeed(motorSpeed); // Map moveX from 1000-2000 to 80-190 degrees int servo_wheel_angle = map(moveX, 1000, 2000, 190, 80); servo_wheel.setAngle(servo_wheel_angle); // Map camX and camY for pan and tilt servos int servo_pan_angle = map(camX, 1044, 1946, 215, 55); servo_pan.setAngle(servo_pan_angle); int servo_tilt_angle = tiltMap(camY); servo_tilt.setAngle(servo_tilt_angle); // Control relays based on switchC value if (switchC == 1000) { // Both relays OFF digitalWrite(propane_relay, LOW); digitalWrite(arc_relay, LOW); } else if (switchC == 1500) { // Propane relay ON, Arc relay OFF digitalWrite(propane_relay, HIGH); digitalWrite(arc_relay, LOW); } else if (switchC == 2000) { // Both relays ON digitalWrite(propane_relay, HIGH); digitalWrite(arc_relay, HIGH); } // Debugging output /* Serial.print("motorSpeed: "); Serial.print(motorSpeed); Serial.print(", servo_wheel_angle: "); Serial.print(servo_wheel_angle); Serial.print(", servo_pan_angle: "); Serial.print(servo_pan_angle); Serial.print(", servo_tilt_angle: "); Serial.print(servo_tilt_angle); Serial.print(", switchC: "); Serial.println(switchC); */ } }
-
5Make it like Ghost Rider
Please, please don't make this your first experiment with flammable gas. There, I'm prepared for the onslaught of the Internet safety nerds. But seriously, this is not the starter project for flammable gas.
That said, let's plumb some propane into little Timmy to make him more awesome and bad ass! Visual aids are in the full video, but basically we do a plane cut through the top of the skull and weld some ~1/8" mild steel to it. I dunno if the skull is truly "cast iron," as that's outside my territory. All I know is it welded to mild steel really nicely! We bevel and cut a 1/4" NPT male threaded pipe (aka "nipple") to the bottom of the skull, then we drill out some holes and screw in a propane line. Control that propane with a male-female 1/4" NPT solenoid valve connected to a relay connected to the esp32.
Last thing to do? We connect another relay, then connect a high-voltage trasformer for the electrical/plasma arc. At this point, the left steering stick controls the speed/ direction, the right steering stick controls the head position, and switch3 (a 3-way switch) controls both the propane and the ignition. Position 0: all off // Positon 1: gas solenoid on, ignition off // Position 2: gas solenoid and ignition on
Let's test it out:
And here is our production code:
#include "driver/mcpwm.h" // --- IBusReceiver Class --- class IBusReceiver { public: // Constructor IBusReceiver(HardwareSerial& serialPort, uint8_t rxPin) : serial(serialPort), rxPin(rxPin) { packetIndex = 0; packetAvailable = false; } // Initialize the serial port void begin(uint32_t baudRate) { serial.begin(baudRate, SERIAL_8N1, rxPin, -1); } // Non-blocking function to read IBUS data void readIBUSData() { while (serial.available()) { uint8_t incomingByte = serial.read(); switch (packetIndex) { case 0: if (incomingByte == HEADER1) { ibusPacket[packetIndex++] = incomingByte; } break; case 1: if (incomingByte == HEADER2) { ibusPacket[packetIndex++] = incomingByte; } else { packetIndex = 0; // Reset if header not matched } break; default: ibusPacket[packetIndex++] = incomingByte; if (packetIndex == IBUS_PACKET_SIZE) { // Full packet received packetAvailable = true; packetIndex = 0; // Reset for next packet } break; } } } // Function to process the received IBUS packet bool processIBUSPacket() { if (!packetAvailable) { return false; } // Verify checksum before processing if (!verifyChecksum()) { packetAvailable = false; return false; // Checksum failed, discard packet } // Extract channel data camX = ibusPacket[2 + 0 * 2] | (ibusPacket[3 + 0 * 2] << 8); camY = ibusPacket[2 + 1 * 2] | (ibusPacket[3 + 1 * 2] << 8); moveY = ibusPacket[2 + 2 * 2] | (ibusPacket[3 + 2 * 2] << 8); moveX = ibusPacket[2 + 3 * 2] | (ibusPacket[3 + 3 * 2] << 8); switchA = ibusPacket[2 + 4 * 2] | (ibusPacket[3 + 4 * 2] << 8); switchB = ibusPacket[2 + 5 * 2] | (ibusPacket[3 + 5 * 2] << 8); switchC = ibusPacket[2 + 6 * 2] | (ibusPacket[3 + 6 * 2] << 8); switchD = ibusPacket[2 + 7 * 2] | (ibusPacket[3 + 7 * 2] << 8); varA = ibusPacket[2 + 8 * 2] | (ibusPacket[3 + 8 * 2] << 8); varB = ibusPacket[2 + 9 * 2] | (ibusPacket[3 + 9 * 2] << 8); packetAvailable = false; return true; } // Getters for channel values int getCamX() { return camX; } int getCamY() { return camY; } int getMoveX() { return moveX; } int getMoveY() { return moveY; } int getSwitchA() { return switchA; } int getSwitchB() { return switchB; } int getSwitchC() { return switchC; } int getSwitchD() { return switchD; } int getVarA() { return varA; } int getVarB() { return varB; } private: HardwareSerial& serial; uint8_t rxPin; static const int IBUS_PACKET_SIZE = 32; static const uint8_t HEADER1 = 0x20; static const uint8_t HEADER2 = 0x40; uint8_t ibusPacket[IBUS_PACKET_SIZE]; int packetIndex; bool packetAvailable; int camX, camY, moveX, moveY, switchA, switchB, switchC, switchD, varA, varB; // Function to verify the checksum of the IBUS packet bool verifyChecksum() { uint16_t checksum = 0xFFFF; for (int i = 0; i < IBUS_PACKET_SIZE - 2; i++) { checksum -= ibusPacket[i]; } uint16_t receivedChecksum = ibusPacket[IBUS_PACKET_SIZE - 2] | (ibusPacket[IBUS_PACKET_SIZE - 1] << 8); return checksum == receivedChecksum; } }; // --- MyMotor Class --- class MyMotor { public: // Constructor: Initializes MCPWM and sets up pins MyMotor(int forwardPin, int reversePin) { this->forwardPin = forwardPin; this->reversePin = reversePin; // Initialize MCPWM for forward and reverse pins mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, forwardPin); // Initialize forward pin for PWM mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0B, reversePin); // Initialize reverse pin for PWM // Configure MCPWM unit 0, timer 0 for both forward and reverse control mcpwm_config_t pwm_config; pwm_config.frequency = 1000; // Set frequency to 1 kHz pwm_config.cmpr_a = 0.0; // Initial duty cycle of PWMxA = 0% pwm_config.cmpr_b = 0.0; // Initial duty cycle of PWMxB = 0% pwm_config.counter_mode = MCPWM_UP_COUNTER; // Up counting mode pwm_config.duty_mode = MCPWM_DUTY_MODE_0; // Active high duty // Initialize MCPWM with the above configuration mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config); } // Method to set speed and direction: -100 to 100 void setSpeed(int speed) { if (speed < -100) speed = -100; // Limit speed to minimum -100 if (speed > 100) speed = 100; // Limit speed to maximum 100 // Calculate the absolute duty cycle percentage (0 to 100) float dutyCycle = abs(speed); // Forward direction if (speed > 0) { mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, dutyCycle); // Set duty for forward mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_B, 0.0); // Ensure reverse is 0 //Serial.print("Forward Speed: "); } // Reverse direction else if (speed < 0) { mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, 0.0); // Ensure forward is 0 mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_B, dutyCycle); // Set duty for reverse //Serial.print("Reverse Speed: "); } // Stop else { mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, 0.0); // Stop forward mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_B, 0.0); // Stop reverse //Serial.println("Motor Stopped"); return; } // Print the set speed to Serial Monitor //Serial.print(speed); //Serial.println("%"); } private: int forwardPin; // GPIO for forward PWM control int reversePin; // GPIO for reverse PWM control }; // --- MyServo Class --- class MyServo { public: // Constructor to initialize the servo on a given MCPWM unit, timer, operator, and GPIO pin MyServo(mcpwm_unit_t unit, mcpwm_timer_t timer, mcpwm_operator_t op, int gpio_pin) { mcpwm_num = unit; timer_num = timer; op_num = op; gpio_num = gpio_pin; // Initialize MCPWM GPIO pin mcpwm_gpio_init(mcpwm_num, getSignal(), gpio_num); // Configure MCPWM unit mcpwm_config_t pwm_config; pwm_config.frequency = 50; // Frequency = 50Hz for servo pwm_config.cmpr_a = 0; // Duty cycle for PWMxA = 0 pwm_config.cmpr_b = 0; // Duty cycle for PWMxB = 0 pwm_config.counter_mode = MCPWM_UP_COUNTER; pwm_config.duty_mode = MCPWM_DUTY_MODE_0; // Initialize MCPWM with the configuration mcpwm_init(mcpwm_num, timer_num, &pwm_config); } // Method to set the servo angle (75 to 195 degrees) void setAngle(int angle) { float duty_cycle = calculateDutyCycle(angle); mcpwm_set_duty(mcpwm_num, timer_num, op_num, duty_cycle); mcpwm_set_duty_type(mcpwm_num, timer_num, op_num, MCPWM_DUTY_MODE_0); } private: mcpwm_unit_t mcpwm_num; // MCPWM unit number mcpwm_timer_t timer_num; // MCPWM timer number mcpwm_operator_t op_num; // MCPWM operator (A or B) int gpio_num; // GPIO pin number // Function to calculate duty cycle based on the desired angle float calculateDutyCycle(int angle) { // Pulse width range from 0.5ms (0 degrees) to 2.5ms (270 degrees) float min_pulse_width = 0.5; // Minimum pulse width in milliseconds float max_pulse_width = 2.5; // Maximum pulse width in milliseconds float frequency = 50.0; // Frequency in Hz float period = 1000.0 / frequency; // Period in milliseconds (20ms) // Ensure the angle is within the specified range if (angle < 75) angle = 75; if (angle > 195) angle = 195; // Calculate pulse width for the given angle float pulse_width = min_pulse_width + ((float)angle / 270.0) * (max_pulse_width - min_pulse_width); // Calculate duty cycle percentage float duty_cycle = (pulse_width / period) * 100.0; return duty_cycle; } // Helper function to get the correct MCPWM signal for GPIO initialization mcpwm_io_signals_t getSignal() { if (op_num == MCPWM_OPR_A) { if (timer_num == MCPWM_TIMER_0) return MCPWM0A; else if (timer_num == MCPWM_TIMER_1) return MCPWM1A; else return MCPWM2A; // MCPWM_TIMER_2 } else { if (timer_num == MCPWM_TIMER_0) return MCPWM0B; else if (timer_num == MCPWM_TIMER_1) return MCPWM1B; else return MCPWM2B; // MCPWM_TIMER_2 } } }; // --- Global Instances --- // Create an instance of IBusReceiver // For ESP32, Serial2 is typically used for UART communication IBusReceiver ibus(Serial2, 16); // Assuming RX is on GPIO 16 // Create an instance of the MyMotor class MyMotor motor(4, 15); // Use GPIO 4 for forward, GPIO 15 for reverse // Create instances of the MyServo class MyServo servo_wheel(MCPWM_UNIT_1, MCPWM_TIMER_0, MCPWM_OPR_A, 25); // Servo on GPIO25 MyServo servo_pan(MCPWM_UNIT_1, MCPWM_TIMER_1, MCPWM_OPR_A, 26); // Servo on GPIO26 MyServo servo_tilt(MCPWM_UNIT_1, MCPWM_TIMER_2, MCPWM_OPR_A, 27); // Servo on GPIO27 // Define GPIO pins for the relays const int propane_relay = 18; // GPIO 18 controls the propane relay const int arc_relay = 19; // GPIO 19 controls the arc relay int tiltMap(int input) { int output; // First linear relationship: from input 1060 to 1500, output 181 to 157 if (input <= 1500) { output = map(input, 1060, 1500, 181, 157); } // Second linear relationship: from input 1500 to 2000, output 157 to 100 else { output = map(input, 1500, 2000, 157, 100); } return output; } void setup() { Serial.begin(115200); // Initialize Serial Monitor ibus.begin(115200); // Initialize IBUS receiver with baud rate 115200 Serial.println("Starting Motor and Servo Control with iBUS Receiver..."); // Initialize relay control pins pinMode(propane_relay, OUTPUT); pinMode(arc_relay, OUTPUT); // Ensure both relays are OFF at startup digitalWrite(propane_relay, LOW); digitalWrite(arc_relay, LOW); // Optional: Give some time for serial ports to initialize delay(1000); } void loop() { // Read IBUS data ibus.readIBUSData(); // Process IBUS packet if available if (ibus.processIBUSPacket()) { // Retrieve the moveY, moveX, camX, camY, and switchC channel values int moveY = ibus.getMoveY(); int moveX = ibus.getMoveX(); int camX = ibus.getCamX(); int camY = ibus.getCamY(); int switchC = ibus.getSwitchC(); // Map moveY from 1000-2000 to -100 to 100 int motorSpeed = map(moveY, 1000, 2000, -100, 100); motor.setSpeed(motorSpeed); // Map moveX from 1000-2000 to 80-190 degrees int servo_wheel_angle = map(moveX, 1000, 2000, 190, 80); servo_wheel.setAngle(servo_wheel_angle); // Map camX and camY for pan and tilt servos int servo_pan_angle = map(camX, 1044, 1946, 215, 55); servo_pan.setAngle(servo_pan_angle); int servo_tilt_angle = tiltMap(camY); servo_tilt.setAngle(servo_tilt_angle); // Control relays based on switchC value if (switchC == 1000) { // Both relays OFF digitalWrite(propane_relay, LOW); digitalWrite(arc_relay, LOW); } else if (switchC == 1500) { // Propane relay ON, Arc relay OFF digitalWrite(propane_relay, HIGH); digitalWrite(arc_relay, LOW); } else if (switchC == 2000) { // Both relays ON digitalWrite(propane_relay, HIGH); digitalWrite(arc_relay, HIGH); } // Debugging output /* Serial.print("motorSpeed: "); Serial.print(motorSpeed); Serial.print(", servo_wheel_angle: "); Serial.print(servo_wheel_angle); Serial.print(", servo_pan_angle: "); Serial.print(servo_pan_angle); Serial.print(", servo_tilt_angle: "); Serial.print(servo_tilt_angle); Serial.print(", switchC: "); Serial.println(switchC); */ } }
-
6Thank you for making it this far
I goof around, but seriously, if there's anything you want to hear more about, I am happy to share. This project involved bunch of different things in a very short period of time. Here's a gratuitous nighttime shot:
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.