;;; vim: set ft=avr:
.nolist
.include "m168def.inc" ; Same family as 328P
.list
.include "macros.inc"
;; Interrupt Vector
.cseg
.org 0x0000
rjmp Main
;; CPU cycles / second: 16 Mhz
.set CPU_CYCLES = 16 * 1000 * 1000
;; Delays
.include "delay.inc"
;; DMX baud raite: 250k
.set DMX_BAUD = 250 * 1000
;; CPU cycles / bit: 64
.set DMX_CYCLES = CPU_CYCLES / DMX_BAUD
;; DMX output I/O
.set DMX_DDR = DDRB
.set DMX_PORT = PORTB
.equ DMX_DATA = PORTB3
;; DMX protocol timer
; Registers
.set DMX_TIMER_CRA = TCCR2A
.set DMX_TIMER_CRB = TCCR2B
.set DMX_TIMER_CNT = TCNT2
.set DMX_TIMER_OCRA = OCR2A
.set DMX_TIMER_OCRB = OCR2B
.set DMX_TIMER_IMSK = TIMSK2
.set DMX_TIMER_IFR = TIFR2
; Compare output match isn't used
.set DMX_TIMER_COMA = 0b00
.set DMX_TIMER_COMB = 0b00
; Control register, generation mode value
.set DMX_TIMER_WGM_10 = 0b10 ; CTC
.set DMX_TIMER_WGM_2 = 0b0
; Clock select
.set DMX_TIMER_CS_STOP = 0b000
.set DMX_TIMER_CS = 0b001 ; 1/1
;.set DMX_TIMER_CS = 0b111 ; 1/1024
; Counted value
.set DMX_TIMER_TOP = DMX_CYCLES ; number of cycles for baud
;; Debug LED
.set LED_DDR = DDRB
.set LED_PORT = PORTB
.set LED_PIN = PINB
.set LED_BIT = PORTB0
;; Set up DMX output
DMX_Init:
; Setup output port
; out
sbi DMX_DDR, DMX_DATA
; drive high
sbi DMX_PORT, DMX_DATA
; Setup timer
; setup CTC mode with no output pins
poke [DMX_TIMER_CRA, r16, (DMX_TIMER_COMA << COM2A0) | (DMX_TIMER_COMB << COM2B0) | (DMX_TIMER_WGM_10 << WGM20)]
poke [DMX_TIMER_CRB, r16, (DMX_TIMER_WGM_2 << WGM22) | (DMX_TIMER_CS_STOP << CS20)]
; trigger threshold for CTC
poke [DMX_TIMER_OCRA, r16, (DMX_TIMER_TOP)]
; OK
ret
;; Start Break signal
;;
;; 22 bits - 1s long; then DMX_Break_Mark
;;
DMX_Break_Start:
; Low
cbi DMX_PORT, DMX_DATA
ret
;; Start Mark-after-break signal
;;
;; 2 bits - 1s long; then DMX_Break_End
;;
DMX_Break_Mark:
; High
sbi DMX_PORT, DMX_DATA
ret
;; End break; prepare for DMX_Frame
DMX_Frame_Start:
; Start timer
poke [DMX_TIMER_CRB, r20, (DMX_TIMER_WGM_2 << WGM22) | (DMX_TIMER_CS << CS20)]
ret
;; Do a full DMX break, using some random length
;;
DMX_Break:
; Break
; start
rcall DMX_Break_Start
; wait; about 100ms?
ldi r20, 82 / 10
rcall VarDelay
; MAB
; mark
rcall DMX_Break_Mark
; short wait
ldi r20, 1
rcall VarDelay
; Timed frames
; start frame
rcall DMX_Frame_Start
; ok
ret
;; Bitbang one DMX bit out
;; uses SREG/C to send
;
; Uses Timer2 as a bit sync clock, sending out the next bit once we've hit 64 cycles on the timer
DMX_Bit:
; Wait for bit sync clock
_dmx_bit_wait:
; test OCA hit
sbic TIFR2, OCF2A
rjmp _dmx_bit_wait
;sbi LED_PORT, LED_BIT
; Output bit
; XXX: ugly bit-testing, can't we do this using something more nifty?
brcs _dmx_bit_1
; bit 0
cbi DMX_PORT, DMX_DATA
rjmp _dmx_bit_done
_dmx_bit_1:
; bit 1
sbi DMX_PORT, DMX_DATA
nop
; Bit sent
_dmx_bit_done:
; reset OCA hit for next bit
cbi TIFR2, OCF2A
; OK, bit sync clock keeps running for next bit
ret
;; Bitbang one DMX byte out, using DMX_Bit
;; r16: byte value
;
; Uses Timer2 as a bit sync clock; must call DMX_Frame_Start before first DMX_Frame
DMX_Frame:
; Start bit
clc
rcall DMX_Bit
; Data bits: 8
ldi r21, 8
_dmx_frame_loop:
; shift + send bit
lsl r16
rcall DMX_Bit
; loop
dec r21
brne _dmx_frame_loop
; Stop bits
sec
rcall DMX_Bit
rcall DMX_Bit
; OK
ret
;; End of DMX frames
DMX_Frame_End:
; Keep mark from end of last frame; DMX_Break_Start starts the break
; Stop the timer
poke [DMX_TIMER_CRB, r20, (DMX_TIMER_WGM_2 << WGM22) | (DMX_TIMER_CS_STOP << CS20)]
; OK
ret
;; Send one value on all frames
;; r17: byte value
DMX_Flood:
; Break
rcall DMX_Break
; Start code
ldi r16, 0
rcall DMX_Frame
; Channels
; number of channels to send
ldi r22, 100
_dmx_flood_channels:
; restore channel value
mov r16, r17
; send channel value
rcall DMX_Frame
; loop
dec r22
brne _dmx_flood_channels
; End packet
rcall DMX_Frame_End
ret
;; Program main
Main:
; Initialization
; Debug
sbi LED_DDR, LED_BIT
sbi LED_PORT, LED_BIT
; Stack
poke [SPL, r16:r17, RAMEND]
; Init
rcall DMX_Init
cbi LED_PORT, LED_BIT
; Send; value
_main_loop:
ldi r17, 255
rcall DMX_Flood
cbi LED_PORT, LED_BIT
; never returns..
rjmp _main_loop