/* Author: Ryan Jeanes Code for Master ATMEGA328P-AU for CE351 project. Unfortunately in my tiredness I forgot to add buttons to the PCB design. However, since I already devised a method of solving the button problem I am sharing this code nonetheless to use for future projects. This ATMEGA328P chip reads temperature data from a DS18B20 temperature sensor and sends it to the slave ATMEGA328P chip. This chip is also connected to two buttons that will allow the user to change wifi credentials and send the new credentials to the slave ATMEGA328P over SPI. Last Modified: 14-Nov-2020 */ #include #include #include #include #include #include //const definitions #define SCREEN_WIDTH 128//px #define SCREEN_HEIGHT 64//px #define OLED_RESET 4 #define ONE_WIRE_BUS 2 #define BUFFER_SZ 5 #define BUFFER_INC 5 #define CONFIRM_INTERVAL 1500//ms //Button definitions #define PROG_BTN 5 #define NEXT_BTN 6 /* * *******************************Program State defintions ***************************************** * DEFAULT_STATE - default state of program, read DS18B20 data to send to slave * AP_RST - This state prompts user through OLED asking if they want to change wifi name * PASS_RST - This state prompts user through OLED asking if they want to change wifi password * AP_ENTER - This state listens to button presses to record a new wifi name to send to slave * PASS_ENTER - This state listens to button presses to record a new wifi password to send to slave * ************************************************************************************************* */ #define DEFAULT_STATE 0 #define AP_RST 1 #define PASS_RST 2 #define AP_ENTER 3 #define PASS_ENTER 4 /************************************Global Definitions********************************************************************************************************* * Adafruit_SSD1306 display(SCREEN_WIDTH,SCREEN_HEIGHT,&Wire,OLED_RESET) - Declaration for SSD1306 OLED display connected using I2C. * * String sDat - Stories temperature reading from DS18B20 as a string to buffer to OLED display * * OneWire oneWire(ONE_WIRE_BUS) - Declaration for oneWire communication with the DS18B20 sensor using pin 2 (ONE_WIRE_BUS) * * DallasTemperature sensors(&oneWire) - Declaration for DallasTemperature function using oneWire() function for communication * * static char*APChnge - pointer to dynamically allocate and store new wifi name to send to slave * * static char*passChnge - pointer to dynamically allocate and store new wifi password to send to slave * * static unsigned int pos - unsigned variable used to keep track of what 'position' the selector is on for the menu prompts during states AP_RST, PASS_RST. * When in states AP_ENTER and PASS_ENTER, the pos variable instead keeps track of which element is currently being written to in either *APChnge or *passChnge * * static unsigned int cntr - unsigned variable used to keep track of what letter to write to either *APChnge or *passChnge * * static unsigned long t - This variable acts as a time_t variable to store return values from millis() function * * const char alphabet[] - This const char array is a lookup table for accepted characters that can be written to either *APChnge or *passChnge * * ************************************************************************************************************************************************************* */ Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); String sDat; OneWire oneWire(ONE_WIRE_BUS); DallasTemperature sensors(&oneWire); static char*APChnge; static char*passChnge; static unsigned int pos=0; static unsigned int cntr=0; static unsigned long t=0; const char alphabet[] = { 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X', 'Y','Z','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v', 'w','x','y','z','-','_','.','!' }; /* * struct flags - bitfield to store program flags, as well as keeping track of progState * *********************************************************************************************************** * progBtnState - flag that keeps track of the Prog button's current state (Pressed or unpressed) * nextBtnState - flag that keeps track of the Next button's current state (Pressed or unpressed) * lastProgState - flag that keeps track of the last state of the Prog button * lastNextState - flag that keeps track of the last state of the Next button * updateAP - flag that keeps track of whether or not the wifi credentials are being updated by the user * APSend - flag that keeps track of whether there is a new wifi AP name to send * passSend - flag that keeps track of whether there is a new wifi password to send * hasSent - flag that keeps track of whether the AP name has been sent, if applicable * isBuffer - flag that keeps track of whether there is a buffer to send to the OLED display * progState - 3-bit variable to keep track of where in the menus the user is when updating wifi credentials * ************************************************************************************************************* */ struct __attribute__((packed)) flags{ union{ unsigned int field; struct{ unsigned progBtnState:1; unsigned nextBtnState:1; unsigned lastProgState:1; unsigned lastNextState:1; unsigned updateAP:1; unsigned APSend:1; unsigned passSend:1; unsigned hasSent:1; unsigned isBuffer:1; unsigned progState:3; }; }; }f; //Since gcc hasn't implemented constexpr void(){} functions, //these definition macros are used to set the current program state to the bitfield #define SET_DEFAULT_STATE f.field&=0x01FF #define SET_AP_RST f.field=(f.field&~(0x03<<9))|(0x01<<9) #define SET_PASS_RST f.field=(f.field&~(0x03<<9))|(0x02<<9) #define SET_AP_ENTER f.field=(f.field&~(0x03<<9))|(0x03<<9) #define SET_PASS_ENTER f.field=(f.field&~(0x03<<9))|(0x04<<9) void setup() { //Initialize sensors sensors.begin(); //Initialize display and clear display.begin(SSD1306_SWITCHCAPVCC,0x3C); display.clearDisplay(); display.display(); pinMode(PROG_BTN,INPUT); pinMode(NEXT_BTN,INPUT); //Initialize SPI SPI.begin(); SPI.setClockDivider(SPI_CLOCK_DIV8);//2MHz digitalWrite(SS,HIGH);//Set SlaveSelect HIGH so master doesn't connect to slave } /* * void loop() - Main function for arduino process. * Conditions: * If updateAP is not set, and neither APSend nor passSend is true, then the default state of the program * is to read sensor data from DS18B20 temperature sensor to send to slave and buffer to SSD1306 OLED * If updateAP is true, then only detectChange() is called to handle user input during menu prompts. * If updateAP is false, but either APSend or passSend is true, then byte value 255 will be sent to slave to signal incoming * credential change and *APChnge and/or *passChnge will be sent to slave over SPI */ void loop() { byte mSend, mReceive; //read progBtn and nextBtn states f.field=f.field&~(0x01)|digitalRead(PROG_BTN); f.field=f.field&~(0x01<<1)|(digitalRead(NEXT_BTN)<<1); detectChange();//detect rising edge on either progBtn or nextBtn if(!f.updateAP&&!(f.APSend||f.passSend)){ sensors.requestTemperatures(); sDat=String(sensors.getTempCByIndex(0)); dispBuff(sDat + " degC"); drawChar(); digitalWrite(SS,LOW);//Start communication w/ slave mReceive=SPI.transfer(mSend=sensors.getTempCByIndex(0));//Send the mSend val to slave also receives val digitalWrite(SS,HIGH); dispBuff(static_cast(167)); dispBuff("C"); drawChar(); } else{ if(f.APSend||f.passSend){ if(f.APSend&&!f.updateAP){ digitalWrite(SS,LOW); mReceive=SPI.transfer(mSend=255); for(int i=0; ;i++){ mReceive=SPI.transfer(APChnge[i]); if(APChnge[i]=='\0') break; } digitalWrite(SS,HIGH); delete[] APChnge; f.field=(f.field&~(0x05<<5))|(0x04<<5);//APSend false and hasSent true } if(f.hasSent&&!f.passSend){ digitalWrite(SS,LOW); mReceive=SPI.transfer('\0'); digitalWrite(SS,HIGH); f.field&=~(0x01<<7);//hasSent false dispBuff("CredSent"); drawChar(); } if(f.passSend&&!f.updateAP){ digitalWrite(SS,LOW); if(!f.hasSent){ mReceive=SPI.transfer(mSend=255); mReceive=SPI.transfer(mSend='\0'); } for(int i=0; ;i++){ mReceive=SPI.transfer(passChnge[i]); if(passChnge[i]=='\0') break; } digitalWrite(SS,HIGH); delete[] passChnge; f.field&=~(0x03<<6);//Set hasSent and passSend false dispBuff("CredSent"); drawChar(); } } } } /* * DetectChange() is the function that has the menu controls for the user * to update the wifi credentials. It sets the appropriate state of the program * depending on what state the program is in and whether the prog button or next * button is being pushed. */ void detectChange(){ switch(f.progState){ case DEFAULT_STATE: if(progChange()&&!f.updateAP){ SET_AP_RST; f.field|=0x01<<4;//set updateAP to true dispBuff(F("Change AP name?\n->Yes No")); drawChar(); } break; case AP_RST: switch(pos){ case 0: if(progChange()){ SET_AP_ENTER; pos=0; } else if(nextChange()){ pos=1; dispBuff(F("Change AP name?\nYes ->No")); drawChar(); } break; case 1: if(progChange()){ SET_PASS_RST; pos=0; dispBuff(F("Change AP password?\n->Yes No")); drawChar(); } else if(nextChange()){ pos=0; dispBuff(F("Change AP name?\n->Yes No")); drawChar(); } break; default: dispBuff(F("Pos select error!")); drawChar(); } break; case PASS_RST: switch(pos){ case 0: if(progChange()){ SET_PASS_ENTER; pos=0; } else if(nextChange()){ pos=1; dispBuff(F("Change AP password?\nYes ->No")); drawChar(); } break; case 1: if(progChange()){ SET_DEFAULT_STATE; pos=0; f.field&=~(0x01<<4);//set updateAP to false } else if(nextChange()){ pos=0; dispBuff(F("Change AP password?\n->Yes No")); drawChar(); } break; default: dispBuff(F("Pos select error!")); drawChar(); } break; case AP_ENTER: APChange(); break; case PASS_ENTER: APChange(); break; default: dispBuff(F("ErrorProcBtn")); drawChar(); } //Update last button states with current button states f.field=f.field&~(0x01<<2)|(f.progBtnState<<2); f.field=f.field&~(0x01<<3)|(f.nextBtnState<<3); } /* * APChange() - This function is called during the AP_ENTER and PASS_ENTER program states. This program reads user input through the buttons, * and records the changes to either *APChnge or *passChnge and buffers information to the OLED to provide feedback to the user. * This is where the memory for *APChnge and *passChnge is dynamically allocated and managed while the new wifi credentials are being written to them. * * After desired changes are inputted, the user accepts the current displayed name or password by holding the Prog button for CONFIRM_INTERVAL amount of time, in milliseconds. * At the writing of this, the current CONFIRM_INTERVAL is 1500ms. */ void APChange(){ static byte currAPSz=BUFFER_SZ; static byte currPassSz=BUFFER_SZ; if(!f.APSend&&f.progState==AP_ENTER){ f.field|=0x01<<5;//Set APSend to true APChnge=new char[BUFFER_SZ+1]; APChnge[0]='\0'; } if(!f.passSend&&f.progState==PASS_ENTER){ f.field|=0x01<<6;//Set passSend to true passChnge=new char[BUFFER_SZ+1]; passChnge[0]='\0'; } if(f.progBtnState&&f.lastProgState){ if(!t) t=millis(); if(millis()-t>=CONFIRM_INTERVAL){ pos=0; cntr=0; t=0; if(f.progState==AP_ENTER){ SET_PASS_RST; dispBuff(F("Change AP password?\n->Yes No")); drawChar(); } else{ SET_DEFAULT_STATE; f.field&=~(0x01<<4);//set updateAP to false; } } } else{ t=0; dispBuff(F("Enter new ")); dispBuff((f.progState==AP_ENTER) ? F("AP name:\n"):F("password:\n")); dispBuff((f.progState==AP_ENTER) ? APChnge:passChnge); dispBuff(static_cast(alphabet[cntr])); drawChar(); if(progChange()){ cntr++; if(cntr==sizeof(alphabet)) cntr=0; } else{ if(nextChange()){ if(f.progState==AP_ENTER){ APChnge[pos++]=alphabet[cntr]; APChnge[pos]='\0'; cntr=0; } else{ passChnge[pos++]=alphabet[cntr]; passChnge[pos]='\0'; cntr=0; } if(pos==currAPSz||pos==currPassSz){ static char* pTemp; if(f.progState==AP_ENTER){ pTemp=new char[currAPSz+1]; strcpy(pTemp,APChnge); delete[] APChnge; APChnge=new char[currAPSz+=BUFFER_INC+1]; strcpy(APChnge,pTemp); } else{ pTemp=new char[currPassSz+1]; strcpy(pTemp,passChnge); delete[] passChnge; passChnge=new char[currPassSz+=BUFFER_INC+1]; strcpy(passChnge,pTemp); } } } } } } //progChange() - Function returns true if rising edge is detected on Prog button inline bool progChange(){ return (f.progBtnState!=f.lastProgState&&f.progBtnState); } //nextChange() - Function returns true if rising edge is detected on Next button inline bool nextChange(){ return (f.nextBtnState!=f.lastNextState&&f.nextBtnState); } /* * void dispBuff(String const&) - Buffers data to display buffer as a string * str is passed as reference to const, allowing String literals to be passed by reference into the function as a constant. Should allow faster runtime, as * a String object copy will not be made for str. * Conditions: * If isBuffer is false, meaning there is nothing in the display buffer, then the display properties are set, isBuffer is set true, and String str is appended * to the display buffer. Subsequent calls only append String str to the buffer to display. After void drawChar() is called, isBuffer is set to false. */ void dispBuff(String const& str){ if(!f.isBuffer){ display.setTextSize(2); display.setTextColor(SSD1306_WHITE); display.setCursor(0,0); display.cp437(true); f.field^=0x01<<8;//toggle buffer flag } display.print(str); } /* * void drawChar() - This function calls the display.display() function, which sends everything in the display buffer to the OLED screen using properties set * in void bispBuff(String). It then clears the display buffer to allow another message to be buffered for sending to the OLED display. * At the end of the function, isBuffer is set to false. */ void drawChar(){//display what's in buffer and clear buffer display.display(); display.clearDisplay();//clearDisplay() only clears buffer, it doesn't modify the display itself f.field^=0x01<<8;//toggle buffer flag } /********************************************************************** Author: Ryan Jeanes Code for slave nano for CE351 project. The slave ATMEGA328P has wifi credentials stored in EEPROM memory. This can be changed by receiving a 255 byte value and 2 c-style strings from the master. The default state of this program receives data from master over SPI and sends it to Thingspeak using the ESP8266 wifi module using Serial communication Last Modified 14-Nov-2020 **********************************************************************/ #include #include //const definitions #define SPI_BUFF_SZ 40 #define BUFF_SZ 10 #define BUFF_INC 5 /* * ********************Global Definitions*************************************************************************************************** * volatile bool received - Flag that is true if data is received through SPI * volatile byte sReceived - Byte data received over SPI * const String API - stores thingspeak upload API key * const string HOST - stores host URL to connect to * const string PORT - stores the port to use when connecting to HOST * char*pAP - Stores wifi name read from EEPROM at startup * char*pPass - Stores wifi password read from EEPROM at startup * char*pNewAP - Stores new wifi name to write to EEPROM that is received from master * char*pNewPass - Stores new wifi password to write to EEPROM that is received from master * volatile char buff[SPI_BUFF_SZ] - Is a volatile char array of size SPI_BUFF_SZ that stores characters receivd from master after * receiving byte value 255, which signals an incoming wifi credentials change * volatile byte apCntr - Keeps track of how many strings that have been received from the master when wifi credentials are being received * int cTrueCommand - keeps track of how many consecutive commands were sent successfully * int cTimeCommand - Keeps track of how many times a sigle command has been sent * bool found - Is true if command was successfully sent to and processed by ESP8266 * bool newAP - Is true if there is a new wifi name or password to write to EEPROM * volatile bool changeAP - Is true if the 255 byte value to signal an incoming wifi credential change was received * volatile int index - variable to keep track of current index to write to in volatile char buff[SPI_BUFF_SZ] * ******************************************************************************************************************************************* */ volatile bool received; volatile byte sReceived; const String API="4UXAGXPGI3TFYCIY"; const String HOST="api.thingspeak.com"; const String PORT="80"; char*pAP,*pPass,*pNewAP,*pNewPass; volatile char buff[SPI_BUFF_SZ]; volatile byte apCntr=0; int cTrueCommand, cTimeCommand; bool found=false; bool newAP=false; bool newPass=false; volatile bool changeAP=false; volatile int index=0; void setup() { Serial.begin(115200); //Read wifi information from EEPROM readAP(); //ESP8266 setup ESPInit(); //SPI setup pinMode(MISO,OUTPUT);//Sets miso as OUTPUT (Have to send data to Master IN) SPCR|=_BV(SPE);//Turn on SPI in slave mode received=false; SPI.attachInterrupt();//Interrupt ON for SPI communication } /* * void loop() - Main function loop * Conditions: * Default state of the program is to listen for sensor data from master using SPI. * changeAP - If a 255 byte value is received from the master, then changeAP is set, and the slave waits until * two c-style strings are received from the master and then proceeds to update wifi credentials and reinitialize * the esp8266 with the new credentials and then saves to EEPROM */ void loop() { if(received){ byte sByte=sReceived;//Integer data in F ready if((sByte!=185 && sByte!=-196)&&!changeAP){//avoid temp sensor error values String strSByte=String(sByte); String getDat="GET /update?api_key="+API+"&field1="+strSByte; sendCommand("AT+CIPMUX=1",5,"OK"); sendCommand("AT+CIPSTART=0,\"TCP\",\""+HOST+"\","+PORT,15,"OK"); sendCommand("AT+CIPSEND=0,"+String(getDat.length()+4),4,">"); Serial.println(getDat); cTrueCommand++; sendCommand("AT+CIPCLOSE=0",5,"OK"); } else{ if(sByte!=255&&sByte!=-196&&sByte!=185){ while(apCntr!=2); pNewAP=new char[BUFF_SZ+1]; pNewPass=new char[BUFF_SZ+1]; char* pTemp; byte currBuffSz=BUFF_SZ; for(index=0;index