/* UART.cpp - Hardware serial library, main file. * 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 * Others (unknown) 2013-2017, 2017-2021 Spence Konde * and 2021 MX682X * * See UART.h for more of a record of changes. */ #include #include #include #include #include //#include #include "Arduino.h" #include "UART.h" #include "UART_private.h" // this next line disables the entire UART.cpp if there's no hardware serial #if defined(USART0) || defined(USART1) || defined(USART2) || defined(USART3) || defined(USART4) || defined(USART5) #if defined(HAVE_HWSERIAL0) || defined(HAVE_HWSERIAL1) || defined(HAVE_HWSERIAL2) || defined(HAVE_HWSERIAL3) || defined(HAVE_HWSERIAL4) || defined(HAVE_HWSERIAL5) // macro to guard critical sections when needed for large TX buffer sizes #if (SERIAL_TX_BUFFER_SIZE > 256) #define TX_BUFFER_ATOMIC ATOMIC_BLOCK(ATOMIC_RESTORESTATE) #else #define TX_BUFFER_ATOMIC #endif /*## ### #### # # # # # ### #### # # # # ### #### # */ /* There ain't no such thing as a free lunch. Every new feature makes these worse. USE_ASM_TXC 2 takes an additional 2 words/2 clocks vs 1 USE_ASM_RXC 2 takes an additional 5 words/5 clocks vs 1 RXC takes another 3 words/4 clocks if SDF errata compensation is needed. Both USE_ASM_RXC and USE_ASM_TXC must be mode 2 for half duplex to work Transmit Complete for Half Duplex This is a more efficient and scalable version of the TXC ISR, the original implementation is below: #if defined(USART1) ISR(USART1_TXC_vect) { uint8_t ctrla; while (USART1.STATUS & USART_RXCIF_bm) { ctrla = USART1.RXDATAL; } ctrla = USART1.CTRLA; ctrla |= USART_RXCIE_bm; // turn on receive complete ctrla &= ~USART_TXCIE_bm; // turn off transmit complete USART1.CTRLA = ctrla; } In the USARTn.cpp, there's something like this that puts the low byte of the address of the USART into r30 after saving the register. Then it just jumps to this routine, which loads the (always the same) high byte! and finishes up clearing out the other register we will need and saving the SREG. The logic is very simple, It's just ugly. It gets worse for the other interrupts because we have to work with the class, not just the hardware. Crucially the only thing different betweren the USARTs here isthe addressthey're working with. Much of the benefit comes from being able to get the benefits of functionsin terms of flash use without the penalties that come with using a true CALL instruction in an ISR (50-80 byte prologue + epiloge), and also being aware that the X register can't do displacement when planning what goes in which regiseser... which is not avr-gcc's strong suite, and often ends up displacing from the X with adiw/sbiw spam. savings for one copy of it is small. Savings for several is gets large fast! Performance is better, but not much. Biggest advantage is for 2-series with the dual UARTs, but potentially as little as 4k of flash. TXC isr starts from this: ISR(USART1_TXC_vect, ISR_NAKED) { __asm__ __volatile__( "push r30" "\n\t" // 1 "ldi r30, 0x20" "\n\t" // 1 "rjmp do_txc" "\n\t" // 1 :::); } */ #if USE_ASM_TXC == 2 void __attribute__((naked)) __attribute__((used)) __attribute__((noreturn)) _do_txc(void) { __asm__ __volatile__( "_do_txc:" "\n\t" // We start out 11-13 clocks after the interrupt "push r24" "\n\t" // r30 and r31 pushed before this. "in r24, 0x3f" "\n\t" // Save SREG "push r24" "\n\t" // "push r25" "\n\t" // "push r28" "\n\t" // "push r29" "\n\t" // "ldd r28, Z + 8" "\n\t" // Load USART into Y pointer, low byte "ldi r29, 0x08" "\n\t" // all USARTs are 0x08n0 where n is an even hex digit. "ldd r25, Y + 5" "\n\t" // Y + 5 = USARTn.CTRLA read CTRLA "_txc_flush_rx:" "\n\t" // start of rx flush loop. "ld r24, Y" "\n\t" // Y + 0 = USARTn.RXDATAL rx data "ldd r24, Y + 4" "\n\t" // Y + 4 = USARTn.STATUS "sbrc r24, 7" "\n\t" // if RXC bit is clear... "rjmp _txc_flush_rx" "\n\t" // .... skip this jump to remove more from the buffer. "andi r25, 0xBF" "\n\t" // clear TXCIE "ori r25, 0x80" "\n\t" // set RXCIE "std Y + 5, r25" "\n\t" // store CTRLA // "ldd r24, Z + 12" "\n\t" // "ahha, always, true" "\n\t" // wait, if we're in TXC, We are in half duplex mode, duuuuh // "sbrs r24, 2" "\n\t" // if we're in half duplex skip... // "rjmp .+ 6" "\n\t" // a jump over the next three instructoins. Do do them iff in half duplex only // "ori r24, 0x10" "\n\t" // add the "there's an echo in here" bit // "std Z + 12, r24" "\n\t" // Store modified state "pop r29" "\n\t" "pop r28" "\n\t" "pop r25" "\n\t" "pop r24" "\n\t" // pop r24 to get old SREG back "out 0x3F, r24" "\n\t" // restore sreg. "pop r24" "\n\t" // pop r24 restore it "pop r31" "\n\t" // and r31 "pop r30" "\n\t" // Pop the register the ISR did "reti" "\n" // return from the interrupt. :: ); __builtin_unreachable(); } #elif USE_ASM_TXC == 1 void __attribute__((naked)) __attribute__((used)) __attribute__((noreturn)) _do_txc(void) { __asm__ __volatile__( "_do_txc:" "\n\t" // "push r31" "\n\t" // push other half of Z register. "push r24" "\n\t" // push r24 "in r24, 0x3f" "\n\t" // save sreg to r24 "push r24" "\n\t" // and push that. r30 pushed and loaded by ISR already. "ldi r31, 0x08" "\n\t" // all USARTs are 0x08n0 where n is an even hex digit. "_txc_flush_rx:" "\n\t" // start of rx flush loop. "ld r24, Z" "\n\t" // Z + 0 = USARTn.RXDATAL rx data "ldd r24, Z + 4" "\n\t" // Z + 4 = USARTn.STATUS "sbrc r24, 7" "\n\t" // if RXC bit is set... "rjmp _txc_flush_rx" "\n\t" // .... skip this jump to remove more from the buffer. "ldd r24, Z + 5" "\n\t" // Z + 5 = USARTn.CTRLA read CTRLA "andi r24, 0xBF" "\n\t" // clear TXCIE "ori r24, 0x80" "\n\t" // set RXCIE "std Z + 5, r24" "\n\t" // store CTRLA "pop r24" "\n\t" // pop r24, xcontaining old sreg. "out 0x3f, r24" "\n\t" // restore it "pop r24" "\n\t" // pop r24 to get it's old value back "pop r31" "\n\t" // and r31 "pop r30" "\n\t" // Pop the register the ISR pushed "reti" "\n" // return from the interrupt. ::); __builtin_unreachable(); } #endif /* We are starting from this: ISR(USART0_RXC_vect, ISR_NAKED) { __asm__ __volatile__( "push r30" "\n\t" "push r31" "\n\t" :::); __asm__ __volatile__( "rjmp do_rxc" "\n\t" ::"z"(&Serialn)); __builtin_unreachable(); } */ #if ((USE_ASM_RXC == 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) ) void __attribute__((naked)) __attribute__((used)) __attribute__((noreturn)) _do_rxc(void) { __asm__ __volatile__( "_do_rxc:" "\n\t" // We start out 11-13 clocks after the interrupt "push r18" "\n\t" // r30 and r31 pushed before this. "in r18, 0x3f" "\n\t" // Save SREG "push r18" "\n\t" // "push r19" "\n\t" // Ugh we needed another register.... "push r24" "\n\t" // "push r25" "\n\t" // "push r28" "\n\t" // "push r29" "\n\t" // "ldd r28, Z + 8" "\n\t" // Load USART into Y pointer // "ldd r29, Z + 9" "\n\t" // We interact with the USART only this once "ldi r29, 0x08" "\n\t" // High byte always 0x08 for USART peripheral: Save-a-clock. 11 clocks to here #if defined(ERRATA_USART_WAKE) // This bug appears to be near-universal, 4 clocks to workaround. "ldd r18, Y + 6" "\n\t" "andi r18, 0xEF" "\n\t" "std Y + 6, r18" "\n\t" // turn off SFD interrupt before reading RXDATA so we don't corrupt the next character. #endif "ldd r24, Y + 1" "\n\t" // Y + 1 = USARTn.RXDATAH - load high byte first - 16 clocks from here to next "ld r25, Y" "\n\t" // Y + 0 = USARTn.RXDATAL - then low byte of RXdata "andi r24, 0x46" "\n\t" // extract framing, parity bits. "lsl r24" "\n\t" // leftshift them one place "ldd r19, Z + 12" "\n\t" // load _state "or r19, r24" "\n\t" // bitwise or with errors extracted from _state "sbrc r24, 2" "\n\t" // if there's a parity error, then do nothing more (note the leftshift). "rjmp _end_rxc" "\n\t" // Copies the behavior of stock implementation - framing errors are ok, apparently... //#if USE_ASM_TXC == 2 && USE_ASM_RXC == 2 //"sbic 0x1F, 0" "\n\t" //"sbi 0x01, 2" "\n\t" //"sbrs r19, 4" "\n\t" // Is there an echo in here? //"rjmp storechar" "\n\t" // if not skip these next isns "\n\t" //"sbic 0x1F, 0" "\n\t" //"sbi 0x02, 4" "\n\t" //"andi r19, 0xEF" "\n\t" // clear t //"rjmp _end_rxc" "\n\t" // "storechar:" //#endif "ldd r28, Z + 13" "\n\t" // load current head index "ldi r24, 1" "\n\t" // Clear r24 and initialize it with 1 "add r24, r28" "\n\t" // add current head index to it #if SERIAL_RX_BUFFER_SIZE == 256 // No additional action needed, head wraps naturally. #elif SERIAL_RX_BUFFER_SIZE == 128 "andi r24, 0x7F" "\n\t" // Wrap the head around #elif SERIAL_RX_BUFFER_SIZE == 64 "andi r24, 0x3F" "\n\t" // Wrap the head around #elif SERIAL_RX_BUFFER_SIZE == 32 "andi r24, 0x1F" "\n\t" // Wrap the head around #elif SERIAL_RX_BUFFER_SIZE == 16 "andi r24, 0x0F" "\n\t" // Wrap the head around #endif "ldd r18, Z + 14" "\n\t" // load tail index This to _end_rxc is 11 clocks unless the buffer was full, in which case it's 8. "cp r18, r24" "\n\t" // See if head is at tail. If so, buffer full. The incoming data is discarded, "breq _buff_full_rxc" "\n\t" // because there is noplace to put it, and we just restore state and leave. "add r28, r30" "\n\t" // r28 has what would be the next index in it. "mov r29, r31" "\n\t" // and this is the high byte of serial instance "ldi r18, 0" "\n\t" // need a known zero to carry. "adc r29, r18" "\n\t" // carry - Y is now pointing 17 bytes before head "std Y + 17, r25" "\n\t" // store the new char in buffer "std Z + 13, r24" "\n\t" // write that new head index. "_end_rxc:" "\n\t" "std Z + 12, r19" "\n\t" // record new state including new errors // Epilogue: 9 pops + 1 out + 1 reti +1 std = 24 clocks "pop r29" "\n\t" // Y Pointer was used for head and usart. "pop r28" "\n\t" // "pop r25" "\n\t" // r25 held the received character "pop r24" "\n\t" // r24 held rxdatah, then the new head. "pop r19" "\n\t" // restore r19 which held the value that State will have after this. "pop r18" "\n\t" // Restore saved SREG "out 0x3f, r18" "\n\t" // and write back "pop r18" "\n\t" // used as tail offset, and then as known zero. "pop r31" "\n\t" // end with Z which the isr pushed to make room for "pop r30" "\n\t" // pointer to serial instance "reti" "\n\t" // return "_buff_full_rxc:" "\n\t" // potential improvement: move _buff_full_rxc to after the reti, and then rjmp back, saving 2 clocks for the common case "ori r19, 0x40" "\n\t" // record that there was a ring buffer overflow. 1 clk "rjmp _end_rxc" "\n\t" // and now jump back to end. That way we don't need to jump over this in the middle of the common case. ::); // total: 77 or 79 clocks, just barely squeaks by for cyclic RX of up to RX_BUFFER_SIZE characters. __builtin_unreachable(); } #elif defined(USE_ASM_RXC) && USE_ASM_RXC == 1 #warning "USE_ASM_RXC is defined and this has more than one serial port, but the buffer size is not supported, falling back to the classical RXC." #else #if defined(PERMIT_USART_WAKE) #error "USART Wake is not supported by the non-ASM RXC interrupt handler" #endif void HardwareSerial::_rx_complete_irq(HardwareSerial& HardwareSerial) { // if (bit_is_clear(*_rxdatah, USART_PERR_bp)) { uint8_t rxDataH = HardwareSerial._hwserial_module->RXDATAH; uint8_t c = HardwareSerial._hwserial_module->RXDATAL; // no need to read the data twice. read it, then decide what to do rx_buffer_index_t rxHead = HardwareSerial._rx_buffer_head; if (!(rxDataH & USART_PERR_bm)) { // No Parity error, read byte and store it in the buffer if there is room // unsigned char c = HardwareSerial._hwserial_module->RXDATAL; #if SERIAL_RX_BUFFER_SIZE > 256 rx_buffer_index_t i = (uint16_t)(rxHead + 1) % SERIAL_RX_BUFFER_SIZE; #else rx_buffer_index_t i = (uint8_t)(rxHead + 1) % SERIAL_RX_BUFFER_SIZE; #endif // if we should be storing the received character into the location // just before the tail (meaning that the head would advance to the // current location of the tail), we're about to overflow the buffer // and so we don't write the character or advance the head. if (i != HardwareSerial._rx_buffer_tail) { HardwareSerial._rx_buffer[rxHead] = c; HardwareSerial._rx_buffer_head = i; } } } #endif /* DRE starts just like RXC ISR(USART0_DRE_vect, ISR_NAKED) { __asm__ __volatile__( push r30 push r31 "\n\t" :::); __asm__ __volatile__( "rjmp do_dre" "\n\t" ::"z"(&Serialn)); __builtin_unreachable(); */ #if 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_TX_BUFFER_SIZE == 256 || SERIAL_TX_BUFFER_SIZE == 128 || SERIAL_TX_BUFFER_SIZE == 64 || SERIAL_TX_BUFFER_SIZE == 32 || SERIAL_TX_BUFFER_SIZE == 16) void __attribute__((naked)) __attribute__((used)) __attribute__((noreturn)) _do_dre(void) { __asm__ __volatile__( "_do_dre:" "\n\t" "push r18" "\n\t" "in r18, 0x3F" "\n\t" "push r18" "\n\t" "push r24" "\n\t" "push r25" "\n\t" "push r26" "\n\t" "push r27" "\n\t" "set" "\n\t" // SEt the T flag - we use this to determine how we got here and hence whether to rjmp to end of poll or reti "_poll_dre:" "\n\t" "push r28" "\n\t" "push r29" "\n\t" "ldi r18, 0" "\n\t" "ldd r28, Z + 8" "\n\t" // usart in Y // "ldd r29, Z + 9" "\n\t" // usart in Y "ldi r29, 0x08" "\n\t" // High byte always 0x08 for USART peripheral: Save-a-clock. "ldd r25, Z + 16" "\n\t" // tx tail in r25 "movw r26, r30" "\n\t" // copy of serial in X "add r26, r25" "\n\t" // SerialN + txtail "adc r27, r18" "\n\t" // X = &Serial + txtail #if SERIAL_RX_BUFFER_SIZE == 256 // RX buffer determines offset from start of class to TX buffer "subi r26, 0xEF" "\n\t" // There's no addi/adci, so we instead subtract (65536-(offset we want to add)) "sbci r27, 0xFE" "\n\t" // +273 "ld r24, X" "\n\t" // grab the character #elif SERIAL_RX_BUFFER_SIZE == 128 "subi r26, 0x6F" "\n\t" // "sbci r27, 0xFF" "\n\t" // +145 "ld r24, X" "\n\t" // grab the character #elif SERIAL_RX_BUFFER_SIZE == 64 "subi r26, 0xAF" "\n\t" // "sbci r27, 0xFF" "\n\t" // +81 "ld r24, X" "\n\t" // grab the character #elif SERIAL_RX_BUFFER_SIZE == 32 "adiw r26, 0x31" "\n\t" // +49 "ld r24, X" "\n\t" // grab the character #elif SERIAL_RX_BUFFER_SIZE == 16 "adiw r26, 0x21" "\n\t" // +33 "ld r24, X" "\n\t" // grab the character #endif "ldi r18, 0x40" "\n\t" "std Y + 4, r18" "\n\t" // Y + 4 = USART.STATUS - clear TXC "std Y + 2, r24" "\n\t" // Y + 2 = USART.TXDATAL - write char "subi r25, 0xFF" "\n\t" // txtail +1 #if SERIAL_TX_BUFFER_SIZE == 256 // // No action needed to wrap the tail around - #elif SERIAL_TX_BUFFER_SIZE == 128 "andi r25, 0x7F" "\n\t" // Wrap the tail around #elif SERIAL_TX_BUFFER_SIZE == 64 "andi r25, 0x3F" "\n\t" // Wrap the tail around #elif SERIAL_TX_BUFFER_SIZE == 32 "andi r25, 0x1F" "\n\t" // Wrap the tail around #elif SERIAL_TX_BUFFER_SIZE == 16 "andi r25, 0x0F" "\n\t" // Wrap the tail around #endif "ldd r24, Y + 5" "\n\t" // Y + 5 = USART.CTRLA - get CTRLA into r24 "ldd r18, Z + 15" "\n\t" // txhead into r18 "cpse r18, r25" "\n\t" // if they're the same "rjmp _done_dre_irq" "\n\t" "andi r24, 0xDF" "\n\t" // DREIE off "std Y + 5, r24" "\n\t" // write new ctrla "_done_dre_irq:" "\n\t" // Beginning of the end of DRE "std Z + 16, r25" "\n\t" // store new tail "pop r29" "\n\t" // pop Y "pop r28" "\n\t" // finish popping Y #if PROGMEM_SIZE > 8192 "brts .+4" "\n\t" // hop over the next insn if T bit set, means entered through do_dre, rather than poll_dre "jmp _poll_dre_done" "\n\t" // >8k parts must us jmp, otherwise it will give PCREL error. #else "brts .+2" "\n\t" // hop over the next insn if T bit set, means entered through do_dre, rather than poll_dre "rjmp _poll_dre_done" "\n\t" // 8k parts can use RJMP #endif "pop r27" "\n\t" // and continue with popping registers. 21 clocks left "pop r26" "\n\t" "pop r25" "\n\t" "pop r24" "\n\t" "pop r18" "\n\t" // pop SREG value from stack "out 0x3f, r18" "\n\t" // restore SREG "pop r18" "\n\t" // pop old r18 "pop r31" "\n\t" // pop the Z that the isr pushed. "pop r30" "\n\t" "reti" "\n" // and RETI! ::); __builtin_unreachable(); } #elif USE_ASM_DRE == 1 #warning "USE_ASM_DRE == 1, but the buffer sizes are not supported, falling back to the classical DRE." #else void HardwareSerial::_tx_data_empty_irq(HardwareSerial& HardwareSerial) { USART_t* usartModule = (USART_t*)HardwareSerial._hwserial_module; // reduces size a little bit tx_buffer_index_t txTail = HardwareSerial._tx_buffer_tail; // Check if tx buffer already empty. when called by _poll_tx_data_empty() // if (HardwareSerial._tx_buffer_head == txTail) { // Buffer empty, so disable "data register empty" interrupt // usartModule->CTRLA &= (~USART_DREIE_bm); // return; //} // moved to poll function to make ISR smaller and faster // There must be more data in the output // buffer. Send the next byte uint8_t c = HardwareSerial._tx_buffer[txTail]; // clear the TXCIF flag -- "can be cleared by writing a one to its bit // location". This makes sure flush() won't return until the bytes // actually got written. It is critical to do this BEFORE we write the next byte usartModule->STATUS = USART_TXCIF_bm; usartModule->TXDATAL = c; txTail = (txTail + 1) & (SERIAL_TX_BUFFER_SIZE - 1); //% SERIAL_TX_BUFFER_SIZE; uint8_t ctrla = usartModule->CTRLA; if (HardwareSerial._tx_buffer_head == txTail) { // Buffer empty, so disable "data register empty" interrupt ctrla &= ~(USART_DREIE_bm); usartModule->CTRLA = ctrla; } HardwareSerial._tx_buffer_tail = txTail; } #endif // To invoke data empty "interrupt" via a call, use this method void HardwareSerial::_poll_tx_data_empty(void) { if ((!(SREG & CPU_I_bm)) || CPUINT.STATUS) { // We're here because we're waiting for space in the buffer *or* we're in flush // and waiting for the last byte to leave, yet we're either in an ISR, or // interrupts are disabled so the ISR can't fire on it's own. // // Interrupts are disabled either globally or for data register empty, // or we are in another ISR. (It doesn't matter *which* ISR we are in // whether it's another level 0, the priority one, or heaven help us // the NMI, if the user code says to print something or flush the buffer // we might as well do it. It is entirely plausible that an NMI might // attempt to print out some sort of record of what happened. // // so we'll have to poll the "data register empty" flag ourselves. // If it is set, pretend an interrupt has happened and call the handler // to free up space for us. // -Spence 10/23/20 // Invoke interrupt handler only if conditions data register is empty if ((*_hwserial_module).STATUS & USART_DREIF_bm) { if (_tx_buffer_head == _tx_buffer_tail) { // Buffer empty, so disable "data register empty" interrupt (*_hwserial_module).CTRLA &= (~USART_DREIE_bm); return; } #if !(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_TX_BUFFER_SIZE == 256 || SERIAL_TX_BUFFER_SIZE == 128 || SERIAL_TX_BUFFER_SIZE == 64 || SERIAL_TX_BUFFER_SIZE == 32 || SERIAL_TX_BUFFER_SIZE == 16)) _tx_data_empty_irq(*this); #else // We're using ASM DRE #ifdef USART1 void * thisSerial = this; #endif __asm__ __volatile__( "clt" "\n\t" // Clear the T flag to signal to the ISR that we got there from here. This is safe per the ABI - The T-flag can be treated like R0 #if PROGMEM_SIZE > 8192 "jmp _poll_dre" "\n\t" #else "rjmp _poll_dre" "\n\t" #endif "_poll_dre_done:" "\n" #ifdef USART1 ::"z"((uint16_t)thisSerial) #else ::"z"(&Serial0) #endif : "r18","r19","r24","r25","r26","r27"); // these got saved and restored in the ISR context, but here we don't need top and in many cases no action is needed. // the Y pointer was already handled, because as a call-saved register, it would always need to be saved and restored, so we save 4 words of flash by doing that after // jumps into the middle of the ISR, and before it jumps back here. #endif } } // In case interrupts are enabled, the interrupt routine will be invoked by itself // Note that this currently does not handle cases where the DRE interruopt becomes // disabled, yet you are actually attempting to send. I don't think it can happen. } /*### # # #### # ### ### # # #### ##### # # ### #### ### # # # # # # # # # ## ## # # # # # # # # # #### # # #### # # # # # # ### # ##### # # # # ### # # # # # # # # # # # # # # # # # # # # ### #### #### ### ### # # #### # # # ### #### ##*/ // Invoke this function before 'begin' to define the pins used bool HardwareSerial::pins(uint8_t tx, uint8_t rx) { uint8_t ret_val = _pins_to_swap(_module_number, tx, rx); // return 127 when correct swap number wasn't found return swap(ret_val); } uint8_t HardwareSerial::_pins_to_swap(uint8_t port_num, uint8_t tx_pin, uint8_t rx_pin) { if (tx_pin == NOT_A_PIN && rx_pin == NOT_A_PIN) { return 128; // get MUX_NONE } else { const uint8_t * muxtab_ptr = _usart_pins[port_num]; if (*muxtab_ptr == tx_pin && (*(muxtab_ptr + 1) == rx_pin)) { return 0; } #if !defined(__AVR_ATtinyx24__) if ((*(muxtab_ptr + 4)) == tx_pin && (*(muxtab_ptr + 5) == rx_pin)) #else if (port_num && (*(muxtab_ptr + 4)) == tx_pin && (*(muxtab_ptr + 5) == rx_pin)) #endif { return 1; } return NOT_A_MUX; // At this point, we have checked all group codes for this peripheral. It ain't there. Return NOT_A_MUX. } } uint8_t HardwareSerial::getPin(uint8_t pin) { if (pin >3) return NOT_A_PIN; return (_usart_pins[_module_number + _pin_set][pin]); } bool HardwareSerial::swap(uint8_t newmux) { #if !(MEGATINYCORE_SERIES == 2 && defined(__ATtinyxy4__)) // it's either a 0/1-series: They have options of 0 and 1. // Or it's a 2-series with 20 or 24 pins. Both USARTS have option of 0 & 1. if (newmux < 2) { _pin_set = newmux; return true; } #else // means it is a 14-pin 2-series, whose second USART doesn't have an alternate location. if (_module_number + newmux < 2) { _pin_set = newmux; return true; } #endif #if MEGATINYCORE_SERIES == 2 else if (newmux == MUX_NONE) { // 128 codes for MUX_NONE _pin_set = 3; return true; } #endif else { _pin_set = 0; } return false; } void HardwareSerial::begin(unsigned long baud, uint16_t options) { // Make sure no transmissions are ongoing and USART is disabled in case begin() is called by accident // without first calling end() if (_state & 1) { this->end(); } uint8_t ctrlc = (uint8_t) options; if (ctrlc == 0) { // see if they passed anything in low byte or SERIAL_CONFIG_VALID. ctrlc = (uint8_t)SERIAL_8N1; // low byte of 0 could mean they want SERIAL_5N1. Or that they thought they'd } ctrlc &= ~0x04; // Now unset that 0x04 bit if it's set, because none of the values with it set are supported. We use that to smuggle in a "this constant was specified" for 5N1 uint8_t ctrla = (uint8_t) (options >> 8);// CTRLA will get the remains of the options high byte. uint16_t baud_setting = 0; // at this point it should be able to reuse those 2 registers that it received options in! uint8_t ctrlb = (~ctrla & 0xC0); // Top two bits (TXEN RXEN), inverted so they match he sense in the registers. if (baud > F_CPU / 16) { // if this baud is too fast for non-U2X ctrlb |= USART_RXMODE0_bm; // set the U2X bit in what will become CTRLB baud >>= 1; // And lower the baud rate by haldf } baud_setting = (((4 * F_CPU) / baud)); // And now the registers that baud was passed in are done. if (baud_setting < 64) // so set to the maximum baud rate setting. baud_setting= 64; // set the U2X bit in what will become CTRLB //} else if (baud < (F_CPU / 16800)) { // Baud rate is too low // baud_setting = 65535; // minimum baud rate.' // Baud setting done now we do the other options not in CTRLC; if (ctrla & 0x04) { // is ODME option set? ctrlb |= USART_ODME_bm; // set the bit in what will become CTRLB } // Baud setting done now we do the other options. // that aren't in CTRLC; ctrla &= 0x2B; // Only LBME and RS485 (both of them); will get written to CTRLA, but we leave the event bit. if (ctrlb & USART_RXEN_bm) { // if RX is to be enabled ctrla |= USART_RXCIE_bm; // we will want to enable the ISR. } uint8_t setpinmask = ctrlb & 0xC8; // ODME in bit 3, TX and RX enabled in bit 6, 7 if ((ctrla & USART_LBME_bm) && (setpinmask == 0xC8)) { // if it's open-drain and loopback, need to set state bit 2. _state |= 2; // since that changes some behavior (RXC disabled while sending) // Now we should be able to ST _state. setpinmask |= 0x10; // this tells _set_pins not to disturb the configuration on the RX pin. } if (ctrla & USART_RS485_bm) { // RS485 mode recorded here too... because we need to set setpinmask |= 0x01; // set pin output if we need to do that. Datasheet isn't clear } uint8_t oldSREG = SREG; cli(); volatile USART_t* MyUSART = _hwserial_module; (*MyUSART).CTRLB = 0; // gotta disable first - some things are enable-locked. (*MyUSART).CTRLC = ctrlc; // No reason not to set first. (*MyUSART).BAUD = baud_setting; // Wish I could have set it long ago if (ctrla & 0x20) { // Now we have to do a bit of work setpinmask &= 0x7F; // Remove the RX pin in this case because we get the input from elsewhere. (*MyUSART).EVCTRL = 1; // enable event input - not clear from datasheet what's needed to (*MyUSART).TXPLCTRL = 0xFF; // Disable pulse length encoding. } else { (*MyUSART).EVCTRL = 0; // This needs to be turned off when not in use. } // finally strip out the SERIAL_EVENT_RX bit which is in the DREIE (*MyUSART).CTRLA = ctrla & 0xDF; // position, which we never set in begin. (*MyUSART).CTRLB = ctrlb; // Set the all important CTRLB... _set_pins(_module_number, _pin_set, setpinmask); // set up the pin(s) SREG = oldSREG; // re-enable interrupts, and we're done. } void HardwareSerial::_set_pins(uint8_t mod_nbr, uint8_t mux_set, uint8_t enmask) { // Set the mux register #if defined(PORTMUX_USARTROUTEA) uint8_t muxregval = PORTMUX.USARTROUTEA; muxregval &= ~(mod_nbr ? 0x0C : 0x03); PORTMUX.USARTROUTEA = (muxregval) | (mux_set << (mod_nbr ? 2 : 0)); // shift muxset left if needed. #else if (mux_set) { PORTMUX.CTRLB |= 0x01; // for 0/1-series this can only be zero or 1 } else { PORTMUX.CTRLB &= 0xFE; } #endif #if MEGATINYCORE_SERIES == 2 if (mux_set == 3) { // not connected to pins... return; // so we are done! } #endif const uint8_t* muxrow = &(_usart_pins[mod_nbr + mux_set][0]); if ((enmask & 0x40 && !(enmask & 0x08))) { pinMode(muxrow[0], OUTPUT); // If and only if TX is enabled and open drain isn't should the TX interrupt be used. . } else if (enmask & 0x50) { // if it is enabled but is in open drain mode, or is disabled, but loopback is enabled // TX should be INPUT_PULLUP. pinMode(muxrow[0], INPUT_PULLUP); } if (enmask & 0x80 && !(enmask & 0x10)) { // Likewise if RX is enabled, unless loopback mode is too (in which case we caught it above, it should be pulled up pinMode(muxrow[1], INPUT_PULLUP); } if (enmask & 0x01) { // finally if RS485 mode is enabled, we make XDIR output, otherwise it can't drive the pin. pinMode(muxrow[3], OUTPUT); // make XDIR output. } /* uint8_t muxrow = mod_nbr + mux_set; if ((enmask & 0x40 && !(enmask & 0x08))) { pinMode(_usart_pins[muxrow][0], OUTPUT); // If any only if TX is enabled and open drain isn't should the TX pin be output. } else if (enmask & 0x50) { // if it is enabled but is in open drain mode, or is disabled, but loopback is enabled // TX should be INPUT_PULLUP. pinMode(_usart_pins[muxrow][0], INPUT_PULLUP); } if (enmask & 0x80 && !(enmask & 0x10)) { // Likewise if RX is enabled, unless loopback mode is too (in which case we caught it above, it should be pulled up pinMode(_usart_pins[muxrow][1], INPUT_PULLUP); } if (enmask & 0x01) { // finally if RS485 mode is enabled, we make XDIR output, otherwise it can't drive the pin. pinMode(_usart_pins[muxrow][3], OUTPUT); // make XDIR output. } // And it is up to the user to configure the XCK pin as required for their application if they are using that. */ /* uint8_t muxrow = mod_nbr + mux_set; if (enmask & 0x40) { // tx enabled pinMode(_usart_pins[muxrow][0], (enmask & 0x08) ? INPUT_PULLUP : OUTPUT); } if (enmask & 0x80) { if (!(enmask & 0x10)) { pinMode(_usart_pins[muxrow][1], INPUT_PULLUP); } else if (!(enmask & 0x40)) { // Loopback mode set, TX disabled, and RX enabled. Wacky configuration, but I guess that means TX should be INPUT_PULLUP. pinMode(_usart_pins[muxrow][0], INPUT_PULLUP); } } if (enmask & 0x01) { // RS485 enabled pinMode(_usart_pins[muxrow][3], OUTPUT); // make XDIR output. } */ } void HardwareSerial::end() { // wait for transmission of outgoing data flush(); // Disable receiver and transmitter as well as the RX complete and the data register empty interrupts. // TXCIE only used in half duplex - we can just turn the damned thing off yo! volatile USART_t * temp = _hwserial_module; /* compiler does a slightly better job with this. */ temp -> CTRLB = 0; //~(USART_RXEN_bm | USART_TXEN_bm); temp -> CTRLA = 0; //~(USART_RXCIE_bm | USART_DREIE_bm | USART_TXCIE_bm); temp -> STATUS = USART_TXCIF_bm | USART_RXCIF_bm; // want to make sure no chance of that firing in error now that the USART is off. TXCIE only used in half duplex // clear any received data _rx_buffer_head = _rx_buffer_tail; // Note: Does not change output pins // though the datasheetsays turning the TX module off sets it to input. _state = 0; } int HardwareSerial::available(void) { return ((unsigned int)(SERIAL_RX_BUFFER_SIZE + _rx_buffer_head - _rx_buffer_tail)) & (SERIAL_RX_BUFFER_SIZE - 1); //% SERIAL_RX_BUFFER_SIZE; } int HardwareSerial::peek(void) { if (_rx_buffer_head == _rx_buffer_tail) { return -1; } else { return _rx_buffer[_rx_buffer_tail]; } } int HardwareSerial::read(void) { // if the head isn't ahead of the tail, we don't have any characters if (_rx_buffer_head == _rx_buffer_tail) { return -1; } else { unsigned char c = _rx_buffer[_rx_buffer_tail]; _rx_buffer_tail = (rx_buffer_index_t)(_rx_buffer_tail + 1) & (SERIAL_RX_BUFFER_SIZE - 1); // % SERIAL_RX_BUFFER_SIZE; return c; } } int HardwareSerial::availableForWrite(void) { tx_buffer_index_t head; tx_buffer_index_t tail; TX_BUFFER_ATOMIC { head = _tx_buffer_head; tail = _tx_buffer_tail; } if (head >= tail) { return SERIAL_TX_BUFFER_SIZE - 1 - head + tail; } return tail - head - 1; } void HardwareSerial::flush() { // If we have never written a byte, no need to flush. This special // case is needed since there is no way to force the TXCIF (transmit // complete) bit to 1 during initialization if (!(_state & 1)) { return; } // Check if we are inside an ISR already (e.g. connected to a different peripheral then UART), in which case the UART ISRs will not be called. // Spence 10/23/20: Changed _poll_tx_data_empty() to instead call the ISR directly in this case too // Why elevate the interrupt if we're going to go into a busywait loop checking if the interrupt is disabled and if so, check for the bit and // manually call the ISR if the bit is set... *anyway*? Plus, in write(), this mode will be enabled upon a write of a single character from an ISR // and will stay that way until the buffer is empty, which would mean that the fairly long and slow UART TX ISR would have priority over a // potentially very fast interrupt that the user may have set to priority level 1. Just because a whizz-bang feature is there doesn't mean // it's appropriate to use for applications where it has only very small benefits, and significant risk of surprising the user and causing // breakage of code that would otherwise work. Finally, the previous implementation didn't check if it was called from the current lvl1 ISR // and in that case flush(), and write() with full buffer would just straight up hang... // Spin until the data-register-empty-interrupt is disabled and TX complete interrupt flag is raised while (((*_hwserial_module).CTRLA & USART_DREIE_bm) || (!((*_hwserial_module).STATUS & USART_TXCIF_bm))) { // If interrupts are globally disabled or the and DR empty interrupt is disabled, // poll the "data register empty" interrupt flag to prevent deadlock _poll_tx_data_empty(); } // When we get here, nothing is queued anymore (DREIE is disabled) and // the hardware finished transmission (TXCIF is set). } size_t HardwareSerial::write(uint8_t c) { _state |= 1; // Record that we have written to serial since it was begun. // If the buffer and the data register is empty, just write the byte // to the data register and be done. This shortcut helps // significantly improve the effective data rate at high (> // 500kbit/s) bit rates, where interrupt overhead becomes a slowdown. if ((_tx_buffer_head == _tx_buffer_tail) && ((*_hwserial_module).STATUS & USART_DREIF_bm)) { if (_state & 2) { // in half duplex mode, we turn off RXC interrupt uint8_t ctrla = (*_hwserial_module).CTRLA; ctrla &= ~USART_RXCIE_bm; ctrla |= USART_TXCIE_bm; (*_hwserial_module).STATUS = USART_TXCIF_bm; (*_hwserial_module).CTRLA = ctrla; } else { (*_hwserial_module).STATUS = USART_TXCIF_bm; } // MUST clear TXCIF **before** writing new char, otherwise ill-timed interrupt can cause it to erase the flag after the new charchter has been sent! (*_hwserial_module).TXDATAL = c; /* I cannot figure out *HOW* the DRE could be enabled at this point (buffer empty and DRE flag up) * When the buffer was emptied, it would have turned off the DREI after it loaded the last byte. * Thus, the only possible way this could happen is if an interrupt also tried to write to serial, * *immediately* after we checked that the buffer was empty, before we made it not empty. And * in that case, without this line it would lose one of the characters... with that line, it could * stop servicing DRE until another serial write, AND lose a character. That's not better! -Spence 4/2021 * So this is to stop a race condition in which people are doing something that every guide everywhere says not to do * (writing serial from within an ISR). * I maintain that users SHOULD NOT WRITE TO SERIAL FROM AN ISR, and certainly not while the non-interrupt code is also writing! * Original comments: * // Make sure data register empty interrupt is disabled to avoid * // that the interrupt handler is called in this situation * (*_hwserial_module).CTRLA &= (~USART_DREIE_bm); */ return 1; } tx_buffer_index_t i = (_tx_buffer_head + 1) & (SERIAL_TX_BUFFER_SIZE - 1); // % SERIAL_TX_BUFFER_SIZE; // If the output buffer is full, there's nothing we can do other than to // wait for the interrupt handler to empty it a bit (or emulate interrupts) while (i == _tx_buffer_tail) { _poll_tx_data_empty(); } _tx_buffer[_tx_buffer_head] = c; _tx_buffer_head = i; if (_state & 2) { // in half duplex mode, we turn off RXC interrupt uint8_t ctrla = (*_hwserial_module).CTRLA; ctrla &= ~USART_RXCIE_bm; ctrla |= USART_TXCIE_bm | USART_DREIE_bm; (*_hwserial_module).STATUS = USART_TXCIF_bm; (*_hwserial_module).CTRLA = ctrla; } else { // Enable "data register empty interrupt" (*_hwserial_module).CTRLA |= USART_DREIE_bm; } return 1; } void HardwareSerial::printHex(const uint8_t b) { char x = (b >> 4) | '0'; if (x > '9') x += 7; write(x); x = (b & 0x0F) | '0'; if (x > '9') x += 7; write(x); } void HardwareSerial::printHex(const uint16_t w, bool swaporder) { uint8_t *ptr = (uint8_t *) &w; if (swaporder) { printHex(*(ptr++)); printHex(*(ptr)); } else { printHex(*(ptr + 1)); printHex(*(ptr)); } } void HardwareSerial::printHex(const uint32_t l, bool swaporder) { uint8_t *ptr = (uint8_t *) &l; if (swaporder) { printHex(*(ptr++)); printHex(*(ptr++)); printHex(*(ptr++)); printHex(*(ptr)); } else { ptr+=3; printHex(*(ptr--)); printHex(*(ptr--)); printHex(*(ptr--)); printHex(*(ptr)); } } uint8_t * HardwareSerial::printHex(uint8_t* p, uint8_t len, char sep) { for (byte i = 0; i < len; i++) { if (sep && i) write(sep); printHex(*p++); } println(); return p; } uint16_t * HardwareSerial::printHex(uint16_t* p, uint8_t len, char sep, bool swaporder) { for (byte i = 0; i < len; i++) { if (sep && i) write(sep); printHex(*p++, swaporder); } println(); return p; } volatile uint8_t * HardwareSerial::printHex(volatile uint8_t* p, uint8_t len, char sep) { for (byte i = 0; i < len; i++) { if (sep && i) write(sep); uint8_t t = *p++; printHex(t); } return p; } volatile uint16_t * HardwareSerial::printHex(volatile uint16_t* p, uint8_t len, char sep, bool swaporder) { for (byte i = 0; i < len; i++) { if (sep && i) write(sep); uint16_t t = *p++; printHex(t, swaporder); } return p; } #endif #endif