summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDuncan Wilkie <antigravityd@gmail.com>2023-08-07 17:31:16 -0500
committerDuncan Wilkie <antigravityd@gmail.com>2023-08-07 17:31:16 -0500
commit0a35e7d7be87baedc193644342ef1636940fba1e (patch)
treece322bdc53fd9e21013fc9cda35f98475035c94e
parent2a3940d8a36433485a6ef489d5123cd491618b50 (diff)
Forgot to stage
-rw-r--r--slaves/Makefile43
-rwxr-xr-xslaves/bin/slaves.binbin0 -> 186 bytes
-rwxr-xr-xslaves/bin/slaves.elfbin0 -> 6836 bytes
-rw-r--r--slaves/ld/ATTINY85.ld46
-rw-r--r--slaves/obj/main.obin0 -> 7136 bytes
-rw-r--r--slaves/src/main.s499
6 files changed, 588 insertions, 0 deletions
diff --git a/slaves/Makefile b/slaves/Makefile
new file mode 100644
index 0000000..cf4d5f3
--- /dev/null
+++ b/slaves/Makefile
@@ -0,0 +1,43 @@
+
+PROJECT = slaves
+MCU = ATTINY85
+
+
+AS = avr-as
+LD = avr-ld
+RM = rm -rf
+MKDIR = @mkdir -p $(@D)
+
+# Custom/TI/build resource locations.
+SRCS = $(wildcard src/*.s) \
+ $(wildcard libs/*.s)
+OBJ = obj/
+OBJS = $(addprefix $(OBJ),$(notdir $(SRCS:.s=.o)))
+LD_SCRIPT = ld/$(MCU).ld
+
+# Flags.
+AFLAGS = -mmcu=attiny85 --fatal-warnings # -ffunction-sections \
+# -ffreestanding -fdata-sections -std=c2x -Wall -Wextra -Werror -DPART_${MCU} -c -
+
+# Compiler/standard resource locations.
+# More flags.
+LDFLAGS = -T $(LD_SCRIPT) --gc-sections
+
+
+# Targets.
+all: bin/$(PROJECT).elf
+
+$(OBJ)%.o: src/%.s
+ $(MKDIR)
+ $(AS) -o $@ $^ $(AFLAGS)
+
+$(info $$PROJECT is [${PROJECT}])
+bin/$(PROJECT).elf: $(OBJS)
+ $(MKDIR)
+ $(LD) -o $@ $^ $(LDFLAGS)
+
+clean:
+ -$(RM) obj
+ -$(RM) bin
+
+.PHONY: all clean
diff --git a/slaves/bin/slaves.bin b/slaves/bin/slaves.bin
new file mode 100755
index 0000000..b006aa9
--- /dev/null
+++ b/slaves/bin/slaves.bin
Binary files differ
diff --git a/slaves/bin/slaves.elf b/slaves/bin/slaves.elf
new file mode 100755
index 0000000..9e1f3d6
--- /dev/null
+++ b/slaves/bin/slaves.elf
Binary files differ
diff --git a/slaves/ld/ATTINY85.ld b/slaves/ld/ATTINY85.ld
new file mode 100644
index 0000000..0690b22
--- /dev/null
+++ b/slaves/ld/ATTINY85.ld
@@ -0,0 +1,46 @@
+/*
+* Copyright (c) 2018, Shawn D'silva <shawn@shawndsilva.com>
+* All rights reserved.
+*
+* This file is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This file 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+*
+* File: TM4C123GH6PHM.ld
+* Author: Shawn D'silva <https://www.shawndsilva.com>.
+* Version: 1.0.0.
+* Description: linker file for the TM4C Launchpad
+*/
+
+ENTRY(reset_handler)
+
+MEMORY
+{
+
+ FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 8K /* FLASH size 256KB */
+
+}
+
+_Min_Heap_Size = 0x3000;
+_Min_Stack_Size = 0x3000;
+
+SECTIONS
+{
+ /* constants and other code stored in FLASH */
+ .text :
+ {
+ _text = .; /* beginning of .text segment,also called code memory */
+ *(.text*) /* other code */
+ _etext = .; /* end of .text segment */
+ } > FLASH
+
+}
diff --git a/slaves/obj/main.o b/slaves/obj/main.o
new file mode 100644
index 0000000..9053edd
--- /dev/null
+++ b/slaves/obj/main.o
Binary files differ
diff --git a/slaves/src/main.s b/slaves/src/main.s
new file mode 100644
index 0000000..c269491
--- /dev/null
+++ b/slaves/src/main.s
@@ -0,0 +1,499 @@
+ ;; Explanation:
+ ;; Each keyboard key lies at the intersection point on a grid of wires.
+ ;; The MCU on which this program runs sits on one end of each wire in the grid,
+ ;; and helps replace a keymatrix circuit in a way that demands fewer pins on the controller MCU.
+ ;; Whenever a key is pressed, it brings both incoming lines of the wire grid low.
+ ;; The two MCUs on the ends of these wires receive an interrupt, and send two bytes to the MCU closer to the controller.
+ ;; The first is a "type" byte, which details the nature of the press; the second is a "count" byte.
+ ;; The adjacent MCU receives these bytes, and relays them further, incrementing the count byte.
+ ;; Whenever the relays from both axes reach the controller, it now has coordinates of the keypress,
+ ;; which it can use to send the information to a synthesizer over MIDI.
+ ;; The communication protocol used is more-or-less daisy-chained SPI, using a common clock generated by the controller.
+ ;; In order to communicate velocity information, there are actually pairs of wires managed by each end MCU,
+ ;; connected to two switches deparated by some distance.
+ ;; The "type" bytes have bits as follows: 7: constant 1; 6: 0 for key event; 1 for velocity event;
+ ;; 5: 0 for release, 1 for press; 4-0: reserved and set to 0.
+ ;; The count byte starts at 1, i.e. it contains the number of "hops" taken from the originating MCU.
+ ;; This is consistent with the grid picture, considering the controller MCU to be at (0, 0).
+ ;; The actual program just does some initial configuration, sets a low-power idle state, and waits for interrupts.
+
+ ;; It takes at most 3 cycles for a pin interrupt to be triggered.
+ ;; The cycle time (default and maximum) is 1 / 10MHz = 100ns.
+ ;; The bus cycle time is subject to change; a high value would be 1 / 500kHz = 2μs and a hard min would be 5Mhz = 200ns.
+ ;; A USIOF interrupt is generated every 8 bus cycles.
+ ;; I cannot find how long it takes to wake from idle.
+ ;; One instruction is guaranteed to be executed from main after the ISR returns, even if another interrupt is signaling;
+ ;; This will be one or two cycles.
+ ;; reset_handler: 25 cycles = 2.5 μs
+ ;; spi_write: 10 cycles + 8 bus cycles = 1 μs + 8 bus cycles
+ ;; usi_ovf_isr, expecting data: 20 cycles + spi_write = 3μs + 8 bus cycles
+ ;; usi_ovf_isr, expecting type, got type: 21 cycles + spi_write = 3.1μs + 8 bus cycles
+ ;; usi_ovf_isr, expecting type, no type: 16 cycles = 1.6μs
+ ;; all of the usi_ovf_isr, with external write: + 9 cycles + 2 * spi_write = + 4.9μs + 16 bus cycles.
+ ;; pcint0_isr: 19 cycles = 1.9μs
+
+ ;; On the basis of these numbers, we can find a handwavy WCET from keypress to MIDI send with an already hot MCU.
+ ;; Two relevant factors: the worst-case clearing time of the relay, and the worst-case detection time of the keypress.
+ ;; First things first, the usi_ovf_isr can preempt the pcint0_isr (hence setting the flag is the last thing done),
+ ;; so we can start from the beginning of the overflow interrupt.
+ ;; The overflow interrupt takes 3.1μs + 8 cycles for the slowest byte.
+ ;; This gets multiplied for each hop to the controller, of which there are a maximum of 256.
+ ;; So, 256 * (3.1 μs + 8 bus cycles) = 0.79ms + 2048 bus cycles for a furthest possible keypress to be relayed.
+ ;; Now for the worst possible keypress detection case.
+ ;; Suppose the keypress occurs simultaneously with the cli instruction of the usi_ovf_isr
+ ;; while handling a valid type byte, with queued writes already (which should be the worst case).
+ ;; The cli instruction wins; all told there is 8μs + 24 bus cycles to clear already existing data.
+ ;; Then the pcint0 can be processed; this takes an additional 1.9μs, and the next counter overflow will write out.
+ ;; There might be (1.9μs / tbus) additional overflows during this execution;
+ ;; at worst, each entails a time cost of 3.1μs + 8 bus cycles.
+ ;; Our number is 1.9μs + (1.9 μs / 1 bus cycle) * (3.1μs + 8 bus cycles); this is the longest it can take a keypress
+ ;; to hit the stream.
+ ;; So, the total upper-bound time from keypress to controller is:
+ ;; (0.79ms + 2048 bus cycles) + (8μs + 24 bus cycles) + 1.9μs + (1.9 μs / 1 bus cycle) * (3.1μs + 8 bus cycles)
+ ;; = 0.82 ms + (5.9μs² / 1 bus cycle) + 2072 bus cycles
+
+ ;; For a bus at 500kHz → 1 bus cycle = 2μs, this translates to 4.96ms of latency.
+
+ ;; Constants
+ .set IO_OFFSET, 0x0020
+
+ .set ADCSRB, 0x0003
+ .set BIN, 7
+ .set ACME, 6
+ .set IPR, 5
+ .set ADTS2, 2
+ .set ADTS1, 1
+ .set ADTS0, 0
+
+ .set ADCL, 0x0004
+ .set ADCH, 0x0005
+
+ .set ADCSRA, 0x0006
+ .set ADEN, 7
+ .set ADSC, 6
+ .set ADATE, 5
+ .set ADIF, 4
+ .set ADIE, 3
+ .set ADPS2, 2
+ .set ADPS1, 1
+ .set ADTS0, 0
+
+ .set ADMUX, 0x0007
+ .set REFS1, 7
+ .set REFS0, 6
+ .set ADLAR, 5
+ .set REFS2, 4
+ .set MUX3, 3
+ .set MUX2, 2
+ .set MUX1, 1
+ .set MUX0, 0
+
+ .set ACSR, 0x0008
+ .set ACD, 7
+ .set ACBG, 6
+ .set ACO, 5
+ .set ACI, 4
+ .set ACIE, 3
+ .set ACIS1, 1
+ .set ACIS0, 0
+
+ .set USICR, 0x000d
+ .set USISIE, 7
+ .set USIOIE, 6
+ .set USIWM1, 5
+ .set USIWM0, 4
+ .set USICS1, 3
+ .set USICS0, 2
+ .set USICLK, 1
+ .set USITC, 0
+
+ .set USISR, 0x000e
+ .set USISIF, 7
+ .set USIOIF, 6
+ .set USIPF, 5
+ .set USIDC, 4
+ .set USICNT3, 3
+ .set USICNT2, 2
+ .set USICNT1, 1
+ .set USICNT0, 0
+
+ .set USIDR, 0x000f
+ .set USIBR, 0x0010
+ .set GPIOR0, 0x0011
+ .set GPIOR1, 0x0012
+ .set GPIOR2, 0x0013
+
+ .set DIDR0, 0x0014
+ .set ADC0D, 5
+ .set ADC2D, 4
+ .set ADC3D, 3
+ .set ADC1D, 2
+ .set AIN1D, 1
+ .set AIN0D, 0
+
+ .set PCMSK, 0x0015
+ .set PCINT5, 5
+ .set PCINT4, 4
+ .set PCINT3, 3
+ .set PCINT2, 2
+ .set PCINT1, 1
+ .set PCINT0, 0
+
+ .set PINB, 0x0016
+ .set PINB5, 5
+ .set PINB4, 4
+ .set PINB3, 3
+ .set PINB2, 2
+ .set PINB1, 1
+ .set PINB0, 0
+
+ .set DDRB, 0x0017
+ .set DDB5, 5
+ .set DDB4, 4
+ .set DDB3, 3
+ .set DDB2, 2
+ .set DDB1, 1
+ .set DDB0, 0
+
+ .set PORTB, 0x0018
+ .set PORTB5, 5
+ .set PORTB4, 4
+ .set PORTB3, 3
+ .set PORTB2, 2
+ .set PORTB1, 1
+ .set PORTB0, 0
+
+ .set EECR, 0x001c
+
+ .set EEDR, 0x001d
+ .set EEPM1, 5
+ .set EEPM0, 4
+ .set EERIE, 3
+ .set EEMPE, 2
+ .set EEPE, 1
+ .set EERE, 0
+
+ .set EEARL, 0x001e
+ .set EEAR7, 7
+ .set EEAR6, 6
+ .set EEAR5, 5
+ .set EEAR4, 4
+ .set EEAR3, 3
+ .set EEAR2, 2
+ .set EEAR1, 1
+ .set EEAR0, 0
+
+ .set EEARH, 0x001f
+ .set EEAR8, 0
+
+ .set PRR, 0x0020
+ .set PRTIM1, 3
+ .set PRTIM0, 2
+ .set PRUSI, 1
+ .set PRADC, 0
+
+ .set WDTCR, 0x0021
+ .set WDIF, 7
+ .set WDIE, 6
+ .set WDP3, 5
+ .set WDCE, 4
+ .set WDE, 3
+ .set WDP2, 2
+ .set WDP1, 1
+ .set WDP0, 0
+
+ .set DWDR, 0x0022
+
+ .set DTPS1, 0x0023
+ .set DTPS11, 1
+ .set DTPS10, 0
+
+ .set DT1B, 0x0024
+ .set DT1BH3, 7
+ .set DT1BH2, 6
+ .set DT1BH1, 5
+ .set DT1BH0, 4
+ .set DT1BL3, 3
+ .set DT1BL2, 2
+ .set DT1BL1, 1
+ .set DT1BL0, 0
+
+ .set DT1A, 0x0025
+ .set DT1AH3, 7
+ .set DT1AH2, 6
+ .set DT1AH1, 5
+ .set DT1AH0, 4
+ .set DT1AL3, 3
+ .set DT1AL2, 2
+ .set DT1AL1, 1
+ .set DT1AL0, 0
+
+ .set CLKPR, 0x0026
+ .set CLKPCE, 7
+ .set CLKPS3, 3
+ .set CLKPS2, 2
+ .set CLKPS1, 1
+ .set CLKPS0, 0
+
+ .set PLLCSR, 0x0027
+ .set LSM, 7
+ .set PCKE, 2
+ .set PLLE, 1
+ .set PLOCK, 0
+
+ .set OCR0B, 0x0028
+ .set OCR0A, 0x0029
+
+ .set TCCR0A, 0x002a
+ .set COM0A1, 7
+ .set COM0A0, 6
+ .set COM0B1, 5
+ .set COM0B0, 4
+ .set WGM01, 1
+ .set WGM00, 0
+
+ .set OCR1B, 0x002b
+
+ .set GTCCR, 0x002c
+ .set TSM, 7
+ .set PWM1B, 6
+ .set COM1B1, 5
+ .set COM1B0, 4
+ .set FOC1B, 3
+ .set FOC1A, 2
+ .set PSR1, 1
+ .set PSR0, 0
+
+ .set OCR1C, 0x002d
+ .set OCR1A, 0x002e
+ .set TCNT1, 0x002f
+
+ .set TCCR1, 0x0030
+ .set CTC1, 7
+ .set PWM1A, 6
+ .set COM1A1, 5
+ .set COM1A0, 4
+ .set CS13, 3
+ .set CS12, 2
+ .set CS11, 1
+ .set CS10, 0
+
+ .set OSCCAL, 0x0031
+ .set TCNT0, 0x0032
+
+ .set TCCR0B, 0x0033
+ .set FOC0A, 7
+ .set FOC0B, 6
+ .set WGM02, 3
+ .set CS02, 2
+ .set CS01, 1
+ .set CS00, 0
+
+ .set MCUSR, 0x0034
+ .set WDRF, 3
+ .set BORF, 2
+ .set EXTRF, 1
+ .set PORF, 0
+
+ .set MCUCR, 0x0035
+ .set BODS, 7
+ .set PUD, 6
+ .set SE, 5
+ .set SM1, 4
+ .set SM0, 3
+ .set BODSE, 2
+ .set ISC01, 1
+ .set ISC00, 0
+
+ .set SPMCSR, 0x0037
+ .set RSIG, 5
+ .set CTPB, 4
+ .set RFLB, 3
+ .set PGWRT, 2
+ .set PGERS, 1
+ .set SPMEN, 0
+
+ .set TIFR, 0x0038
+ .set OCF1A, 6
+ .set OCF1B, 5
+ .set OCF0A, 4
+ .set OCF0B, 3
+ .set TOV1, 2
+ .set TOV0, 1
+
+ .set TIMSK, 0x0039
+ .set OCIE1A, 6
+ .set OCIE1B, 5
+ .set OCIE0A, 4
+ .set OCIE0B, 3
+ .set TOIE1, 2
+ .set TOIE0, 1
+
+ .set GIFR, 0x003a
+ .set INTF0, 6
+ .set PCIF, 5
+
+ .set GIMSK, 0x003b
+ .set INT0, 6
+ .set PCIE, 5
+
+ .set SPL, 0x003d
+ .set SP7, 7
+ .set SP6, 6
+ .set SP5, 5
+ .set SP4, 4
+ .set SP3, 3
+ .set SP2, 2
+ .set SP1, 1
+ .set SP0, 0
+
+ .set SPH, 0x003e
+ .set SP9, 1
+ .set SP8, 0
+
+ .set SREG, 0x003f
+ .set I, 7
+ .set T, 6
+ .set H, 5
+ .set S, 4
+ .set V, 3
+ .set N, 2
+ .set Z, 1
+ .set C, 0
+
+vector_table:
+ .org 0x0000
+ .text
+ rjmp reset_handler
+ rjmp unhandled_isr
+ rjmp pcint0_isr
+ rjmp unhandled_isr
+ rjmp unhandled_isr
+ rjmp unhandled_isr
+ rjmp unhandled_isr
+ rjmp unhandled_isr
+ rjmp unhandled_isr
+ rjmp unhandled_isr
+ rjmp unhandled_isr
+ rjmp unhandled_isr
+ rjmp unhandled_isr
+ rjmp unhandled_isr
+ rjmp usi_ovf_isr
+
+
+reset_handler:
+ .global reset_handler
+ ;; Set up three-wire interface with positive edge clock, and interrupts on counter overflow.
+ ldi r16, (1 << USIOIE) | (1 << USICS1) | (1 << USIWM0)
+ out USICR, r16
+ ldi r16, (1 << DDB2) | (1 << DDB1)
+ out DDRB, r16
+ ;; Set PB3 and PB4 as GPIO inputs, with pull-up active; enable pin-change interrupts on the same;
+ ;; initialize pin state storage register.
+ ldi r16, (1 << PORTB4) | (1 << PORTB3)
+ out PORTB, r16
+ ldi r16, (1 << PCINT4) | (1 << PCINT3)
+ out PCMSK, r16
+ ldi r16, (1 << PCIE)
+ out GIMSK, r16
+ in r18, PINB
+ andi r18, (0b00011000)
+ ;; Shut down unnecessary peripherals; disable clock to them; enable sleep instruction.
+ ldi r16, (1 << ACD)
+ out ACSR, r16
+ ldi r16, (1 << WDCE) | (1 << WDE)
+ out WDTCR, r16
+ ldi r16, 0x00
+ out WDTCR, r16
+ ldi r16, (1 << PRTIM1) | (1 << PRTIM0) | (1 << PRADC)
+ out PRR, r16
+ ldi r16, (1 << SE)
+ out MCUCR, r16
+ ;; Un-globally-disable interrupts
+ sei
+ ;; Call main.
+ rjmp main
+
+ ;; Current tactic: make it magic bit + key/velocity bit + presed/released bit + byte's worth other flags + 1 count byte.
+ ;; All MCUs will count clock cycles modulo 8; senders must only send when their counters overflow.
+ ;; r22 is a flag, set to 1 when the last received byte was a type byte. Cleared by ISR when data byte is received.
+ ;; r24 is a flag, set to 1 when the next clock overflow should write out the type byte in r25 and data byte in r26.
+ ;; r24 is cleared by the ISR after write is completed.
+
+spi_write: ; Should be called with interrupts disabled; blocks until data byte is fully written.
+ ;; Put the parameter r16 in USI data register
+ out USIDR, r16
+spi_write_loop:
+ ;; Loop until clock overflows, i.e. byte in USIDR is fully written.
+ in r16, USISR
+ sbrs r16, USIOIF
+ rjmp spi_write_loop
+ ;; Clear interrupt from write counter overflow.
+ ldi r16, (1 << USIOIF)
+ out USISR, r16
+ ;; Return.
+ ret
+
+usi_ovf_isr: ; Full SPI byte in USIDR. Act fast!
+ cli
+ ldi r16, (1 << USIOIF)
+ out USISR, r16
+ sbrc r22, 0
+ rjmp expecting_data
+expecting_type:
+ in r16, USIDR
+ sbrs r16, 7
+ rjmp default
+got_type:
+ ldi r22, 0x01
+ rcall spi_write
+ rjmp default
+expecting_data:
+ in r16, USIDR
+ inc r16 ; What to do if 0 data byte received?
+ rcall spi_write
+ ldi r22, 0x00
+default:
+ sbrs r24, 0
+ rjmp isr_exit
+ mov r16, r25
+ rcall spi_write
+ mov r16, r26
+ rcall spi_write
+ ldi r24, 0x00
+isr_exit:
+ sei
+ reti
+
+pcint0_isr: ; This one's preemptable.
+ ;; Send key/velocity bit and on/off bit.
+ in r16, PINB
+ andi r16, 0b00011000
+ mov r17, r16
+ eor r17, r18
+ sbrs r17, 3
+ rjmp key_line
+ sbrs r17, 4
+ rjmp velocity_line
+key_line:
+ ldi r25, 0b10000000
+ sbrs r16, 3
+ ori r25, 0b00100000
+ rjmp count_byte
+velocity_line:
+ ldi r25, 0b11000000
+ sbrs r16, 4
+ ori r25, 0b00100000
+count_byte:
+ ldi r26, 0x01
+ mov r18, r16
+ ldi r24, 0x01
+ reti
+
+unhandled_isr:
+ nop
+ rjmp unhandled_isr ; Error.
+
+main:
+ sleep
+ rjmp main ; Application loop.