//go:build mimxrt1062 package machine import ( "device/nxp" "runtime/interrupt" "runtime/volatile" ) // UART peripheral abstraction layer for the MIMXRT1062 type UART struct { Bus *nxp.LPUART_Type Buffer *RingBuffer Interrupt interrupt.Interrupt // txBuffer should be allocated globally (such as when UART is created) to // prevent it being reclaimed or cleaned up prematurely. txBuffer *RingBuffer // these hold the input selector ("daisy chain") values that select which pins // are connected to the LPUART device, and should be defined where the UART // instance is declared. see the godoc comments on type muxSelect for more // details. muxRX, muxTX muxSelect // these are copied from UARTConfig, during (*UART).Configure(UARTConfig), and // should be considered read-only for internal reference (i.e., modifying them // will have no desirable effect). rx, tx Pin baud uint32 // auxiliary state data used internally configured bool transmitting volatile.Register32 } func (uart *UART) isTransmitting() bool { return uart.transmitting.Get() != 0 } func (uart *UART) startTransmitting() { uart.transmitting.Set(1) } func (uart *UART) stopTransmitting() { uart.transmitting.Set(0) } func (uart *UART) resetTransmitting() { uart.stopTransmitting() uart.Bus.GLOBAL.SetBits(nxp.LPUART_GLOBAL_RST) uart.Bus.GLOBAL.ClearBits(nxp.LPUART_GLOBAL_RST) } // Configure initializes a UART with the given UARTConfig and other default // settings. func (uart *UART) Configure(config UARTConfig) { const defaultUartFreq = 115200 // use default baud rate if not specified if config.BaudRate == 0 { config.BaudRate = defaultUartFreq } // use default UART pins if not specified if config.RX == 0 && config.TX == 0 { config.RX = UART_RX_PIN config.TX = UART_TX_PIN } uart.baud = config.BaudRate uart.rx = config.RX uart.tx = config.TX // configure the mux and pad control registers uart.rx.Configure(PinConfig{Mode: PinModeUARTRX}) uart.tx.Configure(PinConfig{Mode: PinModeUARTTX}) // configure the mux input selector uart.muxRX.connect() uart.muxTX.connect() // reset all internal logic and registers uart.resetTransmitting() // disable until we have finished configuring registers uart.Bus.CTRL.Set(0) // determine the baud rate and over-sample divisors sbr, osr := uart.getBaudRateDivisor(uart.baud) // set the baud rate, over-sample configuration, stop bits baudBits := (((osr - 1) << nxp.LPUART_BAUD_OSR_Pos) & nxp.LPUART_BAUD_OSR_Msk) | ((sbr << nxp.LPUART_BAUD_SBR_Pos) & nxp.LPUART_BAUD_SBR_Msk) if osr <= 8 { // if OSR less than or equal to 8, we must enable sampling on both edges baudBits |= nxp.LPUART_BAUD_BOTHEDGE } uart.Bus.BAUD.Set(baudBits) uart.Bus.PINCFG.Set(0) // disable triggers // configure watermarks, flush and enable TX/RX FIFOs rxSize, txSize := uart.getFIFOSize() rxWater := rxSize >> 1 if rxWater > uint32(nxp.LPUART_FIFO_RXFIFOSIZE_Msk>>nxp.LPUART_FIFO_RXFIFOSIZE_Pos) { rxWater = uint32(nxp.LPUART_FIFO_RXFIFOSIZE_Msk >> nxp.LPUART_FIFO_RXFIFOSIZE_Pos) } txWater := txSize >> 1 if txWater > uint32(nxp.LPUART_FIFO_TXFIFOSIZE_Msk>>nxp.LPUART_FIFO_TXFIFOSIZE_Pos) { txWater = uint32(nxp.LPUART_FIFO_TXFIFOSIZE_Msk >> nxp.LPUART_FIFO_TXFIFOSIZE_Pos) } uart.Bus.WATER.Set( ((rxWater << nxp.LPUART_WATER_RXWATER_Pos) & nxp.LPUART_WATER_RXWATER_Msk) | ((txWater << nxp.LPUART_WATER_TXWATER_Pos) & nxp.LPUART_WATER_TXWATER_Msk)) uart.Bus.FIFO.SetBits(nxp.LPUART_FIFO_RXFE | nxp.LPUART_FIFO_TXFE | nxp.LPUART_FIFO_RXFLUSH | nxp.LPUART_FIFO_TXFLUSH) // for now we assume some configuration. in particular: // Data bits -> 8-bit // Parity bit -> None (parity bit generation disabled) // Stop bits -> 1 stop bit // MSB first -> false // RX idle type -> idle count starts after start bit // RX idle config -> 1 idle character // RX RTS enabled -> false // TX CTS enabled -> false // enable transmitter, receiver functions uart.Bus.CTRL.Set(nxp.LPUART_CTRL_TE | nxp.LPUART_CTRL_RE | // enable receiver, idle line interrupts nxp.LPUART_CTRL_RIE | nxp.LPUART_CTRL_ILIE) // clear all status flags uart.Bus.STAT.Set(uart.Bus.STAT.Get()) // enable RX interrupt uart.Interrupt.SetPriority(0xC0) uart.Interrupt.Enable() uart.configured = true } // Disable disables the UART interface. // // If any buffered data has not yet been transmitted, Disable waits until // transmission completes before disabling the interface. The receiver UART's // interrupt is also disabled, and the RX/TX pins are reconfigured for GPIO // input (pull-up). func (uart *UART) Disable() { // first ensure the device is enabled if uart.configured { // wait for any buffered data to send uart.Sync() // stop trapping RX interrupts uart.Interrupt.Disable() // reset all internal registers uart.resetTransmitting() // disable RX/TX functions uart.Bus.CTRL.ClearBits(nxp.LPUART_CTRL_TE | nxp.LPUART_CTRL_RE) // put pins back into GPIO mode uart.rx.Configure(PinConfig{Mode: PinInputPullup}) uart.tx.Configure(PinConfig{Mode: PinInputPullup}) } uart.configured = false } // Sync blocks the calling goroutine until all data in the output buffer has // been transmitted. func (uart *UART) Sync() error { for uart.isTransmitting() { } return nil } // WriteByte writes a single byte of data to the UART interface. func (uart *UART) writeByte(c byte) error { uart.startTransmitting() for !uart.txBuffer.Put(c) { } uart.Bus.CTRL.SetBits(nxp.LPUART_CTRL_TIE) return nil } func (uart *UART) flush() {} // getBaudRateDivisor finds the greatest over-sampling factor (4..32) and // corresponding baud rate divisor (1..8191) that best partition a given baud // rate into equal intervals. // // This is an integral (non-floating point) translation of the logic at the // beginning of: // // void HardwareSerial::begin(uint32_t baud, uint16_t format) // // (from Teensyduino: cores/teensy4/HardwareSerial.cpp) // // We don't want to use floating point here in case it gets called from an ISR // or very early during system init. func (uart *UART) getBaudRateDivisor(baudRate uint32) (sbr uint32, osr uint32) { const clock = 24000000 // UART is muxed to 24 MHz OSC err := uint32(0xFFFFFFFF) sbr, osr = 0, 0 for o := uint32(4); o <= 32; o++ { s := ((clock*10)/(baudRate*o) + 5) / 10 if s == 0 { s = 1 } b := clock / (s * o) var e uint32 if b > baudRate { e = b - baudRate } else { e = baudRate - b } if e <= err { err = e osr = o sbr = s } } return sbr, osr } func (uart *UART) getFIFOSize() (rx, tx uint32) { fifo := uart.Bus.FIFO.Get() rx = uint32(1) << ((fifo & nxp.LPUART_FIFO_RXFIFOSIZE_Msk) >> nxp.LPUART_FIFO_RXFIFOSIZE_Pos) if rx > 1 { rx <<= 1 } tx = uint32(1) << ((fifo & nxp.LPUART_FIFO_TXFIFOSIZE_Msk) >> nxp.LPUART_FIFO_TXFIFOSIZE_Pos) if tx > 1 { tx <<= 1 } return rx, tx } func (uart *UART) getStatus() uint32 { return uart.Bus.STAT.Get() | ((uart.Bus.FIFO.Get() & uint32(nxp.LPUART_FIFO_TXEMPT_Msk|nxp.LPUART_FIFO_RXEMPT_Msk| nxp.LPUART_FIFO_TXOF_Msk|nxp.LPUART_FIFO_RXUF_Msk)) >> 16) } func (uart *UART) getEnabledInterrupts() uint32 { return ((uart.Bus.BAUD.Get() & uint32(nxp.LPUART_BAUD_LBKDIE_Msk|nxp.LPUART_BAUD_RXEDGIE_Msk)) >> 8) | ((uart.Bus.FIFO.Get() & uint32(nxp.LPUART_FIFO_TXOFE_Msk|nxp.LPUART_FIFO_RXUFE_Msk)) >> 8) | (uart.Bus.CTRL.Get() & uint32(0xFF0C000)) } func (uart *UART) disableInterrupts(mask uint32) { uart.Bus.BAUD.ClearBits((mask << 8) & uint32(nxp.LPUART_BAUD_LBKDIE_Msk|nxp.LPUART_BAUD_RXEDGIE_Msk)) uart.Bus.FIFO.Set((uart.Bus.FIFO.Get() & ^uint32(nxp.LPUART_FIFO_TXOF_Msk|nxp.LPUART_FIFO_RXUF_Msk)) & ^uint32((mask<<8)&(nxp.LPUART_FIFO_TXOFE_Msk|nxp.LPUART_FIFO_RXUFE_Msk))) mask &= uint32(0xFFFFFF00) uart.Bus.CTRL.ClearBits(mask) } func (uart *UART) handleInterrupt(interrupt.Interrupt) { stat := uart.getStatus() inte := uart.getEnabledInterrupts() _, txSize := uart.getFIFOSize() // check for and clear overrun, otherwise RX will not work if (stat & uint32(nxp.LPUART_STAT_OR)) != 0 { uart.Bus.STAT.Set((uart.Bus.STAT.Get() & uint32(0x3FE00000)) | nxp.LPUART_STAT_OR) } // idle or receive data register is full if (stat & uint32(nxp.LPUART_STAT_RDRF|nxp.LPUART_STAT_IDLE)) != 0 { count := (uart.Bus.WATER.Get() & uint32(nxp.LPUART_WATER_RXCOUNT_Msk)) >> nxp.LPUART_WATER_RXCOUNT_Pos for ; count > 0; count-- { // read up to 8 bits of data at a time // TODO: 7, 9, and 10-bit support? uart.Buffer.Put(uint8(uart.Bus.DATA.Get() & uint32(0xFF))) } // if it was an IDLE status, clear the flag if (stat & uint32(nxp.LPUART_STAT_IDLE)) != 0 { uart.Bus.STAT.SetBits(nxp.LPUART_STAT_IDLE) } // disable idle line interrupts uart.disableInterrupts(nxp.LPUART_CTRL_RIE | nxp.LPUART_CTRL_ORIE) } // check if we have data to write if ((inte & nxp.LPUART_CTRL_TIE) != 0) && ((stat & nxp.LPUART_STAT_TDRE) != 0) { for ((uart.Bus.WATER.Get() & uint32(nxp.LPUART_WATER_TXCOUNT_Msk)) >> nxp.LPUART_WATER_TXCOUNT_Pos) < txSize { if b, ok := uart.txBuffer.Get(); ok { uart.Bus.DATA.Set(uint32(b)) } else { break } } if uart.Bus.STAT.HasBits(nxp.LPUART_STAT_TDRE) { uart.Bus.CTRL.Set((uart.Bus.CTRL.Get() & ^uint32(nxp.LPUART_CTRL_TIE)) | nxp.LPUART_CTRL_TCIE) } } if ((inte & nxp.LPUART_CTRL_TCIE) != 0) && ((stat & nxp.LPUART_STAT_TC) != 0) { uart.stopTransmitting() uart.Bus.CTRL.ClearBits(nxp.LPUART_CTRL_TCIE) } }