From 0a35e7d7be87baedc193644342ef1636940fba1e Mon Sep 17 00:00:00 2001 From: Duncan Wilkie Date: Mon, 7 Aug 2023 17:31:16 -0500 Subject: Forgot to stage --- slaves/Makefile | 43 +++++ slaves/bin/slaves.bin | Bin 0 -> 186 bytes slaves/bin/slaves.elf | Bin 0 -> 6836 bytes slaves/ld/ATTINY85.ld | 46 +++++ slaves/obj/main.o | Bin 0 -> 7136 bytes slaves/src/main.s | 499 ++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 588 insertions(+) create mode 100644 slaves/Makefile create mode 100755 slaves/bin/slaves.bin create mode 100755 slaves/bin/slaves.elf create mode 100644 slaves/ld/ATTINY85.ld create mode 100644 slaves/obj/main.o create mode 100644 slaves/src/main.s (limited to 'slaves') 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 Binary files /dev/null and b/slaves/bin/slaves.bin differ diff --git a/slaves/bin/slaves.elf b/slaves/bin/slaves.elf new file mode 100755 index 0000000..9e1f3d6 Binary files /dev/null and b/slaves/bin/slaves.elf 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 +* 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 . +* +* File: TM4C123GH6PHM.ld +* Author: Shawn D'silva . +* 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 Binary files /dev/null and b/slaves/obj/main.o 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. -- cgit v1.2.3