Skip to content

Commit 5815c9f

Browse files
committed
feat(rp2350): add preemptive scheduler and DMA UART for crea8 board
This commit introduces an alternate runtime for RP2350 (crea8 board) optimized for real-time applications like 3D printing and PNP machines. Key features: - Preemptive scheduler using SysTick timer for time slicing - PendSV-based context switching (matches Pico SDK pattern) - DMA-based UART for non-blocking serial I/O - Priority-based task scheduling with 8 priority levels - Multi-core support with both RP2350 cores - Improved IRQ priority management New files: - targets/crea8.json, crea8-preemptive.json - Board targets - src/machine/board_crea8.go - Pin definitions for 3D printing/PNP - src/runtime/scheduler_preemptive.go - Preemptive scheduler - src/runtime/runtime_rp2_preemptive.go - RP2350 preemptive runtime - src/machine/machine_rp2_uart_dma.go - DMA-based UART driver - src/internal/task/*_preemptive*.go - Task/context support - src/examples/crea8-preemptive/ - Example application Build with: tinygo build -target=crea8-preemptive
1 parent 3869f76 commit 5815c9f

14 files changed

+2366
-1
lines changed

compileopts/options.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
var (
1111
validBuildModeOptions = []string{"default", "c-shared", "wasi-legacy"}
1212
validGCOptions = []string{"none", "leaking", "conservative", "custom", "precise", "boehm"}
13-
validSchedulerOptions = []string{"none", "tasks", "asyncify", "threads", "cores"}
13+
validSchedulerOptions = []string{"none", "tasks", "asyncify", "threads", "cores", "preemptive"}
1414
validSerialOptions = []string{"none", "uart", "usb", "rtt"}
1515
validPrintSizeOptions = []string{"none", "short", "full", "html"}
1616
validPanicStrategyOptions = []string{"print", "trap"}
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
// Example demonstrating preemptive scheduler and DMA UART on RP2350 (Crea8 board).
2+
//
3+
// Build with:
4+
// tinygo build -target=crea8-preemptive -o firmware.uf2 ./examples/crea8-preemptive
5+
//
6+
// This example shows:
7+
// - Preemptive multitasking with time slicing
8+
// - DMA-based UART for non-blocking serial I/O
9+
// - Priority-based task scheduling
10+
// - Real-time task patterns for 3D printing / PNP applications
11+
12+
package main
13+
14+
import (
15+
"machine"
16+
"runtime"
17+
"time"
18+
)
19+
20+
// Task priorities (0 = highest, 7 = lowest)
21+
const (
22+
PriorityMotion = 0 // Highest - stepper motor control
23+
PrioritySerial = 2 // High - serial communication
24+
PriorityTemperature = 4 // Medium - temperature control
25+
PriorityDisplay = 6 // Low - display updates
26+
PriorityIdle = 7 // Lowest - idle task
27+
)
28+
29+
// Shared state (protected by mutex in real applications)
30+
var (
31+
stepperX int32
32+
stepperY int32
33+
stepperZ int32
34+
35+
hotendTemp float32
36+
bedTemp float32
37+
targetHotend float32 = 200.0
38+
targetBed float32 = 60.0
39+
)
40+
41+
func main() {
42+
// Configure LED for status indication
43+
machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput})
44+
45+
// Configure stepper pins (example)
46+
machine.STEPPER_X_STEP.Configure(machine.PinConfig{Mode: machine.PinOutput})
47+
machine.STEPPER_X_DIR.Configure(machine.PinConfig{Mode: machine.PinOutput})
48+
machine.STEPPER_Y_STEP.Configure(machine.PinConfig{Mode: machine.PinOutput})
49+
machine.STEPPER_Y_DIR.Configure(machine.PinConfig{Mode: machine.PinOutput})
50+
51+
// Configure heater pins
52+
machine.HEATER_HOTEND.Configure(machine.PinConfig{Mode: machine.PinOutput})
53+
machine.HEATER_BED.Configure(machine.PinConfig{Mode: machine.PinOutput})
54+
55+
println("Crea8 Preemptive Scheduler Demo")
56+
println("================================")
57+
println("Number of CPUs:", runtime.NumCPU())
58+
59+
// Start high-priority motion control task
60+
go motionControlTask()
61+
62+
// Start serial communication task
63+
go serialTask()
64+
65+
// Start temperature control task
66+
go temperatureControlTask()
67+
68+
// Start display update task
69+
go displayTask()
70+
71+
// Main loop - blink LED to show system is running
72+
for {
73+
machine.LED.High()
74+
time.Sleep(500 * time.Millisecond)
75+
machine.LED.Low()
76+
time.Sleep(500 * time.Millisecond)
77+
}
78+
}
79+
80+
// motionControlTask handles stepper motor control with highest priority
81+
// This task should not be preempted during critical step pulse generation
82+
func motionControlTask() {
83+
stepInterval := 100 * time.Microsecond // 10kHz step rate
84+
85+
for {
86+
// Generate step pulses (simplified example)
87+
// In a real implementation, you would use interrupt.Disable()/Restore()
88+
// for critical timing sections
89+
machine.STEPPER_X_STEP.High()
90+
machine.STEPPER_Y_STEP.High()
91+
92+
// Small delay for step pulse width (~1-2us)
93+
for i := 0; i < 10; i++ {
94+
// Busy wait
95+
}
96+
97+
machine.STEPPER_X_STEP.Low()
98+
machine.STEPPER_Y_STEP.Low()
99+
100+
// Update position counters
101+
stepperX++
102+
stepperY++
103+
104+
// Wait for next step
105+
time.Sleep(stepInterval)
106+
}
107+
}
108+
109+
// serialTask handles serial communication
110+
func serialTask() {
111+
// Buffer for incoming data
112+
buf := make([]byte, 256)
113+
114+
for {
115+
// Check for incoming data
116+
if machine.Serial.Buffered() > 0 {
117+
n, _ := machine.Serial.Read(buf)
118+
if n > 0 {
119+
// Echo back received data
120+
machine.Serial.Write(buf[:n])
121+
122+
// Process G-code commands (simplified)
123+
processCommand(buf[:n])
124+
}
125+
}
126+
127+
// Yield to other tasks
128+
runtime.Gosched()
129+
}
130+
}
131+
132+
// temperatureControlTask handles heater PID control
133+
func temperatureControlTask() {
134+
for {
135+
// Read temperature sensors (simplified - would use ADC)
136+
hotendTemp = readTemperature(machine.TEMP_HOTEND_ADC)
137+
bedTemp = readTemperature(machine.TEMP_BED_ADC)
138+
139+
// Simple bang-bang control (real implementation would use PID)
140+
if hotendTemp < targetHotend-2 {
141+
machine.HEATER_HOTEND.High()
142+
} else if hotendTemp > targetHotend+2 {
143+
machine.HEATER_HOTEND.Low()
144+
}
145+
146+
if bedTemp < targetBed-2 {
147+
machine.HEATER_BED.High()
148+
} else if bedTemp > targetBed+2 {
149+
machine.HEATER_BED.Low()
150+
}
151+
152+
// Temperature control runs at 10Hz
153+
time.Sleep(100 * time.Millisecond)
154+
}
155+
}
156+
157+
// displayTask updates the display (lowest priority)
158+
func displayTask() {
159+
for {
160+
// Print status (would update LCD in real application)
161+
println("X:", stepperX, "Y:", stepperY, "Z:", stepperZ)
162+
println("Hotend:", hotendTemp, "/", targetHotend)
163+
println("Bed:", bedTemp, "/", targetBed)
164+
println("---")
165+
166+
// Display updates at 1Hz
167+
time.Sleep(1 * time.Second)
168+
}
169+
}
170+
171+
// readTemperature reads temperature from ADC (simplified)
172+
func readTemperature(pin machine.Pin) float32 {
173+
// Simplified - real implementation would read ADC and convert
174+
return 25.0 // Room temperature placeholder
175+
}
176+
177+
// processCommand processes incoming G-code commands
178+
func processCommand(data []byte) {
179+
// Simplified G-code parsing
180+
if len(data) > 0 {
181+
switch data[0] {
182+
case 'G':
183+
println("G-code command received")
184+
case 'M':
185+
println("M-code command received")
186+
}
187+
}
188+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
//go:build scheduler.preemptive && (rp2040 || rp2350)
2+
3+
// Atomic operations for preemptive scheduler on RP2040/RP2350.
4+
// Uses hardware spinlocks for atomicity.
5+
6+
package task
7+
8+
import "runtime/interrupt"
9+
10+
// Uint32 provides atomic uint32 operations
11+
type Uint32 struct {
12+
v uint32
13+
}
14+
15+
// Load atomically loads the value
16+
func (u *Uint32) Load() uint32 {
17+
mask := lockAtomics()
18+
v := u.v
19+
unlockAtomics(mask)
20+
return v
21+
}
22+
23+
// Store atomically stores a value
24+
func (u *Uint32) Store(v uint32) {
25+
mask := lockAtomics()
26+
u.v = v
27+
unlockAtomics(mask)
28+
}
29+
30+
// Swap atomically swaps and returns the old value
31+
func (u *Uint32) Swap(new uint32) uint32 {
32+
mask := lockAtomics()
33+
old := u.v
34+
u.v = new
35+
unlockAtomics(mask)
36+
return old
37+
}
38+
39+
// CompareAndSwap performs atomic compare-and-swap
40+
func (u *Uint32) CompareAndSwap(old, new uint32) bool {
41+
mask := lockAtomics()
42+
if u.v == old {
43+
u.v = new
44+
unlockAtomics(mask)
45+
return true
46+
}
47+
unlockAtomics(mask)
48+
return false
49+
}
50+
51+
// Add atomically adds delta and returns new value
52+
func (u *Uint32) Add(delta uint32) uint32 {
53+
mask := lockAtomics()
54+
u.v += delta
55+
v := u.v
56+
unlockAtomics(mask)
57+
return v
58+
}
59+
60+
//go:linkname lockAtomics runtime.lockAtomics
61+
func lockAtomics() interrupt.State
62+
63+
//go:linkname unlockAtomics runtime.unlockAtomics
64+
func unlockAtomics(interrupt.State)
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//go:build scheduler.preemptive
2+
3+
// Futex implementation for preemptive scheduler.
4+
5+
package task
6+
7+
import "runtime/interrupt"
8+
9+
// Futex provides wait/wake functionality using a pointer as key
10+
type Futex struct {
11+
Uint32
12+
waiters Stack
13+
}
14+
15+
// Wait atomically checks if value equals cmp and if so, sleeps
16+
func (f *Futex) Wait(cmp uint32) (awoken bool) {
17+
mask := lockFutex()
18+
19+
if f.Uint32.Load() != cmp {
20+
unlockFutex(mask)
21+
return false
22+
}
23+
24+
f.waiters.Push(Current())
25+
unlockFutex(mask)
26+
27+
Pause()
28+
return true
29+
}
30+
31+
// Wake wakes a single waiter
32+
func (f *Futex) Wake() {
33+
mask := lockFutex()
34+
if t := f.waiters.Pop(); t != nil {
35+
scheduleTask(t)
36+
}
37+
unlockFutex(mask)
38+
}
39+
40+
// WakeAll wakes all waiters
41+
func (f *Futex) WakeAll() {
42+
mask := lockFutex()
43+
for t := f.waiters.Pop(); t != nil; t = f.waiters.Pop() {
44+
scheduleTask(t)
45+
}
46+
unlockFutex(mask)
47+
}
48+
49+
//go:linkname lockFutex runtime.lockFutex
50+
func lockFutex() interrupt.State
51+
52+
//go:linkname unlockFutex runtime.unlockFutex
53+
func unlockFutex(interrupt.State)

0 commit comments

Comments
 (0)