diff --git a/library.properties b/library.properties index 603862c..8920465 100644 --- a/library.properties +++ b/library.properties @@ -1,10 +1,10 @@ -name=P1AM -version=1.0.5 -author=FACTS Engineering -maintainer=Adam Cummick -sentence=P1AM-100 CPU library -paragraph=A library that has all the functions needed to interface a P1AM-100 CPU to P1 I/O modules. -category=Signal Input/Output -url=https://github.com/facts-engineering/P1AM -architectures=samd -includes=P1AM.h +name=P1AM +version=1.0.6 +author=FACTS Engineering +maintainer=Adam Cummick +sentence=P1AM-100 CPU library +paragraph=A library that has all the functions needed to interface a P1AM-100 CPU to P1 I/O modules. +category=Signal Input/Output +url=https://github.com/facts-engineering/P1AM +architectures=samd +includes=P1AM.h diff --git a/src/Module_List.h b/src/Module_List.h index 2a3a804..1b9903f 100644 --- a/src/Module_List.h +++ b/src/Module_List.h @@ -1,152 +1,160 @@ -/* -MIT License - -Copyright (c) 2019 FACTS Engineering, LLC - -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. -*/ - -#ifndef Module_List_h -#define Module_List_h - -const struct moduleProps -{ - unsigned int moduleID; - char diBytes; //Number of bytes used by all Discrete Input channels - char doBytes; //Number of bytes used by all Discrete Output channels - char aiBytes; //Number of bytes used by all Analog Input channels - char aoBytes; //Number of bytes used by all Analog Output channels - char statusBytes; //Number of status bytes. Things like overrange errors or missing 24V. - char configBytes; //Number of bytes used to configure module. Things like ranges and enabled channels. - char dataSize; //Resolution or Specialty info - const char* moduleName; //Text name of module -}mdb[] = { - - //{0x000000ID,di,do,ai,ao,st,cf,ds} - {0x00000000, 0, 0, 0, 0, 0, 0, 0, "Empty"}, //Empty first entry for defaults - - {0x04A00081, 1, 0, 0, 0, 0, 0, 1, "P1-08ND3"}, //P1-08ND3 - - {0x04A00085, 1, 0, 0, 0, 0, 0, 1, "P1-08NA"}, //P1-08NA - - {0x04A00087, 1, 0, 0, 0, 0, 0, 1, "P1-08SIM"}, //P1-08SIM - - {0x04A00088, 1, 0, 0, 0, 0, 0, 1, "P1-08NE3"}, //P1-08NE3 - - {0x05200082, 2, 0, 0, 0, 0, 0, 1, "P1-16ND3"}, //P1-16ND3 - - {0x05200089, 2, 0, 0, 0, 0, 0, 1, "P1-16NE3"}, //P1-16NE3 - - {0x1403F481, 0, 0, 0, 32, 4, 4, 0xA0, "P1-04PWM"}, //P1-04PWM - - {0x1404008D, 0, 1, 0, 0, 0, 0, 1, "P1-08TA"}, //P1-08TA - - {0x1404008F, 0, 1, 0, 0, 0, 0, 1, "P1-08TRS"}, //P1-08TRS - - {0x14040091, 0, 2, 0, 0, 0, 0, 1, "P1-16TR"}, //P1-16TR - - {0x14050081, 0, 1, 0, 0, 0, 0, 1, "P1-08TD1"}, //P1-08TD1 - - {0x14050082, 0, 1, 0, 0, 0, 0, 1, "P1-08TD2"}, //P1-08TD2 - - {0x14080085, 0, 2, 0, 0, 0, 0, 1, "P1-15TD1"}, //P1-15TD1 - - {0x14080086, 0, 2, 0, 0, 0, 0, 1, "P1-15TD2"}, //P1-15TD2 - - {0x24A50081, 1, 1, 0, 0, 0, 0, 1, "P1-16CDR"}, //P1-16CDR - - {0x24A50082, 1, 1, 0, 0, 0, 0, 1, "P1-15CDD1"}, //P1-15CDD1 - - {0x24A50083, 1, 1, 0, 0, 0, 0, 1, "P1-15CDD2"}, //P1-15CDD2 - - {0x34605581, 0, 0, 16, 0, 12, 18, 16, "P1-04AD"}, //P1-04AD - - {0x34605588, 0, 0, 16, 0, 12, 8, 16, "P1-04RTD"}, //P1-04RTD - - {0x3460558F, 0, 0, 16, 0, 12, 2, 12, "P1-04ADL-1"}, //P1-04ADL-1 - - {0x34605590, 0, 0, 16, 0, 12, 2, 12, "P1-04ADL-2"}, //P1-04ADL-2 - - {0x34608C81, 0, 0, 16, 0, 12, 20, 32, "P1-04THM"}, //P1-04THM - - {0x34608C8E, 0, 0, 16, 0, 12, 8, 32, "P1-04NTC"}, //P1-04NTC - - {0x34A0558A, 0, 0, 32, 0, 12, 2, 12, "P1-08ADL-1"}, //P1-08ADL-1 - - {0x34A0558B, 0, 0, 32, 0, 12, 2, 12, "P1-08ADL-2"}, //P1-08ADL-2 - - {0x34A5A481, 2, 0, 36, 36, 4, 12, 0xC0, "P1-02HSC"}, //P1-02HSC - - {0x44035583, 0, 0, 0, 16, 4, 0, 12, "P1-04DAL-1"}, //P1-04DAL-1 - - {0x44035584, 0, 0, 0, 16, 4, 0, 12, "P1-04DAL-2"}, //P1-04DAL-2 - - {0x44055588, 0, 0, 0, 32, 4, 0, 12, "P1-08DAL-1"}, //P1-08DAL-1 - - {0x44055589, 0, 0, 0, 32, 4, 0, 12, "P1-08DAL-2"}, //P1-08DAL-2 - - {0x5461A783, 0, 0, 16, 8, 12, 2, 12, "P1-4ADL2DAL-1"}, //P1-4ADL2DAL-1 - - {0x5461A784, 0, 0, 16, 8, 12, 2, 12, "P1-4ADL2DAL-2"}, //P1-4ADL2DAL-2 - - {0xFFFFFFFF, 0, 0, 0, 0, 0, 0, 0, "BAD SLOT"}, //empty in case no modules are defined. - - {0x00000000, 0, 0, 0, 0, 0, 0, 0, "BAD SLOT"} //empty in case no modules are defined. -}; - -const char P1_04ADL_1_DEFAULT_CONFIG[] = {0x40,0x03}; - -const char P1_04ADL_2_DEFAULT_CONFIG[] = {0x40,0x03}; - -const char P1_08ADL_1_DEFAULT_CONFIG[] = {0x40,0x07}; - -const char P1_08ADL_2_DEFAULT_CONFIG[] = {0x40,0x07}; - -const char P1_04PWM_DEFAULT_CONFIG[] = {0x02,0x02,0x02,0x02}; - -const char P1_04ADL2DAL_1_DEFAULT_CONFIG[] = {0x40,0x03}; - -const char P1_04ADL2DAL_2_DEFAULT_CONFIG[] = {0x40,0x03}; - -const char P1_04NTC_DEFAULT_CONFIG[] = {0x40,0x03,0x60,0x05, - 0x20,0x00,0x80,0x02}; - -const char P1_04THM_DEFAULT_CONFIG[] = {0x40,0x03,0x60,0x05, - 0x21,0x00,0x22,0x00, - 0x23,0x00,0x24,0x00, - 0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00}; - -const char P1_04RTD_DEFAULT_CONFIG[] = {0x40,0x03,0x60,0x05, - 0x20,0x01,0x80,0x00}; - -const char P1_04AD_DEFAULT_CONFIG[] = {0x40,0x03,0x00,0x00, - 0x20,0x03,0x00,0x00, - 0x21,0x03,0x00,0x00, - 0x22,0x03,0x00,0x00, - 0x23,0x03}; - - -const char P1_02HSC_DEFAULT_CONFIG[] = {0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x01, - 0x00,0x00,0x00,0x01}; - - - -#endif +/* +MIT License + +Copyright (c) 2019 FACTS Engineering, LLC + +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. +*/ + +#ifndef Module_List_h +#define Module_List_h + +const struct moduleProps +{ + unsigned int moduleID; + char diBytes; //Number of bytes used by all Discrete Input channels + char doBytes; //Number of bytes used by all Discrete Output channels + char aiBytes; //Number of bytes used by all Analog Input channels + char aoBytes; //Number of bytes used by all Analog Output channels + char statusBytes; //Number of status bytes. Things like overrange errors or missing 24V. + char configBytes; //Number of bytes used to configure module. Things like ranges and enabled channels. + char dataSize; //Resolution or Specialty info + const char* moduleName; //Text name of module +}mdb[] = { + + //{0x000000ID,di,do,ai,ao,st,cf,ds} + {0x00000000, 0, 0, 0, 0, 0, 0, 0, "Empty"}, //Empty first entry for defaults + + {0x04A00081, 1, 0, 0, 0, 0, 0, 1, "P1-08ND3"}, //P1-08ND3 + + {0x04A00085, 1, 0, 0, 0, 0, 0, 1, "P1-08NA"}, //P1-08NA + + {0x04A00087, 1, 0, 0, 0, 0, 0, 1, "P1-08SIM"}, //P1-08SIM + + {0x04A00088, 1, 0, 0, 0, 0, 0, 1, "P1-08NE3"}, //P1-08NE3 + + {0x05200082, 2, 0, 0, 0, 0, 0, 1, "P1-16ND3"}, //P1-16ND3 + + {0x05200089, 2, 0, 0, 0, 0, 0, 1, "P1-16NE3"}, //P1-16NE3 + + {0x1403F481, 0, 0, 0, 32, 4, 4, 0xA0, "P1-04PWM"}, //P1-04PWM + + {0x1404008D, 0, 1, 0, 0, 0, 0, 1, "P1-08TA"}, //P1-08TA + + {0x1404008F, 0, 1, 0, 0, 0, 0, 1, "P1-08TRS"}, //P1-08TRS + + {0x14040091, 0, 2, 0, 0, 0, 0, 1, "P1-16TR"}, //P1-16TR + + {0x14050081, 0, 1, 0, 0, 0, 0, 1, "P1-08TD1"}, //P1-08TD1 + + {0x14050082, 0, 1, 0, 0, 0, 0, 1, "P1-08TD2"}, //P1-08TD2 + + {0x14080085, 0, 2, 0, 0, 0, 0, 1, "P1-15TD1"}, //P1-15TD1 + + {0x14080086, 0, 2, 0, 0, 0, 0, 1, "P1-15TD2"}, //P1-15TD2 + + {0x24A50081, 1, 1, 0, 0, 0, 0, 1, "P1-16CDR"}, //P1-16CDR + + {0x24A50082, 1, 1, 0, 0, 0, 0, 1, "P1-15CDD1"}, //P1-15CDD1 + + {0x24A50083, 1, 1, 0, 0, 0, 0, 1, "P1-15CDD2"}, //P1-15CDD2 + + {0x34605581, 0, 0, 16, 0, 12, 18, 16, "P1-04AD"}, //P1-04AD + + {0x34605582, 0, 0, 16, 0, 12, 2, 16, "P1-04AD-1"}, //P1-04AD-1 + + {0x34605583, 0, 0, 16, 0, 12, 2, 16, "P1-04AD-2"}, //P1-04AD-2 + + {0x34605588, 0, 0, 16, 0, 12, 8, 16, "P1-04RTD"}, //P1-04RTD + + {0x3460558F, 0, 0, 16, 0, 12, 2, 12, "P1-04ADL-1"}, //P1-04ADL-1 + + {0x34605590, 0, 0, 16, 0, 12, 2, 12, "P1-04ADL-2"}, //P1-04ADL-2 + + {0x34608C81, 0, 0, 16, 0, 12, 20, 32, "P1-04THM"}, //P1-04THM + + {0x34608C8E, 0, 0, 16, 0, 12, 8, 32, "P1-04NTC"}, //P1-04NTC + + {0x34A0558A, 0, 0, 32, 0, 12, 2, 12, "P1-08ADL-1"}, //P1-08ADL-1 + + {0x34A0558B, 0, 0, 32, 0, 12, 2, 12, "P1-08ADL-2"}, //P1-08ADL-2 + + {0x34A5A481, 2, 0, 36, 36, 4, 12, 0xC0, "P1-02HSC"}, //P1-02HSC + + {0x44035583, 0, 0, 0, 16, 4, 0, 12, "P1-04DAL-1"}, //P1-04DAL-1 + + {0x44035584, 0, 0, 0, 16, 4, 0, 12, "P1-04DAL-2"}, //P1-04DAL-2 + + {0x44055588, 0, 0, 0, 32, 4, 0, 12, "P1-08DAL-1"}, //P1-08DAL-1 + + {0x44055589, 0, 0, 0, 32, 4, 0, 12, "P1-08DAL-2"}, //P1-08DAL-2 + + {0x5461A783, 0, 0, 16, 8, 12, 2, 12, "P1-4ADL2DAL-1"}, //P1-4ADL2DAL-1 + + {0x5461A784, 0, 0, 16, 8, 12, 2, 12, "P1-4ADL2DAL-2"}, //P1-4ADL2DAL-2 + + {0xFFFFFFFF, 0, 0, 0, 0, 0, 0, 0, "BAD SLOT"}, //empty in case no modules are defined. + + {0x00000000, 0, 0, 0, 0, 0, 0, 0, "BAD SLOT"} //empty in case no modules are defined. +}; + +const char P1_04AD_1_DEFAULT_CONFIG[] = {0x40,0x03}; + +const char P1_04AD_2_DEFAULT_CONFIG[] = {0x40,0x03}; + +const char P1_04ADL_1_DEFAULT_CONFIG[] = {0x40,0x03}; + +const char P1_04ADL_2_DEFAULT_CONFIG[] = {0x40,0x03}; + +const char P1_08ADL_1_DEFAULT_CONFIG[] = {0x40,0x07}; + +const char P1_08ADL_2_DEFAULT_CONFIG[] = {0x40,0x07}; + +const char P1_04PWM_DEFAULT_CONFIG[] = {0x02,0x02,0x02,0x02}; + +const char P1_04ADL2DAL_1_DEFAULT_CONFIG[] = {0x40,0x03}; + +const char P1_04ADL2DAL_2_DEFAULT_CONFIG[] = {0x40,0x03}; + +const char P1_04NTC_DEFAULT_CONFIG[] = {0x40,0x03,0x60,0x05, + 0x20,0x00,0x80,0x02}; + +const char P1_04THM_DEFAULT_CONFIG[] = {0x40,0x03,0x60,0x05, + 0x21,0x00,0x22,0x00, + 0x23,0x00,0x24,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00}; + +const char P1_04RTD_DEFAULT_CONFIG[] = {0x40,0x03,0x60,0x05, + 0x20,0x01,0x80,0x00}; + +const char P1_04AD_DEFAULT_CONFIG[] = {0x40,0x03,0x00,0x00, + 0x20,0x03,0x00,0x00, + 0x21,0x03,0x00,0x00, + 0x22,0x03,0x00,0x00, + 0x23,0x03}; + + +const char P1_02HSC_DEFAULT_CONFIG[] = {0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x01, + 0x00,0x00,0x00,0x01}; + + + +#endif diff --git a/src/P1AM.cpp b/src/P1AM.cpp index 005a2ce..98b379b 100644 --- a/src/P1AM.cpp +++ b/src/P1AM.cpp @@ -1,1552 +1,1544 @@ -/* -MIT License - -Copyright (c) 2019 FACTS Engineering, LLC - -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. -*/ - -#include "P1AM.h" - -SPISettings P100_SPI_SETTINGS(1000000, MSBFIRST, SPI_MODE2); - -P1AM::P1AM(){ - pinMode(slaveSelectPin, OUTPUT); //Define Slave select pin for Base Controller - pinMode(slaveAckPin, INPUT); //Define Ack pin for Base Controller - pinMode(baseEnable, OUTPUT); //Define baseEnable pin used by Arduino to enable Base Controller -} - -P1AM P1; //Create Class instance - -/******************************************************************************* -Description: Initialise the P1AM-100 Base Controller. It automatically configures each module - with default settings and returns the number of modules that have signed on. - -Parameters: -None - -Returns: -uint8_t - Number of good modules that signed on. -*******************************************************************************/ -uint8_t P1AM::init() { - uint32_t slots = 0; - int dbLoc = 0; - char *cfgArray; - uint8_t di_bytes = 0; // Number of DI Bytes - uint8_t do_bytes = 0; // Number of DO Bytes - uint8_t ai_bytes = 0; // Number of AI Bytes - uint8_t ao_bytes = 0; // Number of AO Bytes - uint8_t st_bytes = 0; // Number of Status Bytes - uint8_t cfg_bytes; // Number of CFG Bytes - union moduleIDs{ // Use a union as a quick convert between byte arrays and ints - uint32_t *IDs; - uint8_t *byteArray; - }modules; - uint8_t retry = 0; - - - memset(baseSlot,0,NUMBER_OF_MODULES); //Clear base constants array - enableBaseController(HIGH); //Start base controller - delay(100); - - if(spiTimeout(1000*5000) == false){ - Serial.println("No Base Controller Activity"); - Serial.println("Check External Supply Connection"); - delay(1000); - return 0; - } - - while(((slots == 0) || (slots > 15)) && (retry < 5)){ //Begin sign-on until number of slots are valid. Only retry 5 times. - if(handleHDR(MOD_HDR)){ - delay(5); - slots = spiSendRecvByte(DUMMY); //Get number of modules in base - if(slots == 0 || slots > 15){ - if(retry > 2){ - enableBaseController(LOW); //Disable base controller - delay(10); - enableBaseController(HIGH); //Start base controller - delay(10); - } - retry++; //Let Base Controller retry - } - } - } - - if(retry >= 5){ //Zero module in the base. Quit sign-on routine and let the user know - Serial.println("Zero modules in the base"); - delay(500); - return 0; - } - - modules.IDs = (uint32_t *)malloc(4*slots); //reserve memory for IDs - modules.byteArray = (uint8_t *)malloc(4*slots); - - spiTimeout(1000*200); - spiSendRecvBuf(modules.byteArray,slots*4,1); //slots * 4 bytes per ID code - - uint8_t *baseControllerConstants = (uint8_t *)malloc(1 * slots * 7);//seven elements in module sign on - for(int i=0;i 0){ //Modules with config Bytes need to havea config loaded - cfgArray = loadConfigBuf(mdb[dbLoc].moduleID); //Get pointer to default config for this module - while(!configureModule(cfgArray, i + 1)){ //configure module - debugPrintln("Working"); - } - } - } - #endif - - delay(50); //Let the Base Controller complete its end of the sign-on - #ifdef DEBUG_PRINT_ON - slots = printModules(); //Returns the number of good modules. Prints IDs to serial monitor - #endif - return slots; -} - -/******************************************************************************* -Description: Enables or disables base controller during normal operation. - -Parameters: -bool state - On or off control - -Returns: -none -*******************************************************************************/ -void P1AM::enableBaseController(bool state){ - - digitalWrite(baseEnable, state); - -} - -/******************************************************************************* -Description: Checks to see if modules in base appear as expected. Disables any modules - that are found not to comply. This function only works after init. - -Parameters: -char* moduleNames[] - List of Module names in order they appear in base - -Returns: -uint16_t - Bitmapped representation of errors. A one in any position - represents a error in configuration. E.g. an error in - slot 1 and 3 would return 0x05. -*******************************************************************************/ -uint16_t P1AM::rollCall(const char* moduleNames[], uint8_t numberOfModules){ - uint8_t numberGoodPresent = 0; - uint16_t slotError = 0; - uint8_t dbLoc = 0; - bool singleError; - - - for(int i = 0;i < numberOfModules;i++){ - dbLoc = baseSlot[i].dbLoc; - - singleError = (moduleNames[i] != mdb[dbLoc].moduleName);//Set to true if there is an error - slotError = slotError | (singleError << i); //Shift bit of error check and add to our return - if(singleError == true){ - debugPrint("Slot "); - debugPrint(i+1); - debugPrintln(" Module Mismatch"); - - debugPrint("Expected: "); - debugPrintln(moduleNames[i]); - - debugPrint("Found: "); - debugPrintln(mdb[dbLoc].moduleName); - - debugPrintln(); //CRLF for next message - - baseSlot[i].dbLoc = 0; //Clear out slot so it no longer functions - } - } - - if(slotError == 0){ - debugPrintln("All Modules Good"); - } - - return slotError; - -} - - -/******************************************************************************* -Description: Read a single discrete input module - -Parameters: -uint8_t slot - Slot to read from. Slots start at 1. - -(Optional) uint8_t channel = 0. If specified reads from "channel". - If left out or 0, reads "data" from all channels where - least Signficant bit is channel 1. - -Returns: -uint32_t data read from the module -*******************************************************************************/ -uint32_t P1AM::readDiscrete(uint8_t slot, uint8_t channel){ - uint32_t data = 0; - uint8_t len = 0; - uint8_t mdbLoc = 0; - char rData[4] = {0,0,0,0}; - - mdbLoc = baseSlot[slot-1].dbLoc; - len = mdb[mdbLoc].diBytes; - - if((slot < 1) || (slot > 15)){ - debugPrintln("Slots must be between 1 and 15"); - return 0; - } - - if((len <= 0)){ - debugPrint("Slot "); - debugPrint(slot); - debugPrintln(": This module has no Discrete Input bytes"); - return 0; - } - - if(channel > (len * 8)){ //8 channels per byte - debugPrintln("This channel is not valid"); - return 0; - } - - rData[0] = READ_DISCRETE_HDR; - rData[1] = slot; - spiSendRecvBuf((uint8_t *)rData,2); - memset(rData,0,4); //clear buffer - if(spiTimeout(1000*200) == true){ - spiSendRecvBuf((uint8_t *)rData,len,true); - data = (rData[3]<<24); - data += (rData[2]<<16); - data += (rData[1]<<8); - data += (rData[0]<<0); - - if(channel != 0){ - data = (data>>(channel-1)) & 1; // shift and mask - } - dataSync(); - return data; - } - else{ - debugPrintln("Slow read"); - delay(100); - return 0; - } -} - -/******************************************************************************* -Description: Write to single discrete output module - -Parameters: -uint32_t data - Data to write to module - -uint8_t slot - Slot to write to. Slots start at 1. - -(Optional) uint8_t channel = 0. If specified writes to "channel". - If left out or 0, writes "data" to all channels where - least Signficant bit is channel 1. - -Returns: -None -*******************************************************************************/ -void P1AM::writeDiscrete(uint32_t data,uint8_t slot, uint8_t channel){ - uint8_t tData[7]; - uint8_t mdbLoc = 0; - uint8_t len = 0; - - mdbLoc = baseSlot[slot-1].dbLoc; - len = mdb[mdbLoc].doBytes; - - if((slot < 1) || (slot > 15)){ - debugPrintln("Slots must be between 1 and 15"); - return; - } - - if((len <= 0)){ - debugPrint("Slot "); - debugPrint(slot); - debugPrintln(": This module has no Discrete Output bytes"); - return; - } - - if(channel > (len * 8)){ //8 channels per byte - debugPrintln("This channel is not valid"); - return; - } - - tData[0] = WRITE_DISCRETE_HDR; - tData[1] = slot; - tData[2] = channel; - - if(channel == 0){ - for(int i=0;i>(8*i) & 0xFF; //Shift and mask - } - } - else{ - tData[3] = data & 0b1; //mask bit - len = 1; //only send 1 byte - } - - - spiSendRecvBuf(tData,len+3); //3 Header bytes plus data length - - dataSync(); - return; -} - -/******************************************************************************* -Description: Read a single analog input module channel. - -Parameters: -uint8_t slot - Slot to read from. Slots start at 1. - -uint8_t channel - Channel to read from. Channels start at 1. - -Returns: -uint32_t - Value of channel in counts. 12-bit returns 0-4095, - 16-bit returns between 0-65535, etc. -*******************************************************************************/ -int P1AM::readAnalog(uint8_t slot, uint8_t channel){ - int data = 0; - uint8_t len = 0; - uint8_t mdbLoc = 0; - char rData[4] = {0,0,0,0}; - - mdbLoc = baseSlot[slot-1].dbLoc; - len = mdb[mdbLoc].aiBytes; - - if((slot < 1) || (slot > 15)){ - debugPrintln("Slots must be between 1 and 15"); - return 0; - } - - if((len <= 0)){ - debugPrint("Slot "); - debugPrint(slot); - debugPrintln(": This module has no Analog Input bytes"); - return 0; - } - - if((channel <= 0) || (channel > (len / 4))){ //4 bytes per channel - debugPrintln("This channel is not valid"); - return 0; - } - - rData[0] = READ_ANALOG_HDR; - rData[1] = slot; - rData[2] = channel; - spiSendRecvBuf((uint8_t *)rData,3); - - if(spiTimeout(1000*200) == true){ - data = spiSendRecvInt(DUMMY); - dataSync(); - return data; - } - else{ - debugPrintln("Slow read"); - delay(100); - return 0; - } -} - -/******************************************************************************* -Description: Read a single temperature input module channel. - -Parameters: -uint8_t slot - Slot to read from. Slots start at 1. - -uint8_t channel - Channel to read from. Channels start at 1. -Returns: -Value of channel in degrees for temperature. mV for voltages. -*******************************************************************************/ -float P1AM::readTemperature(uint8_t slot, uint8_t channel){ - union int2float{ - int data; //We get an int back no matter what analog module. Unions set variables as the same/ - float temperature; //Floats and Ints are both 32 bits, so this lets quickly convert our int to a float. - }ourValue; - - ourValue.data = readAnalog(slot,channel); //Use the same analog read function to get int - - return ourValue.temperature; //return the float -} - -/******************************************************************************* -Description: Write to a single analog output module channel. - -Parameters: -uint32_t data - Value of channel in counts. 12-bit in range 0-4095, - 16-bit in range 0-65535, etc. - -uint8_t slot - Slot to write to. Slots start at 1. - -uint8_t channel - Channel to write to. Channels start at 1. -Returns: -None -*******************************************************************************/ -void P1AM::writeAnalog(uint32_t data,uint8_t slot, uint8_t channel){ - uint8_t tData[7]; - uint8_t mdbLoc = 0; - uint8_t len = 0; - - mdbLoc = baseSlot[slot-1].dbLoc; - len = mdb[mdbLoc].aoBytes; - - if((slot < 1) || (slot > 15)){ - debugPrintln("Slots must be between 1 and 15"); - return; - } - - if((len <= 0)){ - debugPrint("Slot "); - debugPrint(slot); - debugPrintln(": This module has no Analog Output bytes"); - return; - } - - if((channel <= 0) || (channel > (len / 4))){ //4 bytes per channel - debugPrintln("This channel is not valid"); - return; - } - - tData[0] = WRITE_ANALOG_HDR; - tData[1] = slot; - tData[2] = channel; - tData[3] = (data>>0) & 0xFF; - tData[4] = (data>>8) & 0xFF; - tData[5] = (data>>16) & 0xFF; - tData[6] = (data>>24) & 0xFF; - spiSendRecvBuf(tData,7); - - dataSync(); - return; -} - -/******************************************************************************* -Description: Read a block of data stored in Base Controller. This allows you to read data - from many modules in one command, but requires you to calculate the - offset and lengths and later parse out the data. You can also read - the state of output data through this for debugging purposes. This - function is more advanced and is not recommended for new programmers. - -Parameters: -char buf[] - Pointer to an array that will hold the values read. This - function does not return a value, but stores in the *buf passed in. - -uint16_t len - Number of bytes to read - -uint8_t offset - Starting byte in array to read. - -uint8_t type - Specifies which Base Controller data block to read from. - 0 is Discrete Input, 1 is Analog Input, 2 is Discrete Output, 3 is Analog Output,4 is Status. - -Returns: -None -*******************************************************************************/ -void P1AM::readBlockData(char buf[], uint16_t len,uint16_t offset, uint8_t type){ - uint8_t readParams[6]; - - if((len+offset) > 1200){ //max of data array is 1200, so we can't read past that - len = 1200-offset; //adjust len in case we're trying to read too far - } - - readParams[0] = READ_BLOCK_HDR; - readParams[1] = type; // 0 is Discrete In, 1 is Analog In, 2 is Discrete Out, 3 is Analog Out,4 is Status - readParams[2] = len >> 8; - readParams[3] = len & 0xFF; - readParams[4] = offset >> 8; - readParams[5] = offset & 0xFF; - spiSendRecvBuf(readParams,6); //Send paramters for block read - - if(spiTimeout(1000*200) == true){ - spiSendRecvBuf((uint8_t *)buf,len,true); //data is stored in buffer passed in - dataSync(); - return; - } - else{ - debugPrintln("Slow block read"); - delay(100); - return; - } -} - -/******************************************************************************* -Description: Write to a block of data stored in Base Controller. This allows you to write data - to many modules in one command, but requires you to calculate the - offset and lengths and parse together the data. You can write to input data, - but be aware the Base Controller may overwrite this data in normal operations. - This function is more advanced than others and is not recommended - for new programmers. - -Parameters: -char buf[] - Pointer to an array that holds the values to write. This - array is not overwritten. - -uint16_t len - Number of bytes to write - -uint8_t offset - Starting byte in array to write. - -uint8_t type - Specifies which Base Controller data block to write to. - 0 is Discrete Input, 1 is Analog Input, 2 is Discrete Output, 3 is Analog Output,4 is Status. - -Returns: -None -*******************************************************************************/ -void P1AM::writeBlockData(char buf[], uint16_t len,uint16_t offset, uint8_t type){ - - if((len+offset) > 1200){ //max of data array is 1200, so we can't read past that - len = 1200-offset; //adjust len in case we're trying to read too far - } - - uint8_t *tData = (uint8_t *)malloc(len+6); - tData[0] = WRITE_BLOCK_HDR; - tData[1] = type; - tData[2] = len >> 8; - tData[3] = len & 0xFF; - tData[4] = offset >> 8; - tData[5] = offset & 0xFF; - - memcpy(tData+6,buf,len); - spiSendRecvBuf(tData,len+6,0); //data is not stored in passed buffer - free(tData); - - dataSync(); - return; -} - -/******************************************************************************* -Description: Set both duty cycle and frequency of a PWM output module channel. - -Parameters: -float duty - Duty cycle. Range 0.00-100.00. You can include up to 2 decimal places - -uint32_t freq - Frequency. See module data sheet for range. P1-04PWM is 0-20kHz. - -uint8_t slot - Slot to write to. Slots start at 1. - -uint8_t channel - Channel to write to. Channels start at 1. - -Returns: -None -*******************************************************************************/ -void P1AM::writePWM(float duty,uint32_t freq,uint8_t slot,uint8_t channel){ - uint8_t mdbLoc = 0; - uint8_t tempLoc = 0; - uint8_t offset = 0; - uint32_t dutyInt = 0; - char tData[8]; - - mdbLoc = baseSlot[slot-1].dbLoc; - - if((slot < 1) || (slot > 15)){ - debugPrintln("Slots must be between 1 and 15"); - return; - } - - if((channel <= 0) || (channel > 4)){ - debugPrintln("This channel is not valid"); - return; - } - - if((mdb[mdbLoc].dataSize & 0xF0) != 0xA0){ //Is this PWM - debugPrint("Slot "); - debugPrint(slot); - debugPrintln(": This module is not a PWM module"); - return; //Not PWM - } - - for(int i=0;i 0){ - offset += mdb[tempLoc].aoBytes; //get offset of analog bytes - } - } - - offset += (channel - 1) * 8; //Each channel uses 8 bytes - dutyInt = (uint32_t)(duty * 100);// shift decimal over 2 places and cast off remainder. e.g. 12.3456 turns into 1234 - - tData[3] = (dutyInt>>0) & 0xFF; //shift and mask to bytes - tData[2] = (dutyInt>>8) & 0xFF; - tData[1] = (dutyInt>>16) & 0xFF; - tData[0] = (dutyInt>>24) & 0xFF; - tData[7] = (freq>>0) & 0xFF; - tData[6] = (freq>>8) & 0xFF; - tData[5] = (freq>>16) & 0xFF; - tData[4] = (freq>>24) & 0xFF; - - writeBlockData(tData, 8, offset, ANALOG_OUT_BLOCK); //Use block data to ensure duty/freq are entered at the same time. - dataSync(); - - return; -} - -/******************************************************************************* -Description: Set duty cycle of a PWM output module channel. - -Parameters: -float duty - Duty cycle. Range 0.00-100.00. You can include up to 2 decimal places - -uint8_t slot - Slot to write to. Slots start at 1. - -uint8_t channel - Channel to write to. Channels start at 1. - -Returns: -None -*******************************************************************************/ -void P1AM::writePWMDuty(float duty,uint8_t slot,uint8_t channel){ - uint8_t mdbLoc = 0; - uint32_t dutyInt = 0; - - mdbLoc = baseSlot[slot-1].dbLoc; - - if((slot < 1) || (slot > 15)){ - debugPrintln("Slots must be between 1 and 15"); - return; - } - - if((channel <= 0) || (channel > 4)){ - debugPrintln("This channel is not valid"); - return; - } - - if((mdb[mdbLoc].dataSize & 0xF0) != 0xA0){ //Is this PWM - debugPrint("Slot "); - debugPrint(slot); - debugPrintln(": This module is not a PWM module"); - return; //Not PWM - } - - dutyInt = (uint32_t)(duty * 100);// shift decimal over 2 places and cast off remainder. e.g. 12.3456 turns into 1234 - channel = 1 + ((channel-1) * 2); - P1.writeAnalog(dutyInt,slot,channel); - - dataSync(); - return; -} - -/******************************************************************************* -Description: Set frequency of a PWM output module channel. - -Parameters: -uint32_t freq - Frequency. See module data sheet for range. P1-04PWM is 0-20kHz. - -uint8_t slot - Slot to write to. Slots start at 1. - -uint8_t channel - Channel to write to. Channels start at 1. - -Returns: -None -*******************************************************************************/ -void P1AM::writePWMFreq(uint32_t freq,uint8_t slot,uint8_t channel){ - uint8_t mdbLoc = 0; - - mdbLoc = baseSlot[slot-1].dbLoc; - - if((slot < 1) || (slot > 15)){ - debugPrintln("Slots must be between 1 and 15"); - return; - } - - if((channel <= 0) || (channel > 4)){ - debugPrintln("This channel is not valid"); - return; - } - - if((mdb[mdbLoc].dataSize & 0xF0) != 0xA0){ //Is this PWM - debugPrint("Slot "); - debugPrint(slot); - debugPrintln("This module is not a PWM module"); - return; //Not PWM - } - - channel = 2 + ((channel-1) * 2); - P1.writeAnalog(freq,slot,channel); - - dataSync(); - return; -} - - /******************************************************************************* -Description: Set direction of a PWM module channel when set to DIR mode. - See configuration document to see how to enable this feature. - -Parameters: -bool data - Set channel on or off - -uint8_t slot - Slot to write to. Slots start at 1. - -uint8_t channel - Channel to write to. Channels start at 1. - -Returns: -None -*******************************************************************************/ -void P1AM::writePWMDir(bool data,uint8_t slot, uint8_t channel){ - uint8_t mdbLoc = 0; - - mdbLoc = baseSlot[slot-1].dbLoc; - - if((slot < 1) || (slot > 15)){ - debugPrintln("Slots must be between 1 and 15"); - return; - } - - if((channel <= 0) || (channel > 4)){ - debugPrintln("This channel is not valid"); - return; - } - - if((mdb[mdbLoc].dataSize & 0xF0) != 0xA0){ //Is this PWM - debugPrint("Slot "); - debugPrint(slot); - debugPrintln(": This module is not a PWM module"); - return; //Not PWM - } - - channel = 1 + ((channel-1) * 2); - P1.writeAnalog(data,slot,channel); - - dataSync(); - return; -} - - -/******************************************************************************* -Description: Print the names of all the modules in the base to the serial monitor - -Parameters: -None - -Returns: -Number of good sign-ons -*******************************************************************************/ -uint8_t P1AM::printModules(){ - uint32_t slot = 0; - uint8_t dbLoc = 0; - uint8_t goodSlots = 0; - - while(baseSlot[slot].dbLoc != 0){ - dbLoc = baseSlot[slot].dbLoc; - - if(mdb[dbLoc].moduleID == 0 || mdb[dbLoc].moduleID == 0xFFFFFFFF){ - Serial.print("Slot "); - Serial.print(slot + 1); - Serial.println(": Sign On Error"); - } - else{ - Serial.print("Slot "); - Serial.print(slot + 1); - Serial.print(": "); - Serial.println(mdb[dbLoc].moduleName); - goodSlots++; - } - slot++; - } - dataSync(); - Serial.println(""); //Print an empty line to give us some breathing room later - return goodSlots; -} - -/******************************************************************************* -Description: Read a single status byte from a single module - -Parameters: -int byteNum - Which status byte to read in. Byte numbering starts at 0. - -int slot - Which slot to read from - -Returns: -char - The status byte. -*******************************************************************************/ -char P1AM::readStatus(int byteNum,int slot){ - uint8_t len = 0; - uint8_t mdbLoc = 0; - uint8_t buf[1]; - uint8_t rData[4]; - - mdbLoc = baseSlot[slot-1].dbLoc; - len = 1; //only need 1 byte - - if((slot < 1) || (slot > 15)){ - debugPrintln("Slots must be between 1 and 15"); - return 0; - } - - if(mdb[mdbLoc].statusBytes <= 0){ - debugPrint("Slot "); - debugPrint(slot); - debugPrintln(": This module has no Status bytes"); - return 0; //No status Bytes - } - - rData[0] = READ_STATUS_HDR; - rData[1] = slot; - rData[2] = len; - rData[3] = byteNum;//offset - spiSendRecvBuf((uint8_t *)rData,4); - - if(spiTimeout(1000*200) == true){ - spiSendRecvBuf((uint8_t *)buf,len,true); //data is stored in buffer passed in - dataSync(); - return buf[0]; - } - else{ - debugPrintln("Slow status read"); - delay(100); - return 0; - } -} - -/******************************************************************************* -Description: Read all status bytes from a single module - -Parameters: -char buf[] - Array to store the read status bytes in. This function - does not return a value, but places it in the array passed in - -uint8_t slot - Slot that you want to read status bytes from - -Returns: -None -*******************************************************************************/ -void P1AM::readStatus(char buf[], uint8_t slot){ - uint8_t len = 0; - uint8_t mdbLoc = 0; - uint8_t rData[4]; - - mdbLoc = baseSlot[slot-1].dbLoc; - len = mdb[mdbLoc].statusBytes; - - if((slot < 1) || (slot > 15)){ - debugPrintln("Slots must be between 1 and 15"); - return; - } - - if(len <= 0){ - debugPrint("Slot "); - debugPrint(slot); - debugPrintln(": This module has no Status bytes"); - return; //No status Bytes - } - - rData[0] = READ_STATUS_HDR; - rData[1] = slot; - rData[2] = len; - rData[3] = 0;//offset - spiSendRecvBuf((uint8_t *)rData,4); - - if(spiTimeout(1000*200) == true){ - spiSendRecvBuf((uint8_t *)buf,len,true); //data is stored in buffer passed in - dataSync(); - return; - } - else{ - debugPrintln("Slow status read"); - delay(100); - return; - } -} - -/******************************************************************************* -Description: Checks the under range error on select P1000 modules. Returns if channel - is under range. This function does not check if the module supports - this feature. - -Parameters: -uint8_t slot - Which slot to read from. - -(Optional) uint8_t channel = 0. If value is non-zero checks the - specified channel. If left out or 0, checks all channels where - least Signficant bit is the status of channel 1. - - -Returns: -uint8_t - returns under range status -*******************************************************************************/ -uint8_t P1AM::checkUnderRange(uint8_t slot, uint8_t channel){ - uint8_t statusByte = 0; - - statusByte = readStatus(UNDER_RANGE_STATUS,slot); - if(channel){ - statusByte = (statusByte >> (channel - 1)) & 1; //shift and mask bits - } - - return statusByte; -} - -/******************************************************************************* -Description: Checks the over range error on select P1000 modules. Returns if channel - is over range. This function does not check if the module supports - this feature. - -Parameters: -uint8_t slot - Which slot to read from. - -(Optional) uint8_t channel = 0. If value is non-zero checks the - specified channel. If left out or 0, checks all channels where - least Signficant bit is the status of channel 1. - - -Returns: -uint8_t - returns over range status -*******************************************************************************/ -uint8_t P1AM::checkOverRange(uint8_t slot, uint8_t channel){ - uint8_t statusByte = 0; - - statusByte = readStatus(OVER_RANGE_STATUS,slot); - if(channel){ - statusByte = (statusByte >> (channel - 1)) & 1; //shift and mask bits. - } - - return statusByte; -} - -/******************************************************************************* -Description: Checks the burnout error on select P1000 modules. Returns if channel - is over range. This function does not check if the module supports - this feature. - -Parameters: -uint8_t slot - Which slot to read from. - -(Optional) uint8_t channel = 0. If value is non-zero checks the - specified channel. If left out or 0, checks all channels where - least Signficant bit is the status of channel 1. - - -Returns: -uint8_t - returns burnout status -*******************************************************************************/ -uint8_t P1AM::checkBurnout(uint8_t slot, uint8_t channel){ - uint8_t statusByte = 0; - - statusByte = readStatus(BURNOUT_STATUS,slot); - if(channel){ - statusByte = (statusByte >> (channel - 1)) & 1; //shift and mask bits - } - - return statusByte; -} - -/******************************************************************************* -Description: Checks the lost 24V error on select P1000 modules. Returns 1 if slot is missing - 24V. This function does not check if the module supports this feature. - -Parameters: -uint8_t slot - Which slot to read from. - -Returns: -uint8_t - 1 if 24V is missing. 0 if 24V is present -*******************************************************************************/ -uint8_t P1AM::check24V(uint8_t slot){ - uint8_t statusByte = 0; - - statusByte = readStatus(MISSING24V_STATUS,slot); - statusByte = (statusByte >> 1) & 1; //Mask and shift for 24V bit - - return statusByte; -} - -/******************************************************************************* -Description: Manually configure a module. Use this if you want to use a setting - that is not the default for a module. - Visit https://facts-engineering.github.io/config.html for more information - -Parameters: -uint8_t slot - Slot you want to configure. - -char *cfgData - Pointer to array that contains the configuration settings - -Returns: -bool - Configuration successfully written to Base Controller. -*******************************************************************************/ -bool P1AM::configureModule(char cfgData[], uint8_t slot){ - uint8_t len = 0; - uint8_t mdbLoc = 0; - uint8_t cfgForSpi[66]; - - mdbLoc = baseSlot[slot-1].dbLoc; - len = mdb[mdbLoc].configBytes + 2; - cfgForSpi[0] = CFG_HDR; - cfgForSpi[1] = slot; - - memcpy(cfgForSpi+2,cfgData,len); - delay(1); - if((slot < 1) || (slot > 15)){ - debugPrintln("Slots must be between 1 and 15"); - return 0; - } - - if(len <= 0){ - debugPrint("Slot "); - debugPrint(slot); - debugPrintln(": This module has no config Bytes"); - return 0; - } - - spiSendRecvBuf(cfgForSpi,len); - delay(100); //Additional time for Config to written - dataSync(); - dataSync(); - return 1; -} - -/******************************************************************************* -Overload of above function for const arrays -*******************************************************************************/ -bool P1AM::configureModule(const char cfgData[], uint8_t slot){ - - return configureModule((char*)cfgData, slot); - -} - -/******************************************************************************* -Description: Read current configuration of a module. Data is stored in the passed - in array pointer. - - -Parameters: -char *cfgData - Pointer to array that will receive the current configuration - -uint8_t slot - Slot you want to configure. - -Returns: -none -*******************************************************************************/ -void P1AM::readModuleConfig(char cfgData[], uint8_t slot){ - uint8_t len = 0; - uint8_t mdbLoc = 0; - uint8_t cfgForSpi[2]; - - mdbLoc = baseSlot[slot-1].dbLoc; - len = mdb[mdbLoc].configBytes; - - if((slot < 1) || (slot > 15)){ - debugPrintln("Slots must be between 1 and 15"); - return; - } - - if(len <= 0){ - debugPrint("Slot "); - debugPrint(slot); - debugPrintln(": This module has no config Bytes"); - return; - } - - cfgForSpi[0] = READ_CFG_HDR; - cfgForSpi[1] = slot; - spiSendRecvBuf((uint8_t *)cfgForSpi,2); - - if(spiTimeout(1000*200) == true){ - spiSendRecvBuf((uint8_t *)cfgData,len,true); //data is stored in buffer passed in - dataSync(); - return; - } - else{ - debugPrintln("Slow config read"); - delay(100); - return; - } -} - -/******************************************************************************* -Description: Configures the behaviour of the watchdog timer. - -Parameters: -uint16_t milliseconds - Time in milliseconds in range 0-65535. - If any P1AM-100 function is not called before the time elapses, - the Base Controller will toggle the reset pin on the P1AM-100. - -Returns: -None -*******************************************************************************/ -void P1AM::configWD(uint16_t milliseconds, uint8_t toggle) { - uint8_t lowByte; - uint8_t highByte; - uint8_t lowToggle; - uint8_t highToggle; - uint16_t toggleTime = 100; - uint8_t wdData[6]; - - lowByte = (uint8_t) milliseconds; - highByte = (uint8_t) (milliseconds >> 8); - - lowToggle = (uint8_t) toggleTime; - highToggle = (uint8_t) (toggleTime >> 8); - - wdData[0] = CONFIGWD_HDR; - wdData[1] = lowByte; - wdData[2] = highByte; - wdData[3] = lowToggle; - wdData[4] = highToggle; - wdData[5] = toggle & 0x01; - - spiSendRecvBuf(wdData,6); - dataSync(); - return; -} - -/******************************************************************************* -Description: Begin the watchdog timer. If the watchdog is not configured beforehand, - Base Controller WD will default to fullscale (65535) timer. If the Base Controller is not - written to within the time period, the Base Controller will reset the P1AM-100. - -Parameters: -None - -Returns: -None -*******************************************************************************/ -void P1AM::startWD() { - - spiSendRecvByte(STARTWD_HDR); //Send "StartWD" header to Base Controller - if(spiTimeout(1000*200) == true){ - spiSendRecvByte(DUMMY); //Send "PetWD" header to Base Controller - dataSync(); - } - return; -} - -/******************************************************************************* -Description: Stops the watchdog timer from running. Use this is you have code - that takes a long time to execute and you are unable to add petWD - calls to. - -Parameters: -None - -Returns: -None -*******************************************************************************/ -void P1AM::stopWD() { - - spiSendRecvByte(STOPWD_HDR); //Send "StopWD" header to Base Controller - if(spiTimeout(1000*200) == true){ - spiSendRecvByte(DUMMY); //Send "PetWD" header to Base Controller - dataSync(); - } - return; -} - -/******************************************************************************* -Description: Pets the watchdog i.e. resets the timer so it does not trigger. Any - P1AM-100 function will pet the watchdog, but this function can be - used in long sections of code or slow loops where the other P1AM-100 - functions are not called. - -Parameters: -None - -Returns: -None -*******************************************************************************/ -void P1AM::petWD() { - - spiSendRecvByte(PETWD_HDR); //Send "PetWD" header to Base Controller - if(spiTimeout(1000*200) == true){ - spiSendRecvByte(DUMMY); //Send "PetWD" header to Base Controller - dataSync(); - } - return; -} - -/******************************************************************************* -Description: Print out current Base Controller firmware version to serial monitor - -Parameters: -None - -Returns: -uint32_t - Firmware Version byte format X.Y.ZZ -*******************************************************************************/ -uint32_t P1AM::getFwVersion(){ - uint32_t version = 0; - - if(handleHDR(VERSION_HDR)){ - version = spiSendRecvInt(DUMMY); - debugPrint("FW V"); - debugPrint(version>>12); - debugPrint("."); - debugPrint(version>>8 & 0xF); - debugPrint("."); - debugPrintln(version & 0xFF); - dataSync(); - return version; - } - else{ - delay(100); - return 0; - } -} - -/******************************************************************************* -Description: Flag to see if the Base Controller has been initialised and is running - -Parameters: -None - -Returns: -Bool - Returns true if Base Controller has been intialised and is active -*******************************************************************************/ -bool P1AM::isBaseActive(){ - bool isActive = false; - - spiSendRecvByte(ACTIVE_HDR); - if(spiTimeout(1000*200) == true){ - isActive = spiSendRecvByte(DUMMY); - } - dataSync(); - return isActive; -} - -/******************************************************************************* -Description: Check to see if any modules that signed on during init have since - dropped out. - -Parameters: -uint8_t numberOfModules - Number of modules expected. Useful if a module - never signed on to begin with. If ommitted this function will only - check for the number of modules that signed on during the last init - - -Returns: -uint8_t - Returns the slot number of the leftmost module in the base - that is no longer functioning. -*******************************************************************************/ -uint8_t P1AM::checkConnection(uint8_t numberOfModules){ - uint8_t expectedSlots = 0; - uint16_t activeSlots = 0; - uint8_t badSlot = 0; - - if(!numberOfModules){ - while(baseSlot[expectedSlots].dbLoc != 0){ - expectedSlots++; //Count number of slots that signed on during init - } - } - else{ - expectedSlots = numberOfModules; - } - - spiSendRecvByte(DROPOUT_HDR); - if(spiTimeout(1000*200) == true){ - activeSlots = spiSendRecvByte(DUMMY); //Get value of active slots. Bitmapped representation. - activeSlots |= spiSendRecvByte(DUMMY) << 8; - } - dataSync(); - - for(int i=0;i> i) == 0){ - return i+1; //Leftmost slot with numbering starting at 1 - } - } - - return 0; //No bad slots found -} - -/******************************************************************************* -Label Functions - These use the channelLabel type defined in P1AM.h instead of -passing slot and channel individually. These Turn P1.readDiscrete(1,1) into -P1.readDiscrete(sens_1). They are functionally the same, so if you want to gain -a better understanding of the inner workings, please check out the actual -function code in the above portion of this library. -*******************************************************************************/ -uint32_t P1AM::readDiscrete(channelLabel label){ - return readDiscrete(label.slot,label.channel); -} - -void P1AM::writeDiscrete(uint32_t data, channelLabel label){ - writeDiscrete(data,label.slot,label.channel); - return; -} - -int P1AM::readAnalog(channelLabel label){ - return readAnalog(label.slot,label.channel); -} - -float P1AM::readTemperature(channelLabel label){ - return readTemperature(label.slot,label.channel); -} - -void P1AM::writeAnalog(uint32_t data, channelLabel label){ - writeAnalog(data,label.slot,label.channel); - return; -} - -void P1AM::writePWM(float duty, uint32_t freq, channelLabel label){ - writePWM(duty,freq,label.slot,label.channel); - return; -} - -void P1AM::writePWMDuty(float duty, channelLabel label){ - writePWMDuty(duty,label.slot,label.channel); - return; -} -void P1AM::writePWMFreq(uint32_t freq, channelLabel label){ - writePWMFreq(freq,label.slot,label.channel); - return; -} - -void P1AM::writePWMDir(bool data, channelLabel label){ - writePWMDir(data,label.slot,label.channel); - return; -} - -uint8_t P1AM::checkUnderRange(channelLabel label){ - return checkUnderRange(label.slot,label.channel); -} - -uint8_t P1AM::checkOverRange(channelLabel label){ - return checkOverRange(label.slot,label.channel); -} - -uint8_t P1AM::checkBurnout(channelLabel label){ - return checkBurnout(label.slot,label.channel); -} - -/******************************************************************************* -PRIVATE FUNCTIONS FOR P1AM.h -*******************************************************************************/ -void P1AM::dataSync(){ - uint32_t currentMillis = 0; - uint32_t startMillis = 0; - - - startMillis = millis(); - while(!digitalRead(slaveAckPin)){ - currentMillis = millis(); - if(currentMillis - startMillis >= 200){ - debugPrintln("Base Sync Timeout"); - break; - } - } - delayMicroseconds(1); - - startMillis = millis(); - while(digitalRead(slaveAckPin)){ - currentMillis = millis(); - if(currentMillis - startMillis >= 200){ - debugPrintln("Base Sync Timeout"); - break; - } - } - delayMicroseconds(1); - - startMillis = millis(); - while(!digitalRead(slaveAckPin)){ - currentMillis = millis(); - if(currentMillis - startMillis >= 200){ - debugPrintln("Base Sync Timeout"); - break; - } - } - delayMicroseconds(1); - - return; -} - -char *P1AM::loadConfigBuf(int moduleID){ - char *defaultCfg; - - switch(moduleID){ - case 0x34605590: - return (char*)P1_04ADL_2_DEFAULT_CONFIG; - break; - case 0x34608C8E: - return (char*)P1_04NTC_DEFAULT_CONFIG; - break; - case 0x34608C81: - return (char*)P1_04THM_DEFAULT_CONFIG; - break; - case 0x34605588: - return (char*)P1_04RTD_DEFAULT_CONFIG; - break; - case 0x34605581: - return (char*)P1_04AD_DEFAULT_CONFIG; - break; - case 0x3460558F: - return (char*)P1_04ADL_1_DEFAULT_CONFIG; - break; - case 0x34A0558A: - return (char*)P1_08ADL_1_DEFAULT_CONFIG; - break; - case 0x34A0558B: - return (char*)P1_08ADL_2_DEFAULT_CONFIG; - break; - case 0x5461A783: - return (char*)P1_04ADL2DAL_1_DEFAULT_CONFIG; - break; - case 0x5461A784: - return (char*)P1_04ADL2DAL_2_DEFAULT_CONFIG; - break; - case 0x1403F481: - return (char*)P1_04PWM_DEFAULT_CONFIG; - break; - case 0x34A5A481: - return (char*)P1_02HSC_DEFAULT_CONFIG; - default: - debugPrintln("wut"); - break; - } -} - -bool P1AM::handleHDR(uint8_t HDR){ - - while(!digitalRead(slaveAckPin)); //Wait for Base Controller to be out of base scanning - spiSendRecvByte(HDR); //Send intital Header to ping DMA - - return spiTimeout(MAX_TIMEOUT,HDR,2000); //1 if we got Base Controller ack, 0 if we took too long -} - -uint8_t P1AM::spiSendRecvByte(uint8_t data){ - - SPI.begin(); - SPI.beginTransaction(P100_SPI_SETTINGS); - digitalWrite(slaveSelectPin, LOW); - data = SPI.transfer(data); - digitalWrite(slaveSelectPin, HIGH); - SPI.endTransaction(); - SPI.end(); - - return data; -} - -uint32_t P1AM::spiSendRecvInt(uint32_t data){ - uint8_t rData[4]; - uint8_t tData[4]; - uint32_t returnInt = 0; - - tData[0] = (data>>0) & 0xFF; // data to write to the Base Controller - 1 byte long - tData[1] = (data>>8) & 0xFF; // data to write to the Base Controller - 1 byte long - tData[2] = (data>>16) & 0xFF; // data to write to the Base Controller - 1 byte long - tData[3] = (data>>24) & 0xFF; // data to write to the Base Controller - 1 byte long - - SPI.begin(); - SPI.beginTransaction(P100_SPI_SETTINGS); - digitalWrite(slaveSelectPin, LOW); - rData[0] = SPI.transfer(tData[0]); - rData[1] = SPI.transfer(tData[1]); - rData[2] = SPI.transfer(tData[2]); - rData[3] = SPI.transfer(tData[3]); - digitalWrite(slaveSelectPin, HIGH); - SPI.endTransaction(); - SPI.end(); - - returnInt = (rData[3]<<24); - returnInt += (rData[2]<<16); - returnInt += (rData[1]<<8); - returnInt += (rData[0]<<0); - - return returnInt; -} - -void P1AM::spiSendRecvBuf(uint8_t *buf, int len, bool returnData){ - - SPI.begin(); - SPI.beginTransaction(P100_SPI_SETTINGS); - digitalWrite(slaveSelectPin, LOW); - - if(returnData){ - for(int i = 0; i < len; ++i){ - buf[i] = SPI.transfer(buf[i]); //return what we get into our buffer - } - } - else{ - for(int i = 0; i < len; ++i){ - SPI.transfer(buf[i]); //or don't return what we get - } - } - - digitalWrite(slaveSelectPin, HIGH); - SPI.endTransaction(); - SPI.end(); - return; -} - -bool P1AM::spiTimeout(uint32_t uS,uint8_t resendMsg,uint16_t retryPeriod){ - uint16_t retryTime = 0; - - while((!digitalRead(slaveAckPin)) && (uS != 0)){ - delayMicroseconds(1); - uS--; - - retryTime++; - if((retryPeriod) && (retryTime == retryPeriod)){ // if we specified a retry period and it equals the time we've waited - - spiSendRecvByte(resendMsg); - retryTime = 0; - } - } - delayMicroseconds(50); //small delay to let Base Controller load next msg in buf - - if(uS > 0){ - return 1; - } - else{ - debugPrintln("Timeout"); - return 0; - } -} - -bool P1AM::Base_Controller_FW_UPDATE(unsigned int fwLen){ - extern unsigned char FW_IMG_Base_Controller[]; - //int fwLen = sizeof(FW_IMG_Base_Controller); - int chunkSize = 1000; - uint8_t status = 0; - uint8_t tData[9]; - - uint8_t *chunkBuffer = (uint8_t *)malloc(chunkSize); //Make spacs for FW chunks - - SPI.begin(); //Start SPI - Serial.println("Establishing Communication"); - - tData[0] = FW_UPDATE_HDR; - - tData[1] = (fwLen>>0) & 0xFF; // data to write to the Base Controller - 1 byte long - tData[2] = (fwLen>>8) & 0xFF; // data to write to the Base Controller - 1 byte long - tData[3] = (fwLen>>16) & 0xFF; // data to write to the Base Controller - 1 byte long - tData[4] = (fwLen>>24) & 0xFF; // data to write to the Base Controller - 1 byte long - - tData[5] = (chunkSize>>0) & 0xFF; // data to write to the Base Controller - 1 byte long - tData[6] = (chunkSize>>8) & 0xFF; // data to write to the Base Controller - 1 byte long - tData[7] = (chunkSize>>16) & 0xFF; // data to write to the Base Controller - 1 byte long - tData[8] = (chunkSize>>24) & 0xFF; // data to write to the Base Controller - 1 byte long - - spiSendRecvBuf(tData,9); //send chunk - - - if(!spiTimeout(1000*200)){ - delay(100); - return 0; - } - if(spiSendRecvByte(DUMMY) != FW_UPDATE_HDR){ - delay(100); - return 0; - } - - delay(10); - Serial.println("FW Transfer Started"); - while(!digitalRead(slaveAckPin)); //wait for Base Controller to be ready - int fullLoops = fwLen/chunkSize; //How many full chunks - - int offset; - for ( offset = 0; offset <= fullLoops; offset++) - { - - Serial.print((float)100*offset/fullLoops,0);//load percentage - Serial.println("%"); - - memcpy(chunkBuffer,(FW_IMG_Base_Controller + offset*chunkSize),chunkSize); //copy image into buffer to send - while(!digitalRead(slaveAckPin)); //wait for Base Controller to be ready - spiSendRecvBuf(chunkBuffer,chunkSize); //send chunk - - - } - - //last chunk - chunkSize = fwLen % chunkSize; //get the remaining bytes smaller than 1 chunk - if(chunkSize != 0) - { - memcpy(chunkBuffer,(FW_IMG_Base_Controller + offset*chunkSize),chunkSize); //copy image into buffer to send - while(!digitalRead(slaveAckPin)); //wait for Base Controller to be ready - spiSendRecvBuf(chunkBuffer,chunkSize); - } - - Serial.println("100%"); - Serial.println("Hang on a sec"); - - while(!digitalRead(slaveAckPin)); - status = spiSendRecvByte(DUMMY); //CRC check on the image - - - if(status == 1) - { - delay(3000); //Good, give the Base Controller a few seconds to reboot - Serial.print("Update Complete! We're now at "); - getFwVersion(); //Print to confirm new FW is correct version - return 1; - } - - else if(status == 4) - { - Serial.println("Bad FW Checksum "); - Serial.println("FW Update FAIL :c"); - return 0; - } - - else if(status == 5) - { - Serial.println("Bad FW Hash "); - Serial.println("FW Update FAIL :c"); - return 0; - } - - else if(status == 9) - { - Serial.println("Bad FW Hash and CRC"); - Serial.println("FW Update FAIL :c"); - return 0; - } -} +/* +MIT License + +Copyright (c) 2019 FACTS Engineering, LLC + +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. +*/ + +#include "P1AM.h" + +SPISettings P100_SPI_SETTINGS(1000000, MSBFIRST, SPI_MODE2); + +P1AM::P1AM(){ + pinMode(slaveSelectPin, OUTPUT); //Define Slave select pin for Base Controller + pinMode(slaveAckPin, INPUT); //Define Ack pin for Base Controller + pinMode(baseEnable, OUTPUT); //Define baseEnable pin used by Arduino to enable Base Controller +} + +P1AM P1; //Create Class instance + +/******************************************************************************* +Description: Initialise the P1AM-100 Base Controller. It automatically configures each module + with default settings and returns the number of modules that have signed on. + +Parameters: -None + +Returns: -uint8_t - Number of good modules that signed on. +*******************************************************************************/ +uint8_t P1AM::init() { + uint32_t slots = 0; + int dbLoc = 0; + char *cfgArray; + uint8_t di_bytes = 0; // Number of DI Bytes + uint8_t do_bytes = 0; // Number of DO Bytes + uint8_t ai_bytes = 0; // Number of AI Bytes + uint8_t ao_bytes = 0; // Number of AO Bytes + uint8_t st_bytes = 0; // Number of Status Bytes + uint8_t cfg_bytes; // Number of CFG Bytes + union moduleIDs{ // Use a union as a quick convert between byte arrays and ints + uint32_t *IDs; + uint8_t *byteArray; + }modules; + uint8_t retry = 0; + + + memset(baseSlot,0,NUMBER_OF_MODULES); //Clear base constants array + enableBaseController(HIGH); //Start base controller + delay(100); + + if(spiTimeout(1000*5000) == false){ + Serial.println("No Base Controller Activity"); + Serial.println("Check External Supply Connection"); + delay(1000); + return 0; + } + + while(((slots == 0) || (slots > 15)) && (retry < 5)){ //Begin sign-on until number of slots are valid. Only retry 5 times. + if(handleHDR(MOD_HDR)){ + delay(5); + slots = spiSendRecvByte(DUMMY); //Get number of modules in base + if(slots == 0 || slots > 15){ + if(retry > 2){ + enableBaseController(LOW); //Disable base controller + delay(10); + enableBaseController(HIGH); //Start base controller + delay(10); + } + retry++; //Let Base Controller retry + } + } + } + + if(retry >= 5){ //Zero module in the base. Quit sign-on routine and let the user know + Serial.println("Zero modules in the base"); + delay(500); + return 0; + } + + modules.IDs = (uint32_t *)malloc(4*slots); //reserve memory for IDs + modules.byteArray = (uint8_t *)malloc(4*slots); + + spiTimeout(1000*200); + spiSendRecvBuf(modules.byteArray,slots*4,1); //slots * 4 bytes per ID code + + uint8_t *baseControllerConstants = (uint8_t *)malloc(1 * slots * 7);//seven elements in module sign on + for(int i=0;i 0){ //Modules with config Bytes need to havea config loaded + cfgArray = loadConfigBuf(mdb[dbLoc].moduleID); //Get pointer to default config for this module + while(!configureModule(cfgArray, i + 1)){ //configure module + debugPrintln("Working"); + } + } + } + #endif + + delay(50); //Let the Base Controller complete its end of the sign-on + #ifdef DEBUG_PRINT_ON + slots = printModules(); //Returns the number of good modules. Prints IDs to serial monitor + #endif + return slots; +} + +/******************************************************************************* +Description: Enables or disables base controller during normal operation. + +Parameters: -bool state - On or off control + +Returns: -none +*******************************************************************************/ +void P1AM::enableBaseController(bool state){ + + digitalWrite(baseEnable, state); + +} + +/******************************************************************************* +Description: Checks to see if modules in base appear as expected. Disables any modules + that are found not to comply. This function only works after init. + +Parameters: -char* moduleNames[] - List of Module names in order they appear in base + +Returns: -uint16_t - Bitmapped representation of errors. A one in any position + represents a error in configuration. E.g. an error in + slot 1 and 3 would return 0x05. +*******************************************************************************/ +uint16_t P1AM::rollCall(const char* moduleNames[], uint8_t numberOfModules){ + uint8_t numberGoodPresent = 0; + uint16_t slotError = 0; + uint8_t dbLoc = 0; + bool singleError; + + + for(int i = 0;i < numberOfModules;i++){ + dbLoc = baseSlot[i].dbLoc; + + singleError = (moduleNames[i] != mdb[dbLoc].moduleName);//Set to true if there is an error + slotError = slotError | (singleError << i); //Shift bit of error check and add to our return + if(singleError == true){ + debugPrint("Slot "); + debugPrint(i+1); + debugPrintln(" Module Mismatch"); + + debugPrint("Expected: "); + debugPrintln(moduleNames[i]); + + debugPrint("Found: "); + debugPrintln(mdb[dbLoc].moduleName); + + debugPrintln(); //CRLF for next message + + baseSlot[i].dbLoc = 0; //Clear out slot so it no longer functions + } + } + + if(slotError == 0){ + debugPrintln("All Modules Good"); + } + + return slotError; + +} + + +/******************************************************************************* +Description: Read a single discrete input module + +Parameters: -uint8_t slot - Slot to read from. Slots start at 1. + -(Optional) uint8_t channel = 0. If specified reads from "channel". + If left out or 0, reads "data" from all channels where + least Signficant bit is channel 1. + +Returns: -uint32_t data read from the module +*******************************************************************************/ +uint32_t P1AM::readDiscrete(uint8_t slot, uint8_t channel){ + uint32_t data = 0; + uint8_t len = 0; + uint8_t mdbLoc = 0; + char rData[4] = {0,0,0,0}; + + mdbLoc = baseSlot[slot-1].dbLoc; + len = mdb[mdbLoc].diBytes; + + if((slot < 1) || (slot > 15)){ + debugPrintln("Slots must be between 1 and 15"); + return 0; + } + + if((len <= 0)){ + debugPrint("Slot "); + debugPrint(slot); + debugPrintln(": This module has no Discrete Input bytes"); + return 0; + } + + if(channel > (len * 8)){ //8 channels per byte + debugPrintln("This channel is not valid"); + return 0; + } + + rData[0] = READ_DISCRETE_HDR; + rData[1] = slot; + spiSendRecvBuf((uint8_t *)rData,2); + memset(rData,0,4); //clear buffer + if(spiTimeout(1000*200) == true){ + spiSendRecvBuf((uint8_t *)rData,len,true); + data = (rData[3]<<24); + data += (rData[2]<<16); + data += (rData[1]<<8); + data += (rData[0]<<0); + + if(channel != 0){ + data = (data>>(channel-1)) & 1; // shift and mask + } + dataSync(); + return data; + } + else{ + debugPrintln("Slow read"); + delay(100); + return 0; + } +} + +/******************************************************************************* +Description: Write to single discrete output module + +Parameters: -uint32_t data - Data to write to module + -uint8_t slot - Slot to write to. Slots start at 1. + -(Optional) uint8_t channel = 0. If specified writes to "channel". + If left out or 0, writes "data" to all channels where + least Signficant bit is channel 1. + +Returns: -None +*******************************************************************************/ +void P1AM::writeDiscrete(uint32_t data,uint8_t slot, uint8_t channel){ + uint8_t tData[7]; + uint8_t mdbLoc = 0; + uint8_t len = 0; + + mdbLoc = baseSlot[slot-1].dbLoc; + len = mdb[mdbLoc].doBytes; + + if((slot < 1) || (slot > 15)){ + debugPrintln("Slots must be between 1 and 15"); + return; + } + + if((len <= 0)){ + debugPrint("Slot "); + debugPrint(slot); + debugPrintln(": This module has no Discrete Output bytes"); + return; + } + + if(channel > (len * 8)){ //8 channels per byte + debugPrintln("This channel is not valid"); + return; + } + + tData[0] = WRITE_DISCRETE_HDR; + tData[1] = slot; + tData[2] = channel; + + if(channel == 0){ + for(int i=0;i>(8*i) & 0xFF; //Shift and mask + } + } + else{ + tData[3] = data & 0b1; //mask bit + len = 1; //only send 1 byte + } + + + spiSendRecvBuf(tData,len+3); //3 Header bytes plus data length + + dataSync(); + return; +} + +/******************************************************************************* +Description: Read a single analog input module channel. + +Parameters: -uint8_t slot - Slot to read from. Slots start at 1. + -uint8_t channel - Channel to read from. Channels start at 1. + +Returns: -uint32_t - Value of channel in counts. 12-bit returns 0-4095, + 16-bit returns between 0-65535, etc. +*******************************************************************************/ +int P1AM::readAnalog(uint8_t slot, uint8_t channel){ + int data = 0; + uint8_t len = 0; + uint8_t mdbLoc = 0; + char rData[4] = {0,0,0,0}; + + mdbLoc = baseSlot[slot-1].dbLoc; + len = mdb[mdbLoc].aiBytes; + + if((slot < 1) || (slot > 15)){ + debugPrintln("Slots must be between 1 and 15"); + return 0; + } + + if((len <= 0)){ + debugPrint("Slot "); + debugPrint(slot); + debugPrintln(": This module has no Analog Input bytes"); + return 0; + } + + if((channel <= 0) || (channel > (len / 4))){ //4 bytes per channel + debugPrintln("This channel is not valid"); + return 0; + } + + rData[0] = READ_ANALOG_HDR; + rData[1] = slot; + rData[2] = channel; + spiSendRecvBuf((uint8_t *)rData,3); + + if(spiTimeout(1000*200) == true){ + data = spiSendRecvInt(DUMMY); + dataSync(); + return data; + } + else{ + debugPrintln("Slow read"); + delay(100); + return 0; + } +} + +/******************************************************************************* +Description: Read a single temperature input module channel. + +Parameters: -uint8_t slot - Slot to read from. Slots start at 1. + -uint8_t channel - Channel to read from. Channels start at 1. +Returns: -Value of channel in degrees for temperature. mV for voltages. +*******************************************************************************/ +float P1AM::readTemperature(uint8_t slot, uint8_t channel){ + union int2float{ + int data; //We get an int back no matter what analog module. Unions set variables as the same/ + float temperature; //Floats and Ints are both 32 bits, so this lets quickly convert our int to a float. + }ourValue; + + ourValue.data = readAnalog(slot,channel); //Use the same analog read function to get int + + return ourValue.temperature; //return the float +} + +/******************************************************************************* +Description: Write to a single analog output module channel. + +Parameters: -uint32_t data - Value of channel in counts. 12-bit in range 0-4095, + 16-bit in range 0-65535, etc. + -uint8_t slot - Slot to write to. Slots start at 1. + -uint8_t channel - Channel to write to. Channels start at 1. +Returns: -None +*******************************************************************************/ +void P1AM::writeAnalog(uint32_t data,uint8_t slot, uint8_t channel){ + uint8_t tData[7]; + uint8_t mdbLoc = 0; + uint8_t len = 0; + + mdbLoc = baseSlot[slot-1].dbLoc; + len = mdb[mdbLoc].aoBytes; + + if((slot < 1) || (slot > 15)){ + debugPrintln("Slots must be between 1 and 15"); + return; + } + + if((len <= 0)){ + debugPrint("Slot "); + debugPrint(slot); + debugPrintln(": This module has no Analog Output bytes"); + return; + } + + if((channel <= 0) || (channel > (len / 4))){ //4 bytes per channel + debugPrintln("This channel is not valid"); + return; + } + + tData[0] = WRITE_ANALOG_HDR; + tData[1] = slot; + tData[2] = channel; + tData[3] = (data>>0) & 0xFF; + tData[4] = (data>>8) & 0xFF; + tData[5] = (data>>16) & 0xFF; + tData[6] = (data>>24) & 0xFF; + spiSendRecvBuf(tData,7); + + dataSync(); + return; +} + +/******************************************************************************* +Description: Read a block of data stored in Base Controller. This allows you to read data + from many modules in one command, but requires you to calculate the + offset and lengths and later parse out the data. You can also read + the state of output data through this for debugging purposes. This + function is more advanced and is not recommended for new programmers. + +Parameters: -char buf[] - Pointer to an array that will hold the values read. This + function does not return a value, but stores in the *buf passed in. + -uint16_t len - Number of bytes to read + -uint8_t offset - Starting byte in array to read. + -uint8_t type - Specifies which Base Controller data block to read from. + 0 is Discrete Input, 1 is Analog Input, 2 is Discrete Output, 3 is Analog Output,4 is Status. + +Returns: -None +*******************************************************************************/ +void P1AM::readBlockData(char buf[], uint16_t len,uint16_t offset, uint8_t type){ + uint8_t readParams[6]; + + if((len+offset) > 1200){ //max of data array is 1200, so we can't read past that + len = 1200-offset; //adjust len in case we're trying to read too far + } + + readParams[0] = READ_BLOCK_HDR; + readParams[1] = type; // 0 is Discrete In, 1 is Analog In, 2 is Discrete Out, 3 is Analog Out,4 is Status + readParams[2] = len >> 8; + readParams[3] = len & 0xFF; + readParams[4] = offset >> 8; + readParams[5] = offset & 0xFF; + spiSendRecvBuf(readParams,6); //Send paramters for block read + + if(spiTimeout(1000*200) == true){ + spiSendRecvBuf((uint8_t *)buf,len,true); //data is stored in buffer passed in + dataSync(); + return; + } + else{ + debugPrintln("Slow block read"); + delay(100); + return; + } +} + +/******************************************************************************* +Description: Write to a block of data stored in Base Controller. This allows you to write data + to many modules in one command, but requires you to calculate the + offset and lengths and parse together the data. You can write to input data, + but be aware the Base Controller may overwrite this data in normal operations. + This function is more advanced than others and is not recommended + for new programmers. + +Parameters: -char buf[] - Pointer to an array that holds the values to write. This + array is not overwritten. + -uint16_t len - Number of bytes to write + -uint8_t offset - Starting byte in array to write. + -uint8_t type - Specifies which Base Controller data block to write to. + 0 is Discrete Input, 1 is Analog Input, 2 is Discrete Output, 3 is Analog Output,4 is Status. + +Returns: -None +*******************************************************************************/ +void P1AM::writeBlockData(char buf[], uint16_t len,uint16_t offset, uint8_t type){ + + if((len+offset) > 1200){ //max of data array is 1200, so we can't read past that + len = 1200-offset; //adjust len in case we're trying to read too far + } + + uint8_t *tData = (uint8_t *)malloc(len+6); + tData[0] = WRITE_BLOCK_HDR; + tData[1] = type; + tData[2] = len >> 8; + tData[3] = len & 0xFF; + tData[4] = offset >> 8; + tData[5] = offset & 0xFF; + + memcpy(tData+6,buf,len); + spiSendRecvBuf(tData,len+6,0); //data is not stored in passed buffer + free(tData); + + dataSync(); + return; +} + +/******************************************************************************* +Description: Set both duty cycle and frequency of a PWM output module channel. + +Parameters: -float duty - Duty cycle. Range 0.00-100.00. You can include up to 2 decimal places + -uint32_t freq - Frequency. See module data sheet for range. P1-04PWM is 0-20kHz. + -uint8_t slot - Slot to write to. Slots start at 1. + -uint8_t channel - Channel to write to. Channels start at 1. + +Returns: -None +*******************************************************************************/ +void P1AM::writePWM(float duty,uint32_t freq,uint8_t slot,uint8_t channel){ + uint8_t mdbLoc = 0; + uint8_t tempLoc = 0; + uint8_t offset = 0; + uint32_t dutyInt = 0; + char tData[8]; + + mdbLoc = baseSlot[slot-1].dbLoc; + + if((slot < 1) || (slot > 15)){ + debugPrintln("Slots must be between 1 and 15"); + return; + } + + if((channel <= 0) || (channel > 4)){ + debugPrintln("This channel is not valid"); + return; + } + + if((mdb[mdbLoc].dataSize & 0xF0) != 0xA0){ //Is this PWM + debugPrint("Slot "); + debugPrint(slot); + debugPrintln(": This module is not a PWM module"); + return; //Not PWM + } + + for(int i=0;i 0){ + offset += mdb[tempLoc].aoBytes; //get offset of analog bytes + } + } + + offset += (channel - 1) * 8; //Each channel uses 8 bytes + dutyInt = (uint32_t)(duty * 100);// shift decimal over 2 places and cast off remainder. e.g. 12.3456 turns into 1234 + + tData[3] = (dutyInt>>0) & 0xFF; //shift and mask to bytes + tData[2] = (dutyInt>>8) & 0xFF; + tData[1] = (dutyInt>>16) & 0xFF; + tData[0] = (dutyInt>>24) & 0xFF; + tData[7] = (freq>>0) & 0xFF; + tData[6] = (freq>>8) & 0xFF; + tData[5] = (freq>>16) & 0xFF; + tData[4] = (freq>>24) & 0xFF; + + writeBlockData(tData, 8, offset, ANALOG_OUT_BLOCK); //Use block data to ensure duty/freq are entered at the same time. + dataSync(); + + return; +} + +/******************************************************************************* +Description: Set duty cycle of a PWM output module channel. + +Parameters: -float duty - Duty cycle. Range 0.00-100.00. You can include up to 2 decimal places + -uint8_t slot - Slot to write to. Slots start at 1. + -uint8_t channel - Channel to write to. Channels start at 1. + +Returns: -None +*******************************************************************************/ +void P1AM::writePWMDuty(float duty,uint8_t slot,uint8_t channel){ + uint8_t mdbLoc = 0; + uint32_t dutyInt = 0; + + mdbLoc = baseSlot[slot-1].dbLoc; + + if((slot < 1) || (slot > 15)){ + debugPrintln("Slots must be between 1 and 15"); + return; + } + + if((channel <= 0) || (channel > 4)){ + debugPrintln("This channel is not valid"); + return; + } + + if((mdb[mdbLoc].dataSize & 0xF0) != 0xA0){ //Is this PWM + debugPrint("Slot "); + debugPrint(slot); + debugPrintln(": This module is not a PWM module"); + return; //Not PWM + } + + dutyInt = (uint32_t)(duty * 100);// shift decimal over 2 places and cast off remainder. e.g. 12.3456 turns into 1234 + channel = 1 + ((channel-1) * 2); + P1.writeAnalog(dutyInt,slot,channel); + + dataSync(); + return; +} + +/******************************************************************************* +Description: Set frequency of a PWM output module channel. + +Parameters: -uint32_t freq - Frequency. See module data sheet for range. P1-04PWM is 0-20kHz. + -uint8_t slot - Slot to write to. Slots start at 1. + -uint8_t channel - Channel to write to. Channels start at 1. + +Returns: -None +*******************************************************************************/ +void P1AM::writePWMFreq(uint32_t freq,uint8_t slot,uint8_t channel){ + uint8_t mdbLoc = 0; + + mdbLoc = baseSlot[slot-1].dbLoc; + + if((slot < 1) || (slot > 15)){ + debugPrintln("Slots must be between 1 and 15"); + return; + } + + if((channel <= 0) || (channel > 4)){ + debugPrintln("This channel is not valid"); + return; + } + + if((mdb[mdbLoc].dataSize & 0xF0) != 0xA0){ //Is this PWM + debugPrint("Slot "); + debugPrint(slot); + debugPrintln("This module is not a PWM module"); + return; //Not PWM + } + + channel = 2 + ((channel-1) * 2); + P1.writeAnalog(freq,slot,channel); + + dataSync(); + return; +} + + /******************************************************************************* +Description: Set direction of a PWM module channel when set to DIR mode. + See configuration document to see how to enable this feature. + +Parameters: -bool data - Set channel on or off + -uint8_t slot - Slot to write to. Slots start at 1. + -uint8_t channel - Channel to write to. Channels start at 1. + +Returns: -None +*******************************************************************************/ +void P1AM::writePWMDir(bool data,uint8_t slot, uint8_t channel){ + uint8_t mdbLoc = 0; + + mdbLoc = baseSlot[slot-1].dbLoc; + + if((slot < 1) || (slot > 15)){ + debugPrintln("Slots must be between 1 and 15"); + return; + } + + if((channel <= 0) || (channel > 4)){ + debugPrintln("This channel is not valid"); + return; + } + + if((mdb[mdbLoc].dataSize & 0xF0) != 0xA0){ //Is this PWM + debugPrint("Slot "); + debugPrint(slot); + debugPrintln(": This module is not a PWM module"); + return; //Not PWM + } + + channel = 1 + ((channel-1) * 2); + P1.writeAnalog(data,slot,channel); + + dataSync(); + return; +} + + +/******************************************************************************* +Description: Print the names of all the modules in the base to the serial monitor + +Parameters: -None + +Returns: -Number of good sign-ons +*******************************************************************************/ +uint8_t P1AM::printModules(){ + uint32_t slot = 0; + uint8_t dbLoc = 0; + uint8_t goodSlots = 0; + + while(baseSlot[slot].dbLoc != 0){ + dbLoc = baseSlot[slot].dbLoc; + + if(mdb[dbLoc].moduleID == 0 || mdb[dbLoc].moduleID == 0xFFFFFFFF){ + Serial.print("Slot "); + Serial.print(slot + 1); + Serial.println(": Sign On Error"); + } + else{ + Serial.print("Slot "); + Serial.print(slot + 1); + Serial.print(": "); + Serial.println(mdb[dbLoc].moduleName); + goodSlots++; + } + slot++; + } + dataSync(); + Serial.println(""); //Print an empty line to give us some breathing room later + return goodSlots; +} + +/******************************************************************************* +Description: Read a single status byte from a single module + +Parameters: -int byteNum - Which status byte to read in. Byte numbering starts at 0. + -int slot - Which slot to read from + +Returns: -char - The status byte. +*******************************************************************************/ +char P1AM::readStatus(int byteNum,int slot){ + uint8_t len = 0; + uint8_t mdbLoc = 0; + uint8_t buf[1]; + uint8_t rData[4]; + + mdbLoc = baseSlot[slot-1].dbLoc; + len = 1; //only need 1 byte + + if((slot < 1) || (slot > 15)){ + debugPrintln("Slots must be between 1 and 15"); + return 0; + } + + if(mdb[mdbLoc].statusBytes <= 0){ + debugPrint("Slot "); + debugPrint(slot); + debugPrintln(": This module has no Status bytes"); + return 0; //No status Bytes + } + + rData[0] = READ_STATUS_HDR; + rData[1] = slot; + rData[2] = len; + rData[3] = byteNum;//offset + spiSendRecvBuf((uint8_t *)rData,4); + + if(spiTimeout(1000*200) == true){ + spiSendRecvBuf((uint8_t *)buf,len,true); //data is stored in buffer passed in + dataSync(); + return buf[0]; + } + else{ + debugPrintln("Slow status read"); + delay(100); + return 0; + } +} + +/******************************************************************************* +Description: Read all status bytes from a single module + +Parameters: -char buf[] - Array to store the read status bytes in. This function + does not return a value, but places it in the array passed in + -uint8_t slot - Slot that you want to read status bytes from + +Returns: -None +*******************************************************************************/ +void P1AM::readStatus(char buf[], uint8_t slot){ + uint8_t len = 0; + uint8_t mdbLoc = 0; + uint8_t rData[4]; + + mdbLoc = baseSlot[slot-1].dbLoc; + len = mdb[mdbLoc].statusBytes; + + if((slot < 1) || (slot > 15)){ + debugPrintln("Slots must be between 1 and 15"); + return; + } + + if(len <= 0){ + debugPrint("Slot "); + debugPrint(slot); + debugPrintln(": This module has no Status bytes"); + return; //No status Bytes + } + + rData[0] = READ_STATUS_HDR; + rData[1] = slot; + rData[2] = len; + rData[3] = 0;//offset + spiSendRecvBuf((uint8_t *)rData,4); + + if(spiTimeout(1000*200) == true){ + spiSendRecvBuf((uint8_t *)buf,len,true); //data is stored in buffer passed in + dataSync(); + return; + } + else{ + debugPrintln("Slow status read"); + delay(100); + return; + } +} + +/******************************************************************************* +Description: Checks the under range error on select P1000 modules. Returns if channel + is under range. This function does not check if the module supports + this feature. + +Parameters: -uint8_t slot - Which slot to read from. + -(Optional) uint8_t channel = 0. If value is non-zero checks the + specified channel. If left out or 0, checks all channels where + least Signficant bit is the status of channel 1. + + +Returns: -uint8_t - returns under range status +*******************************************************************************/ +uint8_t P1AM::checkUnderRange(uint8_t slot, uint8_t channel){ + uint8_t statusByte = 0; + + statusByte = readStatus(UNDER_RANGE_STATUS,slot); + if(channel){ + statusByte = (statusByte >> (channel - 1)) & 1; //shift and mask bits + } + + return statusByte; +} + +/******************************************************************************* +Description: Checks the over range error on select P1000 modules. Returns if channel + is over range. This function does not check if the module supports + this feature. + +Parameters: -uint8_t slot - Which slot to read from. + -(Optional) uint8_t channel = 0. If value is non-zero checks the + specified channel. If left out or 0, checks all channels where + least Signficant bit is the status of channel 1. + + +Returns: -uint8_t - returns over range status +*******************************************************************************/ +uint8_t P1AM::checkOverRange(uint8_t slot, uint8_t channel){ + uint8_t statusByte = 0; + + statusByte = readStatus(OVER_RANGE_STATUS,slot); + if(channel){ + statusByte = (statusByte >> (channel - 1)) & 1; //shift and mask bits. + } + + return statusByte; +} + +/******************************************************************************* +Description: Checks the burnout error on select P1000 modules. Returns if channel + is over range. This function does not check if the module supports + this feature. + +Parameters: -uint8_t slot - Which slot to read from. + -(Optional) uint8_t channel = 0. If value is non-zero checks the + specified channel. If left out or 0, checks all channels where + least Signficant bit is the status of channel 1. + + +Returns: -uint8_t - returns burnout status +*******************************************************************************/ +uint8_t P1AM::checkBurnout(uint8_t slot, uint8_t channel){ + uint8_t statusByte = 0; + + statusByte = readStatus(BURNOUT_STATUS,slot); + if(channel){ + statusByte = (statusByte >> (channel - 1)) & 1; //shift and mask bits + } + + return statusByte; +} + +/******************************************************************************* +Description: Checks the lost 24V error on select P1000 modules. Returns 1 if slot is missing + 24V. This function does not check if the module supports this feature. + +Parameters: -uint8_t slot - Which slot to read from. + +Returns: -uint8_t - 1 if 24V is missing. 0 if 24V is present +*******************************************************************************/ +uint8_t P1AM::check24V(uint8_t slot){ + uint8_t statusByte = 0; + + statusByte = readStatus(MISSING24V_STATUS,slot); + statusByte = (statusByte >> 1) & 1; //Mask and shift for 24V bit + + return statusByte; +} + +/******************************************************************************* +Description: Manually configure a module. Use this if you want to use a setting + that is not the default for a module. + Visit https://facts-engineering.github.io/config.html for more information + +Parameters: -uint8_t slot - Slot you want to configure. + -char *cfgData - Pointer to array that contains the configuration settings + +Returns: -bool - Configuration successfully written to Base Controller. +*******************************************************************************/ +bool P1AM::configureModule(char cfgData[], uint8_t slot){ + uint8_t len = 0; + uint8_t mdbLoc = 0; + uint8_t cfgForSpi[66]; + + mdbLoc = baseSlot[slot-1].dbLoc; + len = mdb[mdbLoc].configBytes + 2; + cfgForSpi[0] = CFG_HDR; + cfgForSpi[1] = slot; + + memcpy(cfgForSpi+2,cfgData,len); + delay(1); + if((slot < 1) || (slot > 15)){ + debugPrintln("Slots must be between 1 and 15"); + return 0; + } + + if(len <= 0){ + debugPrint("Slot "); + debugPrint(slot); + debugPrintln(": This module has no config Bytes"); + return 0; + } + + spiSendRecvBuf(cfgForSpi,len); + delay(100); //Additional time for Config to written + dataSync(); + dataSync(); + return 1; +} + +/******************************************************************************* +Overload of above function for const arrays +*******************************************************************************/ +bool P1AM::configureModule(const char cfgData[], uint8_t slot){ + + return configureModule((char*)cfgData, slot); + +} + +/******************************************************************************* +Description: Read current configuration of a module. Data is stored in the passed + in array pointer. + + +Parameters: -char *cfgData - Pointer to array that will receive the current configuration + -uint8_t slot - Slot you want to configure. + +Returns: -none +*******************************************************************************/ +void P1AM::readModuleConfig(char cfgData[], uint8_t slot){ + uint8_t len = 0; + uint8_t mdbLoc = 0; + uint8_t cfgForSpi[2]; + + mdbLoc = baseSlot[slot-1].dbLoc; + len = mdb[mdbLoc].configBytes; + + if((slot < 1) || (slot > 15)){ + debugPrintln("Slots must be between 1 and 15"); + return; + } + + if(len <= 0){ + debugPrint("Slot "); + debugPrint(slot); + debugPrintln(": This module has no config Bytes"); + return; + } + + cfgForSpi[0] = READ_CFG_HDR; + cfgForSpi[1] = slot; + spiSendRecvBuf((uint8_t *)cfgForSpi,2); + + if(spiTimeout(1000*200) == true){ + spiSendRecvBuf((uint8_t *)cfgData,len,true); //data is stored in buffer passed in + dataSync(); + return; + } + else{ + debugPrintln("Slow config read"); + delay(100); + return; + } +} + +/******************************************************************************* +Description: Configures the behaviour of the watchdog timer. + +Parameters: -uint16_t milliseconds - Time in milliseconds in range 0-65535. + If any P1AM-100 function is not called before the time elapses, + the Base Controller will toggle the reset pin on the P1AM-100. + +Returns: -None +*******************************************************************************/ +void P1AM::configWD(uint16_t milliseconds, uint8_t toggle) { + uint8_t lowByte; + uint8_t highByte; + uint8_t lowToggle; + uint8_t highToggle; + uint16_t toggleTime = 100; + uint8_t wdData[6]; + + lowByte = (uint8_t) milliseconds; + highByte = (uint8_t) (milliseconds >> 8); + + lowToggle = (uint8_t) toggleTime; + highToggle = (uint8_t) (toggleTime >> 8); + + wdData[0] = CONFIGWD_HDR; + wdData[1] = lowByte; + wdData[2] = highByte; + wdData[3] = lowToggle; + wdData[4] = highToggle; + wdData[5] = toggle & 0x01; + + spiSendRecvBuf(wdData,6); + dataSync(); + return; +} + +/******************************************************************************* +Description: Begin the watchdog timer. If the watchdog is not configured beforehand, + Base Controller WD will default to fullscale (65535) timer. If the Base Controller is not + written to within the time period, the Base Controller will reset the P1AM-100. + +Parameters: -None + +Returns: -None +*******************************************************************************/ +void P1AM::startWD() { + + spiSendRecvByte(STARTWD_HDR); //Send "StartWD" header to Base Controller + if(spiTimeout(1000*200) == true){ + spiSendRecvByte(DUMMY); //Send "PetWD" header to Base Controller + dataSync(); + } + return; +} + +/******************************************************************************* +Description: Stops the watchdog timer from running. Use this is you have code + that takes a long time to execute and you are unable to add petWD + calls to. + +Parameters: -None + +Returns: -None +*******************************************************************************/ +void P1AM::stopWD() { + + spiSendRecvByte(STOPWD_HDR); //Send "StopWD" header to Base Controller + if(spiTimeout(1000*200) == true){ + spiSendRecvByte(DUMMY); //Send "PetWD" header to Base Controller + dataSync(); + } + return; +} + +/******************************************************************************* +Description: Pets the watchdog i.e. resets the timer so it does not trigger. Any + P1AM-100 function will pet the watchdog, but this function can be + used in long sections of code or slow loops where the other P1AM-100 + functions are not called. + +Parameters: -None + +Returns: -None +*******************************************************************************/ +void P1AM::petWD() { + + spiSendRecvByte(PETWD_HDR); //Send "PetWD" header to Base Controller + if(spiTimeout(1000*200) == true){ + spiSendRecvByte(DUMMY); //Send "PetWD" header to Base Controller + dataSync(); + } + return; +} + +/******************************************************************************* +Description: Print out current Base Controller firmware version to serial monitor + +Parameters: -None + +Returns: -uint32_t - Firmware Version byte format X.Y.ZZ +*******************************************************************************/ +uint32_t P1AM::getFwVersion(){ + uint32_t version = 0; + + if(handleHDR(VERSION_HDR)){ + version = spiSendRecvInt(DUMMY); + debugPrint("FW V"); + debugPrint(version>>12); + debugPrint("."); + debugPrint(version>>8 & 0xF); + debugPrint("."); + debugPrintln(version & 0xFF); + dataSync(); + return version; + } + else{ + delay(100); + return 0; + } +} + +/******************************************************************************* +Description: Flag to see if the Base Controller has been initialised and is running + +Parameters: -None + +Returns: -Bool - Returns true if Base Controller has been intialised and is active +*******************************************************************************/ +bool P1AM::isBaseActive(){ + bool isActive = false; + + spiSendRecvByte(ACTIVE_HDR); + if(spiTimeout(1000*200) == true){ + isActive = spiSendRecvByte(DUMMY); + } + dataSync(); + return isActive; +} + +/******************************************************************************* +Description: Check to see if any modules that signed on during init have since + dropped out. + +Parameters: -uint8_t numberOfModules - Number of modules expected. Useful if a module + never signed on to begin with. If ommitted this function will only + check for the number of modules that signed on during the last init + + +Returns: -uint8_t - Returns the slot number of the leftmost module in the base + that is no longer functioning. +*******************************************************************************/ +uint8_t P1AM::checkConnection(uint8_t numberOfModules){ + uint8_t expectedSlots = 0; + uint16_t activeSlots = 0; + uint8_t badSlot = 0; + + if(!numberOfModules){ + while(baseSlot[expectedSlots].dbLoc != 0){ + expectedSlots++; //Count number of slots that signed on during init + } + } + else{ + expectedSlots = numberOfModules; + } + + spiSendRecvByte(DROPOUT_HDR); + if(spiTimeout(1000*200) == true){ + activeSlots = spiSendRecvByte(DUMMY); //Get value of active slots. Bitmapped representation. + activeSlots |= spiSendRecvByte(DUMMY) << 8; + } + dataSync(); + + for(int i=0;i> i) == 0){ + return i+1; //Leftmost slot with numbering starting at 1 + } + } + + return 0; //No bad slots found +} + +/******************************************************************************* +Label Functions - These use the channelLabel type defined in P1AM.h instead of +passing slot and channel individually. These Turn P1.readDiscrete(1,1) into +P1.readDiscrete(sens_1). They are functionally the same, so if you want to gain +a better understanding of the inner workings, please check out the actual +function code in the above portion of this library. +*******************************************************************************/ +uint32_t P1AM::readDiscrete(channelLabel label){ + return readDiscrete(label.slot,label.channel); +} + +void P1AM::writeDiscrete(uint32_t data, channelLabel label){ + writeDiscrete(data,label.slot,label.channel); + return; +} + +int P1AM::readAnalog(channelLabel label){ + return readAnalog(label.slot,label.channel); +} + +float P1AM::readTemperature(channelLabel label){ + return readTemperature(label.slot,label.channel); +} + +void P1AM::writeAnalog(uint32_t data, channelLabel label){ + writeAnalog(data,label.slot,label.channel); + return; +} + +void P1AM::writePWM(float duty, uint32_t freq, channelLabel label){ + writePWM(duty,freq,label.slot,label.channel); + return; +} + +void P1AM::writePWMDuty(float duty, channelLabel label){ + writePWMDuty(duty,label.slot,label.channel); + return; +} +void P1AM::writePWMFreq(uint32_t freq, channelLabel label){ + writePWMFreq(freq,label.slot,label.channel); + return; +} + +void P1AM::writePWMDir(bool data, channelLabel label){ + writePWMDir(data,label.slot,label.channel); + return; +} + +uint8_t P1AM::checkUnderRange(channelLabel label){ + return checkUnderRange(label.slot,label.channel); +} + +uint8_t P1AM::checkOverRange(channelLabel label){ + return checkOverRange(label.slot,label.channel); +} + +uint8_t P1AM::checkBurnout(channelLabel label){ + return checkBurnout(label.slot,label.channel); +} + +/******************************************************************************* +PRIVATE FUNCTIONS FOR P1AM.h +*******************************************************************************/ +void P1AM::dataSync(){ + uint32_t currentMillis = 0; + uint32_t startMillis = 0; + + + startMillis = millis(); + while(!digitalRead(slaveAckPin)){ + currentMillis = millis(); + if(currentMillis - startMillis >= 200){ + debugPrintln("Base Sync Timeout"); + break; + } + } + delayMicroseconds(1); + + startMillis = millis(); + while(digitalRead(slaveAckPin)){ + currentMillis = millis(); + if(currentMillis - startMillis >= 200){ + debugPrintln("Base Sync Timeout"); + break; + } + } + delayMicroseconds(1); + + startMillis = millis(); + while(!digitalRead(slaveAckPin)){ + currentMillis = millis(); + if(currentMillis - startMillis >= 200){ + debugPrintln("Base Sync Timeout"); + break; + } + } + delayMicroseconds(1); + + return; +} + +char *P1AM::loadConfigBuf(int moduleID){ + char *defaultCfg; + + switch(moduleID){ + case 0x34605582: + return (char*)P1_04AD_1_DEFAULT_CONFIG; + case 0x34605583: + return (char*)P1_04AD_2_DEFAULT_CONFIG; + case 0x34605590: + return (char*)P1_04ADL_2_DEFAULT_CONFIG; + case 0x34608C8E: + return (char*)P1_04NTC_DEFAULT_CONFIG; + case 0x34608C81: + return (char*)P1_04THM_DEFAULT_CONFIG; + case 0x34605588: + return (char*)P1_04RTD_DEFAULT_CONFIG; + case 0x34605581: + return (char*)P1_04AD_DEFAULT_CONFIG; + case 0x3460558F: + return (char*)P1_04ADL_1_DEFAULT_CONFIG; + case 0x34A0558A: + return (char*)P1_08ADL_1_DEFAULT_CONFIG; + case 0x34A0558B: + return (char*)P1_08ADL_2_DEFAULT_CONFIG; + case 0x5461A783: + return (char*)P1_04ADL2DAL_1_DEFAULT_CONFIG; + case 0x5461A784: + return (char*)P1_04ADL2DAL_2_DEFAULT_CONFIG; + case 0x1403F481: + return (char*)P1_04PWM_DEFAULT_CONFIG; + case 0x34A5A481: + return (char*)P1_02HSC_DEFAULT_CONFIG; + default: + break; + } +} + +bool P1AM::handleHDR(uint8_t HDR){ + + while(!digitalRead(slaveAckPin)); //Wait for Base Controller to be out of base scanning + spiSendRecvByte(HDR); //Send intital Header to ping DMA + + return spiTimeout(MAX_TIMEOUT,HDR,2000); //1 if we got Base Controller ack, 0 if we took too long +} + +uint8_t P1AM::spiSendRecvByte(uint8_t data){ + + SPI.begin(); + SPI.beginTransaction(P100_SPI_SETTINGS); + digitalWrite(slaveSelectPin, LOW); + data = SPI.transfer(data); + digitalWrite(slaveSelectPin, HIGH); + SPI.endTransaction(); + SPI.end(); + + return data; +} + +uint32_t P1AM::spiSendRecvInt(uint32_t data){ + uint8_t rData[4]; + uint8_t tData[4]; + uint32_t returnInt = 0; + + tData[0] = (data>>0) & 0xFF; // data to write to the Base Controller - 1 byte long + tData[1] = (data>>8) & 0xFF; // data to write to the Base Controller - 1 byte long + tData[2] = (data>>16) & 0xFF; // data to write to the Base Controller - 1 byte long + tData[3] = (data>>24) & 0xFF; // data to write to the Base Controller - 1 byte long + + SPI.begin(); + SPI.beginTransaction(P100_SPI_SETTINGS); + digitalWrite(slaveSelectPin, LOW); + rData[0] = SPI.transfer(tData[0]); + rData[1] = SPI.transfer(tData[1]); + rData[2] = SPI.transfer(tData[2]); + rData[3] = SPI.transfer(tData[3]); + digitalWrite(slaveSelectPin, HIGH); + SPI.endTransaction(); + SPI.end(); + + returnInt = (rData[3]<<24); + returnInt += (rData[2]<<16); + returnInt += (rData[1]<<8); + returnInt += (rData[0]<<0); + + return returnInt; +} + +void P1AM::spiSendRecvBuf(uint8_t *buf, int len, bool returnData){ + + SPI.begin(); + SPI.beginTransaction(P100_SPI_SETTINGS); + digitalWrite(slaveSelectPin, LOW); + + if(returnData){ + for(int i = 0; i < len; ++i){ + buf[i] = SPI.transfer(buf[i]); //return what we get into our buffer + } + } + else{ + for(int i = 0; i < len; ++i){ + SPI.transfer(buf[i]); //or don't return what we get + } + } + + digitalWrite(slaveSelectPin, HIGH); + SPI.endTransaction(); + SPI.end(); + return; +} + +bool P1AM::spiTimeout(uint32_t uS,uint8_t resendMsg,uint16_t retryPeriod){ + uint16_t retryTime = 0; + + while((!digitalRead(slaveAckPin)) && (uS != 0)){ + delayMicroseconds(1); + uS--; + + retryTime++; + if((retryPeriod) && (retryTime == retryPeriod)){ // if we specified a retry period and it equals the time we've waited + + spiSendRecvByte(resendMsg); + retryTime = 0; + } + } + delayMicroseconds(50); //small delay to let Base Controller load next msg in buf + + if(uS > 0){ + return 1; + } + else{ + debugPrintln("Timeout"); + return 0; + } +} + +bool P1AM::Base_Controller_FW_UPDATE(unsigned int fwLen){ + extern unsigned char FW_IMG_Base_Controller[]; + //int fwLen = sizeof(FW_IMG_Base_Controller); + int chunkSize = 1000; + uint8_t status = 0; + uint8_t tData[9]; + + uint8_t *chunkBuffer = (uint8_t *)malloc(chunkSize); //Make spacs for FW chunks + + SPI.begin(); //Start SPI + Serial.println("Establishing Communication"); + + tData[0] = FW_UPDATE_HDR; + + tData[1] = (fwLen>>0) & 0xFF; // data to write to the Base Controller - 1 byte long + tData[2] = (fwLen>>8) & 0xFF; // data to write to the Base Controller - 1 byte long + tData[3] = (fwLen>>16) & 0xFF; // data to write to the Base Controller - 1 byte long + tData[4] = (fwLen>>24) & 0xFF; // data to write to the Base Controller - 1 byte long + + tData[5] = (chunkSize>>0) & 0xFF; // data to write to the Base Controller - 1 byte long + tData[6] = (chunkSize>>8) & 0xFF; // data to write to the Base Controller - 1 byte long + tData[7] = (chunkSize>>16) & 0xFF; // data to write to the Base Controller - 1 byte long + tData[8] = (chunkSize>>24) & 0xFF; // data to write to the Base Controller - 1 byte long + + spiSendRecvBuf(tData,9); //send chunk + + + if(!spiTimeout(1000*200)){ + delay(100); + return 0; + } + if(spiSendRecvByte(DUMMY) != FW_UPDATE_HDR){ + delay(100); + return 0; + } + + delay(10); + Serial.println("FW Transfer Started"); + while(!digitalRead(slaveAckPin)); //wait for Base Controller to be ready + int fullLoops = fwLen/chunkSize; //How many full chunks + + int offset; + for ( offset = 0; offset <= fullLoops; offset++) + { + + Serial.print((float)100*offset/fullLoops,0);//load percentage + Serial.println("%"); + + memcpy(chunkBuffer,(FW_IMG_Base_Controller + offset*chunkSize),chunkSize); //copy image into buffer to send + while(!digitalRead(slaveAckPin)); //wait for Base Controller to be ready + spiSendRecvBuf(chunkBuffer,chunkSize); //send chunk + + + } + + //last chunk + chunkSize = fwLen % chunkSize; //get the remaining bytes smaller than 1 chunk + if(chunkSize != 0) + { + memcpy(chunkBuffer,(FW_IMG_Base_Controller + offset*chunkSize),chunkSize); //copy image into buffer to send + while(!digitalRead(slaveAckPin)); //wait for Base Controller to be ready + spiSendRecvBuf(chunkBuffer,chunkSize); + } + + Serial.println("100%"); + Serial.println("Hang on a sec"); + + while(!digitalRead(slaveAckPin)); + status = spiSendRecvByte(DUMMY); //CRC check on the image + + + if(status == 1) + { + delay(3000); //Good, give the Base Controller a few seconds to reboot + Serial.print("Update Complete! We're now at "); + getFwVersion(); //Print to confirm new FW is correct version + return 1; + } + + else if(status == 4) + { + Serial.println("Bad FW Checksum "); + Serial.println("FW Update FAIL :c"); + return 0; + } + + else if(status == 5) + { + Serial.println("Bad FW Hash "); + Serial.println("FW Update FAIL :c"); + return 0; + } + + else if(status == 9) + { + Serial.println("Bad FW Hash and CRC"); + Serial.println("FW Update FAIL :c"); + return 0; + } +} diff --git a/src/P1AM.h b/src/P1AM.h index 9a251d7..81f898b 100644 --- a/src/P1AM.h +++ b/src/P1AM.h @@ -1,116 +1,116 @@ -/* -MIT License - -Copyright (c) 2019 FACTS Engineering, LLC - -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. - - P1AM.h - Library for Communicating with P1000 Series Modules - Library V1.0.5 -*/ - -#ifndef P1AM_h -#define P1AM_h - -#include "Arduino.h" -#include "SPI.h" -#include "Module_List.h" -#include "defines.h" - -struct channelLabel{ //Used to call functions through names rather than slot/channel numbers. - uint8_t slot; - uint8_t channel; -}; - -class P1AM{ - public: - P1AM(); - - //Init - uint8_t init(); //Initialise modules in the base. Returns the number of slots that have signed on. - void enableBaseController(bool state); //Enable or Disable base controller. Automatically called in init. - uint16_t rollCall(const char* moduleNames[], uint8_t numberOfModules); //Pass in an array of module names to check if current modules in base match. - - //Data IO Functions - For more info see function headers in P1AM.cpp - uint32_t readDiscrete(uint8_t slot, uint8_t channel = 0); //Read Discrete Module. Passing 0 instead of a channel will return data from all of the channels at once. - void writeDiscrete(uint32_t data,uint8_t slot, uint8_t channel = 0); //Write Discrete Module. Passing 0 instead of a channel will write data for all of the channels at once. - int readAnalog(uint8_t slot, uint8_t channel); //Read Analog Module. Returns 32 bits of data. 16/14/12/etc bit modules are not scaled and will return a bit appropriate value. - float readTemperature(uint8_t slot, uint8_t channel); //Read Temperature Module. Returns float. - void writeAnalog(uint32_t data,uint8_t slot, uint8_t channel); //Write Analog Module. Send up to 32 bits of data. 16/14/12/etc bit modules are masked on Base Controller - void readBlockData(char *buf, uint16_t len,uint16_t offset, uint8_t type); //Read raw data buffers. Allows for data updates for large numbers of points. - void writeBlockData(char *buf, uint16_t len,uint16_t offset, uint8_t type); //Write to raw data buffers. Allows for data updates for large numbers of points. - - //PWM Module functions - For more info see function headers in P1AM.cpp - void writePWM(float duty,uint32_t freq,uint8_t slot,uint8_t channel); //Set duty cycle and frequency of a PWM module channel - void writePWMDuty(float duty,uint8_t slot,uint8_t channel); //Set duty cycle of a PWM module channel without changing its frequency - void writePWMFreq(uint32_t freq,uint8_t slot,uint8_t channel); //Set frequency of a PWM module channel without changing its duty cycle - void writePWMDir(bool data,uint8_t slot, uint8_t channel); //Set state of PWM channel when configured for DIR mode - - //Utility Functions - For more info see function headers in P1AM.cpp - uint8_t printModules(); //Print list of all signed on modules to the Serial monitor - uint8_t checkUnderRange(uint8_t slot, uint8_t channel = 0);//Returns 1 if slot channel is under range and supports this function - uint8_t checkOverRange(uint8_t slot, uint8_t channel = 0);//Returns 1 if slot channel is over range and supports this function - uint8_t checkBurnout(uint8_t slot, uint8_t channel = 0);//Returns 1 if slot channel is in burnout and supports this function - uint8_t check24V(uint8_t slot); //Returns 1 if slot is missing 24V and supports this status - char readStatus(int byteNum,int slot); //Return 1 status byte for a module in a slot. See !!!!! THIS DOC REFERENCED !!!!!! for details - void readStatus(char buf[], uint8_t slot); //Return all status bytes for a module in a slot. See !!!!! THIS DOC REFERENCED !!!!!! for details - bool configureModule(char cfgData[],uint8_t slot); //Select slot and pass in buffer that contains configuration data see !!!!! THIS DOC REFERENCED !!!!!! for details - bool configureModule(const char cfgData[], uint8_t slot); - void readModuleConfig(char cfgData[], uint8_t slot); //Select slot and pass in buffer to return configurationd data to. Does not indicate config is live - void configWD(uint16_t milliseconds, uint8_t toggle); //Configure Watchdog Behaviour - void startWD(); //Start Watchdog Functionality - void stopWD(); //Stop Watchdog Functionality - void petWD(); //Pet WD in case you aren't talking to Base Controller often - uint32_t getFwVersion(); //Returns the Base Controller FW version - bool isBaseActive(); //Check if Base Controller is currently configured. If not, call init. - uint8_t checkConnection(uint8_t numberOfModules = 0); //Checks the modules to see if a connection has been lost. Returns first missing module. - bool Base_Controller_FW_UPDATE(unsigned int fwLen); //For FW update of Base Controller - - //Label functions - functionally the same as the above Data IO but use the channelLabel datatype for easier to read code. - uint32_t readDiscrete(channelLabel label); - void writeDiscrete(uint32_t data, channelLabel label); - int readAnalog(channelLabel label); - float readTemperature(channelLabel label); - void writeAnalog(uint32_t data, channelLabel label); - void writePWM(float duty,uint32_t freq, channelLabel label); - void writePWMDuty(float duty, channelLabel label); - void writePWMFreq(uint32_t freq, channelLabel label); - void writePWMDir(bool data, channelLabel label); - uint8_t checkUnderRange(channelLabel label); - uint8_t checkOverRange(channelLabel label); - uint8_t checkBurnout(channelLabel label); - - //Private functions for Base Controller communication. - private: - uint8_t spiSendRecvByte(uint8_t data); - uint32_t spiSendRecvInt(uint32_t data); - void spiSendRecvBuf(uint8_t *buf, int len, bool returnData = 0); - bool spiTimeout(uint32_t uS, uint8_t resendMsg = 0,uint16_t retryPeriod = 0); - char *loadConfigBuf(int moduleID); - bool handleHDR(uint8_t HDR); - void dataSync(); - struct moduleInfo{ - uint8_t dbLoc; //mdb location - }baseSlot[NUMBER_OF_MODULES]; - -}; - -extern P1AM P1; //Default P1AM class instance - -#endif +/* +MIT License + +Copyright (c) 2019 FACTS Engineering, LLC + +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. + + P1AM.h - Library for Communicating with P1000 Series Modules + Library V1.0.6 +*/ + +#ifndef P1AM_h +#define P1AM_h + +#include "Arduino.h" +#include "SPI.h" +#include "Module_List.h" +#include "defines.h" + +struct channelLabel{ //Used to call functions through names rather than slot/channel numbers. + uint8_t slot; + uint8_t channel; +}; + +class P1AM{ + public: + P1AM(); + + //Init + uint8_t init(); //Initialise modules in the base. Returns the number of slots that have signed on. + void enableBaseController(bool state); //Enable or Disable base controller. Automatically called in init. + uint16_t rollCall(const char* moduleNames[], uint8_t numberOfModules); //Pass in an array of module names to check if current modules in base match. + + //Data IO Functions - For more info see function headers in P1AM.cpp + uint32_t readDiscrete(uint8_t slot, uint8_t channel = 0); //Read Discrete Module. Passing 0 instead of a channel will return data from all of the channels at once. + void writeDiscrete(uint32_t data,uint8_t slot, uint8_t channel = 0); //Write Discrete Module. Passing 0 instead of a channel will write data for all of the channels at once. + int readAnalog(uint8_t slot, uint8_t channel); //Read Analog Module. Returns 32 bits of data. 16/14/12/etc bit modules are not scaled and will return a bit appropriate value. + float readTemperature(uint8_t slot, uint8_t channel); //Read Temperature Module. Returns float. + void writeAnalog(uint32_t data,uint8_t slot, uint8_t channel); //Write Analog Module. Send up to 32 bits of data. 16/14/12/etc bit modules are masked on Base Controller + void readBlockData(char *buf, uint16_t len,uint16_t offset, uint8_t type); //Read raw data buffers. Allows for data updates for large numbers of points. + void writeBlockData(char *buf, uint16_t len,uint16_t offset, uint8_t type); //Write to raw data buffers. Allows for data updates for large numbers of points. + + //PWM Module functions - For more info see function headers in P1AM.cpp + void writePWM(float duty,uint32_t freq,uint8_t slot,uint8_t channel); //Set duty cycle and frequency of a PWM module channel + void writePWMDuty(float duty,uint8_t slot,uint8_t channel); //Set duty cycle of a PWM module channel without changing its frequency + void writePWMFreq(uint32_t freq,uint8_t slot,uint8_t channel); //Set frequency of a PWM module channel without changing its duty cycle + void writePWMDir(bool data,uint8_t slot, uint8_t channel); //Set state of PWM channel when configured for DIR mode + + //Utility Functions - For more info see function headers in P1AM.cpp + uint8_t printModules(); //Print list of all signed on modules to the Serial monitor + uint8_t checkUnderRange(uint8_t slot, uint8_t channel = 0);//Returns 1 if slot channel is under range and supports this function + uint8_t checkOverRange(uint8_t slot, uint8_t channel = 0);//Returns 1 if slot channel is over range and supports this function + uint8_t checkBurnout(uint8_t slot, uint8_t channel = 0);//Returns 1 if slot channel is in burnout and supports this function + uint8_t check24V(uint8_t slot); //Returns 1 if slot is missing 24V and supports this status + char readStatus(int byteNum,int slot); //Return 1 status byte for a module in a slot. See !!!!! THIS DOC REFERENCED !!!!!! for details + void readStatus(char buf[], uint8_t slot); //Return all status bytes for a module in a slot. See !!!!! THIS DOC REFERENCED !!!!!! for details + bool configureModule(char cfgData[],uint8_t slot); //Select slot and pass in buffer that contains configuration data see !!!!! THIS DOC REFERENCED !!!!!! for details + bool configureModule(const char cfgData[], uint8_t slot); + void readModuleConfig(char cfgData[], uint8_t slot); //Select slot and pass in buffer to return configurationd data to. Does not indicate config is live + void configWD(uint16_t milliseconds, uint8_t toggle); //Configure Watchdog Behaviour + void startWD(); //Start Watchdog Functionality + void stopWD(); //Stop Watchdog Functionality + void petWD(); //Pet WD in case you aren't talking to Base Controller often + uint32_t getFwVersion(); //Returns the Base Controller FW version + bool isBaseActive(); //Check if Base Controller is currently configured. If not, call init. + uint8_t checkConnection(uint8_t numberOfModules = 0); //Checks the modules to see if a connection has been lost. Returns first missing module. + bool Base_Controller_FW_UPDATE(unsigned int fwLen); //For FW update of Base Controller + + //Label functions - functionally the same as the above Data IO but use the channelLabel datatype for easier to read code. + uint32_t readDiscrete(channelLabel label); + void writeDiscrete(uint32_t data, channelLabel label); + int readAnalog(channelLabel label); + float readTemperature(channelLabel label); + void writeAnalog(uint32_t data, channelLabel label); + void writePWM(float duty,uint32_t freq, channelLabel label); + void writePWMDuty(float duty, channelLabel label); + void writePWMFreq(uint32_t freq, channelLabel label); + void writePWMDir(bool data, channelLabel label); + uint8_t checkUnderRange(channelLabel label); + uint8_t checkOverRange(channelLabel label); + uint8_t checkBurnout(channelLabel label); + + //Private functions for Base Controller communication. + private: + uint8_t spiSendRecvByte(uint8_t data); + uint32_t spiSendRecvInt(uint32_t data); + void spiSendRecvBuf(uint8_t *buf, int len, bool returnData = 0); + bool spiTimeout(uint32_t uS, uint8_t resendMsg = 0,uint16_t retryPeriod = 0); + char *loadConfigBuf(int moduleID); + bool handleHDR(uint8_t HDR); + void dataSync(); + struct moduleInfo{ + uint8_t dbLoc; //mdb location + }baseSlot[NUMBER_OF_MODULES]; + +}; + +extern P1AM P1; //Default P1AM class instance + +#endif