REM ===================================================================== REM SerialPower Network stack, master node part REM Version: 3.0 (same as document version) REM REM "INTELLIGENT" NETWORK-ROAMING MASTER NODE REM REM PICAXE-08M implementation REM REM Provides for implementation of the following activities and processes: REM REM - Network power-up REM - Configuration of (optional) Network Manager Node REM - Roaming and registration of sending processes on slaves REM - Creation of timeslots for registered processes in an endless loop REM - Support for plugNplay nodes with dynamic process ID assignment REM REM --------------------------------------------------------------------- REM REM Implements following processes (can be addressed by slave nodes): REM REM - registerSendingprocess REM - unRegisterSendingprocess REM - flashLEDmasterNode REM REM See code and website for detailed documentation of process parameters REM REM --------------------------------------------------------------------- REM REM Network message frame format used: 4 bytes: REM [IDcalledProcess, IDcallerInfo, dataByteH, dataByteLow] REM REM --------------------------------------------------------------------- REM STATEMENT OF COPYRIGHT REM Copyright (C) 2008, Jurjen Kranenborg REM This work is licensed under the Creative Commons REM Attribution-Noncommercial-Share Alike 3.0 (Swedish port). REM To view a copy of this license, visit REM http://creativecommons.org/licenses/by-nc-sa/3.0/deed.se REM To view a translation of this license in English, visit: REM http://creativecommons.org/licenses/by-nc-sa/3.0 REM or send a letter to REM Creative Commons, REM 171 Second Street, Suite 300, REM San Francisco, California, 94105, USA. REM In short form (me, my = holder of copyright): REM * You may use, adapt, and distribute this work. REM * You must give credit to me when using this code in your application. REM * If you use my code commercially, I want my share. REM * You may share your adapted code (now with you as holder of copyright) REM on the condition that the same license conditions are applied. REM In order to acknowledge Jurjen Kranenborg (i.e. not the copyright holder of any derived work) REM as the architect of the original network concepts, REM please include the following statement in all derived works (code, webpages, docs, etc.): REM The network architecture of this work was originally developed by Jurjen Kranenborg. REM ===================================================================== #Picaxe 08m REM input & output communications pin REM Note that the interrupt configuration must be changed if the netSerialIn input is changed REM Note that the internal pullup configuration (start of main program) must be changed if the frequency switch input is changed SYMBOL netSerialIN = 3 SYMBOL netSerialOUT = 4 SYMBOL indicatorLEDoutput = 1 SYMBOL frequencySwitch = input2 REM Serial data registers: SYMBOL commandIndex = b9 SYMBOL IDcalledProcess = b10 SYMBOL IDcallerInfo = b11 SYMBOL dataByteH = b12 SYMBOL dataByteL = b13 SYMBOL noProcessIndex = $FF REM Boolean constants definition: SYMBOL FALSE = 0 SYMBOL TRUE = 1 REM Definition of RAM table (and pointers) to store IDs of sending processes in network: SYMBOL RAMtablePointer = $50 SYMBOL RAMtableLimit = $7F SYMBOL RAMtableIndex = b8 SYMBOL currentSendingProcessPointer = b7 SYMBOL RAMsearchedIDpointer = b3 SYMBOL RAMnextElementPointer = b2 REM time intervals for message frame synchronization REM - in milliseconds REM - assuming 2400 baud data rate: REM - 1 bit takes 1/2400 = 0.42 ms REM - 1 byte takes 8/2400 = 1/300 sec = 3.3 ms = approx. 4ms SYMBOL t_startUp = 1000 SYMBOL t_startUp_masterNode = 2*t_startUp SYMBOL t_byte = 4 SYMBOL t_INTmaster = 2*t_byte SYMBOL t_INTslave = t_INTmaster SYMBOL t_dataFrame = 14 SYMBOL t_respondDelay = 2*t_byte SYMBOL t_testTimeSlot = 2*t_byte SYMBOL t_calc1 = t_respondDelay + t_INTslave SYMBOL t_slaveTimeSlot = t_calc1 + t_dataFrame SYMBOL t_masterTimeSlot = t_INTmaster + t_dataFrame SYMBOL t_comms = t_masterTimeSlot + t_slaveTimeSlot REM interrupt definitions SYMBOL INTinputs = %00000000 SYMBOL INTmask = %00001000 REM Network Status definitions SYMBOL NOT_READY = %00000000 SYMBOL READY_FREQ_4MHZ = %00000001 SYMBOL READY_FREQ_8MHZ = %00000011 REM Network stack related processes: SYMBOL IDallProcesses = 255 SYMBOL IDnoProcess = 0 SYMBOL IDroamSendingProcess = 1 'Slave process SYMBOL IDregisterSendingProcess = 2 SYMBOL IDunRegisterSendingProcess = 3 SYMBOL IDavailableTimeSlot = 4 'Slave process SYMBOL IDroamPlugNplayNode = 5 'Slave process (plugNplay node) SYMBOL IDnetworkManager = 6 'Slave process (special external Network Manager Node; not currently implemented) SYMBOL IDflashLEDmasterNode = 7 SYMBOL IDnetworkConfigProcess = 8 'Allows to configure slaves at start-up (clock speed etc.) SYMBOL IDspecialProcess9 = 9 'Reserved ID for special system purposes (do not use for user processes) REM Data on allowed user-defined process IDs REM (used for roaming sending slave processes) REM Note that ID=255 is reserved (IDnoprocess) and should NOT be used as a user process! SYMBOL firstUserProcessID = 10 SYMBOL lastUserProcessID = 254 SYMBOL IDcurrentProcess = b6 SYMBOL IDdestinationProcess = b5 SYMBOL maxCurrentID = b4 SYMBOL LEDlightPeriod = w5 REM *********************** REM User programmable area: REM *********************** REM - - - - - - - - - - - - REM plugNlay usage, define here whether plugNplay option is used (if so, following line should be de-commented out) REM ++++++++++++++++++ REM #define plugNplay REM ++++++++++++++++++ REM - - - - - - - - - - - - REM Guarantee a certain time with active power provision between timeslots REM Note: shorter period implies higher network bandwitdh but also more interruptions of slaves! REM Do not take a shorter period than t_comms REM ++++++++++++++++++++++++++++++++++++ SYMBOL t_pauseBetweenTimeSlots = t_comms REM ++++++++++++++++++++++++++++++++++++ REM ****************************** REM END of User programmable area: REM ****************************** REM =============================== REM START of Main Program execution REM =============================== Initialize: REM Enable internal pullup resistor for new frequency switch input (switch to ground: 4 -> 8 MHz execution speed on all nodes) REM The pullup is enabled to allow backward hardware compatibility with Version 2.0 and earlier hardware designs REM that do not have this switch connected and the input floating. The network operation speed then defaults to 4 MHz. REM If the Plug&Play option is enabled there is not enough code space left for the internal pullup enabling code: an external REM pullup (100 kOhm) IS then necessary to allow frequency selection: it should not be floating. REM Instead of a hardware switch the frequency can also be defined solely in software. In that case remove the following REM code part and set the frequency in subroutine configureNetwork appropriately #ifndef plugNplay then SYMBOL WPU = $95 SYMBOL Option_REG = $81 POKE WPU, %00000100 REM define pullup for appropriate frequency switch input POKE Option_REG, %01111111 REM activate the pullup settings by clearing GPPU bit (active at zero) #endif REM Current maximum ID in use is initialized here (gets updated automatically during process registration) LET maxCurrentID = IDspecialProcess9 REM Network Power-up: Power-up all slaves for a while REM Power-up the network firmly by enabling the MOSfet push-pull driver to deliver full power for a while REM Note that this is particularly necessary for slaves with an 18X (which does not have its brown-out bit set) ... HIGH netSerialOUT REM Full power for a while (twice as long as the power-up time of the slave nodes) PAUSE t_startUp_masterNode REM Master node sends configuration parameters to all slave nodes REM This allows network configuration without having to reprogram all slave nodes REM Currently only Clock Speed change to 8 MHz is defined by master node, using the frequency switch (if present) GOSUB configureNetwork REM Search all slaves for sending process IDs, and store all IDs thus collected in RAM memory (RAMtable) REM The indicator LED is lit constantly to indicate that the master node is busy roaming HIGH indicatorLEDoutput GOSUB roamForSendingProcesses LOW indicatorLEDoutput REM - - - - - - - - - - - - - - - - REM Endless loop to provide network timeslots to slaves REM REM NOTE: The master also listens to slave process responses via an interrupt, REM allowing it to implement user functionality itself as well! REM - - - - - - - - - - - - - - - - loopCreateTimeSlots: DO REM Endless loop for continuous timeslot & power provision REM REM Create timeslots for all processes on the network that potentially can send. REM All registered IDs are already available in RAMtable (due to roamForSendingProcesses) with: REM - RAMtablePointer as pointer to first registered ID REM - RAMtableIndex as pointer to last registered ID LET currentSendingProcessPointer = RAMtablePointer DO WHILE currentSendingProcessPointer <= RAMtableIndex REM Fetch the process ID from RAMtable PEEK currentSendingProcessPointer, IDcurrentProcess REM Guarantee a certain period with active power provision between timeslots PAUSE t_pauseBetweenTimeSlots REM Now create a timeslot for process with identifier IDcurrentProcess LET IDdestinationProcess = IDavailableTimeSlot LET dataByteH = maxCurrentID LET dataByteL = currentSendingProcessPointer GOSUB createSingleTimeSlot INC currentSendingProcessPointer LOOP REM Insert a plugNplay timeslot if this has been specified in user programmable area (default off) REM REM This command also informs the node on the largest ID currently registered (in dataByteH), allowing REM it to add extra processes with new IDs without risk of conflict with existing processes. REM Consequently, the plugNplay node need not know the currently registered IDs if it does not intend to use them. #IFDEF plugNplay IDdestinationProcess = IDroamPlugNplayNode dataByteH = maxCurrentID GOSUB createSingleTimeSlot #ENDIF LOOP REM =================== REM Program subroutines REM =================== configureNetwork: REM Configure all nodes in the network by sending a configuration byte REM Currently only the node clock frequency can be changed REM After this message is sent by the master, the master node itself switches to the chosen frequency as well LET IDdestinationProcess = IDnetworkConfigProcess IF frequencySwitch = 0 THEN LET dataByteL = READY_FREQ_8MHZ ELSE LET dataByteL = READY_FREQ_4MHZ ENDIF GOSUB createSingleTimeSlot REM If 8MHz execution speed has been selected, then enable it now for the master node as well IF frequencySwitch = 0 THEN SETFREQ m8 ENDIF RETURN REM ------------------- roamForSendingProcesses: REM This subroutine roams the network for potentially sending process IDs REM and stores them in the RAM table. This process is called after network power-up. REM The network slaves are roamed for sending processes by cycling through all possible process IDs. REM A special slave process is used for this: roamSendingProcess REM A slave that implements the particular sending process will then respond directly REM by addressing the master's registerSendingProcess (see Interrupt routine) and providing REM its ID and additional parameters. See subroutine registerSendingProcess for the REM description of these parameters. REM Note that we use RAMtableIndex in the interrupt routine REM as the index to the lastly added entry in the RAMtable; REM it is incremented there if there has been a registered process REM Start with empty table LET RAMtableIndex = RAMtablePointer - 1 REM Indicate to slaves that they need to provide IDs of their sending processes. LET IDdestinationProcess = IDroamSendingProcess REM Include information on network configuration, in particular the required REM slave node frequency that will be set after roaming is finished LET dataByteH = maxCurrentID REM Cycle through all possible IDs and allow any sending processes at slave nodes to respond FOR IDcurrentProcess = firstUserProcessID TO lastUserProcessID PAUSE t_comms LET dataByteH = maxCurrentID GOSUB createSingleTimeSlot NEXT IDcurrentProcess RETURN REM ------------------- createSingleTimeslot: REM send out the command informing slave processes that a timeslot is created REM first bring down the line a while to force all slaves to listen: LOW netSerialOUT REM take a while for slaves to get into receiving mode PAUSE t_INTmaster REM ... and send the timeslot command out for the IDcurrentProcess process on a slave! SEROUT netSerialOUT, N2400, (IDdestinationProcess, IDcurrentProcess, dataByteH, dataByteL) REM Bring the line high again for a short while (but a little bit shorter than t_responddelay) HIGH netSerialOut PAUSE 1 REM Now the slave timeslot needs to be created by having a pull-up instead of active push-pull INPUT netSerialOut REM Enable interrupts during timeslots to allow for master listening and action as well SETINT INTinputs, INTmask REM Now sit and wait (unless interrupted) for a while to implement the timeslot PAUSE t_slaveTimeSlot REM Timeslot is over ... disable interrupts and reinforce powermode SETINT INTinputs, 0 HIGH netSerialOut RETURN REM ==================================================== REM Interrupt Routine: REM Handles Network message frame reception and handling REM ==================================================== interrupt: REM Line has been brought low by some slave process, REM read the command frame (4 bytes), then restore power supply to slaves REM and determine if the master needs to take action on the message LET commandIndex = noProcessIndex SERIN netSerialIN, N2400, IDcalledProcess, IDcallerInfo, dataByteH, dataByteL REM data frame from slave received, so master node interrupts can be disabled now ... SETINT INTinputs, 0 REM .. and active power provision can start again HIGH netSerialOut REM Flash the LED very shortly to indicate bus activity by slaves PULSOUT indicatorLEDoutput, 100 REM Decode process ID LOOKDOWN IDcalledProcess,(IDregisterSendingProcess,IDunRegisterSendingProcess,IDflashLEDmasterNode), commandIndex REM Jump to proper command handling routine (if available), and then REM return to normal execution BRANCH commandIndex, (registerSendingProcess, unRegisterSendingProcess, flashLEDmasterNode) REM if command is not recognised then return to main program (timeslot provision) directly RETURN REM ================================== REM Master Node processes follow below REM REM Implemented processes: REM - registerSendingprocess REM - unRegisterSendingprocess REM - flashLEDmasterNode REM REM ================================== registerSendingprocess: REM Module to register a sending process as indicated by a slave node. REM The ID of the process gets stored in RAM memory in RAMtable. REM REM The information in the received message frame is interpreted as follows: REM - IDcalledProcess is the ID of this registering process. REM - IDcallerInfo contains the ID of the process that requested this activity. REM - dataByteH indicates the largest ID (sendng & nonsending) of all IDs implemented at the node that sent REM the current message frame REM - dataByteL contains the ID of the process to be registered as a sending process REM that requests for timeslots REM Note that RAMtableIndex is adjusted to correctly indicate the end REM of the list of registered processes. REM REM This process can be used by slave nodes to register their sending processes REM at network start-up as well as later on ,for example for forking processes because REM some condition has been reached. In this way timeslots need only be reserved REM when needed. REM First check if this process has already been registered REM (multiple registration of same ID is not allowed): GOSUB searchIDinRAMtable IF RAMsearchedIDpointer > RAMtableIndex THEN REM OK, the ID has not been registered before, so it may be added. REM Store the ID in the RAMtable (if space is available) IF RAMtableIndex < RAMtableLimit THEN REM First increment the pointer to point at the new location INC RAMtableIndex REM write the ID to the RAMtable POKE RAMtableIndex, dataByteL REM Determine new maximum of all known IDs in the network IF dataByteH > maxCurrentID THEN maxCurrentID = dataByteH ENDIF ENDIF ENDIF RETURN REM ------------------- unRegisterSendingprocess: REM A process ID has to be removed from RAMtable. This can be done in two ways: REM - Through the indication of the process ID (as contained in dataByteL, databyte <> IDnoProcess) REM - Through the position of the RAMtable entry (through dataByteH when dataByteL contains IDnoProcess) REM The latter option is used by the optinal Network Manager node. REM This means: REM - look up the particular ID in the RAMtable (not needed for Network Manager) REM - remove it by "copying down" all IDs after this location REM Thus the size of the RAMtable shrinks; new elements can be added REM at the end (no "holes" within the table are allowed) REM REM The information in the received message frame is interpreted as follows: REM - IDcalledProcess is the ID of this unregistering process. REM - IDcallerInfo contains the ID of the calling process that requested the registery. REM - dataByteH (slave nodes): indicates the largest ID of all IDs implemented at the node REM that sent the current message frame. REM - dataByteH (IDcallerinfo = Network Manager): contains new (conservative) estimate REM of maximum ID active in the network (both sending and listening-only) REM - dataByteL contains the ID of the process to be removed as a sending process REM that requests for timeslots. IF IDcallerInfo <> IDnetworkManager THEN REM The ID has to be searched in the RAMtable GOSUB searchIDinRAMtable REM If ID is found then remove it, else return without action IF RAMsearchedIDpointer <= RAMtableIndex THEN GOSUB shiftDownRAMtableElements ELSE REM The position of the ID to be removed is known by the Network Manager REM and it has recalculated (conservatively re-estimated) maxCurrentID as well LET RAMsearchedIDpointer = dataByteL GOSUB shiftDownRAMtableElements maxCurrentID = dataByteH ENDIF RETURN REM ------------------- shiftDownRAMtableElements: REM Helper routine to unregistering of sending process REM Overwrite the found location by shifting down all remaining table elements one step, REM thus reducing the size of RAMtable DO WHILE RAMsearchedIDpointer < RAMtableIndex LET RAMnextElementPointer = RAMsearchedIDpointer + 1 PEEK RAMnextElementPointer, IDcurrentProcess POKE RAMsearchedIDpointer, IDcurrentProcess INC RAMsearchedIDpointer LOOP DEC RAMtableIndex RETURN REM ------------------- searchIDinRAMtable: REM Helper routine to registering/unregistering of sending process REM The ID in dataByteL is searched in the RAMtable REM If found then: REM - RAMsearchedIDpointer <= RAMtableIndex REM - RAMsearchedIDpointer points to the particular ID REM If not found then the condition RAMsearchedIDpointer > RAMtableIndex holds LET RAMsearchedIDpointer = RAMtablePointer DO WHILE RAMsearchedIDpointer <= RAMtableIndex REM Fetch a registered ID from the RAMtable into IDcurrentProcess PEEK RAMsearchedIDpointer, IDcurrentProcess REM If the fetched ID is the same as the requested one then exit this loop REM because it should not be registered again. REM Then RAMsearchedIDpointer will always be <= RAMtableIndex REM which is used as a test condition directly after the execution of this subroutine IF IDcurrentProcess = dataByteL THEN EXIT INC RAMsearchedIDpointer LOOP RETURN REM ------------------- flashLEDmasterNode: REM Master process, can be used by slaves to indicate network activity REM Parameter: REM - databyteL: contains the flash duration (in 2ms units) LET LEDlightPeriod = 200*dataByteL PULSOUT indicatorLEDoutput, LEDlightPeriod RETURN REM ================================= REM END of MASTER NODE implementation REM =================================