From 226f2a3e3ffa81eab5c5004679f81175b5106697 Mon Sep 17 00:00:00 2001 From: damage Date: Sat, 17 May 2025 13:08:17 +0200 Subject: [PATCH] display control --- master/global.h | 9 +++ master/master.ino | 82 ++++++++++++------- rotator/global.h | 1 + rotator/rotator.ino | 188 ++++++++++++++++++++++++++++++++++++-------- 4 files changed, 219 insertions(+), 61 deletions(-) create mode 100644 master/global.h create mode 120000 rotator/global.h diff --git a/master/global.h b/master/global.h new file mode 100644 index 0000000..5be3f92 --- /dev/null +++ b/master/global.h @@ -0,0 +1,9 @@ +#define DATA_STOP_BYTE 0x00 +#define DATA_INIT_BYTE_1 0x01 +#define DATA_INIT_BYTE_2 0x02 +#define DATA_RESET_BYTE 0xFF +#define TRIGGER_BIT_1 1 +#define HIGH_BIT_1 2 +#define TRIGGER_BIT_2 4 +#define HIGH_BIT_2 8 +#define I2C_ADDRESS_START 0x08 diff --git a/master/master.ino b/master/master.ino index a6ce98f..89214e8 100644 --- a/master/master.ino +++ b/master/master.ino @@ -1,38 +1,39 @@ #include #include #include + #include "config.h" +#include "global.h" #define READ_BYTES_FROM_WIRE 1 -// needs global define -#define DATA_STOP_BYTE 0x00 -#define DATA_RESET_BYTE 0xFF -#define HEADING_TRIGGER_BIT 1 -#define HEADING_HIGH_BIT 2 -#define ALTITUDE_TRIGGER_BIT 4 -#define ALTITUDE_HIGH_BIT 8 - #define HEADING_SIM_UP "sim/autopilot/heading_up" #define HEADING_SIM_DOWN "sim/autopilot/heading_down" #define ALTITUDE_SIM_UP "sim/autopilot/altitude_up" #define ALTITUDE_SIM_DOWN "sim/autopilot/altitude_down" -#define AIRPSEED_SIM_UP "sim/autopilot/airspeed_up" +#define AIRSPEED_SIM_UP "sim/autopilot/airspeed_up" #define AIRSPEED_SIM_DOWN "sim/autopilot/airspeed_down" #define MQTT_SIM_COMMAND_TOPIC "/xplane/meta/cmnd" #define MQTT_SIM_DEVICE_TOPIC "/xplane/meta/device" +#define MQTT_SIM_LOG_TOPIC "/xplane/meta/log" #define MQTT_SIM_VALUE_TOPIC "/xplane/rref/#" #define MQTT_KEEPALIVE_INTERVAL_MS 15000 struct device { - byte address; - String name; + byte address; // I2C address of device + char *name; // name of this device + char name1[3]; // first name to send on init of device + char *highCommand1; // command to use if TRIGGER_BIT_1 is set and HIGH_BIT_1 is set + char *lowCommand1; // command to use if TRIGGER_BIT_1 is set and HIGH_BIT_1 is not set + char name2[3]; // second name to send on init of device + char *highCommand2; // command to use if TRIGGER_BIT_2 is set and HIGH_BIT_2 is set + char *lowCommand2; // command to use if TRIGGER_BIT_2 is set and HIGH_BIT_2 is not set }; struct device devices[] = { - {0x00, "althead"} + { 0x08, "Heading Speed", {'H', 'D', 'G'}, HEADING_SIM_UP, HEADING_SIM_DOWN, {'S', 'P', 'D'}, AIRSPEED_SIM_UP, AIRSPEED_SIM_DOWN } }; int numDevices = -1; @@ -65,12 +66,28 @@ void onMqttMessage(int messageSize) { // Serial.println(); } -void sendMqttMessage(char *topic, char *message) { +void sendMqttMessage(char *topic, int n, ...) { mqttClient.beginMessage(topic); - mqttClient.print(message); + va_list args; + va_start(args, n); + for (int i = 0; i < n; i++) { + mqttClient.print(va_arg(args, char*)); + } + va_end(args); mqttClient.endMessage(); } +void sendI2CInit(struct device d) { + sendMqttMessage(MQTT_SIM_LOG_TOPIC, 2, "Sending init to ", d.name); + Wire.beginTransmission(d.address); + Wire.write(DATA_RESET_BYTE); + Wire.write(DATA_INIT_BYTE_1); + Wire.write(d.name1, 3); + Wire.write(DATA_INIT_BYTE_2); + Wire.write(d.name2, 3); + Wire.endTransmission(d.address); +} + void setup() { // count devices for loop numDevices = sizeof(devices) / sizeof(device); @@ -96,7 +113,7 @@ void setup() { while (1); } else { Serial.println("MQTT connected"); - sendMqttMessage(MQTT_SIM_DEVICE_TOPIC, "Master online"); + sendMqttMessage(MQTT_SIM_DEVICE_TOPIC, 1, "Master online"); } // subscribe to MQTT topic @@ -115,9 +132,7 @@ void setup() { // initialize devices for (int i = 0; i < numDevices; i++) { - Wire.beginTransmission(devices[i].address); - Wire.write(DATA_RESET_BYTE); - Wire.endTransmission(devices[i].address); + sendI2CInit(devices[i]); } } @@ -129,31 +144,40 @@ void loop() { // device has data, ask as long for data as it sends // if it never stops sending, we are fucked :) byte data = Wire.read(); - if (data != DATA_STOP_BYTE) { - if (data & ALTITUDE_TRIGGER_BIT) { - if (data & ALTITUDE_HIGH_BIT) { - sendMqttMessage(MQTT_SIM_COMMAND_TOPIC, ALTITUDE_SIM_UP); + if (data == DATA_STOP_BYTE) { + // do not request further data + continue; + } else if (data == DATA_RESET_BYTE) { + // device needs initialization + sendMqttMessage(MQTT_SIM_LOG_TOPIC, 2, "got reset byte from ", devices[i].name); + sendI2CInit(devices[i]); + } else { + // handle as payload + if (data & TRIGGER_BIT_1) { + if (data & HIGH_BIT_1) { + sendMqttMessage(MQTT_SIM_COMMAND_TOPIC, 1, devices[i].highCommand1); } else { - sendMqttMessage(MQTT_SIM_COMMAND_TOPIC, ALTITUDE_SIM_DOWN); + sendMqttMessage(MQTT_SIM_COMMAND_TOPIC, 1, devices[i].lowCommand1); } - } else if (data & HEADING_TRIGGER_BIT) { - if (data & HEADING_HIGH_BIT) { - sendMqttMessage(MQTT_SIM_COMMAND_TOPIC, HEADING_SIM_UP); + } else if (data & TRIGGER_BIT_2) { + if (data & HIGH_BIT_2) { + sendMqttMessage(MQTT_SIM_COMMAND_TOPIC, 1, devices[i].highCommand2); } else { - sendMqttMessage(MQTT_SIM_COMMAND_TOPIC, HEADING_SIM_DOWN); + sendMqttMessage(MQTT_SIM_COMMAND_TOPIC, 1, devices[i].lowCommand2); } } else { // who are you? } - Wire.requestFrom(devices[i].address, READ_BYTES_FROM_WIRE); } + + Wire.requestFrom(devices[i].address, READ_BYTES_FROM_WIRE); } } // transmit MQTT keepalive message if MQTT_KEEPALIVE_INTERVAL_MS is reached // or millis() is wrapping to 0 if (mqttLastKeepAlive + MQTT_KEEPALIVE_INTERVAL_MS < millis() || mqttLastKeepAlive > millis()) { - sendMqttMessage(MQTT_SIM_DEVICE_TOPIC, localIP); + sendMqttMessage(MQTT_SIM_DEVICE_TOPIC, 1, localIP); mqttLastKeepAlive = millis(); } } diff --git a/rotator/global.h b/rotator/global.h new file mode 120000 index 0000000..4adadf7 --- /dev/null +++ b/rotator/global.h @@ -0,0 +1 @@ +../master/global.h \ No newline at end of file diff --git a/rotator/rotator.ino b/rotator/rotator.ino index ffcbe35..0413bd2 100644 --- a/rotator/rotator.ino +++ b/rotator/rotator.ino @@ -1,5 +1,17 @@ #include +#include +#include +#include +#include +#include +#include + +#include "global.h" + +#define SCREEN_WIDTH 128 // OLED display width, in pixels +#define SCREEN_HEIGHT 64 // OLED display height, in pixels + #define PIN_ROTATOR1_WHITE 2 #define PIN_ROTATOR1_GREEN 4 @@ -10,18 +22,30 @@ #define SKIP_ROTARY_INPUTS 50 -// needs global define -#define DATA_STOP_BYTE 0x00 -#define DATA_RESET_BYTE 0xFF -#define ROTATOR1_TRIGGER_BIT 1 -#define ROTATOR1_HIGH_BIT 2 -#define ROTATOR2_TRIGGER_BIT 4 -#define ROTATOR2_HIGH_BIT 8 -#define PIN_ADDRESS_1 11 -#define PIN_ADDRESS_2 12 -#define PIN_ADDRESS_3 13 +#define INTERNAL_STATE_UNKNOWN 0 +#define INTERNAL_STATE_INITIALIZED 1 -int lastClk = HIGH; +#define PIN_ADDRESS_1 14 +#define PIN_ADDRESS_2 15 +#define PIN_ADDRESS_3 16 + +#define PIN_DISPLAY1_DC 8 +#define PIN_DISPLAY1_RST 9 +#define PIN_DISPLAY1_CS 10 +#define PIN_DISPLAY2_DC 6 +#define PIN_DISPLAY2_RST 7 +#define PIN_DISPLAY2_CS 17 + +Adafruit_SSD1306 display1(SCREEN_WIDTH, SCREEN_HEIGHT, + &SPI, PIN_DISPLAY1_DC, PIN_DISPLAY1_RST, PIN_DISPLAY1_CS); +Adafruit_SSD1306 display2(SCREEN_WIDTH, SCREEN_HEIGHT, + &SPI, PIN_DISPLAY2_DC, PIN_DISPLAY2_RST, PIN_DISPLAY2_CS); + +char name1[] = {'X', 'X', 'X'}; +char name2[] = {'Y', 'Y', 'Y'}; +int internalState = INTERNAL_STATE_UNKNOWN; + +uint8_t lastClk = HIGH; // ringbuffer of trigger and direction values byte valueBuffer[VALUE_BUFFER] = { 0 }; @@ -29,9 +53,21 @@ uint8_t readerPos, writerPos = 0; void addValue(uint8_t value) { valueBuffer[writerPos++] = value; + + // check for writer position overflow if (writerPos >= VALUE_BUFFER) { writerPos = 0; - } + } + + // check if writer overtooks reader + // if so, increment reader and check + // for reader position overflow + if (writerPos == readerPos) { + readerPos++; + if (readerPos >= VALUE_BUFFER) { + readerPos = 0; + } + } } void falling(uint8_t pin, byte triggerBit, byte highBit) { @@ -51,15 +87,18 @@ void falling(uint8_t pin, byte triggerBit, byte highBit) { } void rotator1Falling() { - falling(PIN_ROTATOR1_GREEN, ROTATOR1_TRIGGER_BIT, ROTATOR1_HIGH_BIT); + falling(PIN_ROTATOR1_GREEN, TRIGGER_BIT_1, HIGH_BIT_1); } void rotator2Falling() { - falling(PIN_ROTATOR2_GREEN, ROTATOR2_TRIGGER_BIT, ROTATOR2_HIGH_BIT); + falling(PIN_ROTATOR2_GREEN, TRIGGER_BIT_2, HIGH_BIT_2); } int eventCount = 0; byte lastValue = 0; +// this method blocks the parallel use of rotators +// if parallel use should be possible +// lastValue and eventCount must be stored per rotator bool useInput(byte value) { if (lastValue == value) { // same event as last event @@ -80,35 +119,68 @@ bool useInput(byte value) { } } - void i2cRequest() { - // if write is ahead, send data - if (writerPos != readerPos) { - byte value = valueBuffer[readerPos++]; - - if (readerPos >= VALUE_BUFFER) { - readerPos = 0; - } - - Wire.write(value); + if (internalState == INTERNAL_STATE_UNKNOWN) { + // looks like we were restarted + // request initialization first + Serial.println("Requesting init"); + Wire.write(DATA_RESET_BYTE); + internalState = INTERNAL_STATE_INITIALIZED; } else { - Wire.write(DATA_STOP_BYTE); + // if write is ahead, send data + if (writerPos != readerPos) { + byte value = valueBuffer[readerPos++]; + + if (readerPos >= VALUE_BUFFER) { + readerPos = 0; + } + + Wire.write(value); + } else { + Wire.write(DATA_STOP_BYTE); + } } } void i2cReceive(int bytes) { - for (int i = 0; i < bytes && Wire.available(); i++) { + while (Wire.available()) { byte data = Wire.read(); if (data == DATA_RESET_BYTE) { + Serial.println("Received reset request"); writerPos = readerPos = 0; + } else if (data == DATA_INIT_BYTE_1) { + Serial.println("Received init 1"); + // next three bytes are the name of the first rotator + for (int i = 0; i < 3 && Wire.available(); i++) { + name1[i] = Wire.read(); + } + } else if (data == DATA_INIT_BYTE_2) { + Serial.println("Received init 2"); + // next three bytes are the name of the second rotator + for (int i = 0; i < 3 && Wire.available(); i++) { + name2[i] = Wire.read(); + } } else { - // nothing else yet implemented + // not yet implemented? + Serial.println("Received unknown"); } } } -uint8_t address = 0; +void displayTestScreen(Adafruit_SSD1306 display) { + display.clearDisplay(); + for (int x = 0; x < SCREEN_WIDTH; x++) { + for (int y = 0; y < SCREEN_HEIGHT; y++) { + display.drawPixel(x, y, SSD1306_WHITE); + } + } + display.display(); +} + +uint8_t address = I2C_ADDRESS_START; void setup() { + // https://support.arduino.cc/hc/en-us/articles/4839084114460-If-your-board-runs-the-sketch-twice + delay(150); Serial.begin(115200); // setup rotator GPIOs @@ -123,9 +195,9 @@ void setup() { pinMode(PIN_ADDRESS_3, INPUT_PULLUP); // calculate address by LOW GPIOs - address = digitalRead(PIN_ADDRESS_1) == LOW; - address |= (digitalRead(PIN_ADDRESS_2) == LOW) << 1; - address |= (digitalRead(PIN_ADDRESS_3) == LOW) << 2; + address |= (digitalRead(PIN_ADDRESS_1) == LOW) << 1; + address |= (digitalRead(PIN_ADDRESS_2) == LOW) << 2; + address |= (digitalRead(PIN_ADDRESS_3) == LOW) << 3; attachInterrupt(digitalPinToInterrupt(PIN_ROTATOR1_WHITE), rotator1Falling, FALLING); attachInterrupt(digitalPinToInterrupt(PIN_ROTATOR2_WHITE), rotator2Falling, FALLING); @@ -133,8 +205,60 @@ void setup() { Wire.begin(address); Wire.onRequest(i2cRequest); Wire.onReceive(i2cReceive); + + if(!display1.begin(SSD1306_SWITCHCAPVCC)) { + Serial.println(F("SSD1306 allocation failed (1)")); + for(;;); // Don't proceed, loop forever + } + displayTestScreen(display1); + + if(!display2.begin(SSD1306_SWITCHCAPVCC)) { + Serial.println(F("SSD1306 allocation failed (2)")); + for(;;); // Don't proceed, loop forever + } + displayTestScreen(display2); + // make sure, test screen is at least displayed 2 seconds + delay(2000); } void loop() { - delay(10000); + display1.clearDisplay(); + display1.setTextColor(SSD1306_WHITE); // Draw white text + display1.cp437(true); // Use full 256 char 'Code Page 437' font + + display1.setTextSize(1); // Normal 1:1 pixel scale + display1.setCursor(0, 8); // Start at top-left corner + display1.setFont(NULL); + display1.write(name1[0]); + display1.write(name1[1]); + display1.write(name1[2]); + + display1.setTextSize(2); // Normal 1:1 pixel scale + display1.setCursor(0, 56); // Start at top-left corner + display1.setFont(&FreeMonoBold18pt7b); + display1.write('8'); + display1.write('9'); + display1.write('0'); + display1.display(); + + display2.clearDisplay(); + display2.setTextColor(SSD1306_WHITE); // Draw white text + display2.cp437(true); // Use full 256 char 'Code Page 437' font + + display2.setTextSize(1); // Normal 1:1 pixel scale + display2.setCursor(0, 8); // Start at top-left corner + display2.setFont(NULL); + display2.write(name2[0]); + display2.write(name2[1]); + display2.write(name2[2]); + + display2.setTextSize(2); // Normal 1:1 pixel scale + display2.setCursor(0, 56); // Start at top-left corner + display2.setFont(&FreeMonoBold18pt7b); + display2.write('1'); + display2.write('2'); + display2.write('3'); + display2.display(); + + delay(1000); }