000000widow-hackspacecon/fw/HackSpaceCon_AS7/Arduino/megatinycore/UART0.cpp

132 lines
5.8 KiB
C++

/* UART0.cpp - Hardware serial library for USART0
* This library is free software released under LGPL 2.1.
* See License.md for more information.
* This file is part of megaTinyCore.
*
* Copyright (c) 2006 Nicholas Zambetti, Modified by
* 11/23/2006 David A. Mellis, 9/20/2010 Mark Sproul,
* 8/24/2012 Alarus, 12/3/2013 Matthijs Kooijman
* unknown others 2013-2020, 2020-2022 Spence Konde
*/
/* Each HardwareSerial is defined in its own file, sine the linker pulls
* in the entire file when any element inside is used. --gc-sections can
* additionally cause unused symbols to be dropped, but ISRs have the
* "used" attribute so are never dropped and they keep the
* HardwareSerial instance in as well. Putting each instance in its own
* file prevents the linker from pulling in any unused instances in the
* first place.
* There are now two versions of each ISR. All versions are stubs to call
* the real code (so it is not duplicated). The ASM versions are more
* efficient than calling normally can ever be because they completely
* ignore the ABI rules for the transition from duplicated to shared
* code and call it the most effcieient way possiblem so the almost all of
* the flash overhead is in the shared section. They'll stop working if
* anything changes, and it's not entirely clear whether the trick of
* dropping out of assembly while naked to grab the address is legal,
* though there's no reason it shouldn't be.
* The assembly implementations over in UART.cpp depends on the structure
* of the SerialClass. Any changes to the class member variables will
* require changes to the asm to match.
* This was done for 1.5.12 to correct a serious defect. amd fix a
* to make things work better with millis off, and free up 4 bytes of sram.
*/
#include "Arduino.h"
#include "UART.h"
#include "UART_private.h"
#if defined(USART0)
#if defined(USE_ASM_TXC) && USE_ASM_TXC == 1 //&& defined(USART1) // No benefit to this if it's just one USART
// Note the difference between this and the other ISRs - here we don't care at all about the serial object, we just have to work with the USART
ISR(USART0_TXC_vect, ISR_NAKED) {
__asm__ __volatile__(
"push r30" "\n\t" // push the low byte of Z - we start out 5-6 clocks behind the ball, these three instructions take 4-5 -> 9 or 11 by th time we reach _do_txc
"ldi r30, 0x00" "\n\t" // and put the low bit of this USART there - 0x20 * n
#if PROGMEM_SIZE > 8192
"jmp _do_txc" "\n\t"
#else
"rjmp _do_txc" "\n\t"
#endif // _do_txc pushes the other necessary registers and loads 0x08 into the high byte.
// The reason this is possible here and not elsewhere is because TXC only needs the USART, while the others need the HardwareSerial instance.
:::);
}
#elif defined(USE_ASM_TXC) && USE_ASM_TXC == 2
ISR(USART0_TXC_vect, ISR_NAKED) {
__asm__ __volatile__(
"push r30" "\n\t"
"push r31" "\n\t"
:::);
__asm__ __volatile__(
#if PROGMEM_SIZE > 8192
"jmp _do_txc" "\n\t"
#else
"rjmp _do_txc" "\n\t"
#endif
::"z"(&Serial0));
__builtin_unreachable();
}
#else
ISR(USART0_TXC_vect) {
// only enabled in half duplex mode - we disable RX interrupt while sending.
// When we are done sending, we re-enable the RX interrupt and disable this one.
// Note that we do NOT clear TXC flag, which the flush() method relies on.
uint8_t ctrla;
while (USART0.STATUS & USART_RXCIF_bm) {
ctrla = USART0.RXDATAL; // We sent these, so dump them, using local var as trashcan.
}
ctrla = USART0.CTRLA; // Get current CTRLA
ctrla |= USART_RXCIE_bm; // turn on receive complete
ctrla &= ~USART_TXCIE_bm; // turn off transmit complete
USART0.CTRLA = ctrla; // Write it back to CTRLA.
}
#endif
#if !(defined(USE_ASM_RXC) && (USE_ASM_RXC == 1 || USE_ASM_RXC == 2) && (SERIAL_RX_BUFFER_SIZE == 256 || SERIAL_RX_BUFFER_SIZE == 128 || SERIAL_RX_BUFFER_SIZE == 64 || SERIAL_RX_BUFFER_SIZE == 32 || SERIAL_RX_BUFFER_SIZE == 16) /* && defined(USART1)*/)
ISR(USART0_RXC_vect) {
HardwareSerial::_rx_complete_irq(Serial);
}
#else
ISR(USART0_RXC_vect, ISR_NAKED) {
__asm__ __volatile__(
"push r30" "\n\t" //we start out 5-6 clocks behind the ball, then do 2 push + 2 ldi + 2-3 for jump = 11 or 13 clocks to _do_rxc (and dre is the same)
"push r31" "\n\t"
"cbi 0x1F, 0" "\n\t"
:::);
__asm__ __volatile__(
#if PROGMEM_SIZE > 8192
"jmp _do_rxc" "\n\t"
#else
"rjmp _do_rxc" "\n\t"
#endif
::"z"(&Serial0));
__builtin_unreachable();
}
#endif
#if !(defined(USE_ASM_DRE) && USE_ASM_DRE == 1 && \
(SERIAL_RX_BUFFER_SIZE == 256 || SERIAL_RX_BUFFER_SIZE == 128 || SERIAL_RX_BUFFER_SIZE == 64 || SERIAL_RX_BUFFER_SIZE == 32 || SERIAL_RX_BUFFER_SIZE == 16) && \
(SERIAL_RX_BUFFER_SIZE == 256 || SERIAL_TX_BUFFER_SIZE == 128 || SERIAL_TX_BUFFER_SIZE == 64 || SERIAL_TX_BUFFER_SIZE == 32 || SERIAL_TX_BUFFER_SIZE == 16))
ISR(USART0_DRE_vect) {
HardwareSerial::_tx_data_empty_irq(Serial);
}
#else
ISR(USART0_DRE_vect, ISR_NAKED) {
__asm__ __volatile__(
"push r30" "\n\t"
"push r31" "\n\t"
"cbi 0x1F, 0" "\n\t"
:::);
__asm__ __volatile__(
#if PROGMEM_SIZE > 8192
"jmp _do_dre" "\n\t"
#else
"rjmp _do_dre" "\n\t"
#endif
::"z"(&Serial0));
__builtin_unreachable();
}
#endif
HardwareSerial Serial0(&USART0, 0, HWSERIAL0_MUX_DEFAULT); //Elsewhere we define an alias of Serial for this :-)
#endif