; SMILENGHEADER ; COMMENT This is the Smile NG file header, do not modify! ; VERSION 1.0 ; PROC CALM \title;Slave communication \b;include file for Petra ; uses 2 levels of call stack, therefore usable on 12C5xx type PICs. .ins Timing.asi ; slave timing constraints \const;program counters| ; Some macros build up different parts of the program code at the same time; for example, the Receive macro extends the communication jump table, ; which lies in code page 0, and then adds a small procedure in page 1. ; There are two different counters for the program code: ; apcp0: All the jump tables are designed to lie between addresses 16'000 and 16'0FF, so they can be used with PCLATH=0. ; in addition, the 12C508 type PICs can only do CALLs to within the first 256 bytes of each program memory page. Therefore, ; all subroutines are placed in memory area 16'000..16'0FF too. ; apcp1: Counter for main program and user tasks. apcvar = 0 ; variables apcp0 = 1 ; code page 0 jump tables and subroutines (16'000..16'0FF) apcp1 = 2 ; code page 1 program code and user tasks .APC apcvar ; initialize APCs .Loc MinVar .APC apcp0 .Loc 16'000 .APC apcp1 .Loc StartPage1 .Macro StartCode00_FF ; put the following code in page 0 (tables and subroutines) .APC apcp0 .EndMacro .Macro EndCode00_FF ; switch back to page 1 (main program) .APC apcp1 .EndMacro \var;system| .APC apcvar ErrorCode: ; error code during initialization ComTmp: .16 1 ; shift register for sending/receiving data (and for other purposes) ComStat: .16 1 ; communication state; indicates which bit in communication sequence is being worked on Task: .16 1 ; user task counter flags: .16 1 ; some state flags (bits 0..6 are free for user program) MyTimeSlot: .16 1 ; indicates how many bits must be ignored before starting to communicate MyID: .16 1 ; module ID, for identification during initialization and assignment of a time slot \const;Bits in flags variable| ; Only bit 7 of the flags variable is used - bits 6..0 are free for the user program ResetReceived = 7 ; just received a reset pulse (for detecting two of them in a row, which signals master reset) \macro;Variables| .Macro StartVariables ; begin declaration of user variables .APC apcvar .EndMacro .Macro EndVariables ; end declaration of user variables .EndMacro \prog;reset vector| .APC apcp0 ; address 16'000 = reset vector Jump Init ; go to initialization in page 1 .Loc MaxVar+1 ; workaround for CALM bug \prog;subroutines| .APC apcp0 ; com.asi contains the Delay subroutine .ins com.asi ; include some utilities shared by master and slave communication codes \prog;initialize communication| .APC apcp1 ; program InitCommunication: ; First wait for two consecutive reset pulses. The master never emits two reset pulses consecutively during normal operation. ICa: Call InitReceiveBit ; wait for first pulse Xor #16'FF,W ; was it a short one? Skip,ZS ; no: keep waiting Jump ICa Call InitReceiveBit ; wait for second pulse Xor #16'FF,W ; was it a short one? Skip,ZS ; no: keep waiting Jump ICa ; Two consecutive reset pulses have been received; it is possible that the first one has been received before the master reset (i.e., a MISO low ; bit or a sequence reset pulse just before master reset). Therefore, be careful in what follows: Clr MyTimeSlot ; this variable will tell when this slave communicates in the sequence ICb: Call InitReceiveByte ; receive next module ID from master Xor #16'FF,W ; initialization terminated? Move #ec_NotUsed,W Skip,ZC Jump Error_Init_B ; yes: initialization error ec_NotUsed Move MyID,W ; no: check if the master is initializing this slave Sub ComTmp,W ; i.e. compare the received byte to this slave's ID Skip,ZS Jump ICe ; not me: wait for next ID in communication initialization sequence ; The ID corresponding to this slave has been received. Acknowledge, then read the header byte and the configuration bytes: Call SendLoBit ; acknowledge reception of module ID Call InitReceiveByte ; receive header byte Xor #16'FF,W ; initialization terminated? Move #ec_EOS,W Skip,ZC Jump Error_Init_B ; yes: initialization error ec_EOS Move ComTmp,W ; received header byte Xor #(16*NumberCommunicationBytes)+NumberConfigBytes,W ; compare to the expected header byte Move #ec_Header,W Skip,ZS Jump Error_Init_A ; not equal: initialization error ec_Header .If NumberConfigBytes .NE. 0 ; if some configuration bytes have to be received: Move #NumberConfigBytes,W Move W,ComStat ; initialize counter Move #ConfigVariables,W ; get start address of the variables block where to store the received bytes Move W,FSR ; indirect addressing ICc: Call InitReceiveByte ; receive next configuration byte Xor #16'FF,W ; initialization terminated? Move #ec_EOS,W Skip,ZC Jump Error_Init_B ; yes: initialization error ec_EOS Move ComTmp,W ; get the received byte Move W,0 ; store (indirect addressing) in configuration variables block Inc FSR ; next time store in next variable DecSkip,EQ ComStat ; done receiving configuration bytes? Jump ICc ; no: receive next one .EndIf ; now ignore initialization of the following modules - just wait for the two reset pulses indicating the end of the initialization: ICd: Call InitReceiveBit ; wait for first pulse Xor #16'FF,W ; was it a short one? Skip,ZS ; no: keep waiting Jump ICd Call InitReceiveBit ; wait for second pulse Xor #16'FF,W ; was it a short one? Skip,ZS ; no: keep waiting Jump ICd Call SendHiBit ; signal correct initialization to the master Call InitReceiveBit ; receive the flag indicating success or failure of network configuration Xor #16'00,W ; received a 0 bit? Skip,ZC Jump StartSynchronous ; yes: configuration ok, start the synchronous cycle and communication Move #ec_MasterAbort,W Jump Error_Init_C ; no: initialization error ec_MasterAbort ; some other slave module is being initialized: skip header and configuration bytes that are not meant for this slave ICe: Call SendHiBit ; do not do anything since not talked to Call InitReceiveByte ; receive header Xor #16'FF,W ; initialization terminated? Move #ec_EOS,W Skip,ZC Jump Error_Init_B ; yes: initialization error ec_EOS ; add 10x number of communication bytes to MyTimeSlot variable ; i.e., during communication, skip 10 communication cycles for each byte exchanged between the master and the other slave that ; communicates before this one. RRC ComTmp,W And #2'01111000,W ; extract 8x number of communication bytes Add W,MyTimeSlot ; add Swap ComTmp RLC ComTmp,W And #2'00011110,W ; extract 2x number of communication bytes Add W,MyTimeSlot ; add ; skip reception of the configuration bytes for the other slave module Swap ComTmp,W And #2'00001111,W ; extract number of configuration bytes Skip,ZC ; none? Jump ICb ; yes: done skipping, wait for next module ID Move W,ComStat ; initialize counter ICf: Call InitReceiveByte ; receive next configuration byte and discard it Xor #16'FF,W ; initialization terminated? Move #ec_EOS,W Skip,ZC Jump Error_Init_B ; yes: initialization error ec_EOS DecSkip,EQ ComStat ; done skipping configuration bytes? Jump ICf ; no: skip next one Jump ICb ; yes: done skipping, wait for next module ID \prog;error handling for communication initialization| ; error codes for Error_Initialization macro: ec_NotUsed = 1 ; module is not used in present network configuration ec_Header = 2 ; wrong header received ec_EOS = 3 ; unexpected end of initialization sequence ec_MasterAbort = 4 ; master aborted initialization .APC apcp1 ; program Error_Init_A: ; first kind of errors: erroneous data received (ec_Header) ; slave has to wait for end of communication sequence first: Move W,ErrorCode ; store error code Erra: Call InitReceiveBit ; wait for first pulse Xor #16'FF,W ; was it a short one? Skip,ZS ; no: keep waiting Jump Erra Call InitReceiveBit ; wait for second pulse Xor #16'FF,W ; was it a short one? Skip,ZS ; no: keep waiting Jump Erra Jump Errb Error_Init_B: ; second kind of errors: unexpected end of communication initialization sequence (ec_NotUsed, ec_EOS) Move W,ErrorCode ; store error code Errb: Call SendLoBit ; signal failed initialization to the master Call InitReceiveBit ; receive master error flag (ignore) Jump Errc Error_Init_C: ; third kind of errors: master aborted initialization (ec_MasterAbort) Move W,ErrorCode ; store error code Errc: Error_Initialization ; execute user macro Jump Init ; wait for next initialization (try again) \prog;subroutines for communication initialization| .APC apcp0 ; subroutines \b;synchronize with master i.e. learn which are sync edges ; wait for a falling edge, then wait long enough (28 us) to make sure that the next falling edge will be a sync edge ; this avoids taking a MISO falling edge for a sync edge SendHiBit: ; sending a HI bit boils down to doing exactly this Synchronize: ; a call to Synchronize uses 2 levels of call stack TestSkip,BS ComPort:#ComPin ; initially, the line must be HI Jump Synchronize Syna: TestSkip,BC ComPort:#ComPin ; wait for falling edge 0 Jump Syna ; 1 Delay SComLength-3 ; wait until communication cycle is definitely over 2..SComLength-2 RetMove #0,W ; done SComLength-1..SComLength \b;wait for next pulse ; return value = 16'FF: reset pulse ; return value = 16'01: 1 bit ; return value = 16'00: 0 bit InitReceiveBit: ; a call to InitReceiveBit uses 2 levels of call stack TestSkip,BS ComPort:#ComPin ; initially the line must be HI Jump InitReceiveBit IRBa: TestSkip,BC ComPort:#ComPin ; wait for falling edge 0..1 Jump IRBa Delay ResetSample-2 ; variable timing 2..ResetSample-1 TestSkip,BC ComPort:#ComPin ; reset pulse? ResetSample RetMove #16'FF,W ; yes: return 16'FF ResetSample+1..ResetSample+2 Delay MOSI_sample-ResetSample-2 ; variable timing ResetSample+2..MOSI_sample-1 TestSkip,BC ComPort:#ComPin ; 1 bit? MOSI_sample RetMove #16'01,W ; yes: return 16'01 MOSI_sample+1..MOSI_sample+2 RetMove #16'00,W ; no: return 16'00 MOSI_sample+2..MOSI_sample+3 \b;receive one byte from the master (during communication initialization) ; return value = 16'FF: two consecutive reset cycles received --> end of initialization sequence ; return value = 16'00: byte received ok, stored in ComTmp variable InitReceiveByte: ; a call to InitReceiveByte uses 2 levels of call stack Move #2'10000000,W ; prepare shift register - done when the HI bit drops out of LSB Move W,ComTmp Clr flags:#ResetReceived ; no reset pulse received yet IRBb: TestSkip,BS ComPort:#ComPin ; initially the line must be HI Jump IRBb IRBc: TestSkip,BC ComPort:#ComPin ; wait for falling edge 0..1 Jump IRBc Delay ResetSample-2 ; variable timing 2..ResetSample-1 TestSkip,BC ComPort:#ComPin ; reset pulse? ResetSample Jump IRBd ; yes: check for end of initialization ResetSample+1..ResetSample+2 Delay MOSI_sample-ResetSample-3 ; variable timing ResetSample+2..MOSI_sample-2 ClrC ; clear carry MOSI_sample-1 TestSkip,BC ComPort:#ComPin ; 1 bit? MOSI_sample SetC ; yes: set carry MOSI_sample+1 RRC ComTmp ; roll prepared carry into MSB; LSB drops out into carry MOSI_sample+2 Delay SComLength-MOSI_sample-5 ; variable timing MOSI_sample+3..SComLength-3 Skip,CS ; was this the last bit? (after 8x RRC, the HI bit drops out) SComLength-2 Jump IRBb ; no: receive next bit SComLength-1..SComLength RetMove #16'00,W ; yes: return 16'00 signaling reception of new config byte IRBd: TestSkip,BC flags:#ResetReceived ; is this the second reset pulse in a row? RetMove #16'FF,W ; yes: return 16'FF signaling end of initialization sequence Set flags:#ResetReceived ; no: set flag for next time Jump IRBb ; receive next bit \b;send a LO bit to the master (during communication initialization) ; SendHiBit is equal to Synchronize SendLoBit: ; a call to SendLoBit uses 2 levels of call stack TestSkip,BC ComPort:#ComPin ; wait for sync edge 0..1 Jump SendLoBit Delay MISO_on-3 ; variable timing 2..MISO_on-2 PinOut ; MISO_on-1..MISO_on Delay SComLength-MISO_on-2 ; variable timing MISO_on+1..SComLength-2 PinIn ; SComLength-1..SComLength RetMove #0,W ; SComLength+1..SComLength+2 \prog;synchronous cycle| .APC apcp1 ; program ; jump here if the communication has been interrupted for an important, long task; the slave has to be resynchronized to the ; communication sequence first. RestartSynchronous: Call Synchronize ; synchronize with master RSa: Call InitReceiveBit ; wait for a pulse Xor #16'FF,W ; was it a short one? i.e. is a new communication sequence about to begin? Skip,ZS ; no: keep waiting Jump RSa ; now a communication sequence is about to start. StartSynchronous: ; after initialization, start the synchronous cycle Clr ComStat ; start new communication sequence (start with first bit) Clr flags:#ResetReceived ; no reset pulse received for the moment Move MyTimeSlot,W ; number of bits to ignore (bits exchanged between master and other slaves) Move W,ComTmp Skip,ZC ; is this the first slave in the sequence? Inc ComStat ; yes: do not ignore anything, start communicating immediately ; synchronization with master clock: wait for sync edge after every task termination TaskDone: ; point of entry after user task termination WaitForSync: ; timings with respect to the detection of the sync edge: TestSkip,BC ComPort:#ComPin ; wait for falling edge (sync edge) 0..1 SComLength+9..SComLength+10 Jump WaitForSync ; keep waiting ; jitter is 3 cycles (3 us) because the ComPin is ; sampled only every 3 cycles. \b;check for reset pulse ; a reset pulse now resets the communication and starts a new sequence Delay ResetSample-4 ; variable timing 2..ResetSample-3 Move ComStat,W ; preparation for communication jump table ResetSample-2 Inc ComStat ; preparation for communication jump table ResetSample-1 TestSkip,BS ComPort:#ComPin ; reset pulse? ResetSample Jump CommunicationJumpTable ; no: do communication ResetSample+1..ResetSample+2 \b;reset communication, start new sequence ; a reset pulse has been detected TestSkip,BC flags:#ResetReceived ; is this the second reset pulse in a row? ResetSample+2..ResetSample+3 Jump ReInitCommunication ; yes: the master has been reset --> redo initialization Set flags:#ResetReceived ; so this was the first reset pulse ResetSample+4 Clr ComStat ; reset communication, restart at first bit ResetSample+5 Move MyTimeSlot,W ; number of bits to ignore (bits exchanged between master and other slaves) ResetSample+6 Move W,ComTmp ; ResetSample+7 Skip,ZC ; is this the first slave in the sequence? ResetSample+8 Inc ComStat ; yes: do not ignore anything, start communicating immediately ResetSample+9 \b;do task upon communication sequence completion ; The EndSeqTask is executed each time the communication sequence has been completed, i.e. each time that all the I/O variables have been ; exchanged with all the slaves. ; All communication procedures (send bit, receive bit, load byte, store byte, ...) have exactly the same length; if the EndSeqTask macro ; is (SComLength-ResetSample-11) cycles long, then it has the same length too, and it is guaranteed that the SyncTask macro and the user tasks are executed at a well defined ; time after the master has started its cycle of T microseconds. EndSeqTask ; SComLength-ResetSample-11 cycles ResetSample+10..SComLength-2 Jump TaskJumpTable ; now do user tasks SComLength-1..SComLength \prog;communication subroutines| .APC apcp1 ; program \b;receive a single bit from the master during normal communication ReceiveBit: Delay MOSI_sample-ResetSample-8 ; variable timing ResetSample+7..MOSI_sample-2 ClrC ; clear carry MOSI_sample-1 TestSkip,BC ComPort:#ComPin ; receive HI bit? MOSI_sample SetC ; yes: set carrybit MOSI_sample+1 RRC ComTmp ; roll prepared carry into MSB of shift register MOSI_sample+2 Delay SComLength-MOSI_sample-7 ; variable timing MOSI_sample+3..SComLength-5 Jump EndMostComm ; go to user tasks SComLength-4..SComLength-3 \b;transmit a single bit to the master during normal communication TransmitBit: Delay MISO_on-ResetSample-9 ; variable timing ResetSample+7..MISO_on-3 Move #TrisOut,W ; MISO_on-2 TestSkip,BS ComTmp:#0 ; send LO bit? MISO_on-1 WriteTris ; yes: pull line down MISO_on RRC ComTmp ; next time send next bit MISO_on+1 Delay SComLength-MISO_on-6 ; variable timing MISO_on+2..SComLength-5 Jump EndMostComm ; go to user tasks SComLength-4..SComLength-3 \b;wait for my turn in communication sequence WaitForMyTurn: DecSkip,EQ ComTmp ; decrement the number of bits to skip before communicating ResetSample+7 Clr ComStat ; if not zero yet, then keep ComStat at zero --> keep decrementing ComTmp ResetSample+8 Jump EndWaitForMyTurn ; go to user tasks ResetSample+9..ResetSample+10 \b;acknowledge byte transmission ; after transmitting a byte, send a single LO bit to tell the master that the byte it just received is valid. This helps preventing errors - if a slave quits the synchronous cycle, then ; the master stops refreshing its variables instead of filling them with 16'FF. AcknowledgeSend: Delay MISO_on-ResetSample-8 ; variable timing ResetSample+7..MISO_on-2 PinOut ; send LO bit MISO_on-1..MISO_on Delay SComLength-MISO_on-5 ; variable timing MISO_on+1..SComLength-5 Jump EndMostComm ; go to user tasks SComLength-4..SComLength-3 \b;make all communication procedures equally long ; This makes sure that the SyncTask macro is executed very regularly. NothingBit: Delay 1 ; ResetSample+7 EndEndCommunication: Delay 3 ; ResetSample+8..ResetSample+10 EndWaitForMyTurn: EndReceiveProc: EndTransmitProc: Delay SComLength-ResetSample-15 ; variable timing ResetSample+11..SComLength-5 Jump EndMostComm ; now do user tasks SComLength-4..SComLength-3 \macro;Initialization| .Macro StartInitialize ; begin initialization code .APC apcp1 ; program Init: .If cpu .EQ. 508 Move W,16'05 ; calibrate internal RC oscillator .EndIf InitPorts ; initialize ports (data and direction) InitializeModuleID ; get module ID (from constant or EEPROM or whatever) ReInitCommunication: ; point of entry after a master reset has been detected during normal operation .EndMacro ; here the user initialization code is inserted ; if the master is reset and the slave detects two consecutive reset pulses, then it once more ; executes the user initialization code before starting to receive the communication initialization ; sequence. Therefore, the user initialization code must be quite short. .Macro EndInitialize ; end user initialization code Clr Task ; start task list with first task Jump InitCommunication ; now initialize the communication .EndMacro \macro;Communication| ; ComStat contains the index to the bit in the communication sequence. As long as ComStat=0, ComTmp counts down the bits that have to be ignored ; while the master communicates with other slave modules. .Macro StartCommunication ; begin activity list for communication .APC apcp0 ; jump table CommunicationJumpTable: Add W,PCL ; jump table: ResetSample+3..ResetSample+4 Jump WaitForMyTurn ; wait until master wants to communicate with this slave ResetSample+5..ResetSample+6 .EndMacro .Macro EndCommunication ; end activity list for communication .APC apcp0 ; jump table Dec ComStat ; stay here until a reset pulse initiates a new communication sequence ResetSample+5 Jump EndEndCommunication ; go to user tasks ResetSample+6..ResetSample+7 .EndMacro \b;receive a byte from the master during normal operation .Macro Receive .LocalMacro ReceiveProc ; parameter 1: name of variable where to store the received byte .APC apcp0 ; jump table Jump NothingBit ; time for master to prepare byte ResetSample+5..ResetSample+6 Jump ReceiveBit ; receive bit 0 ResetSample+5..ResetSample+6 Jump ReceiveBit ; receive bit 1 ResetSample+5..ResetSample+6 Jump ReceiveBit ; receive bit 2 ResetSample+5..ResetSample+6 Jump ReceiveBit ; receive bit 3 ResetSample+5..ResetSample+6 Jump ReceiveBit ; receive bit 4 ResetSample+5..ResetSample+6 Jump ReceiveBit ; receive bit 5 ResetSample+5..ResetSample+6 Jump ReceiveBit ; receive bit 6 ResetSample+5..ResetSample+6 Jump ReceiveBit ; receive bit 7 ResetSample+5..ResetSample+6 Jump ReceiveProc ; store the received byte ResetSample+5..ResetSample+6 .APC apcp1 ; program ReceiveProc: Move ComTmp,W ; get the received byte ResetSample+7 Move W,%1 ; and store it in the desired variable ResetSample+8 Jump EndReceiveProc ; go to user tasks ResetSample+9..ResetSample+10 .EndMacro \b;send a byte to the master during normal operation .Macro Transmit .LocalMacro TransmitProc ; parameter 1: name of variable where to read the byte to send .APC apcp0 ; jump table Jump TransmitProc ; load the byte into the shift register ResetSample+5..ResetSample+6 Jump TransmitBit ; transmit bit 0 ResetSample+5..ResetSample+6 Jump TransmitBit ; transmit bit 1 ResetSample+5..ResetSample+6 Jump TransmitBit ; transmit bit 2 ResetSample+5..ResetSample+6 Jump TransmitBit ; transmit bit 3 ResetSample+5..ResetSample+6 Jump TransmitBit ; transmit bit 4 ResetSample+5..ResetSample+6 Jump TransmitBit ; transmit bit 5 ResetSample+5..ResetSample+6 Jump TransmitBit ; transmit bit 6 ResetSample+5..ResetSample+6 Jump TransmitBit ; transmit bit 7 ResetSample+5..ResetSample+6 Jump AcknowledgeSend ; acknowledge byte send ResetSample+5..ResetSample+6 .APC apcp1 ; program TransmitProc: Move %1,W ; read the byte ResetSample+7 Move W,ComTmp ; and place in shift register ResetSample+8 Jump EndTransmitProc ; go to user tasks ResetSample+9..ResetSample+10 .EndMacro \macro;task scheduler| ; the first task must be 4 cycles shorter because it is executed after resetting the task counter (in macro EndTaskList) ; there must be at least one task (dummy task, Jump TaskDone) to avoid an infinite loop in EndTaskList ; The SyncTask is executed in every cycle, that is every T microseconds, after the communication procedures. If EndSeqTask is exactly (SComLength-ResetSample-11) cycles ; long, then SyncTask occurs very regularly and can be used, for example, to control a stepper motor, or in general to sample/write the I/O ports ; (processing of the data for the I/O ports can take place in the user tasks). .Macro StartTaskList ; begin user task jump table .APC apcp0 ; jump table EndMostComm: ; jump here after termination of most communication procedures ; (all except reset communication cycle) Clr flags:#ResetReceived ; no reset pulse received in this cycle SComLength-2 PinIn ; for TransmitBit only SComLength-1..SComLength TaskJumpTable: SyncTask ; macro defined in main program Move Task,W ; get task number SComLength+1 Inc Task ; next time do next task SComLength+2 Add W,PCL ; jump table: SComLength+3..SComLength+4 FirstTask: ; address of the first entry in the task jump table .EndMacro .Macro EndTaskList ; end user task jump table ; this is the last entry in the task jump table Move #1,W ; next time do the second task in the list Move W,Task Jump FirstTask ; and this time do the first task in the list .APC apcp1 ; the following tasks are in page 1 (program) .EndMacro \macro;check program/data size| ; check whether the 2 different program code pages and the variable area satisfy the conditions imposed by the processor architecture and do not overflow one into the other .Macro RangeCheck .APC apcvar .If APC .HI. MaxVar+1 .Error "too many variables" .EndIf .APC apcp0 .If APC .HI. StartPage1 .Error "page 0 (16'000..16'0FF) code too large" .EndIf .APC apcp1 .If APC .HI. MaxCode+1 .Error "program too large" .EndIf .EndMacro