Skip to content

Commit 776a655

Browse files
authored
- Added watchdog timer API for both RP2040 and RP2350 (ZigEmbeddedGroup#338)
1 parent cd3180c commit 776a655

File tree

6 files changed

+257
-1
lines changed

6 files changed

+257
-1
lines changed

examples/raspberrypi/rp2xxx/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ examples will eventually be able to run on either chip with no changes due to th
1919
Shows an example of a fully custom clock configuration.
2020
- [gpio clock output](src/gpio_clock_output.zig) on the [Pico](https://www.raspberrypi.com/products/raspberry-pi-pico/) or [Pico2](https://www.raspberrypi.com/products/raspberry-pi-pico-2/) boards
2121
Routes the SYS clock divided by 1000 out to GPIO25.
22+
- [watchdog timer](src/watchdog_timer.zig) on the [Pico](https://www.raspberrypi.com/products/raspberry-pi-pico/) or [Pico2](https://www.raspberrypi.com/products/raspberry-pi-pico-2/) boards
23+
Enables a watchdog timer for 1 second, and demonstrates the chip resetting when the watchdog timer elapses
2224

2325
### RP2040 Only
2426

examples/raspberrypi/rp2xxx/build.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ pub fn build(b: *std.Build) void {
4747
.{ .name = "gpio-clock-output", .file = "src/gpio_clock_output.zig" },
4848
.{ .name = "changing-system-clocks", .file = "src/changing_system_clocks.zig" },
4949
.{ .name = "custom-clock-config", .file = "src/custom_clock_config.zig" },
50+
.{ .name = "watchdog-timer", .file = "src/watchdog_timer.zig" },
5051
};
5152

5253
var available_examples = std.ArrayList(Example).init(b.allocator);
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
const std = @import("std");
2+
const microzig = @import("microzig");
3+
const rp2xxx = microzig.hal;
4+
const watchdog = rp2xxx.watchdog;
5+
const time = rp2xxx.time;
6+
7+
const pin_config = rp2xxx.pins.GlobalConfiguration{
8+
.GPIO25 = .{
9+
.name = "led",
10+
.direction = .out,
11+
},
12+
};
13+
const pins = pin_config.pins();
14+
15+
pub fn main() !void {
16+
pin_config.apply();
17+
18+
// Set up different blinking behavior if this reset was due to a WD trip
19+
const wd_reset: bool = if (watchdog.caused_reboot()) |_|
20+
true
21+
else
22+
false;
23+
24+
watchdog.apply(.{
25+
// Set up the watchdog timer to trip after 1 second
26+
.duration_us = 1000 * 1000,
27+
// Watchdog timer should NOT trip if a debugger is connected
28+
// NOTE: This doesn't appear to work with a JLink, your mileage may vary
29+
.pause_during_debug = true,
30+
});
31+
32+
var blink_time: usize = 100;
33+
var fast_counter: usize = 0;
34+
35+
// Slowly decrease blink frequency until a watchdog reset is triggered from waiting too long between watchdog.update() calls
36+
while (true) {
37+
38+
// "null" uses whatever the previously configured timeout period for the watchdog was
39+
watchdog.update(null);
40+
pins.led.toggle();
41+
time.sleep_ms(@as(usize, @intCast(blink_time)));
42+
43+
// 10 fast blinks to start if the WD caused reset
44+
if (wd_reset and fast_counter < 10) {
45+
fast_counter += 1;
46+
} else {
47+
blink_time += 100;
48+
}
49+
}
50+
}

port/raspberrypi/rp2xxx/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ The RP2350 is very similar to the RP2040, but has enough differences to require
2424
- [ ] spi
2525
- [x] time
2626
- [ ] uart
27-
- [ ] usb
27+
- [ ] usb
28+
- [x] watchdog

port/raspberrypi/rp2xxx/src/hal.zig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ pub const i2c = @import("hal/i2c.zig");
2424
pub const time = @import("hal/time.zig");
2525
pub const uart = @import("hal/uart.zig");
2626
pub const usb = @import("hal/usb.zig");
27+
pub const watchdog = @import("hal/watchdog.zig");
2728
pub const drivers = @import("hal/drivers.zig");
2829
pub const compatibility = @import("hal/compatibility.zig");
2930

@@ -44,6 +45,9 @@ pub inline fn init() void {
4445
/// using the reccomended initialization sequence
4546
pub fn init_sequence(comptime clock_cfg: clocks.config.Global) void {
4647

48+
// Disable the watchdog as a soft reset doesn't disable the WD automatically!
49+
watchdog.disable();
50+
4751
// Reset all peripherals to put system into a known state, - except
4852
// for QSPI pads and the XIP IO bank, as this is fatal if running from
4953
// flash - and the PLLs, as this is fatal if clock muxing has not been
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
const std = @import("std");
2+
const microzig = @import("microzig");
3+
const WATCHDOG = microzig.chip.peripherals.WATCHDOG;
4+
const PSM = microzig.chip.peripherals.PSM;
5+
const hw = @import("hw.zig");
6+
const cpu = @import("compatibility.zig").cpu;
7+
8+
pub const Config =
9+
switch (cpu) {
10+
.RP2040 => struct {
11+
// See errata RP2040-E1, duration ends up getting multiplied by 2 reducing the allowable delay range
12+
duration_us: u23,
13+
pause_during_debug: bool,
14+
},
15+
.RP2350 => struct {
16+
duration_us: u24,
17+
pause_during_debug: bool,
18+
},
19+
};
20+
21+
var previous_watchdog_delay: ?u24 = null;
22+
23+
/// Configure and enable the watchdog timer
24+
pub fn apply(config: Config) void {
25+
previous_watchdog_delay = config.duration_us;
26+
27+
// Disable WD during changing settings
28+
disable();
29+
30+
switch (cpu) {
31+
.RP2040 => hw.set_alias(&PSM.WDSEL).write(.{
32+
.ROSC = 0,
33+
.XOSC = 0,
34+
.CLOCKS = 1,
35+
.RESETS = 1,
36+
.BUSFABRIC = 1,
37+
.ROM = 1,
38+
.SRAM0 = 1,
39+
.SRAM1 = 1,
40+
.SRAM2 = 1,
41+
.SRAM3 = 1,
42+
.SRAM4 = 1,
43+
.SRAM5 = 1,
44+
.XIP = 1,
45+
.VREG_AND_CHIP_RESET = 1,
46+
.SIO = 1,
47+
.PROC0 = 1,
48+
.PROC1 = 1,
49+
.padding = 0,
50+
}),
51+
.RP2350 => hw.set_alias(&PSM.WDSEL).write(.{
52+
.PROC_COLD = 1,
53+
.OTP = 1,
54+
.ROSC = 0,
55+
.XOSC = 0,
56+
.RESETS = 1,
57+
.CLOCKS = 1,
58+
.PSM_READY = 1,
59+
.BUSFABRIC = 1,
60+
.ROM = 1,
61+
.BOOTRAM = 1,
62+
.SRAM0 = 1,
63+
.SRAM1 = 1,
64+
.SRAM2 = 1,
65+
.SRAM3 = 1,
66+
.SRAM4 = 1,
67+
.SRAM5 = 1,
68+
.SRAM6 = 1,
69+
.SRAM7 = 1,
70+
.SRAM8 = 1,
71+
.SRAM9 = 1,
72+
.XIP = 1,
73+
.SIO = 1,
74+
.ACCESSCTRL = 1,
75+
.PROC0 = 1,
76+
.PROC1 = 1,
77+
.padding = 0,
78+
}),
79+
}
80+
// Tell RESETS hardware to reset everything except ROSC/XOSC on a watchdog reset
81+
82+
// See errata RP2040-E1, duration needs to be multiplied by 2 for RP2040
83+
const duration: u24 = if (cpu == .RP2040) @as(u24, config.duration_us) << 1 else config.duration_us;
84+
WATCHDOG.LOAD.write(.{
85+
.LOAD = duration,
86+
.padding = 0,
87+
});
88+
hw.set_alias(&WATCHDOG.CTRL).write(.{
89+
.TIME = 0,
90+
.PAUSE_JTAG = if (config.pause_during_debug) 1 else 0,
91+
.PAUSE_DBG0 = if (config.pause_during_debug) 1 else 0,
92+
.PAUSE_DBG1 = if (config.pause_during_debug) 1 else 0,
93+
.reserved30 = 0,
94+
.ENABLE = 0,
95+
.TRIGGER = 0,
96+
});
97+
98+
enable();
99+
}
100+
101+
/// Used in a scratch register to determine if the reboot was due to a user enabled
102+
/// watchdog reset, or ROM code using the watchdog
103+
const WATCHDOG_NON_REBOOT_MAGIC: usize = 0x6ab73121;
104+
105+
/// Enable (resume) the watchdog timer
106+
pub fn enable() void {
107+
// Update scratch[4] to distinguish from both the magic number used for rebooting to a
108+
// specific address, or 0 used to reboot into regular flash path
109+
WATCHDOG.SCRATCH4.write(.{ .SCRATCH4 = WATCHDOG_NON_REBOOT_MAGIC });
110+
111+
hw.set_alias(&WATCHDOG.CTRL).write(.{
112+
.TIME = 0,
113+
.PAUSE_JTAG = 0,
114+
.PAUSE_DBG0 = 0,
115+
.PAUSE_DBG1 = 0,
116+
.reserved30 = 0,
117+
.ENABLE = 1,
118+
.TRIGGER = 0,
119+
});
120+
}
121+
122+
/// Disable (pause) the watchdog timer
123+
pub inline fn disable() void {
124+
hw.clear_alias(&WATCHDOG.CTRL).write(.{
125+
.TIME = 0,
126+
.PAUSE_JTAG = 0,
127+
.PAUSE_DBG0 = 0,
128+
.PAUSE_DBG1 = 0,
129+
.reserved30 = 0,
130+
.ENABLE = 1,
131+
.TRIGGER = 0,
132+
});
133+
}
134+
135+
/// Update the watchdog delay (pet the watchdog), this should be called periodically
136+
/// to keep the watchdog from triggering
137+
///
138+
/// null for new_delay uses the previously configured delay from apply()
139+
pub fn update(delay_us: ?u24) void {
140+
var duration_us: u24 = if (delay_us) |nd| nd else if (previous_watchdog_delay) |pd| pd else std.debug.panic("update() called before watchdog configured with apply()", .{});
141+
142+
// See errata RP2040-E1, duration needs to be multiplied by 2 for RP2040
143+
if (cpu == .RP2040) duration_us = duration_us << 1;
144+
WATCHDOG.LOAD.write(.{
145+
.LOAD = duration_us,
146+
.padding = 0,
147+
});
148+
}
149+
150+
/// Force a watchdog processor reset to occur
151+
///
152+
/// WARNING: Will reset the MCU!
153+
pub inline fn force() void {
154+
hw.set_alias(&WATCHDOG.CTRL).write(.{
155+
.TIME = 0,
156+
.PAUSE_JTAG = 0,
157+
.PAUSE_DBG0 = 0,
158+
.PAUSE_DBG1 = 0,
159+
.reserved30 = 0,
160+
.ENABLE = 0,
161+
.TRIGGER = 1,
162+
});
163+
}
164+
165+
/// The remaining us until a watchdog triggers
166+
///
167+
/// NOTE: Due to a silicon bug on the RP2040 this always
168+
/// just returns the configured ticks, NOT the remaining
169+
/// ticks. RP2350 functions as expected.
170+
pub fn remaining_us() u24 {
171+
const rd = WATCHDOG.CTRL.read();
172+
return switch (cpu) {
173+
.RP2040 => rd.TIME / 2,
174+
.RP2350 => rd.TIME,
175+
};
176+
}
177+
178+
const TriggerType = enum {
179+
UserInitiatedTimer,
180+
UserInitiatedForce,
181+
RomInitiatedTimer,
182+
RomInitiatedForce,
183+
};
184+
185+
/// Check if the watchdog was the reason for the last reboot
186+
///
187+
/// TODO: RP2350 SDK masks this with a ROM function:
188+
/// return watchdog_hw->reason && rom_get_last_boot_type() == BOOT_TYPE_NORMAL;
189+
pub fn caused_reboot() ?TriggerType {
190+
const scratch4 = WATCHDOG.SCRATCH4.read();
191+
const reason = WATCHDOG.REASON.read();
192+
if (reason.TIMER == 0 and reason.FORCE == 0) return null;
193+
if (scratch4.SCRATCH4 == WATCHDOG_NON_REBOOT_MAGIC) {
194+
return if (reason.TIMER == 1) .UserInitiatedTimer else .UserInitiatedForce;
195+
} else {
196+
return if (reason.TIMER == 1) .RomInitiatedTimer else .RomInitiatedForce;
197+
}
198+
}

0 commit comments

Comments
 (0)