From 9aa50853b8b7d1b38cce05e8000cace8d0c31528 Mon Sep 17 00:00:00 2001 From: ardnew Date: Wed, 21 Oct 2020 14:01:15 -0500 Subject: [PATCH] teensy40: add UART support --- src/machine/board_teensy40.go | 100 +++++++- src/machine/machine_mimxrt1062_uart.go | 317 +++++++++++++++++++++++++ src/machine/uart.go | 2 +- src/runtime/runtime_mimxrt1062.go | 10 +- 4 files changed, 425 insertions(+), 4 deletions(-) create mode 100644 src/machine/machine_mimxrt1062_uart.go diff --git a/src/machine/board_teensy40.go b/src/machine/board_teensy40.go index ffda21ab..c5ed90d6 100644 --- a/src/machine/board_teensy40.go +++ b/src/machine/board_teensy40.go @@ -2,6 +2,11 @@ package machine +import ( + "device/nxp" + "runtime/interrupt" +) + // Digital pins const ( // = Pin // [Pad]: Alt Func 0 Alt Func 1 Alt Func 2 Alt Func 3 Alt Func 4 Alt Func 5 Alt Func 6 Alt Func 7 Alt Func 8 Alt Func 9 @@ -83,6 +88,17 @@ const ( I2C_SCL_PIN = I2C1_SCL_PIN // D19/A5 ) +func init() { + // register any interrupt handlers for this board's peripherals + UART1.Interrupt = interrupt.New(nxp.IRQ_LPUART6, UART1.handleInterrupt) + UART2.Interrupt = interrupt.New(nxp.IRQ_LPUART4, UART2.handleInterrupt) + UART3.Interrupt = interrupt.New(nxp.IRQ_LPUART2, UART3.handleInterrupt) + UART4.Interrupt = interrupt.New(nxp.IRQ_LPUART3, UART4.handleInterrupt) + UART5.Interrupt = interrupt.New(nxp.IRQ_LPUART8, UART5.handleInterrupt) + UART6.Interrupt = interrupt.New(nxp.IRQ_LPUART1, UART6.handleInterrupt) + UART7.Interrupt = interrupt.New(nxp.IRQ_LPUART7, UART7.handleInterrupt) +} + // #=====================================================# // | UART | // #===========#===========#=============#===============# @@ -119,8 +135,88 @@ const ( UART7_TX_PIN = D29 ) -// #==================================================================# -// | SPI | +var ( + UART1 = UART{ + Buffer: NewRingBuffer(), + Bus: nxp.LPUART6, + muxRX: muxSelect{ // D0 (PA3 [AD_B0_03]) + mux: nxp.IOMUXC_LPUART6_RX_SELECT_INPUT_DAISY_GPIO_AD_B0_03_ALT2, + sel: &nxp.IOMUXC.LPUART6_RX_SELECT_INPUT, + }, + muxTX: muxSelect{ // D1 (PA2 [AD_B0_02]) + mux: nxp.IOMUXC_LPUART6_TX_SELECT_INPUT_DAISY_GPIO_AD_B0_02_ALT2, + sel: &nxp.IOMUXC.LPUART6_TX_SELECT_INPUT, + }, + } + UART2 = UART{ + Buffer: NewRingBuffer(), + Bus: nxp.LPUART4, + muxRX: muxSelect{ // D7 (PB17 [B1_01]) + mux: nxp.IOMUXC_LPUART4_RX_SELECT_INPUT_DAISY_GPIO_B1_01_ALT2, + sel: &nxp.IOMUXC.LPUART4_RX_SELECT_INPUT, + }, + muxTX: muxSelect{ // D8 (PB16 [B1_00]) + mux: nxp.IOMUXC_LPUART4_TX_SELECT_INPUT_DAISY_GPIO_B1_00_ALT2, + sel: &nxp.IOMUXC.LPUART4_TX_SELECT_INPUT, + }, + } + UART3 = UART{ + Buffer: NewRingBuffer(), + Bus: nxp.LPUART2, + muxRX: muxSelect{ // D15 (PA19 [AD_B1_03]) + mux: nxp.IOMUXC_LPUART2_RX_SELECT_INPUT_DAISY_GPIO_AD_B1_03_ALT2, + sel: &nxp.IOMUXC.LPUART2_RX_SELECT_INPUT, + }, + muxTX: muxSelect{ // D14 (PA18 [AD_B1_02]) + mux: nxp.IOMUXC_LPUART2_TX_SELECT_INPUT_DAISY_GPIO_AD_B1_02_ALT2, + sel: &nxp.IOMUXC.LPUART2_TX_SELECT_INPUT, + }, + } + UART4 = UART{ + Buffer: NewRingBuffer(), + Bus: nxp.LPUART3, + muxRX: muxSelect{ // D16 (PA23 [AD_B1_07]) + mux: nxp.IOMUXC_LPUART3_RX_SELECT_INPUT_DAISY_GPIO_AD_B1_07_ALT2, + sel: &nxp.IOMUXC.LPUART3_RX_SELECT_INPUT, + }, + muxTX: muxSelect{ // D17 (PA22 [AD_B1_06]) + mux: nxp.IOMUXC_LPUART3_TX_SELECT_INPUT_DAISY_GPIO_AD_B1_06_ALT2, + sel: &nxp.IOMUXC.LPUART3_TX_SELECT_INPUT, + }, + } + UART5 = UART{ + Buffer: NewRingBuffer(), + Bus: nxp.LPUART8, + muxRX: muxSelect{ // D21 (PA27 [AD_B1_11]) + mux: nxp.IOMUXC_LPUART8_RX_SELECT_INPUT_DAISY_GPIO_AD_B1_11_ALT2, + sel: &nxp.IOMUXC.LPUART8_RX_SELECT_INPUT, + }, + muxTX: muxSelect{ // D20 (PA26 [AD_B1_10]) + mux: nxp.IOMUXC_LPUART8_TX_SELECT_INPUT_DAISY_GPIO_AD_B1_10_ALT2, + sel: &nxp.IOMUXC.LPUART8_TX_SELECT_INPUT, + }, + } + UART6 = UART{ + Buffer: NewRingBuffer(), + Bus: nxp.LPUART1, + // LPUART1 not connected via IOMUXC + // RX: D24 (PA12 [AD_B0_12]) + // TX: D25 (PA13 [AD_B0_13]) + } + UART7 = UART{ + Buffer: NewRingBuffer(), + Bus: nxp.LPUART7, + muxRX: muxSelect{ // D28 (PC18 [EMC_32]) + mux: nxp.IOMUXC_LPUART7_RX_SELECT_INPUT_DAISY_GPIO_EMC_32_ALT2, + sel: &nxp.IOMUXC.LPUART7_RX_SELECT_INPUT, + }, + muxTX: muxSelect{ // D29 (PD31 [EMC_31]) + mux: nxp.IOMUXC_LPUART7_TX_SELECT_INPUT_DAISY_GPIO_EMC_31_ALT2, + sel: &nxp.IOMUXC.LPUART7_TX_SELECT_INPUT, + }, + } +) + // #===========#==========#===============#===========================# // | Interface | Hardware | Clock(Freq) | SDI/SDO/SCK/CS : Alt | // #===========#==========#===============#=================-=========# diff --git a/src/machine/machine_mimxrt1062_uart.go b/src/machine/machine_mimxrt1062_uart.go new file mode 100644 index 00000000..56107b38 --- /dev/null +++ b/src/machine/machine_mimxrt1062_uart.go @@ -0,0 +1,317 @@ +// +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 + + // 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 + msbFirst bool + transmitting volatile.Register32 + txBuffer *RingBuffer +} + +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 + } + uart.baud = config.BaudRate + + // 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.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() + + // determine the baud rate and over-sample divisors + sbr, osr := uart.getBaudRateDivisor(uart.baud) + + // 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 + + // 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) | + ((nxp.LPUART_BAUD_SBNS_SBNS_0 << nxp.LPUART_BAUD_SBNS_Pos) & nxp.LPUART_BAUD_SBNS_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 + + // use 8 data bits, disable parity, use 1 idle char, and idle count starts + // after start bit + ctrlBits := uint32(((nxp.LPUART_CTRL_M_M_0 << nxp.LPUART_CTRL_M_Pos) & nxp.LPUART_CTRL_M_Msk) | + ((nxp.LPUART_CTRL_PE_PE_0 << nxp.LPUART_CTRL_PE_Pos) & nxp.LPUART_CTRL_PE_Msk) | + ((nxp.LPUART_CTRL_ILT_ILT_0 << nxp.LPUART_CTRL_ILT_Pos) & nxp.LPUART_CTRL_ILT_Msk) | + ((nxp.LPUART_CTRL_IDLECFG_IDLECFG_0 << nxp.LPUART_CTRL_IDLECFG_Pos) & nxp.LPUART_CTRL_IDLECFG_Msk)) + uart.Bus.CTRL.Set(ctrlBits) + + 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)) + + // enable TX/RX FIFOs + uart.Bus.FIFO.SetBits(nxp.LPUART_FIFO_RXFE | nxp.LPUART_FIFO_TXFE) + + // flush TX/RX FIFOs + uart.Bus.FIFO.SetBits(nxp.LPUART_FIFO_RXFLUSH | nxp.LPUART_FIFO_TXFLUSH) + + uart.Bus.MODIR.SetBits( // set the CTS configuration/TX CTS source + ((nxp.LPUART_MODIR_TXCTSC_TXCTSC_0 << nxp.LPUART_MODIR_TXCTSC_Pos) & nxp.LPUART_MODIR_TXCTSC_Msk) | + ((nxp.LPUART_MODIR_TXCTSSRC_TXCTSSRC_0 << nxp.LPUART_MODIR_TXCTSSRC_Pos) & nxp.LPUART_MODIR_TXCTSSRC_Msk)) + + // clear all status flags + stat := uint32(nxp.LPUART_STAT_RXEDGIF_Msk | nxp.LPUART_STAT_IDLE_Msk | nxp.LPUART_STAT_OR_Msk | + nxp.LPUART_STAT_NF_Msk | nxp.LPUART_STAT_FE_Msk | nxp.LPUART_STAT_PF_Msk | + nxp.LPUART_STAT_LBKDIF_Msk | nxp.LPUART_STAT_MA1F_Msk | nxp.LPUART_STAT_MA2F_Msk) + + // set data bits order + if uart.msbFirst { + stat |= nxp.LPUART_STAT_MSBF + } else { + stat &^= nxp.LPUART_STAT_MSBF + } + + uart.Bus.STAT.SetBits(stat) + + // enable RX/TX functions + uart.Bus.CTRL.SetBits(nxp.LPUART_CTRL_TE | nxp.LPUART_CTRL_RE) + + // enable RX IRQ + uart.Interrupt.SetPriority(0xc0) + uart.Interrupt.Enable() + + uart.configured = true +} + +func (uart *UART) Disable() { + + // first ensure the device is enabled + if uart.configured { + + // wait for any buffered data to send + uart.Flush() + + // 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 +} + +// Flush blocks the calling goroutine until all data in the output buffer has +// been written out. +func (uart *UART) Flush() { + for uart.isTransmitting() { + } +} + +func (uart *UART) WriteByte(c byte) error { + if nil == uart.txBuffer { + uart.txBuffer = NewRingBuffer() + } + uart.startTransmitting() + for !uart.txBuffer.Put(c) { + } + uart.Bus.CTRL.SetBits(nxp.LPUART_CTRL_TIE) + return nil +} + +// 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 (i.e. non-floating point) port 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 risk using floating point here in the machine package in +// case it gets called before the FPU or interrupts are ready (e.g., 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) + } +} diff --git a/src/machine/uart.go b/src/machine/uart.go index c0e0f8b3..40d2d99c 100644 --- a/src/machine/uart.go +++ b/src/machine/uart.go @@ -1,4 +1,4 @@ -// +build avr esp nrf sam sifive stm32 k210 nxpmk66f18 +// +build avr esp nrf sam sifive stm32 k210 nxp package machine diff --git a/src/runtime/runtime_mimxrt1062.go b/src/runtime/runtime_mimxrt1062.go index 289da507..3d0d5747 100644 --- a/src/runtime/runtime_mimxrt1062.go +++ b/src/runtime/runtime_mimxrt1062.go @@ -5,6 +5,7 @@ package runtime import ( "device/arm" "device/nxp" + "machine" "math/bits" "unsafe" ) @@ -114,6 +115,7 @@ func initPeripherals() { initPins() // configure GPIO enablePeripheralClocks() // activate peripheral clock gates + initUART() // configure UART (initialized first for debugging) } func initPins() { @@ -124,7 +126,13 @@ func initPins() { nxp.IOMUXC_GPR.GPR29.Set(0xFFFFFFFF) } -func putchar(c byte) {} +func initUART() { + machine.UART1.Configure(machine.UARTConfig{}) +} + +func putchar(c byte) { + machine.UART1.WriteByte(c) +} func abort() { for {