Compare commits

...

1 Commits
master ... work

Author SHA1 Message Date
flabbergast e69b4a472a WIP. 8 months ago
  1. 19
      work/ass
  2. 130
      work/attic/datastack.asm
  3. 68
      work/attic/interrupts.asm
  4. 59
      work/core.asm
  5. 181
      work/i2c.asm
  6. 145
      work/main.asm
  7. 316
      work/rf69.h
  8. 306
      work/rfm69-tx.asm
  9. 73
      work/spi.asm
  10. 168
      work/spiflash.asm
  11. 21
      work/timers.asm
  12. 81
      work/uart_lp.asm
  13. 192
      work/write.asm

19
work/ass

@ -0,0 +1,19 @@
#!/bin/bash
set -euo pipefail
ARG1=${1:-}
TARGET=main
function clean { rm -f $TARGET.hex $TARGET.p $TARGET.lst ; }
clean
if test "$ARG1" = "clean" ; then
exit 0
fi
asl -r 2 -L $TARGET.asm && p2hex $TARGET.p -r 0x0000-0xffff
if test $? -eq 0 -a "$ARG1" == "flash" ; then
mspdebug rf2500 "prog $TARGET.hex"
fi

130
work/attic/datastack.asm

@ -0,0 +1,130 @@
;
; Mecrisp - A native code Forth implementation for MSP430 microcontrollers
; Copyright (C) 2011 Matthias Koch
;
; This program is free software: you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation, either version 3 of the License, or
; (at your option) any later version.
;
; This program is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
; GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public License
; along with this program. If not, see <http://www.gnu.org/licenses/>.
;
; Datastack - a second stack, emulated, but interrupt-proof.
padding on ; Strings sometimes need an extra padding zero so that instructions are aligned.
popda macro dst ; Fetch from datastack - 2 Bytes
mov @r4+, dst ; Increment stackpointer by two after fetching content.
endm
pushda macro src ; Put something on datastack - 8 Bytes
decd r4 ; Adjust stack pointer
mov src, @r4 ; and put value in.
endm
pushdadouble macro src1, src2 ; Put two elements at once on datastack.
sub #4, r4 ; Adjust stack pointer
mov src1, 2(r4) ; and put values in.
mov src2, 0(r4)
endm
; Datastack not designed for byte access...
; DUP ( n -- n n ) Duplicates the top stack item.
dup macro
pushda 2(r4)
endm
ddup macro
sub #4, r4
mov 4(r4), 0(r4)
mov 6(r4), 2(r4)
endm
; OVER ( n1 n2 -- n1 n2 n1 ) Makes a copy of the second item and pushes it on top.
over macro
pushda 4(r4)
endm
; SWAP ( n1 n2 -- n2 n1 ) Reverses the top two stack items.
;swap macro
; push 2(r4) ; ( B A ) R : B
; mov @r4, 2(r4) ; ( A A ) R : B
; pop @r4 ; ( A B ) R : B
; endm
swap macro ; Same length, other way
mov @r4, r7 ; ( B A ) r7 : A
mov 2(r4), @r4 ; ( B B ) r7 : A
mov r7, 2(r4) ; ( A B ) r7 : A
endm
; ROT ( n1 n2 n3 -- n2 n3 n1 ) Rotates the third item to the top.
; rot rot rot is identity.
rot macro ; ( 1 2 3 )
mov @r4, r7 ; ( 1 2 3 ) R: 3
mov 4(r4), 0(r4) ; ( 1 2 1 ) R: 3
mov 2(r4), 4(r4) ; ( 2 2 1 ) R: 3
mov r7, 2(r4) ; ( 2 3 1 ) R: 3
endm
minusrot macro ; ( 1 2 3 )
mov @r4, r7 ; ( 1 2 3 ) R: 3
mov 2(r4), 0(r4) ; ( 1 2 2 ) R: 3
mov 4(r4), 2(r4) ; ( 1 1 2 ) R: 3
mov r7, 4(r4) ; ( 3 1 2 ) R: 3
endm
; DROP ( n -- ) Discards the top stack item.
drop macro ; 2 Bytes long
incd r4
endm
ddrop macro ; 2 Bytes long
add #4, r4
endm
; NIP ( x1 x2 -- x2 ) Drop the first item below the top of stack.
nip macro
mov @r4+, 0(r4) ; Deletes second element of stack.
endm
; Fetch and store on second stack - Returnstack
to_r macro ; >R 2 Bytes long
push @r4+
endm
r_from macro ; R> 4 Bytes long
pushda @sp+
endm
; Fast calculations
plus macro
add @r4+, 0(r4) ; Add top two elements of stack
endm
minus macro
sub @r4+, 0(r4)
endm
negate macro ; Flip sign
inv @r4
inc @r4
endm
abs macro ; Calculate absolute value
bit #8000h, @r4
jnc +
negate
+
endm

68
work/attic/interrupts.asm

@ -0,0 +1,68 @@
;
; Mecrisp - A native code Forth implementation for MSP430 microcontrollers
; Copyright (C) 2011 Matthias Koch
;
; This program is free software: you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation, either version 3 of the License, or
; (at your option) any later version.
;
; This program is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
; GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public License
; along with this program. If not, see <http://www.gnu.org/licenses/>.
;
; Common tools for interrupts
;;------------------------------------------------------------------------------
; Wortbirne Flag_visible_inline, "eint?"
;;------------------------------------------------------------------------------
; decd r4
; bit #GIE, sr
; bit #z, sr
; subc @r4, @r4
; t
coldreset:
clr &wdtctl ; Reset with cold start
;------------------------------------------------------------------------------
null_handler: ; Catches unwired interrupts
;------------------------------------------------------------------------------
reti
;------------------------------------------------------------------------------
do_lpm0:
bis #LPM0|GIE, sr
ret
;------------------------------------------------------------------------------
do_lpm1:
bis #LPM1|GIE, sr
ret
;------------------------------------------------------------------------------
do_lpm2:
bis #LPM2|GIE, sr
ret
;------------------------------------------------------------------------------
do_lpm3:
bis #LPM3|GIE, sr
ret
;------------------------------------------------------------------------------
do_lpm4:
bis #LPM4|GIE, sr
ret
;------------------------------------------------------------------------------
wakeup:
; Return stack contents on entry: ( Return SR r7 PC )
; You can use this directly as interrupt handler.
bic #lpm4, 4(sp)
ret

59
work/core.asm

@ -0,0 +1,59 @@
;------------------------------------------------------------------------------
; core: some basic functions
;------------------------------------------------------------------------------
;
; based off of Matthias Koch's mecrisp code (GPL3)
;
; (assumes 'ds' register alias)
; ----------------------------------------------------------
; core: RAM allocations
rampointer set RamBegin ; Beginning of Ram
; Creating labels for variables and buffers in RAM
ramallot macro Name, Size
Name equ rampointer
rampointer set rampointer + Size
endm
; allocate a scratch buffer for testing
buffer_len equ 32
ramallot buffer, buffer_len
; allocate datastack (ideally with uncritical data around it...)
datastack_length equ 64 ; Size of the datastack, has to be even
ramallot datastack_end, datastack_length
datastack_begin equ datastack_end + datastack_length
; allocate return stack
returnstack_length equ 64 ; Size of the return stack, has to be even
ramallot returnstack_end, returnstack_length
returnstack_begin equ returnstack_end + returnstack_length
; ----------------------------------------------------------
; core: Data stack
popda macro dst ; Fetch from datastack - 2 Bytes
mov @ds+, dst ; Increment stackpointer by two after fetching content.
endm
pushda macro src ; Put something on datastack - 8 Bytes
decd ds ; Adjust stack pointer
mov src, @ds ; and put value in.
endm
; ----------------------------------------------------------
; core: Macros
; Sized strings
static_string macro Text ; Text should not have parentheses
.word STRLEN(Text) ; word b/c alignment - asl freaks when .byte and escape in Text
.byte Text
align 2
endm

181
work/i2c.asm

@ -0,0 +1,181 @@
;------------------------------------------------------------------------------
; Software I2C driver
;------------------------------------------------------------------------------
;
; based on https://calcium3000.wordpress.com/2016/08/19/i2c-bit-banging-tutorial-part-i/
;
; the driver assumes that there are pull-up resistors on both SDA and SCL
; (as clearing a line is done by making it an input)
; clock stretching is allowed; but possible to save some bytes (~50?) when disabled
; (you'll need to comment out *_stretch parts below)
; SCL:P2.4 SDA:P2.5
SCL equ 00010000b
SDA equ 00100000b
I2C_PSEL equ P2SEL
I2C_P2SEL equ P2SEL2
I2C_INPORT equ P2IN
I2C_PDIR equ P2DIR
I2C_POUT equ P2OUT
; "half i2c cycle" (configure i2c bus speed here)
; for 400kHz (fast i2c) on 8MHz MCU clock it should be 10 cycles
; so 100kHz (normal speed) should be 40
; use the nop instead of a loop to really get 10 cycles
i2c_delay_loop: ; call is 5 cycles
mov #8, t1 ; 2 cycles
- dec t1 ; 2 cycles
jnz - ; 2 cycles
; nop ; 1 cycle
; nop ; 1 cycle
ret ; 3 cycles
;------------------------------------------------------------------------------
; i2c: Sample usage
; assume we have a standard I2C EEPROM on P2.4(scl) and P2.5(sda)
; address #50h
; call #i2c_init
; i2c_delay
; mov.b #(50h << 1), a0 ; address (write)
; call #i2c_start ; start condition on I2C bus
; call #i2c_write_byte ; send address
; jc + ; got NACK (no response), finish
; mov.b #01, a0 ; byte we're sending (eep address)
; call #i2c_write_byte
; i2c_delay
; mov.b #(50h << 1)|1, a0 ; address (read)
; call #i2c_start ; re-start
; call #i2c_write_byte ; send address
; jc + ; got NACK (no response), finish
; mov.b #0, a0 ; send ACK after reading a byte
; call #i2c_read_byte ; read into a0
; mov.b #1, a0 ; send NACK after reading last byte
; call #i2c_read_byte
; + call #i2c_stop
; ; the second read byte is now in a0
;------------------------------------------------------------------------------
; i2c: Implementation
; registers:
; t0 used for bit loops
; t1 used for delay loop
; a0 used for data
; a1 used for n/ack
scl_high macro
bic.b #SCL, &I2C_PDIR
endm
scl_low macro ; needs to preserve carry
bis.b #SCL, &I2C_PDIR
endm
scl_high_stretch:
scl_high ; needs to make SCL input
- bit.b #SCL, &I2C_INPORT
jz - ; wait for stretching to finish
ret
sda_high macro
bic.b #SDA, &I2C_PDIR
endm
sda_low macro
bis.b #SDA, &I2C_PDIR
endm
i2c_delay macro
call #i2c_delay_loop
endm
i2c_init:
bic.b #SDA|SCL, &I2C_PSEL
bic.b #SDA|SCL, &I2C_P2SEL
bic.b #SDA|SCL, &I2C_POUT
scl_high
sda_high
ret
i2c_start:
scl_high
sda_high
i2c_delay
sda_low
i2c_delay
scl_low
i2c_delay
ret
i2c_stop:
sda_low
i2c_delay
scl_high
i2c_delay
sda_high
i2c_delay
ret
; bit in carry
i2c_write_bit:
jc +
sda_low
jmp ++
+ sda_high
+ i2c_delay
scl_high
i2c_delay
scl_low
ret
; rotates I2C bit into LSB of a0
i2c_read_bit:
sda_high
i2c_delay
scl_high
i2c_delay
bit #SDA, &I2C_INPORT
rlc.b a0 ; (bit7 will end up in carry)
scl_low ; (this needs to preserve carry)
ret ; (b/c want to keep carry for i2c_read_byte)
; rotates I2C bit into LSB of a0 (stretching allowed)
i2c_read_bit_stretch:
sda_high
i2c_delay
call #scl_high_stretch
i2c_delay
bit #SDA, &I2C_INPORT
rlc.b a0
scl_low
ret
; a0: byte to send
; returns NACK in carry (C=NACK)
i2c_write_byte:
mov #8, t0
- rlc.b a0 ; shift MSbit to carry
call #i2c_write_bit
dec t0
jnz -
; call #i2c_read_bit
call #i2c_read_bit_stretch
rrc.b a0 ; move NACK to carry
ret
; a0: LSbit is NACK (1=NACK)
; returns byte in a0
i2c_read_byte:
mov #8, t0
- dec t0
jz +
call #i2c_read_bit
jmp -
;+ call #i2c_read_bit ; NACK is now in carry
+ call #i2c_read_bit_stretch ; NACK is now in carry
call #i2c_write_bit
ret

145
work/main.asm

@ -0,0 +1,145 @@
;
; for MSP430G2553
;
; Based off of Matthias Koch's mecrisp code
;
;------------------------------------------------------------------------------
; Base Definitions
;------------------------------------------------------------------------------
cpu msp430
include "../common/registers.asm"
include "../common/regconvention.asm"
;------------------------------------------------------------------------------
; Memory map
;------------------------------------------------------------------------------
RamBegin equ 200h ; Start of RAM
RamEnd equ 400h ; End of RAM, 512 Bytes
FlashBegin equ 0C000h ; Start of Flash, 16 kb, Flash end always is $FFFF.
ProgBegin equ 0F000h ; Start of code
org ProgBegin ; ... let the assembler know where to place the code
;------------------------------------------------------------------------------
; main: Load up functions
;------------------------------------------------------------------------------
include "core.asm"
include "timers.asm"
include "uart_lp.asm"
include "write.asm"
include "i2c.asm"
include "spi.asm"
include "rfm69-tx.asm"
;------------------------------------------------------------------------------
; main: Static data
;------------------------------------------------------------------------------
;------------------------------------------------------------------------------
Reset: ; main: entry point
;------------------------------------------------------------------------------
mov #returnstack_begin, sp ; Initialise return stack pointer
mov.w #WDTPW+WDTHOLD, &WDTCTL ; Watchdog off
mov #datastack_begin, ds ; Initialise return stack pointer
; Initialize hardware
; 8 MHz ! Take care: If you change clock, you have to change Flash clock divider, too !
; Current divider 19+1 --> 400kHz @ 8 MHz. Has to be in range 257 kHz - 476 kHz.
mov.b &CALBC1_8MHZ, &BCSCTL1 ; Set DCO
mov.b &CALDCO_8MHZ, &DCOCTL ; to 8 MHz.
clr.b &IE1 ; Clear interrupt flags of oscillator fault, NMR and flash violation.
mov.w #FWKEY, &FCTL1 ; Lock flash against writing
mov.w #FWKEY|FSSEL_1|19, &FCTL2 ; MCLK/20 for Flash Timing Generator
;------------------------------------------------------------------------------
; init subsystems
;------------------------------------------------------------------------------
call #uart_init
call #spi_init
;------------------------------------------------------------------------------
eint
;------------------------------------------------------------------------------
; this is where we dance
;------------------------------------------------------------------------------
puts "Welcome!\n\r"
bis.b #00000001b, &P1DIR ; make P1.0 output
- bic.b #00000001b, &P1OUT ; clear P1.0 (red off)
call #uart_recv ; wait for a byte, comes in a0
call #uart_emit ; ... and send it back
bis.b #00000001b, &P1OUT ; set P1.0 (red on)
call #uart_recv ; wait for a byte, comes in a0
call #uart_emit ; ... and send it back
jmp - ; loop
;------------------------------------------------------------------------------
;------------------------------------------------------------------------------
coldreset:
clr &WDTCTL ; Reset with cold start
;------------------------------------------------------------------------------
; Interrupt vectors
;------------------------------------------------------------------------------
;------------------------------------------------------------------------------
null_handler: ; Catches unwired interrupts
reti
;------------------------------------------------------------------------------
; choose the isr functions
irq_vector_port1 equ null_handler
irq_vector_port2 equ null_handler
irq_vector_adc equ null_handler
irq_vector_tx equ wake_tx ; used for uart wait in lpm
irq_vector_rx equ wake_rx ; used for uart wait in lpm
irq_vector_timera1 equ null_handler
irq_vector_timera0 equ null_handler
irq_vector_watchdog equ null_handler
irq_vector_comp equ null_handler
irq_vector_timerb1 equ null_handler
irq_vector_timerb0 equ wake_timer ; used for timed sleep
;------------------------------------------------------------------------------
org 0FFE0h ; Interrupt table
.word null_handler ; 01: 0FFE0 Unused
.word null_handler ; 02: 0FFE2 Unused
.word irq_vector_port1 ; 03: 0FFE4 Port 1
.word irq_vector_port2 ; 04: 0FFE6 Port 2
.word null_handler ; 05: 0FFE8 Unused
.word irq_vector_adc ; 06: 0FFEA ADC10
.word irq_vector_tx ; 07: 0FFEC USCI Transmit - Terminal
.word irq_vector_rx ; 08: 0FFEE USCI Receive - interrupts
.word irq_vector_timera1 ; 09: 0FFF0 Timer A
.word irq_vector_timera0 ; 10: 0FFF2 Timer A
.word irq_vector_watchdog ; 11: 0FFF4 Watchdog
.word irq_vector_comp ; 12: 0FFF6 Comparator
.word irq_vector_timerb1 ; 13: 0FFF8 Timer B
.word irq_vector_timerb0 ; 14: 0FFFA Timer B
.word null_handler ; 15: 0FFFC NMI. Unused.
.word Reset ; 16: 0FFFE Main entry point
end

316
work/rf69.h

@ -0,0 +1,316 @@
// Native mode RF69 driver.
#ifndef chThdYield
#define chThdYield() // FIXME should be renamed, ChibiOS leftover
#endif
template< typename SPI >
class RF69 {
public:
void init (uint8_t id, uint8_t group, int freq);
void encrypt (const char* key);
void txPower (uint8_t level);
void setHighPower (uint8_t onOff);
int receive (void* ptr, int len);
void send (uint8_t header, const void* ptr, int len);
void sleep ();
void standby ();
int16_t afc;
uint8_t rssi;
uint8_t lna;
uint8_t myId;
uint8_t parity;
uint8_t readReg (uint8_t addr) {
return spi.rwReg(addr, 0);
}
void writeReg (uint8_t addr, uint8_t val) {
spi.rwReg(addr | 0x80, val);
}
protected:
enum {
REG_FIFO = 0x00,
REG_OPMODE = 0x01,
REG_FRFMSB = 0x07,
REG_PALEVEL = 0x11,
REG_OCP = 0x13,
REG_LNAVALUE = 0x18,
REG_AFCMSB = 0x1F,
REG_AFCLSB = 0x20,
REG_FEIMSB = 0x21,
REG_FEILSB = 0x22,
REG_RSSIVALUE = 0x24,
REG_IRQFLAGS1 = 0x27,
REG_IRQFLAGS2 = 0x28,
REG_SYNCVALUE1 = 0x2F,
REG_SYNCVALUE2 = 0x30,
REG_SYNCVALUE3 = 0x31,
REG_NODEADDR = 0x39,
REG_BCASTADDR = 0x3A,
REG_FIFOTHRESH = 0x3C,
REG_PKTCONFIG2 = 0x3D,
REG_AESKEYMSB = 0x3E,
REG_TESTPA1 = 0x5A, // only present on RFM69HW/SX1231H
REG_TESTPA2 = 0x5C, // only present on RFM69HW/SX1231H
MODE_SLEEP = 0<<2,
MODE_STANDBY = 1<<2,
MODE_TRANSMIT = 3<<2,
MODE_RECEIVE = 4<<2,
START_TX = 0xC2,
STOP_TX = 0x42,
RCCALSTART = 0x80,
IRQ1_MODEREADY = 1<<7,
IRQ1_RXREADY = 1<<6,
IRQ1_SYNADDRMATCH = 1<<0,
IRQ2_FIFONOTEMPTY = 1<<6,
IRQ2_PACKETSENT = 1<<3,
IRQ2_PAYLOADREADY = 1<<2,
};
void setMode (uint8_t newMode);
void configure (const uint8_t* p);
void setFrequency (uint32_t freq);
void setHighPowerRegs (uint8_t onOff);
SPI spi;
volatile uint8_t mode;
uint8_t isRFM69HW;
};
// driver implementation
template< typename SPI >
void RF69<SPI>::setMode (uint8_t newMode) {
mode = newMode;
if (isRFM69HW) {
if ( newMode == MODE_TRANSMIT ) {
setHighPowerRegs(1);
} else {
setHighPowerRegs(0);
}
}
writeReg(REG_OPMODE, (readReg(REG_OPMODE) & 0xE3) | newMode);
while ((readReg(REG_IRQFLAGS1) & IRQ1_MODEREADY) == 0)
;
}
template< typename SPI >
void RF69<SPI>::setFrequency (uint32_t hz) {
// accept any frequency scale as input, including KHz and MHz
// multiply by 10 until freq >= 100 MHz (don't specify 0 as input!)
while (hz < 100000000)
hz *= 10;
// Frequency steps are in units of (32,000,000 >> 19) = 61.03515625 Hz
// use multiples of 64 to avoid multi-precision arithmetic, i.e. 3906.25 Hz
// due to this, the lower 6 bits of the calculated factor will always be 0
// this is still 4 ppm, i.e. well below the radio's 32 MHz crystal accuracy
// 868.0 MHz = 0xD90000, 868.3 MHz = 0xD91300, 915.0 MHz = 0xE4C000
uint32_t frf = (hz << 2) / (32000000L >> 11);
writeReg(REG_FRFMSB, frf >> 10);
writeReg(REG_FRFMSB+1, frf >> 2);
writeReg(REG_FRFMSB+2, frf << 6);
}
template< typename SPI >
void RF69<SPI>::configure (const uint8_t* p) {
while (true) {
uint8_t cmd = p[0];
if (cmd == 0)
break;
writeReg(cmd, p[1]);
p += 2;
}
}
static const uint8_t configRegs [] = {
// POR value is better for first rf_sleep 0x01, 0x00, // OpMode = sleep
0x02, 0x00, // DataModul = packet mode, fsk
0x03, 0x02, // BitRateMsb, data rate = 49,261 khz
0x04, 0x8A, // BitRateLsb, divider = 32 MHz / 650
0x05, 0x02, // FdevMsb = 45 KHz
0x06, 0xE1, // FdevLsb = 45 KHz
0x0B, 0x20, // Low M
0x19, 0x4A, // RxBw 100 KHz
0x1A, 0x42, // AfcBw 125 KHz
0x1E, 0x0C, // AfcAutoclearOn, AfcAutoOn
//0x25, 0x40, //0x80, // DioMapping1 = SyncAddress (Rx)
0x26, 0x07, // disable clkout
0x29, 0xA0, // RssiThresh -80 dB
// 0x2B, 0x40, // RSSI timeout after 128 bytes
0x2D, 0x05, // PreambleSize = 5
0x2E, 0x90, // SyncConfig = sync on, sync size = 3
0x2F, 0xAA, // SyncValue1 = 0xAA
0x30, 0x2D, // SyncValue2 = 0x2D ! this is used for group in old jee protocol, but jz4 uses 0x2d hardcoded
0x31, 0x2A, // network group, default 42
0x37, 0xD0, // PacketConfig1 = fixed, white, no filtering
0x38, 0x42, // PayloadLength = 0, unlimited
0x3C, 0x8F, // FifoTresh, not empty, level 15
0x3D, 0x12, // 0x10, // PacketConfig2, interpkt = 1, autorxrestart off
0x6F, 0x20, // TestDagc ...
0x71, 0x02, // RegTestAfc
0
};
template< typename SPI >
void RF69<SPI>::init (uint8_t id, uint8_t group, int freq) {
myId = id;
// b7 = group b7^b5^b3^b1, b6 = group b6^b4^b2^b0
parity = group ^ (group << 4);
parity = (parity ^ (parity << 2)) & 0xC0;
// 10 MHz, i.e. 30 MHz / 3 (or 4 MHz if clock is still at 12 MHz)
spi.master(3);
do
writeReg(REG_SYNCVALUE1, 0xAA);
while (readReg(REG_SYNCVALUE1) != 0xAA);
do
writeReg(REG_SYNCVALUE1, 0x55);
while (readReg(REG_SYNCVALUE1) != 0x55);
configure(configRegs);
configure(configRegs); // TODO why is this needed ???
setFrequency(freq);
writeReg(REG_SYNCVALUE3, group);
isRFM69HW = 0;
}
template< typename SPI >
void RF69<SPI>::encrypt (const char* key) {
uint8_t cfg = readReg(REG_PKTCONFIG2) & ~0x01;
if (key) {
for (int i = 0; i < 16; ++i) {
writeReg(REG_AESKEYMSB + i, *key);
if (*key != 0)
++key;
}
cfg |= 0x01;
}
writeReg(REG_PKTCONFIG2, cfg);
}
template< typename SPI >
void RF69<SPI>::txPower (uint8_t level) {
writeReg(REG_PALEVEL, (readReg(REG_PALEVEL) & ~0x1F) | level);
}
template< typename SPI >
void RF69<SPI>::setHighPower (uint8_t onOff) { // must call this with RFM69HW!
isRFM69HW = onOff;
writeReg(REG_OCP, isRFM69HW ? 0x0F : 0x1A);
if (isRFM69HW) // turning ON
writeReg(REG_PALEVEL, (readReg(REG_PALEVEL) & 0x1F) | 0x40 | 0x20); // enable P1 & P2 amplifier stages
else
writeReg(REG_PALEVEL, (readReg(REG_PALEVEL) & 0x1F) | 0x80 ); // enable P0 only
}
template< typename SPI >
void RF69<SPI>::setHighPowerRegs (uint8_t onOff) {
writeReg(REG_TESTPA1, onOff ? 0x5D : 0x55);
writeReg(REG_TESTPA2, onOff ? 0x7C : 0x70);
}
template< typename SPI >
void RF69<SPI>::sleep () {
setMode(MODE_SLEEP);
}
template< typename SPI >
void RF69<SPI>::standby () {
setMode(MODE_STANDBY);
}
template< typename SPI >
int RF69<SPI>::receive (void* ptr, int len) {
if (mode != MODE_RECEIVE)
setMode(MODE_RECEIVE);
else {
static uint8_t lastFlag;
if ((readReg(REG_IRQFLAGS1) & IRQ1_RXREADY) != lastFlag) {
lastFlag ^= IRQ1_RXREADY;
if (lastFlag) { // flag just went from 0 to 1
rssi = readReg(REG_RSSIVALUE);
lna = (readReg(REG_LNAVALUE) >> 3) & 0x7;
#if RF69_SPI_BULK
spi.enable();
spi.transfer(REG_AFCMSB);
afc = spi.transfer(0) << 8;
afc |= spi.transfer(0);
spi.disable();
#else
afc = readReg(REG_AFCMSB) << 8;
afc |= readReg(REG_AFCLSB);
#endif
}
}
if (readReg(REG_IRQFLAGS2) & IRQ2_PAYLOADREADY) {
#if RF69_SPI_BULK
spi.enable();
spi.transfer(REG_FIFO);
int count = spi.transfer(0);
for (int i = 0; i < count; ++i) {
uint8_t v = spi.transfer(0);
if (i < len)
((uint8_t*) ptr)[i] = v;
}
spi.disable();
#else
int count = readReg(REG_FIFO);
for (int i = 0; i < count; ++i) {
uint8_t v = readReg(REG_FIFO);
if (i < len)
((uint8_t*) ptr)[i] = v;
}
#endif
// only accept packets intended for us, or broadcasts
// ... or any packet if we're the special catch-all node
uint8_t dest = *(uint8_t*) ptr;
if ((dest & 0xC0) == parity) {
uint8_t destId = dest & 0x3F;
if (destId == myId || destId == 0 || myId == 63)
return count;
}
}
}
return -1;
}
template< typename SPI >
void RF69<SPI>::send (uint8_t header, const void* ptr, int len) {
setMode(MODE_STANDBY);
#if RF69_SPI_BULK
spi.enable();
spi.transfer(REG_FIFO | 0x80);
spi.transfer(len + 2);
spi.transfer((header & 0x3F) | parity);
spi.transfer((header & 0xC0) | myId);
for (int i = 0; i < len; ++i)
spi.transfer(((const uint8_t*) ptr)[i]);
spi.disable();
#else
writeReg(REG_FIFO, len + 2);
writeReg(REG_FIFO, (header & 0x3F) | parity);
writeReg(REG_FIFO, (header & 0xC0) | myId);
for (int i = 0; i < len; ++i)
writeReg(REG_FIFO, ((const uint8_t*) ptr)[i]);
#endif
setMode(MODE_TRANSMIT);
while ((readReg(REG_IRQFLAGS2) & IRQ2_PACKETSENT) == 0)
chThdYield();
setMode(MODE_STANDBY);
}

306
work/rfm69-tx.asm

@ -0,0 +1,306 @@
;------------------------------------------------------------------------------
; RFM69 driver (TX and init part)
;------------------------------------------------------------------------------
;
; settings compatible with JCW's "new" jeenode zero
; used in my sensors: https://git.drak.xyz/flabbergast/jee-sensors
; (anyway, adjust to your setup)
;
; (assumes spi.asm)
;
;------------------------------------------------------------------------------
; rfm69-tx: "variables"
rfm69_id equ 13
rfm69_group equ 109
rfm69_parity equ ((rfm69_group!(rfm69_group<<4)) ! ((rfm69_group!(rfm69_group<<4)) << 2)) & 0C0h
; b7 = group b7^b5^b3^b1, b6 = group b6^b4^b2^b0
; parity = group XOR (group << 4);
; parity = (parity XOR (parity << 2)) AND 0xC0;
rfm69_freq1 equ 0D9h
rfm69_freq2 equ 026h
rfm69_freq3 equ 040h
; Frequency steps are in units of (32,000,000 >> 19) = 61.03515625 Hz
; use multiples of 64 to avoid multi-precision arithmetic, i.e. 3906.25 Hz
; due to this, the lower 6 bits of the calculated factor will always be 0
; this is still 4 ppm, i.e. well below the radio's 32 MHz crystal accuracy
; 868.0 MHz = 0xD90000, 868.3 MHz = 0xD91300, 915.0 MHz = 0xE4C000
; 868.6 MHz = 0xD92640
; uint32_t frf = (hz << 2) / (32000000L >> 11);
; then the bytes are: frf >> 10, frf >> 2, frf << 6
rfm69_key equ "beleampanchineto" ; 16 bytes
;------------------------------------------------------------------------------
; rfm69-tx: registers and flags
REG_FIFO equ 000h
REG_OPMODE equ 001h
REG_FRFMSB equ 007h
REG_PALEVEL equ 011h
REG_OCP equ 013h
REG_LNAVALUE equ 018h
REG_AFCMSB equ 01Fh
REG_AFCLSB equ 020h
REG_FEIMSB equ 021h
REG_FEILSB equ 022h
REG_RSSIVALUE equ 024h
REG_IRQFLAGS1 equ 027h
REG_IRQFLAGS2 equ 028h
REG_SYNCVALUE1 equ 02Fh
REG_SYNCVALUE2 equ 030h
REG_SYNCVALUE3 equ 031h
REG_NODEADDR equ 039h
REG_BCASTADDR equ 03Ah
REG_FIFOTHRESH equ 03Ch
REG_PKTCONFIG2 equ 03Dh
REG_AESKEYMSB equ 03Eh
REG_TESTPA2 equ 05Ch ; only present on RFM69HW/SX1231H
REG_TESTPA1 equ 05Ah ; only present on RFM69HW/SX1231H
MODE_SLEEP equ 0<<2
MODE_STANDBY equ 1<<2
MODE_TRANSMIT equ 3<<2
MODE_RECEIVE equ 4<<2
START_TX equ 0C2h
STOP_TX equ 042h
RCCALSTART equ 080h
IRQ1_MODEREADY equ 1<<7
IRQ1_RXREADY equ 1<<6
IRQ1_SYNADDRMATCH equ 1<<0
IRQ2_FIFONOTEMPTY equ 1<<6
IRQ2_PACKETSENT equ 1<<3
IRQ2_PAYLOADREADY equ 1<<2
;------------------------------------------------------------------------------
; rfm69-tx: implementation
; rfm69 initial configuration
align 2
rfm69_config_regs_data:
; POR value is better for first rf_sleep 0x01, 0x00, // OpMode = sleep
.byte 002h, 000h ; DataModul = packet mode, fsk
.byte 003h, 002h ; BitRateMsb, data rate = 49,261 khz
.byte 004h, 08Ah ; BitRateLsb, divider = 32 MHz / 650
.byte 005h, 002h ; FdevMsb = 45 KHz
.byte 006h, 0E1h ; FdevLsb = 45 KHz
.byte 00Bh, 020h ; Low M
.byte 019h, 04Ah ; RxBw 100 KHz
.byte 01Ah, 042h ; AfcBw 125 KHz
.byte 01Eh, 00Ch ; AfcAutoclearOn, AfcAutoOn
;.byte 025h, 040h ; 0x80, // DioMapping1 = SyncAddress (Rx)
.byte 026h, 007h ; disable clkout
.byte 029h, 0A0h ; RssiThresh -80 dB
;.byte 02Bh, 040h ; RSSI timeout after 128 bytes
.byte 02Dh, 005h ; PreambleSize = 5
.byte 02Eh, 090h ; SyncConfig = sync on, sync size = 3
.byte 02Fh, 0AAh ; SyncValue1 = 0xAA
.byte 030h, 02Dh ; SyncValue2 = 0x2D ! this is used for group in old jee protocol, but jz4 uses 0x2d hardcoded
.byte 031h, 02Ah ; network group, default 42
.byte 037h, 0D0h ; PacketConfig1 = fixed, white, no filtering
.byte 038h, 042h ; PayloadLength = 0, unlimited
.byte 03Ch, 08Fh ; FifoTresh, not empty, level 15
.byte 03Dh, 012h ; 0x10, // PacketConfig2, interpkt = 1, autorxrestart off
.byte 06Fh, 020h ; TestDagc ...
.byte 071h, 002h ; RegTestAfc
; these are ess "runtime" things, but since we hardcode...
.byte REG_FRFMSB, rfm69_freq1
.byte REG_FRFMSB+1, rfm69_freq2
.byte REG_FRFMSB+2, rfm69_freq3
.byte REG_SYNCVALUE3, rfm69_group
cnt set 0 ; expand the AES key to the appropriate registers
while cnt<16
.byte REG_AESKEYMSB+cnt, charfromstr(rfm69_key,cnt)
cnt set cnt+1
endm
rfm69_config_regs_data_end:
; read a rfm69 register
; a0: register (byte)
; returns a byte in a0
rfm69_readreg:
spi_select
spi_write_byte
spi_read_byte
spi_deselect
ret
; write into a rfm69 registar
; a0: register (byte)
; a1: value (byte)
rfm69_writereg:
add #080h, a0
spi_select
spi_write_byte
mov a1, a0
spi_write_byte
spi_deselect
ret
; send all configuration registers
; clobbers: a0
rfm69_config_regs:
push s0
mov #rfm69_config_regs_data, s0 ; load config data address
- mov.b @s0+, a0 ; read register
mov.b @s0+, a1 ; read value
call #rfm69_writereg ; write
cmp #rfm69_config_regs_data_end, s0 ; are we done?
jnz -
pop s0
ret
; initialise
; (assuming spi is initialised already)
rfm69_init:
- mov #REG_SYNCVALUE1, a0
mov #0AAh, a1
call #rfm69_writereg
mov #REG_SYNCVALUE1, a0
call #rfm69_readreg
cmp #0AAh, a0 ; keep writing 0AAh into REG_SYNCVALUE1
jnz - ; until it takes
- mov #REG_SYNCVALUE1, a0
mov #055h, a1
call #rfm69_writereg
mov #REG_SYNCVALUE1, a0
call #rfm69_readreg
cmp #055h, a0 ; keep writing 055h into REG_SYNCVALUE1
jnz - ; until it takes
call #rfm69_config_regs
call #rfm69_config_regs ; why? even jcw didn't know
; TODO: HW flag?
ret
; update registar
; a0: register
; a1: new value
; a2: mask
; will do: @a0 = (@a0)&mask + a1
rfm69_updatereg:
push a0 ; save the register (TODO: use a3?)
call #rfm69_readreg ; read the register
bic a2, a0 ; clear the mask
add a0, a1 ; set the new value, into a1
pop a0 ; get the register again
call #rfm69_writereg ; write it back
ret
; turn encryption on/off
; a1 (!): either 0 (disable) or 1 (enable)
; clobbers: a0, a2
rfm69_encrypt:
mov #1, a2 ; mask
mov #REG_PKTCONFIG2, a0 ; register
jmp rfm69_updatereg ; continue with updating the register
; set tx power
; a1 (!): power (0-31)
; clobbers: a0, a2
rfm69_txpower:
mov #01Fh, a2 ; mask
mov #REG_PALEVEL, a0 ; register
jmp rfm69_updatereg ; continue with updating the register
; set mode
; a1 (!): mode (MODE_*)
; clobbers: a0, a2
rfm69_setmode:
; TODO: if rfm69hw:
; if MODE_TRANSMIT then setHighPowerRegs(1)
; else setHighPowerRegs(0)
mov #0E3h, a2 ; mask
mov #REG_OPMODE, a0 ; register
call #rfm69_updatereg ; update the register
- mov #REG_IRQFLAGS1, a0
call #rfm69_readreg
bit #IRQ1_MODEREADY, a0 ; wait until we see this bit set
jz -
ret
rfm69_sleep macro
mov #MODE_SLEEP, a1
call #rfm69_setmode
endm
rfm69_standby macro
mov #MODE_STANDBY, a1
call #rfm69_setmode
endm
rfm69_transmit macro
mov #MODE_TRANSMIT, a1
call #rfm69_setmode
endm
; send a packet
; a0: header (byte)
; a1: pointer to data
; a2: length (up to 64)
rfm69_send:
push a2 ; prepare things on stack
push a1 ; a1, because rfm69_standby clobbers
push a0 ; save the header
push a2 ; a2, because rfm69_standby clobbers
rfm69_standby
mov #REG_FIFO|080h, a0 ; we'll be writing to FIFO
spi_select
spi_write_byte
pop a0 ; next up, length+2
incd a0
spi_write_byte
pop a0 ; next, header with parity
mov a0, a1 ; save to already clobbered register
bic 0C0h, a0
add #rfm69_parity, a0
spi_write_byte
mov a1, a0 ; recover parity
bic 03Fh, a0 ; next is our id
add #rfm69_id, a0
spi_write_byte
pop a1 ; recover the data pointer
pop a2 ; recover the length
add a1, a2 ; final address: we'll use it for looping
- cmp a1, a2 ; "while" loop, allowing length=0
jz +
mov @a1+, a0 ; get next byte; inc pointer
spi_write_byte
jmp - ; loop
+ spi_deselect
rfm69_transmit ; send!
- mov #REG_IRQFLAGS2, a0
call #rfm69_readreg
bit #IRQ2_PACKETSENT, a0 ; wait until we see this bit set
jz -
rfm69_standby
ret
;------------------------------------------------------------------------------
; rfm69-tx: attic
;
; setting high power, original in C
; did not work for me really, locked up the radio, so need to verify this
;
; void RF69<SPI>::setHighPower (uint8_t onOff) { // must call this with RFM69HW!
; isRFM69HW = onOff;
; writeReg(REG_OCP, isRFM69HW ? 0x0F : 0x1A);
; if (isRFM69HW) // turning ON
; writeReg(REG_PALEVEL, (readReg(REG_PALEVEL) & 0x1F) | 0x40 | 0x20); // enable P1 & P2 amplifier stages
; else
; writeReg(REG_PALEVEL, (readReg(REG_PALEVEL) & 0x1F) | 0x80 ); // enable P0 only
; }
;
; void RF69<SPI>::setHighPowerRegs (uint8_t onOff) {
; writeReg(REG_TESTPA1, onOff ? 0x5D : 0x55);
; writeReg(REG_TESTPA2, onOff ? 0x7C : 0x70);
; }

73
work/spi.asm

@ -0,0 +1,73 @@
;------------------------------------------------------------------------------
; Hardware SPI driver (using USCI_B0)
;------------------------------------------------------------------------------
;
; no interrupts, just busy waits
;
; USCI_B0 pins: P1.5 SCK, P1.6 MISO, P1.7 MOSI
; chip select (CS) is arbitrary, let's use P2.1
SPI_PIN equ 00000010b
SPI_DIR equ P2DIR
SPI_OUT equ P2OUT
SPI_SEL equ P1SEL
SPI_SEL2 equ P1SEL2
SPI_SELBITS equ 11100000b
;------------------------------------------------------------------------------
; spi: Implementation
; registers:
; t0 used for bit loops
; t1 used for delay loop
; a0 used for data
; a1 used for n/ack
spi_deselect macro
bis.b #SPI_PIN, &SPI_OUT
endm
spi_select macro
bic.b #SPI_PIN, &SPI_OUT
endm
; a0: byte to send
; returns the incoming byte in a0
_spi_rw_byte:
- bit.b #UCB0TXIFG, &IFG2 ; txbuf ready?
jz -
mov.b a0, &UCB0TXBUF ; send byte
- bit.b #UCB0RXIFG, &IFG2 ; rxbuf ready?
jz -
mov.b &UCB0RXBUF, a0 ; read byte
ret
; macro alias for the above function
spi_rw_byte macro
call #_spi_rw_byte
endm
; returns byte in a0
spi_read_byte macro
mov #0, a0
call #_spi_rw_byte
endm
; a0: byte to send
spi_write_byte macro
call #_spi_rw_byte
endm
spi_init:
spi_deselect ; keepin' it high
bis.b #SPI_PIN, &SPI_DIR ; CS is output
mov.b #UCSWRST, &UCB0CTL1 ; put USCI_B0 in reset
bis.b #SPI_SELBITS, &SPI_SEL ; SCK|MISO|MOSI pins
bis.b #SPI_SELBITS, &SPI_SEL2 ; SCK|MISO|MOSI pins
mov.b #UCCKPH|UCMSB|UCMST|UCSYNC, &UCB0CTL0
; 3-pin 8-bit SPI master
bis.b #UCSSEL_3, &UCB0CTL1 ; clock source SMCLK
mov.b #8, &UCB0BR0 ;
mov.b #0, &UCB0BR1 ; prescaler /8
bic.b #UCSWRST, &UCB0CTL1 ; init USCI
ret

168
work/spiflash.asm

@ -0,0 +1,168 @@
;------------------------------------------------------------------------------
; spiflash: (A few) functions for accessing SPI flash
;------------------------------------------------------------------------------
;
; (assumes spi.asm)
;
;------------------------------------------------------------------------------
; spiflash: sample code
; ; read both status registers
; call #spiflash_read_status1 ; status1 in a0
; mov.b a0, s0 ; save
; call #spiflash_read_status2 ; status2 in a0
; swpb a0 ; move to upper
; add s0, a0 ; combine
; ; read bytes from flash into buffer
; mov #2, a0 ; lower part of flash address
; mov.b #0, a1 ; upper part of flash address
; mov #buffer, a2 ; where to write
; mov #buffer_len, a3 ; how many bytes
; call #spiflash_read_bytes
; ; write bytes from buffer to flash
; call #spiflash_enable_write ; by default it's disabled
; mov #0, a0 ; lower part of flash address
; mov.b #0, a1 ; upper part of flash address
; mov #buffer, a2 ; where to read from
; mov #buffer_len, a3 ; how many bytes
; call #spiflash_write_bytes
;------------------------------------------------------------------------------
; spiflash: (some) registers
SPIFLASH_REG_STATUS1 equ 005h
SPIFLASH_REG_STATUS2 equ 035h
SPIFLASH_MANUF_ID equ 090h
SPIFLASH_JEDEC_ID equ 09Fh
SPIFLASH_WRITE_ENABLE equ 006h
SPIFLASH_WRITE_DISABLE equ 004h
SPIFLASH_SECTOR_ERASE equ 020h ; 4kB
SPIFLASH_BLOCK_ERASE equ 052h ; 32kB
SPIFLASH_BLOCK_ERASE2 equ 0D8h ; 64kB
SPIFLASH_CHIP_ERASE equ 0C7h ; or 060h?
SPIFLASH_POWER_DOWN equ 0B9h
SPIFLASH_READ_DATA equ 003h
SPIFLASH_ENABLE_RESET equ 066h
SPIFLASH_RESET equ 099h
SPIFLASH_PAGE_PROGRAM equ 002h
SPIFLASH_STATUS1_BUSY equ (1<<0)
SPIFLASH_STATUS1_WEL equ (1<<1)
;------------------------------------------------------------------------------
; spiflash: implementation
; read status1/2 register from flash
; returns register value in a0
spiflash_read_status1:
mov.b #SPIFLASH_REG_STATUS1, a0
spiflash_read_single:
spi_select
spi_write_byte
spi_read_byte
spi_deselect
ret
spiflash_read_status2:
mov.b #SPIFLASH_REG_STATUS2, a0
jmp spiflash_read_single
; returns manufacturer ID in a1 (byte)
; and device ID in a0 (MSB memtype, LSB capacity)
; clobbers t0
spiflash_get_JEDEC_ID:
mov.b #SPIFLASH_JEDEC_ID, a0
spi_select
spi_write_byte
spi_read_byte
mov.b a0, a1
spi_read_byte
mov.b a0, t0
spi_read_byte
spi_deselect
swpb t0
add t0, a0
ret
; read a byte from flash
; expects flash address in a1.L_a0.H_a0.L (3 bytes)
; returns byte in a0
; spiflash_read_byte:
; mov sp, a2 ; find a space in RAM
; sub #4, a2 ; a little below the stack
; mov #1, a3 ; 1 byte
; call #spiflash_read_bytes
; mov.b -4(sp), a0 ; get the byte
; ret
; read a bunch of bytes from flash
; expects flash address in a1.L_a0.H_a0.L (3 bytes)
; expects memory address in a2
; expects number of bytes in a3 (>=1)
; clobbers t0
spiflash_read_bytes:
mov a0, t0 ; save lower part of flash address
mov.b #SPIFLASH_READ_DATA, a0
call #_spiflash_write_address
add a2, a3 ; compute the final mem address
- spi_read_byte ; reading loop starts
mov.b a0, 0(a2) ; store the read byte
inc a2 ; increment mem address
cmp a2, a3 ; are we there yet?
jnz - ; we are not, so do some more
spi_deselect
ret
; internal
; write instruction and address
; a0: instruction, t0(!): lower addr, a1: upper addr
_spiflash_write_address:
spi_select
spi_write_byte ; send command
mov.b a1, a0
spi_write_byte ; send uppermost flash address byte
mov t0, a0
swpb a0
spi_write_byte ; send middle flash address byte
mov.b t0, a0
spi_write_byte ; send LSB of flash address
ret
; enable writing to flash
; (WEL = bit1) in status1 should be checked afterwards
spiflash_enable_write:
mov.b #SPIFLASH_WRITE_ENABLE, a0
spiflash_write_instruction: ; write a single instruction
spi_select
spi_write_byte
spi_deselect
ret
; write a bunch of bytes to flash
; expects flash address in a1.L_a0.H_a0.L (3 bytes)
; expects memory address in a2
; expects number of bytes in a3
; clobbers t0
; note that WEN in status1 needs to be already set
; so call spiflash_enable_write before
; note this uses page write:
; - so up to 256 bytes
; - and should not go over page boundary (as it wraps then)
spiflash_write_bytes:
mov a0, t0 ; save lower part of flash address
mov.b #SPIFLASH_PAGE_PROGRAM, a0
call #_spiflash_write_address
add a2, a3 ; compute the final mem address
- mov.b @a2+, a0 ; get the byte to write; inc pointer
spi_write_byte ; write
cmp a2, a3 ; are we there yet?
jnz - ; we are not, so do some more
spi_deselect ; this triggers the write process
spiflash_busy_wait: ; wait for the flash to become not busy
- call #spiflash_read_status1 ; read status1 into a0
bit #SPIFLASH_STATUS1_BUSY, a0
jnz -
ret

21
work/timers.asm

@ -0,0 +1,21 @@
;------------------------------------------------------------------------------
; timers
;------------------------------------------------------------------------------
; assumes irq_vector_timerb0 is "wake_timer"
delayticks_lp: ; a0:ticks (on a 32768 Hz crystal)
push sr ; save GIE flag
mov.w #CCIE, &TBCCTL0 ; enable CC interrupt
clr &TBR ; clear the counter
mov.w a0, &TBCCR0 ; set the required delay
mov.w #TASSEL_1|MC_1, &TBCTL ; ACLK, up-mode
bis #LPM3|GIE, sr ; sleep in LPM3
clr &TBCTL ; stop timer
reti ; reti also restores SR from stack
;------------------------------------------------------------------------------
; timers: irq
wake_timer: ; waking up from sleep through an interrupt
bic #LPM4, 0(sp) ; clears all lpm flags
reti

81
work/uart_lp.asm

@ -0,0 +1,81 @@
;------------------------------------------------------------------------------
; uart_lp: basic uart using USCI_A0 on P1.1/P1.2 with LPM wait
;------------------------------------------------------------------------------
;
; based off of Matthias Koch's mecrisp code
;
; assumes "core.asm"
;
; (note that MSP430G2 FET can only do 9600 baud)
uart_init: ; Initialize 9600 baud on 8MHz clock
; Init pins
mov.b #06h, &P1SEL ; use P1.1/P1.2 for USCI_A0
mov.b #06h, &P1SEL2 ; use P1.1/P1.2 for USCI_A0
bis.b #UCSSEL_2,&UCA0CTL1 ; SMCLK as clock source
; UCBRx and UCBRSx from table 15-4 (p424) in SLAU144J
mov.b #65,&UCA0BR0 ; 8MHz 9600 baud --> 833 cycles/bit
mov.b #3,&UCA0BR1 ; 8MHz 9600 baud
mov.b #UCBRS_2,&UCA0MCTL ; modulation UCBRSx = 2
; mov.b #69,&UCA0BR0 ; 8MHz 115200 baud --> 69 cycles/bit
; mov.b #0,&UCA0BR1 ; 8MHz 115200 baud
; mov.b #UCBRS_4,&UCA0MCTL ; modulation UCBRSx = 4
bic.b #UCSWRST,&UCA0CTL1 ; **initialize USCI state machine**
ret
uart_emit: ; output the character in a0
mov #UCA0TXIFG, t0
call #_uart_sleeproutine ; sleep until available
mov.b a0, &UCA0TXBUF
ret
jq_uart_emit: ; branch to addr if ready to emit ( addr -> )
bit.b #UCA0TXIFG, &IFG2
jz +
mov a0, @sp ; change return address
+ ret
uart_recv: ; return incoming byte ( -> byte )
mov #UCA0RXIFG, t0
call #_uart_sleeproutine ; sleep until available
mov.b &UCA0RXBUF, a0
ret
jq_uart_recv: ; branch to add if incoming byte ( addr -> )
bit.b #UCA0RXIFG, &IFG2
jz +
mov a0, @sp ; change return address
+ ret
;------------------------------------------------------------------------------
; uart_lp: internals
_uart_sleeproutine: ; sleep (or loop) until tx||rx (selected in t0) happens
push sr
dint
nop
bis.b t0, &IE2 ; enable interrupt source for tx||rx
- bit.b t0, &IFG2 ; check if available
jnz +
bit #GIE, @sp ; have interrupts been enabled before?
jnc - ; if not, simply poll for a character
bis #LPM3|GIE, sr ; if yes, sleep and wait for interrupt
_uart_here_we_dream:
jmp -
+ bic.b t0, &IE2 ; disable interrupt source for tx||rx
nop
reti
;------------------------------------------------------------------------------
; uart_lp: irq
wake_tx:
wake_rx:
cmp #_uart_here_we_dream, 2(sp)
jne +
bic #LPM3|GIE, 0(sp)
+ reti

192
work/write.asm

@ -0,0 +1,192 @@
;
; write: basic string writing and manipulation
;
; assumes "core.asm" (data stack and macros)
; assumes uart_emit symbol
; sized string: w_size=n b_c1 b_c2 ... b_cn
; pointer where the "emit" routine address is stored
ramallot write_emit_routine_p, 2
; initialise with the default "emit"
write_init:
mov #uart_emit, &write_emit_routine_p
ret
; print a single hexadecimal digit
; expects:
; - a0: value to print (0-15)
_write_hexdigit:
add.b #'0', a0
cmp #':', a0 ; ':' = '9'+1
jl +
add.b #39, a0 ; get to lowercase alpha
+ mov &write_emit_routine_p, pc ; jump to "emit" (call/ret shortcut)
; print a value, hexadecimal, unsigned
; expects:
; - a0: value to print
; - a1: how many bits to print (multiple of 4)
; - a2: if non zero, pad with zeroes
; clobbers: t0
write_value_hex:
push s0 ; let's not clobber anything
mov a0, s0
add a2, s0 ; =0 iff printing 0 with no padding
jnz +
mov #4, a1 ; in that case, only print the last digit
+ cmp #4, a1 ; check if we're only printing the last digit
jnz +
mov #1, a2 ; if yes, we're printing it
+ mov a1, t0 ; t0: counter for
- cmp #16, t0 ; .. rotating the first nibble to print into
jeq + ; ... the highest nibble of a0
rla a0
inc t0 ; counting up to 16
jmp -
+ mov a0, s0 ; save the value we are printing
.loop ; main digit printing loop
mov #4, t0 ; rotate next nibble into a0
- rla s0 ; mini loop
rlc a0
dec t0
jnz -
and #0Fh, a0 ; got the next digit in a0 now
jnz .emit ; it's not zero => we're printing it
cmp #0, a2 ; check if we're requesting not to print zeroes
jz + ; yes, do not print
.emit
mov #1, a2 ; printing a digit, so printing the rest as well
call #_write_hexdigit
+ sub #4, a1 ; move to the next nibble
jnz .loop ; finish if =0
pop s0
ret
; print a byte, hexadecimal, unsigned
; expects:
; - a0: value to print
; - a2: if non zero, always print two digits
; clobbers: a1, t0
write_byte_hex:
mov #8, a1
jmp write_value_hex
; print a word, decimal, unsigned
; expects:
; - a0: value to print
; clobbers: a1, a2, t0
write_word_dec:
call #to_bcd ; a2|a0 is now BCD
and #0Fh, a2
jz write_word_hex ; skipping leading zeroes
push a0 ; need to deal with fifth digit
mov a2, a0
call #_write_hexdigit ; print fifth digit
pop a0 ; here a2!=0
; ! continues onto write_word_hex below !
; print a word, hexadecimal, unsigned
; expects:
; - a0: value to print
; - a2: if non-zero, pad with zeroes
; clobbers: a1, t0
write_word_hex:
mov #16, a1
jmp write_value_hex
; expected usage: puts "text\n"
; will type "text\n" out
puts macro Text
call #_write_puts_pc
static_string Text
endm
; internal
_write_puts_pc: ; sized string on stack (as a return address)
mov @sp, a0
add @a0, @sp ; fix the return address
incd @sp
bit #1, @sp ; add 1 if odd
jz write_str
inc @sp
; ! continues onto write_str below !
; print a sized string
; expects:
; - a0: sized string address
; - data stack: data for variables printing (%..)
; clobbers: potentially (flags): a1, a2, t0, t1
write_str:
push s0
push s1
mov @a0+, s1 ; set up loop: s0:current, s1:end
add a0, s1 ; s1 = a0.orig + @a0.orig + 2
mov a0, s0 ; a0 = a0.orig + 2
- mov.b @s0+, a0
cmp #'%', a0 ; check for "print value" char
jne .emit
; logic to deal with % flags
mov.b @s0+, a0
mov #1, a2 ; padding with 0s by default
cmp #'B', a0 ; byte in hex
jne +
popda a0 ; get the value to be printed
mov #8, a1 ; want leading zeroes
call #write_value_hex
jmp -
+ cmp #'W', a0 ; word (16bit) in hex, padded 4 digits
jne +