diff --git a/src/examples/echo/echo.go b/src/examples/echo/echo.go index 20cae145..c20d0d35 100644 --- a/src/examples/echo/echo.go +++ b/src/examples/echo/echo.go @@ -7,27 +7,34 @@ import ( "time" ) +// change these to test a different UART or pins if available +var ( + uart = machine.UART0 + tx uint8 = machine.UART_TX_PIN + rx uint8 = machine.UART_RX_PIN +) + func main() { - machine.UART0.Configure(machine.UARTConfig{}) - machine.UART0.Write([]byte("Echo console enabled. Type something then press enter:\r\n")) + uart.Configure(machine.UARTConfig{TX: tx, RX: rx}) + uart.Write([]byte("Echo console enabled. Type something then press enter:\r\n")) input := make([]byte, 64) i := 0 for { - if machine.UART0.Buffered() > 0 { - data, _ := machine.UART0.ReadByte() + if uart.Buffered() > 0 { + data, _ := uart.ReadByte() switch data { case 13: // return key - machine.UART0.Write([]byte("\r\n")) - machine.UART0.Write([]byte("You typed: ")) - machine.UART0.Write(input[:i]) - machine.UART0.Write([]byte("\r\n")) + uart.Write([]byte("\r\n")) + uart.Write([]byte("You typed: ")) + uart.Write(input[:i]) + uart.Write([]byte("\r\n")) i = 0 default: // just echo the character - machine.UART0.WriteByte(data) + uart.WriteByte(data) input[i] = data i++ } diff --git a/src/machine/board_arduino.go b/src/machine/board_arduino.go index dbf20061..ce2f635a 100644 --- a/src/machine/board_arduino.go +++ b/src/machine/board_arduino.go @@ -16,3 +16,9 @@ const ( ADC4 = 4 // Used by TWI for SDA ADC5 = 5 // Used by TWI for SCL ) + +// UART pins +const ( + UART_TX_PIN = 1 + UART_RX_PIN = 0 +) diff --git a/src/machine/board_itsybitsy-m0.go b/src/machine/board_itsybitsy-m0.go index fca87b4f..6c369e1b 100644 --- a/src/machine/board_itsybitsy-m0.go +++ b/src/machine/board_itsybitsy-m0.go @@ -4,8 +4,8 @@ package machine // GPIO Pins const ( - D0 = 11 // RX: SERCOM0/PAD[3] - D1 = 10 // TX: SERCOM0/PAD[2] + D0 = 11 // UART0 RX: SERCOM0/PAD[3] + D1 = 10 // UART0 TX: SERCOM0/PAD[2] D2 = 14 D3 = 9 D4 = 8 @@ -14,8 +14,8 @@ const ( D7 = 21 D8 = 6 D9 = 7 - D10 = 18 - D11 = 16 + D10 = 18 // UART1 TX(1): SERCOM1/PAD[2] can be used for UART1 TX + D11 = 16 // UART1 TX(2): SERCOM1/PAD[0] can be used for UART1 TX D12 = 19 D13 = 17 ) diff --git a/src/machine/buffer.go b/src/machine/buffer.go new file mode 100644 index 00000000..437829a7 --- /dev/null +++ b/src/machine/buffer.go @@ -0,0 +1,49 @@ +package machine + +const bufferSize = 128 + +//go:volatile +type volatileByte byte + +// RingBuffer is ring buffer implementation inspired by post at +// https://www.embeddedrelated.com/showthread/comp.arch.embedded/77084-1.php +// +// It has some limitations currently due to how "volatile" variables that are +// members of a struct are not compiled correctly by TinyGo. +// See https://github.com/aykevl/tinygo/issues/151 for details. +type RingBuffer struct { + rxbuffer [bufferSize]volatileByte + head volatileByte + tail volatileByte +} + +// NewRingBuffer returns a new ring buffer. +func NewRingBuffer() *RingBuffer { + return &RingBuffer{} +} + +// Used returns how many bytes in buffer have been used. +func (rb *RingBuffer) Used() uint8 { + return uint8(rb.head - rb.tail) +} + +// Put stores a byte in the buffer. If the buffer is already +// full, the method will return false. +func (rb *RingBuffer) Put(val byte) bool { + if rb.Used() != bufferSize { + rb.head++ + rb.rxbuffer[rb.head%bufferSize] = volatileByte(val) + return true + } + return false +} + +// Get returns a byte from the buffer. If the buffer is empty, +// the method will return a false as the second value. +func (rb *RingBuffer) Get() (byte, bool) { + if rb.Used() != 0 { + rb.tail++ + return byte(rb.rxbuffer[rb.tail%bufferSize]), true + } + return 0, false +} diff --git a/src/machine/machine_atmega.go b/src/machine/machine_atmega.go index fef10a46..24fdacf8 100644 --- a/src/machine/machine_atmega.go +++ b/src/machine/machine_atmega.go @@ -211,6 +211,11 @@ func (i2c I2C) readByte() byte { return byte(*avr.TWDR) } +// UART on the AVR. +type UART struct { + Buffer *RingBuffer +} + // Configure the UART on the AVR. Defaults to 9600 baud on Arduino. func (uart UART) Configure(config UARTConfig) { if config.BaudRate == 0 { @@ -248,6 +253,6 @@ func handleUSART_RX() { // Ensure no error. if (*avr.UCSR0A & (avr.UCSR0A_FE0 | avr.UCSR0A_DOR0 | avr.UCSR0A_UPE0)) == 0 { // Put data from UDR register into buffer. - bufferPut(byte(data)) + UART0.Receive(byte(data)) } } diff --git a/src/machine/machine_atsamd21g18.go b/src/machine/machine_atsamd21g18.go index a742eb2b..4551ae08 100644 --- a/src/machine/machine_atsamd21g18.go +++ b/src/machine/machine_atsamd21g18.go @@ -297,10 +297,18 @@ func (p GPIO) setPinCfg(val sam.RegValue8) { } } -// UART +// UART on the SAMD21. +type UART struct { + Buffer *RingBuffer + Bus *sam.SERCOM_USART_Type +} + var ( // The first hardware serial port on the SAMD21. Uses the SERCOM0 interface. - UART0 = &UART{} + UART0 = UART{Bus: sam.SERCOM0_USART, Buffer: NewRingBuffer()} + + // The second hardware serial port on the SAMD21. Uses the SERCOM1 interface. + UART1 = UART{Bus: sam.SERCOM1_USART, Buffer: NewRingBuffer()} ) const ( @@ -322,20 +330,55 @@ func (uart UART) Configure(config UARTConfig) { config.BaudRate = 115200 } - // enable pins - GPIO{UART_TX_PIN}.Configure(GPIOConfig{Mode: GPIO_SERCOM}) - GPIO{UART_RX_PIN}.Configure(GPIOConfig{Mode: GPIO_SERCOM}) + // determine pins + if config.TX == 0 { + // use default pins + config.TX = UART_TX_PIN + config.RX = UART_RX_PIN + } + + // determine pads + var txpad, rxpad int + switch config.TX { + case UART_TX_PIN: + txpad = sercomTXPad2 + case D10: + txpad = sercomTXPad2 + case D11: + txpad = sercomTXPad0 + default: + panic("Invalid TX pin for UART") + } + + switch config.RX { + case UART_RX_PIN: + rxpad = sercomRXPad3 + case D10: + rxpad = sercomRXPad2 + case D11: + rxpad = sercomRXPad0 + case D12: + rxpad = sercomRXPad3 + case D13: + rxpad = sercomRXPad1 + default: + panic("Invalid RX pin for UART") + } + + // configure pins + GPIO{config.TX}.Configure(GPIOConfig{Mode: GPIO_SERCOM}) + GPIO{config.RX}.Configure(GPIOConfig{Mode: GPIO_SERCOM}) // reset SERCOM0 - sam.SERCOM0_USART.CTRLA |= sam.SERCOM_USART_CTRLA_SWRST - for (sam.SERCOM0_USART.CTRLA&sam.SERCOM_USART_CTRLA_SWRST) > 0 || - (sam.SERCOM0_USART.SYNCBUSY&sam.SERCOM_USART_SYNCBUSY_SWRST) > 0 { + uart.Bus.CTRLA |= sam.SERCOM_USART_CTRLA_SWRST + for (uart.Bus.CTRLA&sam.SERCOM_USART_CTRLA_SWRST) > 0 || + (uart.Bus.SYNCBUSY&sam.SERCOM_USART_SYNCBUSY_SWRST) > 0 { } // set UART mode/sample rate // SERCOM_USART_CTRLA_MODE(mode) | // SERCOM_USART_CTRLA_SAMPR(sampleRate); - sam.SERCOM0_USART.CTRLA = (sam.SERCOM_USART_CTRLA_MODE_USART_INT_CLK << sam.SERCOM_USART_CTRLA_MODE_Pos) | + uart.Bus.CTRLA = (sam.SERCOM_USART_CTRLA_MODE_USART_INT_CLK << sam.SERCOM_USART_CTRLA_MODE_Pos) | (1 << sam.SERCOM_USART_CTRLA_SAMPR_Pos) // sample rate of 16x // Set baud rate @@ -344,39 +387,44 @@ func (uart UART) Configure(config UARTConfig) { // setup UART frame // SERCOM_USART_CTRLA_FORM( (parityMode == SERCOM_NO_PARITY ? 0 : 1) ) | // dataOrder << SERCOM_USART_CTRLA_DORD_Pos; - sam.SERCOM0_USART.CTRLA |= (0 << sam.SERCOM_USART_CTRLA_FORM_Pos) | // no parity + uart.Bus.CTRLA |= (0 << sam.SERCOM_USART_CTRLA_FORM_Pos) | // no parity (lsbFirst << sam.SERCOM_USART_CTRLA_DORD_Pos) // data order // set UART stop bits/parity // SERCOM_USART_CTRLB_CHSIZE(charSize) | // nbStopBits << SERCOM_USART_CTRLB_SBMODE_Pos | // (parityMode == SERCOM_NO_PARITY ? 0 : parityMode) << SERCOM_USART_CTRLB_PMODE_Pos; //If no parity use default value - sam.SERCOM0_USART.CTRLB |= (0 << sam.SERCOM_USART_CTRLB_CHSIZE_Pos) | // 8 bits is 0 + uart.Bus.CTRLB |= (0 << sam.SERCOM_USART_CTRLB_CHSIZE_Pos) | // 8 bits is 0 (0 << sam.SERCOM_USART_CTRLB_SBMODE_Pos) | // 1 stop bit is zero (0 << sam.SERCOM_USART_CTRLB_PMODE_Pos) // no parity // set UART pads. This is not same as pins... // SERCOM_USART_CTRLA_TXPO(txPad) | // SERCOM_USART_CTRLA_RXPO(rxPad); - sam.SERCOM0_USART.CTRLA |= (sercomTXPad2 << sam.SERCOM_USART_CTRLA_TXPO_Pos) | - (sercomRXPad3 << sam.SERCOM_USART_CTRLA_RXPO_Pos) + uart.Bus.CTRLA |= sam.RegValue((txpad << sam.SERCOM_USART_CTRLA_TXPO_Pos) | + (rxpad << sam.SERCOM_USART_CTRLA_RXPO_Pos)) // Enable Transceiver and Receiver //sercom->USART.CTRLB.reg |= SERCOM_USART_CTRLB_TXEN | SERCOM_USART_CTRLB_RXEN ; - sam.SERCOM0_USART.CTRLB |= (sam.SERCOM_USART_CTRLB_TXEN | sam.SERCOM_USART_CTRLB_RXEN) + uart.Bus.CTRLB |= (sam.SERCOM_USART_CTRLB_TXEN | sam.SERCOM_USART_CTRLB_RXEN) // Enable USART1 port. // sercom->USART.CTRLA.bit.ENABLE = 0x1u; - sam.SERCOM0_USART.CTRLA |= sam.SERCOM_USART_CTRLA_ENABLE - for (sam.SERCOM0_USART.SYNCBUSY & sam.SERCOM_USART_SYNCBUSY_ENABLE) > 0 { + uart.Bus.CTRLA |= sam.SERCOM_USART_CTRLA_ENABLE + for (uart.Bus.SYNCBUSY & sam.SERCOM_USART_SYNCBUSY_ENABLE) > 0 { } // setup interrupt on receive - sam.SERCOM0_USART.INTENSET = sam.SERCOM_USART_INTENSET_RXC + uart.Bus.INTENSET = sam.SERCOM_USART_INTENSET_RXC // Enable RX IRQ. - //arm.SetPriority(sam.IRQ_SERCOM0, 0xc0) - arm.EnableIRQ(sam.IRQ_SERCOM0) + if config.TX == UART_TX_PIN { + // UART0 + arm.EnableIRQ(sam.IRQ_SERCOM0) + } else { + // UART1 + arm.EnableIRQ(sam.IRQ_SERCOM1) + } } // SetBaudRate sets the communication speed for the UART. @@ -389,24 +437,31 @@ func (uart UART) SetBaudRate(br uint32) { // sercom->USART.BAUD.FRAC.FP = (baudTimes8 % 8); // sercom->USART.BAUD.FRAC.BAUD = (baudTimes8 / 8); - sam.SERCOM0_USART.BAUD = sam.RegValue16(((baud % 8) << sam.SERCOM_USART_BAUD_FRAC_MODE_FP_Pos) | + uart.Bus.BAUD = sam.RegValue16(((baud % 8) << sam.SERCOM_USART_BAUD_FRAC_MODE_FP_Pos) | ((baud / 8) << sam.SERCOM_USART_BAUD_FRAC_MODE_BAUD_Pos)) } // WriteByte writes a byte of data to the UART. func (uart UART) WriteByte(c byte) error { // wait until ready to receive - for (sam.SERCOM0_USART.INTFLAG & sam.SERCOM_USART_INTFLAG_DRE) == 0 { + for (uart.Bus.INTFLAG & sam.SERCOM_USART_INTFLAG_DRE) == 0 { } - sam.SERCOM0_USART.DATA = sam.RegValue16(c) + uart.Bus.DATA = sam.RegValue16(c) return nil } //go:export SERCOM0_IRQHandler func handleUART0() { // should reset IRQ - bufferPut(byte((sam.SERCOM0_USART.DATA & 0xFF))) - sam.SERCOM0_USART.INTFLAG |= sam.SERCOM_USART_INTFLAG_RXC + UART0.Receive(byte((UART0.Bus.DATA & 0xFF))) + UART0.Bus.INTFLAG |= sam.SERCOM_USART_INTFLAG_RXC +} + +//go:export SERCOM1_IRQHandler +func handleUART1() { + // should reset IRQ + UART1.Receive(byte((UART1.Bus.DATA & 0xFF))) + UART1.Bus.INTFLAG |= sam.SERCOM_USART_INTFLAG_RXC } // I2C on the SAMD21. diff --git a/src/machine/machine_attiny.go b/src/machine/machine_attiny.go index 23ed589b..454d4a29 100644 --- a/src/machine/machine_attiny.go +++ b/src/machine/machine_attiny.go @@ -25,6 +25,12 @@ func (p GPIO) Get() bool { return (val > 0) } +// UART on the AVR is a dummy implementation. UART has not been implemented for ATtiny +// devices. +type UART struct { + Buffer *RingBuffer +} + // Configure is a dummy implementation. UART has not been implemented for ATtiny // devices. func (uart UART) Configure(config UARTConfig) { diff --git a/src/machine/machine_avr.go b/src/machine/machine_avr.go index c7a97622..6331960c 100644 --- a/src/machine/machine_avr.go +++ b/src/machine/machine_avr.go @@ -92,5 +92,5 @@ var I2C0 = I2C{} // UART var ( // UART0 is the hardware serial port on the AVR. - UART0 = &UART{} + UART0 = UART{Buffer: NewRingBuffer()} ) diff --git a/src/machine/machine_nrf.go b/src/machine/machine_nrf.go index 34bd78b2..8ec578c4 100644 --- a/src/machine/machine_nrf.go +++ b/src/machine/machine_nrf.go @@ -54,10 +54,15 @@ func (p GPIO) Get() bool { return (port.IN>>pin)&1 != 0 } +// UART on the NRF. +type UART struct { + Buffer *RingBuffer +} + // UART var ( // UART0 is the hardware serial port on the NRF. - UART0 = &UART{} + UART0 = UART{Buffer: NewRingBuffer()} ) // Configure the UART. @@ -108,7 +113,7 @@ func (uart UART) WriteByte(c byte) error { func (uart UART) handleInterrupt() { if nrf.UART0.EVENTS_RXDRDY != 0 { - bufferPut(byte(nrf.UART0.RXD)) + uart.Receive(byte(nrf.UART0.RXD)) nrf.UART0.EVENTS_RXDRDY = 0x0 } } diff --git a/src/machine/machine_stm32f103xx.go b/src/machine/machine_stm32f103xx.go index c10df792..99b121db 100644 --- a/src/machine/machine_stm32f103xx.go +++ b/src/machine/machine_stm32f103xx.go @@ -100,11 +100,15 @@ func (p GPIO) Set(high bool) { } // UART +type UART struct { + Buffer *RingBuffer +} + var ( // USART1 is the first hardware serial port on the STM32. - // Both UART0 and UART1 refers to USART1. - UART0 = &UART{} - UART1 = UART0 + // Both UART0 and UART1 refer to USART1. + UART0 = UART{Buffer: NewRingBuffer()} + UART1 = &UART0 ) // Configure the UART. @@ -160,7 +164,7 @@ func (uart UART) WriteByte(c byte) error { //go:export USART1_IRQHandler func handleUART1() { - bufferPut(byte((stm32.USART1.DR & 0xFF))) + UART1.Receive(byte((stm32.USART1.DR & 0xFF))) } // SPI on the STM32. diff --git a/src/machine/uart.go b/src/machine/uart.go index a621bc1c..4286e9e7 100644 --- a/src/machine/uart.go +++ b/src/machine/uart.go @@ -10,8 +10,19 @@ type UARTConfig struct { RX uint8 } -type UART struct { -} +// To implement the UART interface for a board, you must declare a concrete type as follows: +// +// type UART struct { +// Buffer *RingBuffer +// } +// +// You can also add additional members to this struct depending on your implementation, +// but the *RingBuffer is required. +// When you are declaring your UARTs for your board, make sure that you also declare the +// RingBuffer using the NewRingBuffer() function when you declare your UART: +// +// UART{Buffer: NewRingBuffer()} +// // Read from the RX buffer. func (uart UART) Read(data []byte) (n int, err error) { @@ -47,41 +58,20 @@ func (uart UART) Write(data []byte) (n int, err error) { // If there is no data in the buffer, returns an error. func (uart UART) ReadByte() (byte, error) { // check if RX buffer is empty - if uart.Buffered() == 0 { + buf, ok := uart.Buffer.Get() + if !ok { return 0, errors.New("Buffer empty") } - - return bufferGet(), nil + return buf, nil } // Buffered returns the number of bytes currently stored in the RX buffer. func (uart UART) Buffered() int { - return int(bufferUsed()) + return int(uart.Buffer.Used()) } -const bufferSize = 64 - -// Minimal ring buffer implementation inspired by post at -// https://www.embeddedrelated.com/showthread/comp.arch.embedded/77084-1.php - -//go:volatile -type volatileByte byte - -var rxbuffer [bufferSize]volatileByte -var head volatileByte -var tail volatileByte - -func bufferUsed() uint8 { return uint8(head - tail) } -func bufferPut(val byte) { - if bufferUsed() != bufferSize { - head++ - rxbuffer[head%bufferSize] = volatileByte(val) - } -} -func bufferGet() byte { - if bufferUsed() != 0 { - tail++ - return byte(rxbuffer[tail%bufferSize]) - } - return 0 +// Receive handles adding data to the UART's data buffer. +// Usually called by the IRQ handler for a machine. +func (uart UART) Receive(data byte) { + uart.Buffer.Put(data) } diff --git a/src/runtime/runtime_atsamd21g18.go b/src/runtime/runtime_atsamd21g18.go index f1ee9800..40e8cff4 100644 --- a/src/runtime/runtime_atsamd21g18.go +++ b/src/runtime/runtime_atsamd21g18.go @@ -297,7 +297,7 @@ func handleRTC() { } func initUARTClock() { - // Turn on clock to SERCOM0 for Serial + // Turn on clock to SERCOM0 for UART0 sam.PM.APBCMASK |= sam.PM_APBCMASK_SERCOM0_ // Use GCLK0 for SERCOM0 aka UART0 @@ -308,6 +308,18 @@ func initUARTClock() { (sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) | sam.GCLK_CLKCTRL_CLKEN) waitForSync() + + // Turn on clock to SERCOM1 for UART1 + sam.PM.APBCMASK |= sam.PM_APBCMASK_SERCOM1_ + + // Use GCLK0 for SERCOM1 aka UART1 + // GCLK_CLKCTRL_ID( clockId ) | // Generic Clock 0 (SERCOMx) + // GCLK_CLKCTRL_GEN_GCLK0 | // Generic Clock Generator 0 is source + // GCLK_CLKCTRL_CLKEN ; + sam.GCLK.CLKCTRL = sam.RegValue16((sam.GCLK_CLKCTRL_ID_SERCOM1_CORE << sam.GCLK_CLKCTRL_ID_Pos) | + (sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) | + sam.GCLK_CLKCTRL_CLKEN) + waitForSync() } func initI2CClock() {