|
| 1 | +/** |
| 2 | + * @file LoadDemo.ino |
| 3 | + * @brief Manually control the actuator |
| 4 | + * @version 0.2.1 |
| 5 | + * @since Winter 2026 |
| 6 | + * @author Noah (@BobSaidHi <https://github.com/bobsaidhi>) for |
| 7 | + * @CalPolyWindPower <https://github.com/calpolywindpower> |
| 8 | + * @author Inspired by Trevor (@rover-t <https://github.com/rover-t>) at |
| 9 | + * @CalPolyWindPower <https://github.com/calpolywindpower> |
| 10 | + * |
| 11 | + * The Unexpected Maker ProS3[D] has one USB C port. The baud rate is set |
| 12 | + * to to 115,000 below and uses the internal USB peripheral, not a separate |
| 13 | + * chip. Some compatible serial monitors include the Arduino IDE |
| 14 | + * <https://www.arduino.cc/en/software/>, VSCode "Serial" extension from |
| 15 | + * Microsoft |
| 16 | + * <https://marketplace.visualstudio.com/items?itemName=ms-vscode.vscode-serial-monitor>,or |
| 17 | + * PuTTY. |
| 18 | + * |
| 19 | + * To re-flash: |
| 20 | + * 1. Install Arduino IDE (I recommend >v2.0) from |
| 21 | + * https://www.arduino.cc/en/software/ if you don't have it. |
| 22 | + * 2. Go to the boards manager in the left sidebar and install |
| 23 | + * "esp32" by Espressif Systems. |
| 24 | + * A. If it's missing, go to the following board URL under |
| 25 | + * File > Preferences > Settings > |
| 26 | + * Additional Board Manager URLS: |
| 27 | + * `https://espressif.github.io/arduino-esp32/package_esp32_index.json` |
| 28 | + * (See also: |
| 29 | + * https://docs.espressif.com/projects/arduino-esp32/en/latest/installing.html) |
| 30 | + * 3. Load the attached sketch or the latest version from GitHub: |
| 31 | + * https://github.com/CalPolyWindPower/2026-Controls_Load/blob/main/demos/LoadDemo/LoadDemo.ino |
| 32 | + * 4. Set the board to "UM PROS3". It may show up as "ESP32 Family Device at first" or even multiple devices (Select the first one). |
| 33 | + * 5. Under Tools, set "USB CDC On Boot" to "Enabled" |
| 34 | + * 6. [Recommended] Under Tools, set "Core Debug Level" to "Info" |
| 35 | + * 7. Select Upload |
| 36 | + * 8. Open a serial terminal |
| 37 | + * A. In Arduino IDE, got Tools > Serial Monitor and switch to 115200 baud |
| 38 | + * |
| 39 | + * @author Noah (@BobSaidHi <https://github.com/bobsaidhi>) for Cal Poly Wind |
| 40 | + * Power (@calpolywindpower <https://github.com/calpolywindpower>) |
| 41 | + * @see Inspired by |
| 42 | + * https://github.com/rover-t/CPWP2024-Controls/blob/main/ControlCode/OpenLoopController/OpenLoopController.ino |
| 43 | + * @see Inspired by |
| 44 | + * https://github.com/rover-t/CPWP2024-Controls/blob/main/ControlCode/OpenLoopController_alt/OpenLoopController_alt.ino |
| 45 | + * @see Based on https://github.com/CalPolyWindPower/2026-Controls_Nacelle/blob/main/demos/ActuatorDemo/ActuatorDemo.ino |
| 46 | + */ |
| 47 | + |
| 48 | +// MARK: Imports |
| 49 | +#include <Arduino.h> |
| 50 | +#include <cstdint> // Fixed size integers |
| 51 | +#include <Wire.h> |
| 52 | +#include "./src/MCP23008T/MCP23008T.hpp" |
| 53 | + |
| 54 | +// MARK: Globals |
| 55 | + |
| 56 | +/** |
| 57 | + * @brief Configuration for serial (UART) communication |
| 58 | + * @details A namespace can be used to organize related constants, variables, |
| 59 | + * and objects together. |
| 60 | + * @see https://www.geeksforgeeks.org/cpp/namespace-in-c/ |
| 61 | + * @details `constexpr` is similar to `#define` macros but type checked. Macros |
| 62 | + * and `constexpr` variables are guaranteed and to be evaluated at compile |
| 63 | + * time, and may even be embedded in the immediate field of an instruction, |
| 64 | + * which means they do not take up additional storage space, memory, or |
| 65 | + * registers at turn time. |
| 66 | + * @see https://en.cppreference.com/w/cpp/language/constexpr.html |
| 67 | + * @see https://www.sciencedirect.com/topics/computer-science/immediate-operand |
| 68 | + */ |
| 69 | +namespace SERIAL_CONFIG { |
| 70 | + constexpr long BAUD = 115200; // BAUD rate (bits per second) |
| 71 | +} // namespace SERIAL_CONFIG |
| 72 | + |
| 73 | +/** |
| 74 | + * @brief Configuration for the load and related pins |
| 75 | + * @details The standard integer types, such as int, only specify a minimum |
| 76 | + * size. FEEDBACK_PIN is of the fixed width unsigned integer type `uint8_t`, |
| 77 | + * which is always 8 bits. This was chosen because the `pinmode()` function |
| 78 | + * takes an `uint8_t` as an argument. |
| 79 | + */ |
| 80 | +namespace LOAD { |
| 81 | + /** |
| 82 | + * Valid addresses are 0x40, 0x42, 0x44, 0x46, 0x48, 0x4A, 0x4C, & 0x4E |
| 83 | + */ |
| 84 | + constexpr MCP23008T::I2C_ADDRESS I2C_ADDRESS = MCP23008T::I2C_ADDRESS::I2C_ADDR_0x27; // A0-A2 all high |
| 85 | + |
| 86 | + /** |
| 87 | + * @brief Bitmask for the load control pins. |
| 88 | + * @details Only the first 6 pins (0-5) are used for load control. |
| 89 | + */ |
| 90 | + constexpr uint8_t PINS_Msk = 0b0011'1111; |
| 91 | + |
| 92 | + MCP23008T loadDevice(I2C_ADDRESS, |
| 93 | + &Wire); |
| 94 | + |
| 95 | + bool configureLoad() { |
| 96 | + // Check all addr. |
| 97 | + // for(MCP23008T::I2C_ADDRESS addr = MCP23008T::I2C_ADDRESS::I2C_ADDR_0x40; addr <= MCP23008T::I2C_ADDRESS::I2C_ADDR_0x4E; addr = (MCP23008T::I2C_ADDRESS)(addr + 2)) { |
| 98 | + // MCP23008T tempLoad(addr, &Wire); |
| 99 | + // for(uint8_t addr = 0; addr <= 0b0111'1111; addr++) { |
| 100 | + // MCP23008T tempLoad(addr, &Wire); |
| 101 | + // if (!tempLoad.begin()) { |
| 102 | + // ESP_LOGE("cL", "Failed to initialize MCP23008T device at 0x%02X", |
| 103 | + // addr); |
| 104 | + // } else { |
| 105 | + // ESP_LOGI("cL", "Sucess init MCP23008T device at 0x%02X", |
| 106 | + // addr); |
| 107 | + // } |
| 108 | + // } |
| 109 | + |
| 110 | + |
| 111 | + if (!loadDevice.begin()) { |
| 112 | + ESP_LOGE("cL", "Failed to initialize MCP23008T device at 0x%02X", |
| 113 | + loadDevice.getAddress()); |
| 114 | + } |
| 115 | + |
| 116 | + // First six pins outputs, made last two inputs as that's the default |
| 117 | + if (!loadDevice.setIODir(0b1100'0000)) { |
| 118 | + ESP_LOGE("cL", "Failed to set IODIR on MCP23008T at 0x%02X", |
| 119 | + loadDevice.getAddress()); |
| 120 | + return false; |
| 121 | + } |
| 122 | + |
| 123 | + // Disable sequential operation, other settings default |
| 124 | + if (!loadDevice.setConfig(0b0010'0000)) { |
| 125 | + ESP_LOGW("cL", "Failed to set IOCON on MCP23008T at 0x%02X", |
| 126 | + loadDevice.getAddress()); |
| 127 | + } |
| 128 | + |
| 129 | + // Set all outputs low, note that pin 0 is inverted |
| 130 | + if (!loadDevice.setGPIO(0b0000'0000)) { |
| 131 | + ESP_LOGE("cL", "Failed to set GPIO on MCP23008T at 0x%02X", |
| 132 | + loadDevice.getAddress()); |
| 133 | + return false; |
| 134 | + } |
| 135 | + |
| 136 | + // Else: success |
| 137 | + return true; |
| 138 | + } |
| 139 | +} // namespace LOAD |
| 140 | + |
| 141 | +/** |
| 142 | + * @brief Configuration for the LED and related pins |
| 143 | + * @details As we have more program storage space, and hopefully more RAM than |
| 144 | + * we need, the slight performance boost from the fast integer types, such ast |
| 145 | + * uint_fast32_t, are likely worth it for us. These types specify a minium |
| 146 | + * size, but allow the compiler (actually the system/ standard libraries) to |
| 147 | + * substitute a type that a particular chip (micro-architecture) is more |
| 148 | + * optimized for. |
| 149 | + */ |
| 150 | +// namespace LED { |
| 151 | +// constexpr uint8_t PIN = 15; // LED pin |
| 152 | +// constexpr uint_fast32_t TIME_ON_MS = |
| 153 | +// 1000; // Time LED stays on in milliseconds |
| 154 | +// constexpr uint_fast32_t TIME_OFF_MS = |
| 155 | +// 3000; // Time LED stays off in milliseconds |
| 156 | +// } // namespace LED |
| 157 | + |
| 158 | +/** |
| 159 | + * @details put your setup code here, to run once: |
| 160 | + */ |
| 161 | +void setup() { |
| 162 | + Serial.begin(SERIAL_CONFIG::BAUD); // Start serial |
| 163 | + |
| 164 | + // The DFRobot FireBeetle 2 ESP32-C5 has one user controllable LED built in |
| 165 | + // #if defined(ESP32) |
| 166 | + // /** |
| 167 | + // * @see |
| 168 | + // * https://docs.arduino.cc/language-reference/en/functions/digital-io/pinMode/ |
| 169 | + // */ |
| 170 | + // pinMode(LED::PIN, OUTPUT); // Setup LED |
| 171 | + // digitalWrite(LED::PIN, HIGH); // Toggle LED |
| 172 | + |
| 173 | + // // Debug actuator pin |
| 174 | + // // pinMode(Actuator::CONTROL_PIN, OUTPUT); // Setup LED |
| 175 | + // // digitalWrite(Actuator::CONTROL_PIN, HIGH); // Toggle LED |
| 176 | + // // while(1) {} |
| 177 | + // #endif |
| 178 | + |
| 179 | + /** |
| 180 | + * @details The load object, is in the Actuator namespace |
| 181 | + */ |
| 182 | + Wire.begin(8, 9); // SDA, SCL |
| 183 | + LOAD::configureLoad(); |
| 184 | + LOAD::loadDevice.setGPIO(0b0000'0000); |
| 185 | + Serial.print("Input load setPoint as a 7 bit unsigned integer: "); |
| 186 | +} |
| 187 | + |
| 188 | +/** |
| 189 | + * @details put your main code here, to run repeatedly: |
| 190 | + */ |
| 191 | +void loop() { |
| 192 | + // Check if Serial data is available |
| 193 | + if (Serial.available() > 0) { |
| 194 | + int setPoint = Serial.parseInt(); |
| 195 | + |
| 196 | + // It would appear parseInt returns 0 on failure |
| 197 | + if (setPoint != 0) { |
| 198 | + // Check and write position |
| 199 | + if (setPoint < 0) { |
| 200 | + Serial.print("\nsetPoint too small: "); |
| 201 | + Serial.println(setPoint); |
| 202 | + } else if ((setPoint > LOAD::PINS_Msk)) { |
| 203 | + Serial.print("\nsetPoint too large: "); |
| 204 | + Serial.println(setPoint); |
| 205 | + } else { |
| 206 | + Serial.print("\nNew setPoint: "); |
| 207 | + Serial.println(setPoint); |
| 208 | + LOAD::loadDevice.setGPIO(0b0000'0000); |
| 209 | + } |
| 210 | + |
| 211 | + Serial.print("Input load setPoint as a bitset: "); |
| 212 | + } |
| 213 | + } |
| 214 | + |
| 215 | +// #if defined(ESP32) |
| 216 | +// // Toggle LED because why not, but only on the EPS32 |
| 217 | +// static int lastLEDTime_ms = 0; // Track time toggled |
| 218 | +// static bool LEDOn = false; // Track LED state |
| 219 | + |
| 220 | +// // Check LED state and update |
| 221 | +// if (!LEDOn && millis() - lastLEDTime_ms > LED::TIME_OFF_MS) { |
| 222 | +// digitalWrite(LED::PIN, HIGH); // Toggle LED |
| 223 | +// lastLEDTime_ms = millis(); // Save time |
| 224 | +// LEDOn = true; |
| 225 | +// } else if (LEDOn && millis() - lastLEDTime_ms > LED::TIME_ON_MS) { |
| 226 | +// digitalWrite(LED::PIN, LOW); // Toggle LED |
| 227 | +// lastLEDTime_ms = millis(); // Save time |
| 228 | +// LEDOn = false; |
| 229 | +// } |
| 230 | +// #endif |
| 231 | +} |
| 232 | + |
0 commit comments