L'Hexapod: Atmel ATtiny2313 Servo Controller v0.1 - source code

Previously published

This article was previously published on lhexapod.com as part of my journey of discovery into robotics and embedded assembly programming. A full index of these articles can be found here.

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