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 demultiplexor 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 taskuart_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_rxcfrcvdchar1: ; * 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 itrcvdchar2: 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 delaypwm1: 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 uSecpwm2: 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 delaydelay1: 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

Leave a comment