Atmel ATtiny2313 Servo Controller v0.1 - source code
The jitters and general instability of the hacked together simple servo controller (see here) for the ATtiny2313 were, it seems, down to the fact that the internal clock wasn’t stable enough and this caused enough timing issues to throw the PWM off enough to jiggle the servo position around rather than hold it steady. This morning my external 4MHz crystals arrived and after changing the processor’s fuse bits to accommodate the change in clock source everything was fine and the code started behaving itself.
The source code for the servo controller follows; as mentioned before, it’s a) pretty simple and b) heavily based on the code that I found here for the AT90S4414. It may be better to adjust it to work at an RS232 baud rate friendly clock speed, but I expect I’ll move away from the hard coded timing loops as soon as possible if I can and this might make such clock speed adjustments slightly easier.
The next step is to expand this to use one (or more) of the demultiplexer chips so that I can expand the number of channels supported.
; *******************************************
; ** **
; ** 8 Channel Serial Servo Controller **
; ** For ATtiny2313 **
; ** **
; ** Copyright (c) May 2009 **
; ** Len Holgate **
; ** **
; ** Based on original work **
; ** by George Vastianos **
; ** **
; *******************************************
; *******************
; * Microcontroller *
; * characteristics *
; *******************
; MCU = ATtiny2313
; Fclk = 4.0 MHz
.include "tn2313def.inc"
.cseg
.org $0000 ; * Reset handler
rjmp start
.org URXC0addr ; * UART RX Complete handler
rjmp uart_rxc
.org $000d ; * Main program start
;******************************
;* Interrupt Service Routines *
;******************************
.def sregb = r16
.def stemp = r17
uart_rxc:
in sregb, SREG ; * Store status register
rjmp rcvdchar ; * Start the task
uart_rxcf:
out SREG, sregb ; * Restore status register
ldi stemp, $90 ; * Enable UART Receiver & RX Complete Interrupt
out UCR, stemp
reti ; * Return to main program
;**************************
;* UART Reception Routine *
;**************************
.def rxchar = r18
rcvdchar: ; * Store the received character
in rxchar, udr
cpi rxchar, $ff ; * Check if character is sync byte
brne rcvdchar1
ldi r30, $60 ; * If character is sync byte then
ldi r31, $00 ; * set Z register in the begin of packet area (in int. SRAM)
rjmp uart_rxcf
rcvdchar1: ; * If character is not sync byte then
st Z+, rxchar ; * increase Z and store in int. SRAM the character
cpi r30, $62 ; * Check if packet finished
brne rcvdchar2
ldi r30, $60
rjmp panalysis ; * If packet finished go to analyze it
rcvdchar2:
rjmp uart_rxcf
;********************************
;* Data Packet Analysis Routine *
;********************************
panalysis:
lds stemp, $0060 ; * Check that our servo address is within range;
andi stemp, $F8 ; * Any value bigger than 8 will mean that the next
cpi stemp, 0 ; * compare to zero will fail and we'll
brne panalysis1 ; * ignore the packet
lds stemp, $0060 ; * It's valid 0-8, update the servo position data
ldi r28, $80
ldi r29, $00
add r28, stemp ; * use our servo number as an index into the control data
lds stemp, $0061 ; * and update our servo's control value...
st Y, stemp ; * into the table...
panalysis1:
rjmp uart_rxcf ; * Analysis finished
;*************************************
;* End Of Interrupt Service Routines *
;*************************************
;****************
;* Main Program *
;****************
start:
;**************
;* Initiation *
;**************
.def temp = r19
init:
ldi temp, RAMEND
out SPL, temp
ldi temp, $19 ; * Set UART on 9600 bps (for 115200 bps use $01)
out UBRR, temp
ldi temp, $90 ; * Enable UART Receiver & RX Complete Interrupt
out UCR, temp
ldi temp, $00
out WDTCR, temp ; * Watchdog Timer disable
out ACSR, temp ; * Analog Comparator disable
sts $0060, temp ; * Init pck byte 01
sts $0061, temp ; * Init pck byte 02
ldi temp, $7f ; * Init servo positions to 'middle'
sts $0080, temp ; * Init pos byte 01
sts $0081, temp ; * Init pos byte 02
sts $0082, temp ; * Init pos byte 03
sts $0083, temp ; * Init pos byte 04
sts $0084, temp ; * Init pos byte 05
sts $0085, temp ; * Init pos byte 06
sts $0086, temp ; * Init pos byte 07
sts $0087, temp ; * Init pos byte 08
ldi temp, $ff ; * Init all PWM outputs
out ddrb, temp
ldi temp, $00 ; * Reset all PWM outputs
out portb, temp
ldi r30, $60 ; * UART routines store data here.
ldi r31, $00 ; * In Z register...
sei ; * Global interrupt enable
mainloop:
;************************
;* PWM Control Routines *
;************************
.equ servo0 = PB0 ; * Set the output pin of Servo0
.equ servo1 = PB1 ; * Set the output pin of Servo1
.equ servo2 = PB2 ; * Set the output pin of Servo2
.equ servo3 = PB3 ; * Set the output pin of Servo3
.equ servo4 = PB4 ; * Set the output pin of Servo4
.equ servo5 = PB5 ; * Set the output pin of Servo5
.equ servo6 = PB6 ; * Set the output pin of Servo6
.equ servo7 = PB7 ; * Set the output pin of Servo8
.def tsout = r20 ; * Temp servo output pin register
.def tspos = r22 ; * Temp servo position register
pwmmark:
ldi tsout, exp2(servo0) ; * Control Servo0
lds tspos, $0080
rcall pwm
ldi tsout, exp2(servo1) ; * Control Servo1
lds tspos, $0081
rcall pwm
ldi tsout, exp2(servo2) ; * Control Servo2
lds tspos, $0082
rcall pwm
ldi tsout, exp2(servo3) ; * Control Servo3
lds tspos, $0083
rcall pwm
ldi tsout, exp2(servo4) ; * Control Servo4
lds tspos, $0084
rcall pwm
ldi tsout, exp2(servo5) ; * Control Servo5
lds tspos, $0085
rcall pwm
ldi tsout, exp2(servo6) ; * Control Servo6
lds tspos, $0086
rcall pwm
ldi tsout, exp2(servo7) ; * Control Servo7
lds tspos, $0087
rcall pwm
rjmp mainloopend
;********************
;* PWM Mark Routine *
;********************
.def count = r24
; * PWM routine for Servos 01-08 *
pwm:
out portb, tsout ; * Set output pins of the Servo
rcall delay ; * Wait for 900uS
ldi count, $00 ; * Start 1400uS delay
pwm1:
cp count, tspos ; * Reset output pin of Servo if position = count
brne pwm2
lds temp, $00
out portb, temp ; * Stop pulse, for tspos of 0 this is 900 uSec
; * for tspos of 0xfe this is 2300 uSec
pwm2:
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
inc count
cpi count, $ff ; * Check if delay completed
brne pwm1
ret ; * Stop 1400uS delay
;*****************
;* Delay Routine
;* The idea is that between setting the pin and unsetting the pin with a
;* time value of 0 we get a 900 uSec delay
delay:
nop
nop
nop
ldi temp, $E0 ; * Start of delay
delay1:
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
dec temp
cpi temp, $00
brne delay1
ret ; * End of delay
;*******************************
;* End Of PWM Control Routines *
;*******************************
mainloopend:
rjmp mainloop