/************************************************************************** This is an example for our Monochrome OLEDs based on SSD1306 drivers Pick one up today in the adafruit shop! ------> http://www.adafruit.com/category/63_98 This example is for a 128x64 pixel display using I2C to communicate 3 pins are required to interface (two I2C and one reset). Adafruit invests time and resources providing this open source code, please support Adafruit and open-source hardware by purchasing products from Adafruit! Written by Limor Fried/Ladyada for Adafruit Industries, with contributions from the open source community. BSD license, check license.txt for more information All text above, and the splash screen below must be included in any redistribution. **************************************************************************/ #include #include #include #include #include #include "RP2040_PWM.h" #define SCREEN_WIDTH 128 // OLED display width, in pixels #define SCREEN_HEIGHT 64 // OLED display height, in pixels #define OLED_RESET -1 #define SCREEN_ADDRESS 0x3C #define SCL 21 #define SDA 20 #define STAT_LED_A 22 #define STAT_LED_B 23 #define STAT_LED_C 17 #define ROT_A 8 #define ROT_B 9 #define WAVE_X1 5 #define WAVE_Y1 25 #define WAVE_X2 123 #define WAVE_Y2 48 #define IN1 26 #define IN2 22 #define ENA 27 #define MOT_ENABLE 23 #define BTN 7 #define EEPROM_WRITE_OFFSET 0x0 bool stat_led_a = 0; bool stat_led_b = 0; bool stat_led_c = 0; float pwm_freq = 20000.0; int8_t pwr = 0; uint8_t pwr_norm = 0; float dc_norm = 0; bool fwd = true; struct TuningParam { float duty_cycle; float freq; bool operator==(TuningParam that) { return this->duty_cycle == that.duty_cycle && this->freq == that.freq; } bool operator!=(TuningParam that) { return !(*this == that); } }; enum DisplayState { RUN, MAP_WAIT, MAP_WRITE }; DisplayState state = RUN; // display uint32_t refresh_timer_ms = 0; const uint32_t refresh_timeout_ms = 33; // rotary encoder polling const uint8_t rot_a_int = digitalPinToInterrupt(ROT_A); const uint8_t rot_b_int = digitalPinToInterrupt(ROT_B); int8_t renc_change = 1; uint32_t poll_timer_ms = 0; const uint32_t poll_timeout_ms = 2; bool poll_wait = false; bool poll_wait_start = false; // tuning map TuningParam tuning_map[100]; bool tuning_map_set = false; // serial read timeout uint32_t serial_timer_ms = 0; const uint32_t serial_timeout_ms = 2000; Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); RP2040_PWM* PWM_Instance; void setup() { Wire.setSCL(SCL); Wire.setSDA(SDA); pinMode(STAT_LED_A, OUTPUT); pinMode(STAT_LED_B, OUTPUT); pinMode(STAT_LED_C, OUTPUT); pinMode(ROT_A, INPUT_PULLUP); pinMode(ROT_B, INPUT_PULLUP); pinMode(MOT_ENABLE, INPUT_PULLUP); pinMode(BTN, INPUT_PULLUP); pinMode(IN1, OUTPUT); pinMode(IN2, OUTPUT); delay(500); if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) { digitalWrite(STAT_LED_C, HIGH); } display.clearDisplay(); display.setTextColor(SSD1306_WHITE); display.setTextSize(2); display.write("initializing..."); display.display(); delay(500); Serial.begin(); delay(500); if (!digitalRead(BTN)) { if (!Serial) { display.setTextSize(1); display.setCursor(5, 5); display.print("Waiting for serial connection..."); display.display(); } bool pix_on = 1; while (!Serial) { delay(100); stat_led_c = !(stat_led_c); digitalWrite(STAT_LED_C, stat_led_c); } display.display(); } Serial.println("start"); arm_rotary_encoder(); EEPROM.begin(1000); tuning_map_set = load_tuning_map(); PWM_Instance = new RP2040_PWM(ENA, pwm_freq, dc_norm); } void loop() { uint32_t now = millis(); if (poll_wait_start && !poll_wait) { poll_timer_ms = now; poll_wait = true; poll_wait_start = false; } uint32_t last_poll_delta = now - poll_timer_ms; if (poll_wait && last_poll_delta >= poll_timeout_ms) { // a sum of 2 means the encoder has settled uint8_t pin_sum = digitalRead(ROT_A) + digitalRead(ROT_B); if (pin_sum == 2) { uint8_t vel_mult = 1; if (last_poll_delta < poll_timeout_ms * 2) vel_mult = 2; pwr += renc_change * vel_mult; // nested because we don't need to check this until ready for next poll poll_wait = false; arm_rotary_encoder(); } } if (Serial.available()) { char cmd = Serial.read(); if (state == MAP_WAIT) { switch (cmd) { case 'R': { clear_buffer(); state = RUN; arm_rotary_encoder(); Serial.println("returning"); display.clearDisplay(); break; } case 'C': { tuning_map_set = false; Serial.println("disabled tuning map"); break; } case 'E': { tuning_map_set = true; Serial.println("enabled tuning map"); break; } case 'S': { Serial.println("saving"); save_tuning_map(); Serial.println("saved"); break; } case 'L': { Serial.println("loading"); if (load_tuning_map()) { Serial.println("loaded"); } else { Serial.println("no tuning map present"); } break; } case 'D': { size_t idx = Serial.parseInt(); if (idx > 99) break; tuning_map[idx].duty_cycle = Serial.parseFloat(); tuning_map[idx].freq = Serial.parseFloat(); display.drawPixel(12 + idx, 30, SSD1306_WHITE); display.display(); Serial.println("ok"); break; } default: { clear_buffer(); Serial.println("invalid command"); break; } } } else { switch (cmd) { case 'M': { state = MAP_WAIT; disarm_rotary_encoder(); display.clearDisplay(); display.setTextSize(1); display.setCursor(5, 5); display.print("receiving tuning map..."); display.display(); Serial.println("ready"); break; } default: { clear_buffer(); Serial.println("invalid command"); break; } } } } switch (state) { case RUN: stat_led_c = !(stat_led_c); digitalWrite(STAT_LED_C, stat_led_c); if (now - refresh_timer_ms >= refresh_timeout_ms) { display.setTextSize(2); if (!tuning_map_set) { PWM_Instance->setPWM(ENA, pwm_freq, 0); display.clearDisplay(); display.setTextSize(1); display.setCursor(5, 5); display.print("critical: no tuning map!"); display.display(); return; } fwd = pwr >= 0; pwr_norm = abs(pwr); if (pwr_norm == 0) { dc_norm = 0; pwm_freq = 0; } else { dc_norm = tuning_map[pwr_norm].duty_cycle; pwm_freq = tuning_map[pwr_norm].freq; } bool pwm_on = !digitalRead(MOT_ENABLE) && pwr_norm != 0; // since it's input_pullup display.clearDisplay(); if (digitalRead(ROT_A)) display.drawPixel(0, 0, SSD1306_WHITE); if (digitalRead(ROT_B)) display.drawPixel(2, 0, SSD1306_WHITE); if (digitalRead(BTN)) display.drawPixel(4, 0, SSD1306_WHITE); // draw text display.setCursor(2, 2); display.print("pwr"); if (dc_norm > 0 && pwm_on) { // draw number display.setCursor(48, 2); display.print(pwr_norm); display.print('%'); // draw fwd/rev display.setCursor(90, 2); if (fwd) display.print("FWD"); else display.print("REV"); } else { display.setCursor(72, 2); display.print("OFF"); } // draw wave uint16_t peak_edge_x = WAVE_X1 + dc_norm; display.drawLine(WAVE_X1, WAVE_Y1, peak_edge_x, WAVE_Y1, SSD1306_WHITE); display.drawLine(peak_edge_x, WAVE_Y1, peak_edge_x, WAVE_Y2, SSD1306_WHITE); display.drawLine(peak_edge_x, WAVE_Y2, WAVE_X2, WAVE_Y2, SSD1306_WHITE); // draw freq display.setTextSize(1); display.setCursor(5, 50); display.print(pwm_freq); display.print("Hz"); // draw duty cycle display.setTextSize(1); display.setCursor(75, 50); display.print(dc_norm); display.print("%"); display.display(); // drive motor if (pwm_on) { if (fwd) { digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW); } else { digitalWrite(IN1, LOW); digitalWrite(IN2, HIGH); } PWM_Instance->setPWM(ENA, pwm_freq, dc_norm); } else { digitalWrite(IN1, LOW); digitalWrite(IN2, LOW); PWM_Instance->setPWM(ENA, pwm_freq, 0); } refresh_timer_ms = now; } break; case MAP_WAIT: break; } } // cw interrupt void cw_b_isr() { disarm_rotary_encoder(); attachInterrupt(rot_a_int, cw_a_isr, FALLING); } void cw_a_isr() { detachInterrupt(rot_a_int); if (pwr < 100) renc_change = 1; poll_wait_start = true; } // ccw interrupt void ccw_a_isr() { disarm_rotary_encoder(); attachInterrupt(rot_b_int, ccw_b_isr, FALLING); } void ccw_b_isr() { detachInterrupt(rot_b_int); if (pwr > -100) renc_change = -1; poll_wait_start = true; } void clear_buffer() { while (Serial.available()) { Serial.read(); } } void save_tuning_map() { bool tuning_map_present; EEPROM.get(EEPROM_WRITE_OFFSET, tuning_map_present); if (!tuning_map_present) { EEPROM.put(EEPROM_WRITE_OFFSET, true); } size_t offset = EEPROM_WRITE_OFFSET + sizeof(bool); for (size_t i = 0; i < 100; i++) { TuningParam param = tuning_map[i]; TuningParam existing_param; EEPROM.get(offset, existing_param); if (existing_param != param) { EEPROM.put(offset, tuning_map[i]); } offset += sizeof(TuningParam); } EEPROM.commit(); } int load_tuning_map() { bool tuning_map_present; EEPROM.get(EEPROM_WRITE_OFFSET, tuning_map_present); if (tuning_map_present) { size_t offset = EEPROM_WRITE_OFFSET + sizeof(bool); for (size_t i = 0; i < 100; i++) { TuningParam param; EEPROM.get(offset, param); tuning_map[i] = param; offset += sizeof(TuningParam); } return 1; } else { return 0; } } void arm_rotary_encoder() { attachInterrupt(rot_b_int, cw_b_isr, FALLING); attachInterrupt(rot_a_int, ccw_a_isr, FALLING); } void disarm_rotary_encoder() { detachInterrupt(rot_a_int); detachInterrupt(rot_b_int); }