diff --git a/Commands_Info.ino b/Commands_Info.ino new file mode 100644 index 0000000..ba83d37 --- /dev/null +++ b/Commands_Info.ino @@ -0,0 +1,67 @@ +// Displays information about the device +void C_Info(){ + Serial.println("--------------------------------------------------------------------------"); + Serial.println(" OASIS Device Information "); + Serial.println("--------------------------------------------------------------------------"); + + // Hardware Version + Serial.print("[OASIS] Device Hardware Version is "); + Serial.print(HW_MAJOR); + Serial.print("."); + Serial.println(HW_MINOR); + + // Firmware Version + Serial.print("[OASIS] Device Firmware Version is "); + Serial.print(FW_MAJOR); + Serial.print("."); + Serial.println(FW_MINOR); + + // ADC Resolution + Serial.print("[OASIS] ADC resolution: "); + Serial.println(ADC_BITS); + + // Feature - TEDS + Serial.print("[OASIS] TEDS module installed (1=yes, 0=no): "); + Serial.println(F_TEDS); + + // Feature - WSS + Serial.print("[OASIS] WSS module installed (1=yes, 0=no): "); + Serial.println(F_WSS); + + // Custom Device Name + Serial.print("[OASIS] Device name: "); + Serial.print(DeviceName); + Serial.println(); + Serial.println("--------------------------------------------------------------------------"); +} + +// Returns raw values over serial for GUI +void C_RawInfo(){ + // Hardware Version + Serial.print(HW_MAJOR); + Serial.print("."); + Serial.print(HW_MINOR); + Serial.print(";"); + + // Firmware Version + Serial.print(FW_MAJOR); + Serial.print("."); + Serial.print(FW_MINOR); + Serial.print(";"); + + // ADC Resolution + Serial.print(ADC_BITS); + Serial.print(";"); + + // Feature - TEDS + Serial.print(F_TEDS); + Serial.print(";"); + + // Feature - WSS + Serial.print(F_WSS); + Serial.print(";"); + + // Custom Device Name + Serial.print(DeviceName); + Serial.println(";"); +} diff --git a/Commands_Sample.ino b/Commands_Sample.ino new file mode 100644 index 0000000..6d1157a --- /dev/null +++ b/Commands_Sample.ino @@ -0,0 +1,182 @@ +// Sample routine +void C_Sample(int source){ + + resetVars(); + + // Get Acquisition parameters + StringPointer = strtok(NULL, delimiter); + t_sample = strtof(StringPointer, NULL); + StringPointer = strtok(NULL, delimiter); + f_sample = strtof(StringPointer, NULL); + StringPointer = strtok(NULL, delimiter); + TRIGG_LEVEL = strtof(StringPointer, NULL); + StringPointer = strtok(NULL, delimiter); + sync_mode = strtof(StringPointer, NULL); + T_sample = 1/f_sample*1000000; //Sample periode in microseconds + n_sample = f_sample*t_sample; + + if(n_sample>PS_CACHE_SIZE*BYTES_PER_SAMPLE){ + Serial.print("[OASIS] FATAL ERROR: Sample size too long to fit into buffer. Aborting data acquisition."); + ledError(); + Sound_Fatal_Error(); + return; + } + + OASISPaw.setTimer(T_sample); + + // Switch between normal and triggered mode + if(TRIGG_LEVEL==0){ + TRIGG = 1; + } + else { + TRIGG = 0; + } + + // Filter triggered mode if synchronization enabled + if(sync_mode){ + TRIGG = 1; + } + + // Confirming Acquisition parameters (Serial) + Serial.print("[OASIS] Sampling for "); + Serial.print(t_sample); + Serial.print(" second(s) with "); + Serial.print(f_sample); + Serial.println(" Hz."); + if(!TRIGG){ + Serial.print("[OASIS] Waiting for TRIGGER on channel 1; when surpassing "); + Serial.print(TRIGG_LEVEL); + Serial.println(" V ..."); + } + + // Confirming Acquisition parameters (UDP) + if(source==2){ + udp.beginPacket(udpTargetIP,udpPort); + udp.print("[OASIS] Sampling for "); + udp.print(t_sample); + udp.print(" second(s) with "); + udp.print(f_sample); + udp.println(" Hz."); + if(!TRIGG){ + udp.print("[OASIS] Waiting for TRIGGER on channel 1; when surpassing "); + udp.print(TRIGG_LEVEL); + udp.println(" V ..."); + } + udp.endPacket(); + } + + // Set send API + if(source==1){ + SendSerial = true; + } + if(source==2){ + SendUDP = true; + } + + // Announce Data Incoming for normal sampling + //if(TRIGG){ + if(source==1){ + Serial.println("<>"); + } + if(source==2){ + Serial.println("[OASIS] Data incoming on UDP."); + udp.beginPacket(udpTargetIP,udpPort); + udp.println("<>"); + udp.endPacket(); + } + //} + + xTaskCreatePinnedToCore(SendData, "DataSendTask", 10000, NULL, 0, &DataSender, 0); + if(sync_mode==0){ + // Start Sampling + ledBlue(); + Sound_Sample_Start(); + OASISPaw.enableTimer(); + } + else { + attachInterrupt(CC_GDO2, WSS_ISR, RISING); + + // Trigger Sync Source + if(sync_mode==1){ + ledCyan(); + Sound_Sample_Start(); + WSS_send(); + } + // Arm Sync Sink + else{ + ledPurple(); + Sound_WSS_armed(); + WSS_listen(); + } + } + + // Sample Loop + while(sampleCount>1); + Wire.write(0x01); // Select output register + Wire.write(0x10); + Wire.endTransmission(); + OASISPaw.tone(500, 120); + + Wire.beginTransmission(0x70>>1); + Wire.write(0x01); // Select output register + Wire.write(0x30); + Wire.endTransmission(); + OASISPaw.tone(600, 120); + + Wire.beginTransmission(0x70>>1); + Wire.write(0x01); // Select output register + Wire.write(0x70); + Wire.endTransmission(); + OASISPaw.tone(700, 120); + + Wire.beginTransmission(0x70>>1); + Wire.write(0x01); // Select output register + Wire.write(0xF0); + Wire.endTransmission(); + OASISPaw.tone(800, 120); +} + +// Sample Start Sound +void Sound_Sample_Start(){ + OASISPaw.tone(1047, 125); +} + +// Sample End Sound +void Sound_Sample_End(){ + OASISPaw.tone(947, 125); +} + +void Sound_Fatal_Error(){ + OASISPaw.tone(1047, 300); + delay(300); + OASISPaw.tone(1047, 300); + delay(300); + OASISPaw.tone(1047, 300); +} + +void Sound_Bad_Command(){ + OASISPaw.tone(1047, 200); + delay(100); + OASISPaw.tone(1047, 500); +} + +void Sound_WSS_armed(){ + OASISPaw.tone(500, 100); + delay(100); + OASISPaw.tone(500, 200); +} diff --git a/Commands_TEDDY.ino b/Commands_TEDDY.ino new file mode 100644 index 0000000..8d5b50a --- /dev/null +++ b/Commands_TEDDY.ino @@ -0,0 +1,29 @@ +// Function to read TEDS and return contents over serial interface - Not implemented yet +void C_ReadTEDS(){ + // Init TEDS reading on Channel X + + // SwitchChannelXToTEDS(); (not implemented yet) + Serial.print("TEDS read status: "); + Serial.println(teddy.read()); + Serial.print("ROM device type: "); + Serial.println(teddy.getROMDeviceType()); + Serial.print("Transducer manufacturer: "); + Serial.println(teddy.getManufacturer()); + Serial.print("Transducer type: "); + Serial.println(teddy.getTransducerType()); + Serial.print("Serial number: "); + unsigned long int SerialNum = teddy.getSerialNumber(); + Serial.println(SerialNum); + Serial.print("Sensor direction: "); + Serial.println(teddy.getSensorDirection()); + Serial.print("Sensor sensitivity [V/(m/s^2)]: "); + Serial.printf("%.8f", teddy.getSensitivity()); + Serial.println(); + Serial.print("Sensor sensitivity [mV/g]: "); + Serial.printf("%.8f",teddy.getSensitivity()*9806.65); + Serial.println(); + Serial.print("Calibration date (days since January 1st, 1998): "); + Serial.println(teddy.getCalibrationDate()); + Serial.println(); + // SwitchChannelXToICP(); (not implemented yet) +} diff --git a/Commands_VoltageRange.ino b/Commands_VoltageRange.ino new file mode 100644 index 0000000..e5e7c04 --- /dev/null +++ b/Commands_VoltageRange.ino @@ -0,0 +1,108 @@ +// Set voltage ranges in ADC +void C_SetVoltageRange(){ + Serial.println("[OASIS] Setting voltage ranges..."); + + // Get Voltage Ranges from command + for(i=0; i<4; i++){ + StringPointer = strtok(NULL, delimiter); + VoltageRangeSet[i] = int(strtof(StringPointer, NULL)); + + if((ADC_BITS==16)&&(VoltageRangeSet[i]==1||VoltageRangeSet[i]==3||VoltageRangeSet[i]==5)){ + Serial.print("[OASIS] Error - Invalid voltage range for channel "); + Serial.print(i+1); + Serial.print(": "); + Serial.print(VoltageRangeSet[i]); + Serial.println(". Setting previous value!"); + ledError(); + Sound_Bad_Command(); + } + else if(VoltageRangeSet[i]==1){ + VoltageRange[i] = 2.5; + } + else if(VoltageRangeSet[i]==2){ + VoltageRange[i] = 5; + } + else if(VoltageRangeSet[i]==3){ + VoltageRange[i] = 6.25; + } + else if(VoltageRangeSet[i]==4){ + VoltageRange[i] = 10; + } + else if(VoltageRangeSet[i]==5){ + VoltageRange[i] = 12.5; + } + else{ + Serial.print("[OASIS] Error - Invalid voltage range for channel "); + Serial.print(i+1); + Serial.print(": "); + Serial.print(VoltageRangeSet[i]); + Serial.println(". Setting previous value!"); + ledError(); + Sound_Bad_Command(); + } + + Serial.print("[OASIS] Voltage Range for channel "); + Serial.print(i+1); + Serial.print(": "); + Serial.print(VoltageRange[i]); + Serial.println("V"); + } + + // Write voltage range to ADC + if(ADC_BITS==16){ + if(VoltageRangeSet[1]!=VoltageRangeSet[0] || VoltageRangeSet[2]!=VoltageRangeSet[0] || VoltageRangeSet[3]!=VoltageRangeSet[0]){ + Serial.print("[OASIS] Error - All voltage ranges must be identical for ADC7606-4. Applying range "); + Serial.print(VoltageRange[0]); + Serial.println("V to all channels."); + for(int i=1; i<4; i++){ + VoltageRange[i] = VoltageRange[0]; + } + ledError(); + Sound_Bad_Command(); + } + Serial.println("[OASIS] Setting voltage ranges..."); + if(VoltageRange[0]==5){ + digitalWrite(RANGE, LOW); + } + else{ + digitalWrite(RANGE, HIGH); + } + } + if(ADC_BITS==18){ + Serial.println("[OASIS] Setting voltage ranges in ADC..."); + writeADCConfig(0x03, (Range[VoltageRangeSet[1]-1] << 4) + Range[VoltageRangeSet[0]-1]); + writeADCConfig(0x04, (Range[VoltageRangeSet[3]-1] << 4) + Range[VoltageRangeSet[2]-1]); + } + + Serial.println("[OASIS] Voltage ranges set."); + Serial.println(); + + if(OASISWiFi){ + udp.beginPacket(udpTargetIP,udpPort); + udp.print("[OASIS] Set voltage ranges: Ch1: "); + udp.print(VoltageRange[0]); + udp.print("V, Ch2: "); + udp.print(VoltageRange[1]); + udp.print("V, Ch3: "); + udp.print(VoltageRange[2]); + udp.print("V, Ch4: "); + udp.print(VoltageRange[3]); + udp.print("V"); + udp.endPacket(); + } +} + +// Get voltage ranges from ADC +void C_GetVoltageRange(){ + Serial.println("[OASIS] Printing voltage ranges..."); + + // Get Voltage Ranges + for(i=0; i<4; i++){ + Serial.print("[OASIS] Voltage Range for channel "); + Serial.print(i+1); + Serial.print(": "); + Serial.print(VoltageRange[i]); + Serial.println("V"); + } + Serial.println(); +} diff --git a/Commands_WiFi.ino b/Commands_WiFi.ino new file mode 100644 index 0000000..8d41569 --- /dev/null +++ b/Commands_WiFi.ino @@ -0,0 +1,83 @@ +// Enable WiFi Access Point +void C_WiFiEnable(){ + EEPROM.write(1, 1); // Write variable to adress 0x01 + EEPROM.commit(); + Serial.println("[OASIS] Enabling WiFi after RESET"); + Serial.println("[OASIS] Reseting System..."); + OASISPaw.resetDevice(); +} + +// Disable WiFi Access Point +void C_WiFiDisable(){ + EEPROM.write(1, 0); // Write variable to adress 0x01 + EEPROM.commit(); + Serial.println("[OASIS] Disabling WiFi after RESET"); + Serial.println("[OASIS] Reseting System..."); + OASISPaw.resetDevice(); +} + +// List connected WiFi clients and send an UDP package to all of them +void C_WiFiDebug(){ + Serial.println("--------------------------------------------------------------------------"); + Serial.println(" Connected WiFi Clients "); + Serial.println("--------------------------------------------------------------------------"); + Serial.println(); + + if(WiFi.softAPgetStationNum()==0){ + Serial.println("No WiFi clients connected."); + Serial.println(); + } + else{ + + wifi_sta_list_t wifi_sta_list; + tcpip_adapter_sta_list_t adapter_sta_list; + + memset(&wifi_sta_list, 0, sizeof(wifi_sta_list)); + memset(&adapter_sta_list, 0, sizeof(adapter_sta_list)); + + esp_wifi_ap_get_sta_list(&wifi_sta_list); + tcpip_adapter_get_sta_list(&wifi_sta_list, &adapter_sta_list); + + for(int i = 0; i < adapter_sta_list.num; i++){ + + tcpip_adapter_sta_info_t station = adapter_sta_list.sta[i]; + + Serial.print("Client #"); + Serial.print(i+1); + Serial.print(" | MAC: "); + + for(int i = 0; i< 6; i++){ + + Serial.printf("%02X", station.mac[i]); + if(i<5)Serial.print(":"); + } + + Serial.print(" | IP: "); + Serial.println(ip4addr_ntoa(&(station.ip))); + udpTarget = ip4addr_ntoa(&(station.ip)); + + udp.beginPacket(udpTarget,udpPort); + udp.print("OASIS UDP Debug Package - You are client #"); + udp.print(i+1); + udp.print(" with IP-Address: "); + udp.println(udpTarget); + udp.endPacket(); + Serial.print("Sent debug package over UDP to "); + Serial.print(udpTarget); + Serial.print(":"); + Serial.println(udpPort); + Serial.println(); + } + } + + Serial.println("--------------------------------------------------------------------------"); + Serial.println(); +} + +// Function for killing the WiFi when data is sampled over the serial connection +void C_KillWiFi(){ + if(OASISWiFi && !KilledWiFi){ + Serial.println("[OASIS] WiFi is ON. Disabling WiFi for Data Acquisition over Serial..."); + C_WiFiDisable(); + } +} diff --git a/OASIS-Firmware.ino b/OASIS-Firmware.ino new file mode 100644 index 0000000..073e538 --- /dev/null +++ b/OASIS-Firmware.ino @@ -0,0 +1,540 @@ +#include +#include +#include +#include +#include + +#define EEPROM_SIZE 40 +#define CACHE_SIZE 1500 +#define PRECACHE_SIZE 1000 +#define PS_CACHE_SIZE 4193992 + +// OASIS PAW +OASISPAW OASISPaw; +extern volatile int OASISPAW::InterruptFlag; +extern volatile bool OASISPAW::OASISBusy; +extern volatile bool OASISPAW::FaultFlag; +extern portMUX_TYPE OASISPAW::timerMux; + +// SPI +SPIClass * OASISSPI = NULL; +SPIClass * OASIS_CC_SPI = NULL; + +// Voltage Range +float VoltageRange[4]; +int VoltageRangeSet[4]; +const byte Range[5] = {0x00, 0x01, 0x02, 0x03, 0x04}; + +// WiFi Setup +char WiFiSSID[50] = "OASIS v.X.X - "; +const char *pwd = "Vibration"; + +// UDP Setup +OASISUDP udp; +char *udpTarget; +const int udpPort = 3333; +char udpBuffer[255]; +IPAddress udpTargetIP; +IPAddress OASISIP = {192, 168, 4, 1}; +IPAddress subnet = {255, 255, 255, 0}; +IPAddress gateway = {0, 0, 0, 0}; + +// ######################################################################################### +// --------------------------------------- Variables --------------------------------------- +// ######################################################################################### + +// Loop Variable +int i = 0; +int k = 0; + +// Loop Variable for Core 0 +int f = 0; +int u = 0; + +// ADC Data Variables +byte Addr; +byte RegisterData; +byte ADCReadback; +unsigned long int ADCData; +float VoltageData; +float BitDivider; +byte BYTES_PER_SAMPLE; +const byte byteReadADC = byte(0b01000000); +const byte byteWriteADC = byte(0b00000000); + +// Sampling +volatile int sampleCount = 0; +int T_sample = 0; +float f_sample = 0; +int n_sample = 0; +float t_sample = 0; +float TRIGG_LEVEL = 0; +volatile int TRIGG; + +// Command Parsing +String Command; +char StrValues[42]; +char delimiter[] = "(,)"; +char *StringPointer; + +// Errorhandling +bool LEDError = false; +bool KilledWiFi = false; + +// Persistant configuration +bool OASISMute = false; +bool OASISWiFi = false; + +// Data Cache +byte OASISCacheA[CACHE_SIZE][9]; +byte OASISCacheB[CACHE_SIZE][9]; +volatile int CachePage = 1; +volatile int CacheIndex; +byte OASISPreCache[PRECACHE_SIZE][9]; +volatile int PreCacheIndex; + +// Parallel computing +volatile bool PacketToSend = false; +volatile bool SendLastPacket = false; +volatile bool SendSerial = false; +volatile bool SendUDP = false; + +// Device Info +byte HW_MAJOR; +byte HW_MINOR; +byte FW_MAJOR; +byte FW_MINOR; +byte ADC_BITS; +byte F_TEDS; +byte F_WSS; +char DeviceName[25]; + +// PSRAM Buffer +byte* OASISBuffer; + +// WSS +byte sync_mode = 0; + +// TEDDY +TEDDY teddy(TEDDYPin); + +// ######################################################################################### +// ----------------------------------- Interrupt Routine ----------------------------------- +// ######################################################################################### + +// Interrupt Routine GPIO +void IRAM_ATTR GPIOISR() { + digitalWrite(HSPI_CS, LOW); + + if(!TRIGG){ + // Before TRIGG + for(i=0; itransfer(0x00); + } + + // Assemble Channel 1 + if(BYTES_PER_SAMPLE==9){ + ADCData = (((OASISPreCache[PreCacheIndex][0] << 16) + (OASISPreCache[PreCacheIndex][1] << 8) + OASISPreCache[PreCacheIndex][2]) >> 6); + } + if(BYTES_PER_SAMPLE==8){ + ADCData = ((OASISPreCache[PreCacheIndex][0] << 8) + OASISPreCache[PreCacheIndex][1]); + } + + // Conversion to voltage + if(bitRead(ADCData, (ADC_BITS-1))==0){ + VoltageData = ADCData*VoltageRange[0]/BitDivider; + } + else{ + VoltageData = ((ADCData-2*BitDivider)/BitDivider)*VoltageRange[0]; + } + + //Check TRIGG + if(VoltageData>TRIGG_LEVEL){ + TRIGG = 1; + //Serial.println("<>"); + } + else{ + PreCacheIndex++; + if(PreCacheIndex==PRECACHE_SIZE){ + PreCacheIndex = 0; + } + } + } + else{ + // After TRIGG + if(CachePage%2){ + for(i=0; itransfer(0x00); + } + } + else{ + for(i=0; itransfer(0x00); + } + } + + // Write samples into PSRAM - Not implemented yet + //for(i=0; itransfer(0x00); + //} + + CacheIndex++; + + if(CacheIndex==CACHE_SIZE){ + CacheIndex = 0; + CachePage++; + PacketToSend = true; + } + } + + digitalWrite(CONVST, LOW); + digitalWrite(HSPI_CS, HIGH); + OASISPaw.InterruptFlag = 1; +} + +void IRAM_ATTR WSS_ISR(){ + OASISPaw.enableTimer(); +} + +// ######################################################################################### +// ----------------------------------------- Tasks ----------------------------------------- +// ######################################################################################### + +TaskHandle_t DataSender; + +void SendData(void * parameter){ + for(;;){ + + if(PacketToSend){ + // Send data over Serial + if(SendSerial){ + if(CachePage%2){ + for(f=0; fbegin(); + OASISSPI->beginTransaction(SPISettings(25000000, MSBFIRST, SPI_MODE3)); + + // WSS SPI Setup + OASIS_CC_SPI = new SPIClass(VSPI); + OASIS_CC_SPI->begin(); + OASIS_CC_SPI->beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0)); + + // Port Expander Setup + initpEXP(); + + // Load Device Info from EEPROM + HW_MAJOR = EEPROM.read(0x08); + HW_MINOR = EEPROM.read(0x09); + FW_MAJOR = EEPROM.read(0x0A); + FW_MINOR = EEPROM.read(0x0B); + ADC_BITS = EEPROM.read(0x0C); + F_TEDS = EEPROM.read(0x0D); + F_WSS = EEPROM.read(0x0E); + for(int i=0; i<24; i++){ + if(EEPROM.read(0x10 + i)!=0){ + DeviceName[i] = (char)EEPROM.read(0x10 + i); + } + } + + // Announce Startup + Serial.print("[OASIS] This is "); + Serial.print(DeviceName); + Serial.println(" starting up..."); + + // Read Firmware Version + Serial.print("[OASIS] Firmware Version "); + Serial.print(FW_MAJOR); + Serial.print("."); + Serial.print(FW_MINOR); + Serial.println(" initializing..."); + + // Load settings in EEPROM + OASISMute = EEPROM.read(0); // Option to mute all sounds, 1 = yes + OASISWiFi = EEPROM.read(1); + + // Pin Setup + pinMode(HSPI_CS, OUTPUT); + pinMode(RESET, OUTPUT); + pinMode(CONVST, OUTPUT); + pinMode(RANGE, OUTPUT); + pinMode(BUSY, INPUT_PULLDOWN); + digitalWrite(HSPI_CS, HIGH); + digitalWrite(RESET, LOW); + digitalWrite(CONVST, LOW); + + StartupSound(); + + // ADC Config + Serial.println("[OASIS] Resetting ADC..."); + digitalWrite(RESET, HIGH); + delay(1); + digitalWrite(RESET, LOW); + delay(1); + BitDivider = pow(2,ADC_BITS)/2; + BYTES_PER_SAMPLE = ADC_BITS/2; // 4 channels / 8 bits/byte + // Config for AD7606C-18 + if(ADC_BITS==18){ + // Set DOUT_FORMAT to 1 data line + writeADCConfig(0x02,0x00); + // Set Channel Range to +/- 2.5 V + writeADCConfig(0x03,0x00); + writeADCConfig(0x04,0x00); + for(int i=0; i<4; i++){ + VoltageRange[i] = 2.5; + } + } + // Config for AD7606-4 + if(ADC_BITS==16){ + // Set Channel Range to +/- 5 V + digitalWrite(RANGE, LOW); + for(int i=0; i<4; i++){ + VoltageRange[i] = 5.0; + } + } + + // Setup WiFi, if activated + if(OASISWiFi){ + + // Assemble WiFi SSID + sprintf(WiFiSSID, "OASIS v.%d.%d - %s", HW_MAJOR, HW_MINOR, DeviceName); + + // Create & configure WiFi Access Point + Serial.print("[OASIS] Starting WiFi Access Point '"); + Serial.print(WiFiSSID); + Serial.println("'"); + WiFi.softAP(WiFiSSID, pwd); + WiFi.softAPConfig(OASISIP, subnet, gateway); + udp.begin(3333); + Serial.print("[OASIS] UDP reachable at: "); + Serial.print(WiFi.softAPIP()); + Serial.print(":"); + Serial.println(udpPort); + } + else{ + Serial.println("[OASIS] WiFi Access Point is DISABLED!"); + } + + // Attach GPIO interrupt + attachInterrupt(BUSY, GPIOISR, FALLING); + + // Allocate PSRAM - Not implemented yet + //Serial.println(psramInit()); + //Serial.println(ESP.getFreePsram()); + //byte* OASISBuffer = (byte*)ps_malloc(PS_CACHE_SIZE); + //Serial.println(ESP.getFreePsram()); + //Serial.println(*OASISBuffer); + //OASISBuffer[0] = 0xFF; + //Serial.println(OASISBuffer[0]); + + // Init WSS + initWSS(); + + delay(500); + ledReady(); + Serial.println("[OASIS] Finished booting."); +} + +// ######################################################################################### +// --------------------------------------- VOID LOOP --------------------------------------- +// ######################################################################################### + +void loop() { + + // Wait for Serial command + if(Serial.available()) { + Command = Serial.readString(); + Command.toCharArray(StrValues,42); + StringPointer = strtok(StrValues, delimiter); + Command = String(StringPointer); + + LEDError = false; + + // Sample data + if(Command=="OASIS.Sample"){C_KillWiFi(); C_Sample(1);} + + // Set voltage ranges in ADC + else if(Command=="OASIS.SetVoltageRange"){C_SetVoltageRange();} + + // Get voltage ranges from ADC + else if(Command=="OASIS.GetVoltageRange"){C_GetVoltageRange();} + + // Mute the buzzer + else if(Command=="OASIS.Mute"){C_Mute();} + + // Unmute the buzzer + else if(Command=="OASIS.Unmute"){C_Unmute();} + + // Enable WiFi Access Point + else if(Command=="OASIS.EnableWiFi"){C_WiFiEnable();} + + // Disable WiFi Access Point + else if(Command=="OASIS.DisableWiFi"){C_WiFiDisable();} + + // List connected WiFi clients and send an UDP package to all of them + else if(Command=="OASIS.WiFiDebug"){C_WiFiDebug();} + + // Display Information about the device + else if(Command=="OASIS.Info"){C_Info();} + + // Return raw Information about the device + else if(Command=="OASIS.RawInfo"){C_RawInfo();} + + // Liaten for WSS impulse + else if(Command=="OASIS.WSSlisten"){Serial.println("[OASIS] Listening for WSS..."); WSS_listen();} + + // Send WSS impulse + else if(Command=="OASIS.WSSsend"){Serial.println("[OASIS] Sending WSS impulse..."); WSS_send();} + + // Read TEDS - Not implemented yet + //else if(Command=="OASIS.ReadTEDS"){C_ReadTEDS();} + + // Unknown Command + else{ + Serial.print("[OASIS] Error - Command is not valid: "); + Serial.print(StringPointer); + Serial.println(); + delay(10); + Sound_Bad_Command(); + ledError(); + } + if(!LEDError){ + ledReady(); + } + } + + // Read UDP Packets + if(udp.parsePacket()){ + Serial.print("[OASIS] Received command from "); + udpTargetIP = udp.remoteIP(); + udp.read(udpBuffer, 255); + Serial.print(udpTargetIP); + Serial.print(": "); + Serial.println(udpBuffer); + + // Parse the command + StringPointer = strtok(udpBuffer, delimiter); + Command = String(StringPointer); + + LEDError = false; + + // Sample data + if(Command=="OASIS.Sample"){C_Sample(2);} + + // Set voltage ranges in ADC + else if(Command=="OASIS.SetVoltageRange"){C_SetVoltageRange();} + + // Unknown Command + else{ + Serial.print("[OASIS] Error - Command is not valid: "); + Serial.println(StringPointer); + Serial.println(); + udp.beginPacket(udpTargetIP,udpPort); + udp.print("[OASIS] Error - Command is not valid: "); + udp.print(StringPointer); + udp.println(); + udp.endPacket(); + delay(10); + Sound_Bad_Command(); + ledError(); + } + if(!LEDError){ + ledReady(); + } + } +} diff --git a/WSS.ino b/WSS.ino new file mode 100644 index 0000000..0b8bd0a --- /dev/null +++ b/WSS.ino @@ -0,0 +1,70 @@ +// Initialize WSS by configuring the CC1101 chip +void initWSS(){ + pinMode(CC_GDO2, INPUT); + pinMode(VSPI_CS, OUTPUT); + digitalWrite(VSPI_CS, HIGH); + + // Reset chip + CC_SendCommand(0x30); // SRES + + delay(10); + + // Set GDO2 to Sync Word Detection + CC_WriteRegister(0x00, 0x06); + + // Set GDO0 to CHIP_RDYn + CC_WriteRegister(0x02, 0x29); + + // Change Sync Word + CC_WriteRegister(0x04, 'A'); + CC_WriteRegister(0x05, 'M'); + + // Disable Power Saving; RX -> Stay in RX; TX -> FSTXON + CC_WriteRegister(0x17, 0x3D); + + // Enable 30/32 sync word + carrier sense + CC_WriteRegister(0x12, 0x07); +} + +// Set CC1101 to recieve mode +void WSS_listen(){ + // Check if RXFIFO_OVERFLOW, acknowledge if necessary + if ((CC_ReadRegister(0x35)>>4)==6){ + CC_SendCommand(0x3A); // SFTX + } + CC_SendCommand(0x34); // SRX +} + +// Send WSS impulse +void WSS_send(){ + // Check if TXFIFO_UNDERFLOW, acknowledge if necessary + if ((CC_ReadRegister(0x35)>>4)==7){ + CC_SendCommand(0x3B); + } + CC_WriteRegister(0x3F,0xFF); + CC_SendCommand(0x35); // STX +} + +// Read CC1101 register at Addr +byte CC_ReadRegister(byte Addr){ + digitalWrite(VSPI_CS, LOW); + OASIS_CC_SPI->transfer(Addr + 128); + RegisterData = OASIS_CC_SPI->transfer(0x00); + digitalWrite(VSPI_CS, HIGH); + return RegisterData; +} + +// Write CC1101 register at Addr +void CC_WriteRegister(byte Addr, byte RegisterData){ + digitalWrite(VSPI_CS, LOW); + OASIS_CC_SPI->transfer(Addr); + OASIS_CC_SPI->transfer(RegisterData); + digitalWrite(VSPI_CS, HIGH); +} + +// Send command byte to CC1101 +void CC_SendCommand(byte CMD){ + digitalWrite(VSPI_CS, LOW); + OASIS_CC_SPI->transfer(CMD); + digitalWrite(VSPI_CS, HIGH); +} diff --git a/ledFeedback.ino b/ledFeedback.ino new file mode 100644 index 0000000..77ebda3 --- /dev/null +++ b/ledFeedback.ino @@ -0,0 +1,34 @@ +// Switch RGB LED to green +void ledReady() { + Wire.beginTransmission(0x70>>1); + Wire.write(0x01); // Select output register + Wire.write(0x00); + Wire.endTransmission(); + OASISPaw.writeRGB(0,35,0); +} + +// Switch RGB LED to blue +void ledBlue(){ + OASISPaw.writeRGB(0,0,35); +} + +// Switch RGB LED to purple +void ledPurple(){ + OASISPaw.writeRGB(35,0,35); +} + +// Switch RGB LED to cyan +void ledCyan(){ + OASISPaw.writeRGB(0,35,35); +} + +// Switch RGB LED to yellow +void ledWaiting(){ + OASISPaw.writeRGB(35,35,0); +} + +// Switch RGB LED to red and lock state +void ledError(){ + LEDError = true; + OASISPaw.writeRGB(35,0,0); +} diff --git a/libraries/Adafruit_NeoPixel/Adafruit_NeoPixel.cpp b/libraries/Adafruit_NeoPixel/Adafruit_NeoPixel.cpp new file mode 100644 index 0000000..540bcf7 --- /dev/null +++ b/libraries/Adafruit_NeoPixel/Adafruit_NeoPixel.cpp @@ -0,0 +1,3441 @@ +/*! + * @file Adafruit_NeoPixel.cpp + * + * @mainpage Arduino Library for driving Adafruit NeoPixel addressable LEDs, + * FLORA RGB Smart Pixels and compatible devicess -- WS2811, WS2812, WS2812B, + * SK6812, etc. + * + * @section intro_sec Introduction + * + * This is the documentation for Adafruit's NeoPixel library for the + * Arduino platform, allowing a broad range of microcontroller boards + * (most AVR boards, many ARM devices, ESP8266 and ESP32, among others) + * to control Adafruit NeoPixels, FLORA RGB Smart Pixels and compatible + * devices -- WS2811, WS2812, WS2812B, SK6812, etc. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing products + * from Adafruit! + * + * @section author Author + * + * Written by Phil "Paint Your Dragon" Burgess for Adafruit Industries, + * with contributions by PJRC, Michael Miller and other members of the + * open source community. + * + * @section license License + * + * This file is part of the Adafruit_NeoPixel library. + * + * Adafruit_NeoPixel is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * Adafruit_NeoPixel is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with NeoPixel. If not, see + * . + * + */ + +#include "Adafruit_NeoPixel.h" + +#if defined(TARGET_LPC1768) +#include +#endif + +#if defined(NRF52) || defined(NRF52_SERIES) +#include "nrf.h" + +// Interrupt is only disabled if there is no PWM device available +// Note: Adafruit Bluefruit nrf52 does not use this option +//#define NRF52_DISABLE_INT +#endif + +#if defined(ARDUINO_ARCH_NRF52840) +#if defined __has_include +#if __has_include() +#include +#endif +#endif +#endif + +/*! + @brief NeoPixel constructor when length, pin and pixel type are known + at compile-time. + @param n Number of NeoPixels in strand. + @param p Arduino pin number which will drive the NeoPixel data in. + @param t Pixel type -- add together NEO_* constants defined in + Adafruit_NeoPixel.h, for example NEO_GRB+NEO_KHZ800 for + NeoPixels expecting an 800 KHz (vs 400 KHz) data stream + with color bytes expressed in green, red, blue order per + pixel. + @return Adafruit_NeoPixel object. Call the begin() function before use. +*/ +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 +} + +/*! + @brief "Empty" NeoPixel constructor when length, pin and/or pixel type + are not known at compile-time, and must be initialized later with + updateType(), updateLength() and setPin(). + @return Adafruit_NeoPixel object. Call the begin() function before use. + @note This function is deprecated, here only for old projects that + may still be calling it. New projects should instead use the + 'new' keyword with the first constructor syntax (length, pin, + type). +*/ +Adafruit_NeoPixel::Adafruit_NeoPixel() + : +#if defined(NEO_KHZ400) + 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) { +} + +/*! + @brief Deallocate Adafruit_NeoPixel object, set data pin back to INPUT. +*/ +Adafruit_NeoPixel::~Adafruit_NeoPixel() { + free(pixels); + if (pin >= 0) + pinMode(pin, INPUT); +} + +/*! + @brief Configure NeoPixel pin for output. +*/ +void Adafruit_NeoPixel::begin(void) { + if (pin >= 0) { + pinMode(pin, OUTPUT); + digitalWrite(pin, LOW); + } + begun = true; +} + +/*! + @brief Change the length of a previously-declared Adafruit_NeoPixel + strip object. Old data is deallocated and new data is cleared. + Pin number and pixel format are unchanged. + @param n New length of strip, in pixels. + @note This function is deprecated, here only for old projects that + may still be calling it. New projects should instead use the + 'new' keyword with the first constructor syntax (length, pin, + type). +*/ +void Adafruit_NeoPixel::updateLength(uint16_t n) { + free(pixels); // Free existing data (if any) + + // Allocate new data -- note: ALL PIXELS ARE CLEARED + numBytes = n * ((wOffset == rOffset) ? 3 : 4); + if ((pixels = (uint8_t *)malloc(numBytes))) { + memset(pixels, 0, numBytes); + numLEDs = n; + } else { + numLEDs = numBytes = 0; + } +} + +/*! + @brief Change the pixel format of a previously-declared + Adafruit_NeoPixel strip object. If format changes from one of + the RGB variants to an RGBW variant (or RGBW to RGB), the old + data will be deallocated and new data is cleared. Otherwise, + the old data will remain in RAM and is not reordered to the + new format, so it's advisable to follow up with clear(). + @param t Pixel type -- add together NEO_* constants defined in + Adafruit_NeoPixel.h, for example NEO_GRB+NEO_KHZ800 for + NeoPixels expecting an 800 KHz (vs 400 KHz) data stream + with color bytes expressed in green, red, blue order per + pixel. + @note This function is deprecated, here only for old projects that + may still be calling it. New projects should instead use the + 'new' keyword with the first constructor syntax + (length, pin, type). +*/ +void Adafruit_NeoPixel::updateType(neoPixelType t) { + bool oldThreeBytesPerPixel = (wOffset == rOffset); // false if RGBW + + 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; +#if defined(NEO_KHZ400) + 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) { + bool newThreeBytesPerPixel = (wOffset == rOffset); + 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" 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); +#endif // ESP8266 + +#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 +/*! + @brief Transmit pixel data in RAM to NeoPixels. + @note On most architectures, interrupts are temporarily disabled in + order to achieve the correct NeoPixel signal timing. This means + that the Arduino millis() and micros() functions, which require + interrupts, will lose small intervals of time whenever this + function is called (about 30 microseconds per RGB pixel, 40 for + RGBW pixels). There's no easy fix for this, but a few + specialized alternative or companion libraries exist that use + very device-specific peripherals to work around it. +*/ +void Adafruit_NeoPixel::show(void) { + + 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 + // the function will simply hold off (if needed) on issuing the + // 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 + // ESP32 may not disable interrupts because espShow() uses RMT which tries to acquire locks +#if !(defined(NRF52) || defined(NRF52_SERIES) || defined(ESP32)) + noInterrupts(); // Need 100% focus on instruction timing +#endif + +#if defined(__AVR__) + // 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 + + // 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) + // for both the WS2811 (400 KHz) and WS2812 (800 KHz) drivers. The + // datastream timing for the LED drivers allows a little wiggle room each + // way (listed in the datasheets), so the conditions for compiling each + // case are set up for a range of frequencies rather than just the exact + // 8, 12 or 16 MHz values, permitting use with some close-but-not-spot-on + // devices (e.g. 16.5 MHz DigiSpark). The ranges were arrived at based + // on the datasheet figures and have not been extensively tested outside + // the canonical 8/12/16 MHz speeds; there's no guarantee these will work + // close to the extremes (or possibly they could be pushed further). + // Keep in mind only one CPU speed case actually gets compiled; the + // resulting program isn't as massive as it might look from source here. + +// 8 MHz(ish) AVR --------------------------------------------------------- +#if (F_CPU >= 7400000UL) && (F_CPU <= 9500000UL) + +#if defined(NEO_KHZ400) // 800 KHz check needed only if 400 KHz support enabled + if (is800KHz) { +#endif + + 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. + + // 10 instruction clocks per bit: HHxxxxxLLL + // OUT instructions: ^ ^ ^ (T=0,2,7) + + // PORTD OUTPUT ---------------------------------------------------- + +#if defined(PORTD) +#if defined(PORTB) || defined(PORTC) || defined(PORTF) + if (port == &PORTD) { +#endif // defined(PORTB/C/F) + + hi = PORTD | pinMask; + lo = PORTD & ~pinMask; + n1 = lo; + 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 + // using two NOPs). This was necessary in order to squeeze the + // loop down to exactly 64 words -- the maximum possible for a + // 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) + } else // other PORT(s) +#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) + + // Same as above, just switched to PORTB and stripped of comments. + hi = PORTB | pinMask; + lo = PORTB & ~pinMask; + n1 = lo; + 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) + } +#endif +#if defined(PORTC) || defined(PORTF) + else +#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) + + // Same as above, just switched to PORTC and stripped of comments. + hi = PORTC | pinMask; + lo = PORTC & ~pinMask; + n1 = lo; + 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) + } +#endif // defined(PORTD/B/F) +#if defined(PORTF) + else +#endif +#endif // defined(PORTC) + + // PORTF OUTPUT ---------------------------------------------------- + +#if defined(PORTF) +#if defined(PORTD) || defined(PORTB) || defined(PORTC) + if (port == &PORTF) { +#endif // defined(PORTD/B/C) + + hi = PORTF | pinMask; + lo = PORTF & ~pinMask; + n1 = lo; + 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) + } +#endif // defined(PORTD/B/C) +#endif // defined(PORTF) + +#if defined(NEO_KHZ400) + } else { // end 800 KHz, do 400 KHz + + // Timing is more relaxed; unrolling the inner loop for each bit is + // not necessary. Still using the peculiar RJMPs as 2X NOPs, not out + // of need but just to trim the code size down a little. + // This 400-KHz-datastream-on-8-MHz-CPU code is not quite identical + // to the 800-on-16 code later -- the hi/lo timing between WS2811 and + // WS2812 is not simply a 2:1 scale! + + // 20 inst. clocks per bit: HHHHxxxxxxLLLLLLLLLL + // ST instructions: ^ ^ ^ (T=0,4,10) + + volatile uint8_t next, bit; + + 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)); + } +#endif // NEO_KHZ400 + +// 12 MHz(ish) AVR -------------------------------------------------------- +#elif (F_CPU >= 11100000UL) && (F_CPU <= 14300000UL) + +#if defined(NEO_KHZ400) // 800 KHz check needed only if 400 KHz support enabled + if (is800KHz) { +#endif + + // In the 12 MHz case, an optimized 800 KHz datastream (no dead time + // between bytes) requires a PORT-specific loop similar to the 8 MHz + // code (but a little more relaxed in this case). + + // 15 instruction clocks per bit: HHHHxxxxxxLLLLL + // OUT instructions: ^ ^ ^ (T=0,4,10) + + volatile uint8_t next; + + // PORTD OUTPUT ---------------------------------------------------- + +#if defined(PORTD) +#if defined(PORTB) || defined(PORTC) || defined(PORTF) + if (port == &PORTD) { +#endif // defined(PORTB/C/F) + + hi = PORTD | pinMask; + lo = PORTD & ~pinMask; + next = lo; + 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) + } else // other PORT(s) +#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) + + hi = PORTB | pinMask; + lo = PORTB & ~pinMask; + next = lo; + 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) + } +#endif +#if defined(PORTC) || defined(PORTF) + else +#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) + + hi = PORTC | pinMask; + lo = PORTC & ~pinMask; + next = lo; + 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) + } +#endif // defined(PORTD/B/F) +#if defined(PORTF) + else +#endif +#endif // defined(PORTC) + + // PORTF OUTPUT ---------------------------------------------------- + +#if defined(PORTF) +#if defined(PORTD) || defined(PORTB) || defined(PORTC) + if (port == &PORTF) { +#endif // defined(PORTD/B/C) + + hi = PORTF | pinMask; + lo = PORTF & ~pinMask; + next = lo; + 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) + } +#endif // defined(PORTD/B/C) +#endif // defined(PORTF) + +#if defined(NEO_KHZ400) + } else { // 400 KHz + + // 30 instruction clocks per bit: HHHHHHxxxxxxxxxLLLLLLLLLLLLLLL + // ST instructions: ^ ^ ^ (T=0,6,15) + + volatile uint8_t next, bit; + + 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)); + } +#endif // NEO_KHZ400 + +// 16 MHz(ish) AVR -------------------------------------------------------- +#elif (F_CPU >= 15400000UL) && (F_CPU <= 19000000L) + +#if defined(NEO_KHZ400) // 800 KHz check needed only if 400 KHz support enabled + if (is800KHz) { +#endif + + // WS2811 and WS2812 have different hi/lo duty cycles; this is + // similar but NOT an exact copy of the prior 400-on-8 code. + + // 20 inst. clocks per bit: HHHHHxxxxxxxxLLLLLLL + // ST instructions: ^ ^ ^ (T=0,5,13) + + volatile uint8_t next, bit; + + 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)); + +#if defined(NEO_KHZ400) + } else { // 400 KHz + + // The 400 KHz clock on 16 MHz MCU is the most 'relaxed' version. + + // 40 inst. clocks per bit: HHHHHHHHxxxxxxxxxxxxLLLLLLLLLLLLLLLLLLLL + // ST instructions: ^ ^ ^ (T=0,8,20) + + volatile uint8_t next, bit; + + 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)); + } +#endif // NEO_KHZ400 + +#else +#error "CPU SPEED NOT SUPPORTED" +#endif // end F_CPU ifdefs on __AVR__ + + // END AVR ---------------------------------------------------------------- + +#elif defined(__arm__) + + // ARM MCUs -- Teensy 3.0, 3.1, LC, Arduino Due, RP2040 ------------------- + +#if defined(ARDUINO_ARCH_RP2040) + // Use PIO + rp2040Show(pin, pixels, numBytes, is800KHz); + +#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; + + 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) { +#endif + cyc = ARM_DWT_CYCCNT + CYCLES_800; + while (p < end) { + pix = *p++; + 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) + ; + } else { + while (ARM_DWT_CYCCNT - cyc < CYCLES_800_T0H) + ; + } + *clr = 1; + } + } + while (ARM_DWT_CYCCNT - cyc < CYCLES_800) + ; +#if defined(NEO_KHZ400) + } else { // 400 kHz bitstream + cyc = ARM_DWT_CYCCNT + CYCLES_400; + while (p < end) { + pix = *p++; + 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) + ; + } else { + while (ARM_DWT_CYCCNT - cyc < CYCLES_400_T0H) + ; + } + *clr = 1; + } + } + 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; + ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA; + +#if defined(NEO_KHZ400) // 800 KHz check needed only if 400 KHz support enabled + if (is800KHz) { +#endif + cyc = ARM_DWT_CYCCNT + CYCLES_800; + while (p < end) { + pix = *p++; + 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) + ; + } else { + while (ARM_DWT_CYCCNT - cyc < CYCLES_800_T0H) + ; + } + *clr = msk; + } + } + while (ARM_DWT_CYCCNT - cyc < CYCLES_800) + ; +#if defined(NEO_KHZ400) + } else { // 400 kHz bitstream + cyc = ARM_DWT_CYCCNT + CYCLES_400; + while (p < end) { + pix = *p++; + 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) + ; + } else { + while (ARM_DWT_CYCCNT - cyc < CYCLES_400_T0H) + ; + } + *clr = msk; + } + } + 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); + 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)); +#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 ------------------------- + +#elif defined(NRF52) || defined(NRF52_SERIES) +// [[[Begin of the Neopixel NRF52 EasyDMA implementation +// by the Hackerspace San Salvador]]] +// This technique uses the PWM peripheral on the NRF52. The PWM uses the +// EasyDMA feature included on the chip. This technique loads the duty +// cycle configuration for each cycle when the PWM is enabled. For this +// to work we need to store a 16 bit configuration for each bit of the +// RGB(W) values in the pixel buffer. +// Comparator values for the PWM were hand picked and are guaranteed to +// be 100% organic to preserve freshness and high accuracy. Current +// parameters are: +// * PWM Clock: 16Mhz +// * Minimum step time: 62.5ns +// * Time for zero in high (T0H): 0.31ms +// * Time for one in high (T1H): 0.75ms +// * Cycle time: 1.25us +// * Frequency: 800Khz +// For 400Khz we just double the calculated times. +// ---------- BEGIN Constants for the EasyDMA implementation ----------- +// The PWM starts the duty cycle in LOW. To start with HIGH we +// need to set the 15th bit on each register. + +// WS2812 (rev A) timing is 0.35 and 0.7us +//#define MAGIC_T0H 5UL | (0x8000) // 0.3125us +//#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 + +// 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 + +// For 400Khz, we double value of CTOPVAL +#define CTOPVAL 20UL // 1.25us +#define CTOPVAL_400KHz 40UL // 2.5us + +// ---------- END Constants for the EasyDMA implementation ------------- +// +// If there is no device available an alternative cycle-counter +// implementation is tried. +// The nRF52 runs with a fixed clock of 64Mhz. The alternative +// implementation is the same as the one used for the Teensy 3.0/1/2 but +// with the Nordic SDK HAL & registers syntax. +// 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_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 + // generate a pattern and store it on the memory. The actual + // memory used in bytes corresponds to the following formula: + // totalMem = numBytes*8*2+(2*2) + // The two additional bytes at the end are needed to reset the + // sequence. + // + // 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; + + 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 +#if defined(NRF_PWM3) + , + NRF_PWM3 +#endif + }; + + 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 = 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 + } + + // Use the identified device to choose the implementation + // If a PWM device is available use DMA + if ((pixels_pattern != NULL) && (pwm != NULL)) { + uint16_t pos = 0; // bit position + + 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 + { + pixels_pattern[pos] = (pix & mask) ? MAGIC_T1H : MAGIC_T0H; + } + + pos++; + } + } + + // Zero padding to indicate the end of que sequence + pixels_pattern[pos++] = 0 | (0x8000); // Seq end + pixels_pattern[pos++] = 0 | (0x8000); // Seq end + + // Set the wave mode to count UP + 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); + + // 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) { + pwm->COUNTERTOP = (CTOPVAL_400KHz << PWM_COUNTERTOP_COUNTERTOP_Pos); + } else +#endif + { + pwm->COUNTERTOP = (CTOPVAL << PWM_COUNTERTOP_COUNTERTOP_Pos); + } + + // Disable loops, we want the sequence to repeat only once + pwm->LOOP = (PWM_LOOP_CNT_Disabled << PWM_LOOP_CNT_Pos); + + // On the "Common" setting the PWM uses the same pattern for the + // for supported sequences. The pattern is stored on half-word + // of 16bits + pwm->DECODER = (PWM_DECODER_LOAD_Common << PWM_DECODER_LOAD_Pos) | + (PWM_DECODER_MODE_RefreshCount << PWM_DECODER_MODE_Pos); + + // Pointer to the memory storing the patter + 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; + + // The following settings are ignored with the current config. + pwm->SEQ[0].REFRESH = 0; + pwm->SEQ[0].ENDDELAY = 0; + + // The Neopixel implementation is a blocking algorithm. DMA + // allows for non-blocking operation. To "simulate" a blocking + // 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<PSEL.OUT[0] = g_APinDescription[pin].name; +#else + pwm->PSEL.OUT[0] = g_ADigitalPinMap[pin]; +#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->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) + yield(); +#endif + } + + // Before leave we clear the flag for the event. + pwm->EVENTS_SEQEND[0] = 0; + + // We need to disable the device and disconnect + // all the outputs before leave or the device will not + // be selected on the next call. + // TODO: Check if disabling the device causes performance issues. + pwm->ENABLE = 0; + + 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 + // --------------------------------------------------------------------- + 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_T1H = CYCLES_800_T1H; + uint32_t CYCLES_X00_T0H = CYCLES_800_T0H; + +#if defined(NEO_KHZ400) + if (!is800KHz) { + CYCLES_X00 = CYCLES_400; + CYCLES_X00_T1H = CYCLES_400_T1H; + CYCLES_X00_T0H = CYCLES_400_T0H; + } +#endif + + // Enable DWT in debug core + CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; + DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; + + // Tries to re-send the frame if is interrupted by the SoftDevice. + while (1) { + uint8_t *p = pixels; + + uint32_t cycStart = DWT->CYCCNT; + uint32_t cyc = 0; + + 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; + + nrf_port->OUTSET |= pinMask; + + if (pix & mask) { + while (DWT->CYCCNT - cyc < CYCLES_X00_T1H) + ; + } else { + while (DWT->CYCCNT - cyc < CYCLES_X00_T0H) + ; + } + + nrf_port->OUTCLR |= pinMask; + } + } + 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))) { + break; + } + + // re-send need 300us delay + delayMicroseconds(300); + } + +// Enable interrupts again +#if defined(ARDUINO_NRF52_ADAFRUIT) + taskEXIT_CRITICAL(); +#elif defined(NRF52_DISABLE_INT) + __enable_irq(); +#endif +#endif + } + // END of NRF52 implementation + +#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; + + 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) { +#endif + for (;;) { + *set = pinMask; + asm("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;"); + *clr = pinMask; + } else { + *clr = pinMask; + asm("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;"); + } else { + if (ptr >= end) + break; + p = *ptr++; + bitMask = 0x80; + } + } +#if defined(NEO_KHZ400) + } else { // 400 KHz bitstream + for (;;) { + *set = pinMask; + asm("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;"); + *clr = pinMask; + } else { + *clr = pinMask; + 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;"); + } + 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;"); + if (bitMask >>= 1) { + asm("nop; nop; nop; nop; nop; nop; nop;"); + } else { + if (ptr >= end) + break; + p = *ptr++; + bitMask = 0x80; + } + } + } +#endif + +//---- +#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; + 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;"); + 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); + + // SAMD51 overclock-compatible timing is only a mild abomination. + // It uses SysTick for a consistent clock reference regardless of + // optimization / cache settings. That's the good news. The bad news, + // since SysTick->VAL is a volatile type it's slow to access...and then, + // with the SysTick interval that Arduino sets up (1 ms), this would + // require a subtract and MOD operation for gauging elapsed time, and + // all taken in combination that lacks adequate temporal resolution + // for NeoPixel timing. So a kind of horrible thing is done here... + // since interrupts are turned off anyway and it's generally accepted + // by now that we're gonna lose track of time in the NeoPixel lib, + // the SysTick timer is reconfigured for a period matching the NeoPixel + // bit timing (either 800 or 400 KHz) and we watch SysTick->VAL very + // closely (just a threshold, no subtract or MOD or anything) and that + // 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; + +#if defined(NEO_KHZ400) // 800 KHz check needed only if 400 KHz support enabled + 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 +#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 + } +#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 + + 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 rollover to 'top' + } + + SysTick->LOAD = saveLoad; // Restore SysTick rollover to 1 ms + SysTick->VAL = saveVal; // Restore SysTick value + +#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; + + 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) { +#endif + for (;;) { + if (p & bitMask) { // ONE + // High 800ns + *set = pinMask; + 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;"); + // Low 450ns + *clr = pinMask; + 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;"); + } else { // ZERO + // High 400ns + *set = pinMask; + 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;"); + // Low 850ns + *clr = pinMask; + 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;"); + } + if (bitMask >>= 1) { + // Move on to the next pixel + asm("nop;"); + } else { + if (ptr >= end) + break; + p = *ptr++; + bitMask = 0x80; + } + } +#if defined(NEO_KHZ400) + } else { // 400 KHz bitstream + // ToDo! + } +#endif + +#elif defined(TARGET_LPC1768) + 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) { +#endif + for (;;) { + if (p & bitMask) { + // data ONE high + // min: 550 typ: 700 max: 5,500 + gpio_set(pin); + time::delay_ns(550); + // min: 450 typ: 600 max: 5,000 + gpio_clear(pin); + time::delay_ns(450); + } else { + // data ZERO high + // min: 200 typ: 350 max: 500 + gpio_set(pin); + time::delay_ns(200); + // data low + // min: 450 typ: 600 max: 5,000 + gpio_clear(pin); + time::delay_ns(450); + } + if (bitMask >>= 1) { + // Move on to the next pixel + asm("nop;"); + } else { + if (ptr >= end) + break; + p = *ptr++; + bitMask = 0x80; + } + } +#if defined(NEO_KHZ400) + } else { // 400 KHz bitstream + // ToDo! + } +#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; + uint32_t saveLoad = SysTick->LOAD, saveVal = SysTick->VAL; +#if defined(NEO_KHZ400) // 800 KHz check needed only if 400 KHz support enabled + 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 + SysTick->LOAD = top - 1; // Config SysTick for NeoPixel bit freq + SysTick->VAL = 0; // Set to start value + for (;;) { + LL_GPIO_SetOutputPin(gpioPort, gpioPin); + cyc = (pix & mask) ? t1 : t0; + while (SysTick->VAL > cyc) + ; + LL_GPIO_ResetOutputPin(gpioPort, gpioPin); + if (!(mask >>= 1)) { + if (p >= end) + break; + pix = *p++; + mask = 0x80; + } + 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 + SysTick->LOAD = top - 1; // Config SysTick for NeoPixel bit freq + SysTick->VAL = 0; // Set to start value + for (;;) { + LL_GPIO_SetOutputPin(gpioPort, gpioPin); + cyc = (pix & mask) ? t1 : t0; + while (SysTick->VAL > cyc) + ; + LL_GPIO_ResetOutputPin(gpioPort, gpioPin); + if (!(mask >>= 1)) { + if (p >= end) + break; + pix = *p++; + mask = 0x80; + } + 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 + + 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 + + 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)); + +#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; + volatile WoReg *portSet, *portClear, *timeValue, *timeReset; + 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_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'. + timeReset = &(TC1->TC_CHANNEL[0].TC_CCR); + 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) { +#endif + 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; + period = PERIOD_400; + } +#endif + + 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) + ; + *portClear = pinMask; + 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 + TC_Stop(TC1, 0); + +#endif // end Due + + // END ARM ---------------------------------------------------------------- + +#elif defined(ESP8266) || defined(ESP32) + + // ESP8266 ---------------------------------------------------------------- + + // ESP8266 show() is external to enforce ICACHE_RAM_ATTR execution + espShow(pin, pixels, numBytes, is800KHz); + +#elif defined(KENDRYTE_K210) + + k210Show(pin, pixels, numBytes, is800KHz); + +#elif defined(__ARDUINO_ARC__) + + // 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 uint8_t *p = pixels; + 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 + 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); + + loop += 1; // include first, special iteration + 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 + } + // ~340ns HIGH + 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 + + if (bitCounter >= 8) { + bitCounter = 0; + currByte = (uint32_t)(*++p); + } + + currBit = 0x80 & currByte; + first = 0; + } + } 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); + + loop += 1; // include first, special iteration + 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(); + } + // ~310ns HIGH + NOPx7 + + // 850ns LOW; per spec, max allowed low here is 5000ns */ + MMIO_REG_VAL(reg) = reg_bit_low; + NOPx7 NOPx7 + + if (bitCounter >= 8) { + bitCounter = 0; + currByte = (uint32_t)(*++p); + } + + currBit = 0x80 & currByte; + first = 0; + } + } + +#else +#error Architecture not supported +#endif + + // END ARCHITECTURE SELECT ------------------------------------------------ + +#if !(defined(NRF52) || defined(NRF52_SERIES) || defined(ESP32)) + interrupts(); +#endif + + endTime = micros(); // Save EOD time for latch on next call +} + +/*! + @brief Set/change the NeoPixel output pin number. Previous pin, + 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(int16_t p) { + if (begun && (pin >= 0)) + pinMode(pin, INPUT); // Disable existing out pin + pin = p; + if (begun) { + pinMode(p, OUTPUT); + digitalWrite(p, LOW); + } +#if defined(__AVR__) + port = portOutputRegister(digitalPinToPort(p)); + pinMask = digitalPinToBitMask(p); +#endif +#if defined(ARDUINO_ARCH_STM32) || defined(ARDUINO_ARCH_ARDUINO_CORE_STM32) + gpioPort = digitalPinToPort(p); + gpioPin = STM_LL_GPIO_PIN(digitalPinToPinName(p)); +#endif +} + +/*! + @brief Set a pixel's color using separate red, green and blue + components. If using RGBW pixels, white will be set to 0. + @param n Pixel index, starting from 0. + @param r Red brightness, 0 = minimum (off), 255 = maximum. + @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) { + + 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 + } + p[rOffset] = r; // R,G,B always stored + p[gOffset] = g; + p[bOffset] = b; + } +} + +/*! + @brief Set a pixel's color using separate red, green, blue and white + components (for RGBW NeoPixels only). + @param n Pixel index, starting from 0. + @param r Red brightness, 0 = minimum (off), 255 = maximum. + @param g Green brightness, 0 = minimum (off), 255 = maximum. + @param b Blue brightness, 0 = minimum (off), 255 = maximum. + @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) { + + 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 + } + p[rOffset] = r; // Store R,G,B + p[gOffset] = g; + p[bOffset] = b; + } +} + +/*! + @brief Set a pixel's color using a 32-bit 'packed' RGB or RGBW value. + @param n Pixel index, starting from 0. + @param c 32-bit color value. Most significant byte is white (for RGBW + pixels) or ignored (for RGB pixels), next is red, then green, + 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() + r = (r * brightness) >> 8; + g = (g * brightness) >> 8; + b = (b * brightness) >> 8; + } + if (wOffset == rOffset) { + p = &pixels[n * 3]; + } else { + p = &pixels[n * 4]; + uint8_t w = (uint8_t)(c >> 24); + p[wOffset] = brightness ? ((w * brightness) >> 8) : w; + } + p[rOffset] = r; + p[gOffset] = g; + p[bOffset] = b; + } +} + +/*! + @brief Fill all or part of the NeoPixel strip with a color. + @param c 32-bit color value. Most significant byte is white (for + RGBW pixels) or ignored (for RGB pixels), next is red, + then green, and least significant byte is blue. If all + arguments are unspecified, this will be 0 (off). + @param first Index of first pixel to fill, starting from 0. Must be + in-bounds, no clipping is performed. 0 if unspecified. + @param count Number of pixels to fill, as a positive value. Passing + 0 or leaving unspecified will fill to end of strip. +*/ +void Adafruit_NeoPixel::fill(uint32_t c, uint16_t first, uint16_t count) { + uint16_t i, end; + + 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) { + // 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; + } + + for (i = first; i < end; i++) { + this->setPixelColor(i, c); + } +} + +/*! + @brief Convert hue, saturation and value into a packed 32-bit RGB color + that can be passed to setPixelColor() or other RGB-compatible + functions. + @param hue An unsigned 16-bit value, 0 to 65535, representing one full + loop of the color wheel, which allows 16-bit hues to "roll + over" while still doing the expected thing (and allowing + more precision than the wheel() function that was common to + prior NeoPixel examples). + @param sat Saturation, 8-bit value, 0 (min or pure grayscale) to 255 + (max or pure hue). Default of 255 if unspecified. + @param val Value (brightness), 8-bit value, 0 (min / black / off) to + 255 (max or full brightness). Default of 255 if unspecified. + @return Packed 32-bit RGB with the most significant byte set to 0 -- the + white element of WRGB pixels is NOT utilized. Result is linearly + but not perceptually correct, so you may want to pass the result + through the gamma32() function (or your own gamma-correction + operation) else colors may appear washed out. This is not done + automatically by this function because coders may desire a more + refined gamma-correction function than the simplified + one-size-fits-all operation of gamma32(). Diffusing the LEDs also + really seems to help when using low-saturation colors. +*/ +uint32_t Adafruit_NeoPixel::ColorHSV(uint16_t hue, uint8_t sat, uint8_t val) { + + uint8_t r, g, b; + + // Remap 0-65535 to 0-1529. Pure red is CENTERED on the 64K rollover; + // 0 is not the start of pure red, but the midpoint...a few values above + // zero and a few below 65536 all yield pure red (similarly, 32768 is the + // midpoint, not start, of pure cyan). The 8-bit RGB hexcone (256 values + // each for red, green, blue) really only allows for 1530 distinct hues + // (not 1536, more on that below), but the full unsigned 16-bit type was + // chosen for hue so that one's code can easily handle a contiguous color + // wheel by allowing hue to roll over in either direction. + hue = (hue * 1530L + 32768) / 65536; + // Because red is centered on the rollover point (the +32768 above, + // essentially a fixed-point +0.5), the above actually yields 0 to 1530, + // where 0 and 1530 would yield the same thing. Rather than apply a + // costly modulo operator, 1530 is handled as a special case below. + + // So you'd think that the color "hexcone" (the thing that ramps from + // pure red, to pure yellow, to pure green and so forth back to red, + // yielding six slices), and with each color component having 256 + // possible values (0-255), might have 1536 possible items (6*256), + // but in reality there's 1530. This is because the last element in + // each 256-element slice is equal to the first element of the next + // slice, and keeping those in there this would create small + // discontinuities in the color wheel. So the last element of each + // slice is dropped...we regard only elements 0-254, with item 255 + // being picked up as element 0 of the next slice. Like this: + // Red to not-quite-pure-yellow is: 255, 0, 0 to 255, 254, 0 + // Pure yellow to not-quite-pure-green is: 255, 255, 0 to 1, 255, 0 + // Pure green to not-quite-pure-cyan is: 0, 255, 0 to 0, 255, 254 + // and so forth. Hence, 1530 distinct hues (0 to 1529), and hence why + // 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 + b = 0; + 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 = 255; + } + } else if (hue < 1020) { // Green to Blue-1 + r = 0; + 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 = 255; + } + } else if (hue < 1530) { // Blue to Red-1 + g = 0; + if (hue < 1275) { // Blue to Magenta-1 + r = hue - 1020; // r = 0 to 254 + b = 255; + } else { // Magenta to Red-1 + r = 255; + b = 1530 - hue; // b = 255 to 1 + } + } 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 + return ((((((r * s1) >> 8) + s2) * v1) & 0xff00) << 8) | + (((((g * s1) >> 8) + s2) * v1) & 0xff00) | + (((((b * s1) >> 8) + s2) * v1) >> 8); +} + +/*! + @brief Query the color of a previously-set pixel. + @param n Index of pixel to read (0 = first). + @return 'Packed' 32-bit RGB or WRGB value. Most significant byte is white + (for RGBW pixels) or 0 (for RGB pixels), next is red, then green, + and least significant byte is blue. + @note If the strip brightness has been changed from the default value + of 255, the color read from a pixel may not exactly match what + was previously written with one of the setPixelColor() functions. + 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. + + uint8_t *p; + + if (wOffset == rOffset) { // Is RGB-type device + p = &pixels[n * 3]; + 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); + } 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]; + } + } else { // Is RGBW-type device + p = &pixels[n * 4]; + 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); + } else { // Return raw color + 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 + refresh the LEDs at this level. + @param b Brightness setting, 0=minimum (off), 255=brightest. + @note This was intended for one-time use in one's setup() function, + not as an animation effect in itself. Because of the way this + library "pre-multiplies" LED colors in RAM, changing the + brightness is often a "lossy" operation -- what you write to + pixels isn't necessary the same as what you'll read back. + Repeated brightness changes using this function exacerbate the + problem. Smart programs therefore treat the strip as a + write-only resource, maintaining their own state to render each + frame of an animation, not relying on read-modify-write. +*/ +void Adafruit_NeoPixel::setBrightness(uint8_t b) { + // Stored brightness value is different than what's passed. + // This simplifies the actual scaling math later, allowing a fast + // 8x8-bit multiply and taking the MSB. 'brightness' is a uint8_t, + // adding 1 here may (intentionally) roll over...so 0 = max brightness + // (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 + // 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 + // aren't enough free cycles to perform this scaling on the fly as data + // is issued. So we make a pass through the existing color data in RAM + // and scale it (subsequent graphics commands also work at this + // brightness level). If there's a significant step up in brightness, + // 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 + 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; + *ptr++ = (c * scale) >> 8; + } + brightness = newBrightness; + } +} + +/*! + @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; } + +/*! + @brief Fill the whole NeoPixel strip with 0 / black / off. +*/ +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. +uint32_t Adafruit_NeoPixel::gamma32(uint32_t x) { + uint8_t *y = (uint8_t *)&x; + // All four bytes of a 32-bit value are filtered even if RGB (not WRGB), + // to avoid a bunch of shifting and masking that would be necessary for + // properly handling different endianisms (and each byte is a fairly + // trivial operation, so it might not even be wasting cycles vs a check + // and branch for the RGB case). In theory this might cause trouble *if* + // 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]); + 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. + * + */ + +#ifndef ADAFRUIT_NEOPIXEL_H +#define ADAFRUIT_NEOPIXEL_H + +#ifdef ARDUINO +#if (ARDUINO >= 100) +#include +#else +#include +#include +#endif + +#ifdef USE_TINYUSB // For Serial when selecting TinyUSB +#include +#endif + +#endif + +#ifdef TARGET_LPC1768 +#include +#endif + +#if defined(ARDUINO_ARCH_RP2040) +#include +#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 +// device types, manufacturers and even different revisions of the same +// item. The third parameter to the Adafruit_NeoPixel constructor encodes +// the per-pixel byte offsets of the red, green and blue primaries (plus +// white, if present) in the data stream -- the following #defines provide +// an easier-to-use named version for each permutation. e.g. NEO_GRB +// indicates a NeoPixel-compatible device expecting three bytes per pixel, +// with the first byte transmitted containing the green value, second +// containing red and third containing blue. The in-memory representation +// of a chain of NeoPixels is the same as the data-stream order; no +// re-ordering of bytes is required when issuing data to the chain. +// Most of these values won't exist in real-world devices, but it's done +// this way so we're ready for it (also, if using the WS2811 driver IC, +// one might have their pixels set up in any weird permutation). + +// Bits 5,4 of this value are the offset (0-3) from the first byte of a +// pixel to the location of the red color byte. Bits 3,2 are the green +// offset and 1,0 are the blue offset. If it is an RGBW-type device +// (supporting a white primary in addition to R,G,B), bits 7,6 are the +// offset to the white byte...otherwise, bits 7,6 are set to the same value +// as 5,4 (red) to indicate an RGB (not RGBW) device. +// i.e. binary representation: +// 0bWWRRGGBB for RGBW devices +// 0bRRRRGGBB for RGB + +// 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 + +// 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_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_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 +// the default if unspecified. Because flash space is very limited on ATtiny +// devices (e.g. Trinket, Gemma), v1 NeoPixels aren't handled by default on +// those chips, though it can be enabled by removing the ifndef/endif below, +// but code will be bigger. Conversely, can disable the NEO_KHZ400 line on +// other MCUs to remove v1 support and save a little space. + +#define NEO_KHZ800 0x0000 ///< 800 KHz data transmission +#ifndef __AVR_ATtiny85__ +#define NEO_KHZ400 0x0100 ///< 400 KHz data transmission +#endif + +// If 400 KHz support is enabled, the third parameter to the constructor +// requires a 16-bit value (in order to select 400 vs 800 KHz speed). +// If only 800 KHz is enabled (as is default on ATtiny), an 8-bit value +// is sufficient to encode pixel color order, saving some space. + +#ifdef NEO_KHZ400 +typedef uint16_t neoPixelType; ///< 3rd arg to Adafruit_NeoPixel constructor +#else +typedef uint8_t neoPixelType; ///< 3rd arg to Adafruit_NeoPixel constructor +#endif + +// These two tables are declared outside the Adafruit_NeoPixel class +// because some boards may require oldschool compilers that don't +// handle the C++11 constexpr keyword. + +/* A PROGMEM (flash mem) table containing 8-bit unsigned sine wave (0-255). + Copy & paste this snippet into a Python REPL to regenerate: +import math +for x in range(256): + print("{:3},".format(int((math.sin(x/128.0*math.pi)+1.0)*127.5+0.5))), + 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}; + +/* Similar to above, but for an 8-bit gamma-correction table. + Copy & paste this snippet into a Python REPL to regenerate: +import math +gamma=2.6 +for x in range(256): + print("{:3},".format(int(math.pow((x)/255.0,gamma)*255.0+0.5))), + 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}; + +/*! + @brief Class that stores state and functions for interacting with + Adafruit NeoPixels and compatible devices. +*/ +class Adafruit_NeoPixel { + +public: + // Constructor: number of LEDs, pin number, LED type + 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(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 + require a short quiet time (about 300 microseconds) after the + last bit is received before the data 'latches' and new data can + start being received. Usually one's sketch is implicitly using + this time to generate a new frame of animation...but if it + finishes very quickly, this function could be used to see if + there's some idle time available for some low-priority + concurrent task. + @return 1 or true if show() will start sending immediately, 0 or false + if show() would block (meaning some idle time is available). + */ + bool canShow(void) { + // 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 (now - endTime) >= 300L; + } + /*! + @brief Get a pointer directly to the NeoPixel data buffer in RAM. + Pixel data is stored in a device-native format (a la the NEO_* + constants) and is not translated here. Applications that access + this buffer will need to be aware of the specific data format + and handle colors appropriately. + @return Pointer to NeoPixel buffer (uint8_t* array). + @note This is for high-performance applications where calling + setPixelColor() on every single pixel would be too slow (e.g. + POV or light-painting projects). There is no bounds checking + on the array, creating tremendous potential for mayhem if one + 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; + /*! + @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; }; + /*! + @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; + /*! + @brief An 8-bit integer sine wave function, not directly compatible + with standard trigonometric units like radians or degrees. + @param x Input angle, 0-255; 256 would loop back to zero, completing + the circle (equivalent to 360 degrees or 2 pi radians). + One can therefore use an unsigned 8-bit variable and simply + add or subtract, allowing it to overflow/underflow and it + still does the expected contiguous thing. + @return Sine result, 0 to 255, or -128 to +127 if type-converted to + 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) { + return pgm_read_byte(&_NeoPixelSineTable[x]); // 0-255 in, 0-255 out + } + /*! + @brief An 8-bit gamma-correction function for basic pixel brightness + adjustment. Makes color transitions appear more perceptially + correct. + @param x Input brightness, 0 (minimum or off/black) to 255 (maximum). + @return Gamma-adjusted brightness, can then be passed to one of the + setPixelColor() functions. This uses a fixed gamma correction + exponent of 2.6, which seems reasonably okay for average + 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) { + return pgm_read_byte(&_NeoPixelGammaTable[x]); // 0-255 in, 0-255 out + } + /*! + @brief Convert separate red, green and blue values into a single + "packed" 32-bit RGB color. + @param r Red brightness, 0 to 255. + @param g Green brightness, 0 to 255. + @param b Blue brightness, 0 to 255. + @return 32-bit packed RGB value, which can then be assigned to a + variable for later use or passed to the setPixelColor() + 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; + } + /*! + @brief Convert separate red, green, blue and white values into a + single "packed" 32-bit WRGB color. + @param r Red brightness, 0 to 255. + @param g Green brightness, 0 to 255. + @param b Blue brightness, 0 to 255. + @param w White brightness, 0 to 255. + @return 32-bit packed WRGB value, which can then be assigned to a + variable for later use or passed to the setPixelColor() + 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 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 + correct. + @param x 32-bit packed RGB or WRGB color. + @return Gamma-adjusted packed color, can then be passed in one of the + setPixelColor() functions. Like gamma8(), this uses a fixed + gamma correction exponent of 2.6, which seems reasonably okay + for average NeoPixels in average tasks. If you need finer + control you'll need to provide your own gamma-correction + function instead. + */ + static uint32_t gamma32(uint32_t x); + + void rainbow(uint16_t first_hue = 0, int8_t reps = 1, + uint8_t saturation = 255, uint8_t brightness = 255, + bool gammify = true); + +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 + +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 +#endif +#if defined(ARDUINO_ARCH_STM32) || defined(ARDUINO_ARCH_ARDUINO_CORE_STM32) + 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 +}; + +#endif // ADAFRUIT_NEOPIXEL_H diff --git a/libraries/Adafruit_NeoPixel/README.md b/libraries/Adafruit_NeoPixel/README.md new file mode 100644 index 0000000..eff1337 --- /dev/null +++ b/libraries/Adafruit_NeoPixel/README.md @@ -0,0 +1,157 @@ +# Adafruit NeoPixel Library [![Build Status](https://github.com/adafruit/Adafruit_NeoPixel/workflows/Arduino%20Library%20CI/badge.svg)](https://github.com/adafruit/Adafruit_NeoPixel/actions)[![Documentation](https://github.com/adafruit/ci-arduino/blob/master/assets/doxygen_badge.svg)](http://adafruit.github.io/Adafruit_NeoPixel/html/index.html) + +Arduino library for controlling single-wire-based LED pixels and strip such as the [Adafruit 60 LED/meter Digital LED strip][strip], the [Adafruit FLORA RGB Smart Pixel][flora], the [Adafruit Breadboard-friendly RGB Smart Pixel][pixel], the [Adafruit NeoPixel Stick][stick], and the [Adafruit NeoPixel Shield][shield]. + +After downloading, rename folder to 'Adafruit_NeoPixel' and install in Arduino Libraries folder. Restart Arduino IDE, then open File->Sketchbook->Library->Adafruit_NeoPixel->strandtest sketch. + +Compatibility notes: Port A is not supported on any AVR processors at this time + +[flora]: http://adafruit.com/products/1060 +[strip]: http://adafruit.com/products/1138 +[pixel]: http://adafruit.com/products/1312 +[stick]: http://adafruit.com/products/1426 +[shield]: http://adafruit.com/products/1430 + +--- + +## Installation + +### First Method + +![image](https://user-images.githubusercontent.com/36513474/68967967-3e37f480-0803-11ea-91d9-601848c306ee.png) + +1. In the Arduino IDE, navigate to Sketch > Include Library > Manage Libraries +1. Then the Library Manager will open and you will find a list of libraries that are already installed or ready for installation. +1. Then search for Neopixel strip using the search bar. +1. Click on the text area and then select the specific version and install it. + +### Second Method + +1. Navigate to the [Releases page](https://github.com/adafruit/Adafruit_NeoPixel/releases). +1. Download the latest release. +1. Extract the zip file +1. In the Arduino IDE, navigate to Sketch > Include Library > Add .ZIP Library + +## Features + +- ### Simple to use + + Controlling NeoPixels “from scratch” is quite a challenge, so we provide a library letting you focus on the fun and interesting bits. + +- ### Give back + + The library is free; you don’t have to pay for anything. Adafruit invests time and resources providing this open source code, please support Adafruit and open-source hardware by purchasing products from Adafruit! + +- ### Supported Chipsets + + We have included code for the following chips - sometimes these break for exciting reasons that we can't control in which case please open an issue! + + - AVR ATmega and ATtiny (any 8-bit) - 8 MHz, 12 MHz and 16 MHz + - Teensy 3.x and LC + - Arduino Due + - Arduino 101 + - ATSAMD21 (Arduino Zero/M0 and other SAMD21 boards) @ 48 MHz + - ATSAMD51 @ 120 MHz + - Adafruit STM32 Feather @ 120 MHz + - 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! + +- ### GNU Lesser General Public License + + Adafruit_NeoPixel is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + +## Functions + +- begin() +- updateLength() +- updateType() +- show() +- delay_ns() +- setPin() +- setPixelColor() +- fill() +- ColorHSV() +- getPixelColor() +- setBrightness() +- getBrightness() +- clear() +- gamma32() + +## Examples + +There are many examples implemented in this library. One of the examples is below. You can find other examples [here](https://github.com/adafruit/Adafruit_NeoPixel/tree/master/examples) + +### Simple + +```Cpp +#include +#ifdef __AVR__ + #include +#endif +#define PIN 6 +#define NUMPIXELS 16 + +Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800); +#define DELAYVAL 500 + +void setup() { +#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000) + clock_prescale_set(clock_div_1); +#endif + + pixels.begin(); +} + +void loop() { + pixels.clear(); + + for(int i=0; i +#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) + +#define WS2812_T0H_NS (400) +#define WS2812_T0L_NS (850) +#define WS2812_T1H_NS (800) +#define WS2812_T1L_NS (450) + +#define WS2811_T0H_NS (500) +#define WS2811_T0L_NS (2000) +#define WS2811_T1H_NS (1200) +#define WS2811_T1L_NS (1300) + +static uint32_t t0h_ticks = 0; +static uint32_t t1h_ticks = 0; +static uint32_t t0l_ticks = 0; +static uint32_t t1l_ticks = 0; + +// Limit the number of RMT channels available for the Neopixels. Defaults to all +// channels (8 on ESP32, 4 on ESP32-S2 and S3). Redefining this value will free +// any channels with a higher number for other uses, such as IR send-and-recieve +// libraries. Redefine as 1 to restrict Neopixels to only a single channel. +#define ADAFRUIT_RMT_CHANNEL_MAX RMT_CHANNEL_MAX + +#define RMT_LL_HW_BASE (&RMT) + +bool rmt_reserved_channels[ADAFRUIT_RMT_CHANNEL_MAX]; + +static void IRAM_ATTR ws2812_rmt_adapter(const void *src, rmt_item32_t *dest, size_t src_size, + size_t wanted_num, size_t *translated_size, size_t *item_num) +{ + if (src == NULL || dest == NULL) { + *translated_size = 0; + *item_num = 0; + return; + } + const rmt_item32_t bit0 = {{{ t0h_ticks, 1, t0l_ticks, 0 }}}; //Logical 0 + const rmt_item32_t bit1 = {{{ t1h_ticks, 1, t1l_ticks, 0 }}}; //Logical 1 + size_t size = 0; + size_t num = 0; + uint8_t *psrc = (uint8_t *)src; + rmt_item32_t *pdest = dest; + while (size < src_size && num < wanted_num) { + for (int i = 0; i < 8; i++) { + // MSB first + if (*psrc & (1 << (7 - i))) { + pdest->val = bit1.val; + } else { + pdest->val = bit0.val; + } + num++; + pdest++; + } + size++; + psrc++; + } + *translated_size = size; + *item_num = num; +} + +void espShow(uint8_t pin, uint8_t *pixels, uint32_t numBytes, boolean is800KHz) { + // Reserve channel + rmt_channel_t channel = ADAFRUIT_RMT_CHANNEL_MAX; + for (size_t i = 0; i < ADAFRUIT_RMT_CHANNEL_MAX; i++) { + if (!rmt_reserved_channels[i]) { + rmt_reserved_channels[i] = true; + channel = i; + break; + } + } + if (channel == ADAFRUIT_RMT_CHANNEL_MAX) { + // Ran out of channels! + 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, + .channel = channel, + .gpio_num = pin, + .clk_div = 2, + .mem_block_num = 1, + .tx_config = { + .carrier_freq_hz = 38000, + .carrier_level = RMT_CARRIER_LEVEL_HIGH, + .idle_level = RMT_IDLE_LEVEL_LOW, + .carrier_duty_percent = 33, + .carrier_en = false, + .loop_en = false, + .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; + uint32_t div = div_cnt == 0 ? 256 : div_cnt; + counter_clk_hz = REF_CLK_FREQ / (div); + } else { + uint32_t div_cnt = RMT_LL_HW_BASE->conf_ch[config.channel].conf0.div_cnt; + 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; + + if (is800KHz) { + t0h_ticks = (uint32_t)(ratio * WS2812_T0H_NS); + t0l_ticks = (uint32_t)(ratio * WS2812_T0L_NS); + t1h_ticks = (uint32_t)(ratio * WS2812_T1H_NS); + t1l_ticks = (uint32_t)(ratio * WS2812_T1L_NS); + } else { + t0h_ticks = (uint32_t)(ratio * WS2811_T0H_NS); + t0l_ticks = (uint32_t)(ratio * WS2811_T0L_NS); + t1h_ticks = (uint32_t)(ratio * WS2811_T1H_NS); + t1l_ticks = (uint32_t)(ratio * WS2811_T1L_NS); + } + + // Initialize automatic timing translator + rmt_translator_init(config.channel, ws2812_rmt_adapter); + + // Write and wait to finish + rmt_write_sample(config.channel, pixels, (size_t)numBytes, true); + rmt_wait_tx_done(config.channel, pdMS_TO_TICKS(100)); + + // Free channel again + rmt_driver_uninstall(config.channel); + rmt_reserved_channels[channel] = false; + + gpio_set_direction(pin, GPIO_MODE_OUTPUT); +} + +#endif diff --git a/libraries/Adafruit_NeoPixel/esp8266.c b/libraries/Adafruit_NeoPixel/esp8266.c new file mode 100644 index 0000000..51c3f3c --- /dev/null +++ b/libraries/Adafruit_NeoPixel/esp8266.c @@ -0,0 +1,86 @@ +// This is a mash-up of the Due show() code + insights from Michael Miller's +// ESP8266 work for the NeoPixelBus library: github.com/Makuna/NeoPixelBus +// Needs to be a separate .c file to enforce ICACHE_RAM_ATTR execution. + +#if defined(ESP8266) + +#include +#ifdef ESP8266 +#include +#endif + +static uint32_t _getCycleCount(void) __attribute__((always_inline)); +static inline uint32_t _getCycleCount(void) { + uint32_t ccount; + __asm__ __volatile__("rsr %0,ccount":"=a" (ccount)); + return ccount; +} + +#ifdef ESP8266 +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 / 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 + + uint8_t *p, *end, pix, mask; + uint32_t t, time0, time1, period, c, startTime; + +#ifdef ESP8266 + uint32_t pinMask; + pinMask = _BV(pin); +#endif + + p = pixels; + end = p + numBytes; + pix = *p++; + mask = 0x80; + startTime = 0; + +#ifdef NEO_KHZ400 + if(is800KHz) { +#endif + time0 = CYCLES_800_T0H; + time1 = CYCLES_800_T1H; + period = CYCLES_800; +#ifdef NEO_KHZ400 + } else { // 400 KHz bitstream + time0 = CYCLES_400_T0H; + time1 = CYCLES_400_T1H; + period = CYCLES_400; + } +#endif + + for(t = time0;; t = time0) { + if(pix & mask) t = time1; // Bit high duration + while(((c = _getCycleCount()) - startTime) < period); // Wait for bit start +#ifdef ESP8266 + GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, pinMask); // Set high +#else + gpio_set_level(pin, HIGH); +#endif + startTime = c; // Save start time + while(((c = _getCycleCount()) - startTime) < t); // Wait high duration +#ifdef ESP8266 + GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, pinMask); // Set low +#else + gpio_set_level(pin, LOW); +#endif + if(!(mask >>= 1)) { // Next bit/byte + if(p >= end) break; + pix = *p++; + mask = 0x80; + } + } + while((_getCycleCount() - startTime) < period); // Wait for last bit +} + +#endif // ESP8266 diff --git a/libraries/Adafruit_NeoPixel/kendyte_k210.c b/libraries/Adafruit_NeoPixel/kendyte_k210.c new file mode 100644 index 0000000..8033a36 --- /dev/null +++ b/libraries/Adafruit_NeoPixel/kendyte_k210.c @@ -0,0 +1,74 @@ +// This is a mash-up of the Due show() code + insights from Michael Miller's +// ESP8266 work for the NeoPixelBus library: github.com/Makuna/NeoPixelBus +// Needs to be a separate .c file to enforce ICACHE_RAM_ATTR execution. +#if defined(K210) +#define KENDRYTE_K210 1 +#endif + +#if defined(KENDRYTE_K210) + +#include +#include "sysctl.h" + +void k210Show( + uint8_t pin, uint8_t *pixels, uint32_t numBytes, boolean is800KHz) +{ + +#define CYCLES_800_T0H (sysctl_clock_get_freq(SYSCTL_CLOCK_CPU) / 2500000) // 0.4us +#define CYCLES_800_T1H (sysctl_clock_get_freq(SYSCTL_CLOCK_CPU) / 1250000) // 0.8us +#define CYCLES_800 (sysctl_clock_get_freq(SYSCTL_CLOCK_CPU) / 800000) // 1.25us per bit +#define CYCLES_400_T0H (sysctl_clock_get_freq(SYSCTL_CLOCK_CPU) / 2000000) // 0.5uS +#define CYCLES_400_T1H (sysctl_clock_get_freq(SYSCTL_CLOCK_CPU) / 833333) // 1.2us +#define CYCLES_400 (sysctl_clock_get_freq(SYSCTL_CLOCK_CPU) / 400000) // 2.5us per bit + + uint8_t *p, *end, pix, mask; + uint32_t t, time0, time1, period, c, startTime; + + p = pixels; + end = p + numBytes; + pix = *p++; + mask = 0x80; + startTime = 0; + +#ifdef NEO_KHZ400 + if (is800KHz) + { +#endif + time0 = CYCLES_800_T0H; + time1 = CYCLES_800_T1H; + period = CYCLES_800; +#ifdef NEO_KHZ400 + } + else + { // 400 KHz bitstream + time0 = CYCLES_400_T0H; + time1 = CYCLES_400_T1H; + period = CYCLES_400; + } +#endif + + for (t = time0;; t = time0) + { + if (pix & mask) + t = time1; // Bit high duration + while (((c = read_cycle()) - startTime) < period) + ; // Wait for bit start + digitalWrite(pin, HIGH); + startTime = c; // Save start time + while (((c = read_cycle()) - startTime) < t) + ; // Wait high duration + digitalWrite(pin, LOW); + + if (!(mask >>= 1)) + { // Next bit/byte + if (p >= end) + break; + pix = *p++; + mask = 0x80; + } + } + while ((read_cycle() - startTime) < period) + ; // Wait for last bit +} + +#endif // KENDRYTE_K210 diff --git a/libraries/Adafruit_NeoPixel/keywords.txt b/libraries/Adafruit_NeoPixel/keywords.txt new file mode 100644 index 0000000..4003ede --- /dev/null +++ b/libraries/Adafruit_NeoPixel/keywords.txt @@ -0,0 +1,72 @@ +####################################### +# Syntax Coloring Map For Adafruit_NeoPixel +####################################### +# Class +####################################### + +Adafruit_NeoPixel KEYWORD1 + +####################################### +# Methods and Functions +####################################### + +begin KEYWORD2 +show KEYWORD2 +setPin KEYWORD2 +setPixelColor KEYWORD2 +fill KEYWORD2 +setBrightness KEYWORD2 +clear KEYWORD2 +updateLength KEYWORD2 +updateType KEYWORD2 +canShow KEYWORD2 +getPixels KEYWORD2 +getBrightness KEYWORD2 +getPin KEYWORD2 +numPixels KEYWORD2 +getPixelColor KEYWORD2 +sine8 KEYWORD2 +gamma8 KEYWORD2 +Color KEYWORD2 +ColorHSV KEYWORD2 +gamma32 KEYWORD2 + +####################################### +# Constants +####################################### + +NEO_COLMASK LITERAL1 +NEO_SPDMASK LITERAL1 +NEO_KHZ800 LITERAL1 +NEO_KHZ400 LITERAL1 +NEO_RGB LITERAL1 +NEO_RBG LITERAL1 +NEO_GRB LITERAL1 +NEO_GBR LITERAL1 +NEO_BRG LITERAL1 +NEO_BGR LITERAL1 +NEO_WRGB LITERAL1 +NEO_WRBG LITERAL1 +NEO_WGRB LITERAL1 +NEO_WGBR LITERAL1 +NEO_WBRG LITERAL1 +NEO_WBGR LITERAL1 +NEO_RWGB LITERAL1 +NEO_RWBG LITERAL1 +NEO_RGWB LITERAL1 +NEO_RGBW LITERAL1 +NEO_RBWG LITERAL1 +NEO_RBGW LITERAL1 +NEO_GWRB LITERAL1 +NEO_GWBR LITERAL1 +NEO_GRWB LITERAL1 +NEO_GRBW LITERAL1 +NEO_GBWR LITERAL1 +NEO_GBRW LITERAL1 +NEO_BWRG LITERAL1 +NEO_BWGR LITERAL1 +NEO_BRWG LITERAL1 +NEO_BRGW LITERAL1 +NEO_BGWR LITERAL1 +NEO_BGRW LITERAL1 + diff --git a/libraries/Adafruit_NeoPixel/library.properties b/libraries/Adafruit_NeoPixel/library.properties new file mode 100644 index 0000000..7bbd488 --- /dev/null +++ b/libraries/Adafruit_NeoPixel/library.properties @@ -0,0 +1,10 @@ +name=Adafruit NeoPixel +version=1.10.6 +author=Adafruit +maintainer=Adafruit +sentence=Arduino library for controlling single-wire-based LED pixels and strip. +paragraph=Arduino library for controlling single-wire-based LED pixels and strip. +category=Display +url=https://github.com/adafruit/Adafruit_NeoPixel +architectures=* +includes=Adafruit_NeoPixel.h diff --git a/libraries/Adafruit_NeoPixel/rp2040_pio.h b/libraries/Adafruit_NeoPixel/rp2040_pio.h new file mode 100644 index 0000000..f7ccd46 --- /dev/null +++ b/libraries/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/libraries/OASIS-PAW_ESP32/keywords.txt b/libraries/OASIS-PAW_ESP32/keywords.txt new file mode 100644 index 0000000..a2c7bb8 --- /dev/null +++ b/libraries/OASIS-PAW_ESP32/keywords.txt @@ -0,0 +1,46 @@ +####################################### +# Syntax Coloring Map For OASISPAW +####################################### + +OASISPAW KEYWORD1 +OASISPaw KEYWORD3 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +setTimer KEYWORD2 +enableTimer KEYWORD2 +disableTimer KEYWORD2 +tone KEYWORD2 +writeRGB KEYWORD2 +ledInit KEYWORD2 +resetDevice KEYWORD2 + +####################################### +# Constants and Variables (LITERAL1) +####################################### +HSPI_MISO LITERAL1 +HSPI_MOSI LITERAL1 +HSPI_SCLK LITERAL1 +HSPI_CS LITERAL1 +VSPI_MISO LITERAL1 +VSPI_MOSI LITERAL1 +VSPI_SCLK LITERAL1 +VSPI_CS LITERAL1 +CC_GDO2 LITERAL1 +TEDDYPin LITERAL1 +I2C_SCL LITERAL1 +I2C_SDA LITERAL1 +RESET LITERAL1 +CONVST LITERAL1 +RANGE LITERAL1 +BUSY LITERAL1 +SPEAKER LITERAL1 +LEDPin LITERAL1 +SerialSpeed LITERAL1 + +InterruptFlag LITERAL1 +OASISBusy LITERAL1 +FaultFlag LITERAL1 +timerMux LITERAL1 \ No newline at end of file diff --git a/libraries/OASIS-PAW_ESP32/library.properties b/libraries/OASIS-PAW_ESP32/library.properties new file mode 100644 index 0000000..8626179 --- /dev/null +++ b/libraries/OASIS-PAW_ESP32/library.properties @@ -0,0 +1,9 @@ +name=OASISPAW +version=1.0.0 +author=Oliver Zobel +maintainer=None +sentence=OASIS Platform Awareness +paragraph= +category= +url= +architectures=esp32 \ No newline at end of file diff --git a/libraries/OASIS-PAW_ESP32/src/OASISPAW.cpp b/libraries/OASIS-PAW_ESP32/src/OASISPAW.cpp new file mode 100644 index 0000000..13e918f --- /dev/null +++ b/libraries/OASIS-PAW_ESP32/src/OASISPAW.cpp @@ -0,0 +1,73 @@ +/* + OASISPAW.cpp + + OASIS Platform Awareness - ESP32 + + */ + +#include + +OASISPAW::OASISPAW(){ + + // Timer Setup + timerMux = portMUX_INITIALIZER_UNLOCKED; + timer = timerBegin(0, 80, true); + timerAttachInterrupt(timer, &TimerISR, true); + + // Speaker Init + ledcSetup(0, 2000, 8); + ledcAttachPin(SPEAKER, 0); +} + +OASISPAW::~OASISPAW(){ + +} + +void OASISPAW::setTimer(int T_sample){ + timerAlarmWrite(timer, T_sample, true); + return; +} + +void OASISPAW::enableTimer(){ + timerAlarmEnable(timer); + return; +} + +void OASISPAW::disableTimer(){ + timerAlarmDisable(timer); + return; +} + +void OASISPAW::tone(int frequency, int duration){ + if(!OASISMute){ + ledcWriteTone(0, frequency); + } + delay(duration); + ledcWriteTone(0, 0); +} + +void OASISPAW::writeRGB(byte PWM_RED, byte PWM_GREEN, byte PWM_BLUE){ + + Adafruit_NeoPixel pixels(2, LEDPin, NEO_GRB + NEO_KHZ800); + pixels.begin(); + pixels.setPixelColor(0, pixels.Color(PWM_RED, PWM_GREEN, PWM_BLUE)); + pixels.setPixelColor(1, pixels.Color(50*(OASISWiFi==0), 50*(OASISWiFi==1), 0)); + pixels.show(); + +} + +void OASISPAW::ledInit(){ + + Adafruit_NeoPixel pixels(2, LEDPin, NEO_GRB + NEO_KHZ800); + pixels.begin(); + pixels.show(); + delay(200); + pixels.setPixelColor(0, pixels.Color(25, 25, 25)); + pixels.setPixelColor(1, pixels.Color(25, 25, 25)); + pixels.show(); + +} + +void OASISPAW::resetDevice(){ + ESP.restart(); +} \ No newline at end of file diff --git a/libraries/OASIS-PAW_ESP32/src/OASISPAW.h b/libraries/OASIS-PAW_ESP32/src/OASISPAW.h new file mode 100644 index 0000000..f89d456 --- /dev/null +++ b/libraries/OASIS-PAW_ESP32/src/OASISPAW.h @@ -0,0 +1,83 @@ +/* + OASISPAW.h + + OASIS Platform Awareness - ESP32 + + */ + +#ifndef OASISPAW_h +#define OASISPAW_h + +#include +#include +#include "WiFi.h" +#include "esp_wifi.h" + +// Pin Setup - ESP32 +// ADC SPI +#define HSPI_MISO 12 +#define HSPI_MOSI 13 +#define HSPI_SCLK 14 +#define HSPI_CS 15 + +// CC1101 +#define VSPI_MISO 19 +#define VSPI_MOSI 23 +#define VSPI_SCLK 18 +#define VSPI_CS 5 +#define CC_GDO2 4 +#define CC_GDO0 34 + +// TEDS +#define TEDDYPin 2 + +// Portexpander I2C +#define I2C_SCL 22 +#define I2C_SDA 21 + +// ADC Control +#define RESET 32 +#define CONVST 33 +#define RANGE 25 +#define BUSY 35 + +// Audiovisual Feedback +#define SPEAKER 26 +#define LEDPin 27 + + +#define SerialSpeed 2000000 + +extern bool OASISMute; +extern bool OASISWiFi; + +class OASISPAW { +public: + OASISPAW(); + ~OASISPAW(); + hw_timer_t * timer; + void setTimer(int T_sample); + void enableTimer(); + void disableTimer(); + void tone(int frequency, int duration); + void writeRGB(byte PWM_RED, byte PWM_GREEN, byte PWM_BLUE); + void ledInit(); + void resetDevice(); + static volatile int InterruptFlag; + static volatile bool OASISBusy; + static volatile bool FaultFlag; + static portMUX_TYPE timerMux; +}; + +// Interrupt Routine Timer +static void IRAM_ATTR TimerISR() { + portENTER_CRITICAL_ISR(&OASISPAW::timerMux); + digitalWrite(CONVST, HIGH); + if (OASISPAW::OASISBusy){ + OASISPAW::FaultFlag = 1; + } + OASISPAW::OASISBusy = 1; + portEXIT_CRITICAL_ISR(&OASISPAW::timerMux); +} + +#endif \ No newline at end of file diff --git a/libraries/OASIS-PAW_ESP32/src/OASISUDP.cpp b/libraries/OASIS-PAW_ESP32/src/OASISUDP.cpp new file mode 100644 index 0000000..0eda0a3 --- /dev/null +++ b/libraries/OASIS-PAW_ESP32/src/OASISUDP.cpp @@ -0,0 +1,281 @@ +/* + Udp.cpp - UDP class for Raspberry Pi + Copyright (c) 2016 Hristo Gochkov All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "OASISUDP.h" +#include +#include +#include + +#undef write +#undef read + +OASISUDP::OASISUDP() +: udp_server(-1) +, server_port(0) +, remote_port(0) +, tx_buffer(0) +, tx_buffer_len(0) +, rx_buffer(0) +{} + +OASISUDP::~OASISUDP(){ + stop(); +} + +uint8_t OASISUDP::begin(IPAddress address, uint16_t port){ + stop(); + + server_port = port; + + tx_buffer = new char[13500]; + if(!tx_buffer){ + log_e("could not create tx buffer: %d", errno); + return 0; + } + + if ((udp_server=socket(AF_INET, SOCK_DGRAM, 0)) == -1){ + log_e("could not create socket: %d", errno); + return 0; + } + + int yes = 1; + if (setsockopt(udp_server,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(yes)) < 0) { + log_e("could not set socket option: %d", errno); + stop(); + return 0; + } + + struct sockaddr_in addr; + memset((char *) &addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(server_port); + addr.sin_addr.s_addr = (in_addr_t)address; + if(bind(udp_server , (struct sockaddr*)&addr, sizeof(addr)) == -1){ + log_e("could not bind socket: %d", errno); + stop(); + return 0; + } + fcntl(udp_server, F_SETFL, O_NONBLOCK); + return 1; +} + +uint8_t OASISUDP::begin(uint16_t p){ + return begin(IPAddress(INADDR_ANY), p); +} + +uint8_t OASISUDP::beginMulticast(IPAddress a, uint16_t p){ + if(begin(IPAddress(INADDR_ANY), p)){ + if(a != 0){ + struct ip_mreq mreq; + mreq.imr_multiaddr.s_addr = (in_addr_t)a; + mreq.imr_interface.s_addr = INADDR_ANY; + if (setsockopt(udp_server, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) { + log_e("could not join igmp: %d", errno); + stop(); + return 0; + } + multicast_ip = a; + } + return 1; + } + return 0; +} + +void OASISUDP::stop(){ + if(tx_buffer){ + delete[] tx_buffer; + tx_buffer = NULL; + } + tx_buffer_len = 0; + if(rx_buffer){ + cbuf *b = rx_buffer; + rx_buffer = NULL; + delete b; + } + if(udp_server == -1) + return; + if(multicast_ip != 0){ + struct ip_mreq mreq; + mreq.imr_multiaddr.s_addr = (in_addr_t)multicast_ip; + mreq.imr_interface.s_addr = (in_addr_t)0; + setsockopt(udp_server, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)); + multicast_ip = IPAddress(INADDR_ANY); + } + close(udp_server); + udp_server = -1; +} + +int OASISUDP::beginMulticastPacket(){ + if(!server_port || multicast_ip == IPAddress(INADDR_ANY)) + return 0; + remote_ip = multicast_ip; + remote_port = server_port; + return beginPacket(); +} + +int OASISUDP::beginPacket(){ + if(!remote_port) + return 0; + + // allocate tx_buffer if is necessary + if(!tx_buffer){ + tx_buffer = new char[13500]; + if(!tx_buffer){ + log_e("could not create tx buffer: %d", errno); + return 0; + } + } + + tx_buffer_len = 0; + + // check whereas socket is already open + if (udp_server != -1) + return 1; + + if ((udp_server=socket(AF_INET, SOCK_DGRAM, 0)) == -1){ + log_e("could not create socket: %d", errno); + return 0; + } + + fcntl(udp_server, F_SETFL, O_NONBLOCK); + + return 1; +} + +int OASISUDP::beginPacket(IPAddress ip, uint16_t port){ + remote_ip = ip; + remote_port = port; + return beginPacket(); +} + +int OASISUDP::beginPacket(const char *host, uint16_t port){ + struct hostent *server; + server = gethostbyname(host); + if (server == NULL){ + log_e("could not get host from dns: %d", errno); + return 0; + } + return beginPacket(IPAddress((const uint8_t *)(server->h_addr_list[0])), port); +} + +int OASISUDP::endPacket(){ + struct sockaddr_in recipient; + recipient.sin_addr.s_addr = (uint32_t)remote_ip; + recipient.sin_family = AF_INET; + recipient.sin_port = htons(remote_port); + int sent = sendto(udp_server, tx_buffer, tx_buffer_len, 0, (struct sockaddr*) &recipient, sizeof(recipient)); + if(sent < 0){ + log_e("could not send data: %d", errno); + return 0; + } + return 1; +} + +size_t OASISUDP::write(uint8_t data){ + if(tx_buffer_len == 13500){ + endPacket(); + tx_buffer_len = 0; + } + tx_buffer[tx_buffer_len++] = data; + return 1; +} + +size_t OASISUDP::write(const uint8_t *buffer, size_t size){ + size_t i; + for(i=0;i 0) { + rx_buffer = new cbuf(len); + rx_buffer->write(buf, len); + } + delete[] buf; + return len; +} + +int OASISUDP::available(){ + if(!rx_buffer) return 0; + return rx_buffer->available(); +} + +int OASISUDP::read(){ + if(!rx_buffer) return -1; + int out = rx_buffer->read(); + if(!rx_buffer->available()){ + cbuf *b = rx_buffer; + rx_buffer = 0; + delete b; + } + return out; +} + +int OASISUDP::read(unsigned char* buffer, size_t len){ + return read((char *)buffer, len); +} + +int OASISUDP::read(char* buffer, size_t len){ + if(!rx_buffer) return 0; + int out = rx_buffer->read(buffer, len); + if(!rx_buffer->available()){ + cbuf *b = rx_buffer; + rx_buffer = 0; + delete b; + } + return out; +} + +int OASISUDP::peek(){ + if(!rx_buffer) return -1; + return rx_buffer->peek(); +} + +void OASISUDP::flush(){ + if(!rx_buffer) return; + cbuf *b = rx_buffer; + rx_buffer = 0; + delete b; +} + +IPAddress OASISUDP::remoteIP(){ + return remote_ip; +} + +uint16_t OASISUDP::remotePort(){ + return remote_port; +} diff --git a/libraries/OASIS-PAW_ESP32/src/OASISUDP.h b/libraries/OASIS-PAW_ESP32/src/OASISUDP.h new file mode 100644 index 0000000..9aeb1d0 --- /dev/null +++ b/libraries/OASIS-PAW_ESP32/src/OASISUDP.h @@ -0,0 +1,77 @@ +/* + * Udp.cpp: Library to send/receive UDP packets. + * + * NOTE: UDP is fast, but has some important limitations (thanks to Warren Gray for mentioning these) + * 1) UDP does not guarantee the order in which assembled UDP packets are received. This + * might not happen often in practice, but in larger network topologies, a UDP + * packet can be received out of sequence. + * 2) UDP does not guard against lost packets - so packets *can* disappear without the sender being + * aware of it. Again, this may not be a concern in practice on small local networks. + * For more information, see http://www.cafeaulait.org/course/week12/35.html + * + * MIT License: + * Copyright (c) 2008 Bjoern Hartmann + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * bjoern@cs.stanford.edu 12/30/2008 + */ + +#ifndef _OASISUDP_H_ +#define _OASISUDP_H_ + +#include +#include +#include + +class OASISUDP : public UDP { +private: + int udp_server; + IPAddress multicast_ip; + IPAddress remote_ip; + uint16_t server_port; + uint16_t remote_port; + char * tx_buffer; + size_t tx_buffer_len; + cbuf * rx_buffer; +public: + OASISUDP(); + ~OASISUDP(); + uint8_t begin(IPAddress a, uint16_t p); + uint8_t begin(uint16_t p); + uint8_t beginMulticast(IPAddress a, uint16_t p); + void stop(); + int beginMulticastPacket(); + int beginPacket(); + int beginPacket(IPAddress ip, uint16_t port); + int beginPacket(const char *host, uint16_t port); + int endPacket(); + size_t write(uint8_t); + size_t write(const uint8_t *buffer, size_t size); + int parsePacket(); + int available(); + int read(); + int read(unsigned char* buffer, size_t len); + int read(char* buffer, size_t len); + int peek(); + void flush(); + IPAddress remoteIP(); + uint16_t remotePort(); +}; + +#endif /* _OASISUDP_H_ */ diff --git a/libraries/OASIS-PAW_ESP32/src/WiFi.cpp b/libraries/OASIS-PAW_ESP32/src/WiFi.cpp new file mode 100644 index 0000000..3812333 --- /dev/null +++ b/libraries/OASIS-PAW_ESP32/src/WiFi.cpp @@ -0,0 +1,101 @@ +/* + ESP8266WiFi.cpp - WiFi library for esp8266 + + Copyright (c) 2014 Ivan Grokhotkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Reworked on 28 Dec 2015 by Markus Sattler + + */ +#include "WiFi.h" + +extern "C" { +#include +#include +#include +#include +#include +#include +#include +#include +#include +} + + +// ----------------------------------------------------------------------------------------------------------------------- +// ---------------------------------------------------------- Debug ------------------------------------------------------ +// ----------------------------------------------------------------------------------------------------------------------- + + +/** + * Output WiFi settings to an object derived from Print interface (like Serial). + * @param p Print interface + */ +void WiFiClass::printDiag(Print& p) +{ + const char* modes[] = { "NULL", "STA", "AP", "STA+AP" }; + + wifi_mode_t mode; + esp_wifi_get_mode(&mode); + + uint8_t primaryChan; + wifi_second_chan_t secondChan; + esp_wifi_get_channel(&primaryChan, &secondChan); + + p.print("Mode: "); + p.println(modes[mode]); + + p.print("Channel: "); + p.println(primaryChan); + /* + p.print("AP id: "); + p.println(wifi_station_get_current_ap_id()); + + p.print("Status: "); + p.println(wifi_station_get_connect_status()); + */ + + wifi_config_t conf; + esp_wifi_get_config(WIFI_IF_STA, &conf); + + const char* ssid = reinterpret_cast(conf.sta.ssid); + p.print("SSID ("); + p.print(strlen(ssid)); + p.print("): "); + p.println(ssid); + + const char* passphrase = reinterpret_cast(conf.sta.password); + p.print("Passphrase ("); + p.print(strlen(passphrase)); + p.print("): "); + p.println(passphrase); + + p.print("BSSID set: "); + p.println(conf.sta.bssid_set); +} + +void WiFiClass::enableProv(bool status) +{ + prov_enable = status; +} + +bool WiFiClass::isProvEnabled() +{ + return prov_enable; +} + +WiFiClass WiFi; diff --git a/libraries/OASIS-PAW_ESP32/src/WiFi.h b/libraries/OASIS-PAW_ESP32/src/WiFi.h new file mode 100644 index 0000000..105c0a4 --- /dev/null +++ b/libraries/OASIS-PAW_ESP32/src/WiFi.h @@ -0,0 +1,75 @@ +/* + WiFi.h - esp32 Wifi support. + Based on WiFi.h from Arduino WiFi shield library. + Copyright (c) 2011-2014 Arduino. All right reserved. + Modified by Ivan Grokhotkov, December 2014 + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef WiFi_h +#define WiFi_h + +#include + +#include "Print.h" +#include "IPAddress.h" +#include "IPv6Address.h" + +#include "WiFiType.h" +#include "WiFiSTA.h" +#include "WiFiAP.h" +#include "WiFiScan.h" +#include "WiFiGeneric.h" + +#include "WiFiClient.h" +#include "WiFiServer.h" +#include "OASISUDP.h" + +class WiFiClass : public WiFiGenericClass, public WiFiSTAClass, public WiFiScanClass, public WiFiAPClass +{ +private: + bool prov_enable; +public: + WiFiClass() + { + prov_enable = false; + } + + using WiFiGenericClass::channel; + + using WiFiSTAClass::SSID; + using WiFiSTAClass::RSSI; + using WiFiSTAClass::BSSID; + using WiFiSTAClass::BSSIDstr; + + using WiFiScanClass::SSID; + using WiFiScanClass::encryptionType; + using WiFiScanClass::RSSI; + using WiFiScanClass::BSSID; + using WiFiScanClass::BSSIDstr; + using WiFiScanClass::channel; +public: + void printDiag(Print& dest); + friend class WiFiClient; + friend class WiFiServer; + friend class OASISUDP; + void enableProv(bool status); + bool isProvEnabled(); +}; + +extern WiFiClass WiFi; + +#endif diff --git a/libraries/OASIS-PAW_ESP32/src/WiFiAP.cpp b/libraries/OASIS-PAW_ESP32/src/WiFiAP.cpp new file mode 100644 index 0000000..a312009 --- /dev/null +++ b/libraries/OASIS-PAW_ESP32/src/WiFiAP.cpp @@ -0,0 +1,380 @@ +/* + ESP8266WiFiSTA.cpp - WiFi library for esp8266 + + Copyright (c) 2014 Ivan Grokhotkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Reworked on 28 Dec 2015 by Markus Sattler + + */ + +#include "WiFi.h" +#include "WiFiGeneric.h" +#include "WiFiAP.h" + +extern "C" { +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dhcpserver/dhcpserver_options.h" +} + + + +// ----------------------------------------------------------------------------------------------------------------------- +// ---------------------------------------------------- Private functions ------------------------------------------------ +// ----------------------------------------------------------------------------------------------------------------------- + +static bool softap_config_equal(const wifi_config_t& lhs, const wifi_config_t& rhs); + + + +/** + * compare two AP configurations + * @param lhs softap_config + * @param rhs softap_config + * @return equal + */ +static bool softap_config_equal(const wifi_config_t& lhs, const wifi_config_t& rhs) +{ + if(strcmp(reinterpret_cast(lhs.ap.ssid), reinterpret_cast(rhs.ap.ssid)) != 0) { + return false; + } + if(strcmp(reinterpret_cast(lhs.ap.password), reinterpret_cast(rhs.ap.password)) != 0) { + return false; + } + if(lhs.ap.channel != rhs.ap.channel) { + return false; + } + if(lhs.ap.ssid_hidden != rhs.ap.ssid_hidden) { + return false; + } + if(lhs.ap.max_connection != rhs.ap.max_connection) { + return false; + } + return true; +} + +// ----------------------------------------------------------------------------------------------------------------------- +// ----------------------------------------------------- AP function ----------------------------------------------------- +// ----------------------------------------------------------------------------------------------------------------------- + + +/** + * Set up an access point + * @param ssid Pointer to the SSID (max 63 char). + * @param passphrase (for WPA2 min 8 char, for open use NULL) + * @param channel WiFi channel number, 1 - 13. + * @param ssid_hidden Network cloaking (0 = broadcast SSID, 1 = hide SSID) + * @param max_connection Max simultaneous connected clients, 1 - 4. +*/ +bool WiFiAPClass::softAP(const char* ssid, const char* passphrase, int channel, int ssid_hidden, int max_connection) +{ + + if(!WiFi.enableAP(true)) { + // enable AP failed + log_e("enable AP first!"); + return false; + } + + if(!ssid || *ssid == 0) { + // fail SSID missing + log_e("SSID missing!"); + return false; + } + + if(passphrase && (strlen(passphrase) > 0 && strlen(passphrase) < 8)) { + // fail passphrase too short + log_e("passphrase too short!"); + return false; + } + + esp_wifi_start(); + + wifi_config_t conf; + strlcpy(reinterpret_cast(conf.ap.ssid), ssid, sizeof(conf.ap.ssid)); + conf.ap.channel = channel; + conf.ap.ssid_len = strlen(reinterpret_cast(conf.ap.ssid)); + conf.ap.ssid_hidden = ssid_hidden; + conf.ap.max_connection = max_connection; + conf.ap.beacon_interval = 100; + + if(!passphrase || strlen(passphrase) == 0) { + conf.ap.authmode = WIFI_AUTH_OPEN; + *conf.ap.password = 0; + } else { + conf.ap.authmode = WIFI_AUTH_WPA2_PSK; + strlcpy(reinterpret_cast(conf.ap.password), passphrase, sizeof(conf.ap.password)); + } + + wifi_config_t conf_current; + esp_wifi_get_config(WIFI_IF_AP, &conf_current); + if(!softap_config_equal(conf, conf_current) && esp_wifi_set_config(WIFI_IF_AP, &conf) != ESP_OK) { + return false; + } + + return true; +} + +/** + * Return the current SSID associated with the network + * @return SSID + */ +String WiFiAPClass::softAPSSID() const +{ + if(WiFiGenericClass::getMode() == WIFI_MODE_NULL){ + return String(); + } + wifi_config_t info; + if(!esp_wifi_get_config(WIFI_IF_AP, &info)) { + return String(reinterpret_cast(info.ap.ssid)); + } + return String(); +} + +/** + * Configure access point + * @param local_ip access point IP + * @param gateway gateway IP + * @param subnet subnet mask + */ +bool WiFiAPClass::softAPConfig(IPAddress local_ip, IPAddress gateway, IPAddress subnet) +{ + + if(!WiFi.enableAP(true)) { + // enable AP failed + return false; + } + + esp_wifi_start(); + + tcpip_adapter_ip_info_t info; + info.ip.addr = static_cast(local_ip); + info.gw.addr = static_cast(gateway); + info.netmask.addr = static_cast(subnet); + tcpip_adapter_dhcps_stop(TCPIP_ADAPTER_IF_AP); + if(tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_AP, &info) == ESP_OK) { + dhcps_lease_t lease; + lease.enable = true; + lease.start_ip.addr = static_cast(local_ip) + (1 << 24); + lease.end_ip.addr = static_cast(local_ip) + (11 << 24); + + tcpip_adapter_dhcps_option( + (tcpip_adapter_option_mode_t)TCPIP_ADAPTER_OP_SET, + (tcpip_adapter_option_id_t)REQUESTED_IP_ADDRESS, + (void*)&lease, sizeof(dhcps_lease_t) + ); + + return tcpip_adapter_dhcps_start(TCPIP_ADAPTER_IF_AP) == ESP_OK; + } + return false; +} + + + +/** + * Disconnect from the network (close AP) + * @param wifioff disable mode? + * @return one value of wl_status_t enum + */ +bool WiFiAPClass::softAPdisconnect(bool wifioff) +{ + bool ret; + wifi_config_t conf; + + if(WiFiGenericClass::getMode() == WIFI_MODE_NULL){ + return false; + } + + *conf.ap.ssid = 0; + *conf.ap.password = 0; + conf.ap.authmode = WIFI_AUTH_OPEN; // auth must be open if pass=0 + ret = esp_wifi_set_config(WIFI_IF_AP, &conf) == ESP_OK; + + if(wifioff) { + ret = WiFi.enableAP(false) == ESP_OK; + } + + return ret; +} + + +/** + * Get the count of the Station / client that are connected to the softAP interface + * @return Stations count + */ +uint8_t WiFiAPClass::softAPgetStationNum() +{ + wifi_sta_list_t clients; + if(WiFiGenericClass::getMode() == WIFI_MODE_NULL){ + return 0; + } + if(esp_wifi_ap_get_sta_list(&clients) == ESP_OK) { + return clients.num; + } + return 0; +} + +/** + * Get the softAP interface IP address. + * @return IPAddress softAP IP + */ +IPAddress WiFiAPClass::softAPIP() +{ + tcpip_adapter_ip_info_t ip; + if(WiFiGenericClass::getMode() == WIFI_MODE_NULL){ + return IPAddress(); + } + tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ip); + return IPAddress(ip.ip.addr); +} + +/** + * Get the softAP broadcast IP address. + * @return IPAddress softAP broadcastIP + */ +IPAddress WiFiAPClass::softAPBroadcastIP() +{ + tcpip_adapter_ip_info_t ip; + if(WiFiGenericClass::getMode() == WIFI_MODE_NULL){ + return IPAddress(); + } + tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ip); + return WiFiGenericClass::calculateBroadcast(IPAddress(ip.gw.addr), IPAddress(ip.netmask.addr)); +} + +/** + * Get the softAP network ID. + * @return IPAddress softAP networkID + */ +IPAddress WiFiAPClass::softAPNetworkID() +{ + tcpip_adapter_ip_info_t ip; + if(WiFiGenericClass::getMode() == WIFI_MODE_NULL){ + return IPAddress(); + } + tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ip); + return WiFiGenericClass::calculateNetworkID(IPAddress(ip.gw.addr), IPAddress(ip.netmask.addr)); +} + +/** + * Get the softAP subnet CIDR. + * @return uint8_t softAP subnetCIDR + */ +uint8_t WiFiAPClass::softAPSubnetCIDR() +{ + tcpip_adapter_ip_info_t ip; + if(WiFiGenericClass::getMode() == WIFI_MODE_NULL){ + return (uint8_t)0; + } + tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ip); + return WiFiGenericClass::calculateSubnetCIDR(IPAddress(ip.netmask.addr)); +} + +/** + * Get the softAP interface MAC address. + * @param mac pointer to uint8_t array with length WL_MAC_ADDR_LENGTH + * @return pointer to uint8_t* + */ +uint8_t* WiFiAPClass::softAPmacAddress(uint8_t* mac) +{ + if(WiFiGenericClass::getMode() != WIFI_MODE_NULL){ + esp_wifi_get_mac(WIFI_IF_AP, mac); + } + return mac; +} + +/** + * Get the softAP interface MAC address. + * @return String mac + */ +String WiFiAPClass::softAPmacAddress(void) +{ + uint8_t mac[6]; + char macStr[18] = { 0 }; + if(WiFiGenericClass::getMode() == WIFI_MODE_NULL){ + return String(); + } + esp_wifi_get_mac(WIFI_IF_AP, mac); + + sprintf(macStr, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + return String(macStr); +} + +/** + * Get the softAP interface Host name. + * @return char array hostname + */ +const char * WiFiAPClass::softAPgetHostname() +{ + const char * hostname = NULL; + if(WiFiGenericClass::getMode() == WIFI_MODE_NULL){ + return hostname; + } + if(tcpip_adapter_get_hostname(TCPIP_ADAPTER_IF_AP, &hostname)) { + return hostname; + } + return hostname; +} + +/** + * Set the softAP interface Host name. + * @param hostname pointer to const string + * @return true on success + */ +bool WiFiAPClass::softAPsetHostname(const char * hostname) +{ + if(WiFiGenericClass::getMode() == WIFI_MODE_NULL){ + return false; + } + return tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_AP, hostname) == ESP_OK; +} + +/** + * Enable IPv6 on the softAP interface. + * @return true on success + */ +bool WiFiAPClass::softAPenableIpV6() +{ + if(WiFiGenericClass::getMode() == WIFI_MODE_NULL){ + return false; + } + return tcpip_adapter_create_ip6_linklocal(TCPIP_ADAPTER_IF_AP) == ESP_OK; +} + +/** + * Get the softAP interface IPv6 address. + * @return IPv6Address softAP IPv6 + */ +IPv6Address WiFiAPClass::softAPIPv6() +{ + static ip6_addr_t addr; + if(WiFiGenericClass::getMode() == WIFI_MODE_NULL){ + return IPv6Address(); + } + if(tcpip_adapter_get_ip6_linklocal(TCPIP_ADAPTER_IF_AP, &addr)) { + return IPv6Address(); + } + return IPv6Address(addr.addr); +} diff --git a/libraries/OASIS-PAW_ESP32/src/WiFiAP.h b/libraries/OASIS-PAW_ESP32/src/WiFiAP.h new file mode 100644 index 0000000..a723483 --- /dev/null +++ b/libraries/OASIS-PAW_ESP32/src/WiFiAP.h @@ -0,0 +1,67 @@ +/* + ESP8266WiFiAP.h - esp8266 Wifi support. + Based on WiFi.h from Arduino WiFi shield library. + Copyright (c) 2011-2014 Arduino. All right reserved. + Modified by Ivan Grokhotkov, December 2014 + Reworked by Markus Sattler, December 2015 + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef ESP32WIFIAP_H_ +#define ESP32WIFIAP_H_ + + +#include "WiFiType.h" +#include "WiFiGeneric.h" + + +class WiFiAPClass +{ + + // ---------------------------------------------------------------------------------------------- + // ----------------------------------------- AP function ---------------------------------------- + // ---------------------------------------------------------------------------------------------- + +public: + + bool softAP(const char* ssid, const char* passphrase = NULL, int channel = 1, int ssid_hidden = 0, int max_connection = 4); + bool softAPConfig(IPAddress local_ip, IPAddress gateway, IPAddress subnet); + bool softAPdisconnect(bool wifioff = false); + + uint8_t softAPgetStationNum(); + + IPAddress softAPIP(); + + IPAddress softAPBroadcastIP(); + IPAddress softAPNetworkID(); + uint8_t softAPSubnetCIDR(); + + bool softAPenableIpV6(); + IPv6Address softAPIPv6(); + + const char * softAPgetHostname(); + bool softAPsetHostname(const char * hostname); + + uint8_t* softAPmacAddress(uint8_t* mac); + String softAPmacAddress(void); + + String softAPSSID(void) const; + +protected: + +}; + +#endif /* ESP32WIFIAP_H_*/ diff --git a/libraries/OASIS-PAW_ESP32/src/WiFiClient.cpp b/libraries/OASIS-PAW_ESP32/src/WiFiClient.cpp new file mode 100644 index 0000000..bec70e4 --- /dev/null +++ b/libraries/OASIS-PAW_ESP32/src/WiFiClient.cpp @@ -0,0 +1,597 @@ +/* + Client.h - Client class for Raspberry Pi + Copyright (c) 2016 Hristo Gochkov All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "WiFiClient.h" +#include "WiFi.h" +#include +#include +#include + +#define WIFI_CLIENT_MAX_WRITE_RETRY (10) +#define WIFI_CLIENT_SELECT_TIMEOUT_US (1000000) +#define WIFI_CLIENT_FLUSH_BUFFER_SIZE (1024) + +#undef connect +#undef write +#undef read + +class WiFiClientRxBuffer { +private: + size_t _size; + uint8_t *_buffer; + size_t _pos; + size_t _fill; + int _fd; + bool _failed; + + size_t r_available() + { + if(_fd < 0){ + return 0; + } + int count; + int res = lwip_ioctl_r(_fd, FIONREAD, &count); + if(res < 0) { + _failed = true; + return 0; + } + return count; + } + + size_t fillBuffer() + { + if(!_buffer){ + _buffer = (uint8_t *)malloc(_size); + if(!_buffer) { + log_e("Not enough memory to allocate buffer"); + _failed = true; + return 0; + } + } + if(_fill && _pos == _fill){ + _fill = 0; + _pos = 0; + } + if(!_buffer || _size <= _fill || !r_available()) { + return 0; + } + int res = recv(_fd, _buffer + _fill, _size - _fill, MSG_DONTWAIT); + if(res < 0) { + if(errno != EWOULDBLOCK) { + _failed = true; + } + return 0; + } + _fill += res; + return res; + } + +public: + WiFiClientRxBuffer(int fd, size_t size=1436) + :_size(size) + ,_buffer(NULL) + ,_pos(0) + ,_fill(0) + ,_fd(fd) + ,_failed(false) + { + //_buffer = (uint8_t *)malloc(_size); + } + + ~WiFiClientRxBuffer() + { + free(_buffer); + } + + bool failed(){ + return _failed; + } + + int read(uint8_t * dst, size_t len){ + if(!dst || !len || (_pos == _fill && !fillBuffer())){ + return _failed ? -1 : 0; + } + size_t a = _fill - _pos; + if(len <= a || ((len - a) <= (_size - _fill) && fillBuffer() >= (len - a))){ + if(len == 1){ + *dst = _buffer[_pos]; + } else { + memcpy(dst, _buffer + _pos, len); + } + _pos += len; + return len; + } + size_t left = len; + size_t toRead = a; + uint8_t * buf = dst; + memcpy(buf, _buffer + _pos, toRead); + _pos += toRead; + left -= toRead; + buf += toRead; + while(left){ + if(!fillBuffer()){ + return len - left; + } + a = _fill - _pos; + toRead = (a > left)?left:a; + memcpy(buf, _buffer + _pos, toRead); + _pos += toRead; + left -= toRead; + buf += toRead; + } + return len; + } + + int peek(){ + if(_pos == _fill && !fillBuffer()){ + return -1; + } + return _buffer[_pos]; + } + + size_t available(){ + return _fill - _pos + r_available(); + } +}; + +class WiFiClientSocketHandle { +private: + int sockfd; + +public: + WiFiClientSocketHandle(int fd):sockfd(fd) + { + } + + ~WiFiClientSocketHandle() + { + close(sockfd); + } + + int fd() + { + return sockfd; + } +}; + +WiFiClient::WiFiClient():_connected(false),next(NULL) +{ +} + +WiFiClient::WiFiClient(int fd):_connected(true),next(NULL) +{ + clientSocketHandle.reset(new WiFiClientSocketHandle(fd)); + _rxBuffer.reset(new WiFiClientRxBuffer(fd)); +} + +WiFiClient::~WiFiClient() +{ + stop(); +} + +WiFiClient & WiFiClient::operator=(const WiFiClient &other) +{ + stop(); + clientSocketHandle = other.clientSocketHandle; + _rxBuffer = other._rxBuffer; + _connected = other._connected; + return *this; +} + +void WiFiClient::stop() +{ + clientSocketHandle = NULL; + _rxBuffer = NULL; + _connected = false; +} + +int WiFiClient::connect(IPAddress ip, uint16_t port) +{ + return connect(ip,port,-1); +} +int WiFiClient::connect(IPAddress ip, uint16_t port, int32_t timeout) +{ + int sockfd = socket(AF_INET, SOCK_STREAM, 0); + if (sockfd < 0) { + log_e("socket: %d", errno); + return 0; + } + fcntl( sockfd, F_SETFL, fcntl( sockfd, F_GETFL, 0 ) | O_NONBLOCK ); + + uint32_t ip_addr = ip; + struct sockaddr_in serveraddr; + memset((char *) &serveraddr, 0, sizeof(serveraddr)); + serveraddr.sin_family = AF_INET; + memcpy((void *)&serveraddr.sin_addr.s_addr, (const void *)(&ip_addr), 4); + serveraddr.sin_port = htons(port); + fd_set fdset; + struct timeval tv; + FD_ZERO(&fdset); + FD_SET(sockfd, &fdset); + tv.tv_sec = 0; + tv.tv_usec = timeout * 1000; + + int res = lwip_connect_r(sockfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)); + if (res < 0 && errno != EINPROGRESS) { + log_e("connect on fd %d, errno: %d, \"%s\"", sockfd, errno, strerror(errno)); + close(sockfd); + return 0; + } + + res = select(sockfd + 1, nullptr, &fdset, nullptr, timeout<0 ? nullptr : &tv); + if (res < 0) { + log_e("select on fd %d, errno: %d, \"%s\"", sockfd, errno, strerror(errno)); + close(sockfd); + return 0; + } else if (res == 0) { + log_i("select returned due to timeout %d ms for fd %d", timeout, sockfd); + close(sockfd); + return 0; + } else { + int sockerr; + socklen_t len = (socklen_t)sizeof(int); + res = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &sockerr, &len); + + if (res < 0) { + log_e("getsockopt on fd %d, errno: %d, \"%s\"", sockfd, errno, strerror(errno)); + close(sockfd); + return 0; + } + + if (sockerr != 0) { + log_e("socket error on fd %d, errno: %d, \"%s\"", sockfd, sockerr, strerror(sockerr)); + close(sockfd); + return 0; + } + } + + fcntl( sockfd, F_SETFL, fcntl( sockfd, F_GETFL, 0 ) & (~O_NONBLOCK) ); + clientSocketHandle.reset(new WiFiClientSocketHandle(sockfd)); + _rxBuffer.reset(new WiFiClientRxBuffer(sockfd)); + _connected = true; + return 1; +} + +int WiFiClient::connect(const char *host, uint16_t port) +{ + return connect(host,port,-1); +} +int WiFiClient::connect(const char *host, uint16_t port, int32_t timeout) +{ + IPAddress srv((uint32_t)0); + if(!WiFiGenericClass::hostByName(host, srv)){ + return 0; + } + return connect(srv, port, timeout); +} + +int WiFiClient::setSocketOption(int option, char* value, size_t len) +{ + int res = setsockopt(fd(), SOL_SOCKET, option, value, len); + if(res < 0) { + log_e("%X : %d", option, errno); + } + return res; +} + +int WiFiClient::setTimeout(uint32_t seconds) +{ + Client::setTimeout(seconds * 1000); + struct timeval tv; + tv.tv_sec = seconds; + tv.tv_usec = 0; + if(setSocketOption(SO_RCVTIMEO, (char *)&tv, sizeof(struct timeval)) < 0) { + return -1; + } + return setSocketOption(SO_SNDTIMEO, (char *)&tv, sizeof(struct timeval)); +} + +int WiFiClient::setOption(int option, int *value) +{ + int res = setsockopt(fd(), IPPROTO_TCP, option, (char *) value, sizeof(int)); + if(res < 0) { + log_e("fail on fd %d, errno: %d, \"%s\"", fd(), errno, strerror(errno)); + } + return res; +} + +int WiFiClient::getOption(int option, int *value) +{ + size_t size = sizeof(int); + int res = getsockopt(fd(), IPPROTO_TCP, option, (char *)value, &size); + if(res < 0) { + log_e("fail on fd %d, errno: %d, \"%s\"", fd(), errno, strerror(errno)); + } + return res; +} + +int WiFiClient::setNoDelay(bool nodelay) +{ + int flag = nodelay; + return setOption(TCP_NODELAY, &flag); +} + +bool WiFiClient::getNoDelay() +{ + int flag = 0; + getOption(TCP_NODELAY, &flag); + return flag; +} + +size_t WiFiClient::write(uint8_t data) +{ + return write(&data, 1); +} + +int WiFiClient::read() +{ + uint8_t data = 0; + int res = read(&data, 1); + if(res < 0) { + return res; + } + if (res == 0) { // No data available. + return -1; + } + return data; +} + +size_t WiFiClient::write(const uint8_t *buf, size_t size) +{ + int res =0; + int retry = WIFI_CLIENT_MAX_WRITE_RETRY; + int socketFileDescriptor = fd(); + size_t totalBytesSent = 0; + size_t bytesRemaining = size; + + if(!_connected || (socketFileDescriptor < 0)) { + return 0; + } + + while(retry) { + //use select to make sure the socket is ready for writing + fd_set set; + struct timeval tv; + FD_ZERO(&set); // empties the set + FD_SET(socketFileDescriptor, &set); // adds FD to the set + tv.tv_sec = 0; + tv.tv_usec = WIFI_CLIENT_SELECT_TIMEOUT_US; + retry--; + + if(select(socketFileDescriptor + 1, NULL, &set, NULL, &tv) < 0) { + return 0; + } + + if(FD_ISSET(socketFileDescriptor, &set)) { + res = send(socketFileDescriptor, (void*) buf, bytesRemaining, MSG_DONTWAIT); + if(res > 0) { + totalBytesSent += res; + if (totalBytesSent >= size) { + //completed successfully + retry = 0; + } else { + buf += res; + bytesRemaining -= res; + retry = WIFI_CLIENT_MAX_WRITE_RETRY; + } + } + else if(res < 0) { + log_e("fail on fd %d, errno: %d, \"%s\"", fd(), errno, strerror(errno)); + if(errno != EAGAIN) { + //if resource was busy, can try again, otherwise give up + stop(); + res = 0; + retry = 0; + } + } + else { + // Try again + } + } + } + return totalBytesSent; +} + +size_t WiFiClient::write_P(PGM_P buf, size_t size) +{ + return write(buf, size); +} + +size_t WiFiClient::write(Stream &stream) +{ + uint8_t * buf = (uint8_t *)malloc(1360); + if(!buf){ + return 0; + } + size_t toRead = 0, toWrite = 0, written = 0; + size_t available = stream.available(); + while(available){ + toRead = (available > 1360)?1360:available; + toWrite = stream.readBytes(buf, toRead); + written += write(buf, toWrite); + available = stream.available(); + } + free(buf); + return written; +} + +int WiFiClient::read(uint8_t *buf, size_t size) +{ + int res = -1; + res = _rxBuffer->read(buf, size); + if(_rxBuffer->failed()) { + log_e("fail on fd %d, errno: %d, \"%s\"", fd(), errno, strerror(errno)); + stop(); + } + return res; +} + +int WiFiClient::peek() +{ + int res = _rxBuffer->peek(); + if(_rxBuffer->failed()) { + log_e("fail on fd %d, errno: %d, \"%s\"", fd(), errno, strerror(errno)); + stop(); + } + return res; +} + +int WiFiClient::available() +{ + if(!_rxBuffer) + { + return 0; + } + int res = _rxBuffer->available(); + if(_rxBuffer->failed()) { + log_e("fail on fd %d, errno: %d, \"%s\"", fd(), errno, strerror(errno)); + stop(); + } + return res; +} + +// Though flushing means to send all pending data, +// seems that in Arduino it also means to clear RX +void WiFiClient::flush() { + int res; + size_t a = available(), toRead = 0; + if(!a){ + return;//nothing to flush + } + uint8_t * buf = (uint8_t *)malloc(WIFI_CLIENT_FLUSH_BUFFER_SIZE); + if(!buf){ + return;//memory error + } + while(a){ + toRead = (a>WIFI_CLIENT_FLUSH_BUFFER_SIZE)?WIFI_CLIENT_FLUSH_BUFFER_SIZE:a; + res = recv(fd(), buf, toRead, MSG_DONTWAIT); + if(res < 0) { + log_e("fail on fd %d, errno: %d, \"%s\"", fd(), errno, strerror(errno)); + stop(); + break; + } + a -= res; + } + free(buf); +} + +uint8_t WiFiClient::connected() +{ + if (_connected) { + uint8_t dummy; + int res = recv(fd(), &dummy, 0, MSG_DONTWAIT); + // avoid unused var warning by gcc + (void)res; + // recv only sets errno if res is <= 0 + if (res <= 0){ + switch (errno) { + case EWOULDBLOCK: + case ENOENT: //caused by vfs + _connected = true; + break; + case ENOTCONN: + case EPIPE: + case ECONNRESET: + case ECONNREFUSED: + case ECONNABORTED: + _connected = false; + log_d("Disconnected: RES: %d, ERR: %d", res, errno); + break; + default: + log_i("Unexpected: RES: %d, ERR: %d", res, errno); + _connected = true; + break; + } + } else { + _connected = true; + } + } + return _connected; +} + +IPAddress WiFiClient::remoteIP(int fd) const +{ + struct sockaddr_storage addr; + socklen_t len = sizeof addr; + getpeername(fd, (struct sockaddr*)&addr, &len); + struct sockaddr_in *s = (struct sockaddr_in *)&addr; + return IPAddress((uint32_t)(s->sin_addr.s_addr)); +} + +uint16_t WiFiClient::remotePort(int fd) const +{ + struct sockaddr_storage addr; + socklen_t len = sizeof addr; + getpeername(fd, (struct sockaddr*)&addr, &len); + struct sockaddr_in *s = (struct sockaddr_in *)&addr; + return ntohs(s->sin_port); +} + +IPAddress WiFiClient::remoteIP() const +{ + return remoteIP(fd()); +} + +uint16_t WiFiClient::remotePort() const +{ + return remotePort(fd()); +} + +IPAddress WiFiClient::localIP(int fd) const +{ + struct sockaddr_storage addr; + socklen_t len = sizeof addr; + getsockname(fd, (struct sockaddr*)&addr, &len); + struct sockaddr_in *s = (struct sockaddr_in *)&addr; + return IPAddress((uint32_t)(s->sin_addr.s_addr)); +} + +uint16_t WiFiClient::localPort(int fd) const +{ + struct sockaddr_storage addr; + socklen_t len = sizeof addr; + getsockname(fd, (struct sockaddr*)&addr, &len); + struct sockaddr_in *s = (struct sockaddr_in *)&addr; + return ntohs(s->sin_port); +} + +IPAddress WiFiClient::localIP() const +{ + return localIP(fd()); +} + +uint16_t WiFiClient::localPort() const +{ + return localPort(fd()); +} + +bool WiFiClient::operator==(const WiFiClient& rhs) +{ + return clientSocketHandle == rhs.clientSocketHandle && remotePort() == rhs.remotePort() && remoteIP() == rhs.remoteIP(); +} + +int WiFiClient::fd() const +{ + if (clientSocketHandle == NULL) { + return -1; + } else { + return clientSocketHandle->fd(); + } +} + diff --git a/libraries/OASIS-PAW_ESP32/src/WiFiClient.h b/libraries/OASIS-PAW_ESP32/src/WiFiClient.h new file mode 100644 index 0000000..48b56d3 --- /dev/null +++ b/libraries/OASIS-PAW_ESP32/src/WiFiClient.h @@ -0,0 +1,108 @@ +/* + Client.h - Base class that provides Client + Copyright (c) 2011 Adrian McEwen. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _WIFICLIENT_H_ +#define _WIFICLIENT_H_ + + +#include "Arduino.h" +#include "Client.h" +#include + +class WiFiClientSocketHandle; +class WiFiClientRxBuffer; + +class ESPLwIPClient : public Client +{ +public: + virtual int connect(IPAddress ip, uint16_t port, int32_t timeout) = 0; + virtual int connect(const char *host, uint16_t port, int32_t timeout) = 0; + virtual int setTimeout(uint32_t seconds) = 0; +}; + +class WiFiClient : public ESPLwIPClient +{ +protected: + std::shared_ptr clientSocketHandle; + std::shared_ptr _rxBuffer; + bool _connected; + +public: + WiFiClient *next; + WiFiClient(); + WiFiClient(int fd); + ~WiFiClient(); + int connect(IPAddress ip, uint16_t port); + int connect(IPAddress ip, uint16_t port, int32_t timeout); + int connect(const char *host, uint16_t port); + int connect(const char *host, uint16_t port, int32_t timeout); + size_t write(uint8_t data); + size_t write(const uint8_t *buf, size_t size); + size_t write_P(PGM_P buf, size_t size); + size_t write(Stream &stream); + int available(); + int read(); + int read(uint8_t *buf, size_t size); + int peek(); + void flush(); + void stop(); + uint8_t connected(); + + operator bool() + { + return connected(); + } + WiFiClient & operator=(const WiFiClient &other); + bool operator==(const bool value) + { + return bool() == value; + } + bool operator!=(const bool value) + { + return bool() != value; + } + bool operator==(const WiFiClient&); + bool operator!=(const WiFiClient& rhs) + { + return !this->operator==(rhs); + }; + + int fd() const; + + int setSocketOption(int option, char* value, size_t len); + int setOption(int option, int *value); + int getOption(int option, int *value); + int setTimeout(uint32_t seconds); + int setNoDelay(bool nodelay); + bool getNoDelay(); + + IPAddress remoteIP() const; + IPAddress remoteIP(int fd) const; + uint16_t remotePort() const; + uint16_t remotePort(int fd) const; + IPAddress localIP() const; + IPAddress localIP(int fd) const; + uint16_t localPort() const; + uint16_t localPort(int fd) const; + + //friend class WiFiServer; + using Print::write; +}; + +#endif /* _WIFICLIENT_H_ */ diff --git a/libraries/OASIS-PAW_ESP32/src/WiFiGeneric.cpp b/libraries/OASIS-PAW_ESP32/src/WiFiGeneric.cpp new file mode 100644 index 0000000..8853b32 --- /dev/null +++ b/libraries/OASIS-PAW_ESP32/src/WiFiGeneric.cpp @@ -0,0 +1,785 @@ +/* + ESP8266WiFiGeneric.cpp - WiFi library for esp8266 + + Copyright (c) 2014 Ivan Grokhotkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Reworked on 28 Dec 2015 by Markus Sattler + + */ + +#include "WiFi.h" +#include "WiFiGeneric.h" + +extern "C" { +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "lwip/ip_addr.h" +#include "lwip/opt.h" +#include "lwip/err.h" +#include "lwip/dns.h" +#include "esp_ipc.h" + +} //extern "C" + +#include "esp32-hal-log.h" +#include +#include "sdkconfig.h" + +static xQueueHandle _network_event_queue; +static TaskHandle_t _network_event_task_handle = NULL; +static EventGroupHandle_t _network_event_group = NULL; + +esp_err_t postToSysQueue(system_prov_event_t *data) +{ + if (xQueueSend(_network_event_queue, &data, portMAX_DELAY) != pdPASS) { + log_w("Network Event Queue Send Failed!"); + return ESP_FAIL; + } + return ESP_OK; +} + +static void _network_event_task(void * arg){ + system_prov_event_t *data; + for (;;) { + if(xQueueReceive(_network_event_queue, &data, portMAX_DELAY) == pdTRUE){ + if(data->prov_event != NULL){ + WiFiGenericClass::_eventCallback(arg, data->sys_event, data->prov_event); + free(data->sys_event); + free(data->prov_event); + } else { + WiFiGenericClass::_eventCallback(arg, data->sys_event, NULL); + } + free(data); + } + } + vTaskDelete(NULL); + _network_event_task_handle = NULL; +} + +static esp_err_t _network_event_cb(void *arg, system_event_t *event){ + system_prov_event_t *sys_prov_data = (system_prov_event_t *)malloc(sizeof(system_prov_event_t)); + if(sys_prov_data == NULL) { + return ESP_FAIL; + } + sys_prov_data->sys_event = event; + sys_prov_data->prov_event = NULL; + if (postToSysQueue(sys_prov_data) != ESP_OK){ + free(sys_prov_data); + return ESP_FAIL; + } + return ESP_OK; +} + +static bool _start_network_event_task(){ + if(!_network_event_group){ + _network_event_group = xEventGroupCreate(); + if(!_network_event_group){ + log_e("Network Event Group Create Failed!"); + return false; + } + xEventGroupSetBits(_network_event_group, WIFI_DNS_IDLE_BIT); + } + if(!_network_event_queue){ + _network_event_queue = xQueueCreate(32, sizeof(system_prov_event_t)); + if(!_network_event_queue){ + log_e("Network Event Queue Create Failed!"); + return false; + } + } + if(!_network_event_task_handle){ + xTaskCreateUniversal(_network_event_task, "network_event", 4096, NULL, ESP_TASKD_EVENT_PRIO - 1, &_network_event_task_handle, CONFIG_ARDUINO_EVENT_RUNNING_CORE); + if(!_network_event_task_handle){ + log_e("Network Event Task Start Failed!"); + return false; + } + } + return esp_event_loop_init(&_network_event_cb, NULL) == ESP_OK; +} + +void tcpipInit(){ + static bool initialized = false; + if(!initialized && _start_network_event_task()){ + initialized = true; + tcpip_adapter_init(); + } +} + +static bool lowLevelInitDone = false; +static bool wifiLowLevelInit(bool persistent){ + if(!lowLevelInitDone){ + tcpipInit(); + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + esp_err_t err = esp_wifi_init(&cfg); + if(err){ + log_e("esp_wifi_init %d", err); + return false; + } + if(!persistent){ + esp_wifi_set_storage(WIFI_STORAGE_RAM); + } + lowLevelInitDone = true; + } + return true; +} + +static bool wifiLowLevelDeinit(){ + //deinit not working yet! + //esp_wifi_deinit(); + return true; +} + +static bool _esp_wifi_started = false; + +static bool espWiFiStart(){ + if(_esp_wifi_started){ + return true; + } + esp_err_t err = esp_wifi_start(); + if (err != ESP_OK) { + log_e("esp_wifi_start %d", err); + return false; + } + _esp_wifi_started = true; + system_event_t event; + event.event_id = SYSTEM_EVENT_WIFI_READY; + WiFiGenericClass::_eventCallback(nullptr, &event, NULL); + return true; +} + +static bool espWiFiStop(){ + esp_err_t err; + if(!_esp_wifi_started){ + return true; + } + _esp_wifi_started = false; + err = esp_wifi_stop(); + if(err){ + log_e("Could not stop WiFi! %d", err); + _esp_wifi_started = true; + return false; + } + return wifiLowLevelDeinit(); +} + +// ----------------------------------------------------------------------------------------------------------------------- +// ------------------------------------------------- Generic WiFi function ----------------------------------------------- +// ----------------------------------------------------------------------------------------------------------------------- + +typedef struct WiFiEventCbList { + static wifi_event_id_t current_id; + wifi_event_id_t id; + WiFiEventCb cb; + WiFiEventFuncCb fcb; + WiFiEventSysCb scb; + WiFiProvEventCb provcb; + system_event_id_t event; + + WiFiEventCbList() : id(current_id++), cb(NULL), fcb(NULL), scb(NULL), provcb(NULL), event(SYSTEM_EVENT_WIFI_READY) {} +} WiFiEventCbList_t; +wifi_event_id_t WiFiEventCbList::current_id = 1; + + +// arduino dont like std::vectors move static here +static std::vector cbEventList; + +bool WiFiGenericClass::_persistent = true; +bool WiFiGenericClass::_long_range = false; +wifi_mode_t WiFiGenericClass::_forceSleepLastMode = WIFI_MODE_NULL; + +WiFiGenericClass::WiFiGenericClass() +{ + +} + +int WiFiGenericClass::setStatusBits(int bits){ + if(!_network_event_group){ + return 0; + } + return xEventGroupSetBits(_network_event_group, bits); +} + +int WiFiGenericClass::clearStatusBits(int bits){ + if(!_network_event_group){ + return 0; + } + return xEventGroupClearBits(_network_event_group, bits); +} + +int WiFiGenericClass::getStatusBits(){ + if(!_network_event_group){ + return 0; + } + return xEventGroupGetBits(_network_event_group); +} + +int WiFiGenericClass::waitStatusBits(int bits, uint32_t timeout_ms){ + if(!_network_event_group){ + return 0; + } + return xEventGroupWaitBits( + _network_event_group, // The event group being tested. + bits, // The bits within the event group to wait for. + pdFALSE, // BIT_0 and BIT_4 should be cleared before returning. + pdTRUE, // Don't wait for both bits, either bit will do. + timeout_ms / portTICK_PERIOD_MS ) & bits; // Wait a maximum of 100ms for either bit to be set. +} + +/** + * set callback function + * @param cbEvent WiFiEventCb + * @param event optional filter (WIFI_EVENT_MAX is all events) + */ +wifi_event_id_t WiFiGenericClass::onEvent(WiFiProvEventCb cbEvent, system_event_id_t event) +{ + if(!cbEvent){ + return 0; + } + WiFiEventCbList_t newEventHandler; + newEventHandler.cb = NULL; + newEventHandler.fcb = NULL; + newEventHandler.scb = NULL; + newEventHandler.provcb = cbEvent; + newEventHandler.event = event; + cbEventList.push_back(newEventHandler); + return newEventHandler.id; +} +wifi_event_id_t WiFiGenericClass::onEvent(WiFiEventCb cbEvent, system_event_id_t event) +{ + if(!cbEvent) { + return 0; + } + WiFiEventCbList_t newEventHandler; + newEventHandler.cb = cbEvent; + newEventHandler.fcb = NULL; + newEventHandler.scb = NULL; + newEventHandler.provcb = NULL; + newEventHandler.event = event; + cbEventList.push_back(newEventHandler); + return newEventHandler.id; +} + +wifi_event_id_t WiFiGenericClass::onEvent(WiFiEventFuncCb cbEvent, system_event_id_t event) +{ + if(!cbEvent) { + return 0; + } + WiFiEventCbList_t newEventHandler; + newEventHandler.cb = NULL; + newEventHandler.fcb = cbEvent; + newEventHandler.scb = NULL; + newEventHandler.provcb = NULL; + newEventHandler.event = event; + cbEventList.push_back(newEventHandler); + return newEventHandler.id; +} + +wifi_event_id_t WiFiGenericClass::onEvent(WiFiEventSysCb cbEvent, system_event_id_t event) +{ + if(!cbEvent) { + return 0; + } + WiFiEventCbList_t newEventHandler; + newEventHandler.cb = NULL; + newEventHandler.fcb = NULL; + newEventHandler.scb = cbEvent; + newEventHandler.provcb = NULL; + newEventHandler.event = event; + cbEventList.push_back(newEventHandler); + return newEventHandler.id; +} + +/** + * removes a callback form event handler + * @param cbEvent WiFiEventCb + * @param event optional filter (WIFI_EVENT_MAX is all events) + */ +void WiFiGenericClass::removeEvent(WiFiEventCb cbEvent, system_event_id_t event) +{ + if(!cbEvent) { + return; + } + + for(uint32_t i = 0; i < cbEventList.size(); i++) { + WiFiEventCbList_t entry = cbEventList[i]; + if(entry.cb == cbEvent && entry.event == event) { + cbEventList.erase(cbEventList.begin() + i); + } + } +} + +void WiFiGenericClass::removeEvent(WiFiEventSysCb cbEvent, system_event_id_t event) +{ + if(!cbEvent) { + return; + } + + for(uint32_t i = 0; i < cbEventList.size(); i++) { + WiFiEventCbList_t entry = cbEventList[i]; + if(entry.scb == cbEvent && entry.event == event) { + cbEventList.erase(cbEventList.begin() + i); + } + } +} + +void WiFiGenericClass::removeEvent(wifi_event_id_t id) +{ + for(uint32_t i = 0; i < cbEventList.size(); i++) { + WiFiEventCbList_t entry = cbEventList[i]; + if(entry.id == id) { + cbEventList.erase(cbEventList.begin() + i); + } + } +} + +/** + * callback for WiFi events + * @param arg + */ +#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_DEBUG +const char * system_event_names[] = { "WIFI_READY", "SCAN_DONE", "STA_START", "STA_STOP", "STA_CONNECTED", "STA_DISCONNECTED", "STA_AUTHMODE_CHANGE", "STA_GOT_IP", "STA_LOST_IP", "STA_WPS_ER_SUCCESS", "STA_WPS_ER_FAILED", "STA_WPS_ER_TIMEOUT", "STA_WPS_ER_PIN", "STA_WPS_ER_PBC_OVERLAP", "AP_START", "AP_STOP", "AP_STACONNECTED", "AP_STADISCONNECTED", "AP_STAIPASSIGNED", "AP_PROBEREQRECVED", "GOT_IP6", "ETH_START", "ETH_STOP", "ETH_CONNECTED", "ETH_DISCONNECTED", "ETH_GOT_IP", "MAX"}; +#endif +#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_WARN +const char * system_event_reasons[] = { "UNSPECIFIED", "AUTH_EXPIRE", "AUTH_LEAVE", "ASSOC_EXPIRE", "ASSOC_TOOMANY", "NOT_AUTHED", "NOT_ASSOCED", "ASSOC_LEAVE", "ASSOC_NOT_AUTHED", "DISASSOC_PWRCAP_BAD", "DISASSOC_SUPCHAN_BAD", "UNSPECIFIED", "IE_INVALID", "MIC_FAILURE", "4WAY_HANDSHAKE_TIMEOUT", "GROUP_KEY_UPDATE_TIMEOUT", "IE_IN_4WAY_DIFFERS", "GROUP_CIPHER_INVALID", "PAIRWISE_CIPHER_INVALID", "AKMP_INVALID", "UNSUPP_RSN_IE_VERSION", "INVALID_RSN_IE_CAP", "802_1X_AUTH_FAILED", "CIPHER_SUITE_REJECTED", "BEACON_TIMEOUT", "NO_AP_FOUND", "AUTH_FAIL", "ASSOC_FAIL", "HANDSHAKE_TIMEOUT", "CONNECTION_FAIL" }; +#define reason2str(r) ((r>176)?system_event_reasons[r-176]:system_event_reasons[r-1]) +#endif +esp_err_t WiFiGenericClass::_eventCallback(void *arg, system_event_t *event, wifi_prov_event_t *prov_event) +{ + if(WiFi.isProvEnabled()) { + wifi_prov_mgr_event_handler(arg,event); + } + if(event->event_id < 26) { + log_d("Event: %d - %s", event->event_id, system_event_names[event->event_id]); + } + if(event->event_id == SYSTEM_EVENT_SCAN_DONE) { + WiFiScanClass::_scanDone(); + + } else if(event->event_id == SYSTEM_EVENT_STA_START) { + WiFiSTAClass::_setStatus(WL_DISCONNECTED); + setStatusBits(STA_STARTED_BIT); + tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_STA, WiFiSTAClass::_hostname.c_str()); + } else if(event->event_id == SYSTEM_EVENT_STA_STOP) { + WiFiSTAClass::_setStatus(WL_NO_SHIELD); + clearStatusBits(STA_STARTED_BIT | STA_CONNECTED_BIT | STA_HAS_IP_BIT | STA_HAS_IP6_BIT); + } else if(event->event_id == SYSTEM_EVENT_STA_CONNECTED) { + WiFiSTAClass::_setStatus(WL_IDLE_STATUS); + setStatusBits(STA_CONNECTED_BIT); + } else if(event->event_id == SYSTEM_EVENT_STA_DISCONNECTED) { + uint8_t reason = event->event_info.disconnected.reason; + log_w("Reason: %u - %s", reason, reason2str(reason)); + if(reason == WIFI_REASON_NO_AP_FOUND) { + WiFiSTAClass::_setStatus(WL_NO_SSID_AVAIL); + } else if(reason == WIFI_REASON_AUTH_FAIL || reason == WIFI_REASON_ASSOC_FAIL) { + WiFiSTAClass::_setStatus(WL_CONNECT_FAILED); + } else if(reason == WIFI_REASON_BEACON_TIMEOUT || reason == WIFI_REASON_HANDSHAKE_TIMEOUT) { + WiFiSTAClass::_setStatus(WL_CONNECTION_LOST); + } else if(reason == WIFI_REASON_AUTH_EXPIRE) { + + } else { + WiFiSTAClass::_setStatus(WL_DISCONNECTED); + } + clearStatusBits(STA_CONNECTED_BIT | STA_HAS_IP_BIT | STA_HAS_IP6_BIT); + if(((reason == WIFI_REASON_AUTH_EXPIRE) || + (reason >= WIFI_REASON_BEACON_TIMEOUT && reason != WIFI_REASON_AUTH_FAIL)) && + WiFi.getAutoReconnect()) + { + WiFi.disconnect(); + WiFi.begin(); + } + } else if(event->event_id == SYSTEM_EVENT_STA_GOT_IP) { +#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_DEBUG + uint8_t * ip = (uint8_t *)&(event->event_info.got_ip.ip_info.ip.addr); + uint8_t * mask = (uint8_t *)&(event->event_info.got_ip.ip_info.netmask.addr); + uint8_t * gw = (uint8_t *)&(event->event_info.got_ip.ip_info.gw.addr); + log_d("STA IP: %u.%u.%u.%u, MASK: %u.%u.%u.%u, GW: %u.%u.%u.%u", + ip[0], ip[1], ip[2], ip[3], + mask[0], mask[1], mask[2], mask[3], + gw[0], gw[1], gw[2], gw[3]); +#endif + WiFiSTAClass::_setStatus(WL_CONNECTED); + setStatusBits(STA_HAS_IP_BIT | STA_CONNECTED_BIT); + } else if(event->event_id == SYSTEM_EVENT_STA_LOST_IP) { + WiFiSTAClass::_setStatus(WL_IDLE_STATUS); + clearStatusBits(STA_HAS_IP_BIT); + + } else if(event->event_id == SYSTEM_EVENT_AP_START) { + setStatusBits(AP_STARTED_BIT); + } else if(event->event_id == SYSTEM_EVENT_AP_STOP) { + clearStatusBits(AP_STARTED_BIT | AP_HAS_CLIENT_BIT); + } else if(event->event_id == SYSTEM_EVENT_AP_STACONNECTED) { + setStatusBits(AP_HAS_CLIENT_BIT); + } else if(event->event_id == SYSTEM_EVENT_AP_STADISCONNECTED) { + wifi_sta_list_t clients; + if(esp_wifi_ap_get_sta_list(&clients) != ESP_OK || !clients.num){ + clearStatusBits(AP_HAS_CLIENT_BIT); + } + + } else if(event->event_id == SYSTEM_EVENT_ETH_START) { + setStatusBits(ETH_STARTED_BIT); + } else if(event->event_id == SYSTEM_EVENT_ETH_STOP) { + clearStatusBits(ETH_STARTED_BIT | ETH_CONNECTED_BIT | ETH_HAS_IP_BIT | ETH_HAS_IP6_BIT); + } else if(event->event_id == SYSTEM_EVENT_ETH_CONNECTED) { + setStatusBits(ETH_CONNECTED_BIT); + } else if(event->event_id == SYSTEM_EVENT_ETH_DISCONNECTED) { + clearStatusBits(ETH_CONNECTED_BIT | ETH_HAS_IP_BIT | ETH_HAS_IP6_BIT); + } else if(event->event_id == SYSTEM_EVENT_ETH_GOT_IP) { +#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_DEBUG + uint8_t * ip = (uint8_t *)&(event->event_info.got_ip.ip_info.ip.addr); + uint8_t * mask = (uint8_t *)&(event->event_info.got_ip.ip_info.netmask.addr); + uint8_t * gw = (uint8_t *)&(event->event_info.got_ip.ip_info.gw.addr); + log_d("ETH IP: %u.%u.%u.%u, MASK: %u.%u.%u.%u, GW: %u.%u.%u.%u", + ip[0], ip[1], ip[2], ip[3], + mask[0], mask[1], mask[2], mask[3], + gw[0], gw[1], gw[2], gw[3]); +#endif + setStatusBits(ETH_CONNECTED_BIT | ETH_HAS_IP_BIT); + + } else if(event->event_id == SYSTEM_EVENT_GOT_IP6) { + if(event->event_info.got_ip6.if_index == TCPIP_ADAPTER_IF_AP){ + setStatusBits(AP_HAS_IP6_BIT); + } else if(event->event_info.got_ip6.if_index == TCPIP_ADAPTER_IF_STA){ + setStatusBits(STA_CONNECTED_BIT | STA_HAS_IP6_BIT); + } else if(event->event_info.got_ip6.if_index == TCPIP_ADAPTER_IF_ETH){ + setStatusBits(ETH_CONNECTED_BIT | ETH_HAS_IP6_BIT); + } + } + + for(uint32_t i = 0; i < cbEventList.size(); i++) { + WiFiEventCbList_t entry = cbEventList[i]; + if(entry.cb || entry.fcb || entry.scb) { + if(entry.event == (system_event_id_t) event->event_id || entry.event == SYSTEM_EVENT_MAX) { + if(entry.cb) { + entry.cb((system_event_id_t) event->event_id); + } else if(entry.fcb) { + entry.fcb((system_event_id_t) event->event_id, (system_event_info_t) event->event_info); + } else { + entry.scb(event); + } + } + } + + if(entry.provcb) { + entry.provcb(event,prov_event); + } + } + return ESP_OK; +} + +/** + * Return the current channel associated with the network + * @return channel (1-13) + */ +int32_t WiFiGenericClass::channel(void) +{ + uint8_t primaryChan = 0; + wifi_second_chan_t secondChan = WIFI_SECOND_CHAN_NONE; + if(!lowLevelInitDone){ + return primaryChan; + } + esp_wifi_get_channel(&primaryChan, &secondChan); + return primaryChan; +} + + +/** + * store WiFi config in SDK flash area + * @param persistent + */ +void WiFiGenericClass::persistent(bool persistent) +{ + _persistent = persistent; +} + + +/** + * enable WiFi long range mode + * @param enable + */ +void WiFiGenericClass::enableLongRange(bool enable) +{ + _long_range = enable; +} + + +/** + * set new mode + * @param m WiFiMode_t + */ +bool WiFiGenericClass::mode(wifi_mode_t m) +{ + wifi_mode_t cm = getMode(); + if(cm == m) { + return true; + } + if(!cm && m){ + if(!wifiLowLevelInit(_persistent)){ + return false; + } + } else if(cm && !m){ + return espWiFiStop(); + } + + esp_err_t err; + err = esp_wifi_set_mode(m); + if(err){ + log_e("Could not set mode! %d", err); + return false; + } + if(_long_range){ + if(m & WIFI_MODE_STA){ + err = esp_wifi_set_protocol(WIFI_IF_STA, WIFI_PROTOCOL_LR); + if(err != ESP_OK){ + log_e("Could not enable long range on STA! %d", err); + return false; + } + } + if(m & WIFI_MODE_AP){ + err = esp_wifi_set_protocol(WIFI_IF_AP, WIFI_PROTOCOL_LR); + if(err != ESP_OK){ + log_e("Could not enable long range on AP! %d", err); + return false; + } + } + } + if(!espWiFiStart()){ + return false; + } + return true; +} + +/** + * get WiFi mode + * @return WiFiMode + */ +wifi_mode_t WiFiGenericClass::getMode() +{ + if(!lowLevelInitDone || !_esp_wifi_started){ + return WIFI_MODE_NULL; + } + wifi_mode_t mode; + if(esp_wifi_get_mode(&mode) == ESP_ERR_WIFI_NOT_INIT){ + log_w("WiFi not started"); + return WIFI_MODE_NULL; + } + return mode; +} + +/** + * control STA mode + * @param enable bool + * @return ok + */ +bool WiFiGenericClass::enableSTA(bool enable) +{ + + wifi_mode_t currentMode = getMode(); + bool isEnabled = ((currentMode & WIFI_MODE_STA) != 0); + + if(isEnabled != enable) { + if(enable) { + return mode((wifi_mode_t)(currentMode | WIFI_MODE_STA)); + } + return mode((wifi_mode_t)(currentMode & (~WIFI_MODE_STA))); + } + return true; +} + +/** + * control AP mode + * @param enable bool + * @return ok + */ +bool WiFiGenericClass::enableAP(bool enable) +{ + + wifi_mode_t currentMode = getMode(); + bool isEnabled = ((currentMode & WIFI_MODE_AP) != 0); + + if(isEnabled != enable) { + if(enable) { + return mode((wifi_mode_t)(currentMode | WIFI_MODE_AP)); + } + return mode((wifi_mode_t)(currentMode & (~WIFI_MODE_AP))); + } + return true; +} + +/** + * control modem sleep when only in STA mode + * @param enable bool + * @return ok + */ +bool WiFiGenericClass::setSleep(bool enable) +{ + if((getMode() & WIFI_MODE_STA) == 0){ + log_w("STA has not been started"); + return false; + } + return esp_wifi_set_ps(enable?WIFI_PS_MIN_MODEM:WIFI_PS_NONE) == ESP_OK; +} + +/** + * control modem sleep when only in STA mode + * @param mode wifi_ps_type_t + * @return ok + */ +bool WiFiGenericClass::setSleep(wifi_ps_type_t mode) +{ + if((getMode() & WIFI_MODE_STA) == 0){ + log_w("STA has not been started"); + return false; + } + return esp_wifi_set_ps(mode) == ESP_OK; +} + +/** + * get modem sleep enabled + * @return true if modem sleep is enabled + */ +bool WiFiGenericClass::getSleep() +{ + wifi_ps_type_t ps; + if((getMode() & WIFI_MODE_STA) == 0){ + log_w("STA has not been started"); + return false; + } + if(esp_wifi_get_ps(&ps) == ESP_OK){ + return ps == WIFI_PS_MIN_MODEM; + } + return false; +} + +/** + * control wifi tx power + * @param power enum maximum wifi tx power + * @return ok + */ +bool WiFiGenericClass::setTxPower(wifi_power_t power){ + if((getStatusBits() & (STA_STARTED_BIT | AP_STARTED_BIT)) == 0){ + log_w("Neither AP or STA has been started"); + return false; + } + return esp_wifi_set_max_tx_power(power) == ESP_OK; +} + +wifi_power_t WiFiGenericClass::getTxPower(){ + int8_t power; + if((getStatusBits() & (STA_STARTED_BIT | AP_STARTED_BIT)) == 0){ + log_w("Neither AP or STA has been started"); + return WIFI_POWER_19_5dBm; + } + if(esp_wifi_get_max_tx_power(&power)){ + return WIFI_POWER_19_5dBm; + } + return (wifi_power_t)power; +} + +// ----------------------------------------------------------------------------------------------------------------------- +// ------------------------------------------------ Generic Network function --------------------------------------------- +// ----------------------------------------------------------------------------------------------------------------------- + +/** + * DNS callback + * @param name + * @param ipaddr + * @param callback_arg + */ +static void wifi_dns_found_callback(const char *name, const ip_addr_t *ipaddr, void *callback_arg) +{ + if(ipaddr) { + (*reinterpret_cast(callback_arg)) = ipaddr->u_addr.ip4.addr; + } + xEventGroupSetBits(_network_event_group, WIFI_DNS_DONE_BIT); +} + +/** + * Resolve the given hostname to an IP address. + * @param aHostname Name to be resolved + * @param aResult IPAddress structure to store the returned IP address + * @return 1 if aIPAddrString was successfully converted to an IP address, + * else error code + */ +int WiFiGenericClass::hostByName(const char* aHostname, IPAddress& aResult) +{ + ip_addr_t addr; + aResult = static_cast(0); + waitStatusBits(WIFI_DNS_IDLE_BIT, 16000); + clearStatusBits(WIFI_DNS_IDLE_BIT | WIFI_DNS_DONE_BIT); + err_t err = dns_gethostbyname(aHostname, &addr, &wifi_dns_found_callback, &aResult); + if(err == ERR_OK && addr.u_addr.ip4.addr) { + aResult = addr.u_addr.ip4.addr; + } else if(err == ERR_INPROGRESS) { + waitStatusBits(WIFI_DNS_DONE_BIT, 15000); //real internal timeout in lwip library is 14[s] + clearStatusBits(WIFI_DNS_DONE_BIT); + } + setStatusBits(WIFI_DNS_IDLE_BIT); + if((uint32_t)aResult == 0){ + log_e("DNS Failed for %s", aHostname); + } + return (uint32_t)aResult != 0; +} + +IPAddress WiFiGenericClass::calculateNetworkID(IPAddress ip, IPAddress subnet) { + IPAddress networkID; + + for (size_t i = 0; i < 4; i++) + networkID[i] = subnet[i] & ip[i]; + + return networkID; +} + +IPAddress WiFiGenericClass::calculateBroadcast(IPAddress ip, IPAddress subnet) { + IPAddress broadcastIp; + + for (int i = 0; i < 4; i++) + broadcastIp[i] = ~subnet[i] | ip[i]; + + return broadcastIp; +} + +uint8_t WiFiGenericClass::calculateSubnetCIDR(IPAddress subnetMask) { + uint8_t CIDR = 0; + + for (uint8_t i = 0; i < 4; i++) { + if (subnetMask[i] == 0x80) // 128 + CIDR += 1; + else if (subnetMask[i] == 0xC0) // 192 + CIDR += 2; + else if (subnetMask[i] == 0xE0) // 224 + CIDR += 3; + else if (subnetMask[i] == 0xF0) // 242 + CIDR += 4; + else if (subnetMask[i] == 0xF8) // 248 + CIDR += 5; + else if (subnetMask[i] == 0xFC) // 252 + CIDR += 6; + else if (subnetMask[i] == 0xFE) // 254 + CIDR += 7; + else if (subnetMask[i] == 0xFF) // 255 + CIDR += 8; + } + + return CIDR; +} diff --git a/libraries/OASIS-PAW_ESP32/src/WiFiGeneric.h b/libraries/OASIS-PAW_ESP32/src/WiFiGeneric.h new file mode 100644 index 0000000..4d17aeb --- /dev/null +++ b/libraries/OASIS-PAW_ESP32/src/WiFiGeneric.h @@ -0,0 +1,140 @@ +/* + ESP8266WiFiGeneric.h - esp8266 Wifi support. + Based on WiFi.h from Ardiono WiFi shield library. + Copyright (c) 2011-2014 Arduino. All right reserved. + Modified by Ivan Grokhotkov, December 2014 + Reworked by Markus Sattler, December 2015 + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef ESP32WIFIGENERIC_H_ +#define ESP32WIFIGENERIC_H_ + +#include +#include +#include +#include "WiFiType.h" +#include "IPAddress.h" +#include + +typedef struct +{ + wifi_prov_cb_event_t event; + void *event_data; +}wifi_prov_event_t; + +typedef struct +{ + wifi_prov_event_t *prov_event; + system_event_t *sys_event; +}system_prov_event_t; + +typedef void (*WiFiEventCb)(system_event_id_t event); +typedef std::function WiFiEventFuncCb; +typedef void (*WiFiEventSysCb)(system_event_t *event); +typedef void (*WiFiProvEventCb)(system_event_t *sys_event, wifi_prov_event_t *prov_event); + +typedef size_t wifi_event_id_t; + +typedef enum { + WIFI_POWER_19_5dBm = 78,// 19.5dBm + WIFI_POWER_19dBm = 76,// 19dBm + WIFI_POWER_18_5dBm = 74,// 18.5dBm + WIFI_POWER_17dBm = 68,// 17dBm + WIFI_POWER_15dBm = 60,// 15dBm + WIFI_POWER_13dBm = 52,// 13dBm + WIFI_POWER_11dBm = 44,// 11dBm + WIFI_POWER_8_5dBm = 34,// 8.5dBm + WIFI_POWER_7dBm = 28,// 7dBm + WIFI_POWER_5dBm = 20,// 5dBm + WIFI_POWER_2dBm = 8,// 2dBm + WIFI_POWER_MINUS_1dBm = -4// -1dBm +} wifi_power_t; + +static const int AP_STARTED_BIT = BIT0; +static const int AP_HAS_IP6_BIT = BIT1; +static const int AP_HAS_CLIENT_BIT = BIT2; +static const int STA_STARTED_BIT = BIT3; +static const int STA_CONNECTED_BIT = BIT4; +static const int STA_HAS_IP_BIT = BIT5; +static const int STA_HAS_IP6_BIT = BIT6; +static const int ETH_STARTED_BIT = BIT7; +static const int ETH_CONNECTED_BIT = BIT8; +static const int ETH_HAS_IP_BIT = BIT9; +static const int ETH_HAS_IP6_BIT = BIT10; +static const int WIFI_SCANNING_BIT = BIT11; +static const int WIFI_SCAN_DONE_BIT= BIT12; +static const int WIFI_DNS_IDLE_BIT = BIT13; +static const int WIFI_DNS_DONE_BIT = BIT14; + +class WiFiGenericClass +{ + public: + WiFiGenericClass(); + + wifi_event_id_t onEvent(WiFiEventCb cbEvent, system_event_id_t event = SYSTEM_EVENT_MAX); + wifi_event_id_t onEvent(WiFiEventFuncCb cbEvent, system_event_id_t event = SYSTEM_EVENT_MAX); + wifi_event_id_t onEvent(WiFiEventSysCb cbEvent, system_event_id_t event = SYSTEM_EVENT_MAX); + wifi_event_id_t onEvent(WiFiProvEventCb cbEvent, system_event_id_t event = SYSTEM_EVENT_MAX); + void removeEvent(WiFiEventCb cbEvent, system_event_id_t event = SYSTEM_EVENT_MAX); + void removeEvent(WiFiEventSysCb cbEvent, system_event_id_t event = SYSTEM_EVENT_MAX); + void removeEvent(wifi_event_id_t id); + + static int getStatusBits(); + static int waitStatusBits(int bits, uint32_t timeout_ms); + + int32_t channel(void); + + void persistent(bool persistent); + void enableLongRange(bool enable); + + static bool mode(wifi_mode_t); + static wifi_mode_t getMode(); + + bool enableSTA(bool enable); + bool enableAP(bool enable); + + bool setSleep(bool enable); + bool setSleep(wifi_ps_type_t mode); + bool getSleep(); + + bool setTxPower(wifi_power_t power); + wifi_power_t getTxPower(); + + static esp_err_t _eventCallback(void *arg, system_event_t *event, wifi_prov_event_t *prov_event); + + protected: + static bool _persistent; + static bool _long_range; + static wifi_mode_t _forceSleepLastMode; + + static int setStatusBits(int bits); + static int clearStatusBits(int bits); + + public: + static int hostByName(const char *aHostname, IPAddress &aResult); + + static IPAddress calculateNetworkID(IPAddress ip, IPAddress subnet); + static IPAddress calculateBroadcast(IPAddress ip, IPAddress subnet); + static uint8_t calculateSubnetCIDR(IPAddress subnetMask); + + protected: + friend class WiFiSTAClass; + friend class WiFiScanClass; + friend class WiFiAPClass; +}; + +#endif /* ESP32WIFIGENERIC_H_ */ diff --git a/libraries/OASIS-PAW_ESP32/src/WiFiMulti.cpp b/libraries/OASIS-PAW_ESP32/src/WiFiMulti.cpp new file mode 100644 index 0000000..7b12aa7 --- /dev/null +++ b/libraries/OASIS-PAW_ESP32/src/WiFiMulti.cpp @@ -0,0 +1,204 @@ +/** + * + * @file WiFiMulti.cpp + * @date 16.05.2015 + * @author Markus Sattler + * + * Copyright (c) 2015 Markus Sattler. All rights reserved. + * This file is part of the esp8266 core for Arduino environment. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "WiFiMulti.h" +#include +#include +#include + +WiFiMulti::WiFiMulti() +{ +} + +WiFiMulti::~WiFiMulti() +{ + for(uint32_t i = 0; i < APlist.size(); i++) { + WifiAPlist_t entry = APlist[i]; + if(entry.ssid) { + free(entry.ssid); + } + if(entry.passphrase) { + free(entry.passphrase); + } + } + APlist.clear(); +} + +bool WiFiMulti::addAP(const char* ssid, const char *passphrase) +{ + WifiAPlist_t newAP; + + if(!ssid || *ssid == 0x00 || strlen(ssid) > 31) { + // fail SSID too long or missing! + log_e("[WIFI][APlistAdd] no ssid or ssid too long"); + return false; + } + + if(passphrase && strlen(passphrase) > 63) { + // fail passphrase too long! + log_e("[WIFI][APlistAdd] passphrase too long"); + return false; + } + + newAP.ssid = strdup(ssid); + + if(!newAP.ssid) { + log_e("[WIFI][APlistAdd] fail newAP.ssid == 0"); + return false; + } + + if(passphrase && *passphrase != 0x00) { + newAP.passphrase = strdup(passphrase); + if(!newAP.passphrase) { + log_e("[WIFI][APlistAdd] fail newAP.passphrase == 0"); + free(newAP.ssid); + return false; + } + } else { + newAP.passphrase = NULL; + } + + APlist.push_back(newAP); + log_i("[WIFI][APlistAdd] add SSID: %s", newAP.ssid); + return true; +} + +uint8_t WiFiMulti::run(uint32_t connectTimeout) +{ + int8_t scanResult; + uint8_t status = WiFi.status(); + if(status == WL_CONNECTED) { + for(uint32_t x = 0; x < APlist.size(); x++) { + if(WiFi.SSID()==APlist[x].ssid) { + return status; + } + } + WiFi.disconnect(false,false); + delay(10); + status = WiFi.status(); + } + + scanResult = WiFi.scanNetworks(); + if(scanResult == WIFI_SCAN_RUNNING) { + // scan is running + return WL_NO_SSID_AVAIL; + } else if(scanResult >= 0) { + // scan done analyze + WifiAPlist_t bestNetwork { NULL, NULL }; + int bestNetworkDb = INT_MIN; + uint8_t bestBSSID[6]; + int32_t bestChannel = 0; + + log_i("[WIFI] scan done"); + + if(scanResult == 0) { + log_e("[WIFI] no networks found"); + } else { + log_i("[WIFI] %d networks found", scanResult); + for(int8_t i = 0; i < scanResult; ++i) { + + String ssid_scan; + int32_t rssi_scan; + uint8_t sec_scan; + uint8_t* BSSID_scan; + int32_t chan_scan; + + WiFi.getNetworkInfo(i, ssid_scan, sec_scan, rssi_scan, BSSID_scan, chan_scan); + + bool known = false; + for(uint32_t x = 0; x < APlist.size(); x++) { + WifiAPlist_t entry = APlist[x]; + + if(ssid_scan == entry.ssid) { // SSID match + known = true; + if(rssi_scan > bestNetworkDb) { // best network + if(sec_scan == WIFI_AUTH_OPEN || entry.passphrase) { // check for passphrase if not open wlan + bestNetworkDb = rssi_scan; + bestChannel = chan_scan; + memcpy((void*) &bestNetwork, (void*) &entry, sizeof(bestNetwork)); + memcpy((void*) &bestBSSID, (void*) BSSID_scan, sizeof(bestBSSID)); + } + } + break; + } + } + + if(known) { + log_d(" ---> %d: [%d][%02X:%02X:%02X:%02X:%02X:%02X] %s (%d) %c", i, chan_scan, BSSID_scan[0], BSSID_scan[1], BSSID_scan[2], BSSID_scan[3], BSSID_scan[4], BSSID_scan[5], ssid_scan.c_str(), rssi_scan, (sec_scan == WIFI_AUTH_OPEN) ? ' ' : '*'); + } else { + log_d(" %d: [%d][%02X:%02X:%02X:%02X:%02X:%02X] %s (%d) %c", i, chan_scan, BSSID_scan[0], BSSID_scan[1], BSSID_scan[2], BSSID_scan[3], BSSID_scan[4], BSSID_scan[5], ssid_scan.c_str(), rssi_scan, (sec_scan == WIFI_AUTH_OPEN) ? ' ' : '*'); + } + } + } + + // clean up ram + WiFi.scanDelete(); + + if(bestNetwork.ssid) { + log_i("[WIFI] Connecting BSSID: %02X:%02X:%02X:%02X:%02X:%02X SSID: %s Channel: %d (%d)", bestBSSID[0], bestBSSID[1], bestBSSID[2], bestBSSID[3], bestBSSID[4], bestBSSID[5], bestNetwork.ssid, bestChannel, bestNetworkDb); + + WiFi.begin(bestNetwork.ssid, bestNetwork.passphrase, bestChannel, bestBSSID); + status = WiFi.status(); + + auto startTime = millis(); + // wait for connection, fail, or timeout + while(status != WL_CONNECTED && status != WL_NO_SSID_AVAIL && status != WL_CONNECT_FAILED && (millis() - startTime) <= connectTimeout) { + delay(10); + status = WiFi.status(); + } + + switch(status) { + case WL_CONNECTED: + log_i("[WIFI] Connecting done."); + log_d("[WIFI] SSID: %s", WiFi.SSID().c_str()); + log_d("[WIFI] IP: %s", WiFi.localIP().toString().c_str()); + log_d("[WIFI] MAC: %s", WiFi.BSSIDstr().c_str()); + log_d("[WIFI] Channel: %d", WiFi.channel()); + break; + case WL_NO_SSID_AVAIL: + log_e("[WIFI] Connecting Failed AP not found."); + break; + case WL_CONNECT_FAILED: + log_e("[WIFI] Connecting Failed."); + break; + default: + log_e("[WIFI] Connecting Failed (%d).", status); + break; + } + } else { + log_e("[WIFI] no matching wifi found!"); + } + } else { + // start scan + log_d("[WIFI] delete old wifi config..."); + WiFi.disconnect(); + + log_d("[WIFI] start scan"); + // scan wifi async mode + WiFi.scanNetworks(true); + } + + return status; +} diff --git a/libraries/OASIS-PAW_ESP32/src/WiFiMulti.h b/libraries/OASIS-PAW_ESP32/src/WiFiMulti.h new file mode 100644 index 0000000..168e973 --- /dev/null +++ b/libraries/OASIS-PAW_ESP32/src/WiFiMulti.h @@ -0,0 +1,51 @@ +/** + * + * @file ESP8266WiFiMulti.h + * @date 16.05.2015 + * @author Markus Sattler + * + * Copyright (c) 2015 Markus Sattler. All rights reserved. + * This file is part of the esp8266 core for Arduino environment. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef WIFICLIENTMULTI_H_ +#define WIFICLIENTMULTI_H_ + +#include "WiFi.h" +#include + +typedef struct { + char * ssid; + char * passphrase; +} WifiAPlist_t; + +class WiFiMulti +{ +public: + WiFiMulti(); + ~WiFiMulti(); + + bool addAP(const char* ssid, const char *passphrase = NULL); + + uint8_t run(uint32_t connectTimeout=5000); + +private: + std::vector APlist; +}; + +#endif /* WIFICLIENTMULTI_H_ */ diff --git a/libraries/OASIS-PAW_ESP32/src/WiFiSTA.cpp b/libraries/OASIS-PAW_ESP32/src/WiFiSTA.cpp new file mode 100644 index 0000000..95bb02c --- /dev/null +++ b/libraries/OASIS-PAW_ESP32/src/WiFiSTA.cpp @@ -0,0 +1,763 @@ +/* + WiFiSTA.cpp - WiFi library for esp32 + + Copyright (c) 2014 Ivan Grokhotkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Reworked on 28 Dec 2015 by Markus Sattler + + */ + +#include "WiFi.h" +#include "WiFiGeneric.h" +#include "WiFiSTA.h" + +extern "C" { +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "lwip/err.h" +#include "lwip/dns.h" +#include +#include +} + +// ----------------------------------------------------------------------------------------------------------------------- +// ---------------------------------------------------- Private functions ------------------------------------------------ +// ----------------------------------------------------------------------------------------------------------------------- + +static bool sta_config_equal(const wifi_config_t& lhs, const wifi_config_t& rhs); + + +/** + * compare two STA configurations + * @param lhs station_config + * @param rhs station_config + * @return equal + */ +static bool sta_config_equal(const wifi_config_t& lhs, const wifi_config_t& rhs) +{ + if(memcmp(&lhs, &rhs, sizeof(wifi_config_t)) != 0) { + return false; + } + return true; +} + +// ----------------------------------------------------------------------------------------------------------------------- +// ---------------------------------------------------- STA function ----------------------------------------------------- +// ----------------------------------------------------------------------------------------------------------------------- + +bool WiFiSTAClass::_autoReconnect = true; +bool WiFiSTAClass::_useStaticIp = false; +String WiFiSTAClass::_hostname = "esp32-arduino"; + +static wl_status_t _sta_status = WL_NO_SHIELD; +static EventGroupHandle_t _sta_status_group = NULL; + +void WiFiSTAClass::_setStatus(wl_status_t status) +{ + if(!_sta_status_group){ + _sta_status_group = xEventGroupCreate(); + if(!_sta_status_group){ + log_e("STA Status Group Create Failed!"); + _sta_status = status; + return; + } + } + xEventGroupClearBits(_sta_status_group, 0x00FFFFFF); + xEventGroupSetBits(_sta_status_group, status); +} + +/** + * Return Connection status. + * @return one of the value defined in wl_status_t + * + */ +wl_status_t WiFiSTAClass::status() +{ + if(!_sta_status_group){ + return _sta_status; + } + return (wl_status_t)xEventGroupClearBits(_sta_status_group, 0); +} + +/** + * Start Wifi connection + * if passphrase is set the most secure supported mode will be automatically selected + * @param ssid const char* Pointer to the SSID string. + * @param passphrase const char * Optional. Passphrase. Valid characters in a passphrase must be between ASCII 32-126 (decimal). + * @param bssid uint8_t[6] Optional. BSSID / MAC of AP + * @param channel Optional. Channel of AP + * @param connect Optional. call connect + * @return + */ +wl_status_t WiFiSTAClass::begin(const char* ssid, const char *passphrase, int32_t channel, const uint8_t* bssid, bool connect) +{ + + if(!WiFi.enableSTA(true)) { + log_e("STA enable failed!"); + return WL_CONNECT_FAILED; + } + + if(!ssid || *ssid == 0x00 || strlen(ssid) > 32) { + log_e("SSID too long or missing!"); + return WL_CONNECT_FAILED; + } + + if(passphrase && strlen(passphrase) > 64) { + log_e("passphrase too long!"); + return WL_CONNECT_FAILED; + } + + wifi_config_t conf; + memset(&conf, 0, sizeof(wifi_config_t)); + strcpy(reinterpret_cast(conf.sta.ssid), ssid); + conf.sta.scan_method = WIFI_ALL_CHANNEL_SCAN; //force full scan to be able to choose the nearest / strongest AP + + if(passphrase) { + if (strlen(passphrase) == 64){ // it's not a passphrase, is the PSK + memcpy(reinterpret_cast(conf.sta.password), passphrase, 64); + } else { + strcpy(reinterpret_cast(conf.sta.password), passphrase); + } + } + + if(bssid) { + conf.sta.bssid_set = 1; + memcpy((void *) &conf.sta.bssid[0], (void *) bssid, 6); + } + + if(channel > 0 && channel <= 13) { + conf.sta.channel = channel; + } + + wifi_config_t current_conf; + esp_wifi_get_config(WIFI_IF_STA, ¤t_conf); + if(!sta_config_equal(current_conf, conf)) { + if(esp_wifi_disconnect()){ + log_e("disconnect failed!"); + return WL_CONNECT_FAILED; + } + + esp_wifi_set_config(WIFI_IF_STA, &conf); + } else if(status() == WL_CONNECTED){ + return WL_CONNECTED; + } else { + esp_wifi_set_config(WIFI_IF_STA, &conf); + } + + if(!_useStaticIp) { + if(tcpip_adapter_dhcpc_start(TCPIP_ADAPTER_IF_STA) == ESP_ERR_TCPIP_ADAPTER_DHCPC_START_FAILED){ + log_e("dhcp client start failed!"); + return WL_CONNECT_FAILED; + } + } else { + tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); + } + + if(connect && esp_wifi_connect()) { + log_e("connect failed!"); + return WL_CONNECT_FAILED; + } + + return status(); +} + +wl_status_t WiFiSTAClass::begin(char* ssid, char *passphrase, int32_t channel, const uint8_t* bssid, bool connect) +{ + return begin((const char*) ssid, (const char*) passphrase, channel, bssid, connect); +} + +/** + * Use to connect to SDK config. + * @return wl_status_t + */ +wl_status_t WiFiSTAClass::begin() +{ + + if(!WiFi.enableSTA(true)) { + log_e("STA enable failed!"); + return WL_CONNECT_FAILED; + } + + wifi_config_t current_conf; + if(esp_wifi_get_config(WIFI_IF_STA, ¤t_conf) != ESP_OK || esp_wifi_set_config(WIFI_IF_STA, ¤t_conf) != ESP_OK) { + log_e("config failed"); + return WL_CONNECT_FAILED; + } + + if(!_useStaticIp) { + if(tcpip_adapter_dhcpc_start(TCPIP_ADAPTER_IF_STA) == ESP_ERR_TCPIP_ADAPTER_DHCPC_START_FAILED){ + log_e("dhcp client start failed!"); + return WL_CONNECT_FAILED; + } + } else { + tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); + } + + if(status() != WL_CONNECTED && esp_wifi_connect()){ + log_e("connect failed!"); + return WL_CONNECT_FAILED; + } + + return status(); +} + +/** + * will force a disconnect and then start reconnecting to AP + * @return true when successful + */ +bool WiFiSTAClass::reconnect() +{ + if(WiFi.getMode() & WIFI_MODE_STA) { + if(esp_wifi_disconnect() == ESP_OK) { + return esp_wifi_connect() == ESP_OK; + } + } + return false; +} + +/** + * Disconnect from the network + * @param wifioff + * @return one value of wl_status_t enum + */ +bool WiFiSTAClass::disconnect(bool wifioff, bool eraseap) +{ + wifi_config_t conf; + + if(WiFi.getMode() & WIFI_MODE_STA){ + if(eraseap){ + memset(&conf, 0, sizeof(wifi_config_t)); + if(esp_wifi_set_config(WIFI_IF_STA, &conf)){ + log_e("clear config failed!"); + } + } + if(esp_wifi_disconnect()){ + log_e("disconnect failed!"); + return false; + } + if(wifioff) { + return WiFi.enableSTA(false); + } + return true; + } + + return false; +} + +/** + * Change IP configuration settings disabling the dhcp client + * @param local_ip Static ip configuration + * @param gateway Static gateway configuration + * @param subnet Static Subnet mask + * @param dns1 Static DNS server 1 + * @param dns2 Static DNS server 2 + */ +bool WiFiSTAClass::config(IPAddress local_ip, IPAddress gateway, IPAddress subnet, IPAddress dns1, IPAddress dns2) +{ + esp_err_t err = ESP_OK; + + if(!WiFi.enableSTA(true)) { + return false; + } + + tcpip_adapter_ip_info_t info; + + if(local_ip != (uint32_t)0x00000000 && local_ip != INADDR_NONE){ + info.ip.addr = static_cast(local_ip); + info.gw.addr = static_cast(gateway); + info.netmask.addr = static_cast(subnet); + } else { + info.ip.addr = 0; + info.gw.addr = 0; + info.netmask.addr = 0; + } + + err = tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); + if(err != ESP_OK && err != ESP_ERR_TCPIP_ADAPTER_DHCP_ALREADY_STOPPED){ + log_e("DHCP could not be stopped! Error: %d", err); + return false; + } + + err = tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &info); + if(err != ERR_OK){ + log_e("STA IP could not be configured! Error: %d", err); + return false; + } + + if(info.ip.addr){ + _useStaticIp = true; + } else { + err = tcpip_adapter_dhcpc_start(TCPIP_ADAPTER_IF_STA); + if(err == ESP_ERR_TCPIP_ADAPTER_DHCPC_START_FAILED){ + log_e("dhcp client start failed!"); + return false; + } + _useStaticIp = false; + } + + ip_addr_t d; + d.type = IPADDR_TYPE_V4; + + if(dns1 != (uint32_t)0x00000000 && dns1 != INADDR_NONE) { + // Set DNS1-Server + d.u_addr.ip4.addr = static_cast(dns1); + dns_setserver(0, &d); + } + + if(dns2 != (uint32_t)0x00000000 && dns2 != INADDR_NONE) { + // Set DNS2-Server + d.u_addr.ip4.addr = static_cast(dns2); + dns_setserver(1, &d); + } + + return true; +} + +/** + * is STA interface connected? + * @return true if STA is connected to an AP + */ +bool WiFiSTAClass::isConnected() +{ + return (status() == WL_CONNECTED); +} + + +/** + * Setting the ESP32 station to connect to the AP (which is recorded) + * automatically or not when powered on. Enable auto-connect by default. + * @param autoConnect bool + * @return if saved + */ +bool WiFiSTAClass::setAutoConnect(bool autoConnect) +{ + /*bool ret; + ret = esp_wifi_set_auto_connect(autoConnect); + return ret;*/ + return false;//now deprecated +} + +/** + * Checks if ESP32 station mode will connect to AP + * automatically or not when it is powered on. + * @return auto connect + */ +bool WiFiSTAClass::getAutoConnect() +{ + /*bool autoConnect; + esp_wifi_get_auto_connect(&autoConnect); + return autoConnect;*/ + return false;//now deprecated +} + +bool WiFiSTAClass::setAutoReconnect(bool autoReconnect) +{ + _autoReconnect = autoReconnect; + return true; +} + +bool WiFiSTAClass::getAutoReconnect() +{ + return _autoReconnect; +} + +/** + * Wait for WiFi connection to reach a result + * returns the status reached or disconnect if STA is off + * @return wl_status_t + */ +uint8_t WiFiSTAClass::waitForConnectResult() +{ + //1 and 3 have STA enabled + if((WiFiGenericClass::getMode() & WIFI_MODE_STA) == 0) { + return WL_DISCONNECTED; + } + int i = 0; + while((!status() || status() >= WL_DISCONNECTED) && i++ < 100) { + delay(100); + } + return status(); +} + +/** + * Get the station interface IP address. + * @return IPAddress station IP + */ +IPAddress WiFiSTAClass::localIP() +{ + if(WiFiGenericClass::getMode() == WIFI_MODE_NULL){ + return IPAddress(); + } + tcpip_adapter_ip_info_t ip; + tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip); + return IPAddress(ip.ip.addr); +} + + +/** + * Get the station interface MAC address. + * @param mac pointer to uint8_t array with length WL_MAC_ADDR_LENGTH + * @return pointer to uint8_t * + */ +uint8_t* WiFiSTAClass::macAddress(uint8_t* mac) +{ + if(WiFiGenericClass::getMode() != WIFI_MODE_NULL){ + esp_wifi_get_mac(WIFI_IF_STA, mac); + } + else{ + esp_read_mac(mac, ESP_MAC_WIFI_STA); + } + return mac; +} + +/** + * Get the station interface MAC address. + * @return String mac + */ +String WiFiSTAClass::macAddress(void) +{ + uint8_t mac[6]; + char macStr[18] = { 0 }; + if(WiFiGenericClass::getMode() == WIFI_MODE_NULL){ + esp_read_mac(mac, ESP_MAC_WIFI_STA); + } + else{ + esp_wifi_get_mac(WIFI_IF_STA, mac); + } + sprintf(macStr, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + return String(macStr); +} + +/** + * Get the interface subnet mask address. + * @return IPAddress subnetMask + */ +IPAddress WiFiSTAClass::subnetMask() +{ + if(WiFiGenericClass::getMode() == WIFI_MODE_NULL){ + return IPAddress(); + } + tcpip_adapter_ip_info_t ip; + tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip); + return IPAddress(ip.netmask.addr); +} + +/** + * Get the gateway ip address. + * @return IPAddress gatewayIP + */ +IPAddress WiFiSTAClass::gatewayIP() +{ + if(WiFiGenericClass::getMode() == WIFI_MODE_NULL){ + return IPAddress(); + } + tcpip_adapter_ip_info_t ip; + tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip); + return IPAddress(ip.gw.addr); +} + +/** + * Get the DNS ip address. + * @param dns_no + * @return IPAddress DNS Server IP + */ +IPAddress WiFiSTAClass::dnsIP(uint8_t dns_no) +{ + if(WiFiGenericClass::getMode() == WIFI_MODE_NULL){ + return IPAddress(); + } + const ip_addr_t* dns_ip = dns_getserver(dns_no); + return IPAddress(dns_ip->u_addr.ip4.addr); +} + +/** + * Get the broadcast ip address. + * @return IPAddress broadcastIP + */ +IPAddress WiFiSTAClass::broadcastIP() +{ + if(WiFiGenericClass::getMode() == WIFI_MODE_NULL){ + return IPAddress(); + } + tcpip_adapter_ip_info_t ip; + tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip); + return WiFiGenericClass::calculateBroadcast(IPAddress(ip.gw.addr), IPAddress(ip.netmask.addr)); +} + +/** + * Get the network id. + * @return IPAddress networkID + */ +IPAddress WiFiSTAClass::networkID() +{ + if(WiFiGenericClass::getMode() == WIFI_MODE_NULL){ + return IPAddress(); + } + tcpip_adapter_ip_info_t ip; + tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip); + return WiFiGenericClass::calculateNetworkID(IPAddress(ip.gw.addr), IPAddress(ip.netmask.addr)); +} + +/** + * Get the subnet CIDR. + * @return uint8_t subnetCIDR + */ +uint8_t WiFiSTAClass::subnetCIDR() +{ + if(WiFiGenericClass::getMode() == WIFI_MODE_NULL){ + return (uint8_t)0; + } + tcpip_adapter_ip_info_t ip; + tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip); + return WiFiGenericClass::calculateSubnetCIDR(IPAddress(ip.netmask.addr)); +} + +/** + * Return the current SSID associated with the network + * @return SSID + */ +String WiFiSTAClass::SSID() const +{ + if(WiFiGenericClass::getMode() == WIFI_MODE_NULL){ + return String(); + } + wifi_ap_record_t info; + if(!esp_wifi_sta_get_ap_info(&info)) { + return String(reinterpret_cast(info.ssid)); + } + return String(); +} + +/** + * Return the current pre shared key associated with the network + * @return psk string + */ +String WiFiSTAClass::psk() const +{ + if(WiFiGenericClass::getMode() == WIFI_MODE_NULL){ + return String(); + } + wifi_config_t conf; + esp_wifi_get_config(WIFI_IF_STA, &conf); + return String(reinterpret_cast(conf.sta.password)); +} + +/** + * Return the current bssid / mac associated with the network if configured + * @return bssid uint8_t * + */ +uint8_t* WiFiSTAClass::BSSID(void) +{ + static uint8_t bssid[6]; + wifi_ap_record_t info; + if(WiFiGenericClass::getMode() == WIFI_MODE_NULL){ + return NULL; + } + if(!esp_wifi_sta_get_ap_info(&info)) { + memcpy(bssid, info.bssid, 6); + return reinterpret_cast(bssid); + } + return NULL; +} + +/** + * Return the current bssid / mac associated with the network if configured + * @return String bssid mac + */ +String WiFiSTAClass::BSSIDstr(void) +{ + uint8_t* bssid = BSSID(); + if(!bssid){ + return String(); + } + char mac[18] = { 0 }; + sprintf(mac, "%02X:%02X:%02X:%02X:%02X:%02X", bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]); + return String(mac); +} + +/** + * Return the current network RSSI. + * @return RSSI value + */ +int8_t WiFiSTAClass::RSSI(void) +{ + if(WiFiGenericClass::getMode() == WIFI_MODE_NULL){ + return 0; + } + wifi_ap_record_t info; + if(!esp_wifi_sta_get_ap_info(&info)) { + return info.rssi; + } + return 0; +} + +/** + * Get the station interface Host name. + * @return char array hostname + */ +const char * WiFiSTAClass::getHostname() +{ + const char * hostname = NULL; + if(WiFiGenericClass::getMode() == WIFI_MODE_NULL){ + return hostname; + } + if(tcpip_adapter_get_hostname(TCPIP_ADAPTER_IF_STA, &hostname)){ + return NULL; + } + return hostname; +} + +/** + * Set the station interface Host name. + * @param hostname pointer to const string + * @return true on success + */ +bool WiFiSTAClass::setHostname(const char * hostname) +{ + _hostname = hostname; + if(WiFiGenericClass::getMode() == WIFI_MODE_NULL){ + return false; + } + return tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_STA, hostname) == 0; +} + +/** + * Enable IPv6 on the station interface. + * @return true on success + */ +bool WiFiSTAClass::enableIpV6() +{ + if(WiFiGenericClass::getMode() == WIFI_MODE_NULL){ + return false; + } + return tcpip_adapter_create_ip6_linklocal(TCPIP_ADAPTER_IF_STA) == 0; +} + +/** + * Get the station interface IPv6 address. + * @return IPv6Address + */ +IPv6Address WiFiSTAClass::localIPv6() +{ + static ip6_addr_t addr; + if(WiFiGenericClass::getMode() == WIFI_MODE_NULL){ + return IPv6Address(); + } + if(tcpip_adapter_get_ip6_linklocal(TCPIP_ADAPTER_IF_STA, &addr)){ + return IPv6Address(); + } + return IPv6Address(addr.addr); +} + + +bool WiFiSTAClass::_smartConfigStarted = false; +bool WiFiSTAClass::_smartConfigDone = false; + + +bool WiFiSTAClass::beginSmartConfig() { + if (_smartConfigStarted) { + return false; + } + + if (!WiFi.mode(WIFI_STA)) { + return false; + } + + esp_wifi_disconnect(); + + esp_err_t err; + err = esp_smartconfig_start(reinterpret_cast(&WiFiSTAClass::_smartConfigCallback), 1); + if (err == ESP_OK) { + _smartConfigStarted = true; + _smartConfigDone = false; + return true; + } + return false; +} + +bool WiFiSTAClass::stopSmartConfig() { + if (!_smartConfigStarted) { + return true; + } + + if (esp_smartconfig_stop() == ESP_OK) { + _smartConfigStarted = false; + return true; + } + + return false; +} + +bool WiFiSTAClass::smartConfigDone() { + if (!_smartConfigStarted) { + return false; + } + + return _smartConfigDone; +} + +#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_DEBUG +const char * sc_status_strings[] = { + "WAIT", + "FIND_CHANNEL", + "GETTING_SSID_PSWD", + "LINK", + "LINK_OVER" +}; + +const char * sc_type_strings[] = { + "ESPTOUCH", + "AIRKISS", + "ESPTOUCH_AIRKISS" +}; +#endif + +void WiFiSTAClass::_smartConfigCallback(uint32_t st, void* result) { + smartconfig_status_t status = (smartconfig_status_t) st; + log_d("Status: %s", sc_status_strings[st % 5]); + if (status == SC_STATUS_GETTING_SSID_PSWD) { +#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_DEBUG + smartconfig_type_t * type = (smartconfig_type_t *)result; + log_d("Type: %s", sc_type_strings[*type % 3]); +#endif + } else if (status == SC_STATUS_LINK) { + wifi_sta_config_t *sta_conf = reinterpret_cast(result); + log_d("SSID: %s", (char *)(sta_conf->ssid)); + sta_conf->bssid_set = 0; + esp_wifi_set_config(WIFI_IF_STA, (wifi_config_t *)sta_conf); + esp_wifi_connect(); + _smartConfigDone = true; + } else if (status == SC_STATUS_LINK_OVER) { + if(result){ +#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_DEBUG + ip4_addr_t * ip = (ip4_addr_t *)result; + log_d("Sender IP: " IPSTR, IP2STR(ip)); +#endif + } + WiFi.stopSmartConfig(); + } +} diff --git a/libraries/OASIS-PAW_ESP32/src/WiFiSTA.h b/libraries/OASIS-PAW_ESP32/src/WiFiSTA.h new file mode 100644 index 0000000..f9dbc35 --- /dev/null +++ b/libraries/OASIS-PAW_ESP32/src/WiFiSTA.h @@ -0,0 +1,108 @@ +/* + ESP8266WiFiSTA.h - esp8266 Wifi support. + Based on WiFi.h from Ardiono WiFi shield library. + Copyright (c) 2011-2014 Arduino. All right reserved. + Modified by Ivan Grokhotkov, December 2014 + Reworked by Markus Sattler, December 2015 + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef ESP32WIFISTA_H_ +#define ESP32WIFISTA_H_ + + +#include "WiFiType.h" +#include "WiFiGeneric.h" + + +class WiFiSTAClass +{ + // ---------------------------------------------------------------------------------------------- + // ---------------------------------------- STA function ---------------------------------------- + // ---------------------------------------------------------------------------------------------- + +public: + + wl_status_t begin(const char* ssid, const char *passphrase = NULL, int32_t channel = 0, const uint8_t* bssid = NULL, bool connect = true); + wl_status_t begin(char* ssid, char *passphrase = NULL, int32_t channel = 0, const uint8_t* bssid = NULL, bool connect = true); + wl_status_t begin(); + + bool config(IPAddress local_ip, IPAddress gateway, IPAddress subnet, IPAddress dns1 = (uint32_t)0x00000000, IPAddress dns2 = (uint32_t)0x00000000); + + bool reconnect(); + bool disconnect(bool wifioff = false, bool eraseap = false); + + bool isConnected(); + + bool setAutoConnect(bool autoConnect); + bool getAutoConnect(); + + bool setAutoReconnect(bool autoReconnect); + bool getAutoReconnect(); + + uint8_t waitForConnectResult(); + + // STA network info + IPAddress localIP(); + + uint8_t * macAddress(uint8_t* mac); + String macAddress(); + + IPAddress subnetMask(); + IPAddress gatewayIP(); + IPAddress dnsIP(uint8_t dns_no = 0); + + IPAddress broadcastIP(); + IPAddress networkID(); + uint8_t subnetCIDR(); + + bool enableIpV6(); + IPv6Address localIPv6(); + + const char * getHostname(); + bool setHostname(const char * hostname); + bool hostname(const String& aHostname) { return setHostname(aHostname.c_str()); } + + // STA WiFi info + static wl_status_t status(); + String SSID() const; + String psk() const; + + uint8_t * BSSID(); + String BSSIDstr(); + + int8_t RSSI(); + + static void _setStatus(wl_status_t status); + static String _hostname; +protected: + static bool _useStaticIp; + static bool _autoReconnect; + +public: + bool beginSmartConfig(); + bool stopSmartConfig(); + bool smartConfigDone(); + +protected: + static bool _smartConfigStarted; + static bool _smartConfigDone; + static void _smartConfigCallback(uint32_t status, void* result); + +}; + + +#endif /* ESP32WIFISTA_H_ */ diff --git a/libraries/OASIS-PAW_ESP32/src/WiFiScan.cpp b/libraries/OASIS-PAW_ESP32/src/WiFiScan.cpp new file mode 100644 index 0000000..8d6a26b --- /dev/null +++ b/libraries/OASIS-PAW_ESP32/src/WiFiScan.cpp @@ -0,0 +1,281 @@ +/* + ESP8266WiFiScan.cpp - WiFi library for esp8266 + + Copyright (c) 2014 Ivan Grokhotkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Reworked on 28 Dec 2015 by Markus Sattler + + */ + + +#include "WiFi.h" +#include "WiFiGeneric.h" +#include "WiFiScan.h" + +extern "C" { +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "lwip/err.h" +} + +bool WiFiScanClass::_scanAsync = false; +uint32_t WiFiScanClass::_scanStarted = 0; +uint32_t WiFiScanClass::_scanTimeout = 10000; +uint16_t WiFiScanClass::_scanCount = 0; +void* WiFiScanClass::_scanResult = 0; + +/** + * Start scan WiFi networks available + * @param async run in async mode + * @param show_hidden show hidden networks + * @return Number of discovered networks + */ +int16_t WiFiScanClass::scanNetworks(bool async, bool show_hidden, bool passive, uint32_t max_ms_per_chan, uint8_t channel) +{ + if(WiFiGenericClass::getStatusBits() & WIFI_SCANNING_BIT) { + return WIFI_SCAN_RUNNING; + } + + WiFiScanClass::_scanTimeout = max_ms_per_chan * 20; + WiFiScanClass::_scanAsync = async; + + WiFi.enableSTA(true); + + scanDelete(); + + wifi_scan_config_t config; + config.ssid = 0; + config.bssid = 0; + config.channel = channel; + config.show_hidden = show_hidden; + if(passive){ + config.scan_type = WIFI_SCAN_TYPE_PASSIVE; + config.scan_time.passive = max_ms_per_chan; + } else { + config.scan_type = WIFI_SCAN_TYPE_ACTIVE; + config.scan_time.active.min = 100; + config.scan_time.active.max = max_ms_per_chan; + } + if(esp_wifi_scan_start(&config, false) == ESP_OK) { + _scanStarted = millis(); + if (!_scanStarted) { //Prevent 0 from millis overflow + ++_scanStarted; + } + + WiFiGenericClass::clearStatusBits(WIFI_SCAN_DONE_BIT); + WiFiGenericClass::setStatusBits(WIFI_SCANNING_BIT); + + if(WiFiScanClass::_scanAsync) { + return WIFI_SCAN_RUNNING; + } + if(WiFiGenericClass::waitStatusBits(WIFI_SCAN_DONE_BIT, 10000)){ + return (int16_t) WiFiScanClass::_scanCount; + } + } + return WIFI_SCAN_FAILED; +} + + +/** + * private + * scan callback + * @param result void *arg + * @param status STATUS + */ +void WiFiScanClass::_scanDone() +{ + esp_wifi_scan_get_ap_num(&(WiFiScanClass::_scanCount)); + if(WiFiScanClass::_scanCount) { + WiFiScanClass::_scanResult = new wifi_ap_record_t[WiFiScanClass::_scanCount]; + if(!WiFiScanClass::_scanResult || esp_wifi_scan_get_ap_records(&(WiFiScanClass::_scanCount), (wifi_ap_record_t*)_scanResult) != ESP_OK) { + WiFiScanClass::_scanCount = 0; + } + } + WiFiScanClass::_scanStarted=0; //Reset after a scan is completed for normal behavior + WiFiGenericClass::setStatusBits(WIFI_SCAN_DONE_BIT); + WiFiGenericClass::clearStatusBits(WIFI_SCANNING_BIT); +} + +/** + * + * @param i specify from which network item want to get the information + * @return bss_info * + */ +void * WiFiScanClass::_getScanInfoByIndex(int i) +{ + if(!WiFiScanClass::_scanResult || (size_t) i >= WiFiScanClass::_scanCount) { + return 0; + } + return reinterpret_cast(WiFiScanClass::_scanResult) + i; +} + +/** + * called to get the scan state in Async mode + * @return scan result or status + * -1 if scan not fin + * -2 if scan not triggered + */ +int16_t WiFiScanClass::scanComplete() +{ + if (WiFiScanClass::_scanStarted && (millis()-WiFiScanClass::_scanStarted) > WiFiScanClass::_scanTimeout) { //Check is scan was started and if the delay expired, return WIFI_SCAN_FAILED in this case + WiFiGenericClass::clearStatusBits(WIFI_SCANNING_BIT); + return WIFI_SCAN_FAILED; + } + + if(WiFiGenericClass::getStatusBits() & WIFI_SCAN_DONE_BIT) { + return WiFiScanClass::_scanCount; + } + + if(WiFiGenericClass::getStatusBits() & WIFI_SCANNING_BIT) { + return WIFI_SCAN_RUNNING; + } + + return WIFI_SCAN_FAILED; +} + +/** + * delete last scan result from RAM + */ +void WiFiScanClass::scanDelete() +{ + WiFiGenericClass::clearStatusBits(WIFI_SCAN_DONE_BIT); + if(WiFiScanClass::_scanResult) { + delete[] reinterpret_cast(WiFiScanClass::_scanResult); + WiFiScanClass::_scanResult = 0; + WiFiScanClass::_scanCount = 0; + } +} + + +/** + * loads all infos from a scanned wifi in to the ptr parameters + * @param networkItem uint8_t + * @param ssid const char** + * @param encryptionType uint8_t * + * @param RSSI int32_t * + * @param BSSID uint8_t ** + * @param channel int32_t * + * @return (true if ok) + */ +bool WiFiScanClass::getNetworkInfo(uint8_t i, String &ssid, uint8_t &encType, int32_t &rssi, uint8_t* &bssid, int32_t &channel) +{ + wifi_ap_record_t* it = reinterpret_cast(_getScanInfoByIndex(i)); + if(!it) { + return false; + } + ssid = (const char*) it->ssid; + encType = it->authmode; + rssi = it->rssi; + bssid = it->bssid; + channel = it->primary; + return true; +} + + +/** + * Return the SSID discovered during the network scan. + * @param i specify from which network item want to get the information + * @return ssid string of the specified item on the networks scanned list + */ +String WiFiScanClass::SSID(uint8_t i) +{ + wifi_ap_record_t* it = reinterpret_cast(_getScanInfoByIndex(i)); + if(!it) { + return String(); + } + return String(reinterpret_cast(it->ssid)); +} + + +/** + * Return the encryption type of the networks discovered during the scanNetworks + * @param i specify from which network item want to get the information + * @return encryption type (enum wl_enc_type) of the specified item on the networks scanned list + */ +wifi_auth_mode_t WiFiScanClass::encryptionType(uint8_t i) +{ + wifi_ap_record_t* it = reinterpret_cast(_getScanInfoByIndex(i)); + if(!it) { + return WIFI_AUTH_OPEN; + } + return it->authmode; +} + +/** + * Return the RSSI of the networks discovered during the scanNetworks + * @param i specify from which network item want to get the information + * @return signed value of RSSI of the specified item on the networks scanned list + */ +int32_t WiFiScanClass::RSSI(uint8_t i) +{ + wifi_ap_record_t* it = reinterpret_cast(_getScanInfoByIndex(i)); + if(!it) { + return 0; + } + return it->rssi; +} + + +/** + * return MAC / BSSID of scanned wifi + * @param i specify from which network item want to get the information + * @return uint8_t * MAC / BSSID of scanned wifi + */ +uint8_t * WiFiScanClass::BSSID(uint8_t i) +{ + wifi_ap_record_t* it = reinterpret_cast(_getScanInfoByIndex(i)); + if(!it) { + return 0; + } + return it->bssid; +} + +/** + * return MAC / BSSID of scanned wifi + * @param i specify from which network item want to get the information + * @return String MAC / BSSID of scanned wifi + */ +String WiFiScanClass::BSSIDstr(uint8_t i) +{ + char mac[18] = { 0 }; + wifi_ap_record_t* it = reinterpret_cast(_getScanInfoByIndex(i)); + if(!it) { + return String(); + } + sprintf(mac, "%02X:%02X:%02X:%02X:%02X:%02X", it->bssid[0], it->bssid[1], it->bssid[2], it->bssid[3], it->bssid[4], it->bssid[5]); + return String(mac); +} + +int32_t WiFiScanClass::channel(uint8_t i) +{ + wifi_ap_record_t* it = reinterpret_cast(_getScanInfoByIndex(i)); + if(!it) { + return 0; + } + return it->primary; +} + diff --git a/libraries/OASIS-PAW_ESP32/src/WiFiScan.h b/libraries/OASIS-PAW_ESP32/src/WiFiScan.h new file mode 100644 index 0000000..459d103 --- /dev/null +++ b/libraries/OASIS-PAW_ESP32/src/WiFiScan.h @@ -0,0 +1,66 @@ +/* + ESP8266WiFiScan.h - esp8266 Wifi support. + Based on WiFi.h from Ardiono WiFi shield library. + Copyright (c) 2011-2014 Arduino. All right reserved. + Modified by Ivan Grokhotkov, December 2014 + Reworked by Markus Sattler, December 2015 + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef ESP32WIFISCAN_H_ +#define ESP32WIFISCAN_H_ + +#include "WiFiType.h" +#include "WiFiGeneric.h" + +class WiFiScanClass +{ + +public: + + int16_t scanNetworks(bool async = false, bool show_hidden = false, bool passive = false, uint32_t max_ms_per_chan = 300, uint8_t channel = 0); + + int16_t scanComplete(); + void scanDelete(); + + // scan result + bool getNetworkInfo(uint8_t networkItem, String &ssid, uint8_t &encryptionType, int32_t &RSSI, uint8_t* &BSSID, int32_t &channel); + + String SSID(uint8_t networkItem); + wifi_auth_mode_t encryptionType(uint8_t networkItem); + int32_t RSSI(uint8_t networkItem); + uint8_t * BSSID(uint8_t networkItem); + String BSSIDstr(uint8_t networkItem); + int32_t channel(uint8_t networkItem); + static void * getScanInfoByIndex(int i) { return _getScanInfoByIndex(i); }; + + static void _scanDone(); +protected: + + static bool _scanAsync; + + static uint32_t _scanStarted; + static uint32_t _scanTimeout; + static uint16_t _scanCount; + + static void* _scanResult; + + static void * _getScanInfoByIndex(int i); + +}; + + +#endif /* ESP32WIFISCAN_H_ */ diff --git a/libraries/OASIS-PAW_ESP32/src/WiFiServer.cpp b/libraries/OASIS-PAW_ESP32/src/WiFiServer.cpp new file mode 100644 index 0000000..28916b9 --- /dev/null +++ b/libraries/OASIS-PAW_ESP32/src/WiFiServer.cpp @@ -0,0 +1,127 @@ +/* + Server.cpp - Server class for Raspberry Pi + Copyright (c) 2016 Hristo Gochkov All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "WiFiServer.h" +#include +#include + +#undef write +#undef close + +int WiFiServer::setTimeout(uint32_t seconds){ + struct timeval tv; + tv.tv_sec = seconds; + tv.tv_usec = 0; + if(setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(struct timeval)) < 0) + return -1; + return setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(struct timeval)); +} + +size_t WiFiServer::write(const uint8_t *data, size_t len){ + return 0; +} + +void WiFiServer::stopAll(){} + +WiFiClient WiFiServer::available(){ + if(!_listening) + return WiFiClient(); + int client_sock; + if (_accepted_sockfd >= 0) { + client_sock = _accepted_sockfd; + _accepted_sockfd = -1; + } + else { + struct sockaddr_in _client; + int cs = sizeof(struct sockaddr_in); + client_sock = lwip_accept_r(sockfd, (struct sockaddr *)&_client, (socklen_t*)&cs); + } + if(client_sock >= 0){ + int val = 1; + if(setsockopt(client_sock, SOL_SOCKET, SO_KEEPALIVE, (char*)&val, sizeof(int)) == ESP_OK) { + val = _noDelay; + if(setsockopt(client_sock, IPPROTO_TCP, TCP_NODELAY, (char*)&val, sizeof(int)) == ESP_OK) + return WiFiClient(client_sock); + } + } + return WiFiClient(); +} + +void WiFiServer::begin(uint16_t port){ + begin(port, 1); +} + +void WiFiServer::begin(uint16_t port, int enable){ + if(_listening) + return; + if(port){ + _port = port; + } + struct sockaddr_in server; + sockfd = socket(AF_INET , SOCK_STREAM, 0); + if (sockfd < 0) + return; + setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)); + server.sin_family = AF_INET; + server.sin_addr.s_addr = INADDR_ANY; + server.sin_port = htons(_port); + if(bind(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0) + return; + if(listen(sockfd , _max_clients) < 0) + return; + fcntl(sockfd, F_SETFL, O_NONBLOCK); + _listening = true; + _noDelay = false; + _accepted_sockfd = -1; +} + +void WiFiServer::setNoDelay(bool nodelay) { + _noDelay = nodelay; +} + +bool WiFiServer::getNoDelay() { + return _noDelay; +} + +bool WiFiServer::hasClient() { + if (_accepted_sockfd >= 0) { + return true; + } + struct sockaddr_in _client; + int cs = sizeof(struct sockaddr_in); + _accepted_sockfd = lwip_accept_r(sockfd, (struct sockaddr *)&_client, (socklen_t*)&cs); + if (_accepted_sockfd >= 0) { + return true; + } + return false; +} + +void WiFiServer::end(){ + lwip_close_r(sockfd); + sockfd = -1; + _listening = false; +} + +void WiFiServer::close(){ + end(); +} + +void WiFiServer::stop(){ + end(); +} + diff --git a/libraries/OASIS-PAW_ESP32/src/WiFiServer.h b/libraries/OASIS-PAW_ESP32/src/WiFiServer.h new file mode 100644 index 0000000..1143597 --- /dev/null +++ b/libraries/OASIS-PAW_ESP32/src/WiFiServer.h @@ -0,0 +1,61 @@ +/* + Server.h - Server class for Raspberry Pi + Copyright (c) 2016 Hristo Gochkov All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef _WIFISERVER_H_ +#define _WIFISERVER_H_ + +#include "Arduino.h" +#include "Server.h" +#include "WiFiClient.h" + +class WiFiServer : public Server { + private: + int sockfd; + int _accepted_sockfd = -1; + uint16_t _port; + uint8_t _max_clients; + bool _listening; + bool _noDelay = false; + + public: + void listenOnLocalhost(){} + + WiFiServer(uint16_t port=80, uint8_t max_clients=4):sockfd(-1),_accepted_sockfd(-1),_port(port),_max_clients(max_clients),_listening(false),_noDelay(false){} + ~WiFiServer(){ end();} + WiFiClient available(); + WiFiClient accept(){return available();} + void begin(uint16_t port=0); + void begin(uint16_t port, int reuse_enable); + void setNoDelay(bool nodelay); + bool getNoDelay(); + bool hasClient(); + size_t write(const uint8_t *data, size_t len); + size_t write(uint8_t data){ + return write(&data, 1); + } + using Print::write; + + void end(); + void close(); + void stop(); + operator bool(){return _listening;} + int setTimeout(uint32_t seconds); + void stopAll(); +}; + +#endif /* _WIFISERVER_H_ */ diff --git a/libraries/OASIS-PAW_ESP32/src/WiFiType.h b/libraries/OASIS-PAW_ESP32/src/WiFiType.h new file mode 100644 index 0000000..cf5999c --- /dev/null +++ b/libraries/OASIS-PAW_ESP32/src/WiFiType.h @@ -0,0 +1,51 @@ +/* + ESP8266WiFiType.h - esp8266 Wifi support. + Copyright (c) 2011-2014 Arduino. All right reserved. + Modified by Ivan Grokhotkov, December 2014 + Reworked by Markus Sattler, December 2015 + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#ifndef ESP32WIFITYPE_H_ +#define ESP32WIFITYPE_H_ + +#define WIFI_SCAN_RUNNING (-1) +#define WIFI_SCAN_FAILED (-2) + +#define WiFiMode_t wifi_mode_t +#define WIFI_OFF WIFI_MODE_NULL +#define WIFI_STA WIFI_MODE_STA +#define WIFI_AP WIFI_MODE_AP +#define WIFI_AP_STA WIFI_MODE_APSTA + +#define WiFiEvent_t system_event_id_t +#define WiFiEventInfo_t system_event_info_t +#define WiFiEventId_t wifi_event_id_t + + +typedef enum { + WL_NO_SHIELD = 255, // for compatibility with WiFi Shield library + WL_IDLE_STATUS = 0, + WL_NO_SSID_AVAIL = 1, + WL_SCAN_COMPLETED = 2, + WL_CONNECTED = 3, + WL_CONNECT_FAILED = 4, + WL_CONNECTION_LOST = 5, + WL_DISCONNECTED = 6 +} wl_status_t; + +#endif /* ESP32WIFITYPE_H_ */ diff --git a/portExpander.ino b/portExpander.ino new file mode 100644 index 0000000..d0eae66 --- /dev/null +++ b/portExpander.ino @@ -0,0 +1,13 @@ +// Configure Port Expander +void initpEXP(){ + Wire.begin(I2C_SDA, I2C_SCL); + Wire.beginTransmission(0x70>>1); + Wire.write(0x01); // Write all outputs low + Wire.write(0x00); + Wire.endTransmission(); + Wire.beginTransmission(0x70>>1); + Wire.write(0x03); // Select configuration register + Wire.write(0x00); // Set all ports as output + Wire.endTransmission(); + delay(600); +} diff --git a/resetVars.ino b/resetVars.ino new file mode 100644 index 0000000..a111f2b --- /dev/null +++ b/resetVars.ino @@ -0,0 +1,14 @@ +void resetVars(){ + sampleCount = 0; + OASISPaw.InterruptFlag = 0; + OASISPaw.FaultFlag = 0; + CachePage = 1; + CacheIndex = 0; + PreCacheIndex = 0; + PacketToSend = false; + SendLastPacket = false; + SendSerial = false; + SendUDP = false; + OASISPaw.OASISBusy = 0; + digitalWrite(CONVST, LOW); +} diff --git a/sendPreTRIGGData.ino b/sendPreTRIGGData.ino new file mode 100644 index 0000000..917477b --- /dev/null +++ b/sendPreTRIGGData.ino @@ -0,0 +1,34 @@ +void sendPreTRIGGData(int source){ + PreCacheIndex++; + if(source==1){ + Serial.println("<>"); + for(i=PreCacheIndex; i"); + udp.endPacket(); + + udp.beginPacket(udpTargetIP,udpPort); + for(i=PreCacheIndex; itransfer16(0b0100001000000000); + digitalWrite(HSPI_CS, HIGH); + + // Write register + digitalWrite(HSPI_CS, LOW); + OASISSPI->transfer(byteWriteADC+Addr); + OASISSPI->transfer(RegisterData); + digitalWrite(HSPI_CS, HIGH); + + // Read back written register + digitalWrite(HSPI_CS, LOW); + OASISSPI->transfer(byteReadADC+Addr); + ADCReadback = OASISSPI->transfer(0b00000000); + digitalWrite(HSPI_CS, HIGH); + + // Exit Register Mode + digitalWrite(HSPI_CS, LOW); + OASISSPI->transfer16(0b0000000000000000); + digitalWrite(HSPI_CS, HIGH); + + // Check written Register + if(ADCReadback==RegisterData){ + Serial.print("[OASIS] Successfully written 0x"); + Serial.print(RegisterData, HEX); + Serial.print(" to address 0x"); + Serial.print(Addr, HEX); + Serial.println(); + return; + } + else{ + Serial.println("[OASIS] FATAL ERROR: Register write or readback failed!"); + ledError(); + Sound_Fatal_Error(); + } +}