Well, that was quicker than I expected. Here's my axiom. I'm not positive I have the interrupt set up correctly, but I read day 23 of
ASM in 28 Days, which helped a lot to understand how to do interrupts. This uses the axiom SDK as of version 1.1.2 (the latest, afaik). I know parts of it are ugly, but the majority of it is just waiting to make sure the timing is perfect.
Code: .nolist
#include "ti83plus.inc"
#include "Axe.inc"
.list
;Wasn't sure if I needed this, but it's in MemKit so I thought it's probably necessary.
#define B_CALL(xxxx) rst 28h \ .dw xxxx
.dw AXM_HEADER
.dw Ax1_End
.db %00010011
.dw $0BEF ;startTmr
.db AXM_INLINE
.db AXM_1ARG
.org 0
;******************************************
;StartDMX()
;******************************************
;Initializes the DMX driver.
;Usage: StartDMX(PTR) where PTR is a pointer to the DMX data (256 bytes minimum).
;PTR doesn't need to be zeroed out because the command does it for you.
;This command needs to be as close to the beginning of the program as possible
;because the DMX cable cannot be plugged in until the link cable is initialized.
di
push af
ld a,0
out (0),a ;Initialize the link cable (set the tip high for the DMX "break").
out ($54),a ;Start outputting power to the transceiver from the USB port.
;The area the data will be stored in must be cleared before sending the first DMX
;signal. The pointer also needs saved for later. The pointer should be passed in
;as hl (first argument of the command).
ld ($8251),hl ;bootTemp will store the DMX data location from now on.
ld b,0 ;Load the number of bytes to clear (256).
ClearByte:
ld (hl),0 ;Clear the byte at hl.
inc hl ;Move forward one byte.
djnz ClearByte ;Jump back and keep clearing bytes
ld hl,$8253 ;Where the interrupt loop counter will be.
ld (hl),54 ;Set up the counter.
;Set up the interrupt to send a DMX packet at ~2 Hz. The DMX spec says it must
;send a packet at least once per second.
ld hl,Interrupt
ld ($993F),hl
ld ($997F),hl
ld ($99BF),hl
ld ($99FF),hl
ld a,$99
ld i,a
ld a,%00000110 ;Make sure the interrupt is at the right speed (108 Hz).
out (4),a
ld a,%00001011 ;Enable interrupts.
out (3),a
im 2
ei
pop af
jr Ax1_End ;Return control to the program.
Interrupt:
ex af,af'
exx
ld a,0 ;Disable interrupts.
out(3),a
B_CALL(_KbdScan) ;Makes getKey work right.
ld hl,$8253 ;Get the location of the interrupt counter
dec (hl)
jp nz,Skip ;Skip the DMX transmission for now
ld (hl),54 ;Reload the counter
;***********************************
;Start sending the DMX signal
;***********************************
;Send the break header (at least 22 low bits).
ld a,1
out (0),a
;Wait for 1422 cycles (24 bits, to be on the safe side):
ld b,108 ;7 cycles.
djnz 0 ;13/8 cycles.
nop ;4 cycles.
nop ;4 cycles.
nop ;4 cycles.
nop ;4 cycles.
;Stop waiting. Next, send the mark-after-break (at least 2 high bits).
ld a,0 ;7 cycles.
out (0),a ;11 cycles.
;Wait for 162 cycles (3 bits, to be on the safe side):
ld b,12 ;7 cycles.
djnz 0 ;13/8 cycles.
nop ;4 cycles.
;Stop waiting. Next, send the start code (2 low bits, 0x00, and 2 high bits).
ld a,1 ;9 low bits. 7 cycles.
out (0),a ;11 cycles.
;Wait for 522 cycles:
ld b,40 ;7 cycles.
djnz 0 ;13/8 cycles.
;Stop waiting. Send 2 high bits to signal the end of the start code.
ld a,0 ;7 cycles.
out (0),a ;11 cycles.
;Wait for 78 cycles:
ld b,5 ;7 cycles.
ld b,5 ;7 cycles.
djnz 0 ;13/8 cycles.
nop ;4 cycles.
;Stop waiting. Begin sending the DMX data (this is where it gets rough).
ld hl,($82A3) ;Load the start of the data. 10 cycles.
ld b,0 ;The number of bytes to send (256). This can go up to 512, but I
;picked 256 because it fits in a single register. 7 cycles.
ld c,0 ;This is only used to get the carry flag. 7 cycles.
ByteLoop: ;Prepares to send a byte.
ld a,1 ;The first bit must be low to signal the start of a byte. 7 cycles.
out (0),a ;This buys me another 60 cycles to calculate the first bit. 11 cycles.
ld e,b ;djnz needs b to hold both the number of bytes left and the
;number of bits left, hence the backup to e. 4 cycles.
ld b,9 ;Number of bits to send (plus 1, see below). 7 cycles.
dec b ;Just to waste cycles. 6 cycles.
ld d,(hl) ;Load the byte to send. 7 cycles.
inc hl ;Move to the next byte. 6 cycles.
BitLoop: ;Reads and sends a bit.
rrc d ;Shift bit 0 of d into the carry flag and shift the rest of d
;to the right. 8 cycles.
ld a,1 ;Prepare the value to send to the link port. 7 cycles.
sbc a,c ;Subtract c and the carry flag from a (remember that c is 0, so
;it essentially stores the inverted carry flag to a). 4 cycles.
out (0),a ;Output high (0) if the carry flag was 1, low (1) if it was 0. 11 cycles.
;Wait for 17 cycles: (same as the total cycles of ByteLoop after out (0),a)
ld a,1 ;7 cycles.
dec a ;6 cycles.
nop ;4 cycles.
;Stop waiting.
djnz BitLoop ;Keep sending bits until the whole byte is sent. 13/8 cycles.
;Wait for 18 cycles:
ld a,0 ;7 cycles.
ld a,0 ;7 cycles.
nop ;4 cycles.
;Stop waiting.
ld a,0 ;The next two bits are high to signal the end of the byte. 7 cycles.
out (0),a ;11 cycles.
;Wait for 85 cycles:
ld b,7 ;7 cycles.
dec b ;Again, only for timing. 6 cycles.
dec b ;6 cycles.
dec b ;6 cycles.
djnz 0 ;13/8 cycles.
;Stop waiting.
ld b,e ;Get the backup of b to see how many bytes have been sent. 4 cycles.
djnz ByteLoop ;Keep going until 256 bytes have been sent. 13/8 cycles.
;Wait for 23 cycles to make sure the last bit gets through:
ld a,2 ;7 cycles.
dec a ;6 cycles.
dec a ;6 cycles.
nop ;4 cycles.
;Stop waiting.
Skip:
ld a,%00001011
out (3),a ;Enable interrupts.
ex af,af'
exx
ei
ret
Ax1_End:
.dw Ax2_End
.db %00010011
.dw $D0BB ;stdDev()
.db AXM_SUB
.db AXM_0ARG
.org 0
;******************************************
;StopDMX()
;******************************************
;Shut down the DMX driver.
;Usage: StopDMX()
;Use it before the main program returns or else bad things will happen.
di
push a
ld a,%00001011 ;Enable hardware.
out (3),a
ld a,0
out (0),a ;Stop holding the link port low, if it was before.
ld a,2
out ($54),a ;Stop sending voltage to the transceiver.
pop a
im 1 ;Re-enable the OS interrupt.
ei
Ax2_End:
.dw AXM_END
.db $8C,$05,9,"StartDMX("
.db $C4,$03,8,"StopDMX("
.end
EDIT: The main problem I see is that it takes over a tenth of a second to send the DMX signal (171,514 processor cycles, if I counted correctly). It's a fairly enormous interrupt, which I don't know if it would create a problem. It would certainly make the program that uses it very, very slow.
EDIT 2: I double checked my math and it actually only takes a hundredth of a second to send 256 channels, which is still really long but is much more manageable. However, the next interrupt would run immediately after it finishes the packet, but the counter should prevent another packet being sent for half a second. I could of course change the time to be shorter, at the expense of the host program's speed. Maybe there could be a way to trigger a packet manually if the data is changed, and only send a packet automatically every quarter second or so. Something to consider.
Also, I realized that I made a mistake on ld hl,($82A3) because that only stores one byte of the address I need. I think I would have to do: Code:
ld hl,$82A3
ld h,(hl)
inc hl
ld l,(hl)
I would of course need to update my previous wait loop then.