diff --git a/arch/portduino/portduino.ini b/arch/portduino/portduino.ini index 6ea1a3747..36bd6e610 100644 --- a/arch/portduino/portduino.ini +++ b/arch/portduino/portduino.ini @@ -1,6 +1,6 @@ ; The Portduino based sim environment on top of any host OS, all hardware will be simulated [portduino_base] -platform = https://github.com/meshtastic/platform-native.git#c95616208ffff4c8a36d48df810a3f072cce3521 +platform = https://github.com/meshtastic/platform-native.git#6fb39b6f94ece9c042141edb4afb91aca94dcaab framework = arduino build_src_filter = diff --git a/bin/config-dist.yaml b/bin/config-dist.yaml index d8cb5a9dd..f729f1ac7 100644 --- a/bin/config-dist.yaml +++ b/bin/config-dist.yaml @@ -101,11 +101,13 @@ Display: # Height: 240 Touchscreen: +### Note, at least for now, the touchscreen must have a CS pin defined, even if you let Linux manage the CS switching. + # Module: STMPE610 # CS: 7 # IRQ: 24 -# Module: XPT2046 +# Module: XPT2046 # Waveshare 2.8inch # CS: 7 # IRQ: 17 diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index c1c578b04..11df951e1 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -1,6 +1,7 @@ #include "configuration.h" #include "main.h" #if ARCH_PORTDUINO +#include "mesh_bus_spi.h" #include "platform/portduino/PortduinoGlue.h" #endif @@ -339,7 +340,7 @@ static TFT_eSPI *tft = nullptr; // Invoke library, pins defined in User_Setup.h class LGFX : public lgfx::LGFX_Device { lgfx::Panel_LCD *_panel_instance; - lgfx::Bus_SPI _bus_instance; + lgfx::Mesh_Bus_SPI _bus_instance; lgfx::ITouch *_touch_instance; @@ -356,6 +357,7 @@ class LGFX : public lgfx::LGFX_Device _panel_instance = new lgfx::Panel_ILI9341; auto buscfg = _bus_instance.config(); buscfg.spi_mode = 0; + _bus_instance.spi_device(DisplaySPI, settingsStrings[displayspidev]); buscfg.pin_dc = settingsMap[displayDC]; // Set SPI DC pin number (-1 = disable) diff --git a/src/graphics/mesh_bus_spi.cpp b/src/graphics/mesh_bus_spi.cpp new file mode 100644 index 000000000..a9536d490 --- /dev/null +++ b/src/graphics/mesh_bus_spi.cpp @@ -0,0 +1,188 @@ +// This code has been copied from LovyanGFX to make the SPI device selectable for touchscreens. +// Ideally this could eventually be an inherited class from BUS_SPI, +// but currently too many internal objects are set private. + +#include "configuration.h" +#if ARCH_PORTDUINO +#include "lgfx/v1/misc/pixelcopy.hpp" +#include "main.h" +#include "mesh_bus_spi.h" +#include +#include + +namespace lgfx +{ +inline namespace v1 +{ +//---------------------------------------------------------------------------- + +void Mesh_Bus_SPI::config(const config_t &config) +{ + _cfg = config; + + if (_cfg.pin_dc >= 0) { + pinMode(_cfg.pin_dc, pin_mode_t::output); + gpio_hi(_cfg.pin_dc); + } +} + +bool Mesh_Bus_SPI::init(void) +{ + dc_h(); + pinMode(_cfg.pin_dc, pin_mode_t::output); + if (SPIName != "") + PrivateSPI->begin(SPIName.c_str()); + else + PrivateSPI->begin(); + return true; +} + +void Mesh_Bus_SPI::release(void) +{ + PrivateSPI->end(); +} + +void Mesh_Bus_SPI::spi_device(HardwareSPI *newSPI, std::string newSPIName) +{ + PrivateSPI = newSPI; + SPIName = newSPIName; +} +void Mesh_Bus_SPI::beginTransaction(void) +{ + dc_h(); + SPISettings setting(_cfg.freq_write, MSBFIRST, _cfg.spi_mode); + PrivateSPI->beginTransaction(setting); +} + +void Mesh_Bus_SPI::endTransaction(void) +{ + PrivateSPI->endTransaction(); + dc_h(); +} + +void Mesh_Bus_SPI::beginRead(void) +{ + PrivateSPI->endTransaction(); + // SPISettings setting(_cfg.freq_read, BitOrder::MSBFIRST, _cfg.spi_mode, false); + SPISettings setting(_cfg.freq_read, MSBFIRST, _cfg.spi_mode); + PrivateSPI->beginTransaction(setting); +} + +void Mesh_Bus_SPI::endRead(void) +{ + PrivateSPI->endTransaction(); + beginTransaction(); +} + +void Mesh_Bus_SPI::wait(void) {} + +bool Mesh_Bus_SPI::busy(void) const +{ + return false; +} + +bool Mesh_Bus_SPI::writeCommand(uint32_t data, uint_fast8_t bit_length) +{ + dc_l(); + PrivateSPI->transfer((uint8_t *)&data, bit_length >> 3); + dc_h(); + return true; +} + +void Mesh_Bus_SPI::writeData(uint32_t data, uint_fast8_t bit_length) +{ + PrivateSPI->transfer((uint8_t *)&data, bit_length >> 3); +} + +void Mesh_Bus_SPI::writeDataRepeat(uint32_t data, uint_fast8_t bit_length, uint32_t length) +{ + const uint8_t dst_bytes = bit_length >> 3; + uint32_t limit = (dst_bytes == 3) ? 12 : 16; + auto buf = _flip_buffer.getBuffer(512); + size_t fillpos = 0; + reinterpret_cast(buf)[0] = data; + fillpos += dst_bytes; + uint32_t len; + do { + len = ((length - 1) % limit) + 1; + if (limit <= 64) + limit <<= 1; + + while (fillpos < len * dst_bytes) { + memcpy(&buf[fillpos], buf, fillpos); + fillpos += fillpos; + } + + PrivateSPI->transfer(buf, len * dst_bytes); + } while (length -= len); +} + +void Mesh_Bus_SPI::writePixels(pixelcopy_t *param, uint32_t length) +{ + const uint8_t dst_bytes = param->dst_bits >> 3; + uint32_t limit = (dst_bytes == 3) ? 12 : 16; + uint32_t len; + do { + len = ((length - 1) % limit) + 1; + if (limit <= 32) + limit <<= 1; + auto buf = _flip_buffer.getBuffer(len * dst_bytes); + param->fp_copy(buf, 0, len, param); + PrivateSPI->transfer(buf, len * dst_bytes); + } while (length -= len); +} + +void Mesh_Bus_SPI::writeBytes(const uint8_t *data, uint32_t length, bool dc, bool use_dma) +{ + if (dc) + dc_h(); + else + dc_l(); + PrivateSPI->transfer(const_cast(data), length); + if (!dc) + dc_h(); +} + +uint32_t Mesh_Bus_SPI::readData(uint_fast8_t bit_length) +{ + uint32_t res = 0; + bit_length >>= 3; + if (!bit_length) + return res; + int idx = 0; + do { + res |= PrivateSPI->transfer(0) << idx; + idx += 8; + } while (--bit_length); + return res; +} + +bool Mesh_Bus_SPI::readBytes(uint8_t *dst, uint32_t length, bool use_dma) +{ + do { + dst[0] = PrivateSPI->transfer(0); + ++dst; + } while (--length); + return true; +} + +void Mesh_Bus_SPI::readPixels(void *dst, pixelcopy_t *param, uint32_t length) +{ + uint32_t bytes = param->src_bits >> 3; + uint32_t dstindex = 0; + uint32_t len = 4; + uint8_t buf[24]; + param->src_data = buf; + do { + if (len > length) + len = length; + readBytes((uint8_t *)buf, len * bytes, true); + param->src_x = 0; + dstindex = param->fp_copy(dst, dstindex, dstindex + len, param); + length -= len; + } while (length); +} + +} // namespace v1 +} // namespace lgfx +#endif \ No newline at end of file diff --git a/src/graphics/mesh_bus_spi.h b/src/graphics/mesh_bus_spi.h new file mode 100644 index 000000000..903f7ad9d --- /dev/null +++ b/src/graphics/mesh_bus_spi.h @@ -0,0 +1,100 @@ +#if ARCH_PORTDUINO +/*----------------------------------------------------------------------------/ + Lovyan GFX - Graphics library for embedded devices. + +Original Source: + https://github.com/lovyan03/LovyanGFX/ + +Licence: + [FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt) + +Author: + [lovyan03](https://twitter.com/lovyan03) + +Contributors: + [ciniml](https://github.com/ciniml) + [mongonta0716](https://github.com/mongonta0716) + [tobozo](https://github.com/tobozo) +/----------------------------------------------------------------------------*/ +#pragma once + +#include + +#include "lgfx/v1/Bus.hpp" +#include "lgfx/v1/platforms/common.hpp" + +namespace lgfx +{ +inline namespace v1 +{ +//---------------------------------------------------------------------------- + +class Mesh_Bus_SPI : public IBus +{ + public: + struct config_t { + uint32_t freq_write = 16000000; + uint32_t freq_read = 8000000; + // bool spi_3wire = true; + // bool use_lock = true; + int16_t pin_sclk = -1; + int16_t pin_miso = -1; + int16_t pin_mosi = -1; + int16_t pin_dc = -1; + uint8_t spi_mode = 0; + }; + + const config_t &config(void) const { return _cfg; } + + void config(const config_t &config); + + bus_type_t busType(void) const override { return bus_type_t::bus_spi; } + + bool init(void) override; + void release(void) override; + void spi_device(HardwareSPI *newSPI, std::string newSPIName); + + void beginTransaction(void) override; + void endTransaction(void) override; + void wait(void) override; + bool busy(void) const override; + + bool writeCommand(uint32_t data, uint_fast8_t bit_length) override; + void writeData(uint32_t data, uint_fast8_t bit_length) override; + void writeDataRepeat(uint32_t data, uint_fast8_t bit_length, uint32_t count) override; + void writePixels(pixelcopy_t *param, uint32_t length) override; + void writeBytes(const uint8_t *data, uint32_t length, bool dc, bool use_dma) override; + + void initDMA(void) {} + void flush(void) {} + void addDMAQueue(const uint8_t *data, uint32_t length) override { writeBytes(data, length, true, true); } + void execDMAQueue(void) {} + uint8_t *getDMABuffer(uint32_t length) override { return _flip_buffer.getBuffer(length); } + + void beginRead(void) override; + void endRead(void) override; + uint32_t readData(uint_fast8_t bit_length) override; + bool readBytes(uint8_t *dst, uint32_t length, bool use_dma) override; + void readPixels(void *dst, pixelcopy_t *param, uint32_t length) override; + + private: + HardwareSPI *PrivateSPI; + std::string SPIName; + __attribute__((always_inline)) inline void dc_h(void) { gpio_hi(_cfg.pin_dc); } + __attribute__((always_inline)) inline void dc_l(void) { gpio_lo(_cfg.pin_dc); } + + config_t _cfg; + FlipBuffer _flip_buffer; + bool _need_wait; + uint32_t _mask_reg_dc; + uint32_t _last_apb_freq = -1; + uint32_t _clkdiv_write; + uint32_t _clkdiv_read; + volatile uint32_t *_gpio_reg_dc_h; + volatile uint32_t *_gpio_reg_dc_l; +}; + +//---------------------------------------------------------------------------- +} // namespace v1 +} // namespace lgfx +#endif \ No newline at end of file diff --git a/src/input/TouchScreenImpl1.cpp b/src/input/TouchScreenImpl1.cpp index 3e4ed4163..c863ead69 100644 --- a/src/input/TouchScreenImpl1.cpp +++ b/src/input/TouchScreenImpl1.cpp @@ -4,7 +4,7 @@ #include "configuration.h" #include "modules/ExternalNotificationModule.h" -#ifdef ARCH_PORTDUINO +#if ARCH_PORTDUINO #include "platform/portduino/PortduinoGlue.h" #endif diff --git a/src/main.cpp b/src/main.cpp index 535b5a2b8..51e027916 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -645,14 +645,12 @@ void setup() pinMode(LORA_CS, OUTPUT); digitalWrite(LORA_CS, HIGH); SPI1.begin(false); -#else // HW_SPI1_DEVICE +#else // HW_SPI1_DEVICE SPI.setSCK(LORA_SCK); SPI.setTX(LORA_MOSI); SPI.setRX(LORA_MISO); SPI.begin(false); -#endif // HW_SPI1_DEVICE -#elif ARCH_PORTDUINO - SPI.begin(settingsStrings[spidev].c_str()); +#endif // HW_SPI1_DEVICE #elif !defined(ARCH_ESP32) // ARCH_RP2040 SPI.begin(); #else @@ -802,7 +800,7 @@ void setup() if (settingsMap[use_sx1262]) { if (!rIf) { LOG_DEBUG("Attempting to activate sx1262 radio on SPI port %s\n", settingsStrings[spidev].c_str()); - LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings); + LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(*LoraSPI, spiSettings); rIf = new SX1262Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset], settingsMap[busy]); if (!rIf->init()) { @@ -816,7 +814,7 @@ void setup() } else if (settingsMap[use_rf95]) { if (!rIf) { LOG_DEBUG("Attempting to activate rf95 radio on SPI port %s\n", settingsStrings[spidev].c_str()); - LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings); + LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(*LoraSPI, spiSettings); rIf = new RF95Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset], settingsMap[busy]); if (!rIf->init()) { @@ -831,7 +829,7 @@ void setup() } else if (settingsMap[use_sx1280]) { if (!rIf) { LOG_DEBUG("Attempting to activate sx1280 radio on SPI port %s\n", settingsStrings[spidev].c_str()); - LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings); + LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(*LoraSPI, spiSettings); rIf = new SX1280Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset], settingsMap[busy]); if (!rIf->init()) { diff --git a/src/main.h b/src/main.h index 132fd190b..bb812b7b6 100644 --- a/src/main.h +++ b/src/main.h @@ -22,6 +22,11 @@ extern NimbleBluetooth *nimbleBluetooth; extern NRF52Bluetooth *nrf52Bluetooth; #endif +#if ARCH_PORTDUINO +extern HardwareSPI *DisplaySPI; +extern HardwareSPI *LoraSPI; + +#endif extern ScanI2C::DeviceAddress screen_found; extern ScanI2C::DeviceAddress cardkb_found; extern uint8_t kb_model; diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index f686ef3dc..f3415aaee 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -15,6 +15,8 @@ #include #include +HardwareSPI *DisplaySPI; +HardwareSPI *LoraSPI; std::map settingsMap; std::map settingsStrings; char *configPath = nullptr; @@ -81,6 +83,7 @@ void portduinoSetup() settingsStrings[keyboardDevice] = ""; settingsStrings[webserverrootpath] = ""; settingsStrings[spidev] = ""; + settingsStrings[displayspidev] = ""; YAML::Node yamlConfig; @@ -187,6 +190,9 @@ void portduinoSetup() settingsMap[displayOffsetY] = yamlConfig["Display"]["OffsetY"].as(0); settingsMap[displayRotate] = yamlConfig["Display"]["Rotate"].as(false); settingsMap[displayInvert] = yamlConfig["Display"]["Invert"].as(false); + if (yamlConfig["Display"]["spidev"]) { + settingsStrings[displayspidev] = "/dev/" + yamlConfig["Display"]["spidev"].as("spidev0.1"); + } } settingsMap[touchscreenModule] = no_touchscreen; if (yamlConfig["Touchscreen"]) { @@ -196,6 +202,9 @@ void portduinoSetup() settingsMap[touchscreenModule] = stmpe610; settingsMap[touchscreenCS] = yamlConfig["Touchscreen"]["CS"].as(-1); settingsMap[touchscreenIRQ] = yamlConfig["Touchscreen"]["IRQ"].as(-1); + if (yamlConfig["Touchscreen"]["spidev"]) { + settingsStrings[touchscreenspidev] = "/dev/" + yamlConfig["Touchscreen"]["spidev"].as(""); + } } if (yamlConfig["Input"]) { settingsStrings[keyboardDevice] = (yamlConfig["Input"]["KeyboardDevice"]).as(""); @@ -267,6 +276,26 @@ void portduinoSetup() initGPIOPin(settingsMap[touchscreenIRQ], gpioChipName); } + // if we specify a touchscreen dev, that is SPI. + // else if we specify a screen dev, that is SPI + // else if we specify a LoRa dev, that is SPI. + if (settingsStrings[touchscreenspidev] != "") { + SPI.begin(settingsStrings[touchscreenspidev].c_str()); + DisplaySPI = new HardwareSPI; + DisplaySPI->begin(settingsStrings[displayspidev].c_str()); + LoraSPI = new HardwareSPI; + LoraSPI->begin(settingsStrings[spidev].c_str()); + } else if (settingsStrings[displayspidev] != "") { + SPI.begin(settingsStrings[displayspidev].c_str()); + DisplaySPI = &SPI; + LoraSPI = new HardwareSPI; + LoraSPI->begin(settingsStrings[spidev].c_str()); + } else { + SPI.begin(settingsStrings[spidev].c_str()); + LoraSPI = &SPI; + DisplaySPI = &SPI; + } + return; } diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h index 505c436d6..980fc63b8 100644 --- a/src/platform/portduino/PortduinoGlue.h +++ b/src/platform/portduino/PortduinoGlue.h @@ -21,6 +21,8 @@ enum configNames { touchscreenModule, touchscreenCS, touchscreenIRQ, + touchscreenspidev, + displayspidev, displayPanel, displayWidth, displayHeight, @@ -45,4 +47,6 @@ enum { level_error, level_warn, level_info, level_debug }; extern std::map settingsMap; extern std::map settingsStrings; -int initGPIOPin(int pinNum, std::string gpioChipname); \ No newline at end of file +int initGPIOPin(int pinNum, std::string gpioChipname); +extern HardwareSPI *DisplaySPI; +extern HardwareSPI *LoraSPI; \ No newline at end of file