initial commit

This commit is contained in:
skybldev
2025-11-17 13:42:59 -05:00
commit dd98d4061f
6 changed files with 610 additions and 0 deletions

1
bezierjs Submodule

Submodule bezierjs added at 4e76729918

470
dc_trackctl.ino Normal file
View File

@@ -0,0 +1,470 @@
/**************************************************************************
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 <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <SPI.h>
#include <Wire.h>
#include <EEPROM.h>
#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);
}

34
map_gen.ts Normal file
View File

@@ -0,0 +1,34 @@
import { Bezier } from "./bezierjs/src/bezier.js";
function map (a: number, a_min: number, a_max: number, b_min: number, b_max: number): number {
return b_min + ((b_max - b_min) / (a_max - a_min)) * (a - a_min);
}
function fmt (x: number) {
return x.toLocaleString("en-US", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
useGrouping: false
});
}
function duty_cycle_map (x: number) {
//const out = Math.log10(x + 1) * 50;
//const out = x;
x = x / 100;
//let y = 1 - Math.pow(1 - x, 5);
let y = x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2;
y = map(y, 0, 1, 30, 100);
return fmt(y);
}
function freq_map (x: number) {
x = x / 100;
let y = 1 - (1 - x) * (1 - x);
y = map(y, 0, 1, 20000, 25000);
return fmt(y);
}
const vals = [...Array(100).keys()].map((x) => x + 1).map((x) => "D" + (x - 1) + " " + duty_cycle_map(x) + " " + freq_map(x)).join("");
console.log("M" + vals + "R");

1
tmap_1to1 Normal file
View File

@@ -0,0 +1 @@
MD0 0.000 200.000D1 1.000 200.000D2 2.000 200.000D3 3.000 200.000D4 4.000 200.000D5 5.000 200.000D6 6.000 200.000D7 7.000 200.000D8 8.000 200.000D9 9.000 200.000D10 10.000 200.000D11 11.000 200.000D12 12.000 200.000D13 13.000 200.000D14 14.000 200.000D15 15.000 200.000D16 16.000 200.000D17 17.000 200.000D18 18.000 200.000D19 19.000 200.000D20 20.000 200.000D21 21.000 200.000D22 22.000 200.000D23 23.000 200.000D24 24.000 200.000D25 25.000 200.000D26 26.000 200.000D27 27.000 200.000D28 28.000 200.000D29 29.000 200.000D30 30.000 200.000D31 31.000 200.000D32 32.000 200.000D33 33.000 200.000D34 34.000 200.000D35 35.000 200.000D36 36.000 200.000D37 37.000 200.000D38 38.000 200.000D39 39.000 200.000D40 40.000 200.000D41 41.000 200.000D42 42.000 200.000D43 43.000 200.000D44 44.000 200.000D45 45.000 200.000D46 46.000 200.000D47 47.000 200.000D48 48.000 200.000D49 49.000 200.000D50 50.000 200.000D51 51.000 200.000D52 52.000 200.000D53 53.000 200.000D54 54.000 200.000D55 55.000 200.000D56 56.000 200.000D57 57.000 200.000D58 58.000 200.000D59 59.000 200.000D60 60.000 200.000D61 61.000 200.000D62 62.000 200.000D63 63.000 200.000D64 64.000 200.000D65 65.000 200.000D66 66.000 200.000D67 67.000 200.000D68 68.000 200.000D69 69.000 200.000D70 70.000 200.000D71 71.000 200.000D72 72.000 200.000D73 73.000 200.000D74 74.000 200.000D75 75.000 200.000D76 76.000 200.000D77 77.000 200.000D78 78.000 200.000D79 79.000 200.000D80 80.000 200.000D81 81.000 200.000D82 82.000 200.000D83 83.000 200.000D84 84.000 200.000D85 85.000 200.000D86 86.000 200.000D87 87.000 200.000D88 88.000 200.000D89 89.000 200.000D90 90.000 200.000D91 91.000 200.000D92 92.000 200.000D93 93.000 200.000D94 94.000 200.000D95 95.000 200.000D96 96.000 200.000D97 97.000 200.000D98 98.000 200.000D99 99.000 200.000D100 100.000 200.000R

1
tmap_log50 Normal file
View File

@@ -0,0 +1 @@
MD0 0.000 20000.000D1 15.051 20000.000D2 23.856 20000.000D3 30.103 20000.000D4 34.949 20000.000D5 38.908 20000.000D6 42.255 20000.000D7 45.154 20000.000D8 47.712 20000.000D9 50.000 20000.000D10 52.070 20000.000D11 53.959 20000.000D12 55.697 20000.000D13 57.306 20000.000D14 58.805 20000.000D15 60.206 20000.000D16 61.522 20000.000D17 62.764 20000.000D18 63.938 20000.000D19 65.051 20000.000D20 66.111 20000.000D21 67.121 20000.000D22 68.086 20000.000D23 69.011 20000.000D24 69.897 20000.000D25 70.749 20000.000D26 71.568 20000.000D27 72.358 20000.000D28 73.120 20000.000D29 73.856 20000.000D30 74.568 20000.000D31 75.257 20000.000D32 75.926 20000.000D33 76.574 20000.000D34 77.203 20000.000D35 77.815 20000.000D36 78.410 20000.000D37 78.989 20000.000D38 79.553 20000.000D39 80.103 20000.000D40 80.639 20000.000D41 81.162 20000.000D42 81.673 20000.000D43 82.173 20000.000D44 82.661 20000.000D45 83.138 20000.000D46 83.605 20000.000D47 84.062 20000.000D48 84.510 20000.000D49 84.949 20000.000D50 85.379 20000.000D51 85.800 20000.000D52 86.214 20000.000D53 86.620 20000.000D54 87.018 20000.000D55 87.409 20000.000D56 87.794 20000.000D57 88.171 20000.000D58 88.543 20000.000D59 88.908 20000.000D60 89.266 20000.000D61 89.620 20000.000D62 89.967 20000.000D63 90.309 20000.000D64 90.646 20000.000D65 90.977 20000.000D66 91.304 20000.000D67 91.625 20000.000D68 91.942 20000.000D69 92.255 20000.000D70 92.563 20000.000D71 92.867 20000.000D72 93.166 20000.000D73 93.462 20000.000D74 93.753 20000.000D75 94.041 20000.000D76 94.325 20000.000D77 94.605 20000.000D78 94.881 20000.000D79 95.154 20000.000D80 95.424 20000.000D81 95.691 20000.000D82 95.954 20000.000D83 96.214 20000.000D84 96.471 20000.000D85 96.725 20000.000D86 96.976 20000.000D87 97.224 20000.000D88 97.470 20000.000D89 97.712 20000.000D90 97.952 20000.000D91 98.189 20000.000D92 98.424 20000.000D93 98.656 20000.000D94 98.886 20000.000D95 99.114 20000.000D96 99.339 20000.000D97 99.561 20000.000D98 99.782 20000.000D99 100.000 20000.000D100 100.216 20000.000R

103
wl_copy Normal file
View File

@@ -0,0 +1,103 @@
M
0.000,20000.000
1.000,20000.000
2.000,20000.000
3.000,20000.000
4.000,20000.000
5.000,20000.000
6.000,20000.000
7.000,20000.000
8.000,20000.000
9.000,20000.000
10.000,20000.000
11.000,20000.000
12.000,20000.000
13.000,20000.000
14.000,20000.000
15.000,20000.000
16.000,20000.000
17.000,20000.000
18.000,20000.000
19.000,20000.000
20.000,20000.000
21.000,20000.000
22.000,20000.000
23.000,20000.000
24.000,20000.000
25.000,20000.000
26.000,20000.000
27.000,20000.000
28.000,20000.000
29.000,20000.000
30.000,20000.000
31.000,20000.000
32.000,20000.000
33.000,20000.000
34.000,20000.000
35.000,20000.000
36.000,20000.000
37.000,20000.000
38.000,20000.000
39.000,20000.000
40.000,20000.000
41.000,20000.000
42.000,20000.000
43.000,20000.000
44.000,20000.000
45.000,20000.000
46.000,20000.000
47.000,20000.000
48.000,20000.000
49.000,20000.000
50.000,20000.000
51.000,20000.000
52.000,20000.000
53.000,20000.000
54.000,20000.000
55.000,20000.000
56.000,20000.000
57.000,20000.000
58.000,20000.000
59.000,20000.000
60.000,20000.000
61.000,20000.000
62.000,20000.000
63.000,20000.000
64.000,20000.000
65.000,20000.000
66.000,20000.000
67.000,20000.000
68.000,20000.000
69.000,20000.000
70.000,20000.000
71.000,20000.000
72.000,20000.000
73.000,20000.000
74.000,20000.000
75.000,20000.000
76.000,20000.000
77.000,20000.000
78.000,20000.000
79.000,20000.000
80.000,20000.000
81.000,20000.000
82.000,20000.000
83.000,20000.000
84.000,20000.000
85.000,20000.000
86.000,20000.000
87.000,20000.000
88.000,20000.000
89.000,20000.000
90.000,20000.000
91.000,20000.000
92.000,20000.000
93.000,20000.000
94.000,20000.000
95.000,20000.000
96.000,20000.000
97.000,20000.000
98.000,20000.000
99.000,20000.000
100.000,20000.000
R