diff --git a/README.md b/README.md index 6d9f82ed5b3cbbf8bf123097313e1628b0fcd1d7..e08e3460f1d458e8eae5826bf58921698f99014b 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ The room should be ventilated as soon as one LED turns red. The *CO<sub>2</sub> Ampel* can: -* Display CO2 concentration on LED ring. +* Display CO<sub>2</sub> concentration on LED ring. * Allow calibration. * Get current time over [NTP](https://en.wikipedia.org/wiki/Network_Time_Protocol) * Send data over [MQTT](https://en.wikipedia.org/wiki/MQTT). @@ -76,15 +76,15 @@ In Arduino IDE *Serial Monitor* or PlatformIO *Monitor*, type `help` + <kbd>Ente * `calibrate` (Starts calibration process). * `calibrate 600` (Starts calibration process, to given ppm). * `calibrate! 600` (Calibrates right now, to given ppm). -* `co2 1500` (Sets co2 level, for debugging purposes). +* `co2 1500` (Sets CO<sub>2</sub> level, for debugging purposes). * `color 0xFF0015` (Shows color, specified as RGB, for debugging). * `csv 60` (Sets CSV writing interval, in s). * `format_filesystem` (Deletes the whole filesystem). * `free` (Displays available heap space). +* `led 0/1` (Turns LEDs on/off). * `local_ip` (Displays local IP and current SSID). * `lora 300` (Sets LoRaWAN sending interval, in s). * `mqtt 60` (Sets MQTT sending interval, in s). -* `night_mode` (Toggles night mode on/off). * `reset` (Restarts the ESP). * `reset_scd` (Resets SCD30). * `send_local_ip` (Sends local IP and SSID via MQTT. Can be useful to find sensor). diff --git a/ampel-firmware/ampel-firmware.h b/ampel-firmware/ampel-firmware.h index 41fe46bd593aa82a1a3c133e08f1fabe88e7b490..abe783aea97c4aa368cd0eb940dda401b131375a 100644 --- a/ampel-firmware/ampel-firmware.h +++ b/ampel-firmware/ampel-firmware.h @@ -33,6 +33,7 @@ #endif #include "util.h" +#include "ntp.h" #include "sensor_console.h" #include "co2_sensor.h" #include "led_effects.h" diff --git a/ampel-firmware/ampel-firmware.ino b/ampel-firmware/ampel-firmware.ino index 40efd11ee447d7de79a494e510ab52e756fbf7c8..c055bcb8aa8808b4b406aceea195c4b29ad31b35 100644 --- a/ampel-firmware/ampel-firmware.ino +++ b/ampel-firmware/ampel-firmware.ino @@ -88,10 +88,7 @@ void setup() { #ifdef AMPEL_WIFI wifi::connect(ampel.sensorId); - Serial.print(F("WiFi - Status: ")); - Serial.println(WiFi.status()); - - if (WiFi.status() == WL_CONNECTED) { + if (wifi::connected()) { # ifdef AMPEL_HTTP web_server::initialize(); # endif @@ -203,7 +200,7 @@ void checkFlashButton() { void keepServicesAlive() { #ifdef AMPEL_WIFI - if (WiFi.status() == WL_CONNECTED) { + if (wifi::connected()) { # if defined(ESP8266) //NOTE: Sadly, there seems to be a bug in the current MDNS implementation. // It stops working after 2 minutes. And forcing a restart leads to a memory leak. diff --git a/ampel-firmware/co2_sensor.cpp b/ampel-firmware/co2_sensor.cpp index 25a7102e19088ae65487234f248fada2ed25fd29..ab58ebf344711c335ecfd70e47857e3975ebfd55 100644 --- a/ampel-firmware/co2_sensor.cpp +++ b/ampel-firmware/co2_sensor.cpp @@ -1,5 +1,15 @@ #include "co2_sensor.h" +#include "config.h" +#include "ntp.h" +#include "led_effects.h" +#include "sensor_console.h" +#include <Wire.h> + +// The SCD30 from Sensirion is a high quality Nondispersive Infrared (NDIR) based CO₂ sensor capable of detecting 400 to 10000ppm with an accuracy of ±(30ppm+3%). +// https://github.com/sparkfun/SparkFun_SCD30_Arduino_Library +#include "src/lib/SparkFun_SCD30_Arduino_Library/src/SparkFun_SCD30_Arduino_Library.h" // From: http://librarymanager/All#SparkFun_SCD30 + namespace config { // UPPERCASE values should be defined in config.h uint16_t measurement_timestep = MEASUREMENT_TIMESTEP; // [s] Value between 2 and 1800 (range for SCD30 sensor). diff --git a/ampel-firmware/co2_sensor.h b/ampel-firmware/co2_sensor.h index d9eff8724f018e885f260060aaed58678c814092..2aea223bdebe2eb9e12254efc741afb331af7f51 100644 --- a/ampel-firmware/co2_sensor.h +++ b/ampel-firmware/co2_sensor.h @@ -1,14 +1,7 @@ #ifndef CO2_SENSOR_H_ #define CO2_SENSOR_H_ -// The SCD30 from Sensirion is a high quality Nondispersive Infrared (NDIR) based CO₂ sensor capable of detecting 400 to 10000ppm with an accuracy of ±(30ppm+3%). -// https://github.com/sparkfun/SparkFun_SCD30_Arduino_Library -#include "src/lib/SparkFun_SCD30_Arduino_Library/src/SparkFun_SCD30_Arduino_Library.h" // From: http://librarymanager/All#SparkFun_SCD30 -#include "config.h" -#include "led_effects.h" -#include "util.h" -#include "sensor_console.h" -#include <Wire.h> +#include <stdint.h> // For uint16_t namespace config { extern uint16_t measurement_timestep; // [s] Value between 2 and 1800 (range for SCD30 sensor) @@ -18,7 +11,6 @@ namespace config { } namespace sensor { - extern SCD30 scd30; extern uint16_t co2; extern float temperature; extern float humidity; diff --git a/ampel-firmware/config.public.h b/ampel-firmware/config.public.h index 44a2bc95b176e1fe6d281a5a1d72dfa7d9c8fd86..cf0325f388367658469cede1eb3b29874eca83bd 100644 --- a/ampel-firmware/config.public.h +++ b/ampel-firmware/config.public.h @@ -33,7 +33,6 @@ # define MEASUREMENT_TIMESTEP 60 // [s] Value between 2 and 1800 (range for SCD30 sensor) // How often should measurements be appended to CSV ? -// Probably a good idea to use a multiple of MEASUREMENT_TIMESTEP, so that averages can be calculated // Set to 0 if you want to send values after each measurement // WARNING: Writing too often might damage the ESP memory # define CSV_INTERVAL 300 // [s] @@ -54,7 +53,7 @@ // Should the sensor try to calibrate itself? // Sensirion recommends 7 days of continuous readings with at least 1 hour a day of 'fresh air' for self-calibration to complete. -# define AUTO_CALIBRATE_SENSOR true // [true / false] +# define AUTO_CALIBRATE_SENSOR false // [true / false] /** * LEDs @@ -106,12 +105,12 @@ # define ALLOW_MQTT_COMMANDS false // How often should measurements be sent to MQTT server? -// Probably a good idea to use a multiple of MEASUREMENT_TIMESTEP, so that averages can be calculated // Set to 0 if you want to send values after each measurement // # define MQTT_SENDING_INTERVAL MEASUREMENT_TIMESTEP * 5 // [s] # define MQTT_SENDING_INTERVAL 60 // [s] # define MQTT_SERVER "test.mosquitto.org" // MQTT server URL or IP address # define MQTT_PORT 8883 +# define MQTT_ENCRYPTED true // Set to false for unencrypted MQTT (e.g. with port 1883). If undefined, MQTT_ENCRYPTED will be set to true. # define MQTT_USER "" # define MQTT_PASSWORD "" @@ -120,10 +119,7 @@ */ // 1) Requires "MCCI LoRaWAN LMIC library", which will be automatically used with PlatformIO but should be added in "Arduino IDE". -// 2) Region and transceiver type should be specified in: -// * Arduino/libraries/MCCI_LoRaWAN_LMIC_library/project_config/lmic_project_config.h for Arduino IDE -// * platformio.ini for PlatformIO -// See https://github.com/mcci-catena/arduino-lmic#configuration for more information +// 2) If you need to, region and transceiver type can be specified in lorawan.cpp. Default is "Europe 868" // 3) It has been tested with "TTGO ESP32 SX1276 LoRa 868" and will only work with an ESP32 + LoRa modem // 4) In order to use LoRaWAN, a gateway should be close to the co2ampel, and an account, an application and a device should be registered, // e.g. on https://www.thethingsnetwork.org/docs/applications/ diff --git a/ampel-firmware/csv_writer.cpp b/ampel-firmware/csv_writer.cpp index d0828a40999dc7326d1ce19f9a4808be10dd6852..35972cde0447c34b6ff32a14a4627b684730b3b5 100644 --- a/ampel-firmware/csv_writer.cpp +++ b/ampel-firmware/csv_writer.cpp @@ -1,5 +1,10 @@ #include "csv_writer.h" +#include "config.h" +#include "ntp.h" +#include "led_effects.h" +#include "sensor_console.h" + namespace config { // Values should be defined in config.h uint16_t csv_interval = CSV_INTERVAL; // [s] diff --git a/ampel-firmware/csv_writer.h b/ampel-firmware/csv_writer.h index f85f34641aaf4f1915e773ad7c1675f846ecfa90..dcee7130c27a23e66d20fb8b390889b0fd53468e 100644 --- a/ampel-firmware/csv_writer.h +++ b/ampel-firmware/csv_writer.h @@ -11,11 +11,6 @@ # error Board should be either ESP8266 or ESP832 #endif -#include "config.h" -#include "util.h" -#include "led_effects.h" -#include "sensor_console.h" - namespace config { extern uint16_t csv_interval; // [s] } diff --git a/ampel-firmware/led_effects.cpp b/ampel-firmware/led_effects.cpp index 1a7eb4a3cef465a5eba2ab1f93cf2ffe15e546d8..4d0d59958aa06b05c54e7b86b406a4e03ea73750 100644 --- a/ampel-firmware/led_effects.cpp +++ b/ampel-firmware/led_effects.cpp @@ -1,4 +1,13 @@ #include "led_effects.h" + +#include "config.h" +#include "sensor_console.h" + +// Adafruit NeoPixel (Arduino library for controlling single-wire-based LED pixels and strip) +// https://github.com/adafruit/Adafruit_NeoPixel +// Documentation : http://adafruit.github.io/Adafruit_NeoPixel/html/class_adafruit___neo_pixel.html +#include "src/lib/Adafruit_NeoPixel/Adafruit_NeoPixel.h" + /***************************************************************** * Configuration * *****************************************************************/ @@ -12,7 +21,7 @@ namespace config { const uint8_t brightness_amplitude = config::max_brightness - config::min_brightness; const int kitt_tail = 3; // How many dimmer LEDs follow in K.I.T.T. wheel const uint16_t poor_air_quality_ppm = 1600; // Above this threshold, LED breathing effect is faster. - bool night_mode = false; //NOTE: Use a class instead? NightMode could then be another state. + bool display_led = true; // Will be set to false during "night mode". #if !defined(LED_COUNT) # define LED_COUNT 12 @@ -78,7 +87,7 @@ namespace led_effects { } void showColor(int32_t color) { - config::night_mode = true; // In order to avoid overwriting the desired color next time CO2 is displayed + config::display_led = false; // In order to avoid overwriting the desired color next time CO2 is displayed pixels.setBrightness(255); pixels.fill(color); pixels.show(); @@ -88,17 +97,21 @@ namespace led_effects { pixels.begin(); pixels.setBrightness(config::max_brightness); LEDsOff(); - sensor_console::defineCommand("night_mode", toggleNightMode, F("(Toggles night mode on/off)")); + sensor_console::defineIntCommand("led", turnLEDsOnOff, F("0/1 (Turns LEDs on/off)")); sensor_console::defineIntCommand("color", showColor, F("0xFF0015 (Shows color, specified as RGB, for debugging)")); } void toggleNightMode() { - config::night_mode = !config::night_mode; - if (config::night_mode) { - Serial.println(F("NIGHT MODE!")); - LEDsOff(); + turnLEDsOnOff(!config::display_led); + } + + void turnLEDsOnOff(int32_t display_led) { + config::display_led = display_led; + if (config::display_led) { + Serial.println(F("LEDs are on!")); } else { - Serial.println(F("DAY MODE!")); + Serial.println(F("Night mode!")); + LEDsOff(); } } @@ -106,7 +119,7 @@ namespace led_effects { void showWaitingLED(uint32_t color) { using namespace config; delay(80); - if (night_mode) { + if (!display_led) { return; } static uint16_t kitt_offset = 0; @@ -162,7 +175,7 @@ namespace led_effects { * Fills the whole ring with green, yellow, orange or black, depending on co2 input and CO2_TICKS. */ void displayCO2color(uint16_t co2) { - if (config::night_mode) { + if (!config::display_led) { return; } pixels.setBrightness(config::max_brightness); @@ -177,7 +190,7 @@ namespace led_effects { } void showRainbowWheel(uint16_t duration_ms) { - if (config::night_mode) { + if (!config::display_led) { return; } static uint16_t wheel_offset = 0; @@ -195,7 +208,7 @@ namespace led_effects { } void redAlert() { - if (config::night_mode) { + if (!config::display_led) { onBoardLEDOn(); delay(500); onBoardLEDOff(); @@ -218,7 +231,7 @@ namespace led_effects { * been released or after every LED has been turned off. */ bool countdownToZero() { - if (config::night_mode) { + if (!config::display_led) { Serial.println(F("Night mode. Not doing anything.")); delay(1000); // Wait for a while, to avoid coming back to this function too many times when button is pressed. return false; diff --git a/ampel-firmware/led_effects.h b/ampel-firmware/led_effects.h index 8f04d83e751a9de5f2110653d10a794d5494bd36..9f681a66c28fe80ce3880123f985acf50ffc8526 100644 --- a/ampel-firmware/led_effects.h +++ b/ampel-firmware/led_effects.h @@ -1,13 +1,7 @@ #ifndef LED_EFFECTS_H_INCLUDED #define LED_EFFECTS_H_INCLUDED -#include <Arduino.h> -#include "config.h" -#include "sensor_console.h" -// Adafruit NeoPixel (Arduino library for controlling single-wire-based LED pixels and strip) -// https://github.com/adafruit/Adafruit_NeoPixel -// Documentation : http://adafruit.github.io/Adafruit_NeoPixel/html/class_adafruit___neo_pixel.html -#include "src/lib/Adafruit_NeoPixel/Adafruit_NeoPixel.h" +#include <stdint.h> // For uint32_t namespace color { const uint32_t red = 0xFF0000; @@ -22,6 +16,7 @@ namespace led_effects { void onBoardLEDOff(); void onBoardLEDOn(); void toggleNightMode(); + void turnLEDsOnOff(int32_t); void LEDsOff(); void setupRing(); diff --git a/ampel-firmware/lorawan.cpp b/ampel-firmware/lorawan.cpp index 746893b3698137ff14417158e5a2728fd8ad4a25..5dfad5e5c1fadb1df3018bc142e438ef7a55a9a9 100644 --- a/ampel-firmware/lorawan.cpp +++ b/ampel-firmware/lorawan.cpp @@ -1,7 +1,42 @@ #include "lorawan.h" + #if defined(AMPEL_LORAWAN) && defined(ESP32) +#include "led_effects.h" +#include "sensor_console.h" +#include "util.h" +#include "ntp.h" + +/*** Define region and transceiver type, and ignore lmic_project_config.h from lmic library ***/ +// Those values are probably okay if you're in Europe. +#define ARDUINO_LMIC_PROJECT_CONFIG_H_SUPPRESS +#define CFG_eu868 1 +#define CFG_sx1276_radio 1 +/****************************************************************************************/ + +// Requires "MCCI LoRaWAN LMIC library", which will be automatically used with PlatformIO but should be added in "Arduino IDE" +// Tested successfully with v3.2.0 and connected to a thethingsnetwork.org app. +#include <lmic.h> +#include <SPI.h> +#include <hal/hal.h> +#include <arduino_lmic_hal_boards.h> + namespace config { +#if defined(CFG_eu868) + const char *lorawan_frequency_plan = "Europe 868"; +#elif defined(CFG_us915) + const char *lorawan_frequency_plan = "US 915"; +#elif defined(CFG_au915) + const char *lorawan_frequency_plan = "Australia 915"; +#elif defined(CFG_as923) + const char *lorawan_frequency_plan = "Asia 923"; +#elif defined(CFG_kr920) + const char *lorawan_frequency_plan = "Korea 920"; +#elif defined(CFG_in866) + const char *lorawan_frequency_plan = "India 866"; +#else +# error "Region should be specified" +#endif // Values should be defined in config.h uint16_t lorawan_sending_interval = LORAWAN_SENDING_INTERVAL; // [s] @@ -36,7 +71,9 @@ namespace lorawan { char last_transmission[23] = ""; void initialize() { - Serial.println(F("Starting LoRaWAN. Frequency plan : " LMIC_FREQUENCY_PLAN " MHz.")); + Serial.print(F("Starting LoRaWAN. Frequency plan : ")); + Serial.print(config::lorawan_frequency_plan); + Serial.println(F(" MHz.")); // More info about pin mapping : https://github.com/mcci-catena/arduino-lmic#pin-mapping // Has been tested successfully with ESP32 TTGO LoRa32 V1, and might work with other ESP32+LoRa boards. @@ -51,7 +88,7 @@ namespace lorawan { } // Checks if OTAA is connected, or if payload should be sent. - // NOTE: while a transaction is in process (i.e. until the TXcomplete event has been received, no blocking code (e.g. delay loops etc.) are allowed, otherwise the LMIC/OS code might miss the event. + // NOTE: while a transaction is in process (i.e. until the TXcomplete event has been received), no blocking code (e.g. delay loops etc.) are allowed, otherwise the LMIC/OS code might miss the event. // If this rule is not followed, a typical symptom is that the first send is ok and all following ones end with the 'TX not complete' failure. void process() { os_runloop_once(); diff --git a/ampel-firmware/lorawan.h b/ampel-firmware/lorawan.h index 45f462b2ef761066797ccd2ff114762088b5c628..ed005f5c3a09e38591009e487b97a6a74e2d2b28 100644 --- a/ampel-firmware/lorawan.h +++ b/ampel-firmware/lorawan.h @@ -3,39 +3,15 @@ #include "config.h" -#if defined(AMPEL_LORAWAN) && defined(ESP32) -#include <Arduino.h> -// Requires "MCCI LoRaWAN LMIC library", which will be automatically used with PlatformIO but should be added in "Arduino IDE". -// Tested successfully with v3.2.0 and connected to a thethingsnetwork.org app. -#include <lmic.h> -#include <hal/hal.h> -#include <arduino_lmic_hal_boards.h> -#include <SPI.h> +# if defined(AMPEL_LORAWAN) && defined(ESP32) -#include "led_effects.h" -#include "sensor_console.h" -#include "util.h" +#include <stdint.h> // For uint32_t & uint16_t namespace config { extern uint16_t lorawan_sending_interval; // [s] + extern const char *lorawan_frequency_plan; // e.g. "Europe 868" } -#if defined(CFG_eu868) -# define LMIC_FREQUENCY_PLAN "Europe 868" -#elif defined(CFG_us915) -# define LMIC_FREQUENCY_PLAN "US 915" -#elif defined(CFG_au915) -# define LMIC_FREQUENCY_PLAN "Australia 915" -#elif defined(CFG_as923) -# define LMIC_FREQUENCY_PLAN "Asia 923" -#elif defined(CFG_kr920) -# define LMIC_FREQUENCY_PLAN "Korea 920" -#elif defined(CFG_in866) -# define LMIC_FREQUENCY_PLAN "India 866" -#else -# error "Region should be specified" -#endif - namespace lorawan { extern bool waiting_for_confirmation; extern bool connected; @@ -47,5 +23,5 @@ namespace lorawan { void setLoRaInterval(int32_t sending_interval); } -#endif +# endif #endif diff --git a/ampel-firmware/mqtt.cpp b/ampel-firmware/mqtt.cpp index 8c61696569dbf14dbf520ff4dacd0f462ec86b03..57b5cd026f57586339e3d90eeb91aa204c242300 100644 --- a/ampel-firmware/mqtt.cpp +++ b/ampel-firmware/mqtt.cpp @@ -1,5 +1,18 @@ #include "mqtt.h" +#include "config.h" +#include "led_effects.h" +#include "sensor_console.h" +#include "wifi_util.h" +#include "ntp.h" +#include "src/lib/PubSubClient/src/PubSubClient.h" + +#if defined(ESP8266) +# include <ESP8266WiFi.h> +#elif defined(ESP32) +# include <WiFi.h> +#endif + namespace config { // Values should be defined in config.h uint16_t mqtt_sending_interval = MQTT_SENDING_INTERVAL; // [s] @@ -12,10 +25,16 @@ namespace config { const bool allow_mqtt_commands = ALLOW_MQTT_COMMANDS; const unsigned long wait_after_fail = 900; // [s] Wait 15 minutes after an MQTT connection fail, before trying again. } -#if defined(ESP32) -# include <WiFiClientSecure.h> -#endif + +#if MQTT_ENCRYPTED +# if defined(ESP32) +# include <WiFiClientSecure.h> +# endif WiFiClientSecure espClient; +#else +WiFiClient espClient; +#endif + PubSubClient mqttClient(espClient); namespace mqtt { @@ -30,9 +49,11 @@ namespace mqtt { void initialize(const char *sensorId) { json_sensor_format = PSTR("{\"time\":\"%s\", \"co2\":%d, \"temp\":%.1f, \"rh\":%.1f}"); snprintf(publish_topic, sizeof(publish_topic), "CO2sensors/%s", sensorId); +#if MQTT_ENCRYPTED // The sensor doesn't check the fingerprint of the MQTT broker, because otherwise this fingerprint should be updated // on the sensor every 3 months. The connection can still be encrypted, though: espClient.setInsecure(); // If not available for ESP32, please update Arduino IDE / PlatformIO +#endif mqttClient.setServer(config::mqtt_server, config::mqtt_port); sensor_console::defineIntCommand("mqtt", setMQTTinterval, F("60 (Sets MQTT sending interval, in s)")); @@ -91,7 +112,13 @@ namespace mqtt { // No WIFI return; } - Serial.print(F("MQTT - Attempting connection...")); + + Serial.print(F("MQTT - Attempting connection to ")); + Serial.print(MQTT_SERVER); + Serial.print(MQTT_ENCRYPTED ? F(" (Encrypted") : F(" (Unencrypted")); + Serial.print(F(", port ")); + Serial.print(MQTT_PORT); + Serial.print(F(") ...")); led_effects::onBoardLEDOn(); // Wait for connection, at most 15s (default) diff --git a/ampel-firmware/mqtt.h b/ampel-firmware/mqtt.h index 4d1088345f061646e9c05fb95e378fbe7feec252..0e39b09f7c88300b93de0b98741a63ffb664a36c 100644 --- a/ampel-firmware/mqtt.h +++ b/ampel-firmware/mqtt.h @@ -1,12 +1,12 @@ #ifndef MQTT_H_INCLUDED #define MQTT_H_INCLUDED -#include <Arduino.h> +#include <stdint.h> // For uint32_t & uint16_t + #include "config.h" -#include "led_effects.h" -#include "sensor_console.h" -#include "src/lib/PubSubClient/src/PubSubClient.h" -#include "wifi_util.h" +#if !defined(MQTT_ENCRYPTED) +# define MQTT_ENCRYPTED true // Old config files might not define it, and encryption was on by default. +#endif namespace config { extern uint16_t mqtt_sending_interval; // [s] diff --git a/ampel-firmware/ntp.cpp b/ampel-firmware/ntp.cpp new file mode 100644 index 0000000000000000000000000000000000000000..468a00bff3915e75f2a09ba63c4c5c3aa04b75fb --- /dev/null +++ b/ampel-firmware/ntp.cpp @@ -0,0 +1,49 @@ +#include "ntp.h" +#include "sensor_console.h" +#include "config.h" +#include <WiFiUdp.h> // required for NTP +#include "src/lib/NTPClient/NTPClient.h" // NTP + +namespace config { + const char *ntp_server = NTP_SERVER; + const long utc_offset_in_seconds = UTC_OFFSET_IN_SECONDS; // UTC+1 +} + +//NOTE: ESP32 sometimes couldn't access the NTP server, and every loop would take +1000ms +// ifdefs could be used to define functions specific to ESP32, e.g. with configTime +namespace ntp { + WiFiUDP ntpUDP; + NTPClient timeClient(ntpUDP, config::ntp_server, config::utc_offset_in_seconds, 60000UL); + bool connected_at_least_once = false; + void setLocalTime(int32_t unix_seconds); + + void initialize() { + timeClient.begin(); + sensor_console::defineIntCommand("set_time", ntp::setLocalTime, F("1618829570 (Sets time to the given UNIX time)")); + } + + void update() { + connected_at_least_once |= timeClient.update(); + } + + void getLocalTime(char *timestamp) { + timeClient.getFormattedDate(timestamp); + } + + void setLocalTime(int32_t unix_seconds) { + char time[23]; + timeClient.getFormattedDate(time); + Serial.print(F("Current time : ")); + Serial.println(time); + if (connected_at_least_once) { + Serial.println(F("NTP update already happened. Not changing anything.")); + return; + } + Serial.print(F("Setting UNIX time to : ")); + Serial.println(unix_seconds); + timeClient.setEpochTime(unix_seconds - seconds()); + timeClient.getFormattedDate(time); + Serial.print(F("Current time : ")); + Serial.println(time); + } +} diff --git a/ampel-firmware/ntp.h b/ampel-firmware/ntp.h new file mode 100644 index 0000000000000000000000000000000000000000..fe1647c7da1f4ca302c6dba3c6bba33e5595d873 --- /dev/null +++ b/ampel-firmware/ntp.h @@ -0,0 +1,13 @@ +#ifndef AMPEL_TIME_H_INCLUDED +#define AMPEL_TIME_H_INCLUDED + +namespace ntp { + void initialize(); + void update(); + void getLocalTime(char *timestamp); +} + +//NOTE: Only use seconds() for duration comparison, not timestamps comparison. Otherwise, problems happen when millis roll over. +#define seconds() (millis() / 1000UL) + +#endif diff --git a/ampel-firmware/sensor_console.h b/ampel-firmware/sensor_console.h index 5cf4450d19535d19e871f5d4d48b11d09521070e..9594317b575bf53db0da92f34e62613423fe6a75 100644 --- a/ampel-firmware/sensor_console.h +++ b/ampel-firmware/sensor_console.h @@ -1,9 +1,9 @@ #ifndef SENSOR_CONSOLE_H_INCLUDED #define SENSOR_CONSOLE_H_INCLUDED -#include <Arduino.h> +#include <Arduino.h> // For Flash strings, uint8_t and int32_t /** Other scripts can use this namespace, in order to define commands, via callbacks. - * Those callbacks can then be used to send commands to the sensor (reset, calibrate, night mode, ...) + * Those callbacks can then be used to send commands to the sensor (reset, calibrate, led on/off, ...) * The callbacks can either have no parameter, or one int32_t parameter. */ @@ -12,7 +12,7 @@ namespace sensor_console { void defineIntCommand(const char *name, void (*function)(int32_t), const __FlashStringHelper *doc_fstring); void defineStringCommand(const char *name, void (*function)(char*), const __FlashStringHelper *doc_fstring); - void processSerialInput(const byte in_byte); + void processSerialInput(const uint8_t in_byte); void execute(const char *command_line); } diff --git a/ampel-firmware/src/lib/Adafruit_NeoPixel/Adafruit_NeoPixel.cpp b/ampel-firmware/src/lib/Adafruit_NeoPixel/Adafruit_NeoPixel.cpp index cb49b4ce589277049cf3b86be5cdfd8d4372c4c4..a1216d9c72bc6980ef6099ae60049cfdcbd0d497 100644 --- a/ampel-firmware/src/lib/Adafruit_NeoPixel/Adafruit_NeoPixel.cpp +++ b/ampel-firmware/src/lib/Adafruit_NeoPixel/Adafruit_NeoPixel.cpp @@ -46,7 +46,7 @@ #include "Adafruit_NeoPixel.h" #if defined(TARGET_LPC1768) - #include <time.h> +#include <time.h> #endif #if defined(NRF52) || defined(NRF52_SERIES) @@ -57,6 +57,14 @@ //#define NRF52_DISABLE_INT #endif +#if defined(ARDUINO_ARCH_NRF52840) +#if defined __has_include +#if __has_include(<pinDefinitions.h>) +#include <pinDefinitions.h> +#endif +#endif +#endif + /*! @brief NeoPixel constructor when length, pin and pixel type are known at compile-time. @@ -69,11 +77,21 @@ pixel. @return Adafruit_NeoPixel object. Call the begin() function before use. */ -Adafruit_NeoPixel::Adafruit_NeoPixel(uint16_t n, uint16_t p, neoPixelType t) : - begun(false), brightness(0), pixels(NULL), endTime(0) { +Adafruit_NeoPixel::Adafruit_NeoPixel(uint16_t n, int16_t p, neoPixelType t) + : begun(false), brightness(0), pixels(NULL), endTime(0) { updateType(t); updateLength(n); setPin(p); +#if defined(ARDUINO_ARCH_RP2040) + // Find a free SM on one of the PIO's + sm = pio_claim_unused_sm(pio, false); // don't panic + // Try pio1 if SM not found + if (sm < 0) { + pio = pio1; + sm = pio_claim_unused_sm(pio, true); // panic if no SM is free + } + init = true; +#endif } /*! @@ -86,12 +104,13 @@ Adafruit_NeoPixel::Adafruit_NeoPixel(uint16_t n, uint16_t p, neoPixelType t) : 'new' keyword with the first constructor syntax (length, pin, type). */ -Adafruit_NeoPixel::Adafruit_NeoPixel() : +Adafruit_NeoPixel::Adafruit_NeoPixel() + : #if defined(NEO_KHZ400) - is800KHz(true), + is800KHz(true), #endif - begun(false), numLEDs(0), numBytes(0), pin(-1), brightness(0), pixels(NULL), - rOffset(1), gOffset(0), bOffset(2), wOffset(1), endTime(0) { + begun(false), numLEDs(0), numBytes(0), pin(-1), brightness(0), + pixels(NULL), rOffset(1), gOffset(0), bOffset(2), wOffset(1), endTime(0) { } /*! @@ -99,14 +118,15 @@ Adafruit_NeoPixel::Adafruit_NeoPixel() : */ Adafruit_NeoPixel::~Adafruit_NeoPixel() { free(pixels); - if(pin >= 0) pinMode(pin, INPUT); + if (pin >= 0) + pinMode(pin, INPUT); } /*! @brief Configure NeoPixel pin for output. */ void Adafruit_NeoPixel::begin(void) { - if(pin >= 0) { + if (pin >= 0) { pinMode(pin, OUTPUT); digitalWrite(pin, LOW); } @@ -128,7 +148,7 @@ void Adafruit_NeoPixel::updateLength(uint16_t n) { // Allocate new data -- note: ALL PIXELS ARE CLEARED numBytes = n * ((wOffset == rOffset) ? 3 : 4); - if((pixels = (uint8_t *)malloc(numBytes))) { + if ((pixels = (uint8_t *)malloc(numBytes))) { memset(pixels, 0, numBytes); numLEDs = n; } else { @@ -159,36 +179,71 @@ void Adafruit_NeoPixel::updateType(neoPixelType t) { wOffset = (t >> 6) & 0b11; // See notes in header file rOffset = (t >> 4) & 0b11; // regarding R/G/B/W offsets gOffset = (t >> 2) & 0b11; - bOffset = t & 0b11; + bOffset = t & 0b11; #if defined(NEO_KHZ400) - is800KHz = (t < 256); // 400 KHz flag is 1<<8 + is800KHz = (t < 256); // 400 KHz flag is 1<<8 #endif // If bytes-per-pixel has changed (and pixel data was previously // allocated), re-allocate to new size. Will clear any data. - if(pixels) { + if (pixels) { bool newThreeBytesPerPixel = (wOffset == rOffset); - if(newThreeBytesPerPixel != oldThreeBytesPerPixel) updateLength(numLEDs); + if (newThreeBytesPerPixel != oldThreeBytesPerPixel) + updateLength(numLEDs); + } +} + +// RP2040 specific driver +#if defined(ARDUINO_ARCH_RP2040) +void Adafruit_NeoPixel::rp2040Init(uint8_t pin, bool is800KHz) +{ + uint offset = pio_add_program(pio, &ws2812_program); + + if (is800KHz) + { + // 800kHz, 8 bit transfers + ws2812_program_init(pio, sm, offset, pin, 800000, 8); + } + else + { + // 400kHz, 8 bit transfers + ws2812_program_init(pio, sm, offset, pin, 400000, 8); } } +// Not a user API +void Adafruit_NeoPixel::rp2040Show(uint8_t pin, uint8_t *pixels, uint32_t numBytes, bool is800KHz) +{ + if (this->init) + { + // On first pass through initialise the PIO + rp2040Init(pin, is800KHz); + this->init = false; + } + + while(numBytes--) + // Bits for transmission must be shifted to top 8 bits + pio_sm_put_blocking(pio, sm, ((uint32_t)*pixels++)<< 24); +} + +#endif #if defined(ESP8266) // ESP8266 show() is external to enforce ICACHE_RAM_ATTR execution -extern "C" void ICACHE_RAM_ATTR espShow( - uint16_t pin, uint8_t *pixels, uint32_t numBytes, uint8_t type); +extern "C" IRAM_ATTR void espShow(uint16_t pin, uint8_t *pixels, + uint32_t numBytes, uint8_t type); #elif defined(ESP32) -extern "C" void espShow( - uint16_t pin, uint8_t *pixels, uint32_t numBytes, uint8_t type); +extern "C" void espShow(uint16_t pin, uint8_t *pixels, uint32_t numBytes, + uint8_t type); #endif // ESP8266 -#if defined(K210) +#if defined(K210) #define KENDRYTE_K210 1 #endif #if defined(KENDRYTE_K210) -extern "C" void k210Show( - uint8_t pin, uint8_t *pixels, uint32_t numBytes, boolean is800KHz); -#endif //KENDRYTE_K210 +extern "C" void k210Show(uint8_t pin, uint8_t *pixels, uint32_t numBytes, + boolean is800KHz); +#endif // KENDRYTE_K210 /*! @brief Transmit pixel data in RAM to NeoPixels. @note On most architectures, interrupts are temporarily disabled in @@ -202,7 +257,8 @@ extern "C" void k210Show( */ void Adafruit_NeoPixel::show(void) { - if(!pixels) return; + if (!pixels) + return; // Data latch = 300+ microsecond pause in the output stream. Rather than // put a delay at the end of the function, the ending time is noted and @@ -210,36 +266,35 @@ void Adafruit_NeoPixel::show(void) { // subsequent round of data until the latch time has elapsed. This // allows the mainline code to start generating the next frame of data // rather than stalling for the latch. - while(!canShow()); - // endTime is a private member (rather than global var) so that multiple - // instances on different pins can be quickly issued in succession (each - // instance doesn't delay the next). - - // In order to make this code runtime-configurable to work with any pin, - // SBI/CBI instructions are eschewed in favor of full PORT writes via the - // OUT or ST instructions. It relies on two facts: that peripheral - // functions (such as PWM) take precedence on output pins, so our PORT- - // wide writes won't interfere, and that interrupts are globally disabled - // while data is being issued to the LEDs, so no other code will be - // accessing the PORT. The code takes an initial 'snapshot' of the PORT - // state, computes 'pin high' and 'pin low' values, and writes these back - // to the PORT register as needed. - - // NRF52 may use PWM + DMA (if available), may not need to disable interrupt -#if !( defined(NRF52) || defined(NRF52_SERIES) ) + while (!canShow()) + ; + // endTime is a private member (rather than global var) so that multiple + // instances on different pins can be quickly issued in succession (each + // instance doesn't delay the next). + + // In order to make this code runtime-configurable to work with any pin, + // SBI/CBI instructions are eschewed in favor of full PORT writes via the + // OUT or ST instructions. It relies on two facts: that peripheral + // functions (such as PWM) take precedence on output pins, so our PORT- + // wide writes won't interfere, and that interrupts are globally disabled + // while data is being issued to the LEDs, so no other code will be + // accessing the PORT. The code takes an initial 'snapshot' of the PORT + // state, computes 'pin high' and 'pin low' values, and writes these back + // to the PORT register as needed. + + // NRF52 may use PWM + DMA (if available), may not need to disable interrupt +#if !(defined(NRF52) || defined(NRF52_SERIES)) noInterrupts(); // Need 100% focus on instruction timing #endif #if defined(__AVR__) -// AVR MCUs -- ATmega & ATtiny (no XMEGA) --------------------------------- + // AVR MCUs -- ATmega & ATtiny (no XMEGA) --------------------------------- - volatile uint16_t - i = numBytes; // Loop counter - volatile uint8_t - *ptr = pixels, // Pointer to next byte - b = *ptr++, // Current byte value - hi, // PORT w/output bit set high - lo; // PORT w/output bit set low + volatile uint16_t i = numBytes; // Loop counter + volatile uint8_t *ptr = pixels, // Pointer to next byte + b = *ptr++, // Current byte value + hi, // PORT w/output bit set high + lo; // PORT w/output bit set low // Hand-tuned assembly code issues data to the LED drivers at a specific // rate. There's separate code for different CPU speeds (8, 12, 16 MHz) @@ -259,10 +314,10 @@ void Adafruit_NeoPixel::show(void) { #if (F_CPU >= 7400000UL) && (F_CPU <= 9500000UL) #if defined(NEO_KHZ400) // 800 KHz check needed only if 400 KHz support enabled - if(is800KHz) { + if (is800KHz) { #endif - volatile uint8_t n1, n2 = 0; // First, next bits out + volatile uint8_t n1, n2 = 0; // First, next bits out // Squeezing an 800 KHz stream out of an 8 MHz chip requires code // specific to each PORT register. @@ -273,14 +328,15 @@ void Adafruit_NeoPixel::show(void) { // PORTD OUTPUT ---------------------------------------------------- #if defined(PORTD) - #if defined(PORTB) || defined(PORTC) || defined(PORTF) - if(port == &PORTD) { - #endif // defined(PORTB/C/F) +#if defined(PORTB) || defined(PORTC) || defined(PORTF) + if (port == &PORTD) { +#endif // defined(PORTB/C/F) - hi = PORTD | pinMask; + hi = PORTD | pinMask; lo = PORTD & ~pinMask; n1 = lo; - if(b & 0x80) n1 = hi; + if (b & 0x80) + n1 = hi; // Dirty trick: RJMPs proceeding to the next instruction are used // to delay two clock cycles in one instruction word (rather than @@ -289,360 +345,618 @@ void Adafruit_NeoPixel::show(void) { // relative branch. asm volatile( - "headD:" "\n\t" // Clk Pseudocode - // Bit 7: - "out %[port] , %[hi]" "\n\t" // 1 PORT = hi - "mov %[n2] , %[lo]" "\n\t" // 1 n2 = lo - "out %[port] , %[n1]" "\n\t" // 1 PORT = n1 - "rjmp .+0" "\n\t" // 2 nop nop - "sbrc %[byte] , 6" "\n\t" // 1-2 if(b & 0x40) - "mov %[n2] , %[hi]" "\n\t" // 0-1 n2 = hi - "out %[port] , %[lo]" "\n\t" // 1 PORT = lo - "rjmp .+0" "\n\t" // 2 nop nop - // Bit 6: - "out %[port] , %[hi]" "\n\t" // 1 PORT = hi - "mov %[n1] , %[lo]" "\n\t" // 1 n1 = lo - "out %[port] , %[n2]" "\n\t" // 1 PORT = n2 - "rjmp .+0" "\n\t" // 2 nop nop - "sbrc %[byte] , 5" "\n\t" // 1-2 if(b & 0x20) - "mov %[n1] , %[hi]" "\n\t" // 0-1 n1 = hi - "out %[port] , %[lo]" "\n\t" // 1 PORT = lo - "rjmp .+0" "\n\t" // 2 nop nop - // Bit 5: - "out %[port] , %[hi]" "\n\t" // 1 PORT = hi - "mov %[n2] , %[lo]" "\n\t" // 1 n2 = lo - "out %[port] , %[n1]" "\n\t" // 1 PORT = n1 - "rjmp .+0" "\n\t" // 2 nop nop - "sbrc %[byte] , 4" "\n\t" // 1-2 if(b & 0x10) - "mov %[n2] , %[hi]" "\n\t" // 0-1 n2 = hi - "out %[port] , %[lo]" "\n\t" // 1 PORT = lo - "rjmp .+0" "\n\t" // 2 nop nop - // Bit 4: - "out %[port] , %[hi]" "\n\t" // 1 PORT = hi - "mov %[n1] , %[lo]" "\n\t" // 1 n1 = lo - "out %[port] , %[n2]" "\n\t" // 1 PORT = n2 - "rjmp .+0" "\n\t" // 2 nop nop - "sbrc %[byte] , 3" "\n\t" // 1-2 if(b & 0x08) - "mov %[n1] , %[hi]" "\n\t" // 0-1 n1 = hi - "out %[port] , %[lo]" "\n\t" // 1 PORT = lo - "rjmp .+0" "\n\t" // 2 nop nop - // Bit 3: - "out %[port] , %[hi]" "\n\t" // 1 PORT = hi - "mov %[n2] , %[lo]" "\n\t" // 1 n2 = lo - "out %[port] , %[n1]" "\n\t" // 1 PORT = n1 - "rjmp .+0" "\n\t" // 2 nop nop - "sbrc %[byte] , 2" "\n\t" // 1-2 if(b & 0x04) - "mov %[n2] , %[hi]" "\n\t" // 0-1 n2 = hi - "out %[port] , %[lo]" "\n\t" // 1 PORT = lo - "rjmp .+0" "\n\t" // 2 nop nop - // Bit 2: - "out %[port] , %[hi]" "\n\t" // 1 PORT = hi - "mov %[n1] , %[lo]" "\n\t" // 1 n1 = lo - "out %[port] , %[n2]" "\n\t" // 1 PORT = n2 - "rjmp .+0" "\n\t" // 2 nop nop - "sbrc %[byte] , 1" "\n\t" // 1-2 if(b & 0x02) - "mov %[n1] , %[hi]" "\n\t" // 0-1 n1 = hi - "out %[port] , %[lo]" "\n\t" // 1 PORT = lo - "rjmp .+0" "\n\t" // 2 nop nop - // Bit 1: - "out %[port] , %[hi]" "\n\t" // 1 PORT = hi - "mov %[n2] , %[lo]" "\n\t" // 1 n2 = lo - "out %[port] , %[n1]" "\n\t" // 1 PORT = n1 - "rjmp .+0" "\n\t" // 2 nop nop - "sbrc %[byte] , 0" "\n\t" // 1-2 if(b & 0x01) - "mov %[n2] , %[hi]" "\n\t" // 0-1 n2 = hi - "out %[port] , %[lo]" "\n\t" // 1 PORT = lo - "sbiw %[count], 1" "\n\t" // 2 i-- (don't act on Z flag yet) - // Bit 0: - "out %[port] , %[hi]" "\n\t" // 1 PORT = hi - "mov %[n1] , %[lo]" "\n\t" // 1 n1 = lo - "out %[port] , %[n2]" "\n\t" // 1 PORT = n2 - "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ - "sbrc %[byte] , 7" "\n\t" // 1-2 if(b & 0x80) - "mov %[n1] , %[hi]" "\n\t" // 0-1 n1 = hi - "out %[port] , %[lo]" "\n\t" // 1 PORT = lo - "brne headD" "\n" // 2 while(i) (Z flag set above) - : [byte] "+r" (b), - [n1] "+r" (n1), - [n2] "+r" (n2), - [count] "+w" (i) - : [port] "I" (_SFR_IO_ADDR(PORTD)), - [ptr] "e" (ptr), - [hi] "r" (hi), - [lo] "r" (lo)); - - #if defined(PORTB) || defined(PORTC) || defined(PORTF) + "headD:" + "\n\t" // Clk Pseudocode + // Bit 7: + "out %[port] , %[hi]" + "\n\t" // 1 PORT = hi + "mov %[n2] , %[lo]" + "\n\t" // 1 n2 = lo + "out %[port] , %[n1]" + "\n\t" // 1 PORT = n1 + "rjmp .+0" + "\n\t" // 2 nop nop + "sbrc %[byte] , 6" + "\n\t" // 1-2 if(b & 0x40) + "mov %[n2] , %[hi]" + "\n\t" // 0-1 n2 = hi + "out %[port] , %[lo]" + "\n\t" // 1 PORT = lo + "rjmp .+0" + "\n\t" // 2 nop nop + // Bit 6: + "out %[port] , %[hi]" + "\n\t" // 1 PORT = hi + "mov %[n1] , %[lo]" + "\n\t" // 1 n1 = lo + "out %[port] , %[n2]" + "\n\t" // 1 PORT = n2 + "rjmp .+0" + "\n\t" // 2 nop nop + "sbrc %[byte] , 5" + "\n\t" // 1-2 if(b & 0x20) + "mov %[n1] , %[hi]" + "\n\t" // 0-1 n1 = hi + "out %[port] , %[lo]" + "\n\t" // 1 PORT = lo + "rjmp .+0" + "\n\t" // 2 nop nop + // Bit 5: + "out %[port] , %[hi]" + "\n\t" // 1 PORT = hi + "mov %[n2] , %[lo]" + "\n\t" // 1 n2 = lo + "out %[port] , %[n1]" + "\n\t" // 1 PORT = n1 + "rjmp .+0" + "\n\t" // 2 nop nop + "sbrc %[byte] , 4" + "\n\t" // 1-2 if(b & 0x10) + "mov %[n2] , %[hi]" + "\n\t" // 0-1 n2 = hi + "out %[port] , %[lo]" + "\n\t" // 1 PORT = lo + "rjmp .+0" + "\n\t" // 2 nop nop + // Bit 4: + "out %[port] , %[hi]" + "\n\t" // 1 PORT = hi + "mov %[n1] , %[lo]" + "\n\t" // 1 n1 = lo + "out %[port] , %[n2]" + "\n\t" // 1 PORT = n2 + "rjmp .+0" + "\n\t" // 2 nop nop + "sbrc %[byte] , 3" + "\n\t" // 1-2 if(b & 0x08) + "mov %[n1] , %[hi]" + "\n\t" // 0-1 n1 = hi + "out %[port] , %[lo]" + "\n\t" // 1 PORT = lo + "rjmp .+0" + "\n\t" // 2 nop nop + // Bit 3: + "out %[port] , %[hi]" + "\n\t" // 1 PORT = hi + "mov %[n2] , %[lo]" + "\n\t" // 1 n2 = lo + "out %[port] , %[n1]" + "\n\t" // 1 PORT = n1 + "rjmp .+0" + "\n\t" // 2 nop nop + "sbrc %[byte] , 2" + "\n\t" // 1-2 if(b & 0x04) + "mov %[n2] , %[hi]" + "\n\t" // 0-1 n2 = hi + "out %[port] , %[lo]" + "\n\t" // 1 PORT = lo + "rjmp .+0" + "\n\t" // 2 nop nop + // Bit 2: + "out %[port] , %[hi]" + "\n\t" // 1 PORT = hi + "mov %[n1] , %[lo]" + "\n\t" // 1 n1 = lo + "out %[port] , %[n2]" + "\n\t" // 1 PORT = n2 + "rjmp .+0" + "\n\t" // 2 nop nop + "sbrc %[byte] , 1" + "\n\t" // 1-2 if(b & 0x02) + "mov %[n1] , %[hi]" + "\n\t" // 0-1 n1 = hi + "out %[port] , %[lo]" + "\n\t" // 1 PORT = lo + "rjmp .+0" + "\n\t" // 2 nop nop + // Bit 1: + "out %[port] , %[hi]" + "\n\t" // 1 PORT = hi + "mov %[n2] , %[lo]" + "\n\t" // 1 n2 = lo + "out %[port] , %[n1]" + "\n\t" // 1 PORT = n1 + "rjmp .+0" + "\n\t" // 2 nop nop + "sbrc %[byte] , 0" + "\n\t" // 1-2 if(b & 0x01) + "mov %[n2] , %[hi]" + "\n\t" // 0-1 n2 = hi + "out %[port] , %[lo]" + "\n\t" // 1 PORT = lo + "sbiw %[count], 1" + "\n\t" // 2 i-- (don't act on Z flag yet) + // Bit 0: + "out %[port] , %[hi]" + "\n\t" // 1 PORT = hi + "mov %[n1] , %[lo]" + "\n\t" // 1 n1 = lo + "out %[port] , %[n2]" + "\n\t" // 1 PORT = n2 + "ld %[byte] , %a[ptr]+" + "\n\t" // 2 b = *ptr++ + "sbrc %[byte] , 7" + "\n\t" // 1-2 if(b & 0x80) + "mov %[n1] , %[hi]" + "\n\t" // 0-1 n1 = hi + "out %[port] , %[lo]" + "\n\t" // 1 PORT = lo + "brne headD" + "\n" // 2 while(i) (Z flag set above) + : [byte] "+r"(b), [n1] "+r"(n1), [n2] "+r"(n2), [count] "+w"(i) + : [port] "I"(_SFR_IO_ADDR(PORTD)), [ptr] "e"(ptr), [hi] "r"(hi), + [lo] "r"(lo)); + +#if defined(PORTB) || defined(PORTC) || defined(PORTF) } else // other PORT(s) - #endif // defined(PORTB/C/F) +#endif // defined(PORTB/C/F) #endif // defined(PORTD) // PORTB OUTPUT ---------------------------------------------------- #if defined(PORTB) - #if defined(PORTD) || defined(PORTC) || defined(PORTF) - if(port == &PORTB) { - #endif // defined(PORTD/C/F) +#if defined(PORTD) || defined(PORTC) || defined(PORTF) + if (port == &PORTB) { +#endif // defined(PORTD/C/F) // Same as above, just switched to PORTB and stripped of comments. - hi = PORTB | pinMask; + hi = PORTB | pinMask; lo = PORTB & ~pinMask; n1 = lo; - if(b & 0x80) n1 = hi; + if (b & 0x80) + n1 = hi; asm volatile( - "headB:" "\n\t" - "out %[port] , %[hi]" "\n\t" - "mov %[n2] , %[lo]" "\n\t" - "out %[port] , %[n1]" "\n\t" - "rjmp .+0" "\n\t" - "sbrc %[byte] , 6" "\n\t" - "mov %[n2] , %[hi]" "\n\t" - "out %[port] , %[lo]" "\n\t" - "rjmp .+0" "\n\t" - "out %[port] , %[hi]" "\n\t" - "mov %[n1] , %[lo]" "\n\t" - "out %[port] , %[n2]" "\n\t" - "rjmp .+0" "\n\t" - "sbrc %[byte] , 5" "\n\t" - "mov %[n1] , %[hi]" "\n\t" - "out %[port] , %[lo]" "\n\t" - "rjmp .+0" "\n\t" - "out %[port] , %[hi]" "\n\t" - "mov %[n2] , %[lo]" "\n\t" - "out %[port] , %[n1]" "\n\t" - "rjmp .+0" "\n\t" - "sbrc %[byte] , 4" "\n\t" - "mov %[n2] , %[hi]" "\n\t" - "out %[port] , %[lo]" "\n\t" - "rjmp .+0" "\n\t" - "out %[port] , %[hi]" "\n\t" - "mov %[n1] , %[lo]" "\n\t" - "out %[port] , %[n2]" "\n\t" - "rjmp .+0" "\n\t" - "sbrc %[byte] , 3" "\n\t" - "mov %[n1] , %[hi]" "\n\t" - "out %[port] , %[lo]" "\n\t" - "rjmp .+0" "\n\t" - "out %[port] , %[hi]" "\n\t" - "mov %[n2] , %[lo]" "\n\t" - "out %[port] , %[n1]" "\n\t" - "rjmp .+0" "\n\t" - "sbrc %[byte] , 2" "\n\t" - "mov %[n2] , %[hi]" "\n\t" - "out %[port] , %[lo]" "\n\t" - "rjmp .+0" "\n\t" - "out %[port] , %[hi]" "\n\t" - "mov %[n1] , %[lo]" "\n\t" - "out %[port] , %[n2]" "\n\t" - "rjmp .+0" "\n\t" - "sbrc %[byte] , 1" "\n\t" - "mov %[n1] , %[hi]" "\n\t" - "out %[port] , %[lo]" "\n\t" - "rjmp .+0" "\n\t" - "out %[port] , %[hi]" "\n\t" - "mov %[n2] , %[lo]" "\n\t" - "out %[port] , %[n1]" "\n\t" - "rjmp .+0" "\n\t" - "sbrc %[byte] , 0" "\n\t" - "mov %[n2] , %[hi]" "\n\t" - "out %[port] , %[lo]" "\n\t" - "sbiw %[count], 1" "\n\t" - "out %[port] , %[hi]" "\n\t" - "mov %[n1] , %[lo]" "\n\t" - "out %[port] , %[n2]" "\n\t" - "ld %[byte] , %a[ptr]+" "\n\t" - "sbrc %[byte] , 7" "\n\t" - "mov %[n1] , %[hi]" "\n\t" - "out %[port] , %[lo]" "\n\t" - "brne headB" "\n" - : [byte] "+r" (b), [n1] "+r" (n1), [n2] "+r" (n2), [count] "+w" (i) - : [port] "I" (_SFR_IO_ADDR(PORTB)), [ptr] "e" (ptr), [hi] "r" (hi), - [lo] "r" (lo)); - - #if defined(PORTD) || defined(PORTC) || defined(PORTF) + "headB:" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n2] , %[lo]" + "\n\t" + "out %[port] , %[n1]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 6" + "\n\t" + "mov %[n2] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "rjmp .+0" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n1] , %[lo]" + "\n\t" + "out %[port] , %[n2]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 5" + "\n\t" + "mov %[n1] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "rjmp .+0" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n2] , %[lo]" + "\n\t" + "out %[port] , %[n1]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 4" + "\n\t" + "mov %[n2] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "rjmp .+0" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n1] , %[lo]" + "\n\t" + "out %[port] , %[n2]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 3" + "\n\t" + "mov %[n1] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "rjmp .+0" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n2] , %[lo]" + "\n\t" + "out %[port] , %[n1]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 2" + "\n\t" + "mov %[n2] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "rjmp .+0" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n1] , %[lo]" + "\n\t" + "out %[port] , %[n2]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 1" + "\n\t" + "mov %[n1] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "rjmp .+0" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n2] , %[lo]" + "\n\t" + "out %[port] , %[n1]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 0" + "\n\t" + "mov %[n2] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "sbiw %[count], 1" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n1] , %[lo]" + "\n\t" + "out %[port] , %[n2]" + "\n\t" + "ld %[byte] , %a[ptr]+" + "\n\t" + "sbrc %[byte] , 7" + "\n\t" + "mov %[n1] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "brne headB" + "\n" + : [byte] "+r"(b), [n1] "+r"(n1), [n2] "+r"(n2), [count] "+w"(i) + : [port] "I"(_SFR_IO_ADDR(PORTB)), [ptr] "e"(ptr), [hi] "r"(hi), + [lo] "r"(lo)); + +#if defined(PORTD) || defined(PORTC) || defined(PORTF) } - #endif - #if defined(PORTC) || defined(PORTF) +#endif +#if defined(PORTC) || defined(PORTF) else - #endif // defined(PORTC/F) +#endif // defined(PORTC/F) #endif // defined(PORTB) // PORTC OUTPUT ---------------------------------------------------- #if defined(PORTC) - #if defined(PORTD) || defined(PORTB) || defined(PORTF) - if(port == &PORTC) { - #endif // defined(PORTD/B/F) +#if defined(PORTD) || defined(PORTB) || defined(PORTF) + if (port == &PORTC) { +#endif // defined(PORTD/B/F) // Same as above, just switched to PORTC and stripped of comments. - hi = PORTC | pinMask; + hi = PORTC | pinMask; lo = PORTC & ~pinMask; n1 = lo; - if(b & 0x80) n1 = hi; + if (b & 0x80) + n1 = hi; asm volatile( - "headC:" "\n\t" - "out %[port] , %[hi]" "\n\t" - "mov %[n2] , %[lo]" "\n\t" - "out %[port] , %[n1]" "\n\t" - "rjmp .+0" "\n\t" - "sbrc %[byte] , 6" "\n\t" - "mov %[n2] , %[hi]" "\n\t" - "out %[port] , %[lo]" "\n\t" - "rjmp .+0" "\n\t" - "out %[port] , %[hi]" "\n\t" - "mov %[n1] , %[lo]" "\n\t" - "out %[port] , %[n2]" "\n\t" - "rjmp .+0" "\n\t" - "sbrc %[byte] , 5" "\n\t" - "mov %[n1] , %[hi]" "\n\t" - "out %[port] , %[lo]" "\n\t" - "rjmp .+0" "\n\t" - "out %[port] , %[hi]" "\n\t" - "mov %[n2] , %[lo]" "\n\t" - "out %[port] , %[n1]" "\n\t" - "rjmp .+0" "\n\t" - "sbrc %[byte] , 4" "\n\t" - "mov %[n2] , %[hi]" "\n\t" - "out %[port] , %[lo]" "\n\t" - "rjmp .+0" "\n\t" - "out %[port] , %[hi]" "\n\t" - "mov %[n1] , %[lo]" "\n\t" - "out %[port] , %[n2]" "\n\t" - "rjmp .+0" "\n\t" - "sbrc %[byte] , 3" "\n\t" - "mov %[n1] , %[hi]" "\n\t" - "out %[port] , %[lo]" "\n\t" - "rjmp .+0" "\n\t" - "out %[port] , %[hi]" "\n\t" - "mov %[n2] , %[lo]" "\n\t" - "out %[port] , %[n1]" "\n\t" - "rjmp .+0" "\n\t" - "sbrc %[byte] , 2" "\n\t" - "mov %[n2] , %[hi]" "\n\t" - "out %[port] , %[lo]" "\n\t" - "rjmp .+0" "\n\t" - "out %[port] , %[hi]" "\n\t" - "mov %[n1] , %[lo]" "\n\t" - "out %[port] , %[n2]" "\n\t" - "rjmp .+0" "\n\t" - "sbrc %[byte] , 1" "\n\t" - "mov %[n1] , %[hi]" "\n\t" - "out %[port] , %[lo]" "\n\t" - "rjmp .+0" "\n\t" - "out %[port] , %[hi]" "\n\t" - "mov %[n2] , %[lo]" "\n\t" - "out %[port] , %[n1]" "\n\t" - "rjmp .+0" "\n\t" - "sbrc %[byte] , 0" "\n\t" - "mov %[n2] , %[hi]" "\n\t" - "out %[port] , %[lo]" "\n\t" - "sbiw %[count], 1" "\n\t" - "out %[port] , %[hi]" "\n\t" - "mov %[n1] , %[lo]" "\n\t" - "out %[port] , %[n2]" "\n\t" - "ld %[byte] , %a[ptr]+" "\n\t" - "sbrc %[byte] , 7" "\n\t" - "mov %[n1] , %[hi]" "\n\t" - "out %[port] , %[lo]" "\n\t" - "brne headC" "\n" - : [byte] "+r" (b), [n1] "+r" (n1), [n2] "+r" (n2), [count] "+w" (i) - : [port] "I" (_SFR_IO_ADDR(PORTC)), [ptr] "e" (ptr), [hi] "r" (hi), - [lo] "r" (lo)); - - #if defined(PORTD) || defined(PORTB) || defined(PORTF) + "headC:" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n2] , %[lo]" + "\n\t" + "out %[port] , %[n1]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 6" + "\n\t" + "mov %[n2] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "rjmp .+0" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n1] , %[lo]" + "\n\t" + "out %[port] , %[n2]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 5" + "\n\t" + "mov %[n1] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "rjmp .+0" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n2] , %[lo]" + "\n\t" + "out %[port] , %[n1]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 4" + "\n\t" + "mov %[n2] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "rjmp .+0" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n1] , %[lo]" + "\n\t" + "out %[port] , %[n2]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 3" + "\n\t" + "mov %[n1] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "rjmp .+0" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n2] , %[lo]" + "\n\t" + "out %[port] , %[n1]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 2" + "\n\t" + "mov %[n2] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "rjmp .+0" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n1] , %[lo]" + "\n\t" + "out %[port] , %[n2]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 1" + "\n\t" + "mov %[n1] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "rjmp .+0" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n2] , %[lo]" + "\n\t" + "out %[port] , %[n1]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 0" + "\n\t" + "mov %[n2] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "sbiw %[count], 1" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n1] , %[lo]" + "\n\t" + "out %[port] , %[n2]" + "\n\t" + "ld %[byte] , %a[ptr]+" + "\n\t" + "sbrc %[byte] , 7" + "\n\t" + "mov %[n1] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "brne headC" + "\n" + : [byte] "+r"(b), [n1] "+r"(n1), [n2] "+r"(n2), [count] "+w"(i) + : [port] "I"(_SFR_IO_ADDR(PORTC)), [ptr] "e"(ptr), [hi] "r"(hi), + [lo] "r"(lo)); + +#if defined(PORTD) || defined(PORTB) || defined(PORTF) } - #endif // defined(PORTD/B/F) - #if defined(PORTF) +#endif // defined(PORTD/B/F) +#if defined(PORTF) else - #endif +#endif #endif // defined(PORTC) // PORTF OUTPUT ---------------------------------------------------- #if defined(PORTF) - #if defined(PORTD) || defined(PORTB) || defined(PORTC) - if(port == &PORTF) { - #endif // defined(PORTD/B/C) +#if defined(PORTD) || defined(PORTB) || defined(PORTC) + if (port == &PORTF) { +#endif // defined(PORTD/B/C) - hi = PORTF | pinMask; + hi = PORTF | pinMask; lo = PORTF & ~pinMask; n1 = lo; - if(b & 0x80) n1 = hi; + if (b & 0x80) + n1 = hi; asm volatile( - "headF:" "\n\t" - "out %[port] , %[hi]" "\n\t" - "mov %[n2] , %[lo]" "\n\t" - "out %[port] , %[n1]" "\n\t" - "rjmp .+0" "\n\t" - "sbrc %[byte] , 6" "\n\t" - "mov %[n2] , %[hi]" "\n\t" - "out %[port] , %[lo]" "\n\t" - "rjmp .+0" "\n\t" - "out %[port] , %[hi]" "\n\t" - "mov %[n1] , %[lo]" "\n\t" - "out %[port] , %[n2]" "\n\t" - "rjmp .+0" "\n\t" - "sbrc %[byte] , 5" "\n\t" - "mov %[n1] , %[hi]" "\n\t" - "out %[port] , %[lo]" "\n\t" - "rjmp .+0" "\n\t" - "out %[port] , %[hi]" "\n\t" - "mov %[n2] , %[lo]" "\n\t" - "out %[port] , %[n1]" "\n\t" - "rjmp .+0" "\n\t" - "sbrc %[byte] , 4" "\n\t" - "mov %[n2] , %[hi]" "\n\t" - "out %[port] , %[lo]" "\n\t" - "rjmp .+0" "\n\t" - "out %[port] , %[hi]" "\n\t" - "mov %[n1] , %[lo]" "\n\t" - "out %[port] , %[n2]" "\n\t" - "rjmp .+0" "\n\t" - "sbrc %[byte] , 3" "\n\t" - "mov %[n1] , %[hi]" "\n\t" - "out %[port] , %[lo]" "\n\t" - "rjmp .+0" "\n\t" - "out %[port] , %[hi]" "\n\t" - "mov %[n2] , %[lo]" "\n\t" - "out %[port] , %[n1]" "\n\t" - "rjmp .+0" "\n\t" - "sbrc %[byte] , 2" "\n\t" - "mov %[n2] , %[hi]" "\n\t" - "out %[port] , %[lo]" "\n\t" - "rjmp .+0" "\n\t" - "out %[port] , %[hi]" "\n\t" - "mov %[n1] , %[lo]" "\n\t" - "out %[port] , %[n2]" "\n\t" - "rjmp .+0" "\n\t" - "sbrc %[byte] , 1" "\n\t" - "mov %[n1] , %[hi]" "\n\t" - "out %[port] , %[lo]" "\n\t" - "rjmp .+0" "\n\t" - "out %[port] , %[hi]" "\n\t" - "mov %[n2] , %[lo]" "\n\t" - "out %[port] , %[n1]" "\n\t" - "rjmp .+0" "\n\t" - "sbrc %[byte] , 0" "\n\t" - "mov %[n2] , %[hi]" "\n\t" - "out %[port] , %[lo]" "\n\t" - "sbiw %[count], 1" "\n\t" - "out %[port] , %[hi]" "\n\t" - "mov %[n1] , %[lo]" "\n\t" - "out %[port] , %[n2]" "\n\t" - "ld %[byte] , %a[ptr]+" "\n\t" - "sbrc %[byte] , 7" "\n\t" - "mov %[n1] , %[hi]" "\n\t" - "out %[port] , %[lo]" "\n\t" - "brne headF" "\n" - : [byte] "+r" (b), [n1] "+r" (n1), [n2] "+r" (n2), [count] "+w" (i) - : [port] "I" (_SFR_IO_ADDR(PORTF)), [ptr] "e" (ptr), [hi] "r" (hi), - [lo] "r" (lo)); - - #if defined(PORTD) || defined(PORTB) || defined(PORTC) + "headF:" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n2] , %[lo]" + "\n\t" + "out %[port] , %[n1]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 6" + "\n\t" + "mov %[n2] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "rjmp .+0" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n1] , %[lo]" + "\n\t" + "out %[port] , %[n2]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 5" + "\n\t" + "mov %[n1] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "rjmp .+0" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n2] , %[lo]" + "\n\t" + "out %[port] , %[n1]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 4" + "\n\t" + "mov %[n2] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "rjmp .+0" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n1] , %[lo]" + "\n\t" + "out %[port] , %[n2]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 3" + "\n\t" + "mov %[n1] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "rjmp .+0" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n2] , %[lo]" + "\n\t" + "out %[port] , %[n1]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 2" + "\n\t" + "mov %[n2] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "rjmp .+0" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n1] , %[lo]" + "\n\t" + "out %[port] , %[n2]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 1" + "\n\t" + "mov %[n1] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "rjmp .+0" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n2] , %[lo]" + "\n\t" + "out %[port] , %[n1]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 0" + "\n\t" + "mov %[n2] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "sbiw %[count], 1" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n1] , %[lo]" + "\n\t" + "out %[port] , %[n2]" + "\n\t" + "ld %[byte] , %a[ptr]+" + "\n\t" + "sbrc %[byte] , 7" + "\n\t" + "mov %[n1] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "brne headF" + "\n" + : [byte] "+r"(b), [n1] "+r"(n1), [n2] "+r"(n2), [count] "+w"(i) + : [port] "I"(_SFR_IO_ADDR(PORTF)), [ptr] "e"(ptr), [hi] "r"(hi), + [lo] "r"(lo)); + +#if defined(PORTD) || defined(PORTB) || defined(PORTC) } - #endif // defined(PORTD/B/C) +#endif // defined(PORTD/B/C) #endif // defined(PORTF) #if defined(NEO_KHZ400) @@ -660,41 +974,56 @@ void Adafruit_NeoPixel::show(void) { volatile uint8_t next, bit; - hi = *port | pinMask; - lo = *port & ~pinMask; + hi = *port | pinMask; + lo = *port & ~pinMask; next = lo; - bit = 8; - - asm volatile( - "head20:" "\n\t" // Clk Pseudocode (T = 0) - "st %a[port], %[hi]" "\n\t" // 2 PORT = hi (T = 2) - "sbrc %[byte] , 7" "\n\t" // 1-2 if(b & 128) - "mov %[next], %[hi]" "\n\t" // 0-1 next = hi (T = 4) - "st %a[port], %[next]" "\n\t" // 2 PORT = next (T = 6) - "mov %[next] , %[lo]" "\n\t" // 1 next = lo (T = 7) - "dec %[bit]" "\n\t" // 1 bit-- (T = 8) - "breq nextbyte20" "\n\t" // 1-2 if(bit == 0) - "rol %[byte]" "\n\t" // 1 b <<= 1 (T = 10) - "st %a[port], %[lo]" "\n\t" // 2 PORT = lo (T = 12) - "rjmp .+0" "\n\t" // 2 nop nop (T = 14) - "rjmp .+0" "\n\t" // 2 nop nop (T = 16) - "rjmp .+0" "\n\t" // 2 nop nop (T = 18) - "rjmp head20" "\n\t" // 2 -> head20 (next bit out) - "nextbyte20:" "\n\t" // (T = 10) - "st %a[port], %[lo]" "\n\t" // 2 PORT = lo (T = 12) - "nop" "\n\t" // 1 nop (T = 13) - "ldi %[bit] , 8" "\n\t" // 1 bit = 8 (T = 14) - "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 16) - "sbiw %[count], 1" "\n\t" // 2 i-- (T = 18) - "brne head20" "\n" // 2 if(i != 0) -> (next byte) - : [port] "+e" (port), - [byte] "+r" (b), - [bit] "+r" (bit), - [next] "+r" (next), - [count] "+w" (i) - : [hi] "r" (hi), - [lo] "r" (lo), - [ptr] "e" (ptr)); + bit = 8; + + asm volatile("head20:" + "\n\t" // Clk Pseudocode (T = 0) + "st %a[port], %[hi]" + "\n\t" // 2 PORT = hi (T = 2) + "sbrc %[byte] , 7" + "\n\t" // 1-2 if(b & 128) + "mov %[next], %[hi]" + "\n\t" // 0-1 next = hi (T = 4) + "st %a[port], %[next]" + "\n\t" // 2 PORT = next (T = 6) + "mov %[next] , %[lo]" + "\n\t" // 1 next = lo (T = 7) + "dec %[bit]" + "\n\t" // 1 bit-- (T = 8) + "breq nextbyte20" + "\n\t" // 1-2 if(bit == 0) + "rol %[byte]" + "\n\t" // 1 b <<= 1 (T = 10) + "st %a[port], %[lo]" + "\n\t" // 2 PORT = lo (T = 12) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 14) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 16) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 18) + "rjmp head20" + "\n\t" // 2 -> head20 (next bit out) + "nextbyte20:" + "\n\t" // (T = 10) + "st %a[port], %[lo]" + "\n\t" // 2 PORT = lo (T = 12) + "nop" + "\n\t" // 1 nop (T = 13) + "ldi %[bit] , 8" + "\n\t" // 1 bit = 8 (T = 14) + "ld %[byte] , %a[ptr]+" + "\n\t" // 2 b = *ptr++ (T = 16) + "sbiw %[count], 1" + "\n\t" // 2 i-- (T = 18) + "brne head20" + "\n" // 2 if(i != 0) -> (next byte) + : [port] "+e"(port), [byte] "+r"(b), [bit] "+r"(bit), + [next] "+r"(next), [count] "+w"(i) + : [hi] "r"(hi), [lo] "r"(lo), [ptr] "e"(ptr)); } #endif // NEO_KHZ400 @@ -702,7 +1031,7 @@ void Adafruit_NeoPixel::show(void) { #elif (F_CPU >= 11100000UL) && (F_CPU <= 14300000UL) #if defined(NEO_KHZ400) // 800 KHz check needed only if 400 KHz support enabled - if(is800KHz) { + if (is800KHz) { #endif // In the 12 MHz case, an optimized 800 KHz datastream (no dead time @@ -717,253 +1046,397 @@ void Adafruit_NeoPixel::show(void) { // PORTD OUTPUT ---------------------------------------------------- #if defined(PORTD) - #if defined(PORTB) || defined(PORTC) || defined(PORTF) - if(port == &PORTD) { - #endif // defined(PORTB/C/F) +#if defined(PORTB) || defined(PORTC) || defined(PORTF) + if (port == &PORTD) { +#endif // defined(PORTB/C/F) - hi = PORTD | pinMask; - lo = PORTD & ~pinMask; + hi = PORTD | pinMask; + lo = PORTD & ~pinMask; next = lo; - if(b & 0x80) next = hi; + if (b & 0x80) + next = hi; // Don't "optimize" the OUT calls into the bitTime subroutine; // we're exploiting the RCALL and RET as 3- and 4-cycle NOPs! - asm volatile( - "headD:" "\n\t" // (T = 0) - "out %[port], %[hi]" "\n\t" // (T = 1) - "rcall bitTimeD" "\n\t" // Bit 7 (T = 15) - "out %[port], %[hi]" "\n\t" - "rcall bitTimeD" "\n\t" // Bit 6 - "out %[port], %[hi]" "\n\t" - "rcall bitTimeD" "\n\t" // Bit 5 - "out %[port], %[hi]" "\n\t" - "rcall bitTimeD" "\n\t" // Bit 4 - "out %[port], %[hi]" "\n\t" - "rcall bitTimeD" "\n\t" // Bit 3 - "out %[port], %[hi]" "\n\t" - "rcall bitTimeD" "\n\t" // Bit 2 - "out %[port], %[hi]" "\n\t" - "rcall bitTimeD" "\n\t" // Bit 1 - // Bit 0: - "out %[port] , %[hi]" "\n\t" // 1 PORT = hi (T = 1) - "rjmp .+0" "\n\t" // 2 nop nop (T = 3) - "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 5) - "out %[port] , %[next]" "\n\t" // 1 PORT = next (T = 6) - "mov %[next] , %[lo]" "\n\t" // 1 next = lo (T = 7) - "sbrc %[byte] , 7" "\n\t" // 1-2 if(b & 0x80) (T = 8) - "mov %[next] , %[hi]" "\n\t" // 0-1 next = hi (T = 9) - "nop" "\n\t" // 1 (T = 10) - "out %[port] , %[lo]" "\n\t" // 1 PORT = lo (T = 11) - "sbiw %[count], 1" "\n\t" // 2 i-- (T = 13) - "brne headD" "\n\t" // 2 if(i != 0) -> (next byte) - "rjmp doneD" "\n\t" - "bitTimeD:" "\n\t" // nop nop nop (T = 4) - "out %[port], %[next]" "\n\t" // 1 PORT = next (T = 5) - "mov %[next], %[lo]" "\n\t" // 1 next = lo (T = 6) - "rol %[byte]" "\n\t" // 1 b <<= 1 (T = 7) - "sbrc %[byte], 7" "\n\t" // 1-2 if(b & 0x80) (T = 8) - "mov %[next], %[hi]" "\n\t" // 0-1 next = hi (T = 9) - "nop" "\n\t" // 1 (T = 10) - "out %[port], %[lo]" "\n\t" // 1 PORT = lo (T = 11) - "ret" "\n\t" // 4 nop nop nop nop (T = 15) - "doneD:" "\n" - : [byte] "+r" (b), - [next] "+r" (next), - [count] "+w" (i) - : [port] "I" (_SFR_IO_ADDR(PORTD)), - [ptr] "e" (ptr), - [hi] "r" (hi), - [lo] "r" (lo)); - - #if defined(PORTB) || defined(PORTC) || defined(PORTF) + asm volatile("headD:" + "\n\t" // (T = 0) + "out %[port], %[hi]" + "\n\t" // (T = 1) + "rcall bitTimeD" + "\n\t" // Bit 7 (T = 15) + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeD" + "\n\t" // Bit 6 + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeD" + "\n\t" // Bit 5 + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeD" + "\n\t" // Bit 4 + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeD" + "\n\t" // Bit 3 + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeD" + "\n\t" // Bit 2 + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeD" + "\n\t" // Bit 1 + // Bit 0: + "out %[port] , %[hi]" + "\n\t" // 1 PORT = hi (T = 1) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 3) + "ld %[byte] , %a[ptr]+" + "\n\t" // 2 b = *ptr++ (T = 5) + "out %[port] , %[next]" + "\n\t" // 1 PORT = next (T = 6) + "mov %[next] , %[lo]" + "\n\t" // 1 next = lo (T = 7) + "sbrc %[byte] , 7" + "\n\t" // 1-2 if(b & 0x80) (T = 8) + "mov %[next] , %[hi]" + "\n\t" // 0-1 next = hi (T = 9) + "nop" + "\n\t" // 1 (T = 10) + "out %[port] , %[lo]" + "\n\t" // 1 PORT = lo (T = 11) + "sbiw %[count], 1" + "\n\t" // 2 i-- (T = 13) + "brne headD" + "\n\t" // 2 if(i != 0) -> (next byte) + "rjmp doneD" + "\n\t" + "bitTimeD:" + "\n\t" // nop nop nop (T = 4) + "out %[port], %[next]" + "\n\t" // 1 PORT = next (T = 5) + "mov %[next], %[lo]" + "\n\t" // 1 next = lo (T = 6) + "rol %[byte]" + "\n\t" // 1 b <<= 1 (T = 7) + "sbrc %[byte], 7" + "\n\t" // 1-2 if(b & 0x80) (T = 8) + "mov %[next], %[hi]" + "\n\t" // 0-1 next = hi (T = 9) + "nop" + "\n\t" // 1 (T = 10) + "out %[port], %[lo]" + "\n\t" // 1 PORT = lo (T = 11) + "ret" + "\n\t" // 4 nop nop nop nop (T = 15) + "doneD:" + "\n" + : [byte] "+r"(b), [next] "+r"(next), [count] "+w"(i) + : [port] "I"(_SFR_IO_ADDR(PORTD)), [ptr] "e"(ptr), + [hi] "r"(hi), [lo] "r"(lo)); + +#if defined(PORTB) || defined(PORTC) || defined(PORTF) } else // other PORT(s) - #endif // defined(PORTB/C/F) +#endif // defined(PORTB/C/F) #endif // defined(PORTD) // PORTB OUTPUT ---------------------------------------------------- #if defined(PORTB) - #if defined(PORTD) || defined(PORTC) || defined(PORTF) - if(port == &PORTB) { - #endif // defined(PORTD/C/F) +#if defined(PORTD) || defined(PORTC) || defined(PORTF) + if (port == &PORTB) { +#endif // defined(PORTD/C/F) - hi = PORTB | pinMask; - lo = PORTB & ~pinMask; + hi = PORTB | pinMask; + lo = PORTB & ~pinMask; next = lo; - if(b & 0x80) next = hi; + if (b & 0x80) + next = hi; // Same as above, just set for PORTB & stripped of comments - asm volatile( - "headB:" "\n\t" - "out %[port], %[hi]" "\n\t" - "rcall bitTimeB" "\n\t" - "out %[port], %[hi]" "\n\t" - "rcall bitTimeB" "\n\t" - "out %[port], %[hi]" "\n\t" - "rcall bitTimeB" "\n\t" - "out %[port], %[hi]" "\n\t" - "rcall bitTimeB" "\n\t" - "out %[port], %[hi]" "\n\t" - "rcall bitTimeB" "\n\t" - "out %[port], %[hi]" "\n\t" - "rcall bitTimeB" "\n\t" - "out %[port], %[hi]" "\n\t" - "rcall bitTimeB" "\n\t" - "out %[port] , %[hi]" "\n\t" - "rjmp .+0" "\n\t" - "ld %[byte] , %a[ptr]+" "\n\t" - "out %[port] , %[next]" "\n\t" - "mov %[next] , %[lo]" "\n\t" - "sbrc %[byte] , 7" "\n\t" - "mov %[next] , %[hi]" "\n\t" - "nop" "\n\t" - "out %[port] , %[lo]" "\n\t" - "sbiw %[count], 1" "\n\t" - "brne headB" "\n\t" - "rjmp doneB" "\n\t" - "bitTimeB:" "\n\t" - "out %[port], %[next]" "\n\t" - "mov %[next], %[lo]" "\n\t" - "rol %[byte]" "\n\t" - "sbrc %[byte], 7" "\n\t" - "mov %[next], %[hi]" "\n\t" - "nop" "\n\t" - "out %[port], %[lo]" "\n\t" - "ret" "\n\t" - "doneB:" "\n" - : [byte] "+r" (b), [next] "+r" (next), [count] "+w" (i) - : [port] "I" (_SFR_IO_ADDR(PORTB)), [ptr] "e" (ptr), [hi] "r" (hi), - [lo] "r" (lo)); - - #if defined(PORTD) || defined(PORTC) || defined(PORTF) + asm volatile("headB:" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeB" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeB" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeB" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeB" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeB" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeB" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeB" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "rjmp .+0" + "\n\t" + "ld %[byte] , %a[ptr]+" + "\n\t" + "out %[port] , %[next]" + "\n\t" + "mov %[next] , %[lo]" + "\n\t" + "sbrc %[byte] , 7" + "\n\t" + "mov %[next] , %[hi]" + "\n\t" + "nop" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "sbiw %[count], 1" + "\n\t" + "brne headB" + "\n\t" + "rjmp doneB" + "\n\t" + "bitTimeB:" + "\n\t" + "out %[port], %[next]" + "\n\t" + "mov %[next], %[lo]" + "\n\t" + "rol %[byte]" + "\n\t" + "sbrc %[byte], 7" + "\n\t" + "mov %[next], %[hi]" + "\n\t" + "nop" + "\n\t" + "out %[port], %[lo]" + "\n\t" + "ret" + "\n\t" + "doneB:" + "\n" + : [byte] "+r"(b), [next] "+r"(next), [count] "+w"(i) + : [port] "I"(_SFR_IO_ADDR(PORTB)), [ptr] "e"(ptr), + [hi] "r"(hi), [lo] "r"(lo)); + +#if defined(PORTD) || defined(PORTC) || defined(PORTF) } - #endif - #if defined(PORTC) || defined(PORTF) +#endif +#if defined(PORTC) || defined(PORTF) else - #endif // defined(PORTC/F) +#endif // defined(PORTC/F) #endif // defined(PORTB) // PORTC OUTPUT ---------------------------------------------------- #if defined(PORTC) - #if defined(PORTD) || defined(PORTB) || defined(PORTF) - if(port == &PORTC) { - #endif // defined(PORTD/B/F) +#if defined(PORTD) || defined(PORTB) || defined(PORTF) + if (port == &PORTC) { +#endif // defined(PORTD/B/F) - hi = PORTC | pinMask; - lo = PORTC & ~pinMask; + hi = PORTC | pinMask; + lo = PORTC & ~pinMask; next = lo; - if(b & 0x80) next = hi; + if (b & 0x80) + next = hi; // Same as above, just set for PORTC & stripped of comments - asm volatile( - "headC:" "\n\t" - "out %[port], %[hi]" "\n\t" - "rcall bitTimeC" "\n\t" - "out %[port], %[hi]" "\n\t" - "rcall bitTimeC" "\n\t" - "out %[port], %[hi]" "\n\t" - "rcall bitTimeC" "\n\t" - "out %[port], %[hi]" "\n\t" - "rcall bitTimeC" "\n\t" - "out %[port], %[hi]" "\n\t" - "rcall bitTimeC" "\n\t" - "out %[port], %[hi]" "\n\t" - "rcall bitTimeC" "\n\t" - "out %[port], %[hi]" "\n\t" - "rcall bitTimeC" "\n\t" - "out %[port] , %[hi]" "\n\t" - "rjmp .+0" "\n\t" - "ld %[byte] , %a[ptr]+" "\n\t" - "out %[port] , %[next]" "\n\t" - "mov %[next] , %[lo]" "\n\t" - "sbrc %[byte] , 7" "\n\t" - "mov %[next] , %[hi]" "\n\t" - "nop" "\n\t" - "out %[port] , %[lo]" "\n\t" - "sbiw %[count], 1" "\n\t" - "brne headC" "\n\t" - "rjmp doneC" "\n\t" - "bitTimeC:" "\n\t" - "out %[port], %[next]" "\n\t" - "mov %[next], %[lo]" "\n\t" - "rol %[byte]" "\n\t" - "sbrc %[byte], 7" "\n\t" - "mov %[next], %[hi]" "\n\t" - "nop" "\n\t" - "out %[port], %[lo]" "\n\t" - "ret" "\n\t" - "doneC:" "\n" - : [byte] "+r" (b), [next] "+r" (next), [count] "+w" (i) - : [port] "I" (_SFR_IO_ADDR(PORTC)), [ptr] "e" (ptr), [hi] "r" (hi), - [lo] "r" (lo)); - - #if defined(PORTD) || defined(PORTB) || defined(PORTF) + asm volatile("headC:" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeC" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeC" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeC" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeC" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeC" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeC" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeC" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "rjmp .+0" + "\n\t" + "ld %[byte] , %a[ptr]+" + "\n\t" + "out %[port] , %[next]" + "\n\t" + "mov %[next] , %[lo]" + "\n\t" + "sbrc %[byte] , 7" + "\n\t" + "mov %[next] , %[hi]" + "\n\t" + "nop" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "sbiw %[count], 1" + "\n\t" + "brne headC" + "\n\t" + "rjmp doneC" + "\n\t" + "bitTimeC:" + "\n\t" + "out %[port], %[next]" + "\n\t" + "mov %[next], %[lo]" + "\n\t" + "rol %[byte]" + "\n\t" + "sbrc %[byte], 7" + "\n\t" + "mov %[next], %[hi]" + "\n\t" + "nop" + "\n\t" + "out %[port], %[lo]" + "\n\t" + "ret" + "\n\t" + "doneC:" + "\n" + : [byte] "+r"(b), [next] "+r"(next), [count] "+w"(i) + : [port] "I"(_SFR_IO_ADDR(PORTC)), [ptr] "e"(ptr), + [hi] "r"(hi), [lo] "r"(lo)); + +#if defined(PORTD) || defined(PORTB) || defined(PORTF) } - #endif // defined(PORTD/B/F) - #if defined(PORTF) +#endif // defined(PORTD/B/F) +#if defined(PORTF) else - #endif +#endif #endif // defined(PORTC) // PORTF OUTPUT ---------------------------------------------------- #if defined(PORTF) - #if defined(PORTD) || defined(PORTB) || defined(PORTC) - if(port == &PORTF) { - #endif // defined(PORTD/B/C) +#if defined(PORTD) || defined(PORTB) || defined(PORTC) + if (port == &PORTF) { +#endif // defined(PORTD/B/C) - hi = PORTF | pinMask; - lo = PORTF & ~pinMask; + hi = PORTF | pinMask; + lo = PORTF & ~pinMask; next = lo; - if(b & 0x80) next = hi; + if (b & 0x80) + next = hi; // Same as above, just set for PORTF & stripped of comments - asm volatile( - "headF:" "\n\t" - "out %[port], %[hi]" "\n\t" - "rcall bitTimeC" "\n\t" - "out %[port], %[hi]" "\n\t" - "rcall bitTimeC" "\n\t" - "out %[port], %[hi]" "\n\t" - "rcall bitTimeC" "\n\t" - "out %[port], %[hi]" "\n\t" - "rcall bitTimeC" "\n\t" - "out %[port], %[hi]" "\n\t" - "rcall bitTimeC" "\n\t" - "out %[port], %[hi]" "\n\t" - "rcall bitTimeC" "\n\t" - "out %[port], %[hi]" "\n\t" - "rcall bitTimeC" "\n\t" - "out %[port] , %[hi]" "\n\t" - "rjmp .+0" "\n\t" - "ld %[byte] , %a[ptr]+" "\n\t" - "out %[port] , %[next]" "\n\t" - "mov %[next] , %[lo]" "\n\t" - "sbrc %[byte] , 7" "\n\t" - "mov %[next] , %[hi]" "\n\t" - "nop" "\n\t" - "out %[port] , %[lo]" "\n\t" - "sbiw %[count], 1" "\n\t" - "brne headF" "\n\t" - "rjmp doneC" "\n\t" - "bitTimeC:" "\n\t" - "out %[port], %[next]" "\n\t" - "mov %[next], %[lo]" "\n\t" - "rol %[byte]" "\n\t" - "sbrc %[byte], 7" "\n\t" - "mov %[next], %[hi]" "\n\t" - "nop" "\n\t" - "out %[port], %[lo]" "\n\t" - "ret" "\n\t" - "doneC:" "\n" - : [byte] "+r" (b), [next] "+r" (next), [count] "+w" (i) - : [port] "I" (_SFR_IO_ADDR(PORTF)), [ptr] "e" (ptr), [hi] "r" (hi), - [lo] "r" (lo)); - - #if defined(PORTD) || defined(PORTB) || defined(PORTC) + asm volatile("headF:" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeC" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeC" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeC" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeC" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeC" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeC" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeC" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "rjmp .+0" + "\n\t" + "ld %[byte] , %a[ptr]+" + "\n\t" + "out %[port] , %[next]" + "\n\t" + "mov %[next] , %[lo]" + "\n\t" + "sbrc %[byte] , 7" + "\n\t" + "mov %[next] , %[hi]" + "\n\t" + "nop" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "sbiw %[count], 1" + "\n\t" + "brne headF" + "\n\t" + "rjmp doneC" + "\n\t" + "bitTimeC:" + "\n\t" + "out %[port], %[next]" + "\n\t" + "mov %[next], %[lo]" + "\n\t" + "rol %[byte]" + "\n\t" + "sbrc %[byte], 7" + "\n\t" + "mov %[next], %[hi]" + "\n\t" + "nop" + "\n\t" + "out %[port], %[lo]" + "\n\t" + "ret" + "\n\t" + "doneC:" + "\n" + : [byte] "+r"(b), [next] "+r"(next), [count] "+w"(i) + : [port] "I"(_SFR_IO_ADDR(PORTF)), [ptr] "e"(ptr), + [hi] "r"(hi), [lo] "r"(lo)); + +#if defined(PORTD) || defined(PORTB) || defined(PORTC) } - #endif // defined(PORTD/B/C) +#endif // defined(PORTD/B/C) #endif // defined(PORTF) #if defined(NEO_KHZ400) @@ -974,45 +1447,64 @@ void Adafruit_NeoPixel::show(void) { volatile uint8_t next, bit; - hi = *port | pinMask; - lo = *port & ~pinMask; + hi = *port | pinMask; + lo = *port & ~pinMask; next = lo; - bit = 8; - - asm volatile( - "head30:" "\n\t" // Clk Pseudocode (T = 0) - "st %a[port], %[hi]" "\n\t" // 2 PORT = hi (T = 2) - "sbrc %[byte] , 7" "\n\t" // 1-2 if(b & 128) - "mov %[next], %[hi]" "\n\t" // 0-1 next = hi (T = 4) - "rjmp .+0" "\n\t" // 2 nop nop (T = 6) - "st %a[port], %[next]" "\n\t" // 2 PORT = next (T = 8) - "rjmp .+0" "\n\t" // 2 nop nop (T = 10) - "rjmp .+0" "\n\t" // 2 nop nop (T = 12) - "rjmp .+0" "\n\t" // 2 nop nop (T = 14) - "nop" "\n\t" // 1 nop (T = 15) - "st %a[port], %[lo]" "\n\t" // 2 PORT = lo (T = 17) - "rjmp .+0" "\n\t" // 2 nop nop (T = 19) - "dec %[bit]" "\n\t" // 1 bit-- (T = 20) - "breq nextbyte30" "\n\t" // 1-2 if(bit == 0) - "rol %[byte]" "\n\t" // 1 b <<= 1 (T = 22) - "rjmp .+0" "\n\t" // 2 nop nop (T = 24) - "rjmp .+0" "\n\t" // 2 nop nop (T = 26) - "rjmp .+0" "\n\t" // 2 nop nop (T = 28) - "rjmp head30" "\n\t" // 2 -> head30 (next bit out) - "nextbyte30:" "\n\t" // (T = 22) - "nop" "\n\t" // 1 nop (T = 23) - "ldi %[bit] , 8" "\n\t" // 1 bit = 8 (T = 24) - "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 26) - "sbiw %[count], 1" "\n\t" // 2 i-- (T = 28) - "brne head30" "\n" // 1-2 if(i != 0) -> (next byte) - : [port] "+e" (port), - [byte] "+r" (b), - [bit] "+r" (bit), - [next] "+r" (next), - [count] "+w" (i) - : [hi] "r" (hi), - [lo] "r" (lo), - [ptr] "e" (ptr)); + bit = 8; + + asm volatile("head30:" + "\n\t" // Clk Pseudocode (T = 0) + "st %a[port], %[hi]" + "\n\t" // 2 PORT = hi (T = 2) + "sbrc %[byte] , 7" + "\n\t" // 1-2 if(b & 128) + "mov %[next], %[hi]" + "\n\t" // 0-1 next = hi (T = 4) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 6) + "st %a[port], %[next]" + "\n\t" // 2 PORT = next (T = 8) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 10) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 12) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 14) + "nop" + "\n\t" // 1 nop (T = 15) + "st %a[port], %[lo]" + "\n\t" // 2 PORT = lo (T = 17) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 19) + "dec %[bit]" + "\n\t" // 1 bit-- (T = 20) + "breq nextbyte30" + "\n\t" // 1-2 if(bit == 0) + "rol %[byte]" + "\n\t" // 1 b <<= 1 (T = 22) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 24) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 26) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 28) + "rjmp head30" + "\n\t" // 2 -> head30 (next bit out) + "nextbyte30:" + "\n\t" // (T = 22) + "nop" + "\n\t" // 1 nop (T = 23) + "ldi %[bit] , 8" + "\n\t" // 1 bit = 8 (T = 24) + "ld %[byte] , %a[ptr]+" + "\n\t" // 2 b = *ptr++ (T = 26) + "sbiw %[count], 1" + "\n\t" // 2 i-- (T = 28) + "brne head30" + "\n" // 1-2 if(i != 0) -> (next byte) + : [port] "+e"(port), [byte] "+r"(b), [bit] "+r"(bit), + [next] "+r"(next), [count] "+w"(i) + : [hi] "r"(hi), [lo] "r"(lo), [ptr] "e"(ptr)); } #endif // NEO_KHZ400 @@ -1020,7 +1512,7 @@ void Adafruit_NeoPixel::show(void) { #elif (F_CPU >= 15400000UL) && (F_CPU <= 19000000L) #if defined(NEO_KHZ400) // 800 KHz check needed only if 400 KHz support enabled - if(is800KHz) { + if (is800KHz) { #endif // WS2811 and WS2812 have different hi/lo duty cycles; this is @@ -1031,42 +1523,58 @@ void Adafruit_NeoPixel::show(void) { volatile uint8_t next, bit; - hi = *port | pinMask; - lo = *port & ~pinMask; + hi = *port | pinMask; + lo = *port & ~pinMask; next = lo; - bit = 8; - - asm volatile( - "head20:" "\n\t" // Clk Pseudocode (T = 0) - "st %a[port], %[hi]" "\n\t" // 2 PORT = hi (T = 2) - "sbrc %[byte], 7" "\n\t" // 1-2 if(b & 128) - "mov %[next], %[hi]" "\n\t" // 0-1 next = hi (T = 4) - "dec %[bit]" "\n\t" // 1 bit-- (T = 5) - "st %a[port], %[next]" "\n\t" // 2 PORT = next (T = 7) - "mov %[next] , %[lo]" "\n\t" // 1 next = lo (T = 8) - "breq nextbyte20" "\n\t" // 1-2 if(bit == 0) (from dec above) - "rol %[byte]" "\n\t" // 1 b <<= 1 (T = 10) - "rjmp .+0" "\n\t" // 2 nop nop (T = 12) - "nop" "\n\t" // 1 nop (T = 13) - "st %a[port], %[lo]" "\n\t" // 2 PORT = lo (T = 15) - "nop" "\n\t" // 1 nop (T = 16) - "rjmp .+0" "\n\t" // 2 nop nop (T = 18) - "rjmp head20" "\n\t" // 2 -> head20 (next bit out) - "nextbyte20:" "\n\t" // (T = 10) - "ldi %[bit] , 8" "\n\t" // 1 bit = 8 (T = 11) - "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 13) - "st %a[port], %[lo]" "\n\t" // 2 PORT = lo (T = 15) - "nop" "\n\t" // 1 nop (T = 16) - "sbiw %[count], 1" "\n\t" // 2 i-- (T = 18) - "brne head20" "\n" // 2 if(i != 0) -> (next byte) - : [port] "+e" (port), - [byte] "+r" (b), - [bit] "+r" (bit), - [next] "+r" (next), - [count] "+w" (i) - : [ptr] "e" (ptr), - [hi] "r" (hi), - [lo] "r" (lo)); + bit = 8; + + asm volatile("head20:" + "\n\t" // Clk Pseudocode (T = 0) + "st %a[port], %[hi]" + "\n\t" // 2 PORT = hi (T = 2) + "sbrc %[byte], 7" + "\n\t" // 1-2 if(b & 128) + "mov %[next], %[hi]" + "\n\t" // 0-1 next = hi (T = 4) + "dec %[bit]" + "\n\t" // 1 bit-- (T = 5) + "st %a[port], %[next]" + "\n\t" // 2 PORT = next (T = 7) + "mov %[next] , %[lo]" + "\n\t" // 1 next = lo (T = 8) + "breq nextbyte20" + "\n\t" // 1-2 if(bit == 0) (from dec above) + "rol %[byte]" + "\n\t" // 1 b <<= 1 (T = 10) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 12) + "nop" + "\n\t" // 1 nop (T = 13) + "st %a[port], %[lo]" + "\n\t" // 2 PORT = lo (T = 15) + "nop" + "\n\t" // 1 nop (T = 16) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 18) + "rjmp head20" + "\n\t" // 2 -> head20 (next bit out) + "nextbyte20:" + "\n\t" // (T = 10) + "ldi %[bit] , 8" + "\n\t" // 1 bit = 8 (T = 11) + "ld %[byte] , %a[ptr]+" + "\n\t" // 2 b = *ptr++ (T = 13) + "st %a[port], %[lo]" + "\n\t" // 2 PORT = lo (T = 15) + "nop" + "\n\t" // 1 nop (T = 16) + "sbiw %[count], 1" + "\n\t" // 2 i-- (T = 18) + "brne head20" + "\n" // 2 if(i != 0) -> (next byte) + : [port] "+e"(port), [byte] "+r"(b), [bit] "+r"(bit), + [next] "+r"(next), [count] "+w"(i) + : [ptr] "e"(ptr), [hi] "r"(hi), [lo] "r"(lo)); #if defined(NEO_KHZ400) } else { // 400 KHz @@ -1078,269 +1586,368 @@ void Adafruit_NeoPixel::show(void) { volatile uint8_t next, bit; - hi = *port | pinMask; - lo = *port & ~pinMask; + hi = *port | pinMask; + lo = *port & ~pinMask; next = lo; - bit = 8; - - asm volatile( - "head40:" "\n\t" // Clk Pseudocode (T = 0) - "st %a[port], %[hi]" "\n\t" // 2 PORT = hi (T = 2) - "sbrc %[byte] , 7" "\n\t" // 1-2 if(b & 128) - "mov %[next] , %[hi]" "\n\t" // 0-1 next = hi (T = 4) - "rjmp .+0" "\n\t" // 2 nop nop (T = 6) - "rjmp .+0" "\n\t" // 2 nop nop (T = 8) - "st %a[port], %[next]" "\n\t" // 2 PORT = next (T = 10) - "rjmp .+0" "\n\t" // 2 nop nop (T = 12) - "rjmp .+0" "\n\t" // 2 nop nop (T = 14) - "rjmp .+0" "\n\t" // 2 nop nop (T = 16) - "rjmp .+0" "\n\t" // 2 nop nop (T = 18) - "rjmp .+0" "\n\t" // 2 nop nop (T = 20) - "st %a[port], %[lo]" "\n\t" // 2 PORT = lo (T = 22) - "nop" "\n\t" // 1 nop (T = 23) - "mov %[next] , %[lo]" "\n\t" // 1 next = lo (T = 24) - "dec %[bit]" "\n\t" // 1 bit-- (T = 25) - "breq nextbyte40" "\n\t" // 1-2 if(bit == 0) - "rol %[byte]" "\n\t" // 1 b <<= 1 (T = 27) - "nop" "\n\t" // 1 nop (T = 28) - "rjmp .+0" "\n\t" // 2 nop nop (T = 30) - "rjmp .+0" "\n\t" // 2 nop nop (T = 32) - "rjmp .+0" "\n\t" // 2 nop nop (T = 34) - "rjmp .+0" "\n\t" // 2 nop nop (T = 36) - "rjmp .+0" "\n\t" // 2 nop nop (T = 38) - "rjmp head40" "\n\t" // 2 -> head40 (next bit out) - "nextbyte40:" "\n\t" // (T = 27) - "ldi %[bit] , 8" "\n\t" // 1 bit = 8 (T = 28) - "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 30) - "rjmp .+0" "\n\t" // 2 nop nop (T = 32) - "st %a[port], %[lo]" "\n\t" // 2 PORT = lo (T = 34) - "rjmp .+0" "\n\t" // 2 nop nop (T = 36) - "sbiw %[count], 1" "\n\t" // 2 i-- (T = 38) - "brne head40" "\n" // 1-2 if(i != 0) -> (next byte) - : [port] "+e" (port), - [byte] "+r" (b), - [bit] "+r" (bit), - [next] "+r" (next), - [count] "+w" (i) - : [ptr] "e" (ptr), - [hi] "r" (hi), - [lo] "r" (lo)); + bit = 8; + + asm volatile("head40:" + "\n\t" // Clk Pseudocode (T = 0) + "st %a[port], %[hi]" + "\n\t" // 2 PORT = hi (T = 2) + "sbrc %[byte] , 7" + "\n\t" // 1-2 if(b & 128) + "mov %[next] , %[hi]" + "\n\t" // 0-1 next = hi (T = 4) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 6) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 8) + "st %a[port], %[next]" + "\n\t" // 2 PORT = next (T = 10) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 12) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 14) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 16) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 18) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 20) + "st %a[port], %[lo]" + "\n\t" // 2 PORT = lo (T = 22) + "nop" + "\n\t" // 1 nop (T = 23) + "mov %[next] , %[lo]" + "\n\t" // 1 next = lo (T = 24) + "dec %[bit]" + "\n\t" // 1 bit-- (T = 25) + "breq nextbyte40" + "\n\t" // 1-2 if(bit == 0) + "rol %[byte]" + "\n\t" // 1 b <<= 1 (T = 27) + "nop" + "\n\t" // 1 nop (T = 28) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 30) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 32) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 34) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 36) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 38) + "rjmp head40" + "\n\t" // 2 -> head40 (next bit out) + "nextbyte40:" + "\n\t" // (T = 27) + "ldi %[bit] , 8" + "\n\t" // 1 bit = 8 (T = 28) + "ld %[byte] , %a[ptr]+" + "\n\t" // 2 b = *ptr++ (T = 30) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 32) + "st %a[port], %[lo]" + "\n\t" // 2 PORT = lo (T = 34) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 36) + "sbiw %[count], 1" + "\n\t" // 2 i-- (T = 38) + "brne head40" + "\n" // 1-2 if(i != 0) -> (next byte) + : [port] "+e"(port), [byte] "+r"(b), [bit] "+r"(bit), + [next] "+r"(next), [count] "+w"(i) + : [ptr] "e"(ptr), [hi] "r"(hi), [lo] "r"(lo)); } #endif // NEO_KHZ400 #else - #error "CPU SPEED NOT SUPPORTED" +#error "CPU SPEED NOT SUPPORTED" #endif // end F_CPU ifdefs on __AVR__ -// END AVR ---------------------------------------------------------------- - + // END AVR ---------------------------------------------------------------- #elif defined(__arm__) -// ARM MCUs -- Teensy 3.0, 3.1, LC, Arduino Due --------------------------- + // ARM MCUs -- Teensy 3.0, 3.1, LC, Arduino Due, RP2040 ------------------- + +#if defined(ARDUINO_ARCH_RP2040) + // Use PIO + rp2040Show(pin, pixels, numBytes, is800KHz); -#if defined(TEENSYDUINO) && defined(KINETISK) // Teensy 3.0, 3.1, 3.2, 3.5, 3.6 -#define CYCLES_800_T0H (F_CPU / 4000000) -#define CYCLES_800_T1H (F_CPU / 1250000) -#define CYCLES_800 (F_CPU / 800000) -#define CYCLES_400_T0H (F_CPU / 2000000) -#define CYCLES_400_T1H (F_CPU / 833333) -#define CYCLES_400 (F_CPU / 400000) +#elif defined(TEENSYDUINO) && \ + defined(KINETISK) // Teensy 3.0, 3.1, 3.2, 3.5, 3.6 +#define CYCLES_800_T0H (F_CPU / 4000000) +#define CYCLES_800_T1H (F_CPU / 1250000) +#define CYCLES_800 (F_CPU / 800000) +#define CYCLES_400_T0H (F_CPU / 2000000) +#define CYCLES_400_T1H (F_CPU / 833333) +#define CYCLES_400 (F_CPU / 400000) - uint8_t *p = pixels, - *end = p + numBytes, pix, mask; - volatile uint8_t *set = portSetRegister(pin), - *clr = portClearRegister(pin); - uint32_t cyc; + uint8_t *p = pixels, *end = p + numBytes, pix, mask; + volatile uint8_t *set = portSetRegister(pin), *clr = portClearRegister(pin); + uint32_t cyc; - ARM_DEMCR |= ARM_DEMCR_TRCENA; + ARM_DEMCR |= ARM_DEMCR_TRCENA; ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA; #if defined(NEO_KHZ400) // 800 KHz check needed only if 400 KHz support enabled - if(is800KHz) { + if (is800KHz) { #endif cyc = ARM_DWT_CYCCNT + CYCLES_800; - while(p < end) { + while (p < end) { pix = *p++; - for(mask = 0x80; mask; mask >>= 1) { - while(ARM_DWT_CYCCNT - cyc < CYCLES_800); - cyc = ARM_DWT_CYCCNT; + for (mask = 0x80; mask; mask >>= 1) { + while (ARM_DWT_CYCCNT - cyc < CYCLES_800) + ; + cyc = ARM_DWT_CYCCNT; *set = 1; - if(pix & mask) { - while(ARM_DWT_CYCCNT - cyc < CYCLES_800_T1H); + if (pix & mask) { + while (ARM_DWT_CYCCNT - cyc < CYCLES_800_T1H) + ; } else { - while(ARM_DWT_CYCCNT - cyc < CYCLES_800_T0H); + while (ARM_DWT_CYCCNT - cyc < CYCLES_800_T0H) + ; } *clr = 1; } } - while(ARM_DWT_CYCCNT - cyc < CYCLES_800); + while (ARM_DWT_CYCCNT - cyc < CYCLES_800) + ; #if defined(NEO_KHZ400) } else { // 400 kHz bitstream cyc = ARM_DWT_CYCCNT + CYCLES_400; - while(p < end) { + while (p < end) { pix = *p++; - for(mask = 0x80; mask; mask >>= 1) { - while(ARM_DWT_CYCCNT - cyc < CYCLES_400); - cyc = ARM_DWT_CYCCNT; + for (mask = 0x80; mask; mask >>= 1) { + while (ARM_DWT_CYCCNT - cyc < CYCLES_400) + ; + cyc = ARM_DWT_CYCCNT; *set = 1; - if(pix & mask) { - while(ARM_DWT_CYCCNT - cyc < CYCLES_400_T1H); + if (pix & mask) { + while (ARM_DWT_CYCCNT - cyc < CYCLES_400_T1H) + ; } else { - while(ARM_DWT_CYCCNT - cyc < CYCLES_400_T0H); + while (ARM_DWT_CYCCNT - cyc < CYCLES_400_T0H) + ; } *clr = 1; } } - while(ARM_DWT_CYCCNT - cyc < CYCLES_400); + while (ARM_DWT_CYCCNT - cyc < CYCLES_400) + ; } #endif // NEO_KHZ400 #elif defined(TEENSYDUINO) && (defined(__IMXRT1052__) || defined(__IMXRT1062__)) -#define CYCLES_800_T0H (F_CPU_ACTUAL / 4000000) -#define CYCLES_800_T1H (F_CPU_ACTUAL / 1250000) -#define CYCLES_800 (F_CPU_ACTUAL / 800000) -#define CYCLES_400_T0H (F_CPU_ACTUAL / 2000000) -#define CYCLES_400_T1H (F_CPU_ACTUAL / 833333) -#define CYCLES_400 (F_CPU_ACTUAL / 400000) - - uint8_t *p = pixels, - *end = p + numBytes, pix, mask; - volatile uint32_t *set = portSetRegister(pin), - *clr = portClearRegister(pin); - uint32_t cyc, - msk = digitalPinToBitMask(pin); - - ARM_DEMCR |= ARM_DEMCR_TRCENA; +#define CYCLES_800_T0H (F_CPU_ACTUAL / 4000000) +#define CYCLES_800_T1H (F_CPU_ACTUAL / 1250000) +#define CYCLES_800 (F_CPU_ACTUAL / 800000) +#define CYCLES_400_T0H (F_CPU_ACTUAL / 2000000) +#define CYCLES_400_T1H (F_CPU_ACTUAL / 833333) +#define CYCLES_400 (F_CPU_ACTUAL / 400000) + + uint8_t *p = pixels, *end = p + numBytes, pix, mask; + volatile uint32_t *set = portSetRegister(pin), *clr = portClearRegister(pin); + uint32_t cyc, msk = digitalPinToBitMask(pin); + + ARM_DEMCR |= ARM_DEMCR_TRCENA; ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA; #if defined(NEO_KHZ400) // 800 KHz check needed only if 400 KHz support enabled - if(is800KHz) { + if (is800KHz) { #endif cyc = ARM_DWT_CYCCNT + CYCLES_800; - while(p < end) { + while (p < end) { pix = *p++; - for(mask = 0x80; mask; mask >>= 1) { - while(ARM_DWT_CYCCNT - cyc < CYCLES_800); - cyc = ARM_DWT_CYCCNT; + for (mask = 0x80; mask; mask >>= 1) { + while (ARM_DWT_CYCCNT - cyc < CYCLES_800) + ; + cyc = ARM_DWT_CYCCNT; *set = msk; - if(pix & mask) { - while(ARM_DWT_CYCCNT - cyc < CYCLES_800_T1H); + if (pix & mask) { + while (ARM_DWT_CYCCNT - cyc < CYCLES_800_T1H) + ; } else { - while(ARM_DWT_CYCCNT - cyc < CYCLES_800_T0H); + while (ARM_DWT_CYCCNT - cyc < CYCLES_800_T0H) + ; } *clr = msk; } } - while(ARM_DWT_CYCCNT - cyc < CYCLES_800); + while (ARM_DWT_CYCCNT - cyc < CYCLES_800) + ; #if defined(NEO_KHZ400) } else { // 400 kHz bitstream cyc = ARM_DWT_CYCCNT + CYCLES_400; - while(p < end) { + while (p < end) { pix = *p++; - for(mask = 0x80; mask; mask >>= 1) { - while(ARM_DWT_CYCCNT - cyc < CYCLES_400); - cyc = ARM_DWT_CYCCNT; + for (mask = 0x80; mask; mask >>= 1) { + while (ARM_DWT_CYCCNT - cyc < CYCLES_400) + ; + cyc = ARM_DWT_CYCCNT; *set = msk; - if(pix & mask) { - while(ARM_DWT_CYCCNT - cyc < CYCLES_400_T1H); + if (pix & mask) { + while (ARM_DWT_CYCCNT - cyc < CYCLES_400_T1H) + ; } else { - while(ARM_DWT_CYCCNT - cyc < CYCLES_400_T0H); + while (ARM_DWT_CYCCNT - cyc < CYCLES_400_T0H) + ; } *clr = msk; } } - while(ARM_DWT_CYCCNT - cyc < CYCLES_400); + while (ARM_DWT_CYCCNT - cyc < CYCLES_400) + ; } #endif // NEO_KHZ400 #elif defined(TEENSYDUINO) && defined(__MKL26Z64__) // Teensy-LC #if F_CPU == 48000000 - uint8_t *p = pixels, - pix, count, dly, - bitmask = digitalPinToBitMask(pin); + uint8_t *p = pixels, pix, count, dly, bitmask = digitalPinToBitMask(pin); volatile uint8_t *reg = portSetRegister(pin); - uint32_t num = numBytes; - asm volatile( - "L%=_begin:" "\n\t" - "ldrb %[pix], [%[p], #0]" "\n\t" - "lsl %[pix], #24" "\n\t" - "movs %[count], #7" "\n\t" - "L%=_loop:" "\n\t" - "lsl %[pix], #1" "\n\t" - "bcs L%=_loop_one" "\n\t" - "L%=_loop_zero:" "\n\t" - "strb %[bitmask], [%[reg], #0]" "\n\t" - "movs %[dly], #4" "\n\t" - "L%=_loop_delay_T0H:" "\n\t" - "sub %[dly], #1" "\n\t" - "bne L%=_loop_delay_T0H" "\n\t" - "strb %[bitmask], [%[reg], #4]" "\n\t" - "movs %[dly], #13" "\n\t" - "L%=_loop_delay_T0L:" "\n\t" - "sub %[dly], #1" "\n\t" - "bne L%=_loop_delay_T0L" "\n\t" - "b L%=_next" "\n\t" - "L%=_loop_one:" "\n\t" - "strb %[bitmask], [%[reg], #0]" "\n\t" - "movs %[dly], #13" "\n\t" - "L%=_loop_delay_T1H:" "\n\t" - "sub %[dly], #1" "\n\t" - "bne L%=_loop_delay_T1H" "\n\t" - "strb %[bitmask], [%[reg], #4]" "\n\t" - "movs %[dly], #4" "\n\t" - "L%=_loop_delay_T1L:" "\n\t" - "sub %[dly], #1" "\n\t" - "bne L%=_loop_delay_T1L" "\n\t" - "nop" "\n\t" - "L%=_next:" "\n\t" - "sub %[count], #1" "\n\t" - "bne L%=_loop" "\n\t" - "lsl %[pix], #1" "\n\t" - "bcs L%=_last_one" "\n\t" - "L%=_last_zero:" "\n\t" - "strb %[bitmask], [%[reg], #0]" "\n\t" - "movs %[dly], #4" "\n\t" - "L%=_last_delay_T0H:" "\n\t" - "sub %[dly], #1" "\n\t" - "bne L%=_last_delay_T0H" "\n\t" - "strb %[bitmask], [%[reg], #4]" "\n\t" - "movs %[dly], #10" "\n\t" - "L%=_last_delay_T0L:" "\n\t" - "sub %[dly], #1" "\n\t" - "bne L%=_last_delay_T0L" "\n\t" - "b L%=_repeat" "\n\t" - "L%=_last_one:" "\n\t" - "strb %[bitmask], [%[reg], #0]" "\n\t" - "movs %[dly], #13" "\n\t" - "L%=_last_delay_T1H:" "\n\t" - "sub %[dly], #1" "\n\t" - "bne L%=_last_delay_T1H" "\n\t" - "strb %[bitmask], [%[reg], #4]" "\n\t" - "movs %[dly], #1" "\n\t" - "L%=_last_delay_T1L:" "\n\t" - "sub %[dly], #1" "\n\t" - "bne L%=_last_delay_T1L" "\n\t" - "nop" "\n\t" - "L%=_repeat:" "\n\t" - "add %[p], #1" "\n\t" - "sub %[num], #1" "\n\t" - "bne L%=_begin" "\n\t" - "L%=_done:" "\n\t" - : [p] "+r" (p), - [pix] "=&r" (pix), - [count] "=&r" (count), - [dly] "=&r" (dly), - [num] "+r" (num) - : [bitmask] "r" (bitmask), - [reg] "r" (reg) - ); + uint32_t num = numBytes; + asm volatile("L%=_begin:" + "\n\t" + "ldrb %[pix], [%[p], #0]" + "\n\t" + "lsl %[pix], #24" + "\n\t" + "movs %[count], #7" + "\n\t" + "L%=_loop:" + "\n\t" + "lsl %[pix], #1" + "\n\t" + "bcs L%=_loop_one" + "\n\t" + "L%=_loop_zero:" + "\n\t" + "strb %[bitmask], [%[reg], #0]" + "\n\t" + "movs %[dly], #4" + "\n\t" + "L%=_loop_delay_T0H:" + "\n\t" + "sub %[dly], #1" + "\n\t" + "bne L%=_loop_delay_T0H" + "\n\t" + "strb %[bitmask], [%[reg], #4]" + "\n\t" + "movs %[dly], #13" + "\n\t" + "L%=_loop_delay_T0L:" + "\n\t" + "sub %[dly], #1" + "\n\t" + "bne L%=_loop_delay_T0L" + "\n\t" + "b L%=_next" + "\n\t" + "L%=_loop_one:" + "\n\t" + "strb %[bitmask], [%[reg], #0]" + "\n\t" + "movs %[dly], #13" + "\n\t" + "L%=_loop_delay_T1H:" + "\n\t" + "sub %[dly], #1" + "\n\t" + "bne L%=_loop_delay_T1H" + "\n\t" + "strb %[bitmask], [%[reg], #4]" + "\n\t" + "movs %[dly], #4" + "\n\t" + "L%=_loop_delay_T1L:" + "\n\t" + "sub %[dly], #1" + "\n\t" + "bne L%=_loop_delay_T1L" + "\n\t" + "nop" + "\n\t" + "L%=_next:" + "\n\t" + "sub %[count], #1" + "\n\t" + "bne L%=_loop" + "\n\t" + "lsl %[pix], #1" + "\n\t" + "bcs L%=_last_one" + "\n\t" + "L%=_last_zero:" + "\n\t" + "strb %[bitmask], [%[reg], #0]" + "\n\t" + "movs %[dly], #4" + "\n\t" + "L%=_last_delay_T0H:" + "\n\t" + "sub %[dly], #1" + "\n\t" + "bne L%=_last_delay_T0H" + "\n\t" + "strb %[bitmask], [%[reg], #4]" + "\n\t" + "movs %[dly], #10" + "\n\t" + "L%=_last_delay_T0L:" + "\n\t" + "sub %[dly], #1" + "\n\t" + "bne L%=_last_delay_T0L" + "\n\t" + "b L%=_repeat" + "\n\t" + "L%=_last_one:" + "\n\t" + "strb %[bitmask], [%[reg], #0]" + "\n\t" + "movs %[dly], #13" + "\n\t" + "L%=_last_delay_T1H:" + "\n\t" + "sub %[dly], #1" + "\n\t" + "bne L%=_last_delay_T1H" + "\n\t" + "strb %[bitmask], [%[reg], #4]" + "\n\t" + "movs %[dly], #1" + "\n\t" + "L%=_last_delay_T1L:" + "\n\t" + "sub %[dly], #1" + "\n\t" + "bne L%=_last_delay_T1L" + "\n\t" + "nop" + "\n\t" + "L%=_repeat:" + "\n\t" + "add %[p], #1" + "\n\t" + "sub %[num], #1" + "\n\t" + "bne L%=_begin" + "\n\t" + "L%=_done:" + "\n\t" + : [p] "+r"(p), [pix] "=&r"(pix), [count] "=&r"(count), + [dly] "=&r"(dly), [num] "+r"(num) + : [bitmask] "r"(bitmask), [reg] "r"(reg)); #else #error "Sorry, only 48 MHz is supported, please set Tools > CPU Speed to 48 MHz" #endif // F_CPU == 48000000 -// Begin of support for nRF52 based boards ------------------------- + // Begin of support for nRF52 based boards ------------------------- #elif defined(NRF52) || defined(NRF52_SERIES) // [[[Begin of the Neopixel NRF52 EasyDMA implementation @@ -1369,16 +1976,16 @@ void Adafruit_NeoPixel::show(void) { //#define MAGIC_T1H 12UL | (0x8000) // 0.75us // WS2812B (rev B) timing is 0.4 and 0.8 us -#define MAGIC_T0H 6UL | (0x8000) // 0.375us -#define MAGIC_T1H 13UL | (0x8000) // 0.8125us +#define MAGIC_T0H 6UL | (0x8000) // 0.375us +#define MAGIC_T1H 13UL | (0x8000) // 0.8125us // WS2811 (400 khz) timing is 0.5 and 1.2 -#define MAGIC_T0H_400KHz 8UL | (0x8000) // 0.5us -#define MAGIC_T1H_400KHz 19UL | (0x8000) // 1.1875us +#define MAGIC_T0H_400KHz 8UL | (0x8000) // 0.5us +#define MAGIC_T1H_400KHz 19UL | (0x8000) // 1.1875us // For 400Khz, we double value of CTOPVAL -#define CTOPVAL 20UL // 1.25us -#define CTOPVAL_400KHz 40UL // 2.5us +#define CTOPVAL 20UL // 1.25us +#define CTOPVAL_400KHz 40UL // 2.5us // ---------- END Constants for the EasyDMA implementation ------------- // @@ -1390,14 +1997,14 @@ void Adafruit_NeoPixel::show(void) { // The number of cycles was hand picked and is guaranteed to be 100% // organic to preserve freshness and high accuracy. // ---------- BEGIN Constants for cycle counter implementation --------- -#define CYCLES_800_T0H 18 // ~0.36 uS -#define CYCLES_800_T1H 41 // ~0.76 uS -#define CYCLES_800 71 // ~1.25 uS +#define CYCLES_800_T0H 18 // ~0.36 uS +#define CYCLES_800_T1H 41 // ~0.76 uS +#define CYCLES_800 71 // ~1.25 uS -#define CYCLES_400_T0H 26 // ~0.50 uS -#define CYCLES_400_T1H 70 // ~1.26 uS -#define CYCLES_400 156 // ~2.50 uS -// ---------- END of Constants for cycle counter implementation -------- +#define CYCLES_400_T0H 26 // ~0.50 uS +#define CYCLES_400_T1H 70 // ~1.26 uS +#define CYCLES_400 156 // ~2.50 uS + // ---------- END of Constants for cycle counter implementation -------- // To support both the SoftDevice + Neopixels we use the EasyDMA // feature from the NRF25. However this technique implies to @@ -1409,55 +2016,60 @@ void Adafruit_NeoPixel::show(void) { // // If there is not enough memory, we will fall back to cycle counter // using DWT - uint32_t pattern_size = numBytes*8*sizeof(uint16_t)+2*sizeof(uint16_t); - uint16_t* pixels_pattern = NULL; + uint32_t pattern_size = + numBytes * 8 * sizeof(uint16_t) + 2 * sizeof(uint16_t); + uint16_t *pixels_pattern = NULL; - NRF_PWM_Type* pwm = NULL; + NRF_PWM_Type *pwm = NULL; // Try to find a free PWM device, which is not enabled // and has no connected pins - NRF_PWM_Type* PWM[] = { - NRF_PWM0, NRF_PWM1, NRF_PWM2 + NRF_PWM_Type *PWM[] = { + NRF_PWM0, + NRF_PWM1, + NRF_PWM2 #if defined(NRF_PWM3) - ,NRF_PWM3 + , + NRF_PWM3 #endif }; - for(unsigned int device = 0; device < (sizeof(PWM)/sizeof(PWM[0])); device++) { - if( (PWM[device]->ENABLE == 0) && + for (unsigned int device = 0; device < (sizeof(PWM) / sizeof(PWM[0])); + device++) { + if ((PWM[device]->ENABLE == 0) && (PWM[device]->PSEL.OUT[0] & PWM_PSEL_OUT_CONNECT_Msk) && (PWM[device]->PSEL.OUT[1] & PWM_PSEL_OUT_CONNECT_Msk) && (PWM[device]->PSEL.OUT[2] & PWM_PSEL_OUT_CONNECT_Msk) && - (PWM[device]->PSEL.OUT[3] & PWM_PSEL_OUT_CONNECT_Msk) - ) { + (PWM[device]->PSEL.OUT[3] & PWM_PSEL_OUT_CONNECT_Msk)) { pwm = PWM[device]; break; } } // only malloc if there is PWM device available - if ( pwm != NULL ) { - #if defined(ARDUINO_NRF52_ADAFRUIT) // use thread-safe malloc - pixels_pattern = (uint16_t *) rtos_malloc(pattern_size); - #else - pixels_pattern = (uint16_t *) malloc(pattern_size); - #endif + if (pwm != NULL) { +#if defined(ARDUINO_NRF52_ADAFRUIT) // use thread-safe malloc + pixels_pattern = (uint16_t *)rtos_malloc(pattern_size); +#else + pixels_pattern = (uint16_t *)malloc(pattern_size); +#endif } // Use the identified device to choose the implementation // If a PWM device is available use DMA - if( (pixels_pattern != NULL) && (pwm != NULL) ) { + if ((pixels_pattern != NULL) && (pwm != NULL)) { uint16_t pos = 0; // bit position - for(uint16_t n=0; n<numBytes; n++) { + for (uint16_t n = 0; n < numBytes; n++) { uint8_t pix = pixels[n]; - for(uint8_t mask=0x80; mask>0; mask >>= 1) { - #if defined(NEO_KHZ400) - if( !is800KHz ) { - pixels_pattern[pos] = (pix & mask) ? MAGIC_T1H_400KHz : MAGIC_T0H_400KHz; - }else - #endif + for (uint8_t mask = 0x80; mask > 0; mask >>= 1) { +#if defined(NEO_KHZ400) + if (!is800KHz) { + pixels_pattern[pos] = + (pix & mask) ? MAGIC_T1H_400KHz : MAGIC_T0H_400KHz; + } else +#endif { pixels_pattern[pos] = (pix & mask) ? MAGIC_T1H : MAGIC_T0H; } @@ -1474,15 +2086,16 @@ void Adafruit_NeoPixel::show(void) { pwm->MODE = (PWM_MODE_UPDOWN_Up << PWM_MODE_UPDOWN_Pos); // Set the PWM to use the 16MHz clock - pwm->PRESCALER = (PWM_PRESCALER_PRESCALER_DIV_1 << PWM_PRESCALER_PRESCALER_Pos); + pwm->PRESCALER = + (PWM_PRESCALER_PRESCALER_DIV_1 << PWM_PRESCALER_PRESCALER_Pos); // Setting of the maximum count // but keeping it on 16Mhz allows for more granularity just // in case someone wants to do more fine-tuning of the timing. #if defined(NEO_KHZ400) - if( !is800KHz ) { + if (!is800KHz) { pwm->COUNTERTOP = (CTOPVAL_400KHz << PWM_COUNTERTOP_COUNTERTOP_Pos); - }else + } else #endif { pwm->COUNTERTOP = (CTOPVAL << PWM_COUNTERTOP_COUNTERTOP_Pos); @@ -1501,10 +2114,10 @@ void Adafruit_NeoPixel::show(void) { pwm->SEQ[0].PTR = (uint32_t)(pixels_pattern) << PWM_SEQ_PTR_PTR_Pos; // Calculation of the number of steps loaded from memory. - pwm->SEQ[0].CNT = (pattern_size/sizeof(uint16_t)) << PWM_SEQ_CNT_CNT_Pos; + pwm->SEQ[0].CNT = (pattern_size / sizeof(uint16_t)) << PWM_SEQ_CNT_CNT_Pos; // The following settings are ignored with the current config. - pwm->SEQ[0].REFRESH = 0; + pwm->SEQ[0].REFRESH = 0; pwm->SEQ[0].ENDDELAY = 0; // The Neopixel implementation is a blocking algorithm. DMA @@ -1512,29 +2125,28 @@ void Adafruit_NeoPixel::show(void) { // operation we enable the interruption for the end of sequence // and block the execution thread until the event flag is set by // the peripheral. -// pwm->INTEN |= (PWM_INTEN_SEQEND0_Enabled<<PWM_INTEN_SEQEND0_Pos); + // pwm->INTEN |= (PWM_INTEN_SEQEND0_Enabled<<PWM_INTEN_SEQEND0_Pos); - // PSEL must be configured before enabling PWM - #if defined(ARDUINO_ARCH_NRF52840) +// PSEL must be configured before enabling PWM +#if defined(ARDUINO_ARCH_NRF52840) pwm->PSEL.OUT[0] = g_APinDescription[pin].name; - #else +#else pwm->PSEL.OUT[0] = g_ADigitalPinMap[pin]; - #endif +#endif // Enable the PWM pwm->ENABLE = 1; // After all of this and many hours of reading the documentation // we are ready to start the sequence... - pwm->EVENTS_SEQEND[0] = 0; + pwm->EVENTS_SEQEND[0] = 0; pwm->TASKS_SEQSTART[0] = 1; // But we have to wait for the flag to be set. - while(!pwm->EVENTS_SEQEND[0]) - { - #if defined(ARDUINO_NRF52_ADAFRUIT) || defined(ARDUINO_ARCH_NRF52840) + while (!pwm->EVENTS_SEQEND[0]) { +#if defined(ARDUINO_NRF52_ADAFRUIT) || defined(ARDUINO_ARCH_NRF52840) yield(); - #endif +#endif } // Before leave we clear the flag for the event. @@ -1548,40 +2160,39 @@ void Adafruit_NeoPixel::show(void) { pwm->PSEL.OUT[0] = 0xFFFFFFFFUL; - #if defined(ARDUINO_NRF52_ADAFRUIT) // use thread-safe free - rtos_free(pixels_pattern); - #else - free(pixels_pattern); - #endif - }// End of DMA implementation +#if defined(ARDUINO_NRF52_ADAFRUIT) // use thread-safe free + rtos_free(pixels_pattern); +#else + free(pixels_pattern); +#endif + } // End of DMA implementation // --------------------------------------------------------------------- - else{ -#ifndef ARDUINO_ARCH_NRF52840 - // Fall back to DWT - #if defined(ARDUINO_NRF52_ADAFRUIT) - // Bluefruit Feather 52 uses freeRTOS - // Critical Section is used since it does not block SoftDevice execution - taskENTER_CRITICAL(); - #elif defined(NRF52_DISABLE_INT) - // If you are using the Bluetooth SoftDevice we advise you to not disable - // the interrupts. Disabling the interrupts even for short periods of time - // causes the SoftDevice to stop working. - // Disable the interrupts only in cases where you need high performance for - // the LEDs and if you are not using the EasyDMA feature. - __disable_irq(); - #endif - - NRF_GPIO_Type* nrf_port = (NRF_GPIO_Type*) digitalPinToPort(pin); + else { +#ifndef ARDUINO_ARCH_NRF52840 +// Fall back to DWT +#if defined(ARDUINO_NRF52_ADAFRUIT) + // Bluefruit Feather 52 uses freeRTOS + // Critical Section is used since it does not block SoftDevice execution + taskENTER_CRITICAL(); +#elif defined(NRF52_DISABLE_INT) + // If you are using the Bluetooth SoftDevice we advise you to not disable + // the interrupts. Disabling the interrupts even for short periods of time + // causes the SoftDevice to stop working. + // Disable the interrupts only in cases where you need high performance for + // the LEDs and if you are not using the EasyDMA feature. + __disable_irq(); +#endif + + NRF_GPIO_Type *nrf_port = (NRF_GPIO_Type *)digitalPinToPort(pin); uint32_t pinMask = digitalPinToBitMask(pin); - uint32_t CYCLES_X00 = CYCLES_800; + uint32_t CYCLES_X00 = CYCLES_800; uint32_t CYCLES_X00_T1H = CYCLES_800_T1H; uint32_t CYCLES_X00_T0H = CYCLES_800_T0H; #if defined(NEO_KHZ400) - if( !is800KHz ) - { - CYCLES_X00 = CYCLES_400; + if (!is800KHz) { + CYCLES_X00 = CYCLES_400; CYCLES_X00_T1H = CYCLES_400_T1H; CYCLES_X00_T0H = CYCLES_400_T0H; } @@ -1592,36 +2203,39 @@ void Adafruit_NeoPixel::show(void) { DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; // Tries to re-send the frame if is interrupted by the SoftDevice. - while(1) { + while (1) { uint8_t *p = pixels; uint32_t cycStart = DWT->CYCCNT; uint32_t cyc = 0; - for(uint16_t n=0; n<numBytes; n++) { + for (uint16_t n = 0; n < numBytes; n++) { uint8_t pix = *p++; - for(uint8_t mask = 0x80; mask; mask >>= 1) { - while(DWT->CYCCNT - cyc < CYCLES_X00); - cyc = DWT->CYCCNT; + for (uint8_t mask = 0x80; mask; mask >>= 1) { + while (DWT->CYCCNT - cyc < CYCLES_X00) + ; + cyc = DWT->CYCCNT; nrf_port->OUTSET |= pinMask; - if(pix & mask) { - while(DWT->CYCCNT - cyc < CYCLES_X00_T1H); + if (pix & mask) { + while (DWT->CYCCNT - cyc < CYCLES_X00_T1H) + ; } else { - while(DWT->CYCCNT - cyc < CYCLES_X00_T0H); + while (DWT->CYCCNT - cyc < CYCLES_X00_T0H) + ; } nrf_port->OUTCLR |= pinMask; } } - while(DWT->CYCCNT - cyc < CYCLES_X00); - + while (DWT->CYCCNT - cyc < CYCLES_X00) + ; // If total time longer than 25%, resend the whole data. // Since we are likely to be interrupted by SoftDevice - if ( (DWT->CYCCNT - cycStart) < ( 8*numBytes*((CYCLES_X00*5)/4) ) ) { + if ((DWT->CYCCNT - cycStart) < (8 * numBytes * ((CYCLES_X00 * 5) / 4))) { break; } @@ -1629,40 +2243,44 @@ void Adafruit_NeoPixel::show(void) { delayMicroseconds(300); } - // Enable interrupts again - #if defined(ARDUINO_NRF52_ADAFRUIT) - taskEXIT_CRITICAL(); - #elif defined(NRF52_DISABLE_INT) - __enable_irq(); - #endif +// Enable interrupts again +#if defined(ARDUINO_NRF52_ADAFRUIT) + taskEXIT_CRITICAL(); +#elif defined(NRF52_DISABLE_INT) + __enable_irq(); +#endif #endif } -// END of NRF52 implementation + // END of NRF52 implementation -#elif defined (__SAMD21E17A__) || defined(__SAMD21G18A__) || defined(__SAMD21E18A__) || defined(__SAMD21J18A__) // Arduino Zero, Gemma/Trinket M0, SODAQ Autonomo and others +#elif defined(__SAMD21E17A__) || defined(__SAMD21G18A__) || \ + defined(__SAMD21E18A__) || defined(__SAMD21J18A__) || \ + defined (__SAMD11C14A__) + // Arduino Zero, Gemma/Trinket M0, SODAQ Autonomo + // and others // Tried this with a timer/counter, couldn't quite get adequate // resolution. So yay, you get a load of goofball NOPs... - uint8_t *ptr, *end, p, bitMask, portNum; - uint32_t pinMask; + uint8_t *ptr, *end, p, bitMask, portNum; + uint32_t pinMask; - portNum = g_APinDescription[pin].ulPort; - pinMask = 1ul << g_APinDescription[pin].ulPin; - ptr = pixels; - end = ptr + numBytes; - p = *ptr++; - bitMask = 0x80; + portNum = g_APinDescription[pin].ulPort; + pinMask = 1ul << g_APinDescription[pin].ulPin; + ptr = pixels; + end = ptr + numBytes; + p = *ptr++; + bitMask = 0x80; volatile uint32_t *set = &(PORT->Group[portNum].OUTSET.reg), *clr = &(PORT->Group[portNum].OUTCLR.reg); #if defined(NEO_KHZ400) // 800 KHz check needed only if 400 KHz support enabled - if(is800KHz) { + if (is800KHz) { #endif - for(;;) { + for (;;) { *set = pinMask; asm("nop; nop; nop; nop; nop; nop; nop; nop;"); - if(p & bitMask) { + if (p & bitMask) { asm("nop; nop; nop; nop; nop; nop; nop; nop;" "nop; nop; nop; nop; nop; nop; nop; nop;" "nop; nop; nop; nop;"); @@ -1673,20 +2291,21 @@ void Adafruit_NeoPixel::show(void) { "nop; nop; nop; nop; nop; nop; nop; nop;" "nop; nop; nop; nop;"); } - if(bitMask >>= 1) { + if (bitMask >>= 1) { asm("nop; nop; nop; nop; nop; nop; nop; nop; nop;"); } else { - if(ptr >= end) break; - p = *ptr++; + if (ptr >= end) + break; + p = *ptr++; bitMask = 0x80; } } #if defined(NEO_KHZ400) } else { // 400 KHz bitstream - for(;;) { + for (;;) { *set = pinMask; asm("nop; nop; nop; nop; nop; nop; nop; nop; nop; nop; nop;"); - if(p & bitMask) { + if (p & bitMask) { asm("nop; nop; nop; nop; nop; nop; nop; nop;" "nop; nop; nop; nop; nop; nop; nop; nop;" "nop; nop; nop; nop; nop; nop; nop; nop;" @@ -1703,29 +2322,195 @@ void Adafruit_NeoPixel::show(void) { "nop; nop; nop; nop; nop; nop; nop; nop;" "nop; nop; nop; nop; nop; nop; nop; nop;" "nop; nop; nop; nop; nop; nop; nop; nop;"); - if(bitMask >>= 1) { + if (bitMask >>= 1) { asm("nop; nop; nop; nop; nop; nop; nop;"); } else { - if(ptr >= end) break; - p = *ptr++; + if (ptr >= end) + break; + p = *ptr++; bitMask = 0x80; } } } #endif -#elif defined (__SAMD51__) // M4 +//---- +#elif defined(XMC1100_XMC2GO) || defined(XMC1100_H_BRIDGE2GO) || defined(XMC1100_Boot_Kit) || defined(XMC1300_Boot_Kit) + + // XMC1100/1200/1300 with ARM Cortex M0 are running with 32MHz, XMC1400 runs with 48MHz so may not work + // Tried this with a timer/counter, couldn't quite get adequate + // resolution. So yay, you get a load of goofball NOPs... - uint8_t *ptr, *end, p, bitMask, portNum, bit; + uint8_t *ptr, *end, p, bitMask, portNum; uint32_t pinMask; - portNum = g_APinDescription[pin].ulPort; - pinMask = 1ul << g_APinDescription[pin].ulPin; ptr = pixels; end = ptr + numBytes; p = *ptr++; bitMask = 0x80; + XMC_GPIO_PORT_t* XMC_port = mapping_port_pin[ pin ].port; + uint8_t XMC_pin = mapping_port_pin[ pin ].pin; + + uint32_t omrhigh = (uint32_t)XMC_GPIO_OUTPUT_LEVEL_HIGH << XMC_pin; + uint32_t omrlow = (uint32_t)XMC_GPIO_OUTPUT_LEVEL_LOW << XMC_pin; + +#ifdef NEO_KHZ400 // 800 KHz check needed only if 400 KHz support enabled + if(is800KHz) { +#endif + for(;;) { + XMC_port->OMR = omrhigh; + asm("nop; nop; nop; nop;"); + if(p & bitMask) { + asm("nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop;"); + XMC_port->OMR = omrlow; + } else { + XMC_port->OMR = omrlow; + asm("nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop;"); + } + if(bitMask >>= 1) { + asm("nop; nop; nop; nop; nop;"); + } else { + if(ptr >= end) break; + p = *ptr++; + bitMask = 0x80; + } + } +#ifdef NEO_KHZ400 // untested code + } else { // 400 KHz bitstream + for(;;) { + XMC_port->OMR = omrhigh; + asm("nop; nop; nop; nop; nop;"); + if(p & bitMask) { + asm("nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop;"); + XMC_port->OMR = omrlow; + } else { + XMC_port->OMR = omrlow; + asm("nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop;"); + } + asm("nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;"); + if(bitMask >>= 1) { + asm("nop; nop; nop;"); + } else { + if(ptr >= end) break; + p = *ptr++; + bitMask = 0x80; + } + } + } + +#endif +//---- + +//---- +#elif defined(XMC4700_Relax_Kit) || defined(XMC4800_Relax_Kit) + +// XMC4700 and XMC4800 with ARM Cortex M4 are running with 144MHz +// Tried this with a timer/counter, couldn't quite get adequate +// resolution. So yay, you get a load of goofball NOPs... + +uint8_t *ptr, *end, p, bitMask, portNum; +uint32_t pinMask; + +ptr = pixels; +end = ptr + numBytes; +p = *ptr++; +bitMask = 0x80; + +XMC_GPIO_PORT_t* XMC_port = mapping_port_pin[ pin ].port; +uint8_t XMC_pin = mapping_port_pin[ pin ].pin; + +uint32_t omrhigh = (uint32_t)XMC_GPIO_OUTPUT_LEVEL_HIGH << XMC_pin; +uint32_t omrlow = (uint32_t)XMC_GPIO_OUTPUT_LEVEL_LOW << XMC_pin; + +#ifdef NEO_KHZ400 // 800 KHz check needed only if 400 KHz support enabled +if(is800KHz) { +#endif + + for(;;) { + XMC_port->OMR = omrhigh; + asm("nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop;"); + if(p & bitMask) { + asm("nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;"); + XMC_port->OMR = omrlow; + } else { + XMC_port->OMR = omrlow; + asm("nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;"); + } + if(bitMask >>= 1) { + asm("nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;"); + } else { + if(ptr >= end) break; + p = *ptr++; + bitMask = 0x80; + } + } + + +#ifdef NEO_KHZ400 + } else { // 400 KHz bitstream + // ToDo! + } +#endif +//---- + +#elif defined(__SAMD51__) // M4 + + uint8_t *ptr, *end, p, bitMask, portNum, bit; + uint32_t pinMask; + + portNum = g_APinDescription[pin].ulPort; + pinMask = 1ul << g_APinDescription[pin].ulPin; + ptr = pixels; + end = ptr + numBytes; + p = *ptr++; + bitMask = 0x80; + volatile uint32_t *set = &(PORT->Group[portNum].OUTSET.reg), *clr = &(PORT->Group[portNum].OUTCLR.reg); @@ -1745,65 +2530,67 @@ void Adafruit_NeoPixel::show(void) { // seems to work just well enough. When finished, the SysTick // peripheral is set back to its original state. - uint32_t t0, t1, top, ticks, - saveLoad = SysTick->LOAD, saveVal = SysTick->VAL; + uint32_t t0, t1, top, ticks, saveLoad = SysTick->LOAD, saveVal = SysTick->VAL; #if defined(NEO_KHZ400) // 800 KHz check needed only if 400 KHz support enabled - if(is800KHz) { + if (is800KHz) { #endif - top = (uint32_t)(F_CPU * 0.00000125); // Bit hi + lo = 1.25 uS - t0 = top - (uint32_t)(F_CPU * 0.00000040); // 0 = 0.4 uS hi - t1 = top - (uint32_t)(F_CPU * 0.00000080); // 1 = 0.8 uS hi + top = (uint32_t)(F_CPU * 0.00000125); // Bit hi + lo = 1.25 uS + t0 = top - (uint32_t)(F_CPU * 0.00000040); // 0 = 0.4 uS hi + t1 = top - (uint32_t)(F_CPU * 0.00000080); // 1 = 0.8 uS hi #if defined(NEO_KHZ400) - } else { // 400 KHz bitstream - top = (uint32_t)(F_CPU * 0.00000250); // Bit hi + lo = 2.5 uS - t0 = top - (uint32_t)(F_CPU * 0.00000050); // 0 = 0.5 uS hi - t1 = top - (uint32_t)(F_CPU * 0.00000120); // 1 = 1.2 uS hi + } else { // 400 KHz bitstream + top = (uint32_t)(F_CPU * 0.00000250); // Bit hi + lo = 2.5 uS + t0 = top - (uint32_t)(F_CPU * 0.00000050); // 0 = 0.5 uS hi + t1 = top - (uint32_t)(F_CPU * 0.00000120); // 1 = 1.2 uS hi } #endif - SysTick->LOAD = top; // Config SysTick for NeoPixel bit freq - SysTick->VAL = top; // Set to start value (counts down) - (void)SysTick->VAL; // Dummy read helps sync up 1st bit + SysTick->LOAD = top; // Config SysTick for NeoPixel bit freq + SysTick->VAL = top; // Set to start value (counts down) + (void)SysTick->VAL; // Dummy read helps sync up 1st bit - for(;;) { - *set = pinMask; // Set output high + for (;;) { + *set = pinMask; // Set output high ticks = (p & bitMask) ? t1 : t0; // SysTick threshold, - while(SysTick->VAL > ticks); // wait for it - *clr = pinMask; // Set output low - if(!(bitMask >>= 1)) { // Next bit for this byte...done? - if(ptr >= end) break; // If last byte sent, exit loop - p = *ptr++; // Fetch next byte - bitMask = 0x80; // Reset bitmask + while (SysTick->VAL > ticks) + ; // wait for it + *clr = pinMask; // Set output low + if (!(bitMask >>= 1)) { // Next bit for this byte...done? + if (ptr >= end) + break; // If last byte sent, exit loop + p = *ptr++; // Fetch next byte + bitMask = 0x80; // Reset bitmask } - while(SysTick->VAL <= ticks); // Wait for rollover to 'top' + while (SysTick->VAL <= ticks) + ; // Wait for rollover to 'top' } - SysTick->LOAD = saveLoad; // Restore SysTick rollover to 1 ms - SysTick->VAL = saveVal; // Restore SysTick value + SysTick->LOAD = saveLoad; // Restore SysTick rollover to 1 ms + SysTick->VAL = saveVal; // Restore SysTick value -#elif defined (ARDUINO_STM32_FEATHER) // FEATHER WICED (120MHz) +#elif defined(ARDUINO_STM32_FEATHER) // FEATHER WICED (120MHz) // Tried this with a timer/counter, couldn't quite get adequate // resolution. So yay, you get a load of goofball NOPs... - uint8_t *ptr, *end, p, bitMask; - uint32_t pinMask; + uint8_t *ptr, *end, p, bitMask; + uint32_t pinMask; - pinMask = BIT(PIN_MAP[pin].gpio_bit); - ptr = pixels; - end = ptr + numBytes; - p = *ptr++; - bitMask = 0x80; + pinMask = BIT(PIN_MAP[pin].gpio_bit); + ptr = pixels; + end = ptr + numBytes; + p = *ptr++; + bitMask = 0x80; volatile uint16_t *set = &(PIN_MAP[pin].gpio_device->regs->BSRRL); volatile uint16_t *clr = &(PIN_MAP[pin].gpio_device->regs->BSRRH); #if defined(NEO_KHZ400) // 800 KHz check needed only if 400 KHz support enabled - if(is800KHz) { + if (is800KHz) { #endif - for(;;) { - if(p & bitMask) { // ONE + for (;;) { + if (p & bitMask) { // ONE // High 800ns *set = pinMask; asm("nop; nop; nop; nop; nop; nop; nop; nop;" @@ -1849,12 +2636,13 @@ void Adafruit_NeoPixel::show(void) { "nop; nop; nop; nop; nop; nop; nop; nop;" "nop; nop; nop; nop;"); } - if(bitMask >>= 1) { + if (bitMask >>= 1) { // Move on to the next pixel asm("nop;"); } else { - if(ptr >= end) break; - p = *ptr++; + if (ptr >= end) + break; + p = *ptr++; bitMask = 0x80; } } @@ -1865,17 +2653,17 @@ void Adafruit_NeoPixel::show(void) { #endif #elif defined(TARGET_LPC1768) - uint8_t *ptr, *end, p, bitMask; - ptr = pixels; - end = ptr + numBytes; - p = *ptr++; - bitMask = 0x80; + uint8_t *ptr, *end, p, bitMask; + ptr = pixels; + end = ptr + numBytes; + p = *ptr++; + bitMask = 0x80; #if defined(NEO_KHZ400) // 800 KHz check needed only if 400 KHz support enabled - if(is800KHz) { + if (is800KHz) { #endif - for(;;) { - if(p & bitMask) { + for (;;) { + if (p & bitMask) { // data ONE high // min: 550 typ: 700 max: 5,500 gpio_set(pin); @@ -1893,12 +2681,13 @@ void Adafruit_NeoPixel::show(void) { gpio_clear(pin); time::delay_ns(450); } - if(bitMask >>= 1) { + if (bitMask >>= 1) { // Move on to the next pixel asm("nop;"); } else { - if(ptr >= end) break; - p = *ptr++; + if (ptr >= end) + break; + p = *ptr++; bitMask = 0x80; } } @@ -1908,204 +2697,229 @@ void Adafruit_NeoPixel::show(void) { } #endif #elif defined(ARDUINO_ARCH_STM32) || defined(ARDUINO_ARCH_ARDUINO_CORE_STM32) - uint8_t *p = pixels, *end = p + numBytes, - pix = *p++, mask = 0x80; - uint32_t cyc; + uint8_t *p = pixels, *end = p + numBytes, pix = *p++, mask = 0x80; + uint32_t cyc; uint32_t saveLoad = SysTick->LOAD, saveVal = SysTick->VAL; #if defined(NEO_KHZ400) // 800 KHz check needed only if 400 KHz support enabled - if(is800KHz) { + if (is800KHz) { #endif - uint32_t top = (F_CPU / 800000); // 1.25µs - uint32_t t0 = top - (F_CPU / 2500000); // 0.4µs - uint32_t t1 = top - (F_CPU / 1250000); // 0.8µs + uint32_t top = (F_CPU / 800000); // 1.25µs + uint32_t t0 = top - (F_CPU / 2500000); // 0.4µs + uint32_t t1 = top - (F_CPU / 1250000); // 0.8µs SysTick->LOAD = top - 1; // Config SysTick for NeoPixel bit freq - SysTick->VAL = 0; // Set to start value - for(;;) { + SysTick->VAL = 0; // Set to start value + for (;;) { LL_GPIO_SetOutputPin(gpioPort, gpioPin); cyc = (pix & mask) ? t1 : t0; - while(SysTick->VAL > cyc); + while (SysTick->VAL > cyc) + ; LL_GPIO_ResetOutputPin(gpioPort, gpioPin); - if(!(mask >>= 1)) { - if(p >= end) break; - pix = *p++; + if (!(mask >>= 1)) { + if (p >= end) + break; + pix = *p++; mask = 0x80; } - while(SysTick->VAL <= cyc); + while (SysTick->VAL <= cyc) + ; } #if defined(NEO_KHZ400) - } else { // 400 kHz bitstream - uint32_t top = (F_CPU / 400000); // 2.5µs - uint32_t t0 = top - (F_CPU / 2000000); // 0.5µs - uint32_t t1 = top - (F_CPU / 833333); // 1.2µs + } else { // 400 kHz bitstream + uint32_t top = (F_CPU / 400000); // 2.5µs + uint32_t t0 = top - (F_CPU / 2000000); // 0.5µs + uint32_t t1 = top - (F_CPU / 833333); // 1.2µs SysTick->LOAD = top - 1; // Config SysTick for NeoPixel bit freq - SysTick->VAL = 0; // Set to start value - for(;;) { + SysTick->VAL = 0; // Set to start value + for (;;) { LL_GPIO_SetOutputPin(gpioPort, gpioPin); cyc = (pix & mask) ? t1 : t0; - while(SysTick->VAL > cyc); + while (SysTick->VAL > cyc) + ; LL_GPIO_ResetOutputPin(gpioPort, gpioPin); - if(!(mask >>= 1)) { - if(p >= end) break; - pix = *p++; + if (!(mask >>= 1)) { + if (p >= end) + break; + pix = *p++; mask = 0x80; } - while(SysTick->VAL <= cyc); + while (SysTick->VAL <= cyc) + ; } } #endif // NEO_KHZ400 - SysTick->LOAD = saveLoad; // Restore SysTick rollover to 1 ms - SysTick->VAL = saveVal; // Restore SysTick value -#elif defined (NRF51) - uint8_t *p = pixels, - pix, count, mask; - int32_t num = numBytes; - unsigned int bitmask = ( 1 << g_ADigitalPinMap[pin] ); -// https://github.com/sandeepmistry/arduino-nRF5/blob/dc53980c8bac27898fca90d8ecb268e11111edc1/variants/BBCmicrobit/variant.cpp + SysTick->LOAD = saveLoad; // Restore SysTick rollover to 1 ms + SysTick->VAL = saveVal; // Restore SysTick value +#elif defined(NRF51) + uint8_t *p = pixels, pix, count, mask; + int32_t num = numBytes; + unsigned int bitmask = (1 << g_ADigitalPinMap[pin]); + // https://github.com/sandeepmistry/arduino-nRF5/blob/dc53980c8bac27898fca90d8ecb268e11111edc1/variants/BBCmicrobit/variant.cpp - volatile unsigned int *reg = (unsigned int *) (0x50000000UL + 0x508); + volatile unsigned int *reg = (unsigned int *)(0x50000000UL + 0x508); -// https://github.com/sandeepmistry/arduino-nRF5/blob/dc53980c8bac27898fca90d8ecb268e11111edc1/cores/nRF5/SDK/components/device/nrf51.h -// http://www.iot-programmer.com/index.php/books/27-micro-bit-iot-in-c/chapters-micro-bit-iot-in-c/47-micro-bit-iot-in-c-fast-memory-mapped-gpio?showall=1 -// https://github.com/Microsoft/pxt-neopixel/blob/master/sendbuffer.asm + // https://github.com/sandeepmistry/arduino-nRF5/blob/dc53980c8bac27898fca90d8ecb268e11111edc1/cores/nRF5/SDK/components/device/nrf51.h + // http://www.iot-programmer.com/index.php/books/27-micro-bit-iot-in-c/chapters-micro-bit-iot-in-c/47-micro-bit-iot-in-c-fast-memory-mapped-gpio?showall=1 + // https://github.com/Microsoft/pxt-neopixel/blob/master/sendbuffer.asm asm volatile( - // "cpsid i" ; disable irq - - // b .start - "b L%=_start" "\n\t" - // .nextbit: ; C0 - "L%=_nextbit:" "\n\t" //; C0 - // str r1, [r3, #0] ; pin := hi C2 - "strb %[bitmask], [%[reg], #0]" "\n\t" //; pin := hi C2 - // tst r6, r0 ; C3 - "tst %[mask], %[pix]" "\n\t"// ; C3 - // bne .islate ; C4 - "bne L%=_islate" "\n\t" //; C4 - // str r1, [r2, #0] ; pin := lo C6 - "strb %[bitmask], [%[reg], #4]" "\n\t" //; pin := lo C6 - // .islate: - "L%=_islate:" "\n\t" - // lsrs r6, r6, #1 ; r6 >>= 1 C7 - "lsr %[mask], %[mask], #1" "\n\t" //; r6 >>= 1 C7 - // bne .justbit ; C8 - "bne L%=_justbit" "\n\t" //; C8 - - // ; not just a bit - need new byte - // adds r4, #1 ; r4++ C9 - "add %[p], #1" "\n\t" //; r4++ C9 - // subs r5, #1 ; r5-- C10 - "sub %[num], #1" "\n\t" //; r5-- C10 - // bcc .stop ; if (r5<0) goto .stop C11 - "bcc L%=_stop" "\n\t" //; if (r5<0) goto .stop C11 - // .start: - "L%=_start:" - // movs r6, #0x80 ; reset mask C12 - "movs %[mask], #0x80" "\n\t" //; reset mask C12 - // nop ; C13 - "nop" "\n\t" //; C13 - - // .common: ; C13 - "L%=_common:" "\n\t" //; C13 - // str r1, [r2, #0] ; pin := lo C15 - "strb %[bitmask], [%[reg], #4]" "\n\t" //; pin := lo C15 - // ; always re-load byte - it just fits with the cycles better this way - // ldrb r0, [r4, #0] ; r0 := *r4 C17 - "ldrb %[pix], [%[p], #0]" "\n\t" //; r0 := *r4 C17 - // b .nextbit ; C20 - "b L%=_nextbit" "\n\t" //; C20 - - // .justbit: ; C10 - "L%=_justbit:" "\n\t" //; C10 - // ; no nops, branch taken is already 3 cycles - // b .common ; C13 - "b L%=_common" "\n\t" //; C13 - - // .stop: - "L%=_stop:" "\n\t" - // str r1, [r2, #0] ; pin := lo - "strb %[bitmask], [%[reg], #4]" "\n\t" //; pin := lo - // cpsie i ; enable irq - - : [p] "+r" (p), - [pix] "=&r" (pix), - [count] "=&r" (count), - [mask] "=&r" (mask), - [num] "+r" (num) - : [bitmask] "r" (bitmask), - [reg] "r" (reg) - ); + // "cpsid i" ; disable irq + + // b .start + "b L%=_start" + "\n\t" + // .nextbit: ; C0 + "L%=_nextbit:" + "\n\t" //; C0 + // str r1, [r3, #0] ; pin := hi C2 + "strb %[bitmask], [%[reg], #0]" + "\n\t" //; pin := hi C2 + // tst r6, r0 ; C3 + "tst %[mask], %[pix]" + "\n\t" // ; C3 + // bne .islate ; C4 + "bne L%=_islate" + "\n\t" //; C4 + // str r1, [r2, #0] ; pin := lo C6 + "strb %[bitmask], [%[reg], #4]" + "\n\t" //; pin := lo C6 + // .islate: + "L%=_islate:" + "\n\t" + // lsrs r6, r6, #1 ; r6 >>= 1 C7 + "lsr %[mask], %[mask], #1" + "\n\t" //; r6 >>= 1 C7 + // bne .justbit ; C8 + "bne L%=_justbit" + "\n\t" //; C8 + + // ; not just a bit - need new byte + // adds r4, #1 ; r4++ C9 + "add %[p], #1" + "\n\t" //; r4++ C9 + // subs r5, #1 ; r5-- C10 + "sub %[num], #1" + "\n\t" //; r5-- C10 + // bcc .stop ; if (r5<0) goto .stop C11 + "bcc L%=_stop" + "\n\t" //; if (r5<0) goto .stop C11 + // .start: + "L%=_start:" + // movs r6, #0x80 ; reset mask C12 + "movs %[mask], #0x80" + "\n\t" //; reset mask C12 + // nop ; C13 + "nop" + "\n\t" //; C13 + + // .common: ; C13 + "L%=_common:" + "\n\t" //; C13 + // str r1, [r2, #0] ; pin := lo C15 + "strb %[bitmask], [%[reg], #4]" + "\n\t" //; pin := lo C15 + // ; always re-load byte - it just fits with the cycles better this way + // ldrb r0, [r4, #0] ; r0 := *r4 C17 + "ldrb %[pix], [%[p], #0]" + "\n\t" //; r0 := *r4 C17 + // b .nextbit ; C20 + "b L%=_nextbit" + "\n\t" //; C20 + + // .justbit: ; C10 + "L%=_justbit:" + "\n\t" //; C10 + // ; no nops, branch taken is already 3 cycles + // b .common ; C13 + "b L%=_common" + "\n\t" //; C13 + + // .stop: + "L%=_stop:" + "\n\t" + // str r1, [r2, #0] ; pin := lo + "strb %[bitmask], [%[reg], #4]" + "\n\t" //; pin := lo + // cpsie i ; enable irq + + : [p] "+r"(p), [pix] "=&r"(pix), [count] "=&r"(count), [mask] "=&r"(mask), + [num] "+r"(num) + : [bitmask] "r"(bitmask), [reg] "r"(reg)); #elif defined(__SAM3X8E__) // Arduino Due - #define SCALE VARIANT_MCK / 2UL / 1000000UL - #define INST (2UL * F_CPU / VARIANT_MCK) - #define TIME_800_0 ((int)(0.40 * SCALE + 0.5) - (5 * INST)) - #define TIME_800_1 ((int)(0.80 * SCALE + 0.5) - (5 * INST)) - #define PERIOD_800 ((int)(1.25 * SCALE + 0.5) - (5 * INST)) - #define TIME_400_0 ((int)(0.50 * SCALE + 0.5) - (5 * INST)) - #define TIME_400_1 ((int)(1.20 * SCALE + 0.5) - (5 * INST)) - #define PERIOD_400 ((int)(2.50 * SCALE + 0.5) - (5 * INST)) - - int pinMask, time0, time1, period, t; - Pio *port; +#define SCALE VARIANT_MCK / 2UL / 1000000UL +#define INST (2UL * F_CPU / VARIANT_MCK) +#define TIME_800_0 ((int)(0.40 * SCALE + 0.5) - (5 * INST)) +#define TIME_800_1 ((int)(0.80 * SCALE + 0.5) - (5 * INST)) +#define PERIOD_800 ((int)(1.25 * SCALE + 0.5) - (5 * INST)) +#define TIME_400_0 ((int)(0.50 * SCALE + 0.5) - (5 * INST)) +#define TIME_400_1 ((int)(1.20 * SCALE + 0.5) - (5 * INST)) +#define PERIOD_400 ((int)(2.50 * SCALE + 0.5) - (5 * INST)) + + int pinMask, time0, time1, period, t; + Pio *port; volatile WoReg *portSet, *portClear, *timeValue, *timeReset; - uint8_t *p, *end, pix, mask; + uint8_t *p, *end, pix, mask; pmc_set_writeprotect(false); pmc_enable_periph_clk((uint32_t)TC3_IRQn); TC_Configure(TC1, 0, - TC_CMR_WAVE | TC_CMR_WAVSEL_UP | TC_CMR_TCCLKS_TIMER_CLOCK1); + TC_CMR_WAVE | TC_CMR_WAVSEL_UP | TC_CMR_TCCLKS_TIMER_CLOCK1); TC_Start(TC1, 0); - pinMask = g_APinDescription[pin].ulPin; // Don't 'optimize' these into - port = g_APinDescription[pin].pPort; // declarations above. Want to - portSet = &(port->PIO_SODR); // burn a few cycles after - portClear = &(port->PIO_CODR); // starting timer to minimize - timeValue = &(TC1->TC_CHANNEL[0].TC_CV); // the initial 'while'. + pinMask = g_APinDescription[pin].ulPin; // Don't 'optimize' these into + port = g_APinDescription[pin].pPort; // declarations above. Want to + portSet = &(port->PIO_SODR); // burn a few cycles after + portClear = &(port->PIO_CODR); // starting timer to minimize + timeValue = &(TC1->TC_CHANNEL[0].TC_CV); // the initial 'while'. timeReset = &(TC1->TC_CHANNEL[0].TC_CCR); - p = pixels; - end = p + numBytes; - pix = *p++; - mask = 0x80; + p = pixels; + end = p + numBytes; + pix = *p++; + mask = 0x80; #if defined(NEO_KHZ400) // 800 KHz check needed only if 400 KHz support enabled - if(is800KHz) { + if (is800KHz) { #endif - time0 = TIME_800_0; - time1 = TIME_800_1; + time0 = TIME_800_0; + time1 = TIME_800_1; period = PERIOD_800; #if defined(NEO_KHZ400) } else { // 400 KHz bitstream - time0 = TIME_400_0; - time1 = TIME_400_1; + time0 = TIME_400_0; + time1 = TIME_400_1; period = PERIOD_400; } #endif - for(t = time0;; t = time0) { - if(pix & mask) t = time1; - while(*timeValue < (unsigned)period); - *portSet = pinMask; + for (t = time0;; t = time0) { + if (pix & mask) + t = time1; + while (*timeValue < (unsigned)period) + ; + *portSet = pinMask; *timeReset = TC_CCR_CLKEN | TC_CCR_SWTRG; - while(*timeValue < (unsigned)t); + while (*timeValue < (unsigned)t) + ; *portClear = pinMask; - if(!(mask >>= 1)) { // This 'inside-out' loop logic utilizes - if(p >= end) break; // idle time to minimize inter-byte delays. + if (!(mask >>= 1)) { // This 'inside-out' loop logic utilizes + if (p >= end) + break; // idle time to minimize inter-byte delays. pix = *p++; mask = 0x80; } } - while(*timeValue < (unsigned)period); // Wait for last bit + while (*timeValue < (unsigned)period) + ; // Wait for last bit TC_Stop(TC1, 0); #endif // end Due -// END ARM ---------------------------------------------------------------- - + // END ARM ---------------------------------------------------------------- #elif defined(ESP8266) || defined(ESP32) -// ESP8266 ---------------------------------------------------------------- + // ESP8266 ---------------------------------------------------------------- // ESP8266 show() is external to enforce ICACHE_RAM_ATTR execution espShow(pin, pixels, numBytes, is800KHz); @@ -2113,94 +2927,97 @@ void Adafruit_NeoPixel::show(void) { #elif defined(KENDRYTE_K210) k210Show(pin, pixels, numBytes, is800KHz); - -#elif defined(__ARDUINO_ARC__) -// Arduino 101 ----------------------------------------------------------- +#elif defined(__ARDUINO_ARC__) -#define NOPx7 { __builtin_arc_nop(); \ - __builtin_arc_nop(); __builtin_arc_nop(); \ - __builtin_arc_nop(); __builtin_arc_nop(); \ - __builtin_arc_nop(); __builtin_arc_nop(); } + // Arduino 101 ----------------------------------------------------------- + +#define NOPx7 \ + { \ + __builtin_arc_nop(); \ + __builtin_arc_nop(); \ + __builtin_arc_nop(); \ + __builtin_arc_nop(); \ + __builtin_arc_nop(); \ + __builtin_arc_nop(); \ + __builtin_arc_nop(); \ + } PinDescription *pindesc = &g_APinDescription[pin]; - register uint32_t loop = 8 * numBytes; // one loop to handle all bytes and all bits + register uint32_t loop = + 8 * numBytes; // one loop to handle all bytes and all bits register uint8_t *p = pixels; - register uint32_t currByte = (uint32_t) (*p); + register uint32_t currByte = (uint32_t)(*p); register uint32_t currBit = 0x80 & currByte; register uint32_t bitCounter = 0; register uint32_t first = 1; - // The loop is unusual. Very first iteration puts all the way LOW to the wire - - // constant LOW does not affect NEOPIXEL, so there is no visible effect displayed. - // During that very first iteration CPU caches instructions in the loop. - // Because of the caching process, "CPU slows down". NEOPIXEL pulse is very time sensitive - // that's why we let the CPU cache first and we start regular pulse from 2nd iteration + // The loop is unusual. Very first iteration puts all the way LOW to the wire + // - constant LOW does not affect NEOPIXEL, so there is no visible effect + // displayed. During that very first iteration CPU caches instructions in the + // loop. Because of the caching process, "CPU slows down". NEOPIXEL pulse is + // very time sensitive that's why we let the CPU cache first and we start + // regular pulse from 2nd iteration if (pindesc->ulGPIOType == SS_GPIO) { register uint32_t reg = pindesc->ulGPIOBase + SS_GPIO_SWPORTA_DR; uint32_t reg_val = __builtin_arc_lr((volatile uint32_t)reg); register uint32_t reg_bit_high = reg_val | (1 << pindesc->ulGPIOId); - register uint32_t reg_bit_low = reg_val & ~(1 << pindesc->ulGPIOId); + register uint32_t reg_bit_low = reg_val & ~(1 << pindesc->ulGPIOId); loop += 1; // include first, special iteration - while(loop--) { - if(!first) { + while (loop--) { + if (!first) { currByte <<= 1; bitCounter++; } // 1 is >550ns high and >450ns low; 0 is 200..500ns high and >450ns low - __builtin_arc_sr(first ? reg_bit_low : reg_bit_high, (volatile uint32_t)reg); - if(currBit) { // ~400ns HIGH (740ns overall) - NOPx7 - NOPx7 + __builtin_arc_sr(first ? reg_bit_low : reg_bit_high, + (volatile uint32_t)reg); + if (currBit) { // ~400ns HIGH (740ns overall) + NOPx7 NOPx7 } // ~340ns HIGH - NOPx7 - __builtin_arc_nop(); + NOPx7 __builtin_arc_nop(); // 820ns LOW; per spec, max allowed low here is 5000ns */ __builtin_arc_sr(reg_bit_low, (volatile uint32_t)reg); - NOPx7 - NOPx7 + NOPx7 NOPx7 - if(bitCounter >= 8) { + if (bitCounter >= 8) { bitCounter = 0; - currByte = (uint32_t) (*++p); + currByte = (uint32_t)(*++p); } currBit = 0x80 & currByte; first = 0; } - } else if(pindesc->ulGPIOType == SOC_GPIO) { + } else if (pindesc->ulGPIOType == SOC_GPIO) { register uint32_t reg = pindesc->ulGPIOBase + SOC_GPIO_SWPORTA_DR; uint32_t reg_val = MMIO_REG_VAL(reg); register uint32_t reg_bit_high = reg_val | (1 << pindesc->ulGPIOId); - register uint32_t reg_bit_low = reg_val & ~(1 << pindesc->ulGPIOId); + register uint32_t reg_bit_low = reg_val & ~(1 << pindesc->ulGPIOId); loop += 1; // include first, special iteration - while(loop--) { - if(!first) { + while (loop--) { + if (!first) { currByte <<= 1; bitCounter++; } MMIO_REG_VAL(reg) = first ? reg_bit_low : reg_bit_high; - if(currBit) { // ~430ns HIGH (740ns overall) - NOPx7 - NOPx7 - __builtin_arc_nop(); + if (currBit) { // ~430ns HIGH (740ns overall) + NOPx7 NOPx7 __builtin_arc_nop(); } // ~310ns HIGH NOPx7 - // 850ns LOW; per spec, max allowed low here is 5000ns */ - MMIO_REG_VAL(reg) = reg_bit_low; - NOPx7 - NOPx7 + // 850ns LOW; per spec, max allowed low here is 5000ns */ + MMIO_REG_VAL(reg) = reg_bit_low; + NOPx7 NOPx7 - if(bitCounter >= 8) { + if (bitCounter >= 8) { bitCounter = 0; - currByte = (uint32_t) (*++p); + currByte = (uint32_t)(*++p); } currBit = 0x80 & currByte; @@ -2212,10 +3029,9 @@ void Adafruit_NeoPixel::show(void) { #error Architecture not supported #endif + // END ARCHITECTURE SELECT ------------------------------------------------ -// END ARCHITECTURE SELECT ------------------------------------------------ - -#if !( defined(NRF52) || defined(NRF52_SERIES) ) +#if !(defined(NRF52) || defined(NRF52_SERIES)) interrupts(); #endif @@ -2227,15 +3043,16 @@ void Adafruit_NeoPixel::show(void) { if any, is set to INPUT and the new pin is set to OUTPUT. @param p Arduino pin number (-1 = no pin). */ -void Adafruit_NeoPixel::setPin(uint16_t p) { - if(begun && (pin >= 0)) pinMode(pin, INPUT); +void Adafruit_NeoPixel::setPin(int16_t p) { + if (begun && (pin >= 0)) + pinMode(pin, INPUT); // Disable existing out pin pin = p; - if(begun) { + if (begun) { pinMode(p, OUTPUT); digitalWrite(p, LOW); } #if defined(__AVR__) - port = portOutputRegister(digitalPinToPort(p)); + port = portOutputRegister(digitalPinToPort(p)); pinMask = digitalPinToBitMask(p); #endif #if defined(ARDUINO_ARCH_STM32) || defined(ARDUINO_ARCH_ARDUINO_CORE_STM32) @@ -2252,23 +3069,23 @@ void Adafruit_NeoPixel::setPin(uint16_t p) { @param g Green brightness, 0 = minimum (off), 255 = maximum. @param b Blue brightness, 0 = minimum (off), 255 = maximum. */ -void Adafruit_NeoPixel::setPixelColor( - uint16_t n, uint8_t r, uint8_t g, uint8_t b) { +void Adafruit_NeoPixel::setPixelColor(uint16_t n, uint8_t r, uint8_t g, + uint8_t b) { - if(n < numLEDs) { - if(brightness) { // See notes in setBrightness() + if (n < numLEDs) { + if (brightness) { // See notes in setBrightness() r = (r * brightness) >> 8; g = (g * brightness) >> 8; b = (b * brightness) >> 8; } uint8_t *p; - if(wOffset == rOffset) { // Is an RGB-type strip - p = &pixels[n * 3]; // 3 bytes per pixel - } else { // Is a WRGB-type strip - p = &pixels[n * 4]; // 4 bytes per pixel - p[wOffset] = 0; // But only R,G,B passed -- set W to 0 + if (wOffset == rOffset) { // Is an RGB-type strip + p = &pixels[n * 3]; // 3 bytes per pixel + } else { // Is a WRGB-type strip + p = &pixels[n * 4]; // 4 bytes per pixel + p[wOffset] = 0; // But only R,G,B passed -- set W to 0 } - p[rOffset] = r; // R,G,B always stored + p[rOffset] = r; // R,G,B always stored p[gOffset] = g; p[bOffset] = b; } @@ -2284,24 +3101,24 @@ void Adafruit_NeoPixel::setPixelColor( @param w White brightness, 0 = minimum (off), 255 = maximum, ignored if using RGB pixels. */ -void Adafruit_NeoPixel::setPixelColor( - uint16_t n, uint8_t r, uint8_t g, uint8_t b, uint8_t w) { +void Adafruit_NeoPixel::setPixelColor(uint16_t n, uint8_t r, uint8_t g, + uint8_t b, uint8_t w) { - if(n < numLEDs) { - if(brightness) { // See notes in setBrightness() + if (n < numLEDs) { + if (brightness) { // See notes in setBrightness() r = (r * brightness) >> 8; g = (g * brightness) >> 8; b = (b * brightness) >> 8; w = (w * brightness) >> 8; } uint8_t *p; - if(wOffset == rOffset) { // Is an RGB-type strip - p = &pixels[n * 3]; // 3 bytes per pixel (ignore W) - } else { // Is a WRGB-type strip - p = &pixels[n * 4]; // 4 bytes per pixel - p[wOffset] = w; // Store W + if (wOffset == rOffset) { // Is an RGB-type strip + p = &pixels[n * 3]; // 3 bytes per pixel (ignore W) + } else { // Is a WRGB-type strip + p = &pixels[n * 4]; // 4 bytes per pixel + p[wOffset] = w; // Store W } - p[rOffset] = r; // Store R,G,B + p[rOffset] = r; // Store R,G,B p[gOffset] = g; p[bOffset] = b; } @@ -2315,17 +3132,14 @@ void Adafruit_NeoPixel::setPixelColor( and least significant byte is blue. */ void Adafruit_NeoPixel::setPixelColor(uint16_t n, uint32_t c) { - if(n < numLEDs) { - uint8_t *p, - r = (uint8_t)(c >> 16), - g = (uint8_t)(c >> 8), - b = (uint8_t)c; - if(brightness) { // See notes in setBrightness() + if (n < numLEDs) { + uint8_t *p, r = (uint8_t)(c >> 16), g = (uint8_t)(c >> 8), b = (uint8_t)c; + if (brightness) { // See notes in setBrightness() r = (r * brightness) >> 8; g = (g * brightness) >> 8; b = (b * brightness) >> 8; } - if(wOffset == rOffset) { + if (wOffset == rOffset) { p = &pixels[n * 3]; } else { p = &pixels[n * 4]; @@ -2352,21 +3166,22 @@ void Adafruit_NeoPixel::setPixelColor(uint16_t n, uint32_t c) { void Adafruit_NeoPixel::fill(uint32_t c, uint16_t first, uint16_t count) { uint16_t i, end; - if(first >= numLEDs) { + if (first >= numLEDs) { return; // If first LED is past end of strip, nothing to do } // Calculate the index ONE AFTER the last pixel to fill - if(count == 0) { + if (count == 0) { // Fill to end of strip end = numLEDs; } else { // Ensure that the loop won't go past the last pixel end = first + count; - if(end > numLEDs) end = numLEDs; + if (end > numLEDs) + end = numLEDs; } - for(i = first; i < end; i++) { + for (i = first; i < end; i++) { this->setPixelColor(i, c); } } @@ -2429,45 +3244,45 @@ uint32_t Adafruit_NeoPixel::ColorHSV(uint16_t hue, uint8_t sat, uint8_t val) { // the constants below are not the multiples of 256 you might expect. // Convert hue to R,G,B (nested ifs faster than divide+mod+switch): - if(hue < 510) { // Red to Green-1 + if (hue < 510) { // Red to Green-1 b = 0; - if(hue < 255) { // Red to Yellow-1 + if (hue < 255) { // Red to Yellow-1 r = 255; - g = hue; // g = 0 to 254 - } else { // Yellow to Green-1 - r = 510 - hue; // r = 255 to 1 + g = hue; // g = 0 to 254 + } else { // Yellow to Green-1 + r = 510 - hue; // r = 255 to 1 g = 255; } - } else if(hue < 1020) { // Green to Blue-1 + } else if (hue < 1020) { // Green to Blue-1 r = 0; - if(hue < 765) { // Green to Cyan-1 + if (hue < 765) { // Green to Cyan-1 g = 255; - b = hue - 510; // b = 0 to 254 - } else { // Cyan to Blue-1 - g = 1020 - hue; // g = 255 to 1 + b = hue - 510; // b = 0 to 254 + } else { // Cyan to Blue-1 + g = 1020 - hue; // g = 255 to 1 b = 255; } - } else if(hue < 1530) { // Blue to Red-1 + } else if (hue < 1530) { // Blue to Red-1 g = 0; - if(hue < 1275) { // Blue to Magenta-1 - r = hue - 1020; // r = 0 to 254 + if (hue < 1275) { // Blue to Magenta-1 + r = hue - 1020; // r = 0 to 254 b = 255; - } else { // Magenta to Red-1 + } else { // Magenta to Red-1 r = 255; - b = 1530 - hue; // b = 255 to 1 + b = 1530 - hue; // b = 255 to 1 } - } else { // Last 0.5 Red (quicker than % operator) + } else { // Last 0.5 Red (quicker than % operator) r = 255; g = b = 0; } // Apply saturation and value to R,G,B, pack into 32-bit result: - uint32_t v1 = 1 + val; // 1 to 256; allows >>8 instead of /255 - uint16_t s1 = 1 + sat; // 1 to 256; same reason - uint8_t s2 = 255 - sat; // 255 to 0 + uint32_t v1 = 1 + val; // 1 to 256; allows >>8 instead of /255 + uint16_t s1 = 1 + sat; // 1 to 256; same reason + uint8_t s2 = 255 - sat; // 255 to 0 return ((((((r * s1) >> 8) + s2) * v1) & 0xff00) << 8) | - (((((g * s1) >> 8) + s2) * v1) & 0xff00) | - ( ((((b * s1) >> 8) + s2) * v1) >> 8); + (((((g * s1) >> 8) + s2) * v1) & 0xff00) | + (((((b * s1) >> 8) + s2) * v1) >> 8); } /*! @@ -2482,44 +3297,41 @@ uint32_t Adafruit_NeoPixel::ColorHSV(uint16_t hue, uint8_t sat, uint8_t val) { This gets more pronounced at lower brightness levels. */ uint32_t Adafruit_NeoPixel::getPixelColor(uint16_t n) const { - if(n >= numLEDs) return 0; // Out of bounds, return no color. + if (n >= numLEDs) + return 0; // Out of bounds, return no color. uint8_t *p; - if(wOffset == rOffset) { // Is RGB-type device + if (wOffset == rOffset) { // Is RGB-type device p = &pixels[n * 3]; - if(brightness) { + if (brightness) { // Stored color was decimated by setBrightness(). Returned value // attempts to scale back to an approximation of the original 24-bit // value used when setting the pixel color, but there will always be // some error -- those bits are simply gone. Issue is most // pronounced at low brightness levels. return (((uint32_t)(p[rOffset] << 8) / brightness) << 16) | - (((uint32_t)(p[gOffset] << 8) / brightness) << 8) | - ( (uint32_t)(p[bOffset] << 8) / brightness ); + (((uint32_t)(p[gOffset] << 8) / brightness) << 8) | + ((uint32_t)(p[bOffset] << 8) / brightness); } else { // No brightness adjustment has been made -- return 'raw' color - return ((uint32_t)p[rOffset] << 16) | - ((uint32_t)p[gOffset] << 8) | - (uint32_t)p[bOffset]; + return ((uint32_t)p[rOffset] << 16) | ((uint32_t)p[gOffset] << 8) | + (uint32_t)p[bOffset]; } - } else { // Is RGBW-type device + } else { // Is RGBW-type device p = &pixels[n * 4]; - if(brightness) { // Return scaled color + if (brightness) { // Return scaled color return (((uint32_t)(p[wOffset] << 8) / brightness) << 24) | (((uint32_t)(p[rOffset] << 8) / brightness) << 16) | - (((uint32_t)(p[gOffset] << 8) / brightness) << 8) | - ( (uint32_t)(p[bOffset] << 8) / brightness ); + (((uint32_t)(p[gOffset] << 8) / brightness) << 8) | + ((uint32_t)(p[bOffset] << 8) / brightness); } else { // Return raw color - return ((uint32_t)p[wOffset] << 24) | - ((uint32_t)p[rOffset] << 16) | - ((uint32_t)p[gOffset] << 8) | - (uint32_t)p[bOffset]; + return ((uint32_t)p[wOffset] << 24) | ((uint32_t)p[rOffset] << 16) | + ((uint32_t)p[gOffset] << 8) | (uint32_t)p[bOffset]; } } } - /*! @brief Adjust output brightness. Does not immediately affect what's currently displayed on the LEDs. The next call to show() will @@ -2543,7 +3355,7 @@ void Adafruit_NeoPixel::setBrightness(uint8_t b) { // (color values are interpreted literally; no scaling), 1 = min // brightness (off), 255 = just below max brightness. uint8_t newBrightness = b + 1; - if(newBrightness != brightness) { // Compare against prior value + if (newBrightness != brightness) { // Compare against prior value // Brightness has changed -- re-scale existing data in RAM, // This process is potentially "lossy," especially when increasing // brightness. The tight timing in the WS2811/WS2812 code means there @@ -2554,15 +3366,17 @@ void Adafruit_NeoPixel::setBrightness(uint8_t b) { // the limited number of steps (quantization) in the old data will be // quite visible in the re-scaled version. For a non-destructive // change, you'll need to re-render the full strip data. C'est la vie. - uint8_t c, - *ptr = pixels, - oldBrightness = brightness - 1; // De-wrap old brightness value + uint8_t c, *ptr = pixels, + oldBrightness = brightness - 1; // De-wrap old brightness value uint16_t scale; - if(oldBrightness == 0) scale = 0; // Avoid /0 - else if(b == 255) scale = 65535 / oldBrightness; - else scale = (((uint16_t)newBrightness << 8) - 1) / oldBrightness; - for(uint16_t i=0; i<numBytes; i++) { - c = *ptr; + if (oldBrightness == 0) + scale = 0; // Avoid /0 + else if (b == 255) + scale = 65535 / oldBrightness; + else + scale = (((uint16_t)newBrightness << 8) - 1) / oldBrightness; + for (uint16_t i = 0; i < numBytes; i++) { + c = *ptr; *ptr++ = (c * scale) >> 8; } brightness = newBrightness; @@ -2573,16 +3387,12 @@ void Adafruit_NeoPixel::setBrightness(uint8_t b) { @brief Retrieve the last-set brightness value for the strip. @return Brightness value: 0 = minimum (off), 255 = maximum. */ -uint8_t Adafruit_NeoPixel::getBrightness(void) const { - return brightness - 1; -} +uint8_t Adafruit_NeoPixel::getBrightness(void) const { return brightness - 1; } /*! @brief Fill the whole NeoPixel strip with 0 / black / off. */ -void Adafruit_NeoPixel::clear(void) { - memset(pixels, 0, numBytes); -} +void Adafruit_NeoPixel::clear(void) { memset(pixels, 0, numBytes); } // A 32-bit variant of gamma8() that applies the same function // to all components of a packed RGB or WRGB value. @@ -2596,6 +3406,35 @@ uint32_t Adafruit_NeoPixel::gamma32(uint32_t x) { // someone's storing information in the unused most significant byte // of an RGB value, but this seems exceedingly rare and if it's // encountered in reality they can mask values going in or coming out. - for(uint8_t i=0; i<4; i++) y[i] = gamma8(y[i]); + for (uint8_t i = 0; i < 4; i++) + y[i] = gamma8(y[i]); return x; // Packed 32-bit return } + +/*! + @brief Fill NeoPixel strip with one or more cycles of hues. + Everyone loves the rainbow swirl so much, now it's canon! + @param first_hue Hue of first pixel, 0-65535, representing one full + cycle of the color wheel. Each subsequent pixel will + be offset to complete one or more cycles over the + length of the strip. + @param reps Number of cycles of the color wheel over the length + of the strip. Default is 1. Negative values can be + used to reverse the hue order. + @param saturation Saturation (optional), 0-255 = gray to pure hue, + default = 255. + @param brightness Brightness/value (optional), 0-255 = off to max, + default = 255. This is distinct and in combination + with any configured global strip brightness. + @param gammify If true (default), apply gamma correction to colors + for better appearance. +*/ +void Adafruit_NeoPixel::rainbow(uint16_t first_hue, int8_t reps, + uint8_t saturation, uint8_t brightness, bool gammify) { + for (uint16_t i=0; i<numLEDs; i++) { + uint16_t hue = first_hue + (i * reps * 65536) / numLEDs; + uint32_t color = ColorHSV(hue, saturation, brightness); + if (gammify) color = gamma32(color); + setPixelColor(i, color); + } +} diff --git a/ampel-firmware/src/lib/Adafruit_NeoPixel/Adafruit_NeoPixel.h b/ampel-firmware/src/lib/Adafruit_NeoPixel/Adafruit_NeoPixel.h index 955be39a23b107845a821cdf6763d25c92b9f9bc..53d29f7ccc536a2d99aecaeb5cdab6ad9e3fb6d8 100644 --- a/ampel-firmware/src/lib/Adafruit_NeoPixel/Adafruit_NeoPixel.h +++ b/ampel-firmware/src/lib/Adafruit_NeoPixel/Adafruit_NeoPixel.h @@ -37,16 +37,28 @@ #define ADAFRUIT_NEOPIXEL_H #ifdef ARDUINO - #if (ARDUINO >= 100) - #include <Arduino.h> - #else - #include <WProgram.h> - #include <pins_arduino.h> - #endif +#if (ARDUINO >= 100) +#include <Arduino.h> +#else +#include <WProgram.h> +#include <pins_arduino.h> +#endif + +#ifdef USE_TINYUSB // For Serial when selecting TinyUSB +#include <Adafruit_TinyUSB.h> +#endif + #endif #ifdef TARGET_LPC1768 - #include <Arduino.h> +#include <Arduino.h> +#endif + +#if defined(ARDUINO_ARCH_RP2040) +#include <stdlib.h> +#include "hardware/pio.h" +#include "hardware/clocks.h" +#include "rp2040_pio.h" #endif // The order of primary colors in the NeoPixel data stream can vary among @@ -76,42 +88,42 @@ // RGB NeoPixel permutations; white and red offsets are always same // Offset: W R G B -#define NEO_RGB ((0<<6) | (0<<4) | (1<<2) | (2)) ///< Transmit as R,G,B -#define NEO_RBG ((0<<6) | (0<<4) | (2<<2) | (1)) ///< Transmit as R,B,G -#define NEO_GRB ((1<<6) | (1<<4) | (0<<2) | (2)) ///< Transmit as G,R,B -#define NEO_GBR ((2<<6) | (2<<4) | (0<<2) | (1)) ///< Transmit as G,B,R -#define NEO_BRG ((1<<6) | (1<<4) | (2<<2) | (0)) ///< Transmit as B,R,G -#define NEO_BGR ((2<<6) | (2<<4) | (1<<2) | (0)) ///< Transmit as B,G,R +#define NEO_RGB ((0 << 6) | (0 << 4) | (1 << 2) | (2)) ///< Transmit as R,G,B +#define NEO_RBG ((0 << 6) | (0 << 4) | (2 << 2) | (1)) ///< Transmit as R,B,G +#define NEO_GRB ((1 << 6) | (1 << 4) | (0 << 2) | (2)) ///< Transmit as G,R,B +#define NEO_GBR ((2 << 6) | (2 << 4) | (0 << 2) | (1)) ///< Transmit as G,B,R +#define NEO_BRG ((1 << 6) | (1 << 4) | (2 << 2) | (0)) ///< Transmit as B,R,G +#define NEO_BGR ((2 << 6) | (2 << 4) | (1 << 2) | (0)) ///< Transmit as B,G,R // RGBW NeoPixel permutations; all 4 offsets are distinct // Offset: W R G B -#define NEO_WRGB ((0<<6) | (1<<4) | (2<<2) | (3)) ///< Transmit as W,R,G,B -#define NEO_WRBG ((0<<6) | (1<<4) | (3<<2) | (2)) ///< Transmit as W,R,B,G -#define NEO_WGRB ((0<<6) | (2<<4) | (1<<2) | (3)) ///< Transmit as W,G,R,B -#define NEO_WGBR ((0<<6) | (3<<4) | (1<<2) | (2)) ///< Transmit as W,G,B,R -#define NEO_WBRG ((0<<6) | (2<<4) | (3<<2) | (1)) ///< Transmit as W,B,R,G -#define NEO_WBGR ((0<<6) | (3<<4) | (2<<2) | (1)) ///< Transmit as W,B,G,R +#define NEO_WRGB ((0 << 6) | (1 << 4) | (2 << 2) | (3)) ///< Transmit as W,R,G,B +#define NEO_WRBG ((0 << 6) | (1 << 4) | (3 << 2) | (2)) ///< Transmit as W,R,B,G +#define NEO_WGRB ((0 << 6) | (2 << 4) | (1 << 2) | (3)) ///< Transmit as W,G,R,B +#define NEO_WGBR ((0 << 6) | (3 << 4) | (1 << 2) | (2)) ///< Transmit as W,G,B,R +#define NEO_WBRG ((0 << 6) | (2 << 4) | (3 << 2) | (1)) ///< Transmit as W,B,R,G +#define NEO_WBGR ((0 << 6) | (3 << 4) | (2 << 2) | (1)) ///< Transmit as W,B,G,R -#define NEO_RWGB ((1<<6) | (0<<4) | (2<<2) | (3)) ///< Transmit as R,W,G,B -#define NEO_RWBG ((1<<6) | (0<<4) | (3<<2) | (2)) ///< Transmit as R,W,B,G -#define NEO_RGWB ((2<<6) | (0<<4) | (1<<2) | (3)) ///< Transmit as R,G,W,B -#define NEO_RGBW ((3<<6) | (0<<4) | (1<<2) | (2)) ///< Transmit as R,G,B,W -#define NEO_RBWG ((2<<6) | (0<<4) | (3<<2) | (1)) ///< Transmit as R,B,W,G -#define NEO_RBGW ((3<<6) | (0<<4) | (2<<2) | (1)) ///< Transmit as R,B,G,W +#define NEO_RWGB ((1 << 6) | (0 << 4) | (2 << 2) | (3)) ///< Transmit as R,W,G,B +#define NEO_RWBG ((1 << 6) | (0 << 4) | (3 << 2) | (2)) ///< Transmit as R,W,B,G +#define NEO_RGWB ((2 << 6) | (0 << 4) | (1 << 2) | (3)) ///< Transmit as R,G,W,B +#define NEO_RGBW ((3 << 6) | (0 << 4) | (1 << 2) | (2)) ///< Transmit as R,G,B,W +#define NEO_RBWG ((2 << 6) | (0 << 4) | (3 << 2) | (1)) ///< Transmit as R,B,W,G +#define NEO_RBGW ((3 << 6) | (0 << 4) | (2 << 2) | (1)) ///< Transmit as R,B,G,W -#define NEO_GWRB ((1<<6) | (2<<4) | (0<<2) | (3)) ///< Transmit as G,W,R,B -#define NEO_GWBR ((1<<6) | (3<<4) | (0<<2) | (2)) ///< Transmit as G,W,B,R -#define NEO_GRWB ((2<<6) | (1<<4) | (0<<2) | (3)) ///< Transmit as G,R,W,B -#define NEO_GRBW ((3<<6) | (1<<4) | (0<<2) | (2)) ///< Transmit as G,R,B,W -#define NEO_GBWR ((2<<6) | (3<<4) | (0<<2) | (1)) ///< Transmit as G,B,W,R -#define NEO_GBRW ((3<<6) | (2<<4) | (0<<2) | (1)) ///< Transmit as G,B,R,W +#define NEO_GWRB ((1 << 6) | (2 << 4) | (0 << 2) | (3)) ///< Transmit as G,W,R,B +#define NEO_GWBR ((1 << 6) | (3 << 4) | (0 << 2) | (2)) ///< Transmit as G,W,B,R +#define NEO_GRWB ((2 << 6) | (1 << 4) | (0 << 2) | (3)) ///< Transmit as G,R,W,B +#define NEO_GRBW ((3 << 6) | (1 << 4) | (0 << 2) | (2)) ///< Transmit as G,R,B,W +#define NEO_GBWR ((2 << 6) | (3 << 4) | (0 << 2) | (1)) ///< Transmit as G,B,W,R +#define NEO_GBRW ((3 << 6) | (2 << 4) | (0 << 2) | (1)) ///< Transmit as G,B,R,W -#define NEO_BWRG ((1<<6) | (2<<4) | (3<<2) | (0)) ///< Transmit as B,W,R,G -#define NEO_BWGR ((1<<6) | (3<<4) | (2<<2) | (0)) ///< Transmit as B,W,G,R -#define NEO_BRWG ((2<<6) | (1<<4) | (3<<2) | (0)) ///< Transmit as B,R,W,G -#define NEO_BRGW ((3<<6) | (1<<4) | (2<<2) | (0)) ///< Transmit as B,R,G,W -#define NEO_BGWR ((2<<6) | (3<<4) | (1<<2) | (0)) ///< Transmit as B,G,W,R -#define NEO_BGRW ((3<<6) | (2<<4) | (1<<2) | (0)) ///< Transmit as B,G,R,W +#define NEO_BWRG ((1 << 6) | (2 << 4) | (3 << 2) | (0)) ///< Transmit as B,W,R,G +#define NEO_BWGR ((1 << 6) | (3 << 4) | (2 << 2) | (0)) ///< Transmit as B,W,G,R +#define NEO_BRWG ((2 << 6) | (1 << 4) | (3 << 2) | (0)) ///< Transmit as B,R,W,G +#define NEO_BRGW ((3 << 6) | (1 << 4) | (2 << 2) | (0)) ///< Transmit as B,R,G,W +#define NEO_BGWR ((2 << 6) | (3 << 4) | (1 << 2) | (0)) ///< Transmit as B,G,W,R +#define NEO_BGRW ((3 << 6) | (2 << 4) | (1 << 2) | (0)) ///< Transmit as B,G,R,W // Add NEO_KHZ400 to the color order value to indicate a 400 KHz device. // All but the earliest v1 NeoPixels expect an 800 KHz data stream, this is @@ -134,7 +146,7 @@ #ifdef NEO_KHZ400 typedef uint16_t neoPixelType; ///< 3rd arg to Adafruit_NeoPixel constructor #else -typedef uint8_t neoPixelType; ///< 3rd arg to Adafruit_NeoPixel constructor +typedef uint8_t neoPixelType; ///< 3rd arg to Adafruit_NeoPixel constructor #endif // These two tables are declared outside the Adafruit_NeoPixel class @@ -149,22 +161,24 @@ for x in range(256): if x&15 == 15: print */ static const uint8_t PROGMEM _NeoPixelSineTable[256] = { - 128,131,134,137,140,143,146,149,152,155,158,162,165,167,170,173, - 176,179,182,185,188,190,193,196,198,201,203,206,208,211,213,215, - 218,220,222,224,226,228,230,232,234,235,237,238,240,241,243,244, - 245,246,248,249,250,250,251,252,253,253,254,254,254,255,255,255, - 255,255,255,255,254,254,254,253,253,252,251,250,250,249,248,246, - 245,244,243,241,240,238,237,235,234,232,230,228,226,224,222,220, - 218,215,213,211,208,206,203,201,198,196,193,190,188,185,182,179, - 176,173,170,167,165,162,158,155,152,149,146,143,140,137,134,131, - 128,124,121,118,115,112,109,106,103,100, 97, 93, 90, 88, 85, 82, - 79, 76, 73, 70, 67, 65, 62, 59, 57, 54, 52, 49, 47, 44, 42, 40, - 37, 35, 33, 31, 29, 27, 25, 23, 21, 20, 18, 17, 15, 14, 12, 11, - 10, 9, 7, 6, 5, 5, 4, 3, 2, 2, 1, 1, 1, 0, 0, 0, - 0, 0, 0, 0, 1, 1, 1, 2, 2, 3, 4, 5, 5, 6, 7, 9, - 10, 11, 12, 14, 15, 17, 18, 20, 21, 23, 25, 27, 29, 31, 33, 35, - 37, 40, 42, 44, 47, 49, 52, 54, 57, 59, 62, 65, 67, 70, 73, 76, - 79, 82, 85, 88, 90, 93, 97,100,103,106,109,112,115,118,121,124}; + 128, 131, 134, 137, 140, 143, 146, 149, 152, 155, 158, 162, 165, 167, 170, + 173, 176, 179, 182, 185, 188, 190, 193, 196, 198, 201, 203, 206, 208, 211, + 213, 215, 218, 220, 222, 224, 226, 228, 230, 232, 234, 235, 237, 238, 240, + 241, 243, 244, 245, 246, 248, 249, 250, 250, 251, 252, 253, 253, 254, 254, + 254, 255, 255, 255, 255, 255, 255, 255, 254, 254, 254, 253, 253, 252, 251, + 250, 250, 249, 248, 246, 245, 244, 243, 241, 240, 238, 237, 235, 234, 232, + 230, 228, 226, 224, 222, 220, 218, 215, 213, 211, 208, 206, 203, 201, 198, + 196, 193, 190, 188, 185, 182, 179, 176, 173, 170, 167, 165, 162, 158, 155, + 152, 149, 146, 143, 140, 137, 134, 131, 128, 124, 121, 118, 115, 112, 109, + 106, 103, 100, 97, 93, 90, 88, 85, 82, 79, 76, 73, 70, 67, 65, + 62, 59, 57, 54, 52, 49, 47, 44, 42, 40, 37, 35, 33, 31, 29, + 27, 25, 23, 21, 20, 18, 17, 15, 14, 12, 11, 10, 9, 7, 6, + 5, 5, 4, 3, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 2, 2, 3, 4, 5, 5, 6, 7, 9, 10, 11, + 12, 14, 15, 17, 18, 20, 21, 23, 25, 27, 29, 31, 33, 35, 37, + 40, 42, 44, 47, 49, 52, 54, 57, 59, 62, 65, 67, 70, 73, 76, + 79, 82, 85, 88, 90, 93, 97, 100, 103, 106, 109, 112, 115, 118, 121, + 124}; /* Similar to above, but for an 8-bit gamma-correction table. Copy & paste this snippet into a Python REPL to regenerate: @@ -175,49 +189,49 @@ for x in range(256): if x&15 == 15: print */ static const uint8_t PROGMEM _NeoPixelGammaTable[256] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, - 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 7, - 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, - 13, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, - 20, 21, 21, 22, 22, 23, 24, 24, 25, 25, 26, 27, 27, 28, 29, 29, - 30, 31, 31, 32, 33, 34, 34, 35, 36, 37, 38, 38, 39, 40, 41, 42, - 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, - 58, 59, 60, 61, 62, 63, 64, 65, 66, 68, 69, 70, 71, 72, 73, 75, - 76, 77, 78, 80, 81, 82, 84, 85, 86, 88, 89, 90, 92, 93, 94, 96, - 97, 99,100,102,103,105,106,108,109,111,112,114,115,117,119,120, - 122,124,125,127,129,130,132,134,136,137,139,141,143,145,146,148, - 150,152,154,156,158,160,162,164,166,168,170,172,174,176,178,180, - 182,184,186,188,191,193,195,197,199,202,204,206,209,211,213,215, - 218,220,223,225,227,230,232,235,237,240,242,245,247,250,252,255}; + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, + 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, + 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, + 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, 17, + 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25, + 25, 26, 27, 27, 28, 29, 29, 30, 31, 31, 32, 33, 34, 34, 35, + 36, 37, 38, 38, 39, 40, 41, 42, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 68, 69, 70, 71, 72, 73, 75, 76, 77, 78, 80, 81, + 82, 84, 85, 86, 88, 89, 90, 92, 93, 94, 96, 97, 99, 100, 102, + 103, 105, 106, 108, 109, 111, 112, 114, 115, 117, 119, 120, 122, 124, 125, + 127, 129, 130, 132, 134, 136, 137, 139, 141, 143, 145, 146, 148, 150, 152, + 154, 156, 158, 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182, + 184, 186, 188, 191, 193, 195, 197, 199, 202, 204, 206, 209, 211, 213, 215, + 218, 220, 223, 225, 227, 230, 232, 235, 237, 240, 242, 245, 247, 250, 252, + 255}; -/*! +/*! @brief Class that stores state and functions for interacting with Adafruit NeoPixels and compatible devices. */ class Adafruit_NeoPixel { - public: - +public: // Constructor: number of LEDs, pin number, LED type - Adafruit_NeoPixel(uint16_t n, uint16_t pin=6, - neoPixelType type=NEO_GRB + NEO_KHZ800); + Adafruit_NeoPixel(uint16_t n, int16_t pin = 6, + neoPixelType type = NEO_GRB + NEO_KHZ800); Adafruit_NeoPixel(void); ~Adafruit_NeoPixel(); - void begin(void); - void show(void); - void setPin(uint16_t p); - void setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b); - void setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b, - uint8_t w); - void setPixelColor(uint16_t n, uint32_t c); - void fill(uint32_t c=0, uint16_t first=0, uint16_t count=0); - void setBrightness(uint8_t); - void clear(void); - void updateLength(uint16_t n); - void updateType(neoPixelType t); + void begin(void); + void show(void); + void setPin(int16_t p); + void setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b); + void setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b, uint8_t w); + void setPixelColor(uint16_t n, uint32_t c); + void fill(uint32_t c = 0, uint16_t first = 0, uint16_t count = 0); + void setBrightness(uint8_t); + void clear(void); + void updateLength(uint16_t n); + void updateType(neoPixelType t); /*! @brief Check whether a call to show() will start sending data immediately or will 'block' for a required interval. NeoPixels @@ -232,10 +246,26 @@ class Adafruit_NeoPixel { if show() would block (meaning some idle time is available). */ bool canShow(void) { - if (endTime > micros()) { - endTime = micros(); + // It's normal and possible for endTime to exceed micros() if the + // 32-bit clock counter has rolled over (about every 70 minutes). + // Since both are uint32_t, a negative delta correctly maps back to + // positive space, and it would seem like the subtraction below would + // suffice. But a problem arises if code invokes show() very + // infrequently...the micros() counter may roll over MULTIPLE times in + // that interval, the delta calculation is no longer correct and the + // next update may stall for a very long time. The check below resets + // the latch counter if a rollover has occurred. This can cause an + // extra delay of up to 300 microseconds in the rare case where a + // show() call happens precisely around the rollover, but that's + // neither likely nor especially harmful, vs. other code that might + // stall for 30+ minutes, or having to document and frequently remind + // and/or provide tech support explaining an unintuitive need for + // show() calls at least once an hour. + uint32_t now = micros(); + if (endTime > now) { + endTime = now; } - return (micros() - endTime) >= 300L; + return (now - endTime) >= 300L; } /*! @brief Get a pointer directly to the NeoPixel data buffer in RAM. @@ -251,19 +281,19 @@ class Adafruit_NeoPixel { writes past the ends of the buffer. Great power, great responsibility and all that. */ - uint8_t *getPixels(void) const { return pixels; }; - uint8_t getBrightness(void) const; + uint8_t *getPixels(void) const { return pixels; }; + uint8_t getBrightness(void) const; /*! @brief Retrieve the pin number used for NeoPixel data output. @return Arduino pin number (-1 if not set). */ - int16_t getPin(void) const { return pin; }; + int16_t getPin(void) const { return pin; }; /*! @brief Return the number of pixels in an Adafruit_NeoPixel strip object. @return Pixel count (0 if not set). */ - uint16_t numPixels(void) const { return numLEDs; } - uint32_t getPixelColor(uint16_t n) const; + uint16_t numPixels(void) const { return numLEDs; } + uint32_t getPixelColor(uint16_t n) const; /*! @brief An 8-bit integer sine wave function, not directly compatible with standard trigonometric units like radians or degrees. @@ -276,7 +306,7 @@ class Adafruit_NeoPixel { a signed int8_t, but you'll most likely want unsigned as this output is often used for pixel brightness in animation effects. */ - static uint8_t sine8(uint8_t x) { + static uint8_t sine8(uint8_t x) { return pgm_read_byte(&_NeoPixelSineTable[x]); // 0-255 in, 0-255 out } /*! @@ -290,7 +320,7 @@ class Adafruit_NeoPixel { NeoPixels in average tasks. If you need finer control you'll need to provide your own gamma-correction function instead. */ - static uint8_t gamma8(uint8_t x) { + static uint8_t gamma8(uint8_t x) { return pgm_read_byte(&_NeoPixelGammaTable[x]); // 0-255 in, 0-255 out } /*! @@ -304,8 +334,8 @@ class Adafruit_NeoPixel { function. Packed RGB format is predictable, regardless of LED strand color order. */ - static uint32_t Color(uint8_t r, uint8_t g, uint8_t b) { - return ((uint32_t)r << 16) | ((uint32_t)g << 8) | b; + static uint32_t Color(uint8_t r, uint8_t g, uint8_t b) { + return ((uint32_t)r << 16) | ((uint32_t)g << 8) | b; } /*! @brief Convert separate red, green, blue and white values into a @@ -319,10 +349,10 @@ class Adafruit_NeoPixel { function. Packed WRGB format is predictable, regardless of LED strand color order. */ - static uint32_t Color(uint8_t r, uint8_t g, uint8_t b, uint8_t w) { - return ((uint32_t)w << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b; + static uint32_t Color(uint8_t r, uint8_t g, uint8_t b, uint8_t w) { + return ((uint32_t)w << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b; } - static uint32_t ColorHSV(uint16_t hue, uint8_t sat=255, uint8_t val=255); + static uint32_t ColorHSV(uint16_t hue, uint8_t sat = 255, uint8_t val = 255); /*! @brief A gamma-correction function for 32-bit packed RGB or WRGB colors. Makes color transitions appear more perceptially @@ -335,31 +365,45 @@ class Adafruit_NeoPixel { control you'll need to provide your own gamma-correction function instead. */ - static uint32_t gamma32(uint32_t x); + static uint32_t gamma32(uint32_t x); - protected: + void rainbow(uint16_t first_hue = 0, int8_t reps = 1, + uint8_t saturation = 255, uint8_t brightness = 255, + bool gammify = true); -#ifdef NEO_KHZ400 // If 400 KHz NeoPixel support enabled... - bool is800KHz; ///< true if 800 KHz pixels +private: +#if defined(ARDUINO_ARCH_RP2040) + void rp2040Init(uint8_t pin, bool is800KHz); + void rp2040Show(uint8_t pin, uint8_t *pixels, uint32_t numBytes, bool is800KHz); #endif - bool begun; ///< true if begin() previously called - uint16_t numLEDs; ///< Number of RGB LEDs in strip - uint16_t numBytes; ///< Size of 'pixels' buffer below - int16_t pin; ///< Output pin number (-1 if not yet set) - uint8_t brightness; ///< Strip brightness 0-255 (stored as +1) - uint8_t *pixels; ///< Holds LED color values (3 or 4 bytes each) - uint8_t rOffset; ///< Red index within each 3- or 4-byte pixel - uint8_t gOffset; ///< Index of green byte - uint8_t bOffset; ///< Index of blue byte - uint8_t wOffset; ///< Index of white (==rOffset if no white) - uint32_t endTime; ///< Latch timing reference + +protected: +#ifdef NEO_KHZ400 // If 400 KHz NeoPixel support enabled... + bool is800KHz; ///< true if 800 KHz pixels +#endif + bool begun; ///< true if begin() previously called + uint16_t numLEDs; ///< Number of RGB LEDs in strip + uint16_t numBytes; ///< Size of 'pixels' buffer below + int16_t pin; ///< Output pin number (-1 if not yet set) + uint8_t brightness; ///< Strip brightness 0-255 (stored as +1) + uint8_t *pixels; ///< Holds LED color values (3 or 4 bytes each) + uint8_t rOffset; ///< Red index within each 3- or 4-byte pixel + uint8_t gOffset; ///< Index of green byte + uint8_t bOffset; ///< Index of blue byte + uint8_t wOffset; ///< Index of white (==rOffset if no white) + uint32_t endTime; ///< Latch timing reference #ifdef __AVR__ - volatile uint8_t *port; ///< Output PORT register - uint8_t pinMask; ///< Output PORT bitmask + volatile uint8_t *port; ///< Output PORT register + uint8_t pinMask; ///< Output PORT bitmask #endif #if defined(ARDUINO_ARCH_STM32) || defined(ARDUINO_ARCH_ARDUINO_CORE_STM32) - GPIO_TypeDef *gpioPort; ///< Output GPIO PORT - uint32_t gpioPin; ///< Output GPIO PIN + GPIO_TypeDef *gpioPort; ///< Output GPIO PORT + uint32_t gpioPin; ///< Output GPIO PIN +#endif +#if defined(ARDUINO_ARCH_RP2040) + PIO pio = pio0; + int sm = 0; + bool init = true; #endif }; diff --git a/ampel-firmware/src/lib/Adafruit_NeoPixel/README.md b/ampel-firmware/src/lib/Adafruit_NeoPixel/README.md index 8c150501f30d226305021e7f0814616924402fea..eff1337119a105870cb989b97dd10648066c2e90 100644 --- a/ampel-firmware/src/lib/Adafruit_NeoPixel/README.md +++ b/ampel-firmware/src/lib/Adafruit_NeoPixel/README.md @@ -56,6 +56,10 @@ Compatibility notes: Port A is not supported on any AVR processors at this time - ESP8266 any speed - ESP32 any speed - Nordic nRF52 (Adafruit Feather nRF52), nRF51 (micro:bit) + - Infineon XMC1100 BootKit @ 32 MHz + - Infineon XMC1100 2Go @ 32 MHz + - Infineon XMC1300 BootKit @ 32 MHz + - Infineon XMC4700 RelaxKit, XMC4800 RelaxKit, XMC4800 IoT Amazon FreeRTOS Kit @ 144 MHz Check forks for other architectures not listed here! diff --git a/ampel-firmware/src/lib/Adafruit_NeoPixel/esp.c b/ampel-firmware/src/lib/Adafruit_NeoPixel/esp.c index 9d2853ff6631264a7e9a3ce68cc3f8f5c5f9683d..c480a20501c7cf6d5786dbd6fff0e45864c5faf5 100644 --- a/ampel-firmware/src/lib/Adafruit_NeoPixel/esp.c +++ b/ampel-firmware/src/lib/Adafruit_NeoPixel/esp.c @@ -22,6 +22,12 @@ #include <Arduino.h> #include "driver/rmt.h" +#if defined(ESP_IDF_VERSION) +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0) +#define HAS_ESP_IDF_4 +#endif +#endif + // This code is adapted from the ESP-IDF v3.4 RMT "led_strip" example, altered // to work with the Arduino version of the ESP-IDF (3.2) @@ -89,6 +95,7 @@ void espShow(uint8_t pin, uint8_t *pixels, uint32_t numBytes, boolean is800KHz) if (!rmt_reserved_channels[i]) { rmt_reserved_channels[i] = true; channel = i; + break; } } if (channel == ADAFRUIT_RMT_CHANNEL_MAX) { @@ -96,6 +103,10 @@ void espShow(uint8_t pin, uint8_t *pixels, uint32_t numBytes, boolean is800KHz) return; } +#if defined(HAS_ESP_IDF_4) + rmt_config_t config = RMT_DEFAULT_CONFIG_TX(pin, channel); + config.clk_div = 2; +#else // Match default TX config from ESP-IDF version 3.4 rmt_config_t config = { .rmt_mode = RMT_MODE_TX, @@ -113,12 +124,16 @@ void espShow(uint8_t pin, uint8_t *pixels, uint32_t numBytes, boolean is800KHz) .idle_output_en = true, } }; +#endif rmt_config(&config); rmt_driver_install(config.channel, 0, 0); // Convert NS timings to ticks uint32_t counter_clk_hz = 0; +#if defined(HAS_ESP_IDF_4) + rmt_get_counter_clock(channel, &counter_clk_hz); +#else // this emulates the rmt_get_counter_clock() function from ESP-IDF 3.4 if (RMT_LL_HW_BASE->conf_ch[config.channel].conf1.ref_always_on == RMT_BASECLK_REF) { uint32_t div_cnt = RMT_LL_HW_BASE->conf_ch[config.channel].conf0.div_cnt; @@ -129,6 +144,7 @@ void espShow(uint8_t pin, uint8_t *pixels, uint32_t numBytes, boolean is800KHz) uint32_t div = div_cnt == 0 ? 256 : div_cnt; counter_clk_hz = APB_CLK_FREQ / (div); } +#endif // NS to tick converter float ratio = (float)counter_clk_hz / 1e9; diff --git a/ampel-firmware/src/lib/Adafruit_NeoPixel/esp8266.c b/ampel-firmware/src/lib/Adafruit_NeoPixel/esp8266.c index bdfa1050669412da0cba1eba318929bda54a9533..51c3f3c8a34f4856f9c2636da364bed5081d2918 100644 --- a/ampel-firmware/src/lib/Adafruit_NeoPixel/esp8266.c +++ b/ampel-firmware/src/lib/Adafruit_NeoPixel/esp8266.c @@ -17,16 +17,16 @@ static inline uint32_t _getCycleCount(void) { } #ifdef ESP8266 -void ICACHE_RAM_ATTR espShow( - uint8_t pin, uint8_t *pixels, uint32_t numBytes, boolean is800KHz) { +IRAM_ATTR void espShow( + uint8_t pin, uint8_t *pixels, uint32_t numBytes, __attribute__((unused)) boolean is800KHz) { #else void espShow( uint8_t pin, uint8_t *pixels, uint32_t numBytes, boolean is800KHz) { #endif -#define CYCLES_800_T0H (F_CPU / 2500000) // 0.4us -#define CYCLES_800_T1H (F_CPU / 1250000) // 0.8us -#define CYCLES_800 (F_CPU / 800000) // 1.25us per bit +#define CYCLES_800_T0H (F_CPU / 2500001) // 0.4us +#define CYCLES_800_T1H (F_CPU / 1250001) // 0.8us +#define CYCLES_800 (F_CPU / 800001) // 1.25us per bit #define CYCLES_400_T0H (F_CPU / 2000000) // 0.5uS #define CYCLES_400_T1H (F_CPU / 833333) // 1.2us #define CYCLES_400 (F_CPU / 400000) // 2.5us per bit diff --git a/ampel-firmware/src/lib/Adafruit_NeoPixel/library.properties b/ampel-firmware/src/lib/Adafruit_NeoPixel/library.properties index 445fa4891a5661846da99b0e5fb8bee8a851c9fb..4bde4ac077147f65142997598641c5be31669e49 100644 --- a/ampel-firmware/src/lib/Adafruit_NeoPixel/library.properties +++ b/ampel-firmware/src/lib/Adafruit_NeoPixel/library.properties @@ -1,5 +1,5 @@ name=Adafruit NeoPixel -version=1.7.0 +version=1.10.4 author=Adafruit maintainer=Adafruit <info@adafruit.com> sentence=Arduino library for controlling single-wire-based LED pixels and strip. diff --git a/ampel-firmware/src/lib/Adafruit_NeoPixel/rp2040_pio.h b/ampel-firmware/src/lib/Adafruit_NeoPixel/rp2040_pio.h new file mode 100644 index 0000000000000000000000000000000000000000..f7ccd46de0a5cc8510f86b10069c6417b57fdde4 --- /dev/null +++ b/ampel-firmware/src/lib/Adafruit_NeoPixel/rp2040_pio.h @@ -0,0 +1,63 @@ +// -------------------------------------------------- // +// This file is autogenerated by pioasm; do not edit! // +// -------------------------------------------------- // + +// Unless you know what you are doing... +// Lines 47 and 52 have been edited to set transmit bit count + +#if !PICO_NO_HARDWARE +#include "hardware/pio.h" +#endif + +// ------ // +// ws2812 // +// ------ // + +#define ws2812_wrap_target 0 +#define ws2812_wrap 3 + +#define ws2812_T1 2 +#define ws2812_T2 5 +#define ws2812_T3 3 + +static const uint16_t ws2812_program_instructions[] = { + // .wrap_target + 0x6221, // 0: out x, 1 side 0 [2] + 0x1123, // 1: jmp !x, 3 side 1 [1] + 0x1400, // 2: jmp 0 side 1 [4] + 0xa442, // 3: nop side 0 [4] + // .wrap +}; + +#if !PICO_NO_HARDWARE +static const struct pio_program ws2812_program = { + .instructions = ws2812_program_instructions, + .length = 4, + .origin = -1, +}; + +static inline pio_sm_config ws2812_program_get_default_config(uint offset) { + pio_sm_config c = pio_get_default_sm_config(); + sm_config_set_wrap(&c, offset + ws2812_wrap_target, offset + ws2812_wrap); + sm_config_set_sideset(&c, 1, false, false); + return c; +} + +#include "hardware/clocks.h" +static inline void ws2812_program_init(PIO pio, uint sm, uint offset, uint pin, + float freq, uint bits) { + pio_gpio_init(pio, pin); + pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true); + pio_sm_config c = ws2812_program_get_default_config(offset); + sm_config_set_sideset_pins(&c, pin); + sm_config_set_out_shift(&c, false, true, + bits); // <----<<< Length changed to "bits" + sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); + int cycles_per_bit = ws2812_T1 + ws2812_T2 + ws2812_T3; + float div = clock_get_hz(clk_sys) / (freq * cycles_per_bit); + sm_config_set_clkdiv(&c, div); + pio_sm_init(pio, sm, offset, &c); + pio_sm_set_enabled(pio, sm, true); +} + +#endif diff --git a/ampel-firmware/src/lib/NTPClient-master/.travis.yml b/ampel-firmware/src/lib/NTPClient-master/.travis.yml deleted file mode 100644 index fa09d0f97ab8d922adfc012cf6849244d54b1cdf..0000000000000000000000000000000000000000 --- a/ampel-firmware/src/lib/NTPClient-master/.travis.yml +++ /dev/null @@ -1,10 +0,0 @@ -language: c -sudo: false -before_install: - - source <(curl -SLs https://raw.githubusercontent.com/adafruit/travis-ci-arduino/master/install.sh) -script: - - build_platform esp8266 -notifications: - email: - on_success: change - on_failure: change diff --git a/ampel-firmware/src/lib/NTPClient-master/README.md b/ampel-firmware/src/lib/NTPClient-master/README.md deleted file mode 100644 index 6c8c07a5cf83b1d70302f65a2168e9167cbcd72f..0000000000000000000000000000000000000000 --- a/ampel-firmware/src/lib/NTPClient-master/README.md +++ /dev/null @@ -1,47 +0,0 @@ -# NTPClient - -[](https://travis-ci.org/arduino-libraries/NTPClient) - -Connect to a NTP server, here is how: - -```cpp -#include <NTPClient.h> -// change next line to use with another board/shield -#include <ESP8266WiFi.h> -//#include <WiFi.h> // for WiFi shield -//#include <WiFi101.h> // for WiFi 101 shield or MKR1000 -#include <WiFiUdp.h> - -const char *ssid = "<SSID>"; -const char *password = "<PASSWORD>"; - -WiFiUDP ntpUDP; - -// By default 'pool.ntp.org' is used with 60 seconds update interval and -// no offset -NTPClient timeClient(ntpUDP); - -// You can specify the time server pool and the offset, (in seconds) -// additionaly you can specify the update interval (in milliseconds). -// NTPClient timeClient(ntpUDP, "europe.pool.ntp.org", 3600, 60000); - -void setup(){ - Serial.begin(115200); - WiFi.begin(ssid, password); - - while ( WiFi.status() != WL_CONNECTED ) { - delay ( 500 ); - Serial.print ( "." ); - } - - timeClient.begin(); -} - -void loop() { - timeClient.update(); - - Serial.println(timeClient.getFormattedTime()); - - delay(1000); -} -``` diff --git a/ampel-firmware/src/lib/NTPClient-master/CHANGELOG b/ampel-firmware/src/lib/NTPClient/CHANGELOG similarity index 100% rename from ampel-firmware/src/lib/NTPClient-master/CHANGELOG rename to ampel-firmware/src/lib/NTPClient/CHANGELOG diff --git a/ampel-firmware/src/lib/NTPClient-master/NTPClient.cpp b/ampel-firmware/src/lib/NTPClient/NTPClient.cpp old mode 100644 new mode 100755 similarity index 65% rename from ampel-firmware/src/lib/NTPClient-master/NTPClient.cpp rename to ampel-firmware/src/lib/NTPClient/NTPClient.cpp index 1b52de5af6eefae690044fa9138222fb3aa78d1b..2149a0604a6b585e544c3effa410fec6233e2cc7 --- a/ampel-firmware/src/lib/NTPClient-master/NTPClient.cpp +++ b/ampel-firmware/src/lib/NTPClient/NTPClient.cpp @@ -25,7 +25,7 @@ NTPClient::NTPClient(UDP& udp) { this->_udp = &udp; } -NTPClient::NTPClient(UDP& udp, int timeOffset) { +NTPClient::NTPClient(UDP& udp, long timeOffset) { this->_udp = &udp; this->_timeOffset = timeOffset; } @@ -35,24 +35,45 @@ NTPClient::NTPClient(UDP& udp, const char* poolServerName) { this->_poolServerName = poolServerName; } -NTPClient::NTPClient(UDP& udp, const char* poolServerName, int timeOffset) { +NTPClient::NTPClient(UDP& udp, IPAddress poolServerIP) { + this->_udp = &udp; + this->_poolServerIP = poolServerIP; + this->_poolServerName = NULL; +} + +NTPClient::NTPClient(UDP& udp, const char* poolServerName, long timeOffset) { this->_udp = &udp; this->_timeOffset = timeOffset; this->_poolServerName = poolServerName; } -NTPClient::NTPClient(UDP& udp, const char* poolServerName, int timeOffset, unsigned long updateInterval) { +NTPClient::NTPClient(UDP& udp, IPAddress poolServerIP, long timeOffset){ + this->_udp = &udp; + this->_timeOffset = timeOffset; + this->_poolServerIP = poolServerIP; + this->_poolServerName = NULL; +} + +NTPClient::NTPClient(UDP& udp, const char* poolServerName, long timeOffset, unsigned long updateInterval) { this->_udp = &udp; this->_timeOffset = timeOffset; this->_poolServerName = poolServerName; this->_updateInterval = updateInterval; } +NTPClient::NTPClient(UDP& udp, IPAddress poolServerIP, long timeOffset, unsigned long updateInterval) { + this->_udp = &udp; + this->_timeOffset = timeOffset; + this->_poolServerIP = poolServerIP; + this->_poolServerName = NULL; + this->_updateInterval = updateInterval; +} + void NTPClient::begin() { this->begin(NTP_DEFAULT_LOCAL_PORT); } -void NTPClient::begin(int port) { +void NTPClient::begin(unsigned int port) { this->_port = port; this->_udp->begin(this->_port); @@ -60,37 +81,15 @@ void NTPClient::begin(int port) { this->_udpSetup = true; } -bool NTPClient::isValid(byte * ntpPacket) -{ - //Perform a few validity checks on the packet - if((ntpPacket[0] & 0b11000000) == 0b11000000) //Check for LI=UNSYNC - return false; - - if((ntpPacket[0] & 0b00111000) >> 3 < 0b100) //Check for Version >= 4 - return false; - - if((ntpPacket[0] & 0b00000111) != 0b100) //Check for Mode == Server - return false; - - if((ntpPacket[1] < 1) || (ntpPacket[1] > 15)) //Check for valid Stratum - return false; - - if( ntpPacket[16] == 0 && ntpPacket[17] == 0 && - ntpPacket[18] == 0 && ntpPacket[19] == 0 && - ntpPacket[20] == 0 && ntpPacket[21] == 0 && - ntpPacket[22] == 0 && ntpPacket[22] == 0) //Check for ReferenceTimestamp != 0 - return false; - - return true; -} - bool NTPClient::forceUpdate() { #ifdef DEBUG_NTPClient Serial.println("Update from NTP Server"); #endif + // flush any existing packets while(this->_udp->parsePacket() != 0) this->_udp->flush(); + this->sendNTPPacket(); // Wait till data is there or timeout... @@ -99,20 +98,14 @@ bool NTPClient::forceUpdate() { do { delay ( 10 ); cb = this->_udp->parsePacket(); - - if(cb > 0) - { - this->_udp->read(this->_packetBuffer, NTP_PACKET_SIZE); - if(!this->isValid(this->_packetBuffer)) - cb = 0; - } - if (timeout > 100) return false; // timeout after 1000 ms timeout++; } while (cb == 0); this->_lastUpdate = millis() - (10 * (timeout + 1)); // Account for delay in reading the time + this->_udp->read(this->_packetBuffer, NTP_PACKET_SIZE); + unsigned long highWord = word(this->_packetBuffer[40], this->_packetBuffer[41]); unsigned long lowWord = word(this->_packetBuffer[42], this->_packetBuffer[43]); // combine the four bytes (two words) into a long integer @@ -121,73 +114,53 @@ bool NTPClient::forceUpdate() { this->_currentEpoc = secsSince1900 - SEVENZYYEARS; - return true; + return true; // return true after successful update } bool NTPClient::update() { if ((millis() - this->_lastUpdate >= this->_updateInterval) // Update after _updateInterval || this->_lastUpdate == 0) { // Update if there was no update yet. - if (!this->_udpSetup) this->begin(); // setup the UDP client if needed + if (!this->_udpSetup || this->_port != NTP_DEFAULT_LOCAL_PORT) this->begin(this->_port); // setup the UDP client if needed return this->forceUpdate(); } - return true; + return false; // return false if update does not occur +} + +bool NTPClient::isTimeSet() const { + return (this->_lastUpdate != 0); // returns true if the time has been set, else false } -unsigned long NTPClient::getEpochTime() { +unsigned long NTPClient::getEpochTime() const { return this->_timeOffset + // User offset - this->_currentEpoc + // Epoc returned by the NTP server + this->_currentEpoc + // Epoch returned by the NTP server ((millis() - this->_lastUpdate) / 1000); // Time since last update } -int NTPClient::getDay() { +int NTPClient::getDay() const { return (((this->getEpochTime() / 86400L) + 4 ) % 7); //0 is Sunday } -int NTPClient::getHours() { +int NTPClient::getHours() const { return ((this->getEpochTime() % 86400L) / 3600); } -int NTPClient::getMinutes() { +int NTPClient::getMinutes() const { return ((this->getEpochTime() % 3600) / 60); } -int NTPClient::getSeconds() { +int NTPClient::getSeconds() const { return (this->getEpochTime() % 60); } -void NTPClient::getFormattedTime(char *formatted_time, unsigned long secs) { - unsigned long rawTime = secs ? secs : this->getEpochTime(); - unsigned int hours = (rawTime % 86400L) / 3600; - unsigned int minutes = (rawTime % 3600) / 60; - unsigned int seconds = rawTime % 60; - - snprintf(formatted_time, 9, "%02d:%02d:%02d", hours, minutes, seconds); -} +String NTPClient::getFormattedTime() const { + unsigned long rawTime = this->getEpochTime(); + unsigned long hours = (rawTime % 86400L) / 3600; + String hoursStr = hours < 10 ? "0" + String(hours) : String(hours); -// Based on https://github.com/PaulStoffregen/Time/blob/master/Time.cpp -void NTPClient::getFormattedDate(char *formatted_date, unsigned long secs) { - unsigned long rawTime = (secs ? secs : this->getEpochTime()) / 86400L; // in days - unsigned long days = 0, year = 1970; - uint8_t month; - static const uint8_t monthDays[]={31,28,31,30,31,30,31,31,30,31,30,31}; + unsigned long minutes = (rawTime % 3600) / 60; + String minuteStr = minutes < 10 ? "0" + String(minutes) : String(minutes); - while((days += (LEAP_YEAR(year) ? 366 : 365)) <= rawTime) - year++; - rawTime -= days - (LEAP_YEAR(year) ? 366 : 365); // now it is days in this year, starting at 0 - days=0; - for (month=0; month<12; month++) { - uint8_t monthLength; - if (month==1) { // february - monthLength = LEAP_YEAR(year) ? 29 : 28; - } else { - monthLength = monthDays[month]; - } - if (rawTime < monthLength) break; - rawTime -= monthLength; - } - month++; // jan is month 1 - rawTime++; // first day is day 1 + unsigned long seconds = rawTime % 60; + String secondStr = seconds < 10 ? "0" + String(seconds) : String(seconds); - char formatted_time[9]; - this->getFormattedTime(formatted_time, secs); - snprintf(formatted_date, 23, "%4lu-%02d-%02lu %s%+03d", year, month, rawTime, formatted_time, this->_timeOffset / 3600); + return hoursStr + ":" + minuteStr + ":" + secondStr; } void NTPClient::end() { @@ -204,28 +177,84 @@ void NTPClient::setUpdateInterval(unsigned long updateInterval) { this->_updateInterval = updateInterval; } +void NTPClient::setPoolServerName(const char* poolServerName) { + this->_poolServerName = poolServerName; +} + void NTPClient::sendNTPPacket() { // set all bytes in the buffer to 0 memset(this->_packetBuffer, 0, NTP_PACKET_SIZE); // Initialize values needed to form NTP request - // (see URL above for details on the packets) this->_packetBuffer[0] = 0b11100011; // LI, Version, Mode this->_packetBuffer[1] = 0; // Stratum, or type of clock this->_packetBuffer[2] = 6; // Polling Interval this->_packetBuffer[3] = 0xEC; // Peer Clock Precision // 8 bytes of zero for Root Delay & Root Dispersion - this->_packetBuffer[12] = 0x49; + this->_packetBuffer[12] = 49; this->_packetBuffer[13] = 0x4E; - this->_packetBuffer[14] = 0x49; - this->_packetBuffer[15] = 0x52; + this->_packetBuffer[14] = 49; + this->_packetBuffer[15] = 52; // all NTP fields have been given values, now // you can send a packet requesting a timestamp: - this->_udp->beginPacket(this->_poolServerName, 123); //NTP requests are to port 123 + if (this->_poolServerName) { + this->_udp->beginPacket(this->_poolServerName, 123); + } else { + this->_udp->beginPacket(this->_poolServerIP, 123); + } this->_udp->write(this->_packetBuffer, NTP_PACKET_SIZE); this->_udp->endPacket(); } +void NTPClient::setRandomPort(unsigned int minValue, unsigned int maxValue) { + randomSeed(analogRead(0)); + this->_port = random(minValue, maxValue); +} + + +/*** Custom code for ampel-firmware ***/ +void NTPClient::getFormattedTime(char *formatted_time, unsigned long secs) { + unsigned long rawTime = secs ? secs : this->getEpochTime(); + unsigned int hours = (rawTime % 86400L) / 3600; + unsigned int minutes = (rawTime % 3600) / 60; + unsigned int seconds = rawTime % 60; + + snprintf(formatted_time, 9, "%02d:%02d:%02d", hours, minutes, seconds); +} + +#define LEAP_YEAR(Y) ( (Y>0) && !(Y%4) && ( (Y%100) || !(Y%400) ) ) + +// Based on https://github.com/PaulStoffregen/Time/blob/master/Time.cpp +void NTPClient::getFormattedDate(char *formatted_date, unsigned long secs) { + unsigned long rawTime = (secs ? secs : this->getEpochTime()) / 86400L; // in days + unsigned long days = 0; + unsigned int year = 1970; + uint8_t month; + static const uint8_t monthDays[]={31,28,31,30,31,30,31,31,30,31,30,31}; + + while((days += (LEAP_YEAR(year) ? 366 : 365)) <= rawTime) + year++; + rawTime -= days - (LEAP_YEAR(year) ? 366 : 365); // now it is days in this year, starting at 0 + days=0; + for (month=0; month<12; month++) { + uint8_t monthLength; + if (month==1) { // february + monthLength = LEAP_YEAR(year) ? 29 : 28; + } else { + monthLength = monthDays[month]; + } + if (rawTime < monthLength) break; + rawTime -= monthLength; + } + month++; // jan is month 1 + rawTime++; // first day is day 1 + + char formatted_time[9]; + this->getFormattedTime(formatted_time, secs); + snprintf(formatted_date, 23, "%4d-%02d-%02lu %s%+03ld", year, month, rawTime, formatted_time, (this->_timeOffset / 3600) % 100); +} + void NTPClient::setEpochTime(unsigned long secs) { this->_currentEpoc = secs; } +/**************************************************************/ \ No newline at end of file diff --git a/ampel-firmware/src/lib/NTPClient-master/NTPClient.h b/ampel-firmware/src/lib/NTPClient/NTPClient.h old mode 100644 new mode 100755 similarity index 61% rename from ampel-firmware/src/lib/NTPClient-master/NTPClient.h rename to ampel-firmware/src/lib/NTPClient/NTPClient.h index 3349dbb7df998fc931874c3f1ec86cda07fc3f80..fb23e46d4ef2e950720590a97aaf809a9ba0c2c7 --- a/ampel-firmware/src/lib/NTPClient-master/NTPClient.h +++ b/ampel-firmware/src/lib/NTPClient/NTPClient.h @@ -7,8 +7,6 @@ #define SEVENZYYEARS 2208988800UL #define NTP_PACKET_SIZE 48 #define NTP_DEFAULT_LOCAL_PORT 1337 -#define LEAP_YEAR(Y) ( (Y>0) && !(Y%4) && ( (Y%100) || !(Y%400) ) ) - class NTPClient { private: @@ -16,8 +14,9 @@ class NTPClient { bool _udpSetup = false; const char* _poolServerName = "pool.ntp.org"; // Default time server - int _port = NTP_DEFAULT_LOCAL_PORT; - int _timeOffset = 0; + IPAddress _poolServerIP; + unsigned int _port = NTP_DEFAULT_LOCAL_PORT; + long _timeOffset = 0; unsigned long _updateInterval = 60000; // In ms @@ -27,14 +26,28 @@ class NTPClient { byte _packetBuffer[NTP_PACKET_SIZE]; void sendNTPPacket(); - bool isValid(byte * ntpPacket); public: NTPClient(UDP& udp); - NTPClient(UDP& udp, int timeOffset); + NTPClient(UDP& udp, long timeOffset); NTPClient(UDP& udp, const char* poolServerName); - NTPClient(UDP& udp, const char* poolServerName, int timeOffset); - NTPClient(UDP& udp, const char* poolServerName, int timeOffset, unsigned long updateInterval); + NTPClient(UDP& udp, const char* poolServerName, long timeOffset); + NTPClient(UDP& udp, const char* poolServerName, long timeOffset, unsigned long updateInterval); + NTPClient(UDP& udp, IPAddress poolServerIP); + NTPClient(UDP& udp, IPAddress poolServerIP, long timeOffset); + NTPClient(UDP& udp, IPAddress poolServerIP, long timeOffset, unsigned long updateInterval); + + /** + * Set time server name + * + * @param poolServerName + */ + void setPoolServerName(const char* poolServerName); + + /** + * Set random local port + */ + void setRandomPort(unsigned int minValue = 49152, unsigned int maxValue = 65535); /** * Starts the underlying UDP client with the default local port @@ -44,7 +57,7 @@ class NTPClient { /** * Starts the underlying UDP client with the specified local port */ - void begin(int port); + void begin(unsigned int port); /** * This should be called in the main loop of your application. By default an update from the NTP Server is only @@ -61,10 +74,17 @@ class NTPClient { */ bool forceUpdate(); - int getDay(); - int getHours(); - int getMinutes(); - int getSeconds(); + /** + * This allows to check if the NTPClient successfully received a NTP packet and set the time. + * + * @return true if time has been set, else false + */ + bool isTimeSet() const; + + int getDay() const; + int getHours() const; + int getMinutes() const; + int getSeconds() const; /** * Changes the time offset. Useful for changing timezones dynamically @@ -78,28 +98,37 @@ class NTPClient { void setUpdateInterval(unsigned long updateInterval); /** - * @return secs argument (or 0 for current time) formatted like `hh:mm:ss` - */ - void getFormattedTime(char *formatted_time, unsigned long secs = 0); + * @return time formatted like `hh:mm:ss` + */ + String getFormattedTime() const; /** * @return time in seconds since Jan. 1, 1970 */ - unsigned long getEpochTime(); - - /** - * @return secs argument (or 0 for current date) formatted to ISO 8601 - * like `2004-02-12T15:19:21+00:00` - */ - void getFormattedDate(char *formatted_date, unsigned long secs = 0); + unsigned long getEpochTime() const; /** * Stops the underlying UDP client */ void end(); - + +/*** Custom code for ampel-firmware ***/ + + /** + * @return secs argument (or 0 for current time) formatted like `hh:mm:ss` + */ + void getFormattedTime(char *formatted_time, unsigned long secs = 0); + + /** + * @return secs argument (or 0 for current date) formatted to ISO 8601 + * like `2004-02-12T15:19:21+00:00` + */ + void getFormattedDate(char *formatted_date, unsigned long secs = 0); + /** * Replace the NTP-fetched time with seconds since Jan. 1, 1970 */ void setEpochTime(unsigned long secs); + +/**************************************************************/ }; diff --git a/ampel-firmware/src/lib/NTPClient/README.md b/ampel-firmware/src/lib/NTPClient/README.md new file mode 100644 index 0000000000000000000000000000000000000000..f83882ce092fc33bc110fc48e72404fb5bc51f3d --- /dev/null +++ b/ampel-firmware/src/lib/NTPClient/README.md @@ -0,0 +1,52 @@ +# NTPClient + +[](https://github.com/arduino-libraries/NTPClient/actions/workflows/check-arduino.yml) +[](https://github.com/arduino-libraries/NTPClient/actions/workflows/compile-examples.yml) +[](https://github.com/arduino-libraries/NTPClient/actions/workflows/spell-check.yml) + +Connect to a NTP server, here is how: + +```cpp +#include <NTPClient.h> +// change next line to use with another board/shield +#include <ESP8266WiFi.h> +//#include <WiFi.h> // for WiFi shield +//#include <WiFi101.h> // for WiFi 101 shield or MKR1000 +#include <WiFiUdp.h> + +const char *ssid = "<SSID>"; +const char *password = "<PASSWORD>"; + +WiFiUDP ntpUDP; + +// By default 'pool.ntp.org' is used with 60 seconds update interval and +// no offset +NTPClient timeClient(ntpUDP); + +// You can specify the time server pool and the offset, (in seconds) +// additionally you can specify the update interval (in milliseconds). +// NTPClient timeClient(ntpUDP, "europe.pool.ntp.org", 3600, 60000); + +void setup(){ + Serial.begin(115200); + WiFi.begin(ssid, password); + + while ( WiFi.status() != WL_CONNECTED ) { + delay ( 500 ); + Serial.print ( "." ); + } + + timeClient.begin(); +} + +void loop() { + timeClient.update(); + + Serial.println(timeClient.getFormattedTime()); + + delay(1000); +} +``` + +## Function documentation +`getEpochTime` returns the Unix epoch, which are the seconds elapsed since 00:00:00 UTC on 1 January 1970 (leap seconds are ignored, every day is treated as having 86400 seconds). **Attention**: If you have set a time offset this time offset will be added to your epoch timestamp. diff --git a/ampel-firmware/src/lib/NTPClient-master/keywords.txt b/ampel-firmware/src/lib/NTPClient/keywords.txt similarity index 78% rename from ampel-firmware/src/lib/NTPClient-master/keywords.txt rename to ampel-firmware/src/lib/NTPClient/keywords.txt index 1430d0482ee1392f764eaea706ca2d7ba13bb05b..edce98923ec56a7d11a9f20de8c0557b559e6b35 100644 --- a/ampel-firmware/src/lib/NTPClient-master/keywords.txt +++ b/ampel-firmware/src/lib/NTPClient/keywords.txt @@ -12,9 +12,13 @@ begin KEYWORD2 end KEYWORD2 update KEYWORD2 forceUpdate KEYWORD2 +isTimeSet KEYWORD2 getDay KEYWORD2 getHours KEYWORD2 getMinutes KEYWORD2 getSeconds KEYWORD2 getFormattedTime KEYWORD2 getEpochTime KEYWORD2 +setTimeOffset KEYWORD2 +setUpdateInterval KEYWORD2 +setPoolServerName KEYWORD2 diff --git a/ampel-firmware/src/lib/NTPClient-master/library.json b/ampel-firmware/src/lib/NTPClient/library.json similarity index 100% rename from ampel-firmware/src/lib/NTPClient-master/library.json rename to ampel-firmware/src/lib/NTPClient/library.json diff --git a/ampel-firmware/src/lib/NTPClient-master/library.properties b/ampel-firmware/src/lib/NTPClient/library.properties similarity index 95% rename from ampel-firmware/src/lib/NTPClient-master/library.properties rename to ampel-firmware/src/lib/NTPClient/library.properties index d4908ca87a33c8292031a8e06a2da02ab202ffa3..309b75d7dc129f42bc7fc871108410f9b3e1fbea 100644 --- a/ampel-firmware/src/lib/NTPClient-master/library.properties +++ b/ampel-firmware/src/lib/NTPClient/library.properties @@ -1,5 +1,5 @@ name=NTPClient -version=3.1.0 +version=3.2.0 author=Fabrice Weinberg maintainer=Fabrice Weinberg <fabrice@weinberg.me> sentence=An NTPClient to connect to a time server diff --git a/ampel-firmware/src/lib/PubSubClient/src/PubSubClient.cpp b/ampel-firmware/src/lib/PubSubClient/src/PubSubClient.cpp index 2b48d2b6b8ff28f5be7ad46a06c2e4f3f389aeae..2619e58e8c0b32c2f2051223140c3d40459e9bc2 100644 --- a/ampel-firmware/src/lib/PubSubClient/src/PubSubClient.cpp +++ b/ampel-firmware/src/lib/PubSubClient/src/PubSubClient.cpp @@ -1,161 +1,161 @@ /* - PubSubClient.cpp - A simple client for MQTT. - Nick O'Leary - http://knolleary.net -*/ + PubSubClient.cpp - A simple client for MQTT. + Nick O'Leary + http://knolleary.net + */ #include "PubSubClient.h" #include "Arduino.h" PubSubClient::PubSubClient() { - this->_state = MQTT_DISCONNECTED; - this->_client = NULL; - this->stream = NULL; - setCallback(NULL); - this->bufferSize = 0; - setBufferSize(MQTT_MAX_PACKET_SIZE); - setKeepAlive(MQTT_KEEPALIVE); - setSocketTimeout(MQTT_SOCKET_TIMEOUT); -} - -PubSubClient::PubSubClient(Client& client) { - this->_state = MQTT_DISCONNECTED; - setClient(client); - this->stream = NULL; - this->bufferSize = 0; - setBufferSize(MQTT_MAX_PACKET_SIZE); - setKeepAlive(MQTT_KEEPALIVE); - setSocketTimeout(MQTT_SOCKET_TIMEOUT); -} - -PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client) { - this->_state = MQTT_DISCONNECTED; - setServer(addr, port); - setClient(client); - this->stream = NULL; - this->bufferSize = 0; - setBufferSize(MQTT_MAX_PACKET_SIZE); - setKeepAlive(MQTT_KEEPALIVE); - setSocketTimeout(MQTT_SOCKET_TIMEOUT); -} -PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client, Stream& stream) { - this->_state = MQTT_DISCONNECTED; - setServer(addr,port); - setClient(client); - setStream(stream); - this->bufferSize = 0; - setBufferSize(MQTT_MAX_PACKET_SIZE); - setKeepAlive(MQTT_KEEPALIVE); - setSocketTimeout(MQTT_SOCKET_TIMEOUT); -} -PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) { - this->_state = MQTT_DISCONNECTED; - setServer(addr, port); - setCallback(callback); - setClient(client); - this->stream = NULL; - this->bufferSize = 0; - setBufferSize(MQTT_MAX_PACKET_SIZE); - setKeepAlive(MQTT_KEEPALIVE); - setSocketTimeout(MQTT_SOCKET_TIMEOUT); -} -PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) { - this->_state = MQTT_DISCONNECTED; - setServer(addr,port); - setCallback(callback); - setClient(client); - setStream(stream); - this->bufferSize = 0; - setBufferSize(MQTT_MAX_PACKET_SIZE); - setKeepAlive(MQTT_KEEPALIVE); - setSocketTimeout(MQTT_SOCKET_TIMEOUT); -} - -PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client) { - this->_state = MQTT_DISCONNECTED; - setServer(ip, port); - setClient(client); - this->stream = NULL; - this->bufferSize = 0; - setBufferSize(MQTT_MAX_PACKET_SIZE); - setKeepAlive(MQTT_KEEPALIVE); - setSocketTimeout(MQTT_SOCKET_TIMEOUT); -} -PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client, Stream& stream) { - this->_state = MQTT_DISCONNECTED; - setServer(ip,port); - setClient(client); - setStream(stream); - this->bufferSize = 0; - setBufferSize(MQTT_MAX_PACKET_SIZE); - setKeepAlive(MQTT_KEEPALIVE); - setSocketTimeout(MQTT_SOCKET_TIMEOUT); -} -PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) { - this->_state = MQTT_DISCONNECTED; - setServer(ip, port); - setCallback(callback); - setClient(client); - this->stream = NULL; - this->bufferSize = 0; - setBufferSize(MQTT_MAX_PACKET_SIZE); - setKeepAlive(MQTT_KEEPALIVE); - setSocketTimeout(MQTT_SOCKET_TIMEOUT); -} -PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) { - this->_state = MQTT_DISCONNECTED; - setServer(ip,port); - setCallback(callback); - setClient(client); - setStream(stream); - this->bufferSize = 0; - setBufferSize(MQTT_MAX_PACKET_SIZE); - setKeepAlive(MQTT_KEEPALIVE); - setSocketTimeout(MQTT_SOCKET_TIMEOUT); -} - -PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client) { - this->_state = MQTT_DISCONNECTED; - setServer(domain,port); - setClient(client); - this->stream = NULL; - this->bufferSize = 0; - setBufferSize(MQTT_MAX_PACKET_SIZE); - setKeepAlive(MQTT_KEEPALIVE); - setSocketTimeout(MQTT_SOCKET_TIMEOUT); -} -PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client, Stream& stream) { - this->_state = MQTT_DISCONNECTED; - setServer(domain,port); - setClient(client); - setStream(stream); - this->bufferSize = 0; - setBufferSize(MQTT_MAX_PACKET_SIZE); - setKeepAlive(MQTT_KEEPALIVE); - setSocketTimeout(MQTT_SOCKET_TIMEOUT); -} -PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) { - this->_state = MQTT_DISCONNECTED; - setServer(domain,port); - setCallback(callback); - setClient(client); - this->stream = NULL; - this->bufferSize = 0; - setBufferSize(MQTT_MAX_PACKET_SIZE); - setKeepAlive(MQTT_KEEPALIVE); - setSocketTimeout(MQTT_SOCKET_TIMEOUT); -} -PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) { - this->_state = MQTT_DISCONNECTED; - setServer(domain,port); - setCallback(callback); - setClient(client); - setStream(stream); - this->bufferSize = 0; - setBufferSize(MQTT_MAX_PACKET_SIZE); - setKeepAlive(MQTT_KEEPALIVE); - setSocketTimeout(MQTT_SOCKET_TIMEOUT); + this->_state = MQTT_DISCONNECTED; + this->_client = NULL; + this->stream = NULL; + setCallback(NULL); + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} + +PubSubClient::PubSubClient(Client &client) { + this->_state = MQTT_DISCONNECTED; + setClient(client); + this->stream = NULL; + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} + +PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client &client) { + this->_state = MQTT_DISCONNECTED; + setServer(addr, port); + setClient(client); + this->stream = NULL; + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} +PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client &client, Stream &stream) { + this->_state = MQTT_DISCONNECTED; + setServer(addr, port); + setClient(client); + setStream(stream); + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} +PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client &client) { + this->_state = MQTT_DISCONNECTED; + setServer(addr, port); + setCallback(callback); + setClient(client); + this->stream = NULL; + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} +PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client &client, Stream &stream) { + this->_state = MQTT_DISCONNECTED; + setServer(addr, port); + setCallback(callback); + setClient(client); + setStream(stream); + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} + +PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client &client) { + this->_state = MQTT_DISCONNECTED; + setServer(ip, port); + setClient(client); + this->stream = NULL; + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} +PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client &client, Stream &stream) { + this->_state = MQTT_DISCONNECTED; + setServer(ip, port); + setClient(client); + setStream(stream); + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} +PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client &client) { + this->_state = MQTT_DISCONNECTED; + setServer(ip, port); + setCallback(callback); + setClient(client); + this->stream = NULL; + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} +PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client &client, Stream &stream) { + this->_state = MQTT_DISCONNECTED; + setServer(ip, port); + setCallback(callback); + setClient(client); + setStream(stream); + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} + +PubSubClient::PubSubClient(const char *domain, uint16_t port, Client &client) { + this->_state = MQTT_DISCONNECTED; + setServer(domain, port); + setClient(client); + this->stream = NULL; + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} +PubSubClient::PubSubClient(const char *domain, uint16_t port, Client &client, Stream &stream) { + this->_state = MQTT_DISCONNECTED; + setServer(domain, port); + setClient(client); + setStream(stream); + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} +PubSubClient::PubSubClient(const char *domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client &client) { + this->_state = MQTT_DISCONNECTED; + setServer(domain, port); + setCallback(callback); + setClient(client); + this->stream = NULL; + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); +} +PubSubClient::PubSubClient(const char *domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client &client, Stream &stream) { + this->_state = MQTT_DISCONNECTED; + setServer(domain, port); + setCallback(callback); + setClient(client); + setStream(stream); + this->bufferSize = 0; + setBufferSize(MQTT_MAX_PACKET_SIZE); + setKeepAlive(MQTT_KEEPALIVE); + setSocketTimeout(MQTT_SOCKET_TIMEOUT); } PubSubClient::~PubSubClient() { @@ -163,424 +163,431 @@ PubSubClient::~PubSubClient() { } boolean PubSubClient::connect(const char *id) { - return connect(id,NULL,NULL,0,0,0,0,1); + return connect(id, NULL, NULL, 0, 0, 0, 0, 1); } boolean PubSubClient::connect(const char *id, const char *user, const char *pass) { - return connect(id,user,pass,0,0,0,0,1); + return connect(id, user, pass, 0, 0, 0, 0, 1); } -boolean PubSubClient::connect(const char *id, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) { - return connect(id,NULL,NULL,willTopic,willQos,willRetain,willMessage,1); +boolean PubSubClient::connect(const char *id, const char *willTopic, uint8_t willQos, boolean willRetain, + const char *willMessage) { + return connect(id, NULL, NULL, willTopic, willQos, willRetain, willMessage, 1); } -boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) { - return connect(id,user,pass,willTopic,willQos,willRetain,willMessage,1); +boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char *willTopic, + uint8_t willQos, boolean willRetain, const char *willMessage) { + return connect(id, user, pass, willTopic, willQos, willRetain, willMessage, 1); } -boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage, boolean cleanSession) { - if (!connected()) { - int result = 0; +boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char *willTopic, + uint8_t willQos, boolean willRetain, const char *willMessage, boolean cleanSession) { + if (!connected()) { + int result = 0; + if (_client->connected()) { + result = 1; + } else { + if (domain != NULL) { + result = _client->connect(this->domain, this->port); + } else { + result = _client->connect(this->ip, this->port); + } + } - if(_client->connected()) { - result = 1; - } else { - if (domain != NULL) { - result = _client->connect(this->domain, this->port); - } else { - result = _client->connect(this->ip, this->port); - } - } - - if (result == 1) { - nextMsgId = 1; - // Leave room in the buffer for header and variable length field - uint16_t length = MQTT_MAX_HEADER_SIZE; - unsigned int j; + if (result == 1) { + nextMsgId = 1; + // Leave room in the buffer for header and variable length field + uint16_t length = MQTT_MAX_HEADER_SIZE; + unsigned int j; #if MQTT_VERSION == MQTT_VERSION_3_1 uint8_t d[9] = {0x00,0x06,'M','Q','I','s','d','p', MQTT_VERSION}; #define MQTT_HEADER_VERSION_LENGTH 9 #elif MQTT_VERSION == MQTT_VERSION_3_1_1 - uint8_t d[7] = {0x00,0x04,'M','Q','T','T',MQTT_VERSION}; + uint8_t d[7] = { 0x00, 0x04, 'M', 'Q', 'T', 'T', MQTT_VERSION }; #define MQTT_HEADER_VERSION_LENGTH 7 #endif - for (j = 0;j<MQTT_HEADER_VERSION_LENGTH;j++) { - this->buffer[length++] = d[j]; - } - - uint8_t v; - if (willTopic) { - v = 0x04|(willQos<<3)|(willRetain<<5); - } else { - v = 0x00; - } - if (cleanSession) { - v = v|0x02; - } - - if(user != NULL) { - v = v|0x80; - - if(pass != NULL) { - v = v|(0x80>>1); - } - } - this->buffer[length++] = v; - - this->buffer[length++] = ((this->keepAlive) >> 8); - this->buffer[length++] = ((this->keepAlive) & 0xFF); - - CHECK_STRING_LENGTH(length,id) - length = writeString(id,this->buffer,length); - if (willTopic) { - CHECK_STRING_LENGTH(length,willTopic) - length = writeString(willTopic,this->buffer,length); - CHECK_STRING_LENGTH(length,willMessage) - length = writeString(willMessage,this->buffer,length); - } - - if(user != NULL) { - CHECK_STRING_LENGTH(length,user) - length = writeString(user,this->buffer,length); - if(pass != NULL) { - CHECK_STRING_LENGTH(length,pass) - length = writeString(pass,this->buffer,length); - } - } + for (j = 0; j < MQTT_HEADER_VERSION_LENGTH; j++) { + this->buffer[length++] = d[j]; + } + + uint8_t v; + if (willTopic) { + v = 0x04 | (willQos << 3) | (willRetain << 5); + } else { + v = 0x00; + } + if (cleanSession) { + v = v | 0x02; + } + + if (user != NULL) { + v = v | 0x80; + + if (pass != NULL) { + v = v | (0x80 >> 1); + } + } + this->buffer[length++] = v; + + this->buffer[length++] = ((this->keepAlive) >> 8); + this->buffer[length++] = ((this->keepAlive) & 0xFF); + + CHECK_STRING_LENGTH(length, id) + length = writeString(id, this->buffer, length); + if (willTopic) { + CHECK_STRING_LENGTH(length, willTopic) + length = writeString(willTopic, this->buffer, length); + CHECK_STRING_LENGTH(length, willMessage) + length = writeString(willMessage, this->buffer, length); + } + + if (user != NULL) { + CHECK_STRING_LENGTH(length, user) + length = writeString(user, this->buffer, length); + if (pass != NULL) { + CHECK_STRING_LENGTH(length, pass) + length = writeString(pass, this->buffer, length); + } + } - write(MQTTCONNECT,this->buffer,length-MQTT_MAX_HEADER_SIZE); + write(MQTTCONNECT, this->buffer, length - MQTT_MAX_HEADER_SIZE); - lastInActivity = lastOutActivity = millis(); + lastInActivity = lastOutActivity = millis(); - while (!_client->available()) { - unsigned long t = millis(); - if (t-lastInActivity >= ((int32_t) this->socketTimeout*1000UL)) { - _state = MQTT_CONNECTION_TIMEOUT; - _client->stop(); - return false; - } - } - uint8_t llen; - uint32_t len = readPacket(&llen); - - if (len == 4) { - if (buffer[3] == 0) { - lastInActivity = millis(); - pingOutstanding = false; - _state = MQTT_CONNECTED; - return true; - } else { - _state = buffer[3]; - } - } - _client->stop(); + while (!_client->available()) { + unsigned long t = millis(); + if (t - lastInActivity >= ((int32_t) this->socketTimeout * 1000UL)) { + _state = MQTT_CONNECTION_TIMEOUT; + _client->stop(); + return false; + } + } + uint8_t llen; + uint32_t len = readPacket(&llen); + + if (len == 4) { + if (buffer[3] == 0) { + lastInActivity = millis(); + pingOutstanding = false; + _state = MQTT_CONNECTED; + return true; } else { - _state = MQTT_CONNECT_FAILED; + _state = buffer[3]; } - return false; + } + _client->stop(); + } else { + _state = MQTT_CONNECT_FAILED; } - return true; + return false; + } + return true; } // reads a byte into result -boolean PubSubClient::readByte(uint8_t * result) { - uint32_t previousMillis = millis(); - while(!_client->available()) { - yield(); - uint32_t currentMillis = millis(); - if(currentMillis - previousMillis >= ((int32_t) this->socketTimeout * 1000)){ - return false; - } - } - *result = _client->read(); - return true; +boolean PubSubClient::readByte(uint8_t *result) { + uint32_t previousMillis = millis(); + while (!_client->available()) { + yield(); + uint32_t currentMillis = millis(); + if (currentMillis - previousMillis >= ((int32_t) this->socketTimeout * 1000)) { + return false; + } + } + *result = _client->read(); + return true; } // reads a byte into result[*index] and increments index -boolean PubSubClient::readByte(uint8_t * result, uint16_t * index){ +boolean PubSubClient::readByte(uint8_t *result, uint16_t *index) { uint16_t current_index = *index; - uint8_t * write_address = &(result[current_index]); - if(readByte(write_address)){ + uint8_t *write_address = &(result[current_index]); + if (readByte(write_address)) { *index = current_index + 1; return true; } return false; } -uint32_t PubSubClient::readPacket(uint8_t* lengthLength) { - uint16_t len = 0; - if(!readByte(this->buffer, &len)) return 0; - bool isPublish = (this->buffer[0]&0xF0) == MQTTPUBLISH; - uint32_t multiplier = 1; - uint32_t length = 0; - uint8_t digit = 0; - uint16_t skip = 0; - uint32_t start = 0; - - do { - if (len == 5) { - // Invalid remaining length encoding - kill the connection - _state = MQTT_DISCONNECTED; - _client->stop(); - return 0; - } - if(!readByte(&digit)) return 0; - this->buffer[len++] = digit; - length += (digit & 127) * multiplier; - multiplier <<=7; //multiplier *= 128 - } while ((digit & 128) != 0); - *lengthLength = len-1; - - if (isPublish) { - // Read in topic length to calculate bytes to skip over for Stream writing - if(!readByte(this->buffer, &len)) return 0; - if(!readByte(this->buffer, &len)) return 0; - skip = (this->buffer[*lengthLength+1]<<8)+this->buffer[*lengthLength+2]; - start = 2; - if (this->buffer[0]&MQTTQOS1) { - // skip message id - skip += 2; - } +uint32_t PubSubClient::readPacket(uint8_t *lengthLength) { + uint16_t len = 0; + if (!readByte(this->buffer, &len)) + return 0; + bool isPublish = (this->buffer[0] & 0xF0) == MQTTPUBLISH; + uint32_t multiplier = 1; + uint32_t length = 0; + uint8_t digit = 0; + uint16_t skip = 0; + uint32_t start = 0; + + do { + if (len == 5) { + // Invalid remaining length encoding - kill the connection + _state = MQTT_DISCONNECTED; + _client->stop(); + return 0; } - uint32_t idx = len; - - for (uint32_t i = start;i<length;i++) { - if(!readByte(&digit)) return 0; - if (this->stream) { - if (isPublish && idx-*lengthLength-2>skip) { - this->stream->write(digit); - } - } - - if (len < this->bufferSize) { - this->buffer[len] = digit; - len++; - } - idx++; + if (!readByte(&digit)) + return 0; + this->buffer[len++] = digit; + length += (digit & 127) * multiplier; + multiplier <<= 7; //multiplier *= 128 + } while ((digit & 128) != 0); + *lengthLength = len - 1; + + if (isPublish) { + // Read in topic length to calculate bytes to skip over for Stream writing + if (!readByte(this->buffer, &len)) + return 0; + if (!readByte(this->buffer, &len)) + return 0; + skip = (this->buffer[*lengthLength + 1] << 8) + this->buffer[*lengthLength + 2]; + start = 2; + if (this->buffer[0] & MQTTQOS1) { + // skip message id + skip += 2; + } + } + uint32_t idx = len; + + for (uint32_t i = start; i < length; i++) { + if (!readByte(&digit)) + return 0; + if (this->stream) { + if (isPublish && idx - *lengthLength - 2 > skip) { + this->stream->write(digit); + } } - if (!this->stream && idx > this->bufferSize) { - len = 0; // This will cause the packet to be ignored. + if (len < this->bufferSize) { + this->buffer[len] = digit; + len++; } - return len; + idx++; + } + + if (!this->stream && idx > this->bufferSize) { + len = 0; // This will cause the packet to be ignored. + } + return len; } boolean PubSubClient::loop() { - if (connected()) { - unsigned long t = millis(); - if ((t - lastInActivity > this->keepAlive*1000UL) || (t - lastOutActivity > this->keepAlive*1000UL)) { - if (pingOutstanding) { - this->_state = MQTT_CONNECTION_TIMEOUT; - _client->stop(); - return false; + if (connected()) { + unsigned long t = millis(); + if ((t - lastInActivity > this->keepAlive * 1000UL) || (t - lastOutActivity > this->keepAlive * 1000UL)) { + if (pingOutstanding) { + this->_state = MQTT_CONNECTION_TIMEOUT; + _client->stop(); + return false; + } else { + this->buffer[0] = MQTTPINGREQ; + this->buffer[1] = 0; + _client->write(this->buffer, 2); + lastOutActivity = t; + lastInActivity = t; + pingOutstanding = true; + } + } + if (_client->available()) { + uint8_t llen; + uint16_t len = readPacket(&llen); + uint16_t msgId = 0; + uint8_t *payload; + if (len > 0) { + lastInActivity = t; + uint8_t type = this->buffer[0] & 0xF0; + if (type == MQTTPUBLISH) { + if (callback) { + uint16_t tl = (this->buffer[llen + 1] << 8) + this->buffer[llen + 2]; /* topic length in bytes */ + memmove(this->buffer + llen + 2, this->buffer + llen + 3, tl); /* move topic inside buffer 1 byte to front */ + this->buffer[llen + 2 + tl] = 0; /* end the topic as a 'C' string with \x00 */ + char *topic = (char*) this->buffer + llen + 2; + // msgId only present for QOS>0 + if ((this->buffer[0] & 0x06) == MQTTQOS1) { + msgId = (this->buffer[llen + 3 + tl] << 8) + this->buffer[llen + 3 + tl + 1]; + payload = this->buffer + llen + 3 + tl + 2; + callback(topic, payload, len - llen - 3 - tl - 2); + + this->buffer[0] = MQTTPUBACK; + this->buffer[1] = 2; + this->buffer[2] = (msgId >> 8); + this->buffer[3] = (msgId & 0xFF); + _client->write(this->buffer, 4); + lastOutActivity = t; + } else { - this->buffer[0] = MQTTPINGREQ; - this->buffer[1] = 0; - _client->write(this->buffer,2); - lastOutActivity = t; - lastInActivity = t; - pingOutstanding = true; - } - } - if (_client->available()) { - uint8_t llen; - uint16_t len = readPacket(&llen); - uint16_t msgId = 0; - uint8_t *payload; - if (len > 0) { - lastInActivity = t; - uint8_t type = this->buffer[0]&0xF0; - if (type == MQTTPUBLISH) { - if (callback) { - uint16_t tl = (this->buffer[llen+1]<<8)+this->buffer[llen+2]; /* topic length in bytes */ - memmove(this->buffer+llen+2,this->buffer+llen+3,tl); /* move topic inside buffer 1 byte to front */ - this->buffer[llen+2+tl] = 0; /* end the topic as a 'C' string with \x00 */ - char *topic = (char*) this->buffer+llen+2; - // msgId only present for QOS>0 - if ((this->buffer[0]&0x06) == MQTTQOS1) { - msgId = (this->buffer[llen+3+tl]<<8)+this->buffer[llen+3+tl+1]; - payload = this->buffer+llen+3+tl+2; - callback(topic,payload,len-llen-3-tl-2); - - this->buffer[0] = MQTTPUBACK; - this->buffer[1] = 2; - this->buffer[2] = (msgId >> 8); - this->buffer[3] = (msgId & 0xFF); - _client->write(this->buffer,4); - lastOutActivity = t; - - } else { - payload = this->buffer+llen+3+tl; - callback(topic,payload,len-llen-3-tl); - } - } - } else if (type == MQTTPINGREQ) { - this->buffer[0] = MQTTPINGRESP; - this->buffer[1] = 0; - _client->write(this->buffer,2); - } else if (type == MQTTPINGRESP) { - pingOutstanding = false; - } - } else if (!connected()) { - // readPacket has closed the connection - return false; + payload = this->buffer + llen + 3 + tl; + callback(topic, payload, len - llen - 3 - tl); } + } + } else if (type == MQTTPINGREQ) { + this->buffer[0] = MQTTPINGRESP; + this->buffer[1] = 0; + _client->write(this->buffer, 2); + } else if (type == MQTTPINGRESP) { + pingOutstanding = false; } - return true; + } else if (!connected()) { + // readPacket has closed the connection + return false; + } } - return false; + return true; + } + return false; } -boolean PubSubClient::publish(const char* topic, const char* payload) { - return publish(topic,(const uint8_t*)payload, payload ? strnlen(payload, this->bufferSize) : 0,false); +boolean PubSubClient::publish(const char *topic, const char *payload) { + return publish(topic, (const uint8_t*) payload, payload ? strnlen(payload, this->bufferSize) : 0, false); } -boolean PubSubClient::publish(const char* topic, const char* payload, boolean retained) { - return publish(topic,(const uint8_t*)payload, payload ? strnlen(payload, this->bufferSize) : 0,retained); +boolean PubSubClient::publish(const char *topic, const char *payload, boolean retained) { + return publish(topic, (const uint8_t*) payload, payload ? strnlen(payload, this->bufferSize) : 0, retained); } -boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength) { - return publish(topic, payload, plength, false); +boolean PubSubClient::publish(const char *topic, const uint8_t *payload, unsigned int plength) { + return publish(topic, payload, plength, false); } -boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) { - if (connected()) { - if (this->bufferSize < MQTT_MAX_HEADER_SIZE + 2+strnlen(topic, this->bufferSize) + plength) { - // Too long - return false; - } - // Leave room in the buffer for header and variable length field - uint16_t length = MQTT_MAX_HEADER_SIZE; - length = writeString(topic,this->buffer,length); - - // Add payload - uint16_t i; - for (i=0;i<plength;i++) { - this->buffer[length++] = payload[i]; - } +boolean PubSubClient::publish(const char *topic, const uint8_t *payload, unsigned int plength, boolean retained) { + if (connected()) { + if (this->bufferSize < MQTT_MAX_HEADER_SIZE + 2 + strnlen(topic, this->bufferSize) + plength) { + // Too long + return false; + } + // Leave room in the buffer for header and variable length field + uint16_t length = MQTT_MAX_HEADER_SIZE; + length = writeString(topic, this->buffer, length); + + // Add payload + uint16_t i; + for (i = 0; i < plength; i++) { + this->buffer[length++] = payload[i]; + } - // Write the header - uint8_t header = MQTTPUBLISH; - if (retained) { - header |= 1; - } - return write(header,this->buffer,length-MQTT_MAX_HEADER_SIZE); + // Write the header + uint8_t header = MQTTPUBLISH; + if (retained) { + header |= 1; } - return false; + return write(header, this->buffer, length - MQTT_MAX_HEADER_SIZE); + } + return false; } -boolean PubSubClient::publish_P(const char* topic, const char* payload, boolean retained) { - return publish_P(topic, (const uint8_t*)payload, payload ? strnlen(payload, this->bufferSize) : 0, retained); +boolean PubSubClient::publish_P(const char *topic, const char *payload, boolean retained) { + return publish_P(topic, (const uint8_t*) payload, payload ? strnlen(payload, this->bufferSize) : 0, retained); } -boolean PubSubClient::publish_P(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) { - uint8_t llen = 0; - uint8_t digit; - unsigned int rc = 0; - uint16_t tlen; - unsigned int pos = 0; - unsigned int i; - uint8_t header; - unsigned int len; - int expectedLength; +boolean PubSubClient::publish_P(const char *topic, const uint8_t *payload, unsigned int plength, boolean retained) { + uint8_t llen = 0; + uint8_t digit; + unsigned int rc = 0; + uint16_t tlen; + unsigned int pos = 0; + unsigned int i; + uint8_t header; + unsigned int len; + uint32_t expectedLength; - if (!connected()) { - return false; - } + if (!connected()) { + return false; + } - tlen = strnlen(topic, this->bufferSize); + tlen = strnlen(topic, this->bufferSize); - header = MQTTPUBLISH; - if (retained) { - header |= 1; + header = MQTTPUBLISH; + if (retained) { + header |= 1; + } + this->buffer[pos++] = header; + len = plength + 2 + tlen; + do { + digit = len & 127; //digit = len %128 + len >>= 7; //len = len / 128 + if (len > 0) { + digit |= 0x80; } - this->buffer[pos++] = header; - len = plength + 2 + tlen; - do { - digit = len & 127; //digit = len %128 - len >>= 7; //len = len / 128 - if (len > 0) { - digit |= 0x80; - } - this->buffer[pos++] = digit; - llen++; - } while(len>0); + this->buffer[pos++] = digit; + llen++; + } while (len > 0); - pos = writeString(topic,this->buffer,pos); + pos = writeString(topic, this->buffer, pos); - rc += _client->write(this->buffer,pos); + rc += _client->write(this->buffer, pos); - for (i=0;i<plength;i++) { - rc += _client->write((char)pgm_read_byte_near(payload + i)); - } + for (i = 0; i < plength; i++) { + rc += _client->write((char) pgm_read_byte_near(payload + i)); + } - lastOutActivity = millis(); + lastOutActivity = millis(); - expectedLength = 1 + llen + 2 + tlen + plength; + expectedLength = 1 + llen + 2 + tlen + plength; - return (rc == expectedLength); + return (rc == expectedLength); } -boolean PubSubClient::beginPublish(const char* topic, unsigned int plength, boolean retained) { - if (connected()) { - // Send the header and variable length field - uint16_t length = MQTT_MAX_HEADER_SIZE; - length = writeString(topic,this->buffer,length); - uint8_t header = MQTTPUBLISH; - if (retained) { - header |= 1; - } - size_t hlen = buildHeader(header, this->buffer, plength+length-MQTT_MAX_HEADER_SIZE); - uint16_t rc = _client->write(this->buffer+(MQTT_MAX_HEADER_SIZE-hlen),length-(MQTT_MAX_HEADER_SIZE-hlen)); - lastOutActivity = millis(); - return (rc == (length-(MQTT_MAX_HEADER_SIZE-hlen))); +boolean PubSubClient::beginPublish(const char *topic, unsigned int plength, boolean retained) { + if (connected()) { + // Send the header and variable length field + uint16_t length = MQTT_MAX_HEADER_SIZE; + length = writeString(topic, this->buffer, length); + uint8_t header = MQTTPUBLISH; + if (retained) { + header |= 1; } - return false; + size_t hlen = buildHeader(header, this->buffer, plength + length - MQTT_MAX_HEADER_SIZE); + uint16_t rc = _client->write(this->buffer + (MQTT_MAX_HEADER_SIZE - hlen), length - (MQTT_MAX_HEADER_SIZE - hlen)); + lastOutActivity = millis(); + return (rc == (length - (MQTT_MAX_HEADER_SIZE - hlen))); + } + return false; } int PubSubClient::endPublish() { - return 1; + return 1; } size_t PubSubClient::write(uint8_t data) { - lastOutActivity = millis(); - return _client->write(data); + lastOutActivity = millis(); + return _client->write(data); } size_t PubSubClient::write(const uint8_t *buffer, size_t size) { - lastOutActivity = millis(); - return _client->write(buffer,size); -} - -size_t PubSubClient::buildHeader(uint8_t header, uint8_t* buf, uint16_t length) { - uint8_t lenBuf[4]; - uint8_t llen = 0; - uint8_t digit; - uint8_t pos = 0; - uint16_t len = length; - do { - - digit = len & 127; //digit = len %128 - len >>= 7; //len = len / 128 - if (len > 0) { - digit |= 0x80; - } - lenBuf[pos++] = digit; - llen++; - } while(len>0); - - buf[4-llen] = header; - for (int i=0;i<llen;i++) { - buf[MQTT_MAX_HEADER_SIZE-llen+i] = lenBuf[i]; + lastOutActivity = millis(); + return _client->write(buffer, size); +} + +size_t PubSubClient::buildHeader(uint8_t header, uint8_t *buf, uint16_t length) { + uint8_t lenBuf[4]; + uint8_t llen = 0; + uint8_t digit; + uint8_t pos = 0; + uint16_t len = length; + do { + + digit = len & 127; //digit = len %128 + len >>= 7; //len = len / 128 + if (len > 0) { + digit |= 0x80; } - return llen+1; // Full header size is variable length bit plus the 1-byte fixed header + lenBuf[pos++] = digit; + llen++; + } while (len > 0); + + buf[4 - llen] = header; + for (int i = 0; i < llen; i++) { + buf[MQTT_MAX_HEADER_SIZE - llen + i] = lenBuf[i]; + } + return llen + 1; // Full header size is variable length bit plus the 1-byte fixed header } -boolean PubSubClient::write(uint8_t header, uint8_t* buf, uint16_t length) { - uint16_t rc; - uint8_t hlen = buildHeader(header, buf, length); +boolean PubSubClient::write(uint8_t header, uint8_t *buf, uint16_t length) { + uint16_t rc; + uint8_t hlen = buildHeader(header, buf, length); #ifdef MQTT_MAX_TRANSFER_SIZE uint8_t* writeBuf = buf+(MQTT_MAX_HEADER_SIZE-hlen); @@ -596,174 +603,173 @@ boolean PubSubClient::write(uint8_t header, uint8_t* buf, uint16_t length) { } return result; #else - rc = _client->write(buf+(MQTT_MAX_HEADER_SIZE-hlen),length+hlen); - lastOutActivity = millis(); - return (rc == hlen+length); + rc = _client->write(buf + (MQTT_MAX_HEADER_SIZE - hlen), length + hlen); + lastOutActivity = millis(); + return (rc == hlen + length); #endif } -boolean PubSubClient::subscribe(const char* topic) { - return subscribe(topic, 0); +boolean PubSubClient::subscribe(const char *topic) { + return subscribe(topic, 0); } -boolean PubSubClient::subscribe(const char* topic, uint8_t qos) { - size_t topicLength = strnlen(topic, this->bufferSize); - if (topic == 0) { - return false; - } - if (qos > 1) { - return false; - } - if (this->bufferSize < 9 + topicLength) { - // Too long - return false; - } - if (connected()) { - // Leave room in the buffer for header and variable length field - uint16_t length = MQTT_MAX_HEADER_SIZE; - nextMsgId++; - if (nextMsgId == 0) { - nextMsgId = 1; - } - this->buffer[length++] = (nextMsgId >> 8); - this->buffer[length++] = (nextMsgId & 0xFF); - length = writeString((char*)topic, this->buffer,length); - this->buffer[length++] = qos; - return write(MQTTSUBSCRIBE|MQTTQOS1,this->buffer,length-MQTT_MAX_HEADER_SIZE); - } +boolean PubSubClient::subscribe(const char *topic, uint8_t qos) { + size_t topicLength = strnlen(topic, this->bufferSize); + if (topic == 0) { + return false; + } + if (qos > 1) { return false; + } + if (this->bufferSize < 9 + topicLength) { + // Too long + return false; + } + if (connected()) { + // Leave room in the buffer for header and variable length field + uint16_t length = MQTT_MAX_HEADER_SIZE; + nextMsgId++; + if (nextMsgId == 0) { + nextMsgId = 1; + } + this->buffer[length++] = (nextMsgId >> 8); + this->buffer[length++] = (nextMsgId & 0xFF); + length = writeString((char*) topic, this->buffer, length); + this->buffer[length++] = qos; + return write(MQTTSUBSCRIBE | MQTTQOS1, this->buffer, length - MQTT_MAX_HEADER_SIZE); + } + return false; } -boolean PubSubClient::unsubscribe(const char* topic) { - size_t topicLength = strnlen(topic, this->bufferSize); - if (topic == 0) { - return false; - } - if (this->bufferSize < 9 + topicLength) { - // Too long - return false; - } - if (connected()) { - uint16_t length = MQTT_MAX_HEADER_SIZE; - nextMsgId++; - if (nextMsgId == 0) { - nextMsgId = 1; - } - this->buffer[length++] = (nextMsgId >> 8); - this->buffer[length++] = (nextMsgId & 0xFF); - length = writeString(topic, this->buffer,length); - return write(MQTTUNSUBSCRIBE|MQTTQOS1,this->buffer,length-MQTT_MAX_HEADER_SIZE); - } +boolean PubSubClient::unsubscribe(const char *topic) { + size_t topicLength = strnlen(topic, this->bufferSize); + if (topic == 0) { return false; + } + if (this->bufferSize < 9 + topicLength) { + // Too long + return false; + } + if (connected()) { + uint16_t length = MQTT_MAX_HEADER_SIZE; + nextMsgId++; + if (nextMsgId == 0) { + nextMsgId = 1; + } + this->buffer[length++] = (nextMsgId >> 8); + this->buffer[length++] = (nextMsgId & 0xFF); + length = writeString(topic, this->buffer, length); + return write(MQTTUNSUBSCRIBE | MQTTQOS1, this->buffer, length - MQTT_MAX_HEADER_SIZE); + } + return false; } void PubSubClient::disconnect() { - this->buffer[0] = MQTTDISCONNECT; - this->buffer[1] = 0; - _client->write(this->buffer,2); - _state = MQTT_DISCONNECTED; - _client->flush(); - _client->stop(); - lastInActivity = lastOutActivity = millis(); -} - -uint16_t PubSubClient::writeString(const char* string, uint8_t* buf, uint16_t pos) { - const char* idp = string; - uint16_t i = 0; - pos += 2; - while (*idp) { - buf[pos++] = *idp++; - i++; - } - buf[pos-i-2] = (i >> 8); - buf[pos-i-1] = (i & 0xFF); - return pos; + this->buffer[0] = MQTTDISCONNECT; + this->buffer[1] = 0; + _client->write(this->buffer, 2); + _state = MQTT_DISCONNECTED; + _client->flush(); + _client->stop(); + lastInActivity = lastOutActivity = millis(); +} + +uint16_t PubSubClient::writeString(const char *string, uint8_t *buf, uint16_t pos) { + const char *idp = string; + uint16_t i = 0; + pos += 2; + while (*idp) { + buf[pos++] = *idp++; + i++; + } + buf[pos - i - 2] = (i >> 8); + buf[pos - i - 1] = (i & 0xFF); + return pos; } - boolean PubSubClient::connected() { - boolean rc; - if (_client == NULL ) { - rc = false; + boolean rc; + if (_client == NULL) { + rc = false; + } else { + rc = (int) _client->connected(); + if (!rc) { + if (this->_state == MQTT_CONNECTED) { + this->_state = MQTT_CONNECTION_LOST; + _client->flush(); + _client->stop(); + } } else { - rc = (int)_client->connected(); - if (!rc) { - if (this->_state == MQTT_CONNECTED) { - this->_state = MQTT_CONNECTION_LOST; - _client->flush(); - _client->stop(); - } - } else { - return this->_state == MQTT_CONNECTED; - } + return this->_state == MQTT_CONNECTED; } - return rc; + } + return rc; } -PubSubClient& PubSubClient::setServer(uint8_t * ip, uint16_t port) { - IPAddress addr(ip[0],ip[1],ip[2],ip[3]); - return setServer(addr,port); +PubSubClient& PubSubClient::setServer(uint8_t *ip, uint16_t port) { + IPAddress addr(ip[0], ip[1], ip[2], ip[3]); + return setServer(addr, port); } PubSubClient& PubSubClient::setServer(IPAddress ip, uint16_t port) { - this->ip = ip; - this->port = port; - this->domain = NULL; - return *this; + this->ip = ip; + this->port = port; + this->domain = NULL; + return *this; } -PubSubClient& PubSubClient::setServer(const char * domain, uint16_t port) { - this->domain = domain; - this->port = port; - return *this; +PubSubClient& PubSubClient::setServer(const char *domain, uint16_t port) { + this->domain = domain; + this->port = port; + return *this; } PubSubClient& PubSubClient::setCallback(MQTT_CALLBACK_SIGNATURE) { - this->callback = callback; - return *this; + this->callback = callback; + return *this; } -PubSubClient& PubSubClient::setClient(Client& client){ - this->_client = &client; - return *this; +PubSubClient& PubSubClient::setClient(Client &client) { + this->_client = &client; + return *this; } -PubSubClient& PubSubClient::setStream(Stream& stream){ - this->stream = &stream; - return *this; +PubSubClient& PubSubClient::setStream(Stream &stream) { + this->stream = &stream; + return *this; } int PubSubClient::state() { - return this->_state; + return this->_state; } boolean PubSubClient::setBufferSize(uint16_t size) { - if (size == 0) { - // Cannot set it back to 0 - return false; - } - if (this->bufferSize == 0) { - this->buffer = (uint8_t*)malloc(size); + if (size == 0) { + // Cannot set it back to 0 + return false; + } + if (this->bufferSize == 0) { + this->buffer = (uint8_t*) malloc(size); + } else { + uint8_t *newBuffer = (uint8_t*) realloc(this->buffer, size); + if (newBuffer != NULL) { + this->buffer = newBuffer; } else { - uint8_t* newBuffer = (uint8_t*)realloc(this->buffer, size); - if (newBuffer != NULL) { - this->buffer = newBuffer; - } else { - return false; - } + return false; } - this->bufferSize = size; - return (this->buffer != NULL); + } + this->bufferSize = size; + return (this->buffer != NULL); } uint16_t PubSubClient::getBufferSize() { - return this->bufferSize; + return this->bufferSize; } PubSubClient& PubSubClient::setKeepAlive(uint16_t keepAlive) { - this->keepAlive = keepAlive; - return *this; + this->keepAlive = keepAlive; + return *this; } PubSubClient& PubSubClient::setSocketTimeout(uint16_t timeout) { - this->socketTimeout = timeout; - return *this; + this->socketTimeout = timeout; + return *this; } diff --git a/ampel-firmware/src/lib/SparkFun_SCD30_Arduino_Library/keywords.txt b/ampel-firmware/src/lib/SparkFun_SCD30_Arduino_Library/keywords.txt index 7dbe45dde02b8e487f4618f845ea85829abc8fdd..e1a66d76d114097d56bcaa2596420b5e835c272b 100644 --- a/ampel-firmware/src/lib/SparkFun_SCD30_Arduino_Library/keywords.txt +++ b/ampel-firmware/src/lib/SparkFun_SCD30_Arduino_Library/keywords.txt @@ -14,25 +14,34 @@ SCD30 KEYWORD1 SCD30 KEYWORD2 begin KEYWORD2 +isConnected KEYWORD2 enableDebugging KEYWORD2 beginMeasuring KEYWORD2 StopMeasurement KEYWORD2 + +setAmbientPressure KEYWORD2 + getSettingValue KEYWORD2 -getForcedRecalibration KEYWORD2 -getMeasurementInterval KEYWORD2 -getTemperatureOffset KEYWORD2 -getAltitudeCompensation KEYWORD2 getFirmwareVersion KEYWORD2 getCO2 KEYWORD2 getHumidity KEYWORD2 getTemperature KEYWORD2 + +getMeasurementInterval KEYWORD2 setMeasurementInterval KEYWORD2 -setAmbientPressure KEYWORD2 + +getAltitudeCompensation KEYWORD2 setAltitudeCompensation KEYWORD2 + +getAutoSelfCalibration KEYWORD2 setAutoSelfCalibration KEYWORD2 + +getForcedRecalibration KEYWORD2 setForcedRecalibrationFactor KEYWORD2 + +getTemperatureOffset KEYWORD2 setTemperatureOffset KEYWORD2 -getAutoSelfCalibration KEYWORD2 + dataAvailable KEYWORD2 readMeasurement KEYWORD2 reset KEYWORD2 diff --git a/ampel-firmware/src/lib/SparkFun_SCD30_Arduino_Library/library.properties b/ampel-firmware/src/lib/SparkFun_SCD30_Arduino_Library/library.properties index 1e004493e332a8dcc2af1dce00ee7aab8e3bdc72..be9ed522d96ab108cbb62a52e4544098f5ea7a39 100644 --- a/ampel-firmware/src/lib/SparkFun_SCD30_Arduino_Library/library.properties +++ b/ampel-firmware/src/lib/SparkFun_SCD30_Arduino_Library/library.properties @@ -1,5 +1,5 @@ name=SparkFun SCD30 Arduino Library -version=1.0.13 +version=1.0.17 author=SparkFun Electronics maintainer=SparkFun Electronics <sparkfun.com> sentence=Library for the Sensirion SCD30 CO2 Sensor diff --git a/ampel-firmware/src/lib/SparkFun_SCD30_Arduino_Library/src/SparkFun_SCD30_Arduino_Library.cpp b/ampel-firmware/src/lib/SparkFun_SCD30_Arduino_Library/src/SparkFun_SCD30_Arduino_Library.cpp index 3d4ed4558b8e321a5308c9f195212e2382ac0e63..f83746fb0fd1c4c6606d36289f2b0d5b22122b49 100644 --- a/ampel-firmware/src/lib/SparkFun_SCD30_Arduino_Library/src/SparkFun_SCD30_Arduino_Library.cpp +++ b/ampel-firmware/src/lib/SparkFun_SCD30_Arduino_Library/src/SparkFun_SCD30_Arduino_Library.cpp @@ -7,9 +7,9 @@ Written by Nathan Seidle @ SparkFun Electronics, May 22nd, 2018 Updated February 1st 2021 to include some of the features of paulvha's version of the library - (while maintaining backward-compatibility): - https://github.com/paulvha/scd30 - Thank you Paul! + (while maintaining backward-compatibility): + https://github.com/paulvha/scd30 + Thank you Paul! The SCD30 measures CO2 with accuracy of +/- 30ppm. @@ -32,14 +32,14 @@ SCD30::SCD30(void) // Constructor } -//Initialize the Serial port +// Initialize the Serial port #ifdef USE_TEENSY3_I2C_LIB bool SCD30::begin(i2c_t3 &wirePort, bool autoCalibrate, bool measBegin) #else bool SCD30::begin(TwoWire &wirePort, bool autoCalibrate, bool measBegin) #endif { - _i2cPort = &wirePort; //Grab which port the user wants us to use + _i2cPort = &wirePort; // Grab which port the user wants us to use /* Especially during obtaining the ACK BIT after a byte sent the SCD30 is using clock stretching (but NOT only there)! * The need for clock stretching is described in the Sensirion_CO2_Sensors_SCD30_Interface_Description.pdf @@ -58,128 +58,153 @@ bool SCD30::begin(TwoWire &wirePort, bool autoCalibrate, bool measBegin) _i2cPort->setClockStretchLimit(200000); #endif - uint16_t fwVer; - if (getFirmwareVersion(&fwVer) == false) // Read the firmware version. Return false if the CRC check fails. + if (isConnected() == false) return (false); - if (_printDebug == true) - { - _debugPort->print(F("SCD30 begin: got firmware version 0x")); - _debugPort->println(fwVer, HEX); - } - if (measBegin == false) // Exit now if measBegin is false return (true); - //Check for device to respond correctly - if (beginMeasuring() == true) //Start continuous measurements + // Check for device to respond correctly + if (beginMeasuring() == true) // Start continuous measurements { - setMeasurementInterval(2); //2 seconds between measurements - setAutoSelfCalibration(autoCalibrate); //Enable auto-self-calibration + setMeasurementInterval(2); // 2 seconds between measurements + setAutoSelfCalibration(autoCalibrate); // Enable auto-self-calibration return (true); } - return (false); //Something went wrong + return (false); // Something went wrong +} + +// Returns true if device responds to a firmware request +bool SCD30::isConnected() +{ + uint16_t fwVer; + if (getFirmwareVersion(&fwVer) == false) // Read the firmware version. Return false if the CRC check fails. + return (false); + + if (_printDebug == true) + { + _debugPort->print(F("Firmware version 0x")); + _debugPort->println(fwVer, HEX); + } + + return (true); } -//Calling this function with nothing sets the debug port to Serial -//You can also call it with other streams like Serial1, SerialUSB, etc. +// Calling this function with nothing sets the debug port to Serial +// You can also call it with other streams like Serial1, SerialUSB, etc. void SCD30::enableDebugging(Stream &debugPort) { - _debugPort = &debugPort; - _printDebug = true; + _debugPort = &debugPort; + _printDebug = true; } -//Returns the latest available CO2 level -//If the current level has already been reported, trigger a new read +// Returns the latest available CO2 level +// If the current level has already been reported, trigger a new read uint16_t SCD30::getCO2(void) { - if (co2HasBeenReported == true) //Trigger a new read - readMeasurement(); //Pull in new co2, humidity, and temp into global vars + if (co2HasBeenReported == true) // Trigger a new read + { + if (readMeasurement() == false) // Pull in new co2, humidity, and temp into global vars + co2 = 0; // Failed to read sensor + } co2HasBeenReported = true; - return (uint16_t)co2; //Cut off decimal as co2 is 0 to 10,000 + return (uint16_t)co2; // Cut off decimal as co2 is 0 to 10,000 } -//Returns the latest available humidity -//If the current level has already been reported, trigger a new read +// Returns the latest available humidity +// If the current level has already been reported, trigger a new read float SCD30::getHumidity(void) { - if (humidityHasBeenReported == true) //Trigger a new read - readMeasurement(); //Pull in new co2, humidity, and temp into global vars + if (humidityHasBeenReported == true) // Trigger a new read + if (readMeasurement() == false) // Pull in new co2, humidity, and temp into global vars + humidity = 0; // Failed to read sensor humidityHasBeenReported = true; return humidity; } -//Returns the latest available temperature -//If the current level has already been reported, trigger a new read +// Returns the latest available temperature +// If the current level has already been reported, trigger a new read float SCD30::getTemperature(void) { - if (temperatureHasBeenReported == true) //Trigger a new read - readMeasurement(); //Pull in new co2, humidity, and temp into global vars + if (temperatureHasBeenReported == true) // Trigger a new read + if (readMeasurement() == false) // Pull in new co2, humidity, and temp into global vars + temperature = 0; // Failed to read sensor temperatureHasBeenReported = true; return temperature; } -//Enables or disables the ASC +// Enables or disables the ASC bool SCD30::setAutoSelfCalibration(bool enable) { if (enable) - return sendCommand(COMMAND_AUTOMATIC_SELF_CALIBRATION, 1); //Activate continuous ASC + return sendCommand(COMMAND_AUTOMATIC_SELF_CALIBRATION, 1); // Activate continuous ASC else - return sendCommand(COMMAND_AUTOMATIC_SELF_CALIBRATION, 0); //Deactivate continuous ASC + return sendCommand(COMMAND_AUTOMATIC_SELF_CALIBRATION, 0); // Deactivate continuous ASC } -//Set the forced recalibration factor. See 1.3.7. -//The reference CO2 concentration has to be within the range 400 ppm ≤ cref(CO2) ≤ 2000 ppm. +// Set the forced recalibration factor. See 1.3.7. +// The reference CO2 concentration has to be within the range 400 ppm ≤ cref(CO2) ≤ 2000 ppm. bool SCD30::setForcedRecalibrationFactor(uint16_t concentration) { if (concentration < 400 || concentration > 2000) { - return false; //Error check. + return false; // Error check. } return sendCommand(COMMAND_SET_FORCED_RECALIBRATION_FACTOR, concentration); } -//Get the temperature offset. See 1.3.8. +// Get the temperature offset. See 1.3.8. float SCD30::getTemperatureOffset(void) { uint16_t response = readRegister(COMMAND_SET_TEMPERATURE_OFFSET); - return (((float)response) / 100.0); -} -//Set the temperature offset. See 1.3.8. -bool SCD30::setTemperatureOffset(float tempOffset) -{ union { int16_t signed16; uint16_t unsigned16; } signedUnsigned; // Avoid any ambiguity casting int16_t to uint16_t - signedUnsigned.signed16 = tempOffset * 100; - return sendCommand(COMMAND_SET_TEMPERATURE_OFFSET, signedUnsigned.unsigned16); + signedUnsigned.signed16 = response; + + return (((float)signedUnsigned.signed16) / 100.0); } -//Get the altitude compenstation. See 1.3.9. +// Set the temperature offset to remove module heating from temp reading +bool SCD30::setTemperatureOffset(float tempOffset) +{ + // Temp offset is only positive. See: https://github.com/sparkfun/SparkFun_SCD30_Arduino_Library/issues/27#issuecomment-971986826 + //"The SCD30 offset temperature is obtained by subtracting the reference temperature from the SCD30 output temperature" + // https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/9.5_CO2/Sensirion_CO2_Sensors_SCD30_Low_Power_Mode.pdf + + if (tempOffset < 0.0) + return (false); + + uint16_t value = tempOffset * 100; + + return sendCommand(COMMAND_SET_TEMPERATURE_OFFSET, value); +} + +// Get the altitude compenstation. See 1.3.9. uint16_t SCD30::getAltitudeCompensation(void) { return readRegister(COMMAND_SET_ALTITUDE_COMPENSATION); } -//Set the altitude compenstation. See 1.3.9. +// Set the altitude compenstation. See 1.3.9. bool SCD30::setAltitudeCompensation(uint16_t altitude) { return sendCommand(COMMAND_SET_ALTITUDE_COMPENSATION, altitude); } -//Set the pressure compenstation. This is passed during measurement startup. -//mbar can be 700 to 1200 +// Set the pressure compenstation. This is passed during measurement startup. +// mbar can be 700 to 1200 bool SCD30::setAmbientPressure(uint16_t pressure_mbar) { if (pressure_mbar < 700 || pressure_mbar > 1200) @@ -192,33 +217,34 @@ bool SCD30::setAmbientPressure(uint16_t pressure_mbar) // SCD30 soft reset void SCD30::reset() { - sendCommand(COMMAND_RESET); - + sendCommand(COMMAND_RESET); } // Get the current ASC setting bool SCD30::getAutoSelfCalibration() { uint16_t response = readRegister(COMMAND_AUTOMATIC_SELF_CALIBRATION); - if (response == 1) { + if (response == 1) + { return true; } - else { - return false; + else + { + return false; } } -//Begins continuous measurements -//Continuous measurement status is saved in non-volatile memory. When the sensor -//is powered down while continuous measurement mode is active SCD30 will measure -//continuously after repowering without sending the measurement command. -//Returns true if successful +// Begins continuous measurements +// Continuous measurement status is saved in non-volatile memory. When the sensor +// is powered down while continuous measurement mode is active SCD30 will measure +// continuously after repowering without sending the measurement command. +// Returns true if successful bool SCD30::beginMeasuring(uint16_t pressureOffset) { return (sendCommand(COMMAND_CONTINUOUS_MEASUREMENT, pressureOffset)); } -//Overload - no pressureOffset +// Overload - no pressureOffset bool SCD30::beginMeasuring(void) { return (beginMeasuring(0)); @@ -227,17 +253,26 @@ bool SCD30::beginMeasuring(void) // Stop continuous measurement bool SCD30::StopMeasurement(void) { - return(sendCommand(COMMAND_STOP_MEAS)); + return (sendCommand(COMMAND_STOP_MEAS)); } -//Sets interval between measurements -//2 seconds to 1800 seconds (30 minutes) +// Sets interval between measurements +// 2 seconds to 1800 seconds (30 minutes) bool SCD30::setMeasurementInterval(uint16_t interval) { return sendCommand(COMMAND_SET_MEASUREMENT_INTERVAL, interval); } -//Returns true when data is available +// Gets interval between measurements +// 2 seconds to 1800 seconds (30 minutes) +uint16_t SCD30::getMeasurementInterval(void) +{ + uint16_t interval = 0; + getSettingValue(COMMAND_SET_MEASUREMENT_INTERVAL, &interval); + return (interval); +} + +// Returns true when data is available bool SCD30::dataAvailable() { uint16_t response = readRegister(COMMAND_GET_DATA_READY); @@ -247,24 +282,27 @@ bool SCD30::dataAvailable() return (false); } -//Get 18 bytes from SCD30 -//Updates global variables with floats -//Returns true if success +// Get 18 bytes from SCD30 +// Updates global variables with floats +// Returns true if success bool SCD30::readMeasurement() { - //Verify we have data from the sensor + // Verify we have data from the sensor if (dataAvailable() == false) return (false); - ByteToFl tempCO2; tempCO2.value = 0; - ByteToFl tempHumidity; tempHumidity.value = 0; - ByteToFl tempTemperature; tempTemperature.value = 0; + ByteToFl tempCO2; + tempCO2.value = 0; + ByteToFl tempHumidity; + tempHumidity.value = 0; + ByteToFl tempTemperature; + tempTemperature.value = 0; _i2cPort->beginTransmission(SCD30_ADDRESS); - _i2cPort->write(COMMAND_READ_MEASUREMENT >> 8); //MSB - _i2cPort->write(COMMAND_READ_MEASUREMENT & 0xFF); //LSB + _i2cPort->write(COMMAND_READ_MEASUREMENT >> 8); // MSB + _i2cPort->write(COMMAND_READ_MEASUREMENT & 0xFF); // LSB if (_i2cPort->endTransmission() != 0) - return (0); //Sensor did not ACK + return (0); // Sensor did not ACK delay(3); @@ -283,25 +321,25 @@ bool SCD30::readMeasurement() case 1: case 3: case 4: - tempCO2.array[x < 3 ? 3-x : 4-x] = incoming; + tempCO2.array[x < 3 ? 3 - x : 4 - x] = incoming; bytesToCrc[x % 3] = incoming; break; case 6: case 7: case 9: case 10: - tempTemperature.array[x < 9 ? 9-x : 10-x] = incoming; + tempTemperature.array[x < 9 ? 9 - x : 10 - x] = incoming; bytesToCrc[x % 3] = incoming; break; case 12: case 13: case 15: case 16: - tempHumidity.array[x < 15 ? 15-x : 16-x] = incoming; + tempHumidity.array[x < 15 ? 15 - x : 16 - x] = incoming; bytesToCrc[x % 3] = incoming; break; default: - //Validate CRC + // Validate CRC uint8_t foundCrc = computeCRC8(bytesToCrc, 2); if (foundCrc != incoming) { @@ -337,28 +375,28 @@ bool SCD30::readMeasurement() _debugPort->println(F("readMeasurement: encountered error reading SCD30 data.")); return false; } - //Now copy the uint32s into their associated floats + // Now copy the uint32s into their associated floats co2 = tempCO2.value; temperature = tempTemperature.value; humidity = tempHumidity.value; - //Mark our global variables as fresh + // Mark our global variables as fresh co2HasBeenReported = false; humidityHasBeenReported = false; temperatureHasBeenReported = false; - return (true); //Success! New data available in globals. + return (true); // Success! New data available in globals. } -//Gets a setting by reading the appropriate register. -//Returns true if the CRC is valid. +// Gets a setting by reading the appropriate register. +// Returns true if the CRC is valid. bool SCD30::getSettingValue(uint16_t registerAddress, uint16_t *val) { _i2cPort->beginTransmission(SCD30_ADDRESS); - _i2cPort->write(registerAddress >> 8); //MSB - _i2cPort->write(registerAddress & 0xFF); //LSB + _i2cPort->write(registerAddress >> 8); // MSB + _i2cPort->write(registerAddress & 0xFF); // LSB if (_i2cPort->endTransmission() != 0) - return (false); //Sensor did not ACK + return (false); // Sensor did not ACK delay(3); @@ -384,14 +422,14 @@ bool SCD30::getSettingValue(uint16_t registerAddress, uint16_t *val) return (false); } -//Gets two bytes from SCD30 +// Gets two bytes from SCD30 uint16_t SCD30::readRegister(uint16_t registerAddress) { _i2cPort->beginTransmission(SCD30_ADDRESS); - _i2cPort->write(registerAddress >> 8); //MSB - _i2cPort->write(registerAddress & 0xFF); //LSB + _i2cPort->write(registerAddress >> 8); // MSB + _i2cPort->write(registerAddress & 0xFF); // LSB if (_i2cPort->endTransmission() != 0) - return (0); //Sensor did not ACK + return (0); // Sensor did not ACK delay(3); @@ -402,49 +440,49 @@ uint16_t SCD30::readRegister(uint16_t registerAddress) uint8_t lsb = _i2cPort->read(); return ((uint16_t)msb << 8 | lsb); } - return (0); //Sensor did not respond + return (0); // Sensor did not respond } -//Sends a command along with arguments and CRC +// Sends a command along with arguments and CRC bool SCD30::sendCommand(uint16_t command, uint16_t arguments) { uint8_t data[2]; data[0] = arguments >> 8; data[1] = arguments & 0xFF; - uint8_t crc = computeCRC8(data, 2); //Calc CRC on the arguments only, not the command + uint8_t crc = computeCRC8(data, 2); // Calc CRC on the arguments only, not the command _i2cPort->beginTransmission(SCD30_ADDRESS); - _i2cPort->write(command >> 8); //MSB - _i2cPort->write(command & 0xFF); //LSB - _i2cPort->write(arguments >> 8); //MSB - _i2cPort->write(arguments & 0xFF); //LSB + _i2cPort->write(command >> 8); // MSB + _i2cPort->write(command & 0xFF); // LSB + _i2cPort->write(arguments >> 8); // MSB + _i2cPort->write(arguments & 0xFF); // LSB _i2cPort->write(crc); if (_i2cPort->endTransmission() != 0) - return (false); //Sensor did not ACK + return (false); // Sensor did not ACK return (true); } -//Sends just a command, no arguments, no CRC +// Sends just a command, no arguments, no CRC bool SCD30::sendCommand(uint16_t command) { _i2cPort->beginTransmission(SCD30_ADDRESS); - _i2cPort->write(command >> 8); //MSB - _i2cPort->write(command & 0xFF); //LSB + _i2cPort->write(command >> 8); // MSB + _i2cPort->write(command & 0xFF); // LSB if (_i2cPort->endTransmission() != 0) - return (false); //Sensor did not ACK + return (false); // Sensor did not ACK return (true); } -//Given an array and a number of bytes, this calculate CRC8 for those bytes -//CRC is only calc'd on the data portion (two bytes) of the four bytes being sent -//From: http://www.sunshine2k.de/articles/coding/crc/understanding_crc.html -//Tested with: http://www.sunshine2k.de/coding/javascript/crc/crc_js.html -//x^8+x^5+x^4+1 = 0x31 +// Given an array and a number of bytes, this calculate CRC8 for those bytes +// CRC is only calc'd on the data portion (two bytes) of the four bytes being sent +// From: http://www.sunshine2k.de/articles/coding/crc/understanding_crc.html +// Tested with: http://www.sunshine2k.de/coding/javascript/crc/crc_js.html +// x^8+x^5+x^4+1 = 0x31 uint8_t SCD30::computeCRC8(uint8_t data[], uint8_t len) { - uint8_t crc = 0xFF; //Init with 0xFF + uint8_t crc = 0xFF; // Init with 0xFF for (uint8_t x = 0; x < len; x++) { @@ -459,5 +497,5 @@ uint8_t SCD30::computeCRC8(uint8_t data[], uint8_t len) } } - return crc; //No output reflection + return crc; // No output reflection } diff --git a/ampel-firmware/src/lib/SparkFun_SCD30_Arduino_Library/src/SparkFun_SCD30_Arduino_Library.h b/ampel-firmware/src/lib/SparkFun_SCD30_Arduino_Library/src/SparkFun_SCD30_Arduino_Library.h index 0104d092167cac83b806c41fdc8694e504b607d2..c5e7d25e5a49be8c7b676c507bae6a587f545c5d 100644 --- a/ampel-firmware/src/lib/SparkFun_SCD30_Arduino_Library/src/SparkFun_SCD30_Arduino_Library.h +++ b/ampel-firmware/src/lib/SparkFun_SCD30_Arduino_Library/src/SparkFun_SCD30_Arduino_Library.h @@ -40,10 +40,10 @@ #include <Wire.h> #endif -//The default I2C address for the SCD30 is 0x61. +// The default I2C address for the SCD30 is 0x61. #define SCD30_ADDRESS 0x61 -//Available commands +// Available commands #define COMMAND_CONTINUOUS_MEASUREMENT 0x0010 #define COMMAND_SET_MEASUREMENT_INTERVAL 0x4600 @@ -70,38 +70,43 @@ public: bool begin(bool autoCalibrate) { return begin(Wire, autoCalibrate); } #ifdef USE_TEENSY3_I2C_LIB - bool begin(i2c_t3 &wirePort = Wire, bool autoCalibrate = false, bool measBegin = true); //By default use Wire port + bool begin(i2c_t3 &wirePort = Wire, bool autoCalibrate = false, bool measBegin = true); // By default use Wire port #else - bool begin(TwoWire &wirePort = Wire, bool autoCalibrate = false, bool measBegin = true); //By default use Wire port + bool begin(TwoWire &wirePort = Wire, bool autoCalibrate = false, bool measBegin = true); // By default use Wire port #endif - void enableDebugging(Stream &debugPort = Serial); //Turn on debug printing. If user doesn't specify then Serial will be used. + bool isConnected(); + void enableDebugging(Stream &debugPort = Serial); // Turn on debug printing. If user doesn't specify then Serial will be used. bool beginMeasuring(uint16_t pressureOffset); bool beginMeasuring(void); bool StopMeasurement(void); // paulvha - // based on paulvha + bool setAmbientPressure(uint16_t pressure_mbar); + bool getSettingValue(uint16_t registerAddress, uint16_t *val); - bool getForcedRecalibration(uint16_t *val) { return (getSettingValue(COMMAND_SET_FORCED_RECALIBRATION_FACTOR, val)); } - bool getMeasurementInterval(uint16_t *val) { return (getSettingValue(COMMAND_SET_MEASUREMENT_INTERVAL, val)); } - bool getTemperatureOffset(uint16_t *val) { return (getSettingValue(COMMAND_SET_TEMPERATURE_OFFSET, val)); } - bool getAltitudeCompensation(uint16_t *val) { return (getSettingValue(COMMAND_SET_ALTITUDE_COMPENSATION, val)); } bool getFirmwareVersion(uint16_t *val) { return (getSettingValue(COMMAND_READ_FW_VER, val)); } - uint16_t getCO2(void); float getHumidity(void); float getTemperature(void); - float getTemperatureOffset(void); - uint16_t getAltitudeCompensation(void); + uint16_t getMeasurementInterval(void); + bool getMeasurementInterval(uint16_t *val) { return (getSettingValue(COMMAND_SET_MEASUREMENT_INTERVAL, val)); } bool setMeasurementInterval(uint16_t interval); - bool setAmbientPressure(uint16_t pressure_mbar); + + uint16_t getAltitudeCompensation(void); + bool getAltitudeCompensation(uint16_t *val) { return (getSettingValue(COMMAND_SET_ALTITUDE_COMPENSATION, val)); } bool setAltitudeCompensation(uint16_t altitude); + + bool getAutoSelfCalibration(void); bool setAutoSelfCalibration(bool enable); + + bool getForcedRecalibration(uint16_t *val) { return (getSettingValue(COMMAND_SET_FORCED_RECALIBRATION_FACTOR, val)); } bool setForcedRecalibrationFactor(uint16_t concentration); + + float getTemperatureOffset(void); + bool getTemperatureOffset(uint16_t *val) { return (getSettingValue(COMMAND_SET_TEMPERATURE_OFFSET, val)); } bool setTemperatureOffset(float tempOffset); - bool getAutoSelfCalibration(void); bool dataAvailable(); bool readMeasurement(); @@ -116,25 +121,25 @@ public: uint8_t computeCRC8(uint8_t data[], uint8_t len); private: - //Variables + // Variables #ifdef USE_TEENSY3_I2C_LIB - i2c_t3 *_i2cPort; //The generic connection to user's chosen I2C hardware + i2c_t3 *_i2cPort; // The generic connection to user's chosen I2C hardware #else - TwoWire *_i2cPort; //The generic connection to user's chosen I2C hardware + TwoWire *_i2cPort; // The generic connection to user's chosen I2C hardware #endif - //Global main datums + // Global main datums float co2 = 0; float temperature = 0; float humidity = 0; - //These track the staleness of the current data - //This allows us to avoid calling readMeasurement() every time individual datums are requested + // These track the staleness of the current data + // This allows us to avoid calling readMeasurement() every time individual datums are requested bool co2HasBeenReported = true; bool humidityHasBeenReported = true; bool temperatureHasBeenReported = true; - //Debug - Stream *_debugPort; //The stream to send debug messages to if enabled. Usually Serial. - boolean _printDebug = false; //Flag to print debugging variables + // Debug + Stream *_debugPort; // The stream to send debug messages to if enabled. Usually Serial. + boolean _printDebug = false; // Flag to print debugging variables }; #endif diff --git a/ampel-firmware/util.cpp b/ampel-firmware/util.cpp index 73f53766a47e5da2a2d079ed64eed019cd8bad5e..130a051e56880b88598408726cc2c5ce3b3dc1fb 100644 --- a/ampel-firmware/util.cpp +++ b/ampel-firmware/util.cpp @@ -1,62 +1,16 @@ #include "util.h" - -namespace config { - const char *ntp_server = NTP_SERVER; - const long utc_offset_in_seconds = UTC_OFFSET_IN_SECONDS; // UTC+1 -} +#include "sensor_console.h" #if defined(ESP8266) +# include <ESP8266WiFi.h> // required to get MAC address const char *current_board = "ESP8266"; -# if !defined(AMPEL_WIFI) -void preinit() { - // WiFi would be initialized otherwise (on ESP8266), even if unused. - // see https://github.com/esp8266/Arduino/issues/2111#issuecomment-224251391 - ESP8266WiFiClass::preinitWiFiOff(); -} -# endif #elif defined(ESP32) +# include <WiFi.h> // required to get MAC address const char *current_board = "ESP32"; #else const char *current_board = "UNKNOWN"; #endif -//NOTE: ESP32 sometimes couldn't access the NTP server, and every loop would take +1000ms -// ifdefs could be used to define functions specific to ESP32, e.g. with configTime -namespace ntp { - WiFiUDP ntpUDP; - NTPClient timeClient(ntpUDP, config::ntp_server, config::utc_offset_in_seconds, 60000UL); - bool connected_at_least_once = false; - - void initialize() { - timeClient.begin(); - } - - void update() { - connected_at_least_once |= timeClient.update(); - } - - void getLocalTime(char *timestamp) { - timeClient.getFormattedDate(timestamp); - } - - void setLocalTime(int32_t unix_seconds) { - char time[23]; - timeClient.getFormattedDate(time); - Serial.print(F("Current time : ")); - Serial.println(time); - if (connected_at_least_once) { - Serial.println(F("NTP update already happened. Not changing anything.")); - return; - } - Serial.print(F("Setting UNIX time to : ")); - Serial.println(unix_seconds); - timeClient.setEpochTime(unix_seconds - seconds()); - timeClient.getFormattedDate(time); - Serial.print(F("Current time : ")); - Serial.println(time); - } -} - void Ampel::showFreeSpace() { Serial.print(F("Free heap space : ")); Serial.print(ESP.getFreeHeap()); @@ -90,7 +44,6 @@ char* getSensorId() { Ampel::Ampel() : board(current_board), sensorId(getSensorId()), macAddress(getMacString()), max_loop_duration(0) { - sensor_console::defineIntCommand("set_time", ntp::setLocalTime, F("1618829570 (Sets time to the given UNIX time)")); sensor_console::defineCommand("free", Ampel::showFreeSpace, F("(Displays available heap space)")); sensor_console::defineCommand("reset", []() { ESP.restart(); diff --git a/ampel-firmware/util.h b/ampel-firmware/util.h index 5d13bae02394ca2db91a319e0d8d7aaf3eac0612..495f7eaa0b149658631ec38b1cd5dab318e00af8 100644 --- a/ampel-firmware/util.h +++ b/ampel-firmware/util.h @@ -1,28 +1,16 @@ #ifndef AMPEL_UTIL_H_INCLUDED #define AMPEL_UTIL_H_INCLUDED -#include <Arduino.h> -#include "config.h" -#include "sensor_console.h" -#include <WiFiUdp.h> // required for NTP -#include "src/lib/NTPClient-master/NTPClient.h" // NTP +#include <stdint.h> // For uint32_t #if defined(ESP8266) -# include <ESP8266WiFi.h> // required to get MAC address # define esp_get_max_free_block_size() ESP.getMaxFreeBlockSize() # define esp_get_heap_fragmentation() ESP.getHeapFragmentation() #elif defined(ESP32) -# include <WiFi.h> // required to get MAC address # define esp_get_max_free_block_size() ESP.getMaxAllocHeap() //largest block of heap that can be allocated. # define esp_get_heap_fragmentation() "?" // apparently not available for ESP32 #endif -namespace ntp { - void initialize(); - void update(); - void getLocalTime(char *timestamp); -} - namespace util { template<typename Tpa, typename Tpb> inline auto min(const Tpa &a, const Tpb &b) -> decltype(a < b ? a : b) { @@ -48,7 +36,4 @@ public: extern Ampel ampel; -//NOTE: Only use seconds() for duration comparison, not timestamps comparison. Otherwise, problems happen when millis roll over. -#define seconds() (millis() / 1000UL) - #endif diff --git a/ampel-firmware/web_server.cpp b/ampel-firmware/web_server.cpp index 75897bc253172c7459be4f1a1a557ff132c73c55..266be7cfa7f33eeb42510182bedd87819a3a70d3 100644 --- a/ampel-firmware/web_server.cpp +++ b/ampel-firmware/web_server.cpp @@ -1,5 +1,27 @@ #include "web_server.h" +#if defined(ESP8266) +# include <ESP8266WebServer.h> +#elif defined(ESP32) +# include <WebServer.h> +#endif + +#include "config.h" +#include "util.h" +#include "ntp.h" +#include "wifi_util.h" +#include "co2_sensor.h" +#include "sensor_console.h" +#ifdef AMPEL_CSV +# include "csv_writer.h" +#endif +#ifdef AMPEL_MQTT +# include "mqtt.h" +#endif +#ifdef AMPEL_LORAWAN +# include "lorawan.h" +#endif + namespace config { // Values should be defined in config.h #ifdef HTTP_USER @@ -236,7 +258,7 @@ namespace web_server { mqtt::connected ? "Yes" : "No", mqtt::last_successful_publish, config::mqtt_sending_interval, #endif #if defined(AMPEL_LORAWAN) && defined(ESP32) - lorawan::connected ? "Yes" : "No", LMIC_FREQUENCY_PLAN, lorawan::last_transmission, + lorawan::connected ? "Yes" : "No", config::lorawan_frequency_plan, lorawan::last_transmission, config::lorawan_sending_interval, #endif config::temperature_offset, config::auto_calibrate_sensor ? "Yes" : "No", ampel.sensorId, ampel.sensorId, diff --git a/ampel-firmware/web_server.h b/ampel-firmware/web_server.h index bc2c0101fca661d1d7cd0db5ff922851b0224e58..ad52b656bf25279d9816679e6de41a6046962985 100644 --- a/ampel-firmware/web_server.h +++ b/ampel-firmware/web_server.h @@ -1,27 +1,6 @@ #ifndef WEB_SERVER_H_ #define WEB_SERVER_H_ -#if defined(ESP8266) -# include <ESP8266WebServer.h> -#elif defined(ESP32) -# include <WebServer.h> -#endif - -#include "config.h" -#include "util.h" -#include "wifi_util.h" -#include "co2_sensor.h" -#include "sensor_console.h" -#ifdef AMPEL_CSV -# include "csv_writer.h" -#endif -#ifdef AMPEL_MQTT -# include "mqtt.h" -#endif -#ifdef AMPEL_LORAWAN -# include "lorawan.h" -#endif - namespace web_server { void initialize(); void update(); diff --git a/ampel-firmware/wifi_util.cpp b/ampel-firmware/wifi_util.cpp index 4cdc7a2a597d3d66523c6e37b837346e5231311e..131a61574bdcec7d83c7087474cf44b87f34147f 100644 --- a/ampel-firmware/wifi_util.cpp +++ b/ampel-firmware/wifi_util.cpp @@ -1,5 +1,17 @@ #include "wifi_util.h" +#include "config.h" +#include "util.h" +#include "ntp.h" +#include "led_effects.h" +#include "sensor_console.h" + +#if defined(ESP8266) +# include <ESP8266WiFi.h> +#elif defined(ESP32) +# include <WiFi.h> +#endif + namespace config { // WiFi config. See 'config.h' if you want to modify those values. const char *wifi_ssid = WIFI_SSID; @@ -8,7 +20,7 @@ namespace config { #ifdef WIFI_TIMEOUT const uint8_t wifi_timeout = WIFI_TIMEOUT; // [s] Will try to connect during wifi_timeout seconds before failing. #else - const uint8_t wifi_timeout = 60; // [s] Will try to connect during wifi_timeout seconds before failing. + const uint8_t wifi_timeout = 60; // [s] Will try to connect during wifi_timeout seconds before failing. #endif } @@ -40,6 +52,10 @@ namespace wifi { Serial.println(WIFI_SSID); } + bool connected() { + return WiFi.status() == WL_CONNECTED; + } + // Initialize Wi-Fi void connect(const char *hostname) { @@ -64,7 +80,7 @@ namespace wifi { led_effects::showRainbowWheel(); Serial.print("."); } - if (WiFi.status() == WL_CONNECTED) { + if (connected()) { led_effects::showKITTWheel(color::green); Serial.println(); Serial.print(F("WiFi - Connected! IP address: ")); @@ -74,7 +90,8 @@ namespace wifi { } else { //TODO: Allow sensor to work as an Access Point, in order to define SSID & password? led_effects::showKITTWheel(color::red); - Serial.println(F("Connection to WiFi failed")); + Serial.print(F("Connection to WiFi failed! Status : ")); + Serial.println(WiFi.status()); } } } diff --git a/ampel-firmware/wifi_util.h b/ampel-firmware/wifi_util.h index 1cfe3da134de83f735bad7b8d58cd825fbd5e5d1..78249ec8269d9b0fead0e0cbf030f0670248ce81 100644 --- a/ampel-firmware/wifi_util.h +++ b/ampel-firmware/wifi_util.h @@ -1,13 +1,10 @@ #ifndef WIFI_UTIL_H_INCLUDED #define WIFI_UTIL_H_INCLUDED -#include "config.h" -#include "util.h" -#include "led_effects.h" - namespace wifi { extern char local_ip[]; void connect(const char *hostname); + bool connected(); } #endif diff --git a/platformio.ini b/platformio.ini index becf69fc263e19f956d1cf7a7ff4701dd7322247..3d016a6c7c5d7c0a985c08a37e29b23aa2c3a69c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -24,8 +24,3 @@ monitor_speed = 115200 lib_deps = MCCI LoRaWAN LMIC library - -build_flags = - -D ARDUINO_LMIC_PROJECT_CONFIG_H_SUPPRESS - -D CFG_eu868=1 - -D CFG_sx1276_radio=1