;; 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.