diff --git a/.gitignore b/.gitignore index 84f90ddbc3..760bbf0a19 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ src/device/avr/*.ld src/device/avr/*.s src/device/nrf/*.go src/device/nrf/*.s +src/device/nxp/*.go +src/device/nxp/*.s src/device/sam/*.go src/device/sam/*.s src/device/sifive/*.go diff --git a/Makefile b/Makefile index 5a1fd02c42..0e033dfbc3 100644 --- a/Makefile +++ b/Makefile @@ -51,7 +51,7 @@ else LLVM_OPTION += '-DLLVM_ENABLE_ASSERTIONS=OFF' endif -.PHONY: all tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-avr +.PHONY: all tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-nxp gen-device-avr LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines coverage debuginfodwarf executionengine frontendopenmp instrumentation interpreter ipo irreader linker lto mc mcjit objcarcopts option profiledata scalaropts support target @@ -118,7 +118,7 @@ fmt-check: @unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1 -gen-device: gen-device-avr gen-device-nrf gen-device-sam gen-device-sifive gen-device-stm32 gen-device-kendryte +gen-device: gen-device-avr gen-device-nrf gen-device-sam gen-device-sifive gen-device-stm32 gen-device-kendryte gen-device-nxp gen-device-avr: $(GO) build -o ./build/gen-device-avr ./tools/gen-device-avr/ @@ -133,6 +133,10 @@ gen-device-nrf: build/gen-device-svd ./build/gen-device-svd -source=https://github.com/NordicSemiconductor/nrfx/tree/master/mdk lib/nrfx/mdk/ src/device/nrf/ GO111MODULE=off $(GO) fmt ./src/device/nrf +gen-device-nxp: build/gen-device-svd + ./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/NXP lib/cmsis-svd/data/NXP/ src/device/nxp/ + GO111MODULE=off $(GO) fmt ./src/device/nxp + gen-device-sam: build/gen-device-svd ./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Atmel lib/cmsis-svd/data/Atmel/ src/device/sam/ GO111MODULE=off $(GO) fmt ./src/device/sam diff --git a/README.md b/README.md index 1c8ab784f0..24231630ec 100644 --- a/README.md +++ b/README.md @@ -140,3 +140,5 @@ The original reasoning was: if [Python](https://micropython.org/) can run on mic This project is licensed under the BSD 3-clause license, just like the [Go project](https://golang.org/LICENSE) itself. Some code has been copied from the LLVM project and is therefore licensed under [a variant of the Apache 2.0 license](http://releases.llvm.org/10.0.0/LICENSE.TXT). This has been clearly indicated in the header of these files. + +Some code has been copied and/or ported from Paul Stoffregen's Teensy libraries and is therefore licensed under PJRC's license. This has been clearly indicated in the header of these files. diff --git a/src/device/arm/arm.go b/src/device/arm/arm.go index 45226dab41..d7ccaab7e9 100644 --- a/src/device/arm/arm.go +++ b/src/device/arm/arm.go @@ -174,6 +174,11 @@ func EnableIRQ(irq uint32) { NVIC.ISER[irq>>5].Set(1 << (irq & 0x1F)) } +// Disable the given interrupt number. +func DisableIRQ(irq uint32) { + NVIC.ICER[irq>>5].Set(1 << (irq & 0x1F)) +} + // Set the priority of the given interrupt number. // Note that the priority is given as a 0-255 number, where some of the lower // bits are not implemented by the hardware. For example, to set a low interrupt diff --git a/src/machine/board_teensy36.go b/src/machine/board_teensy36.go new file mode 100644 index 0000000000..f0eccecd3d --- /dev/null +++ b/src/machine/board_teensy36.go @@ -0,0 +1,101 @@ +// +build nxp,mk66f18,teensy36 + +package machine + +// CPUFrequency returns the frequency of the ARM core clock (180MHz) +func CPUFrequency() uint32 { return 180000000 } + +// ClockFrequency returns the frequency of the external oscillator (16MHz) +func ClockFrequency() uint32 { return 16000000 } + +// LED on the Teensy +const LED = PC05 + +// digital IO +const ( + D00 = PB16 + D01 = PB17 + D02 = PD00 + D03 = PA12 + D04 = PA13 + D05 = PD07 + D06 = PD04 + D07 = PD02 + D08 = PD03 + D09 = PC03 + D10 = PC04 + D11 = PC06 + D12 = PC07 + D13 = PC05 + D14 = PD01 + D15 = PC00 + D16 = PB00 + D17 = PB01 + D18 = PB03 + D19 = PB02 + D20 = PD05 + D21 = PD06 + D22 = PC01 + D23 = PC02 + D24 = PE26 + D25 = PA05 + D26 = PA14 + D27 = PA15 + D28 = PA16 + D29 = PB18 + D30 = PB19 + D31 = PB10 + D32 = PB11 + D33 = PE24 + D34 = PE25 + D35 = PC08 + D36 = PC09 + D37 = PC10 + D38 = PC11 + D39 = PA17 + D40 = PA28 + D41 = PA29 + D42 = PA26 + D43 = PB20 + D44 = PB22 + D45 = PB23 + D46 = PB21 + D47 = PD08 + D48 = PD09 + D49 = PB04 + D50 = PB05 + D51 = PD14 + D52 = PD13 + D53 = PD12 + D54 = PD15 + D55 = PD11 + D56 = PE10 + D57 = PE11 + D58 = PE00 + D59 = PE01 + D60 = PE02 + D61 = PE03 + D62 = PE04 + D63 = PE05 +) + +var ( + TeensyUART1 = &UART0 + TeensyUART2 = &UART1 + TeensyUART3 = &UART2 + TeensyUART4 = &UART3 + TeensyUART5 = &UART4 +) + +const ( + defaultUART0RX = D00 + defaultUART0TX = D01 + defaultUART1RX = D09 + defaultUART1TX = D10 + defaultUART2RX = D07 + defaultUART2TX = D08 + defaultUART3RX = D31 + defaultUART3TX = D32 + defaultUART4RX = D34 + defaultUART4TX = D33 +) diff --git a/src/machine/buffer.go b/src/machine/buffer.go index ee12b9bcc3..1528c25be0 100644 --- a/src/machine/buffer.go +++ b/src/machine/buffer.go @@ -44,3 +44,9 @@ func (rb *RingBuffer) Get() (byte, bool) { } return 0, false } + +// Clear resets the head and tail pointer to zero. +func (rb *RingBuffer) Clear() { + rb.head.Set(0) + rb.tail.Set(0) +} diff --git a/src/machine/machine_nxpmk66f18.go b/src/machine/machine_nxpmk66f18.go new file mode 100644 index 0000000000..3804b67b37 --- /dev/null +++ b/src/machine/machine_nxpmk66f18.go @@ -0,0 +1,284 @@ +// Derivative work of Teensyduino Core Library +// http://www.pjrc.com/teensy/ +// Copyright (c) 2017 PJRC.COM, LLC. +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// 1. The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// 2. If the Software is incorporated into a build system that allows +// selection among a list of target devices, then similar target +// devices manufactured by PJRC.COM must be included in the list of +// target devices and selectable in the same manner. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +// BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// +build nxp,mk66f18 + +package machine + +import ( + "device/nxp" + "runtime/volatile" + "unsafe" +) + +type PinMode uint8 + +const ( + PinInput PinMode = iota + PinInputPullUp + PinInputPullDown + PinOutput + PinOutputOpenDrain + PinDisable +) + +const ( + PA00 Pin = iota + PA01 + PA02 + PA03 + PA04 + PA05 + PA06 + PA07 + PA08 + PA09 + PA10 + PA11 + PA12 + PA13 + PA14 + PA15 + PA16 + PA17 + PA18 + PA19 + PA20 + PA21 + PA22 + PA23 + PA24 + PA25 + PA26 + PA27 + PA28 + PA29 +) + +const ( + PB00 Pin = iota + 32 + PB01 + PB02 + PB03 + PB04 + PB05 + PB06 + PB07 + PB08 + PB09 + PB10 + PB11 + _ + _ + _ + _ + PB16 + PB17 + PB18 + PB19 + PB20 + PB21 + PB22 + PB23 +) + +const ( + PC00 Pin = iota + 64 + PC01 + PC02 + PC03 + PC04 + PC05 + PC06 + PC07 + PC08 + PC09 + PC10 + PC11 + PC12 + PC13 + PC14 + PC15 + PC16 + PC17 + PC18 + PC19 +) + +const ( + PD00 Pin = iota + 96 + PD01 + PD02 + PD03 + PD04 + PD05 + PD06 + PD07 + PD08 + PD09 + PD10 + PD11 + PD12 + PD13 + PD14 + PD15 +) + +const ( + PE00 Pin = iota + 128 + PE01 + PE02 + PE03 + PE04 + PE05 + PE06 + PE07 + PE08 + PE09 + PE10 + PE11 + PE12 + PE13 + PE14 + PE15 + PE16 + PE17 + PE18 + PE19 + PE20 + PE21 + PE22 + PE23 + PE24 + PE25 + PE26 + PE27 + PE28 +) + +//go:inline +func (p Pin) reg() (*nxp.GPIO_Type, *volatile.Register32, uint8) { + var gpio *nxp.GPIO_Type + var pcr *nxp.PORT_Type + + if p < 32 { + gpio, pcr = nxp.GPIOA, nxp.PORTA + } else if p < 64 { + gpio, pcr = nxp.GPIOB, nxp.PORTB + } else if p < 96 { + gpio, pcr = nxp.GPIOC, nxp.PORTC + } else if p < 128 { + gpio, pcr = nxp.GPIOD, nxp.PORTD + } else if p < 160 { + gpio, pcr = nxp.GPIOE, nxp.PORTE + } else { + panic("invalid pin number") + } + + return gpio, &(*[32]volatile.Register32)(unsafe.Pointer(pcr))[p%32], uint8(p % 32) +} + +// Configure this pin with the given configuration. +func (p Pin) Configure(config PinConfig) { + gpio, pcr, pos := p.reg() + + switch config.Mode { + case PinOutput: + gpio.PDDR.SetBits(1 << pos) + pcr.Set((1 << nxp.PORT_PCR0_MUX_Pos) | nxp.PORT_PCR0_SRE | nxp.PORT_PCR0_DSE) + + case PinOutputOpenDrain: + gpio.PDDR.SetBits(1 << pos) + pcr.Set((1 << nxp.PORT_PCR0_MUX_Pos) | nxp.PORT_PCR0_SRE | nxp.PORT_PCR0_DSE | nxp.PORT_PCR0_ODE) + + case PinInput: + gpio.PDDR.ClearBits(1 << pos) + pcr.Set((1 << nxp.PORT_PCR0_MUX_Pos)) + + case PinInputPullUp: + gpio.PDDR.ClearBits(1 << pos) + pcr.Set((1 << nxp.PORT_PCR0_MUX_Pos) | nxp.PORT_PCR0_PE | nxp.PORT_PCR0_PS) + + case PinInputPullDown: + gpio.PDDR.ClearBits(1 << pos) + pcr.Set((1 << nxp.PORT_PCR0_MUX_Pos) | nxp.PORT_PCR0_PE) + + case PinDisable: + gpio.PDDR.ClearBits(1 << pos) + pcr.Set((0 << nxp.PORT_PCR0_MUX_Pos)) + } +} + +// Set changes the value of the GPIO pin. The pin must be configured as output. +func (p Pin) Set(value bool) { + gpio, _, pos := p.reg() + if value { + gpio.PSOR.Set(1 << pos) + } else { + gpio.PCOR.Set(1 << pos) + } +} + +// Get returns the current value of a GPIO pin. +func (p Pin) Get() bool { + gpio, _, pos := p.reg() + return gpio.PDIR.HasBits(1 << pos) +} + +func (p Pin) Control() *volatile.Register32 { + _, pcr, _ := p.reg() + return pcr +} + +func (p Pin) Fast() FastPin { + gpio, _, pos := p.reg() + return FastPin{ + PDOR: gpio.PDOR.Bit(pos), + PSOR: gpio.PSOR.Bit(pos), + PCOR: gpio.PCOR.Bit(pos), + PTOR: gpio.PTOR.Bit(pos), + PDIR: gpio.PDIR.Bit(pos), + PDDR: gpio.PDDR.Bit(pos), + } +} + +type FastPin struct { + PDOR *volatile.BitRegister + PSOR *volatile.BitRegister + PCOR *volatile.BitRegister + PTOR *volatile.BitRegister + PDIR *volatile.BitRegister + PDDR *volatile.BitRegister +} + +func (p FastPin) Set() { p.PSOR.Set(true) } +func (p FastPin) Clear() { p.PCOR.Set(true) } +func (p FastPin) Toggle() { p.PTOR.Set(true) } +func (p FastPin) Write(v bool) { p.PDOR.Set(v) } +func (p FastPin) Read() bool { return p.PDIR.Get() } diff --git a/src/machine/uart.go b/src/machine/uart.go index f3f46a5712..c68324b610 100644 --- a/src/machine/uart.go +++ b/src/machine/uart.go @@ -1,4 +1,4 @@ -// +build avr nrf sam sifive stm32 k210 +// +build avr nrf sam sifive stm32 k210 nxp package machine diff --git a/src/machine/uart_nxpmk66f18.go b/src/machine/uart_nxpmk66f18.go new file mode 100644 index 0000000000..3fb4d9496a --- /dev/null +++ b/src/machine/uart_nxpmk66f18.go @@ -0,0 +1,305 @@ +// Derivative work of Teensyduino Core Library +// http://www.pjrc.com/teensy/ +// Copyright (c) 2017 PJRC.COM, LLC. +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// 1. The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// 2. If the Software is incorporated into a build system that allows +// selection among a list of target devices, then similar target +// devices manufactured by PJRC.COM must be included in the list of +// target devices and selectable in the same manner. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +// BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// +build nxp,mk66f18 + +package machine + +import ( + "device/arm" + "device/nxp" + "errors" + "runtime/interrupt" + "runtime/volatile" + + _ "unsafe" // for go:linkname +) + +const ( + uartC2Enable = nxp.UART_C2_TE | nxp.UART_C2_RE | nxp.UART_C2_RIE | nxp.UART_C2_ILIE + uartC2TXActive = uartC2Enable | nxp.UART_C2_TIE + uartC2TXCompleting = uartC2Enable | nxp.UART_C2_TCIE + uartC2TXInactive = uartC2Enable + + uartIRQPriority = 64 + + // determined from UARTx_PFIFO + uartRXFIFODepth = 8 + uartTXFIFODepth = 8 +) + +var ( + ErrNotImplemented = errors.New("device has not been implemented") + ErrNotConfigured = errors.New("device has not been configured") +) + +//go:linkname gosched runtime.Gosched +func gosched() + +// PutcharUART writes a byte to the UART synchronously, without using interrupts +// or calling the scheduler +func PutcharUART(u UART, c byte) { + // ensure the UART has been configured + if !u.SCGC.HasBits(u.SCGCMask) { + u.configure(UARTConfig{}, false) + } + + for u.TCFIFO.Get() > 0 { + // busy wait + } + u.D.Set(c) + u.C2.Set(uartC2TXActive) +} + +// PollUART manually checks a UART status and calls the ISR. This should only be +// called by runtime.abort. +func PollUART(u UART) { + if u.SCGC.HasBits(u.SCGCMask) { + u.handleStatusInterrupt(u.Interrupt) + } +} + +type UART = *UARTData + +type UARTData struct { + *nxp.UART_Type + SCGC *volatile.Register32 + SCGCMask uint32 + + DefaultRX Pin + DefaultTX Pin + + // state + Buffer RingBuffer // RX Buffer + TXBuffer RingBuffer + Configured bool + Transmitting volatile.Register8 + Interrupt interrupt.Interrupt +} + +var UART0 = UARTData{UART_Type: nxp.UART0, SCGC: &nxp.SIM.SCGC4, SCGCMask: nxp.SIM_SCGC4_UART0, DefaultRX: defaultUART0RX, DefaultTX: defaultUART0TX} +var UART1 = UARTData{UART_Type: nxp.UART1, SCGC: &nxp.SIM.SCGC4, SCGCMask: nxp.SIM_SCGC4_UART1, DefaultRX: defaultUART1RX, DefaultTX: defaultUART1TX} +var UART2 = UARTData{UART_Type: nxp.UART2, SCGC: &nxp.SIM.SCGC4, SCGCMask: nxp.SIM_SCGC4_UART2, DefaultRX: defaultUART2RX, DefaultTX: defaultUART2TX} +var UART3 = UARTData{UART_Type: nxp.UART3, SCGC: &nxp.SIM.SCGC4, SCGCMask: nxp.SIM_SCGC4_UART3, DefaultRX: defaultUART3RX, DefaultTX: defaultUART3TX} +var UART4 = UARTData{UART_Type: nxp.UART4, SCGC: &nxp.SIM.SCGC1, SCGCMask: nxp.SIM_SCGC1_UART4, DefaultRX: defaultUART4RX, DefaultTX: defaultUART4TX} + +func init() { + UART0.Interrupt = interrupt.New(nxp.IRQ_UART0_RX_TX, UART0.handleStatusInterrupt) + UART1.Interrupt = interrupt.New(nxp.IRQ_UART1_RX_TX, UART1.handleStatusInterrupt) + UART2.Interrupt = interrupt.New(nxp.IRQ_UART2_RX_TX, UART2.handleStatusInterrupt) + UART3.Interrupt = interrupt.New(nxp.IRQ_UART3_RX_TX, UART3.handleStatusInterrupt) + UART4.Interrupt = interrupt.New(nxp.IRQ_UART4_RX_TX, UART4.handleStatusInterrupt) +} + +// Configure the UART. +func (u UART) Configure(config UARTConfig) { + u.configure(config, true) +} + +func (u UART) configure(config UARTConfig, canSched bool) { + // from: serial_begin + + if !u.Configured { + u.Transmitting.Set(0) + + // turn on the clock + u.SCGC.Set(u.SCGCMask) + + // configure pins + u.DefaultRX.Control().Set(nxp.PORT_PCR0_PE | nxp.PORT_PCR0_PS | nxp.PORT_PCR0_PFE | (3 << nxp.PORT_PCR0_MUX_Pos)) + u.DefaultTX.Control().Set(nxp.PORT_PCR0_DSE | nxp.PORT_PCR0_SRE | (3 << nxp.PORT_PCR0_MUX_Pos)) + u.C1.Set(nxp.UART_C1_ILT) + } + + // default to 115200 baud + if config.BaudRate == 0 { + config.BaudRate = 115200 + } + + // copied from teensy core's BAUD2DIV macro + divisor := ((CPUFrequency() * 2) + (config.BaudRate >> 1)) / config.BaudRate + if divisor < 32 { + divisor = 32 + } + + if u.Configured { + // don't change baud rate mid transmit + if canSched { + u.Flush() + } else { + for u.Transmitting.Get() != 0 { + // busy wait flush + } + } + } + + // set the divisor + u.BDH.Set(uint8((divisor >> 13) & 0x1F)) + u.BDL.Set(uint8((divisor >> 5) & 0xFF)) + u.C4.Set(uint8(divisor & 0x1F)) + + if !u.Configured { + u.Configured = true + + u.C1.Set(nxp.UART_C1_ILT) + + // configure TX and RX watermark + u.TWFIFO.Set(2) // causes bit TDRE of S1 to set + u.RWFIFO.Set(4) // causes bit RDRF of S1 to set + + // enable FIFOs + u.PFIFO.Set(nxp.UART_PFIFO_TXFE | nxp.UART_PFIFO_RXFE) + + // setup interrupts + u.C2.Set(uartC2TXInactive) + u.Interrupt.SetPriority(uartIRQPriority) + u.Interrupt.Enable() + } +} + +func (u UART) Disable() { + // from: serial_end + + // check if the device has been enabled already + if !u.SCGC.HasBits(u.SCGCMask) { + return + } + + u.Flush() + + u.Interrupt.Disable() + u.C2.Set(0) + + // reconfigure pin + u.DefaultRX.Configure(PinConfig{Mode: PinInputPullUp}) + u.DefaultTX.Configure(PinConfig{Mode: PinInputPullUp}) + + // clear flags + u.S1.Get() + u.D.Get() + u.Buffer.Clear() +} + +func (u UART) Flush() { + for u.Transmitting.Get() != 0 { + gosched() + } +} + +func (u UART) handleStatusInterrupt(interrupt.Interrupt) { + // from: uart0_status_isr + + // receive + if u.S1.HasBits(nxp.UART_S1_RDRF | nxp.UART_S1_IDLE) { + intrs := arm.DisableInterrupts() + avail := u.RCFIFO.Get() + if avail == 0 { + // The only way to clear the IDLE interrupt flag is + // to read the data register. But reading with no + // data causes a FIFO underrun, which causes the + // FIFO to return corrupted data. If anyone from + // Freescale reads this, what a poor design! There + // write should be a write-1-to-clear for IDLE. + u.D.Get() + // flushing the fifo recovers from the underrun, + // but there's a possible race condition where a + // new character could be received between reading + // RCFIFO == 0 and flushing the FIFO. To minimize + // the chance, interrupts are disabled so a higher + // priority interrupt (hopefully) doesn't delay. + // TODO: change this to disabling the IDLE interrupt + // which won't be simple, since we already manage + // which transmit interrupts are enabled. + u.CFIFO.Set(nxp.UART_CFIFO_RXFLUSH) + arm.EnableInterrupts(intrs) + + } else { + arm.EnableInterrupts(intrs) + + for { + u.Buffer.Put(u.D.Get()) + avail-- + if avail <= 0 { + break + } + } + } + } + + // transmit + if u.C2.HasBits(nxp.UART_C2_TIE) && u.S1.HasBits(nxp.UART_S1_TDRE) { + data := make([]byte, 0, uartTXFIFODepth) + avail := uartTXFIFODepth - u.TCFIFO.Get() + + // get avail bytes from ring buffer + for len(data) < int(avail) { + if b, ok := u.TXBuffer.Get(); ok { + data = append(data, b) + } else { + break + } + } + + // write data to FIFO + l := len(data) + for i, b := range data { + if i == l-1 { + // only clear TDRE on last write, per the manual + u.S1.Get() + } + u.D.Set(b) + } + + // if FIFO still has room, disable TIE, enable TCIE + if u.S1.HasBits(nxp.UART_S1_TDRE) { + u.C2.Set(uartC2TXCompleting) + } + } + + // transmit complete + if u.C2.HasBits(nxp.UART_C2_TCIE) && u.S1.HasBits(nxp.UART_S1_TC) { + u.Transmitting.Set(0) + u.C2.Set(uartC2TXInactive) + } +} + +// WriteByte writes a byte of data to the UART. +func (u UART) WriteByte(c byte) error { + if !u.Configured { + return ErrNotConfigured + } + + for !u.TXBuffer.Put(c) { + gosched() + } + + u.Transmitting.Set(1) + u.C2.Set(uartC2TXActive) + return nil +} diff --git a/src/runtime/interrupt/interrupt_cortexm.go b/src/runtime/interrupt/interrupt_cortexm.go index c23f736d80..0653cb69a5 100644 --- a/src/runtime/interrupt/interrupt_cortexm.go +++ b/src/runtime/interrupt/interrupt_cortexm.go @@ -12,6 +12,11 @@ func (irq Interrupt) Enable() { arm.EnableIRQ(uint32(irq.num)) } +// Disable disables this interrupt. +func (irq Interrupt) Disable() { + arm.DisableIRQ(uint32(irq.num)) +} + // SetPriority sets the interrupt priority for this interrupt. A lower number // means a higher priority. Additionally, most hardware doesn't implement all // priority bits (only the uppoer bits). diff --git a/src/runtime/runtime_cortexm.go b/src/runtime/runtime_cortexm.go index 160c60f73b..ae20f7dec5 100644 --- a/src/runtime/runtime_cortexm.go +++ b/src/runtime/runtime_cortexm.go @@ -3,7 +3,6 @@ package runtime import ( - "device/arm" "unsafe" ) @@ -40,16 +39,6 @@ func preinit() { } } -func abort() { - // disable all interrupts - arm.DisableInterrupts() - - // lock up forever - for { - arm.Asm("wfi") - } -} - // The stack layout at the moment an interrupt occurs. // Registers can be accessed if the stack pointer is cast to a pointer to this // struct. @@ -63,35 +52,3 @@ type interruptStack struct { PC uintptr PSR uintptr } - -// This function is called at HardFault. -// Before this function is called, the stack pointer is reset to the initial -// stack pointer (loaded from addres 0x0) and the previous stack pointer is -// passed as an argument to this function. This allows for easy inspection of -// the stack the moment a HardFault occurs, but it means that the stack will be -// corrupted by this function and thus this handler must not attempt to recover. -// -// For details, see: -// https://community.arm.com/developer/ip-products/system/f/embedded-forum/3257/debugging-a-cortex-m0-hard-fault -// https://blog.feabhas.com/2013/02/developing-a-generic-hard-fault-handler-for-arm-cortex-m3cortex-m4/ -//export handleHardFault -func handleHardFault(sp *interruptStack) { - print("fatal error: ") - if uintptr(unsafe.Pointer(sp)) < 0x20000000 { - print("stack overflow") - } else { - // TODO: try to find the cause of the hard fault. Especially on - // Cortex-M3 and higher it is possible to find more detailed information - // in special status registers. - print("HardFault") - } - print(" with sp=", sp) - if uintptr(unsafe.Pointer(&sp.PC)) >= 0x20000000 { - // Only print the PC if it points into memory. - // It may not point into memory during a stack overflow, so check that - // first before accessing the stack. - print(" pc=", sp.PC) - } - println() - abort() -} diff --git a/src/runtime/runtime_cortexm_abort.go b/src/runtime/runtime_cortexm_abort.go new file mode 100644 index 0000000000..7eda1e5d8f --- /dev/null +++ b/src/runtime/runtime_cortexm_abort.go @@ -0,0 +1,17 @@ +// +build cortexm,!nxp + +package runtime + +import ( + "device/arm" +) + +func abort() { + // disable all interrupts + arm.DisableInterrupts() + + // lock up forever + for { + arm.Asm("wfi") + } +} diff --git a/src/runtime/runtime_cortexm_hardfault.go b/src/runtime/runtime_cortexm_hardfault.go new file mode 100644 index 0000000000..7e6179ff70 --- /dev/null +++ b/src/runtime/runtime_cortexm_hardfault.go @@ -0,0 +1,39 @@ +// +build cortexm,!nxp + +package runtime + +import ( + "unsafe" +) + +// This function is called at HardFault. +// Before this function is called, the stack pointer is reset to the initial +// stack pointer (loaded from addres 0x0) and the previous stack pointer is +// passed as an argument to this function. This allows for easy inspection of +// the stack the moment a HardFault occurs, but it means that the stack will be +// corrupted by this function and thus this handler must not attempt to recover. +// +// For details, see: +// https://community.arm.com/developer/ip-products/system/f/embedded-forum/3257/debugging-a-cortex-m0-hard-fault +// https://blog.feabhas.com/2013/02/developing-a-generic-hard-fault-handler-for-arm-cortex-m3cortex-m4/ +//export handleHardFault +func handleHardFault(sp *interruptStack) { + print("fatal error: ") + if uintptr(unsafe.Pointer(sp)) < 0x20000000 { + print("stack overflow") + } else { + // TODO: try to find the cause of the hard fault. Especially on + // Cortex-M3 and higher it is possible to find more detailed information + // in special status registers. + print("HardFault") + } + print(" with sp=", sp) + if uintptr(unsafe.Pointer(&sp.PC)) >= 0x20000000 { + // Only print the PC if it points into memory. + // It may not point into memory during a stack overflow, so check that + // first before accessing the stack. + print(" pc=", sp.PC) + } + println() + abort() +} diff --git a/src/runtime/runtime_cortexm_hardfault_debug.go b/src/runtime/runtime_cortexm_hardfault_debug.go new file mode 100644 index 0000000000..25d3e5507d --- /dev/null +++ b/src/runtime/runtime_cortexm_hardfault_debug.go @@ -0,0 +1,304 @@ +// +build nxp + +package runtime + +import ( + "device/nxp" + "unsafe" +) + +const ( + SystemControl_CFSR_KnownFault = nxp.SystemControl_CFSR_IACCVIOL | nxp.SystemControl_CFSR_DACCVIOL | nxp.SystemControl_CFSR_MUNSTKERR | nxp.SystemControl_CFSR_MSTKERR | nxp.SystemControl_CFSR_MLSPERR | nxp.SystemControl_CFSR_IBUSERR | nxp.SystemControl_CFSR_PRECISERR | nxp.SystemControl_CFSR_IMPRECISERR | nxp.SystemControl_CFSR_UNSTKERR | nxp.SystemControl_CFSR_STKERR | nxp.SystemControl_CFSR_LSPERR | nxp.SystemControl_CFSR_UNDEFINSTR | nxp.SystemControl_CFSR_INVSTATE | nxp.SystemControl_CFSR_INVPC | nxp.SystemControl_CFSR_NOCP | nxp.SystemControl_CFSR_UNALIGNED | nxp.SystemControl_CFSR_DIVBYZERO +) + +// See runtime_cortexm_hardfault.go +//go:export handleHardFault +func handleHardFault(sp *interruptStack) { + fault := GetFaultStatus() + spValid := !fault.Bus().ImpreciseDataBusError() + + print("fatal error: ") + if spValid && uintptr(unsafe.Pointer(sp)) < 0x20000000 { + print("stack overflow? ") + } + if fault.Mem().InstructionAccessViolation() { + print("instruction access violation") + } + if fault.Mem().DataAccessViolation() { + print("data access violation") + } + if fault.Mem().WhileUnstackingException() { + print(" while unstacking exception") + } + if fault.Mem().WileStackingException() { + print(" while stacking exception") + } + if fault.Mem().DuringFPLazyStatePres() { + print(" during floating-point lazy state preservation") + } + + if fault.Bus().InstructionBusError() { + print("instruction bus error") + } + if fault.Bus().PreciseDataBusError() { + print("data bus error (precise)") + } + if fault.Bus().ImpreciseDataBusError() { + print("data bus error (imprecise)") + } + if fault.Bus().WhileUnstackingException() { + print(" while unstacking exception") + } + if fault.Bus().WhileStackingException() { + print(" while stacking exception") + } + if fault.Bus().DuringFPLazyStatePres() { + print(" during floating-point lazy state preservation") + } + + if fault.Usage().UndefinedInstruction() { + print("undefined instruction") + } + if fault.Usage().IllegalUseOfEPSR() { + print("illegal use of the EPSR") + } + if fault.Usage().IllegalExceptionReturn() { + print("illegal load of EXC_RETURN to the PC") + } + if fault.Usage().AttemptedToAccessCoprocessor() { + print("coprocessor access violation") + } + if fault.Usage().UnalignedMemoryAccess() { + print("unaligned memory access") + } + if fault.Usage().DivideByZero() { + print("divide by zero") + } + + if fault.Unknown() { + print("unknown hard fault") + } + + if addr, ok := fault.Mem().Address(); ok { + print(" with fault address ", addr) + } + + if addr, ok := fault.Bus().Address(); ok { + print(" with bus fault address ", addr) + } + if spValid { + print(" with sp=", sp) + if uintptr(unsafe.Pointer(&sp.PC)) >= 0x20000000 { + // Only print the PC if it points into memory. + // It may not point into memory during a stack overflow, so check that + // first before accessing the stack. + print(" pc=", sp.PC) + } + } + println() + abort() +} + +// Descriptions are sourced from the K66 SVD and +// http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0552a/Cihcfefj.html + +// GetFaultStatus reads the System Control Block Configurable Fault Status +// Register and returns it as a FaultStatus. +func GetFaultStatus() FaultStatus { return FaultStatus(nxp.SystemControl.CFSR.Get()) } + +type FaultStatus uint32 +type MemFaultStatus uint32 +type BusFaultStatus uint32 +type UsageFaultStatus uint32 + +func (fs FaultStatus) Mem() MemFaultStatus { return MemFaultStatus(fs) } +func (fs FaultStatus) Bus() BusFaultStatus { return BusFaultStatus(fs) } +func (fs FaultStatus) Usage() UsageFaultStatus { return UsageFaultStatus(fs) } + +// Unknown returns true if the cause of the fault is not know +func (fs FaultStatus) Unknown() bool { return fs&SystemControl_CFSR_KnownFault == 0 } + +// InstructionAccessViolation: the processor attempted an instruction fetch from +// a location that does not permit execution +// +// "This fault occurs on any access to an XN region, even when the MPU is +// disabled or not present. +// +// When this bit is 1, the PC value stacked for the exception return points to +// the faulting instruction. The processor has not written a fault address to +// the MMAR." +func (fs MemFaultStatus) InstructionAccessViolation() bool { + return fs&nxp.SystemControl_CFSR_IACCVIOL != 0 +} + +// DataAccessViolation: the processor attempted a load or store at a location +// that does not permit the operation +// +// "When this bit is 1, the PC value stacked for the exception return points to +// the faulting instruction. The processor has loaded the MMAR with the address +// of the attempted access." +func (fs MemFaultStatus) DataAccessViolation() bool { return fs&nxp.SystemControl_CFSR_DACCVIOL != 0 } + +// WhileUnstackingException: unstack for an exception return has caused one or +// more access violations +// +// "This fault is chained to the handler. This means that when this bit is 1, +// the original return stack is still present. The processor has not adjusted +// the SP from the failing return, and has not performed a new save. The +// processor has not written a fault address to the MMAR." +func (fs MemFaultStatus) WhileUnstackingException() bool { + return fs&nxp.SystemControl_CFSR_MUNSTKERR != 0 +} + +// WileStackingException: stacking for an exception entry has caused one or more +// access violations +// +// "When this bit is 1, the SP is still adjusted but the values in the context +// area on the stack might be incorrect. The processor has not written a fault +// address to the MMAR." +func (fs MemFaultStatus) WileStackingException() bool { return fs&nxp.SystemControl_CFSR_MSTKERR != 0 } + +// DuringFPLazyStatePres: A MemManage fault occurred during floating-point lazy +// state preservation +func (fs MemFaultStatus) DuringFPLazyStatePres() bool { return fs&nxp.SystemControl_CFSR_MLSPERR != 0 } + +// InstructionBusError: instruction bus error +// +// "The processor detects the instruction bus error on prefetching an +// instruction, but it sets the IBUSERR flag to 1 only if it attempts to issue +// the faulting instruction. +// +// When the processor sets this bit is 1, it does not write a fault address to +// the BFAR." +func (fs BusFaultStatus) InstructionBusError() bool { return fs&nxp.SystemControl_CFSR_IBUSERR != 0 } + +// PreciseDataBusError: a data bus error has occurred, and the PC value stacked +// for the exception return points to the instruction that caused the fault +func (fs BusFaultStatus) PreciseDataBusError() bool { return fs&nxp.SystemControl_CFSR_PRECISERR != 0 } + +// ImpreciseDataBusError: a data bus error has occurred, but the return address +// in the stack frame is not related to the instruction that caused the error +// +// "When the processor sets this bit to 1, it does not write a fault address to +// the BFAR. +// +// This is an asynchronous fault. Therefore, if it is detected when the priority +// of the current process is higher than the BusFault priority, the BusFault +// becomes pending and becomes active only when the processor returns from all +// higher priority processes. If a precise fault occurs before the processor +// enters the handler for the imprecise BusFault, the handler detects both +// IMPRECISERR set to 1 and one of the precise fault status bits set to 1." +func (fs BusFaultStatus) ImpreciseDataBusError() bool { + return fs&nxp.SystemControl_CFSR_IMPRECISERR != 0 +} + +// WhileUnstackingException: unstack for an exception return has caused one or +// more BusFaults +// +// "This fault is chained to the handler. This means that when the processor +// sets this bit to 1, the original return stack is still present. The processor +// does not adjust the SP from the failing return, does not performed a new +// save, and does not write a fault address to the BFAR." +func (fs BusFaultStatus) WhileUnstackingException() bool { + return fs&nxp.SystemControl_CFSR_UNSTKERR != 0 +} + +// WhileStackingException: stacking for an exception entry has caused one or +// more BusFaults +// +// "When the processor sets this bit to 1, the SP is still adjusted but the +// values in the context area on the stack might be incorrect. The processor +// does not write a fault address to the BFAR." +func (fs BusFaultStatus) WhileStackingException() bool { return fs&nxp.SystemControl_CFSR_STKERR != 0 } + +// DuringFPLazyStatePres: A bus fault occurred during floating-point lazy state +// preservation +func (fs BusFaultStatus) DuringFPLazyStatePres() bool { return fs&nxp.SystemControl_CFSR_LSPERR != 0 } + +// UndefinedInstruction: the processor has attempted to execute an undefined +// instruction +// +// "When this bit is set to 1, the PC value stacked for the exception return +// points to the undefined instruction. +// +// An undefined instruction is an instruction that the processor cannot decode." +func (fs UsageFaultStatus) UndefinedInstruction() bool { + return fs&nxp.SystemControl_CFSR_UNDEFINSTR != 0 +} + +// IllegalUseOfEPSR: the processor has attempted to execute an instruction that +// makes illegal use of the EPSR +// +// "When this bit is set to 1, the PC value stacked for the exception return +// points to the instruction that attempted the illegal use of the EPSR. +// +// This bit is not set to 1 if an undefined instruction uses the EPSR." +func (fs UsageFaultStatus) IllegalUseOfEPSR() bool { return fs&nxp.SystemControl_CFSR_INVSTATE != 0 } + +// IllegalExceptionReturn: the processor has attempted an illegal load of +// EXC_RETURN to the PC +// +// "When this bit is set to 1, the PC value stacked for the exception return +// points to the instruction that tried to perform the illegal load of the PC." +func (fs UsageFaultStatus) IllegalExceptionReturn() bool { return fs&nxp.SystemControl_CFSR_INVPC != 0 } + +// AttemptedToAccessCoprocessor: the processor has attempted to access a +// coprocessor +func (fs UsageFaultStatus) AttemptedToAccessCoprocessor() bool { + return fs&nxp.SystemControl_CFSR_NOCP != 0 +} + +// UnalignedMemoryAccess: the processor has made an unaligned memory access +// +// "Enable trapping of unaligned accesses by setting the UNALIGN_TRP bit in the +// CCR to 1. +// +// Unaligned LDM, STM, LDRD, and STRD instructions always fault irrespective of +// the setting of UNALIGN_TRP." +func (fs UsageFaultStatus) UnalignedMemoryAccess() bool { + return fs&nxp.SystemControl_CFSR_UNALIGNED != 0 +} + +// DivideByZero: the processor has executed an SDIV or UDIV instruction with a +// divisor of 0 +// +// "When the processor sets this bit to 1, the PC value stacked for the +// exception return points to the instruction that performed the divide by zero. +// +// Enable trapping of divide by zero by setting the DIV_0_TRP bit in the CCR to +// 1." +func (fs UsageFaultStatus) DivideByZero() bool { return fs&nxp.SystemControl_CFSR_DIVBYZERO != 0 } + +// Address returns the MemManage Fault Address Register if the fault status +// indicates the address is valid. +// +// "If a MemManage fault occurs and is escalated to a HardFault because of +// priority, the HardFault handler must set this bit to 0. This prevents +// problems on return to a stacked active MemManage fault handler whose MMAR +// value has been overwritten." +func (fs MemFaultStatus) Address() (uintptr, bool) { + if fs&nxp.SystemControl_CFSR_MMARVALID == 0 { + return 0, false + } else { + return uintptr(nxp.SystemControl.MMFAR.Get()), true + } +} + +// Address returns the BusFault Address Register if the fault status +// indicates the address is valid. +// +// "The processor sets this bit to 1 after a BusFault where the address is +// known. Other faults can set this bit to 0, such as a MemManage fault +// occurring later. +// +// If a BusFault occurs and is escalated to a hard fault because of priority, +// the hard fault handler must set this bit to 0. This prevents problems if +// returning to a stacked active BusFault handler whose BFAR value has been +// overwritten."" +func (fs BusFaultStatus) Address() (uintptr, bool) { + if fs&nxp.SystemControl_CFSR_BFARVALID == 0 { + return 0, false + } else { + return uintptr(nxp.SystemControl.BFAR.Get()), true + } +} diff --git a/src/runtime/runtime_nxpmk66f18.go b/src/runtime/runtime_nxpmk66f18.go new file mode 100644 index 0000000000..84d421b7fd --- /dev/null +++ b/src/runtime/runtime_nxpmk66f18.go @@ -0,0 +1,267 @@ +// Derivative work of Teensyduino Core Library +// http://www.pjrc.com/teensy/ +// Copyright (c) 2017 PJRC.COM, LLC. +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// 1. The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// 2. If the Software is incorporated into a build system that allows +// selection among a list of target devices, then similar target +// devices manufactured by PJRC.COM must be included in the list of +// target devices and selectable in the same manner. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +// BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// +build nxp,mk66f18 + +package runtime + +import ( + "device/arm" + "device/nxp" + "machine" +) + +const ( + watchdogUnlockSequence1 = 0xC520 + watchdogUnlockSequence2 = 0xD928 + + _DEFAULT_FTM_MOD = 61440 - 1 + _DEFAULT_FTM_PRESCALE = 1 +) + +const ( + _SIM_SOPT2_IRC48SEL = 3 << nxp.SIM_SOPT2_PLLFLLSEL_Pos + _SMC_PMCTRL_HSRUN = 3 << nxp.SMC_PMCTRL_RUNM_Pos + _SMC_PMSTAT_HSRUN = 0x80 << nxp.SMC_PMSTAT_PMSTAT_Pos +) + +//go:export Reset_Handler +func main() { + initSystem() + arm.Asm("CPSIE i") + initInternal() + + run() + abort() +} + +func initSystem() { + // from: ResetHandler + + nxp.WDOG.UNLOCK.Set(watchdogUnlockSequence1) + nxp.WDOG.UNLOCK.Set(watchdogUnlockSequence2) + arm.Asm("nop") + arm.Asm("nop") + // TODO: hook for overriding? 'startupEarlyHook' + nxp.WDOG.STCTRLH.Set(nxp.WDOG_STCTRLH_ALLOWUPDATE) + + // enable clocks to always-used peripherals + nxp.SIM.SCGC3.Set(nxp.SIM_SCGC3_ADC1 | nxp.SIM_SCGC3_FTM2 | nxp.SIM_SCGC3_FTM3) + nxp.SIM.SCGC5.Set(0x00043F82) // clocks active to all GPIO + nxp.SIM.SCGC6.Set(nxp.SIM_SCGC6_RTC | nxp.SIM_SCGC6_FTM0 | nxp.SIM_SCGC6_FTM1 | nxp.SIM_SCGC6_ADC0 | nxp.SIM_SCGC6_FTF) + nxp.SystemControl.CPACR.Set(0x00F00000) + nxp.LMEM.PCCCR.Set(0x85000003) + + // release I/O pins hold, if we woke up from VLLS mode + if nxp.PMC.REGSC.HasBits(nxp.PMC_REGSC_ACKISO) { + nxp.PMC.REGSC.SetBits(nxp.PMC_REGSC_ACKISO) + } + + // since this is a write once register, make it visible to all F_CPU's + // so we can into other sleep modes in the future at any speed + nxp.SMC.PMPROT.Set(nxp.SMC_PMPROT_AHSRUN | nxp.SMC_PMPROT_AVLP | nxp.SMC_PMPROT_ALLS | nxp.SMC_PMPROT_AVLLS) + + preinit() + + // copy the vector table to RAM default all interrupts to medium priority level + // for (i=0; i < NVIC_NUM_INTERRUPTS + 16; i++) _VectorsRam[i] = _VectorsFlash[i]; + for i := uint32(0); i <= nxp.IRQ_max; i++ { + arm.SetPriority(i, 128) + } + // SCB_VTOR = (uint32_t)_VectorsRam; // use vector table in RAM + + // hardware always starts in FEI mode + // C1[CLKS] bits are written to 00 + // C1[IREFS] bit is written to 1 + // C6[PLLS] bit is written to 0 + // MCG_SC[FCDIV] defaults to divide by two for internal ref clock + // I tried changing MSG_SC to divide by 1, it didn't work for me + // enable capacitors for crystal + nxp.OSC.CR.Set(nxp.OSC_CR_SC8P | nxp.OSC_CR_SC2P | nxp.OSC_CR_ERCLKEN) + // enable osc, 8-32 MHz range, low power mode + nxp.MCG.C2.Set(uint8((2 << nxp.MCG_C2_RANGE_Pos) | nxp.MCG_C2_EREFS)) + // switch to crystal as clock source, FLL input = 16 MHz / 512 + nxp.MCG.C1.Set(uint8((2 << nxp.MCG_C1_CLKS_Pos) | (4 << nxp.MCG_C1_FRDIV_Pos))) + // wait for crystal oscillator to begin + for !nxp.MCG.S.HasBits(nxp.MCG_S_OSCINIT0) { + } + // wait for FLL to use oscillator + for nxp.MCG.S.HasBits(nxp.MCG_S_IREFST) { + } + // wait for MCGOUT to use oscillator + for (nxp.MCG.S.Get() & nxp.MCG_S_CLKST_Msk) != (2 << nxp.MCG_S_CLKST_Pos) { + } + + // now in FBE mode + // C1[CLKS] bits are written to 10 + // C1[IREFS] bit is written to 0 + // C1[FRDIV] must be written to divide xtal to 31.25-39 kHz + // C6[PLLS] bit is written to 0 + // C2[LP] is written to 0 + // we need faster than the crystal, turn on the PLL (F_CPU > 120000000) + nxp.SMC.PMCTRL.Set(_SMC_PMCTRL_HSRUN) // enter HSRUN mode + for nxp.SMC.PMSTAT.Get() != _SMC_PMSTAT_HSRUN { + } // wait for HSRUN + nxp.MCG.C5.Set((1 << nxp.MCG_C5_PRDIV_Pos)) + nxp.MCG.C6.Set(nxp.MCG_C6_PLLS | (29 << nxp.MCG_C6_VDIV_Pos)) + + // wait for PLL to start using xtal as its input + for !nxp.MCG.S.HasBits(nxp.MCG_S_PLLST) { + } + // wait for PLL to lock + for !nxp.MCG.S.HasBits(nxp.MCG_S_LOCK0) { + } + // now we're in PBE mode + + // now program the clock dividers + // config divisors: 180 MHz core, 60 MHz bus, 25.7 MHz flash, USB = IRC48M + nxp.SIM.CLKDIV1.Set((0 << nxp.SIM_CLKDIV1_OUTDIV1_Pos) | (2 << nxp.SIM_CLKDIV1_OUTDIV2_Pos) | (0 << nxp.SIM_CLKDIV1_OUTDIV1_Pos) | (6 << nxp.SIM_CLKDIV1_OUTDIV4_Pos)) + nxp.SIM.CLKDIV2.Set((0 << nxp.SIM_CLKDIV2_USBDIV_Pos)) + + // switch to PLL as clock source, FLL input = 16 MHz / 512 + nxp.MCG.C1.Set((0 << nxp.MCG_C1_CLKS_Pos) | (4 << nxp.MCG_C1_FRDIV_Pos)) + // wait for PLL clock to be used + for (nxp.MCG.S.Get() & nxp.MCG_S_CLKST_Msk) != (3 << nxp.MCG_S_CLKST_Pos) { + } + // now we're in PEE mode + // trace is CPU clock, CLKOUT=OSCERCLK0 + // USB uses IRC48 + nxp.SIM.SOPT2.Set(nxp.SIM_SOPT2_USBSRC | _SIM_SOPT2_IRC48SEL | nxp.SIM_SOPT2_TRACECLKSEL | (6 << nxp.SIM_SOPT2_CLKOUTSEL_Pos)) + + // If the RTC oscillator isn't enabled, get it started. For Teensy 3.6 + // we don't do this early. See comment above about slow rising power. + if !nxp.RTC.CR.HasBits(nxp.RTC_CR_OSCE) { + nxp.RTC.SR.Set(0) + nxp.RTC.CR.Set(nxp.RTC_CR_SC16P | nxp.RTC_CR_SC4P | nxp.RTC_CR_OSCE) + } + + // initialize the SysTick counter + initSysTick() +} + +func initInternal() { + // from: _init_Teensyduino_internal_ + // arm.EnableIRQ(nxp.IRQ_PORTA) + // arm.EnableIRQ(nxp.IRQ_PORTB) + // arm.EnableIRQ(nxp.IRQ_PORTC) + // arm.EnableIRQ(nxp.IRQ_PORTD) + // arm.EnableIRQ(nxp.IRQ_PORTE) + + nxp.FTM0.CNT.Set(0) + nxp.FTM0.MOD.Set(_DEFAULT_FTM_MOD) + nxp.FTM0.C0SC.Set(0x28) // MSnB:MSnA = 10, ELSnB:ELSnA = 10 + nxp.FTM0.C1SC.Set(0x28) + nxp.FTM0.C2SC.Set(0x28) + nxp.FTM0.C3SC.Set(0x28) + nxp.FTM0.C4SC.Set(0x28) + nxp.FTM0.C5SC.Set(0x28) + nxp.FTM0.C6SC.Set(0x28) + nxp.FTM0.C7SC.Set(0x28) + + nxp.FTM3.C0SC.Set(0x28) + nxp.FTM3.C1SC.Set(0x28) + nxp.FTM3.C2SC.Set(0x28) + nxp.FTM3.C3SC.Set(0x28) + nxp.FTM3.C4SC.Set(0x28) + nxp.FTM3.C5SC.Set(0x28) + nxp.FTM3.C6SC.Set(0x28) + nxp.FTM3.C7SC.Set(0x28) + + nxp.FTM0.SC.Set((1 << nxp.FTM_SC_CLKS_Pos) | (_DEFAULT_FTM_PRESCALE << nxp.FTM_SC_PS_Pos)) + nxp.FTM1.CNT.Set(0) + nxp.FTM1.MOD.Set(_DEFAULT_FTM_MOD) + nxp.FTM1.C0SC.Set(0x28) + nxp.FTM1.C1SC.Set(0x28) + nxp.FTM1.SC.Set((1 << nxp.FTM_SC_CLKS_Pos) | (_DEFAULT_FTM_PRESCALE << nxp.FTM_SC_PS_Pos)) + + // causes a data bus error for unknown reasons + // nxp.FTM2.CNT.Set(0) + // nxp.FTM2.MOD.Set(_DEFAULT_FTM_MOD) + // nxp.FTM2.C0SC.Set(0x28) + // nxp.FTM2.C1SC.Set(0x28) + // nxp.FTM2.SC.Set((1 << nxp.FTM_SC_CLKS_Pos) | (_DEFAULT_FTM_PRESCALE << nxp.FTM_SC_PS_Pos)) + + nxp.FTM3.CNT.Set(0) + nxp.FTM3.MOD.Set(_DEFAULT_FTM_MOD) + nxp.FTM3.C0SC.Set(0x28) + nxp.FTM3.C1SC.Set(0x28) + nxp.FTM3.SC.Set((1 << nxp.FTM_SC_CLKS_Pos) | (_DEFAULT_FTM_PRESCALE << nxp.FTM_SC_PS_Pos)) + + nxp.SIM.SCGC2.SetBits(nxp.SIM_SCGC2_TPM1) + nxp.SIM.SOPT2.SetBits((2 << nxp.SIM_SOPT2_TPMSRC_Pos)) + nxp.TPM1.CNT.Set(0) + nxp.TPM1.MOD.Set(32767) + nxp.TPM1.C0SC.Set(0x28) + nxp.TPM1.C1SC.Set(0x28) + nxp.TPM1.SC.Set((1 << nxp.FTM_SC_CLKS_Pos) | (0 << nxp.FTM_SC_PS_Pos)) + + // configure the sleep timer + initSleepTimer() + + // analog_init(); +} + +func postinit() {} + +func putchar(c byte) { + machine.PutcharUART(&machine.UART0, c) +} + +// ??? +const asyncScheduler = false + +func abort() { + println("!!! ABORT !!!") + + m := arm.DisableInterrupts() + arm.Asm("mov r12, #1") + arm.Asm("msr basepri, r12") // only execute interrupts of priority 0 + nxp.SystemControl.SHPR3.ClearBits(nxp.SystemControl_SHPR3_PRI_15_Msk) // set systick to priority 0 + arm.EnableInterrupts(m) + + machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput}) + + var v bool + for { + machine.LED.Set(v) + v = !v + + t := millisSinceBoot() + for millisSinceBoot()-t < 60 { + arm.Asm("wfi") + } + + // keep polling some communication while in fault + // mode, so we don't completely die. + // machine.PollUSB(&machine.USB0) + machine.PollUART(&machine.UART0) + machine.PollUART(&machine.UART1) + machine.PollUART(&machine.UART2) + } +} diff --git a/src/runtime/time_nxpmk66f18.go b/src/runtime/time_nxpmk66f18.go new file mode 100644 index 0000000000..3836fc39a4 --- /dev/null +++ b/src/runtime/time_nxpmk66f18.go @@ -0,0 +1,163 @@ +// Derivative work of Teensyduino Core Library +// http://www.pjrc.com/teensy/ +// Copyright (c) 2017 PJRC.COM, LLC. +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// 1. The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// 2. If the Software is incorporated into a build system that allows +// selection among a list of target devices, then similar target +// devices manufactured by PJRC.COM must be included in the list of +// target devices and selectable in the same manner. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +// BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// +build nxp,mk66f18 + +package runtime + +import ( + "device/arm" + "device/nxp" + "machine" + "runtime/interrupt" + "runtime/volatile" +) + +type timeUnit int64 + +func ticksToNanoseconds(ticks timeUnit) int64 { + return int64(ticks) * 1000 +} + +func nanosecondsToTicks(ns int64) timeUnit { + return timeUnit(ns / 1000) +} + +// cyclesPerMilli-1 is used for the systick reset value +// the systick current value will be decremented on every clock cycle +// an interrupt is generated when the current value reaches 0 +// a value of freq/1000 generates a tick (irq) every millisecond (1/1000 s) +var cyclesPerMilli = machine.CPUFrequency() / 1000 + +// number of systick irqs (milliseconds) since boot +var systickCount volatile.Register64 + +func millisSinceBoot() uint64 { + return systickCount.Get() +} + +func initSysTick() { + nxp.SysTick.RVR.Set(cyclesPerMilli - 1) + nxp.SysTick.CVR.Set(0) + nxp.SysTick.CSR.Set(nxp.SysTick_CSR_CLKSOURCE | nxp.SysTick_CSR_TICKINT | nxp.SysTick_CSR_ENABLE) + nxp.SystemControl.SHPR3.Set((32 << nxp.SystemControl_SHPR3_PRI_15_Pos) | (32 << nxp.SystemControl_SHPR3_PRI_14_Pos)) // set systick and pendsv priority to 32 +} + +func initSleepTimer() { + nxp.SIM.SCGC5.SetBits(nxp.SIM_SCGC5_LPTMR) + nxp.LPTMR0.CSR.Set(nxp.LPTMR0_CSR_TIE) + + timerInterrupt = interrupt.New(nxp.IRQ_LPTMR0, timerWake) + timerInterrupt.Enable() +} + +//go:export SysTick_Handler +func tick() { + systickCount.Set(systickCount.Get() + 1) +} + +// ticks are in microseconds +func ticks() timeUnit { + mask := arm.DisableInterrupts() + current := nxp.SysTick.CVR.Get() // current value of the systick counter + count := millisSinceBoot() // number of milliseconds since boot + istatus := nxp.SystemControl.ICSR.Get() // interrupt status register + arm.EnableInterrupts(mask) + + micros := timeUnit(count * 1000) // a tick (1ms) = 1000 us + + // if the systick counter was about to reset and ICSR indicates a pending systick irq, increment count + if istatus&nxp.SystemControl_ICSR_PENDSTSET != 0 && current > 50 { + micros += 1000 + } else { + cycles := cyclesPerMilli - 1 - current // number of cycles since last 1ms tick + cyclesPerMicro := machine.CPUFrequency() / 1000000 + micros += timeUnit(cycles / cyclesPerMicro) + } + + return micros +} + +// sleepTicks spins for a number of microseconds +func sleepTicks(duration timeUnit) { + now := ticks() + end := duration + now + cyclesPerMicro := machine.ClockFrequency() / 1000000 + + if duration <= 0 { + return + } + + nxp.LPTMR0.PSR.Set((3 << nxp.LPTMR0_PSR_PCS_Pos) | nxp.LPTMR0_PSR_PBYP) // use 16MHz clock, undivided + + for now < end { + count := uint32(end-now) / cyclesPerMicro + if count > 65535 { + count = 65535 + } + + if !timerSleep(count) { + // return early due to interrupt + return + } + + now = ticks() + } +} + +var timerInterrupt interrupt.Interrupt +var timerActive volatile.Register32 + +func timerSleep(count uint32) bool { + timerActive.Set(1) + nxp.LPTMR0.CMR.Set(count) // set count + nxp.LPTMR0.CSR.SetBits(nxp.LPTMR0_CSR_TEN) // enable + + for { + arm.Asm("wfi") + if timerActive.Get() == 0 { + return true + } + + if hasScheduler { + // bail out, as the interrupt may have awoken a goroutine + break + } + + // if there is no scheduler, block for the entire count + } + + timerWake(timerInterrupt) + return false +} + +func timerWake(interrupt.Interrupt) { + timerActive.Set(0) + nxp.LPTMR0.CSR.Set(nxp.LPTMR0.CSR.Get()&^nxp.LPTMR0_CSR_TEN | nxp.LPTMR0_CSR_TCF) // clear flag and disable +} diff --git a/src/runtime/volatile/bitband_nxpmk66f18.go b/src/runtime/volatile/bitband_nxpmk66f18.go new file mode 100644 index 0000000000..0a4904db2d --- /dev/null +++ b/src/runtime/volatile/bitband_nxpmk66f18.go @@ -0,0 +1,78 @@ +// +build nxp,mk66f18 + +package volatile + +import "unsafe" + +const registerBase = 0x40000000 +const registerEnd = 0x40100000 +const bitbandBase = 0x42000000 + +//go:inline +func bitbandAddress(reg uintptr, bit uint8) uintptr { + if uintptr(bit) > 32 { + panic("invalid bit position") + } + if reg < registerBase || reg >= registerEnd { + panic("register is out of range") + } + return (reg-registerBase)*32 + uintptr(bit)*4 + bitbandBase +} + +// Special types that causes loads/stores to be volatile (necessary for +// memory-mapped registers). +type BitRegister struct { + Reg uint32 +} + +// Get returns the of the mapped register bit. It is the volatile equivalent of: +// +// *r.Reg +// +//go:inline +func (r *BitRegister) Get() bool { + return LoadUint32(&r.Reg) != 0 +} + +// Set sets the mapped register bit. It is the volatile equivalent of: +// +// *r.Reg = 1 +// +//go:inline +func (r *BitRegister) Set(v bool) { + var i uint32 + if v { + i = 1 + } + StoreUint32(&r.Reg, i) +} + +// Bit maps bit N of register R to the corresponding bitband address. Bit panics +// if R is not an AIPS or GPIO register or if N is out of range (greater than +// the number of bits in a register minus one). +// +// go:inline +func (r *Register8) Bit(bit uint8) *BitRegister { + ptr := bitbandAddress(uintptr(unsafe.Pointer(&r.Reg)), bit) + return (*BitRegister)(unsafe.Pointer(ptr)) +} + +// Bit maps bit N of register R to the corresponding bitband address. Bit panics +// if R is not an AIPS or GPIO register or if N is out of range (greater than +// the number of bits in a register minus one). +// +// go:inline +func (r *Register16) Bit(bit uint8) *BitRegister { + ptr := bitbandAddress(uintptr(unsafe.Pointer(&r.Reg)), bit) + return (*BitRegister)(unsafe.Pointer(ptr)) +} + +// Bit maps bit N of register R to the corresponding bitband address. Bit panics +// if R is not an AIPS or GPIO register or if N is out of range (greater than +// the number of bits in a register minus one). +// +// go:inline +func (r *Register32) Bit(bit uint8) *BitRegister { + ptr := bitbandAddress(uintptr(unsafe.Pointer(&r.Reg)), bit) + return (*BitRegister)(unsafe.Pointer(ptr)) +} diff --git a/targets/nxpmk66f18.ld b/targets/nxpmk66f18.ld new file mode 100644 index 0000000000..470a5b384b --- /dev/null +++ b/targets/nxpmk66f18.ld @@ -0,0 +1,86 @@ + +/* Unused, but here to silence a linker warning. */ +ENTRY(Reset_Handler) + +/* define memory layout */ +MEMORY +{ + FLASH_TEXT (rx) : ORIGIN = 0x00000000, LENGTH = 1024K + RAM (rwx) : ORIGIN = 0x1FFF0000, LENGTH = 256K +} + +_stack_size = 2K; + +/* define output sections */ +SECTIONS +{ + /* Program code and read-only data goes to FLASH_TEXT. */ + .text : + { + /* vector table MUST start at 0x0 */ + . = 0; + KEEP(*(.isr_vector)) + + /* flash configuration MUST be at 0x400 */ + . = 0x400; + KEEP(*(.flash_config)) + + /* everything else */ + *(.text) + *(.text.*) + *(.rodata) + *(.rodata.*) + . = ALIGN(4); + + } >FLASH_TEXT = 0xFF + + /* Put the stack at the bottom of RAM, so that the application will + * crash on stack overflow instead of silently corrupting memory. + * See: http://blog.japaric.io/stack-overflow-protection/ */ + .stack (NOLOAD) : + { + . = ALIGN(4); + . += _stack_size; + _stack_top = .; + } >RAM + + /* Start address (in flash) of .data, used by startup code. */ + _sidata = LOADADDR(.data); + + /* this is where Teensy's LD script places .usbdescriptortable .dmabuffers .usbbuffers */ + + /* Globals with initial value */ + .data : + { + . = ALIGN(4); + _sdata = .; /* used by startup code */ + *(.data) + *(.data.*) + . = ALIGN(4); + _edata = .; /* used by startup code */ + } >RAM AT>FLASH_TEXT + + /* Zero-initialized globals */ + .bss : + { + . = ALIGN(4); + _sbss = .; /* used by startup code */ + *(.bss) + *(.bss.*) + *(COMMON) + . = ALIGN(4); + _ebss = .; /* used by startup code */ + } >RAM + + /DISCARD/ : + { + *(.ARM.exidx) /* causes 'no memory region specified' error in lld */ + *(.ARM.exidx.*) /* causes spurious 'undefined reference' errors */ + } +} + +/* For the memory allocator. */ +_heap_start = _ebss; +_heap_end = ORIGIN(RAM) + LENGTH(RAM); +_globals_start = _sdata; +_globals_end = _ebss; diff --git a/targets/teensy36.json b/targets/teensy36.json new file mode 100644 index 0000000000..473aec2eb6 --- /dev/null +++ b/targets/teensy36.json @@ -0,0 +1,19 @@ +{ + "inherits": ["cortex-m"], + "llvm-target": "armv7em-none-eabi", + "cpu": "cortex-m4", + "build-tags": ["teensy36", "teensy", "mk66f18", "nxp"], + "cflags": [ + "--target=armv7em-none-eabi", + "-Qunused-arguments", + "-mfloat-abi=hard", + "-mfpu=fpv4-sp-d16" + ], + "linkerscript": "targets/nxpmk66f18.ld", + "extra-files": [ + "src/device/nxp/mk66f18.s", + "targets/teensy36.s" + ], + "flash-command": "teensy_loader_cli -mmcu=mk66fx1m0 -v -w {hex}" +} + diff --git a/targets/teensy36.s b/targets/teensy36.s new file mode 100644 index 0000000000..ebfaac387b --- /dev/null +++ b/targets/teensy36.s @@ -0,0 +1,19 @@ +.section .flash_config +.global __flash_config +__flash_config: + .byte 0xFF + .byte 0xFF + .byte 0xFF + .byte 0xFF + .byte 0xFF + .byte 0xFF + .byte 0xFF + .byte 0xFF + .byte 0xFF + .byte 0xFF + .byte 0xFF + .byte 0xFF + .byte 0xDE + .byte 0xF9 + .byte 0xFF + .byte 0xFF diff --git a/tools/gen-device-svd/gen-device-svd.go b/tools/gen-device-svd/gen-device-svd.go index f376a69326..ee1a86bbb5 100755 --- a/tools/gen-device-svd/gen-device-svd.go +++ b/tools/gen-device-svd/gen-device-svd.go @@ -16,6 +16,7 @@ import ( ) var validName = regexp.MustCompile("^[a-zA-Z0-9_]+$") +var enumBitSpecifier = regexp.MustCompile("^#[x01]+$") type SVDFile struct { XMLName xml.Name `xml:"device"` @@ -41,6 +42,7 @@ type SVDRegister struct { Name string `xml:"name"` Description string `xml:"description"` Dim *string `xml:"dim"` + DimIndex *string `xml:"dimIndex"` DimIncrement string `xml:"dimIncrement"` Size *string `xml:"size"` Fields []*SVDField `xml:"fields>field"` @@ -484,13 +486,24 @@ func parseBitfields(groupName, regName string, fieldEls []*SVDField, bitfieldPre } for _, enumEl := range fieldEl.EnumeratedValues { enumName := enumEl.Name + if strings.EqualFold(enumName, "reserved") || !validName.MatchString(enumName) { + continue + } if !unicode.IsUpper(rune(enumName[0])) && !unicode.IsDigit(rune(enumName[0])) { enumName = strings.ToUpper(enumName) } enumDescription := strings.Replace(enumEl.Description, "\n", " ", -1) enumValue, err := strconv.ParseUint(enumEl.Value, 0, 32) if err != nil { - panic(err) + if enumBitSpecifier.MatchString(enumEl.Value) { + // NXP SVDs use the form #xx1x, #x0xx, etc for values + enumValue, err = strconv.ParseUint(strings.Replace(enumEl.Value[1:], "x", "0", -1), 2, 32) + if err != nil { + panic(err) + } + } else { + panic(err) + } } fields = append(fields, Bitfield{ name: fmt.Sprintf("%s_%s%s_%s_%s", groupName, bitfieldPrefix, regName, fieldName, enumName), @@ -545,6 +558,59 @@ func (r *Register) dim() int { return int(dim) } +func (r *Register) dimIndex() []string { + defer func() { + if err := recover(); err != nil { + fmt.Println("register", r.name()) + panic(err) + } + }() + + dim := r.dim() + if r.element.DimIndex == nil { + if dim <= 0 { + return nil + } + + idx := make([]string, dim) + for i := range idx { + idx[i] = strconv.FormatInt(int64(i), 10) + } + return idx + } + + t := strings.Split(*r.element.DimIndex, "-") + if len(t) == 2 { + x, err := strconv.ParseInt(t[0], 0, 32) + if err != nil { + panic(err) + } + y, err := strconv.ParseInt(t[1], 0, 32) + if err != nil { + panic(err) + } + + if x < 0 || y < x || y-x != int64(dim-1) { + panic("invalid dimIndex") + } + + idx := make([]string, dim) + for i := x; i <= y; i++ { + idx[i-x] = strconv.FormatInt(i, 10) + } + return idx + } else if len(t) > 2 { + panic("invalid dimIndex") + } + + s := strings.Split(*r.element.DimIndex, ",") + if len(s) != dim { + panic("invalid dimIndex") + } + + return s +} + func (r *Register) size() int { if r.element.Size != nil { size, err := strconv.ParseInt(*r.element.Size, 0, 32) @@ -568,10 +634,10 @@ func parseRegister(groupName string, regEl *SVDRegister, baseAddress uint64, bit // a "spaced array" of registers, special processing required // we need to generate a separate register for each "element" var results []*PeripheralField - for i := uint64(0); i < uint64(reg.dim()); i++ { - regAddress := reg.address() + (i * dimIncrement) + for i, j := range reg.dimIndex() { + regAddress := reg.address() + (uint64(i) * dimIncrement) results = append(results, &PeripheralField{ - name: strings.ToUpper(strings.Replace(reg.name(), "%s", strconv.FormatUint(i, 10), -1)), + name: strings.ToUpper(strings.Replace(reg.name(), "%s", j, -1)), address: regAddress, description: reg.description(), array: -1, @@ -589,11 +655,12 @@ func parseRegister(groupName string, regEl *SVDRegister, baseAddress uint64, bit regName = strings.ToUpper(regName) } + bitfields := parseBitfields(groupName, regName, regEl.Fields, bitfieldPrefix) return []*PeripheralField{&PeripheralField{ name: regName, address: reg.address(), description: reg.description(), - bitfields: parseBitfields(groupName, regName, regEl.Fields, bitfieldPrefix), + bitfields: bitfields, array: reg.dim(), elementSize: reg.size(), }} @@ -770,7 +837,7 @@ var ( if register.array != -1 { regType = fmt.Sprintf("[%d]%s", register.array, regType) } - fmt.Fprintf(w, "\t%s %s\n", register.name, regType) + fmt.Fprintf(w, "\t%s %s // 0x%X\n", register.name, regType, register.address-peripheral.BaseAddress) // next address if lastCluster {