diff --git a/src/machine/machine_esp32c3.go b/src/machine/machine_esp32c3.go index 695615c1..e1892f47 100644 --- a/src/machine/machine_esp32c3.go +++ b/src/machine/machine_esp32c3.go @@ -5,11 +5,15 @@ package machine import ( "device/esp" + "runtime/interrupt" "runtime/volatile" + "sync" "unsafe" ) const deviceName = esp.Device +const maxPin = 22 +const cpuInterruptFromPin = 6 // CPUFrequency returns the current CPU frequency of the chip. // Currently it is a fixed frequency but it may allow changing in the future. @@ -24,6 +28,18 @@ const ( PinInputPulldown ) +type PinChange uint8 + +// Pin change interrupt constants for SetInterrupt. +const ( + PinNoInterrupt PinChange = iota + PinRising + PinFalling + PinToggle + PinLowLevel + PinHighLevel +) + // Configure this pin with the given configuration. func (p Pin) Configure(config PinConfig) { if p == NoPin { @@ -84,6 +100,11 @@ func (p Pin) mux() *volatile.Register32 { return (*volatile.Register32)(unsafe.Pointer((uintptr(unsafe.Pointer(&esp.IO_MUX.GPIO0)) + uintptr(p)*4))) } +// pin returns the PIN register corresponding to the given GPIO pin. +func (p Pin) pin() *volatile.Register32 { + return (*volatile.Register32)(unsafe.Pointer((uintptr(unsafe.Pointer(&esp.GPIO.PIN0)) + uintptr(p)*4))) +} + // Set the pin to high or low. // Warning: only use this on an output pin! func (p Pin) Set(value bool) { @@ -129,6 +150,70 @@ func (p Pin) portMaskClear() (*volatile.Register32, uint32) { return &esp.GPIO.OUT_W1TC, 1 << p } +// SetInterrupt sets an interrupt to be executed when a particular pin changes +// state. The pin should already be configured as an input, including a pull up +// or down if no external pull is provided. +// +// You can pass a nil func to unset the pin change interrupt. If you do so, +// the change parameter is ignored and can be set to any value (such as 0). +// If the pin is already configured with a callback, you must first unset +// this pins interrupt before you can set a new callback. +func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) (err error) { + if p >= maxPin { + return ErrInvalidInputPin + } + + if callback == nil || change == PinNoInterrupt { + // Disable this pin interrupt + p.pin().ClearBits(esp.GPIO_PIN_PIN_INT_TYPE_Msk | esp.GPIO_PIN_PIN_INT_ENA_Msk) + + if pinCallbacks[p] != nil { + pinCallbacks[p] = nil + } + return nil + } + + if pinCallbacks[p] != nil { + // The pin was already configured. + // To properly re-configure a pin, unset it first and set a new + // configuration. + return ErrNoPinChangeChannel + } + pinCallbacks[p] = callback + + onceSetupPinInterrupt.Do(func() { + err = setupPinInterrupt() + }) + if err != nil { + return err + } + + p.pin().Set( + (p.pin().Get() & ^uint32(esp.GPIO_PIN_PIN_INT_TYPE_Msk|esp.GPIO_PIN_PIN_INT_ENA_Msk)) | + uint32(change)< 0 { - // save mepc, which could be overwritten by another CPU interrupt + // save MSTATUS & MEPC, which could be overwritten by another CPU interrupt + mstatus := riscv.MSTATUS.Get() mepc := riscv.MEPC.Get() + // Useing threshold to temporary disable this interrupts. + // FYI: using CPU interrupt enable bit make runtime to loose interrupts. + reg := (*volatile.Register32)(unsafe.Pointer((uintptr(unsafe.Pointer(&esp.INTERRUPT_CORE0.CPU_INT_PRI_0)) + uintptr(interruptNumber)*4))) + thresholdSave := reg.Get() + reg.Set(disableThreshold) + riscv.Asm("fence") - // disable interrupt interruptBit := uint32(1 << interruptNumber) - esp.INTERRUPT_CORE0.CPU_INT_ENABLE.ClearBits(interruptBit) // reset pending status interrupt if esp.INTERRUPT_CORE0.CPU_INT_TYPE.Get()&interruptBit != 0 { @@ -72,23 +189,22 @@ func handleInterrupt() { } // enable CPU interrupts - riscv.MSTATUS.SetBits(0x8) + riscv.MSTATUS.SetBits(1 << 3) // Call registered interrupt handler(s) - esp.HandleInterrupt(int(interruptNumber)) + callHandler(int(interruptNumber)) // disable CPU interrupts - riscv.MSTATUS.ClearBits(0x8) + riscv.MSTATUS.ClearBits(1 << 3) - // mpie must be set to 1 to resume interrupts after 'MRET' - riscv.MSTATUS.SetBits(0x80) + // restore interrupt threshold to enable interrupt again + reg.Set(thresholdSave) + riscv.Asm("fence") - // restore MEPC + // restore MSTATUS & MEPC + riscv.MSTATUS.Set(mstatus) riscv.MEPC.Set(mepc) - // enable this interrupt - esp.INTERRUPT_CORE0.CPU_INT_ENABLE.SetBits(interruptBit) - // do not enable CPU interrupts now // the 'MRET' in src/device/riscv/handleinterrupt.S will copies the state of MPIE back into MIE, and subsequently clears MPIE. // riscv.MSTATUS.SetBits(0x8) @@ -104,6 +220,16 @@ func handleException(mcause uintptr) { println("*** Exception: pc:", riscv.MEPC.Get()) println("*** Exception: code:", uint32(mcause&0x1f)) println("*** Exception: mcause:", mcause) + switch uint32(mcause & 0x1f) { + case 1: + println("*** virtual addess:", riscv.MTVAL.Get()) + case 2: + println("*** opcode:", riscv.MTVAL.Get()) + case 5: + println("*** read address:", riscv.MTVAL.Get()) + case 7: + println("*** write address:", riscv.MTVAL.Get()) + } for { riscv.Asm("wfi") }