diff --git a/.vscode/settings.json b/.vscode/settings.json index 8ea4339d..cd2a5ca5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -53,6 +53,7 @@ }, "cSpell.words": [ "Blox", + "EINK", "HFSR", "Meshtastic", "NEMAGPS", diff --git a/boards/eink.json b/boards/eink.json new file mode 100644 index 00000000..d9f40e80 --- /dev/null +++ b/boards/eink.json @@ -0,0 +1,61 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DARDUINO_NRF52840_LORA_RELAY_V1 -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + [ + "0x239A", + "0x4405" + ] + ], + "usb_product": "TTGO_eink", + "mcu": "nrf52840", + "variant": "eink", + "variants_dir": "variants", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": [ + "bluetooth" + ], + "debug": { + "jlink_device": "nRF52840_xxAA", + "onboard_tools": [ + "jlink" + ], + "svd_path": "nrf52840.svd" + }, + "frameworks": [ + "arduino" + ], + "name": "TTGO eink (Adafruit BSP)", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "require_upload_port": true, + "speed": 115200, + "protocol": "jlink", + "protocols": [ + "jlink", + "nrfjprog", + "stlink" + ] + }, + "url": "FIXME", + "vendor": "TTGO" +} \ No newline at end of file diff --git a/docs/hardware/air530/Air530 GPS Manual Text English.pdf b/docs/hardware/air530/Air530 GPS Manual Text English.pdf new file mode 100644 index 00000000..99c52ba3 Binary files /dev/null and b/docs/hardware/air530/Air530 GPS Manual Text English.pdf differ diff --git a/docs/hardware/air530/Air530_GPS_User_Booklet.V1.7.pdf b/docs/hardware/air530/Air530_GPS_User_Booklet.V1.7.pdf new file mode 100644 index 00000000..5b0528ad Binary files /dev/null and b/docs/hardware/air530/Air530_GPS_User_Booklet.V1.7.pdf differ diff --git a/platformio.ini b/platformio.ini index fb6508e7..5fe2fdbf 100644 --- a/platformio.ini +++ b/platformio.ini @@ -227,6 +227,20 @@ lib_deps = ${arduino_base.lib_deps} UC1701 +; Prototype eink/nrf52840/sx1262 device +[env:eink] +extends = nrf52_base +board = eink +# add our variants files to the include and src paths +# define build flags for the TFT_eSPI library +build_flags = ${nrf52_base.build_flags} -Ivariants/eink + -DBUSY_PIN=3 -DRST_PIN=2 -DDC_PIN=28 -DCS_PIN=30 +src_filter = ${nrf52_base.src_filter} +<../variants/eink> +lib_deps = + ${arduino_base.lib_deps} + https://github.com/geeksville/EPD_Libraries.git + TFT_eSPI + ; The https://github.com/BigCorvus/SX1262-LoRa-BLE-Relay board by @BigCorvus [env:lora-relay-v1] extends = nrf52_base diff --git a/src/GPSStatus.h b/src/GPSStatus.h index ed9e0fbc..237964eb 100644 --- a/src/GPSStatus.h +++ b/src/GPSStatus.h @@ -1,126 +1,98 @@ #pragma once -#include #include "Status.h" #include "configuration.h" +#include -namespace meshtastic { +namespace meshtastic +{ - /// Describes the state of the GPS system. - class GPSStatus : public Status +/// Describes the state of the GPS system. +class GPSStatus : public Status +{ + + private: + CallbackObserver statusObserver = + CallbackObserver(this, &GPSStatus::updateStatus); + + bool hasLock = false; // default to false, until we complete our first read + bool isConnected = false; // Do we have a GPS we are talking to + int32_t latitude = 0, longitude = 0; // as an int mult by 1e-7 to get value as double + int32_t altitude = 0; + uint32_t dop = 0; // Diminution of position; PDOP where possible (UBlox), HDOP otherwise (TinyGPS) in 10^2 units (needs + // scaling before use) + uint32_t heading = 0; + uint32_t numSatellites = 0; + + public: + GPSStatus() { statusType = STATUS_TYPE_GPS; } + GPSStatus(bool hasLock, bool isConnected, int32_t latitude, int32_t longitude, int32_t altitude, uint32_t dop, + uint32_t heading, uint32_t numSatellites) + : Status() { + this->hasLock = hasLock; + this->isConnected = isConnected; + this->latitude = latitude; + this->longitude = longitude; + this->altitude = altitude; + this->dop = dop; + this->heading = heading; + this->numSatellites = numSatellites; + } + GPSStatus(const GPSStatus &); + GPSStatus &operator=(const GPSStatus &); - private: - CallbackObserver statusObserver = CallbackObserver(this, &GPSStatus::updateStatus); + void observe(Observable *source) { statusObserver.observe(source); } - bool hasLock = false; // default to false, until we complete our first read - bool isConnected = false; // Do we have a GPS we are talking to - int32_t latitude = 0, longitude = 0; // as an int mult by 1e-7 to get value as double - int32_t altitude = 0; - uint32_t dop = 0; // Diminution of position; PDOP where possible (UBlox), HDOP otherwise (TinyGPS) in 10^2 units (needs scaling before use) - uint32_t heading = 0; - uint32_t numSatellites = 0; + bool getHasLock() const { return hasLock; } - public: + bool getIsConnected() const { return isConnected; } - GPSStatus() { - statusType = STATUS_TYPE_GPS; - } - GPSStatus( bool hasLock, bool isConnected, int32_t latitude, int32_t longitude, int32_t altitude, uint32_t dop, uint32_t heading, uint32_t numSatellites ) : Status() + int32_t getLatitude() const { return latitude; } + + int32_t getLongitude() const { return longitude; } + + int32_t getAltitude() const { return altitude; } + + uint32_t getDOP() const { return dop; } + + uint32_t getHeading() const { return heading; } + + uint32_t getNumSatellites() const { return numSatellites; } + + bool matches(const GPSStatus *newStatus) const + { + return (newStatus->hasLock != hasLock || newStatus->isConnected != isConnected || newStatus->latitude != latitude || + newStatus->longitude != longitude || newStatus->altitude != altitude || newStatus->dop != dop || + newStatus->heading != heading || newStatus->numSatellites != numSatellites); + } + int updateStatus(const GPSStatus *newStatus) + { + // Only update the status if values have actually changed + bool isDirty; { - this->hasLock = hasLock; - this->isConnected = isConnected; - this->latitude = latitude; - this->longitude = longitude; - this->altitude = altitude; - this->dop = dop; - this->heading = heading; - this->numSatellites = numSatellites; + isDirty = matches(newStatus); + initialized = true; + hasLock = newStatus->hasLock; + isConnected = newStatus->isConnected; + latitude = newStatus->latitude; + longitude = newStatus->longitude; + altitude = newStatus->altitude; + dop = newStatus->dop; + heading = newStatus->heading; + numSatellites = newStatus->numSatellites; } - GPSStatus(const GPSStatus &); - GPSStatus &operator=(const GPSStatus &); - - void observe(Observable *source) - { - statusObserver.observe(source); + if (isDirty) { + if (hasLock) + DEBUG_MSG("New GPS pos lat=%f, lon=%f, alt=%d, pdop=%f, heading=%f, sats=%d\n", latitude * 1e-7, longitude * 1e-7, + altitude, dop * 1e-2, heading * 1e-5, numSatellites); + else + DEBUG_MSG("No GPS lock\n"); + onNewStatus.notifyObservers(this); } + return 0; + } +}; - bool getHasLock() const - { - return hasLock; - } - - bool getIsConnected() const - { - return isConnected; - } - - int32_t getLatitude() const - { - return latitude; - } - - int32_t getLongitude() const - { - return longitude; - } - - int32_t getAltitude() const - { - return altitude; - } - - uint32_t getDOP() const - { - return dop; - } - - uint32_t getHeading() const - { - return heading; - } - - uint32_t getNumSatellites() const - { - return numSatellites; - } - - bool matches(const GPSStatus *newStatus) const - { - return ( - newStatus->hasLock != hasLock || - newStatus->isConnected != isConnected || - newStatus->latitude != latitude || - newStatus->longitude != longitude || - newStatus->altitude != altitude || - newStatus->dop != dop || - newStatus->heading != heading || - newStatus->numSatellites != numSatellites - ); - } - int updateStatus(const GPSStatus *newStatus) { - // Only update the status if values have actually changed - bool isDirty; - { - isDirty = matches(newStatus); - initialized = true; - hasLock = newStatus->hasLock; - isConnected = newStatus->isConnected; - latitude = newStatus->latitude; - longitude = newStatus->longitude; - altitude = newStatus->altitude; - dop = newStatus->dop; - heading = newStatus->heading; - numSatellites = newStatus->numSatellites; - } - if(isDirty) { - DEBUG_MSG("New GPS pos lat=%f, lon=%f, alt=%d, pdop=%f, heading=%f, sats=%d\n", latitude * 1e-7, longitude * 1e-7, altitude, dop * 1e-2, heading * 1e-5, numSatellites); - onNewStatus.notifyObservers(this); - } - return 0; - } - - }; - -} +} // namespace meshtastic extern meshtastic::GPSStatus *gpsStatus; \ No newline at end of file diff --git a/src/Power.cpp b/src/Power.cpp index 7d032b66..f6ae1c69 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -66,7 +66,10 @@ bool Power::analogInit() { #ifdef BATTERY_PIN DEBUG_MSG("Using analog input for battery level\n"); +#ifndef NO_ESP32 + // ESP32 needs special analog stuff adcAttachPin(BATTERY_PIN); +#endif // adcStart(BATTERY_PIN); analogReadResolution(10); // Default of 12 is not very linear. Recommended to use 10 or 11 depending on needed resolution. batteryLevel = &analogLevel; diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index f72e5842..14f7b996 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -11,16 +11,7 @@ static void sdsEnter() { - /* - - // Don't deepsleep if we have USB power or if the user as pressed a button recently - // !isUSBPowered <- doesn't work yet because the axp192 isn't letting the battery fully charge when we are awake - FIXME - if (millis() - lastPressMs > radioConfig.preferences.mesh_sds_timeout_secs) - { - doDeepSleep(radioConfig.preferences.sds_secs); - } -*/ - + // FIXME - make sure GPS and LORA radio are off first - because we want close to zero current draw doDeepSleep(radioConfig.preferences.sds_secs * 1000LL); } diff --git a/src/configuration.h b/src/configuration.h index 9087ca8c..026bb188 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -145,7 +145,9 @@ along with this program. If not, see . #define FLIP_SCREEN_VERTICALLY // DEBUG LED +#ifndef LED_INVERTED #define LED_INVERTED 0 // define as 1 if LED is active low (on) +#endif // ----------------------------------------------------------------------------- // GPS diff --git a/src/gps/NMEAGPS.cpp b/src/gps/NMEAGPS.cpp index 2100fdcb..08918d6d 100644 --- a/src/gps/NMEAGPS.cpp +++ b/src/gps/NMEAGPS.cpp @@ -1,6 +1,51 @@ #include "NMEAGPS.h" #include "configuration.h" +/* +Helpful translations from the Air530 GPS datasheet + +Sat acquision mode +捕获电流值@3.3v 42.6 mA + +sat tracking mode +跟踪电流值@3.3v 36.7 mA + +Low power mode +低功耗模式@3.3V 0.85 mA +(发送指令:$PGKC051,0) + +Super low power mode +超低功耗模式@3.3V 31 uA +(发送指令:$PGKC105,4) + +To exit sleep use WAKE pin + +Commands to enter sleep +6、Command: 105 +进入周期性低功耗模式 +Arguments: + +Arg1: “0”,正常运行模式 (normal mode) +“1”,周期超低功耗跟踪模式,需要拉高 WAKE 来唤醒 (periodic low power tracking mode - keeps sat positions, use wake to wake up) +“2”,周期低功耗模式 (periodic low power mode) +“4”,直接进入超低功耗跟踪模式,需要拉高 WAKE 来唤醒 (super low power consumption mode immediately, need WAKE to resume) +“8”,自动低功耗模式,可以通过串口唤醒 (automatic low power mode, wake by sending characters to serial port) +“9”, 自动超低功耗跟踪模式,需要拉高 WAKE 来唤醒 (automatic low power tracking when possible, need wake pin to resume) + +(Arg 2 & 3 only valid if Arg1 is "1" or "2") +Arg2:运行时间(毫秒),在 Arg1 为 1、2 的周期模式下,此参数起作用 +ON time in msecs + +Arg3:睡眠时间(毫秒),在 Arg1 为 1、2 的周期模式下,此参数起作用 +Sleep time in msecs + +Example: +$PGKC105,8*3F +This will set automatic low power mode with waking when we send chars to the serial port. Possibly do this as soon as we get a new +location. When we wake again in a minute we send a character to wake up. + +*/ + static int32_t toDegInt(RawDegrees d) { int32_t degMult = 10000000; // 1e7 @@ -10,12 +55,27 @@ static int32_t toDegInt(RawDegrees d) return r; } +bool NMEAGPS::setup() +{ +#ifdef PIN_GPS_PPS + // pulse per second + // FIXME - move into shared GPS code + pinMode(PIN_GPS_PPS, INPUT); +#endif + + return true; +} + void NMEAGPS::loop() { while (_serial_gps->available() > 0) { int c = _serial_gps->read(); - // Serial.write(c); - reader.encode(c); + // DEBUG_MSG("%c", c); + bool isValid = reader.encode(c); + + // if we have received valid NMEA claim we are connected + if (isValid) + isConnected = true; } uint32_t now = millis(); @@ -39,8 +99,6 @@ void NMEAGPS::loop() t.tm_year = d.year() - 1900; t.tm_isdst = false; perhapsSetRTC(t); - - isConnected = true; // we seem to have a real GPS (but not necessarily a lock) } uint8_t fixtype = reader.fixQuality(); @@ -68,7 +126,7 @@ void NMEAGPS::loop() } // expect gps pos lat=37.520825, lon=-122.309162, alt=158 - DEBUG_MSG("new NMEA GPS pos lat=%f, lon=%f, alt=%d, hdop=%f, heading=%f\n", latitude * 1e-7, longitude * 1e-7, + DEBUG_MSG("new NMEA GPS pos lat=%f, lon=%f, alt=%d, hdop=%g, heading=%f\n", latitude * 1e-7, longitude * 1e-7, altitude, dop * 1e-2, heading * 1e-5); } diff --git a/src/gps/NMEAGPS.h b/src/gps/NMEAGPS.h index 71b24a65..b46aa0c6 100644 --- a/src/gps/NMEAGPS.h +++ b/src/gps/NMEAGPS.h @@ -17,5 +17,7 @@ class NMEAGPS : public GPS uint32_t lastUpdateMsec = 0; public: + virtual bool setup(); + virtual void loop(); }; diff --git a/src/graphics/EInkDisplay.cpp b/src/graphics/EInkDisplay.cpp new file mode 100644 index 00000000..2156f591 --- /dev/null +++ b/src/graphics/EInkDisplay.cpp @@ -0,0 +1,125 @@ +#include "configuration.h" + +#ifdef HAS_EINK +#include "EInkDisplay.h" +#include "SPILock.h" +#include "epd1in54.h" // Screen specific library +#include "graphics/configs.h" +#include +#include // Graphics library and Sprite class + +Epd ePaper; // Create an instance ePaper + +TFT_eSPI glc = TFT_eSPI(); // Invoke the graphics library class +TFT_eSprite frame = TFT_eSprite(&glc); // Invoke the Sprite class for the image frame buffer +uint8_t *framePtr; // Pointer for the black frame buffer + +#define COLORED 0 +#define UNCOLORED 1 + +#define INK COLORED // Black ink +#define PAPER UNCOLORED // 'paper' background colour + +//------------------------------------------------------------------------------------ +// Update display - different displays have different function names in the default +// Waveshare libraries :-( +//------------------------------------------------------------------------------------ +#if defined(EPD1IN54B_H) || defined(EPD1IN54C_H) || defined(EPD2IN13B_H) || defined(EPD2IN7B_H) || defined(EPD2IN9B_H) || \ + defined(EPD4IN2_H) +void updateDisplay(uint8_t *blackFrame = blackFramePtr, uint8_t *redFrame = redFramePtr) +{ + ePaper.DisplayFrame(blackFrame, redFrame); // Update 3 colour display +#else +void updateDisplay(uint8_t *blackFrame = framePtr) +{ +#if defined(EPD2IN7_H) || defined(EPD4IN2_H) + ePaper.DisplayFrame(blackFrame); // Update 2 color display + +#elif defined(EPD1IN54_H) || defined(EPD2IN13_H) || defined(EPD2IN9_H) + ePaper.SetFrameMemory(blackFrame); // Update 2 colour display + ePaper.DisplayFrame(); +#else +#error "Selected ePaper library is not supported" +#endif +#endif +} + +EInkDisplay::EInkDisplay(uint8_t address, int sda, int scl) +{ + setGeometry(GEOMETRY_128_64); // FIXME - currently we lie and claim 128x64 because I'm not yet sure other resolutions will + // work ie GEOMETRY_RAWMODE +} + +// FIXME quick hack to limit drawing to a very slow rate +uint32_t lastDrawMsec; + +// Write the buffer to the display memory +void EInkDisplay::display(void) +{ + concurrency::LockGuard g(spiLock); + + uint32_t now = millis(); + uint32_t sinceLast = now - lastDrawMsec; + + if (framePtr && (sinceLast > 30 * 1000 || lastDrawMsec == 0)) { + lastDrawMsec = now; + + // FIXME - only draw bits have changed (use backbuf similar to the other displays) + // tft.drawBitmap(0, 0, buffer, 128, 64, TFT_YELLOW, TFT_BLACK); + for (uint8_t y = 0; y < SCREEN_HEIGHT; y++) { + for (uint8_t x = 0; x < SCREEN_WIDTH; x++) { + + // get src pixel in the page based ordering the OLED lib uses FIXME, super inefficent + auto b = buffer[x + (y / 8) * SCREEN_WIDTH]; + auto isset = b & (1 << (y & 7)); + frame.drawPixel(x, y, isset ? INK : PAPER); + } + } + + updateDisplay(); // Send image to display and refresh + + // Put screen to sleep to save power (if wanted) + // ePaper.Sleep(); + } +} + +// Send a command to the display (low level function) +void EInkDisplay::sendCommand(uint8_t com) +{ + (void)com; + // Drop all commands to device (we just update the buffer) +} + +// Connect to the display +bool EInkDisplay::connect() +{ + DEBUG_MSG("Doing EInk init\n"); + +#ifdef PIN_EINK_EN + digitalWrite(PIN_EINK_EN, HIGH); + pinMode(PIN_EINK_EN, OUTPUT); +#endif + + // Initialise the ePaper library + // FIXME - figure out how to use lut_partial_update + if (ePaper.Init(lut_full_update) != 0) { + DEBUG_MSG("ePaper init failed\n"); + return false; + } else { + frame.setColorDepth(1); // Must set the bits per pixel to 1 for ePaper displays + // Set bit depth BEFORE creating Sprite, default is 16! + + // Create a frame buffer in RAM of defined size and save the pointer to it + // RAM needed is about (EPD_WIDTH * EPD_HEIGHT)/8 , ~5000 bytes for 200 x 200 pixels + // Note: always create the Sprite before setting the Sprite rotation + framePtr = (uint8_t *)frame.createSprite(EPD_WIDTH, EPD_HEIGHT); + + frame.fillSprite(PAPER); // Fill frame with white + /* frame.drawLine(0, 0, frame.width() - 1, frame.height() - 1, INK); + frame.drawLine(0, frame.height() - 1, frame.width() - 1, 0, INK); + updateDisplay(); */ + return true; + } +} + +#endif diff --git a/src/graphics/EInkDisplay.h b/src/graphics/EInkDisplay.h new file mode 100644 index 00000000..f12ad688 --- /dev/null +++ b/src/graphics/EInkDisplay.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +/** + * An adapter class that allows using the TFT_eSPI library as if it was an OLEDDisplay implementation. + * + * Remaining TODO: + * optimize display() to only draw changed pixels (see other OLED subclasses for examples) + * implement displayOn/displayOff to turn off the TFT device (and backlight) + * Use the fast NRF52 SPI API rather than the slow standard arduino version + * + * turn radio back on - currently with both on spi bus is fucked? or are we leaving chip select asserted? + */ +class EInkDisplay : public OLEDDisplay +{ + public: + /* constructor + FIXME - the parameters are not used, just a temporary hack to keep working like the old displays + */ + EInkDisplay(uint8_t address, int sda, int scl); + + // Write the buffer to the display memory + virtual void display(void); + + protected: + // the header size of the buffer used, e.g. for the SPI command header + virtual int getBufferOffset(void) { return 0; } + + // Send a command to the display (low level function) + virtual void sendCommand(uint8_t com); + + // Connect to the display + virtual bool connect(); +}; diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index 0fc6e588..6a7e0b52 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -10,7 +10,8 @@ #include #endif -#include "TFT.h" +#include "EInkDisplay.h" +#include "TFTDisplay.h" #include "TypedQueue.h" #include "commands.h" #include "concurrency/LockGuard.h" @@ -48,7 +49,6 @@ class DebugInfo void drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); void drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); - std::string channelName; /// Protects all of internal state. @@ -240,6 +240,8 @@ class Screen : public concurrency::PeriodicTask /** FIXME cleanup display abstraction */ #ifdef ST7735_CS TFTDisplay dispdev; +#elif defined(HAS_EINK) + EInkDisplay dispdev; #elif defined(USE_SH1106) SH1106Wire dispdev; #else diff --git a/src/graphics/TFT.cpp b/src/graphics/TFTDisplay.cpp similarity index 97% rename from src/graphics/TFT.cpp rename to src/graphics/TFTDisplay.cpp index dcaf53ec..b9484b9d 100644 --- a/src/graphics/TFT.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -2,7 +2,7 @@ #ifdef ST7735_CS #include "SPILock.h" -#include "TFT.h" +#include "TFTDisplay.h" #include "graphics/configs.h" #include #include // Graphics and font library for ST7735 driver chip @@ -20,7 +20,6 @@ void TFTDisplay::display(void) { concurrency::LockGuard g(spiLock); -#if 1 // FIXME - only draw bits have changed (use backbuf similar to the other displays) // tft.drawBitmap(0, 0, buffer, 128, 64, TFT_YELLOW, TFT_BLACK); for (uint8_t y = 0; y < SCREEN_HEIGHT; y++) { @@ -32,7 +31,6 @@ void TFTDisplay::display(void) tft.drawPixel(x, y, isset ? TFT_WHITE : TFT_BLACK); } } -#endif } // Send a command to the display (low level function) @@ -52,12 +50,10 @@ bool TFTDisplay::connect() pinMode(ST7735_BACKLIGHT_EN, OUTPUT); #endif -#if 1 tft.init(); tft.setRotation(3); // Orient horizontal and wide underneath the silkscreen name label tft.fillScreen(TFT_BLACK); // tft.drawRect(0, 0, 40, 10, TFT_PURPLE); // wide rectangle in upper left -#endif return true; } diff --git a/src/graphics/TFT.h b/src/graphics/TFTDisplay.h similarity index 100% rename from src/graphics/TFT.h rename to src/graphics/TFTDisplay.h diff --git a/src/main.cpp b/src/main.cpp index f5626e45..c53fdfef 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -213,16 +213,16 @@ void setup() esp32Setup(); #endif +#ifdef NRF52_SERIES + nrf52Setup(); +#endif + // Currently only the tbeam has a PMU power = new Power(); power->setStatusHandler(powerStatus); powerStatus->observe(&power->newStatus); power->setup(); // Must be after status handler is installed, so that handler gets notified of the initial configuration -#ifdef NRF52_SERIES - nrf52Setup(); -#endif - // Init our SPI controller (must be before screen and lora) initSPI(); #ifdef NO_ESP32 @@ -234,7 +234,7 @@ void setup() #endif // Initialize the screen first so we can show the logo while we start up everything else. -#ifdef ST7735_CS +#if defined(ST7735_CS) || defined(HAS_EINK) screen.setup(); #else if (ssd1306_found) diff --git a/src/nrf52/main-nrf52.cpp b/src/nrf52/main-nrf52.cpp index 40ca6266..4a5c93f7 100644 --- a/src/nrf52/main-nrf52.cpp +++ b/src/nrf52/main-nrf52.cpp @@ -1,6 +1,6 @@ #include "NRF52Bluetooth.h" #include "configuration.h" -#include "graphics/TFT.h" +#include "graphics/TFTDisplay.h" #include #include #include @@ -49,14 +49,19 @@ void getMacAddr(uint8_t *dmac) NRF52Bluetooth *nrf52Bluetooth; static bool bleOn = false; +static const bool enableBle = true; // Set to false for easier debugging + void setBluetoothEnable(bool on) { if (on != bleOn) { if (on) { if (!nrf52Bluetooth) { - // DEBUG_MSG("DISABLING NRF52 BLUETOOTH WHILE DEBUGGING\n"); - nrf52Bluetooth = new NRF52Bluetooth(); - nrf52Bluetooth->setup(); + if (!enableBle) + DEBUG_MSG("DISABLING NRF52 BLUETOOTH WHILE DEBUGGING\n"); + else { + nrf52Bluetooth = new NRF52Bluetooth(); + nrf52Bluetooth->setup(); + } } } else { DEBUG_MSG("FIXME: implement BLE disable\n"); @@ -88,6 +93,11 @@ void nrf52Setup() // This is the recommended setting for Monitor Mode Debugging NVIC_SetPriority(DebugMonitor_IRQn, 6UL); +#ifdef PIN_PWR_ON + digitalWrite(PIN_PWR_ON, HIGH); // If we need to assert a pin to power external peripherals + pinMode(PIN_PWR_ON, OUTPUT); +#endif + // Not yet on board // pmu.init(); diff --git a/src/nrf52/pgmspace.h b/src/nrf52/pgmspace.h new file mode 100644 index 00000000..5ad8035b --- /dev/null +++ b/src/nrf52/pgmspace.h @@ -0,0 +1,5 @@ +#pragma once + +// dummy file to keep old arduino code happy +#define PROGMEM +#define pgm_read_byte(addr) (*((unsigned const char *)addr)) \ No newline at end of file diff --git a/variants/eink/variant.cpp b/variants/eink/variant.cpp new file mode 100644 index 00000000..cae079b7 --- /dev/null +++ b/variants/eink/variant.cpp @@ -0,0 +1,44 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // P0 - pins 0 and 1 are hardwired for xtal and should never be enabled + 0xff, 0xff, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + +void initVariant() +{ + // LED1 & LED2 + pinMode(PIN_LED1, OUTPUT); + ledOff(PIN_LED1); + + pinMode(PIN_LED2, OUTPUT); + ledOff(PIN_LED2); + + pinMode(PIN_LED3, OUTPUT); + ledOff(PIN_LED3); +} diff --git a/variants/eink/variant.h b/variants/eink/variant.h new file mode 100644 index 00000000..e0cdda0d --- /dev/null +++ b/variants/eink/variant.h @@ -0,0 +1,218 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _VARIANT_TTGO_EINK_V1_ +#define _VARIANT_TTGO_EINK_V1_ + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32khz crystal for LF + +/* +@geeksville eink TODO: + +confirm that watchdog reset (i.e. all pins now become inputs) won't cause the board to power down when we are not connected to USB +(I bet it will). If this happens recommended fix is to add an external pullup on PWR_ON GPIO. + +fix bootloader to use two buttons - remove bootloader hacks +fix battery voltage sensing +fix floating point SEGGER printf on nrf52 - see "new NMEA GPS pos" +get second button working in app load +if battery falls too low deassert PWR_ON (to force board to shutdown) +fix display width and height +clean up eink drawing to not have the nasty timeout hack +put eink to sleep when we think the screen is off +enable flash on spi0, share chip selects on spi1. +https://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.nrf52832.ps.v1.1%2Fspi.html enable reset as a button in +bootloader fix battery pin usage drive TCXO DIO3 enable high whenever we want the clock use PIN_GPS_WAKE to sleep the GPS use +tp_ser_io as a button, it goes high when pressed unify eink display classes +make screen.adjustBrightness() a nop on eink screens +enable gps sleep mode +use new flash chip +add factory/power on self test +*/ + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (1) +#define NUM_ANALOG_OUTPUTS (0) + +// LEDs +#define PIN_LED1 (0 + 13) // green (but red on my prototype) +#define PIN_LED2 (0 + 15) // blue (but red on my prototype) +#define PIN_LED3 (0 + 14) // red (not functional on my prototype) + +#define LED_RED PIN_LED3 +#define LED_GREEN PIN_LED1 +#define LED_BLUE PIN_LED2 + +#define LED_BUILTIN LED_GREEN +#define LED_CONN PIN_BLUE + +#define LED_STATE_ON 0 // State when LED is lit +#define LED_INVERTED 1 + +/* + * Buttons + */ +#define PIN_BUTTON1 (32 + 10) + +/* + * Analog pins + */ +#define PIN_A0 (4) // Battery ADC + +#define BATTERY_PIN PIN_A0 + +static const uint8_t A0 = PIN_A0; + +#define ADC_RESOLUTION 14 + +#define PIN_NFC1 (9) +#define PIN_NFC2 (10) + +/* + * Serial interfaces + */ + +/* +This serial port is _also_ connected to the incoming D+/D- pins from the USB header. FIXME, figure out how that is supposed to +work. +*/ +#define PIN_SERIAL2_RX (0 + 6) +#define PIN_SERIAL2_TX (0 + 8) +// #define PIN_SERIAL2_EN (0 + 17) + +/** + Wire Interfaces + */ +#define WIRE_INTERFACES_COUNT 1 + +#define PIN_WIRE_SDA (26) // Not connected on board? +#define PIN_WIRE_SCL (27) + +/* touch sensor, active high */ + +#define TP_SER_IO (0 + 11) + +// Board power is enabled either by VBUS from USB or the CPU asserting PWR_ON +#define PIN_PWR_ON (0 + 12) + +#define PIN_RTC_INT (0 + 16) // Interrupt from the PCF8563 RTC + +/* + +FIXME define/FIX flash access + +// QSPI Pins +#define PIN_QSPI_SCK 19 +#define PIN_QSPI_CS 17 +#define PIN_QSPI_IO0 20 +#define PIN_QSPI_IO1 21 +#define PIN_QSPI_IO2 22 +#define PIN_QSPI_IO3 23 + +// On-board QSPI Flash +#define EXTERNAL_FLASH_DEVICES MX25R6435F +#define EXTERNAL_FLASH_USE_QSPI + +*/ + +/* + * Lora radio + */ + +#define SX1262_CS (0 + 24) // FIXME - we really should define LORA_CS instead +#define SX1262_DIO1 (0 + 20) +// Note DIO2 is attached internally to the module to an analog switch for TX/RX switching +#define SX1262_DIO3 \ + (0 + 21) // This is used as an *output* from the sx1262 and connected internally to power the tcxo, do not drive from the main + // CPU? +#define SX1262_BUSY (0 + 17) +#define SX1262_RESET (0 + 25) +#define SX1262_E22 // Not really an E22 but TTGO seems to be trying to clone that +// Internally the TTGO module hooks the SX1262-DIO2 in to control the TX/RX switch (which is the default for the sx1262interface +// code) + +// #define LORA_DISABLE_SENDING // Define this to disable transmission for testing (power testing etc...) + +/* + * eink display pins + */ + +#define PIN_EINK_EN (32 + 11) +#define PIN_EINK_CS (0 + 30) +#define PIN_EINK_BUSY (0 + 3) +#define PIN_EINK_DC (0 + 28) +#define PIN_EINK_RES (0 + 2) +#define PIN_EINK_SCLK (0 + 31) +#define PIN_EINK_MOSI (0 + 29) // also called SDI + +#define HAS_EINK + +#define PIN_SPI1_MISO \ + (32 + 7) // FIXME not really needed, but for now the SPI code requires something to be defined, pick an used GPIO +#define PIN_SPI1_MOSI PIN_EINK_MOSI +#define PIN_SPI1_SCK PIN_EINK_SCLK + +/* + * Air530 GPS pins + */ + +#define PIN_GPS_WAKE (32 + 2) +#define PIN_GPS_PPS (32 + 4) +#define PIN_GPS_TX (32 + 9) // This is for bits going TOWARDS the CPU +#define PIN_GPS_RX (32 + 8) // This is for bits going TOWARDS the GPS + +#define PIN_SERIAL1_RX PIN_GPS_TX +#define PIN_SERIAL1_TX PIN_GPS_RX + +/* + * SPI Interfaces + */ +#define SPI_INTERFACES_COUNT 2 + +// For LORA +#define PIN_SPI_MISO (0 + 23) +#define PIN_SPI_MOSI (0 + 22) +#define PIN_SPI_SCK (0 + 19) + +// To debug via the segger JLINK console rather than the CDC-ACM serial device +#define USE_SEGGER + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif