; ******************************************* ; ** 64 Channel Serial Servo Controller ** ; ** For ATMega168 ** ; ** Version 6.0 ** ; ** ** ; ** Copyright (c) September 2009 ** ; ** Len Holgate ** ; ** ** ; ** See http://www.lhexapod.com ** ; ** ** ; ** Note that this controller assumes ** ; ** that we have CD74HCT238E or equivalent** ; ** demultiplexor chips connected to pins ** ; ** 0-4 of Ports B and C and that the ** ; ** required address lines for these MUXs ** ; ** are run from pins 2-4 of PortD. ** ; ******************************************* ; .nolist .include "m168def.inc" .list .def resl = r0 ; serial + PWM setup (used by mul) .def resh = r1 ; serial + PWM setup (used by mul) .def currentPos = r2 ; PWM setup ONLY .def stepEvery = r3 ; PWM setup ONLY .def targetPos = r4 ; PWM setup ONLY .def stepCount = r5 ; PWM setup ONLY .def stepSize = r6 ; PWM setup ONLY .def bankMask = r7 ; init and PWM setup .def changed = r8 ; PWM setup ONLY .def sCommandLength = r9 ; serial ONLY .def pulseStopTemp1 = r10 ; PWM switch off ONLY .def pulseStopTemp2 = r11 ; PWM switch off ONLY .def sExpectedBytes = r12 ; serial ONLY .def sCurrentPos = r13 ; serial ONLY .def sStepEvery = r14 ; serial ONLY .def sTargetPos = r15 ; serial ONLY .def servoIndex = r16 ; serial ONLY .def serialChar = r17 ; serial ONLY .def movesComplete = r18 ; serial + PWM setup .def index = r19 ; PWM setup ONLY .def bankIndex = r20 ; init and PWM setup .def muxAddress = r21 ; init and PWM setup .def temp1 = r22 ; serial + PWM .def temp2 = r23 ; serial + PWM .def temp3 = r24 ; PWM ONLY .def temp4 = r25 ; PWM ONLY .equ POSITION_DATA_START = SRAM_START .equ NUM_SERVOS = 64 ; Max 64! ; Each servo has 5 bytes of configuration/working data. These are as follows: ; 0 - current position ; 1 - step every ; 2 - target position ; 3 - step size ; 4 - step counter ; TODO... ; min ; max ; centre adjust ; reset value .equ CURRENT_POS_OFFSET = 0 .equ STEP_EVERY_OFFSET = 1 .equ TARGET_POS_OFFSET = 2 .equ STEP_SIZE_OFFSET = 3 .equ STEP_COUNT_OFFSET = 4 .equ BYTES_PER_SERVO = 5 .equ POSITION_DATA_END = POSITION_DATA_START + (NUM_SERVOS * BYTES_PER_SERVO) .equ PWM_DATA_START = POSITION_DATA_END .equ PWM_SERVOS_PER_CYCLE = 8 .equ PWM_BYTES_PER_SERVO = 3 .equ PWM_DATA_SIZE = (PWM_SERVOS_PER_CYCLE + 1) * PWM_BYTES_PER_SERVO .equ PWM_DATA_END = PWM_DATA_START + PWM_DATA_SIZE .equ SERIAL_DATA_START = PWM_DATA_END .equ SERIAL_DATA_LENGTH = 20 .equ SERIAL_DATA_END = SERIAL_DATA_START + SERIAL_DATA_LENGTH .equ STACK_START = RAMEND ;.equ STACK_SIZE = 19 ;.equ STACK_END = STACK_START - STACK_SIZE + 1 ; inclusive .equ JUMP_TABLE_START = INT_VECTORS_SIZE .equ clock = 7372800 .equ baudrate = 9600 .equ baudconstant = (clock/(16*baudrate))-1 .ORG $0000 rjmp Init ; Reset .ORG OC1Aaddr rjmp TC1CmpA ; Timer/Counter1 Compare Match A .ORG JUMP_TABLE_START rjmp PWMSetup rjmp PWMPulseStop ; Timer 1 interrupt for CTC timer TC1CmpA: ; Each timer interrup handler deals with its own stack maintenance... ijmp ; Z is set up to point into our jump table (above) so we jump to Z then ; jump to the correct state for our timer handling... ; Program start Init: ; Set stack pointer - we use the stack for the return address of the interrupt handler... ldi temp1, LOW(STACK_START) out SPL, temp1 ldi temp1, HIGH(STACK_START) out SPH, temp1 ; Set up the serial port ldi temp1, HIGH(baudconstant) ; Set the baud rate sts UBRR0H, temp1 ldi temp1, LOW(baudconstant) sts UBRR0L, temp1 ldi temp1, (1 << RXEN0) | (1 << TXEN0) ; enable rx and tx sts UCSR0B, temp1 ldi temp1, (3 << UCSZ00) ; 8N1 ; Initialise Timer1 in CTC mode clr temp1 ; Controlword A 0x0000 sts TCCR1A, temp1 ldi temp1, (1< where ; bad param index is a 1 based index into the parameters in the echoed command and ; indicates the first parameter that failed validation. ; Some commands, Stop and Query commands for example, also cause a notification to be ; returned once the command has been executed, these should be treated as asynchronous ; messages, though, in fact, they are guarenteed to occur immediately after the command ; echo completes. SerialStart : ; we start with a zero command length, we're waiting for a command byte to work ; out how long the command will be... clr sCommandLength clr sExpectedBytes SerialLoop : tst movesComplete ; check to see if we have any incremental breq SerialLoop1 ; move completion notifications to send rcall SerialSendMoveCompleteNotification ; send them... SerialLoop1 : lds temp1, UCSR0A sbrs temp1, RXC0 rjmp SerialLoop lds serialChar, UDR0 ; read the character tst sExpectedBytes ; are we already accumulating a command? brne SerialDataCharacter ; new command to accumulate? ldi XL, LOW(SERIAL_DATA_START) ; new command, set pointer to buffer start ldi XH, HIGH(SERIAL_DATA_START) ; is the command byte valid?? ; calculate the length of data required for valid commands... cpi serialChar, 0x00 breq SerialSetCommandLengthGetInfo cpi serialChar, 0x41 breq SerialSetCommandLengthSetPosn cpi serialChar, 0x42 breq SerialSetCommandLengthSetDelayPosn cpi serialChar, 0x43 breq SerialSetCommandLengthSetDelayPosn2 cpi serialChar, 0x44 breq SerialSetCommandLengthStopServo cpi serialChar, 0x45 breq SerialSetCommandLengthStopServos cpi serialChar, 0x46 breq SerialSetCommandLengthStopAll cpi serialChar, 0x47 breq SerialSetCommandLengthQueryServo cpi serialChar, 0x48 breq SerialSetCommandLengthQueryServos cpi serialChar, 0x49 breq SerialSetCommandLengthQueryAll ; return an error for unrecognised commands... rjmp SerialError SerialDataCharacter : ldi temp1, HIGH(SERIAL_DATA_END) ; check for buffer overruns... cp temp1, XH brge PC+2 rjmp SerialError cpi XL, LOW(SERIAL_DATA_END) ; if we have already filled our buffer space brne PC+2 rjmp SerialError ; that's an error st X+, serialChar dec sExpectedBytes tst sExpectedBytes ; if we have accumulated the expected number of bytes breq SerialProcessCommand ; process the command rjmp SerialLoop ; Determine the length of the fixed portion of the commands. SerialSetCommandLengthSetDelayPosn2 : ldi temp1, 5 mov sExpectedBytes, temp1 mov sCommandLength, temp1 rjmp SerialDataCharacter SerialSetCommandLengthSetDelayPosn : ldi temp1, 4 mov sExpectedBytes, temp1 mov sCommandLength, temp1 rjmp SerialDataCharacter SerialSetCommandLengthSetPosn : ldi temp1, 3 mov sExpectedBytes, temp1 mov sCommandLength, temp1 rjmp SerialDataCharacter SerialSetCommandLengthStopServo : SerialSetCommandLengthQueryServo : SerialSetCommandLengthStopServos : ; This is a variable length command with a two byte header SerialSetCommandLengthQueryServos : ; This is a variable length command with a two byte header ldi temp1, 2 mov sExpectedBytes, temp1 mov sCommandLength, temp1 rjmp SerialDataCharacter SerialSetCommandLengthGetInfo : SerialSetCommandLengthStopAll : SerialSetCommandLengthQueryAll : ldi temp1, 1 mov sExpectedBytes, temp1 mov sCommandLength, temp1 rjmp SerialDataCharacter ; process the command... SerialProcessCommand : ; we have an 'x' byte serial command... ldi XL, LOW(SERIAL_DATA_START) ldi XH, HIGH(SERIAL_DATA_START) ld temp1, X+ ; get the command byte... cpi temp1, 0x00 brne PC+2 rjmp SerialProcessCommandGetInfo cpi temp1, 0x41 brne PC+2 rjmp SerialProcessCommandSetPosn cpi temp1, 0x42 brne PC+2 rjmp SerialProcessCommandSetDelayPosn cpi temp1, 0x43 brne PC+2 rjmp SerialProcessCommandSetDelayPosn2 cpi temp1, 0x44 brne PC+2 rjmp SerialProcessCommandStopServo cpi temp1, 0x45 brne PC+2 rjmp SerialProcessCommandStopServos cpi temp1, 0x46 brne PC+2 rjmp SerialProcessCommandStopAll cpi temp1, 0x47 brne PC+2 rjmp SerialProcessCommandQueryServo cpi temp1, 0x48 brne PC+2 rjmp SerialProcessCommandQueryServos cpi temp1, 0x49 brne PC+2 rjmp SerialProcessCommandQueryAll rjmp SerialError SerialProcessCommandGetInfo : ; send back version major/minor then number of servos supported ldi serialChar, 0x00 rcall SendSerial ldi serialChar, 0x06 rcall SendSerial ldi serialChar, 0x00 rcall SendSerial ldi serialChar, NUM_SERVOS rcall SendSerial rjmp SerialStart ; Set Servo Position. ; 0x41 ; This command is the same as the SSC 0xFF command. We set the ; specified servo to the specified position. The servo must be in the range ; 0 - NUM_SERVOS and the position must be in the range 0 - 0xFE. We validate ; the parameters and send an error message if any are out of bounds. ; Since we could be setting a position when a delayed move is in progress we ; MUST set stepEvery to 0 before updating currentPos. Once stepEvery is 0 ; we can update currentPos to the new desired servo position and then set ; the other delayed move parameters to zero. SerialProcessCommandSetPosn : ld servoIndex, X+ cpi servoIndex, NUM_SERVOS ; check the servo index is valid brlt PC+2 rjmp SerialServoOutOfRange ld temp1, X+ cpi temp1, 0xFF ; check the servo position is valid brne PC+2 rjmp SerialPosnOutOfRange ; we have a servo index 0-NUM_SERVOS in servoIndex ; we have a control value 0-254 in temp1 ldi XL, LOW(POSITION_DATA_START) ldi XH, HIGH(POSITION_DATA_START) ldi temp2, BYTES_PER_SERVO mul servoIndex, temp2 add XL, resl adc XH, resh adiw XL:XH, 1 ; we must update the 'step every' value to 0 before changing anything else ; or the PWM generation may get confused and use this data as part of a ; delayed move... clr temp2 st X, temp2 ; step every... sbiw XL:XH, 1 ; now update the whole data structure, we reset unused values to zero, we need only really set ; current position and step every... st X+, temp1 ; current position st X+, temp2 ; step every... st X+, temp2 ; target position st X+, temp2 ; step size rcall SerialEchoCommand rjmp SerialStart ; Delayed Set Servo Position ; 0x42 ; Sets the servo to move from the current position to the specified position with the specified ; step size which increments each refresh of the PWM signal (that is, 20 times per second). ; The servo must be in the range 0 - NUM_SERVOS and the position must be in the range 0 - 0xFE, ; the step size must be non zero. We validate the parameters and send an error message if any are ; out of bounds. ; Since we're updating the multiple byte part of the servo control data we MUST set the stepEvery ; value to zero before we update anything else. Once that's done we update the targetPos and the ; stepSize. We then set the stepEvery value to 1 so that we step each cycle. SerialProcessCommandSetDelayPosn : ld servoIndex, X+ cpi servoIndex, NUM_SERVOS ; check the servo index is valid brlt PC+2 rjmp SerialServoOutOfRange ld temp2, X+ cpi temp2, 0xFF ; check the servo position is valid brne PC+2 rjmp SerialPosnOutOfRange mov sTargetPos, temp2 ld temp1, X+ tst temp1 ; check the step size is valid brne PC+2 rjmp SerialStepSizeOutOfRange ; we have a servo index 0-NUM_SERVOS in servoIndex ; we have a control value 0-254 in sTargetPos ; we have a step size in temp1 ldi XL, LOW(POSITION_DATA_START) ldi XH, HIGH(POSITION_DATA_START) ldi temp2, BYTES_PER_SERVO mul servoIndex, temp2 add XL, resl adc XH, resh adiw XL:XH, 1 ; we must update the 'step every' value to 0 before changing anything else ; or the PWM generation may get confused and use this data as part of a ; delayed move... clr temp2 st X+, temp2 ; step every... ; now update the rest of the data structure for a delayed move st X+, sTargetPos ; target position (where we want to move to) st X, temp1 ; step size sbiw XL:XH, 2 ; move back to the 'step every' part of the data structure ldi temp2, 1 st X, temp2 ; step every cycle... rcall SerialEchoCommand rjmp SerialStart ; Delayed Set Servo Position with step frequency ; 0x42 ; Sets the servo to move from the current position to the specified position with the specified ; step size which increments at the specified frequency of the PWM signal (that is a value of 1 ; steps 20 times per second, a value of 2 steps 10 times per second, a value of 20 once per second, ; etc). The servo must be in the range 0 - NUM_SERVOS and the position must be in the range ; 0 - 0xFE, the step size and the step frequency must be non zero. We validate the parameters and ; send an error message if any are out of bounds. ; Since we're updating the multiple byte part of the servo control data we MUST set the stepEvery ; value to zero before we update anything else. Once that's done we update the targetPos and the ; stepSize. We then set the stepEvery value to the desired step frequency. SerialProcessCommandSetDelayPosn2 : ld servoIndex, X+ cpi servoIndex, NUM_SERVOS ; check the servo index is valid brlt PC+2 rjmp SerialServoOutOfRange ld temp2, X+ cpi temp2, 0xFF ; check the servo position is valid brne PC+2 rjmp SerialPosnOutOfRange mov sTargetPos, temp2 ld temp1, X+ tst temp1 ; check the step size is valid brne PC+2 rjmp SerialStepSizeOutOfRange ld sStepEvery, X+ tst sStepEvery ; check the step frequency is valid brne PC+2 rjmp SerialStepEveryOutOfRange ; we have a servo index 0-NUM_SERVOS in servoIndex ; we have a control value 0-254 in sTargetPos ; we have a step size in temp1 ; we have a step frequency in sStepEvery ldi XL, LOW(POSITION_DATA_START) ldi XH, HIGH(POSITION_DATA_START) ldi temp2, BYTES_PER_SERVO mul servoIndex, temp2 add XL, resl adc XH, resh adiw XL:XH, 1 ; we must update the 'step every' value to 0 before changing anything else ; or the PWM generation may get confused and use this data as part of a ; delayed move... clr temp2 st X+, temp2 ; step every... ; now update the rest of the data structure for a delayed move st X+, sTargetPos ; target position (where we want to move to) st X, temp1 ; step size sbiw XL:XH, 2 ; move back to the 'step every' part of the data structure st X, sStepEvery ; step every cycle... rcall SerialEchoCommand rjmp SerialStart ; Servo Stop functions ; Stop Servo ; 0x43 ; Stops the specific servo and causes a servo stopped response to be returned (after the command echo). ; The servo must be in the range 0 - NUM_SERVOS. We validate the parameters and send an error message ; if any are out of bounds. Note that in addition to the command echo this command also generates a ; single stop notificiation message in the form: ; 0xFD ; If the servo was not moving when the stop command was sent then the value in the ; notification will be 0. SerialProcessCommandStopServo : ld servoIndex, X+ cpi servoIndex, NUM_SERVOS ; check the servo index is valid brlt PC+2 rjmp SerialServoOutOfRange rcall SerialEchoCommand rcall SerialStopServo rjmp SerialStart ; Stop Servos ; 0x44 ... ; Stops all of the specified servos. This is a variable length command. ; The first thing we do is check to see if we've been called once already. The initial ; command accumulation code works with a fixed length of 2 bytes, enough for us to ; accumulate the length of the actual command. If the command length is currently 2 then ; we go off to calculate the real length and continue to accumulate more data until we ; have a complete command. ; Once we have a complete command we loop over all of the servos and validate them. ; this allows us to send the command echo, or error before we start to send ; servo stop notifications. ; Finally we loop over the servos again and stop each one and send the appropriate ; notification SerialProcessCommandStopServos : ; sCommandLength == 2 then our command length is actually ; 2 + value of length byte, else we're complete... ldi temp1, 2 cp sCommandLength, temp1 breq SerialProcessCommandStopServosSetRealLength ld temp1, X+ ; number of servos to process... SerialProcessCommandStopServosValidationLoop : tst temp1 ; it's valid, though unusual, to have 0 servos... breq SerialProcessCommandStopServosValidationLoopEnd ld servoIndex, X+ cpi servoIndex, NUM_SERVOS ; check the servo index is valid brlt PC+2 rjmp SerialServoListElementOutOfRange dec temp1 rjmp SerialProcessCommandStopServosValidationLoop SerialProcessCommandStopServosValidationLoopEnd : rcall SerialEchoCommand ; now process the command... ldi XL, LOW(SERIAL_DATA_START) ; back to the start of the buffer ldi XH, HIGH(SERIAL_DATA_START) ld temp1, X+ ; throw away the command code... ld temp2, X+ ; number of servos to process... SerialProcessCommandStopServosActionLoop : tst temp2 ; it's valid, though unusual, to have 0 servos... breq SerialProcessCommandStopServosActionLoopEnd ld servoIndex, X+ rcall SerialStopServo dec temp2 rjmp SerialProcessCommandStopServosActionLoop SerialProcessCommandStopServosActionLoopEnd : rjmp SerialStart SerialProcessCommandStopServosSetRealLength : ; the stop servos command is in the form .... ; where there are 'length' servo indexes after the length... ; the initial command length is set to 2, once we have 2 bytes we can work out ; the complete length... ld temp1, X+ tst temp1 ; 0 is valid, though pointless! breq SerialProcessCommandStopServosValidationLoop mov sExpectedBytes, temp1 ldi temp2, 2 add temp1, temp2 mov sCommandLength, temp1 rjmp SerialLoop ; back to the command accumulation loop! ; This function does the actual work of stopping a servo, also called from serial stop servos.... ; Note that we first read the stepEvery value, then set it to zero to stop any further movement ; of the servo, we must then (and only then) read the currentPos of the servo as the PWM code ; will not change it once stepEvery is zero. SerialStopServo : push XL push XH ; servo to stop is in servoIndex ldi XL, LOW(POSITION_DATA_START) ldi XH, HIGH(POSITION_DATA_START) ldi temp1, BYTES_PER_SERVO mul servoIndex, temp1 add XL, resl adc XH, resh ld sCurrentPos, X+ ld sStepEvery, X tst sStepEvery breq SerialStopServoAlreadyStopped clr temp1 ; zero the step every value to stop the servo st X, temp1 SerialStopServoAlreadyStopped : sbiw XL:XH, 1 ; move back to the current pos as this is used ; in SerialSendStopResponse rcall SerialSendStopResponse pop XH pop XL ret ; Stop All Servos ; 0x46 ; Stops all of the servos that the controller controls. SerialProcessCommandStopAll : rcall SerialEchoCommand ldi XL, LOW(POSITION_DATA_START) ldi XH, HIGH(POSITION_DATA_START) clr servoIndex SerialStopAllServosLoop : ld sCurrentPos, X+ ld sStepEvery, X tst sStepEvery breq SerialStopAllServosLoopServoAlreadyStopped clr temp1 ; zero the step every value to stop the servo st X, temp1 SerialStopAllServosLoopServoAlreadyStopped : sbiw XL:XH, 1 ; move back to the current pos... rcall SerialSendStopResponse adiw XL:XH, 1 ; step over the final member of the structure ; to the next record inc servoIndex cpi servoIndex, NUM_SERVOS brne SerialStopAllServosLoop rjmp SerialStart ; Sends a servo stopped notification SerialSendStopResponse : ld sCurrentPos, X+ ; load current pos after we've set 'step every' to 0 ldi serialChar, 0xFD rcall SendSerial mov serialChar, servoIndex ; servo we stopped rcall SendSerial mov serialChar, sCurrentPos ; position it was in rcall SendSerial adiw XL:XH, 1 ; step the pointer over the 'step every' value mov serialChar, sStepEvery ; how often it steped before we stopped it rcall SendSerial ld temp1, X+ ; target position mov serialChar, temp1 ; where it's moving too rcall SendSerial ld temp1, X+ ; step size mov serialChar, temp1 ; the size of the step rcall SendSerial ret ; Query Servo Position ; 0x47 ; Queries the position of the specific servo and causes a servo query response to be returned ; (after the command echo). ; The servo must be in the range 0 - NUM_SERVOS. We validate the parameters and send an error message ; if any are out of bounds. Note that in addition to the command echo this command also generates a ; single query notificiation message in the form: ; 0xFC ; If the servo was not moving when the query command was sent then the value in the ; notification will be 0. If the and the are not equal and the value ; is non-zero then the servo is moving. SerialProcessCommandQueryServo : ld servoIndex, X+ cpi servoIndex, NUM_SERVOS ; check the servo index is valid brlt PC+2 rjmp SerialServoOutOfRange rcall SerialEchoCommand rcall SerialQueryServo rjmp SerialStart ; Query Servos ; 0x48 ... ; Queries all of the specified servos. This is a variable length command. ; The first thing we do is check to see if we've been called once already. The initial ; command accumulation code works with a fixed length of 2 bytes, enough for us to ; accumulate the length of the actual command. If the command length is currently 2 then ; we go off to calculate the real length and continue to accumulate more data until we ; have a complete command. ; Once we have a complete command we loop over all of the servos and validate them. ; this allows us to send the command echo, or error before we start to send ; servo query notifications. ; Finally we loop over the servos again and query each one and send the appropriate ; notification SerialProcessCommandQueryServos : ; sCommandLength == 2 then our command length is actually ; 2 + value of length byte, else we're complete... ldi temp1, 2 cp sCommandLength, temp1 breq SerialProcessCommandQueryServosSetRealLength ld temp1, X+ ; number of servos to process... SerialProcessCommandQueryServosValidationLoop : tst temp1 ; it's valid, though unusual, to have 0 servos... breq SerialProcessCommandQueryServosValidationLoopEnd ld servoIndex, X+ cpi servoIndex, NUM_SERVOS ; check the servo index is valid brlt PC+2 rjmp SerialServoListElementOutOfRange dec temp1 rjmp SerialProcessCommandQueryServosValidationLoop SerialProcessCommandQueryServosValidationLoopEnd : rcall SerialEchoCommand ; now process the command... ldi XL, LOW(SERIAL_DATA_START) ; back to the start of the buffer ldi XH, HIGH(SERIAL_DATA_START) ld temp1, X+ ; throw away the command code... ld temp2, X+ ; number of servos to process... SerialProcessCommandQueryServosActionLoop : tst temp2 ; it's valid, though unusual, to have 0 servos... breq SerialProcessCommandQueryServosActionLoopEnd ld servoIndex, X+ rcall SerialQueryServo dec temp2 rjmp SerialProcessCommandQueryServosActionLoop SerialProcessCommandQueryServosActionLoopEnd : rjmp SerialStart SerialProcessCommandQueryServosSetRealLength : ; the query servos command is in the form .... ; where there are 'length' servo indexes after the length... ; the initial command length is set to 2, once we have 2 bytes we can work out ; the complete length... ld temp1, X+ tst temp1 ; 0 is valid, though pointless! breq SerialProcessCommandQueryServosValidationLoop mov sExpectedBytes, temp1 ldi temp2, 2 add temp1, temp2 mov sCommandLength, temp1 rjmp SerialLoop ; back to the command accumulation loop! ; This function does the actual work of querying a servo, also called from serial query servos.... SerialQueryServo : ; servo to query is in servoIndex push XL push XH ldi XL, LOW(POSITION_DATA_START) ldi XH, HIGH(POSITION_DATA_START) ldi temp1, BYTES_PER_SERVO mul servoIndex, temp1 add XL, resl adc XH, resh rcall SendQueryResponse pop XH pop XL ret ; Query All Servos ; 0x49 ; Queries all of the servos that the controller controls. SerialProcessCommandQueryAll : rcall SerialEchoCommand ldi XL, LOW(POSITION_DATA_START) ldi XH, HIGH(POSITION_DATA_START) clr servoIndex SerialQueryAllServosLoop : rcall SendQueryResponse adiw XL:XH, BYTES_PER_SERVO ; next servo... inc servoIndex cpi servoIndex, NUM_SERVOS brne SerialQueryAllServosLoop rjmp SerialStart ; Sends a query response SendQueryResponse : ldi serialChar, 0xFC rcall SendSerial mov serialChar, servoIndex ; servo we queried rcall SendSerial ld sCurrentPos, X+ mov serialChar, sCurrentPos ; position it is in rcall SendSerial ld temp1, X+ ; step every mov serialChar, temp1 ; how often it steps rcall SendSerial ld temp1, X+ ; target position mov serialChar, temp1 ; where it's moving too rcall SendSerial ld temp1, X+ ; step size mov serialChar, temp1 ; the size of the step rcall SendSerial sbiw XL:XH, 4 ; move X back to where we started ret ; When the PWM code completes some delayed servo moves, that is, when ; the servo reaches the target position, it signals the serial code to ; send notifications. The siginally is based on the PWM code setting the ; movesComplete register (it sets the bit for the bank in which the ; servo that has completed its move). Right now we ignore the bits and ; always check all servos if the movesComplete register is non zero. ; Note that to ensure that we don't miss any move completions we MUST ; set the movesComplete register to 0 before we begin to process the ; servo data and send notifications. This is because the PWM code can ; interrupt us at any point and may complete more servo moves. ; Completion messages are in the form: 0xFE . SerialSendMoveCompleteNotification : ; we could optimise this so that we only check the bank that is ; set in movesComplete... ; note that we've processed these completions... ldi movesComplete, 0x00 ; loop over all the config data... push XL push XH ldi XL, LOW(POSITION_DATA_START) ldi XH, HIGH(POSITION_DATA_START) clr servoIndex SerialSendMoveCompleteNotificationLoop : ld sCurrentPos, X+ ld sStepEvery, X+ ld sTargetPos, X cp sCurrentPos, sTargetPos brne SerialSendMoveCompleteNotificationLoopIncrement tst sStepEvery breq SerialSendMoveCompleteNotificationLoopIncrement sbiw XL:XH, 1 clr sStepEvery st X+, sStepEvery ldi serialChar, 0xFE rcall SendSerial mov serialChar, servoIndex rcall SendSerial mov serialChar, sCurrentPos rcall SendSerial SerialSendMoveCompleteNotificationLoopIncrement : adiw XL:XH, BYTES_PER_SERVO - 2 inc servoIndex cpi servoIndex, NUM_SERVOS breq SerialSendMoveCompleteNotificationLoopEnd rjmp SerialSendMoveCompleteNotificationLoop SerialSendMoveCompleteNotificationLoopEnd : pop XH pop XL ret ; Echoes the command in the serial command accumulation buffer ; back to the client. SerialEchoCommand : ; echo the command back to the sender... mov sExpectedBytes, sCommandLength ldi XL, LOW(SERIAL_DATA_START) ldi XH, HIGH(SERIAL_DATA_START) SerialEchoCommandSendNextByte : ld serialChar, X+ rcall SendSerial dec sExpectedBytes tst sExpectedBytes brne SerialEchoCommandSendNextByte ret ; Sets up temp2 for parameter out of range errors. SerialServoListElementOutOfRange : ldi temp2, 1 ; add one for the command length... add temp2, temp1 rjmp SerialParamOutOfRange SerialServoOutOfRange : ldi temp2, 1 ; index of the bad parameter rjmp SerialParamOutOfRange SerialPosnOutOfRange: ldi temp2, 2 ; index of the bad parameter rjmp SerialParamOutOfRange SerialStepSizeOutOfRange : ldi temp2, 3 ; index of the bad parameter rjmp SerialParamOutOfRange SerialStepEveryOutOfRange : ldi temp2, 4 ; index of the bad parameter rjmp SerialParamOutOfRange ; Sends a parameter out of range error; 0xFF ; where is a 1 based index into the parameters in the command ; that indicates which parameter is invalid. SerialParamOutOfRange : ; send the error message back to the sender... ldi serialChar, 0xFF ; command error response rcall SendSerial mov serialChar, temp2 ; index of parameter that is out of range rcall SendSerial rcall SerialEchoCommand ; the command... rjmp SerialStart ; Sends a serial error, which is simply a parameter out of range error ; with a parameter index of 0. SerialError : ; we could light a led and only unlight it on valid data... ldi temp2, 0 ; index of the bad parameter rjmp SerialParamOutOfRange ; Sends a byte out of the serial port. SendSerial : lds temp1, UCSR0A sbrs temp1, UDRE0 rjmp SendSerial sts UDR0, serialChar ret