mirror of https://github.com/tinygo-org/tinygo.git
Yurii Soldak
2 years ago
committed by
Ron Evans
3 changed files with 277 additions and 0 deletions
@ -0,0 +1,35 @@ |
|||
//go:build rp2040
|
|||
|
|||
package main |
|||
|
|||
// This example demonstrates scheduling a delayed interrupt by real time clock.
|
|||
//
|
|||
// An interrupt may execute user callback function or used for its side effects
|
|||
// like waking up from sleep or dormant states.
|
|||
//
|
|||
// The interrupt can be configured to repeat.
|
|||
//
|
|||
// There is no separate method to disable interrupt, use 0 delay for that.
|
|||
//
|
|||
// Unfortunately, it is not possible to use time.Duration to work with RTC directly,
|
|||
// that would introduce a circular dependency between "machine" and "time" packages.
|
|||
|
|||
import ( |
|||
"fmt" |
|||
"machine" |
|||
"time" |
|||
) |
|||
|
|||
func main() { |
|||
|
|||
// Schedule and enable recurring interrupt.
|
|||
// The callback function is executed in the context of an interrupt handler,
|
|||
// so regular restructions for this sort of code apply: no blocking, no memory allocation, etc.
|
|||
delay := time.Minute + 12*time.Second |
|||
machine.RTC.SetInterrupt(uint32(delay.Seconds()), true, func() { println("Peekaboo!") }) |
|||
|
|||
for { |
|||
fmt.Printf("%v\r\n", time.Now().Format(time.RFC3339)) |
|||
time.Sleep(1 * time.Second) |
|||
} |
|||
} |
@ -0,0 +1,240 @@ |
|||
//go:build rp2040
|
|||
|
|||
// Implementation based on code located here:
|
|||
// https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/hardware_rtc/rtc.c
|
|||
|
|||
package machine |
|||
|
|||
import ( |
|||
"device/rp" |
|||
"errors" |
|||
"runtime/interrupt" |
|||
"unsafe" |
|||
) |
|||
|
|||
type rtcType rp.RTC_Type |
|||
|
|||
type rtcTime struct { |
|||
Year int16 |
|||
Month int8 |
|||
Day int8 |
|||
Dotw int8 |
|||
Hour int8 |
|||
Min int8 |
|||
Sec int8 |
|||
} |
|||
|
|||
var RTC = (*rtcType)(unsafe.Pointer(rp.RTC)) |
|||
|
|||
const ( |
|||
second = 1 |
|||
minute = 60 * second |
|||
hour = 60 * minute |
|||
day = 24 * hour |
|||
) |
|||
|
|||
var ( |
|||
rtcAlarmRepeats bool |
|||
rtcCallback func() |
|||
rtcEpoch = rtcTime{ |
|||
Year: 1970, Month: 1, Day: 1, Dotw: 4, Hour: 0, Min: 0, Sec: 0, |
|||
} |
|||
) |
|||
|
|||
var ( |
|||
ErrRtcDelayTooSmall = errors.New("RTC interrupt deplay is too small, shall be at least 1 second") |
|||
ErrRtcDelayTooLarge = errors.New("RTC interrupt deplay is too large, shall be no more than 1 day") |
|||
) |
|||
|
|||
// SetInterrupt configures delayed and optionally recurring interrupt by real time clock.
|
|||
//
|
|||
// Delay is specified in whole seconds, allowed range depends on platform.
|
|||
// Zero delay disables previously configured interrupt, if any.
|
|||
//
|
|||
// RP2040 implementation allows delay to be up to 1 day, otherwise a respective error is emitted.
|
|||
func (rtc *rtcType) SetInterrupt(delay uint32, repeat bool, callback func()) error { |
|||
|
|||
// Verify delay range
|
|||
if delay > day { |
|||
return ErrRtcDelayTooLarge |
|||
} |
|||
|
|||
// De-configure delayed interrupt if delay is zero
|
|||
if delay == 0 { |
|||
rtc.disableInterruptMatch() |
|||
return nil |
|||
} |
|||
|
|||
// Configure delayed interrupt
|
|||
rtc.setDivider() |
|||
|
|||
rtcAlarmRepeats = repeat |
|||
rtcCallback = callback |
|||
|
|||
err := rtc.setTime(rtcEpoch) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
rtc.setAlarm(toAlarmTime(delay), callback) |
|||
|
|||
return nil |
|||
} |
|||
|
|||
func toAlarmTime(delay uint32) rtcTime { |
|||
result := rtcEpoch |
|||
remainder := delay + 1 // needed "+1", otherwise alarm fires one second too early
|
|||
if remainder >= hour { |
|||
result.Hour = int8(remainder / hour) |
|||
remainder %= hour |
|||
} |
|||
if remainder >= minute { |
|||
result.Min = int8(remainder / minute) |
|||
remainder %= minute |
|||
} |
|||
result.Sec = int8(remainder) |
|||
return result |
|||
} |
|||
|
|||
func (rtc *rtcType) setDivider() { |
|||
// Get clk_rtc freq and make sure it is running
|
|||
rtcFreq := configuredFreq[clkRTC] |
|||
if rtcFreq == 0 { |
|||
panic("can not set RTC divider, clock is not running") |
|||
} |
|||
|
|||
// Take rtc out of reset now that we know clk_rtc is running
|
|||
resetBlock(rp.RESETS_RESET_RTC) |
|||
unresetBlockWait(rp.RESETS_RESET_RTC) |
|||
|
|||
// Set up the 1 second divider.
|
|||
// If rtc_freq is 400 then clkdiv_m1 should be 399
|
|||
rtcFreq -= 1 |
|||
|
|||
// Check the freq is not too big to divide
|
|||
if rtcFreq > rp.RTC_CLKDIV_M1_CLKDIV_M1_Msk { |
|||
panic("can not set RTC divider, clock frequency is too big to divide") |
|||
} |
|||
|
|||
// Write divide value
|
|||
rtc.CLKDIV_M1.Set(rtcFreq) |
|||
} |
|||
|
|||
// setTime configures RTC with supplied time, initialises and activates it.
|
|||
func (rtc *rtcType) setTime(t rtcTime) error { |
|||
|
|||
// Disable RTC and wait while it is still running
|
|||
rtc.CTRL.Set(0) |
|||
for rtc.isActive() { |
|||
} |
|||
|
|||
rtc.SETUP_0.Set((uint32(t.Year) << rp.RTC_SETUP_0_YEAR_Pos) | |
|||
(uint32(t.Month) << rp.RTC_SETUP_0_MONTH_Pos) | |
|||
(uint32(t.Day) << rp.RTC_SETUP_0_DAY_Pos)) |
|||
|
|||
rtc.SETUP_1.Set((uint32(t.Dotw) << rp.RTC_SETUP_1_DOTW_Pos) | |
|||
(uint32(t.Hour) << rp.RTC_SETUP_1_HOUR_Pos) | |
|||
(uint32(t.Min) << rp.RTC_SETUP_1_MIN_Pos) | |
|||
(uint32(t.Sec) << rp.RTC_SETUP_1_SEC_Pos)) |
|||
|
|||
// Load setup values into RTC clock domain
|
|||
rtc.CTRL.SetBits(rp.RTC_CTRL_LOAD) |
|||
|
|||
// Enable RTC and wait for it to be running
|
|||
rtc.CTRL.SetBits(rp.RTC_CTRL_RTC_ENABLE) |
|||
for !rtc.isActive() { |
|||
} |
|||
|
|||
return nil |
|||
} |
|||
|
|||
func (rtc *rtcType) isActive() bool { |
|||
return rtc.CTRL.HasBits(rp.RTC_CTRL_RTC_ACTIVE) |
|||
} |
|||
|
|||
// setAlarm configures alarm in RTC and arms it.
|
|||
// The callback is executed in the context of an interrupt handler,
|
|||
// so regular restructions for this sort of code apply: no blocking, no memory allocation, etc.
|
|||
func (rtc *rtcType) setAlarm(t rtcTime, callback func()) { |
|||
|
|||
rtc.disableInterruptMatch() |
|||
|
|||
// Clear all match enable bits
|
|||
rtc.IRQ_SETUP_0.ClearBits(rp.RTC_IRQ_SETUP_0_YEAR_ENA | rp.RTC_IRQ_SETUP_0_MONTH_ENA | rp.RTC_IRQ_SETUP_0_DAY_ENA) |
|||
rtc.IRQ_SETUP_1.ClearBits(rp.RTC_IRQ_SETUP_1_DOTW_ENA | rp.RTC_IRQ_SETUP_1_HOUR_ENA | rp.RTC_IRQ_SETUP_1_MIN_ENA | rp.RTC_IRQ_SETUP_1_SEC_ENA) |
|||
|
|||
// Only add to setup if it isn't -1 and set the match enable bits for things we care about
|
|||
if t.Year >= 0 { |
|||
rtc.IRQ_SETUP_0.SetBits(uint32(t.Year) << rp.RTC_SETUP_0_YEAR_Pos) |
|||
rtc.IRQ_SETUP_0.SetBits(rp.RTC_IRQ_SETUP_0_YEAR_ENA) |
|||
} |
|||
|
|||
if t.Month >= 0 { |
|||
rtc.IRQ_SETUP_0.SetBits(uint32(t.Month) << rp.RTC_SETUP_0_MONTH_Pos) |
|||
rtc.IRQ_SETUP_0.SetBits(rp.RTC_IRQ_SETUP_0_MONTH_ENA) |
|||
} |
|||
|
|||
if t.Day >= 0 { |
|||
rtc.IRQ_SETUP_0.SetBits(uint32(t.Day) << rp.RTC_SETUP_0_DAY_Pos) |
|||
rtc.IRQ_SETUP_0.SetBits(rp.RTC_IRQ_SETUP_0_DAY_ENA) |
|||
} |
|||
|
|||
if t.Dotw >= 0 { |
|||
rtc.IRQ_SETUP_1.SetBits(uint32(t.Dotw) << rp.RTC_SETUP_1_DOTW_Pos) |
|||
rtc.IRQ_SETUP_1.SetBits(rp.RTC_IRQ_SETUP_1_DOTW_ENA) |
|||
} |
|||
|
|||
if t.Hour >= 0 { |
|||
rtc.IRQ_SETUP_1.SetBits(uint32(t.Hour) << rp.RTC_SETUP_1_HOUR_Pos) |
|||
rtc.IRQ_SETUP_1.SetBits(rp.RTC_IRQ_SETUP_1_HOUR_ENA) |
|||
} |
|||
|
|||
if t.Min >= 0 { |
|||
rtc.IRQ_SETUP_1.SetBits(uint32(t.Min) << rp.RTC_SETUP_1_MIN_Pos) |
|||
rtc.IRQ_SETUP_1.SetBits(rp.RTC_IRQ_SETUP_1_MIN_ENA) |
|||
} |
|||
|
|||
if t.Sec >= 0 { |
|||
rtc.IRQ_SETUP_1.SetBits(uint32(t.Sec) << rp.RTC_SETUP_1_SEC_Pos) |
|||
rtc.IRQ_SETUP_1.SetBits(rp.RTC_IRQ_SETUP_1_SEC_ENA) |
|||
} |
|||
|
|||
// Enable the IRQ at the proc
|
|||
interrupt.New(rp.IRQ_RTC_IRQ, rtcHandleInterrupt).Enable() |
|||
|
|||
// Enable the IRQ at the peri
|
|||
rtc.INTE.Set(rp.RTC_INTE_RTC) |
|||
|
|||
rtc.enableInterruptMatch() |
|||
} |
|||
|
|||
func (rtc *rtcType) enableInterruptMatch() { |
|||
// Set matching and wait for it to be enabled
|
|||
rtc.IRQ_SETUP_0.SetBits(rp.RTC_IRQ_SETUP_0_MATCH_ENA) |
|||
for !rtc.IRQ_SETUP_0.HasBits(rp.RTC_IRQ_SETUP_0_MATCH_ACTIVE) { |
|||
} |
|||
} |
|||
|
|||
func (rtc *rtcType) disableInterruptMatch() { |
|||
// Disable matching and wait for it to stop being active
|
|||
rtc.IRQ_SETUP_0.ClearBits(rp.RTC_IRQ_SETUP_0_MATCH_ENA) |
|||
for rtc.IRQ_SETUP_0.HasBits(rp.RTC_IRQ_SETUP_0_MATCH_ACTIVE) { |
|||
} |
|||
} |
|||
|
|||
func rtcHandleInterrupt(itr interrupt.Interrupt) { |
|||
// Always disable the alarm to clear the current IRQ.
|
|||
// Even if it is a repeatable alarm, we don't want it to keep firing.
|
|||
// If it matches on a second it can keep firing for that second.
|
|||
RTC.disableInterruptMatch() |
|||
|
|||
// Call user callback function
|
|||
if rtcCallback != nil { |
|||
rtcCallback() |
|||
} |
|||
|
|||
if rtcAlarmRepeats { |
|||
// If it is a repeatable alarm, reset time and re-enable the alarm.
|
|||
RTC.setTime(rtcEpoch) |
|||
RTC.enableInterruptMatch() |
|||
} |
|||
} |
Loading…
Reference in new issue