Skip to content

Commit c56856e

Browse files
committed
Rewrite to use the new gpio v2 interface
1 parent 83762b0 commit c56856e

5 files changed

Lines changed: 590 additions & 315 deletions

File tree

deploy/src/encoder.jl

Lines changed: 95 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,72 @@
1-
include(joinpath(@__DIR__, "gpio.jl"))
1+
# Uses GPIO module (must be included before this file)
22
using .GPIO
33

44
#=============================================================================
55
Encoder Struct and Basic Operations
66
=============================================================================#
77

88
mutable struct Encoder
9-
line::GPIO.GPIOLine
9+
pin::GPIO.GPIOPin
1010
count::Int
11-
state::Bool
1211
end
1312

14-
function Encoder(chip::GPIO.GPIOChip, pin::Int)
15-
line = GPIO.get_line(chip, pin)
16-
GPIO.request_input(line, "encoder")
17-
initial_state = GPIO.get_value(line) == 1
18-
return Encoder(line, 0, initial_state)
13+
"""
14+
Encoder(gpio::GPIOController, pin_num::Int; edge::EdgeType=EDGE_FALLING)
15+
16+
Create an encoder that counts edges on the specified pin.
17+
Uses interrupt-based edge detection (no polling).
18+
"""
19+
function Encoder(gpio::GPIO.GPIOController, pin_num::Int; edge::GPIO.EdgeType=GPIO.EDGE_FALLING)
20+
pin = GPIO.request_input_edge(gpio, pin_num, "encoder", edge)
21+
return Encoder(pin, 0)
1922
end
2023

21-
function step!(e::Encoder)
22-
new_state = GPIO.get_value(e.line)
23-
if e.state && new_state == 0
24+
"""
25+
wait_edge!(e::Encoder, timeout_ms::Int=-1) -> Union{GPIO.GPIOEvent, Nothing}
26+
27+
Wait for an edge event. Returns the event or nothing on timeout.
28+
Increments the encoder count on each edge.
29+
"""
30+
function wait_edge!(e::Encoder, timeout_ms::Int=-1)
31+
if GPIO.poll_event(e.pin, timeout_ms)
32+
event = GPIO.read_event(e.pin)
2433
e.count += 1
25-
e.state = false
26-
elseif !e.state && new_state == 1
27-
e.state = true
34+
return event
2835
end
36+
return nothing
37+
end
38+
39+
"""
40+
drain_events(pin::GPIO.GPIOPin) -> Int
41+
42+
Read all pending events from a pin without blocking. Returns number of events drained.
43+
"""
44+
function drain_events(pin::GPIO.GPIOPin)
45+
n = 0
46+
while GPIO.poll_event(pin, 0)
47+
GPIO.read_event(pin)
48+
n += 1
49+
end
50+
return n
51+
end
52+
53+
"""
54+
drain_events!(e::Encoder) -> Int
55+
56+
Read all pending events without blocking. Returns number of events drained.
57+
"""
58+
function drain_events!(e::Encoder)
59+
n = drain_events(e.pin)
60+
e.count += n
61+
return n
2962
end
3063

3164
function reset!(e::Encoder)
3265
e.count = 0
3366
end
3467

3568
function close!(e::Encoder)
36-
GPIO.release_line(e.line)
69+
close(e.pin)
3770
end
3871

3972
#=============================================================================
@@ -45,72 +78,79 @@ end
4578
"""
4679
ThreadedEncoder
4780
48-
A thread-safe wrapper around Encoder that polls at a specified interval.
49-
Send commands via the command channel, receive count via the response channel.
81+
A thread-safe wrapper around Encoder using two dedicated threads:
82+
- Event thread: continuously reads edge events and atomically updates count
83+
- Command thread: handles read/reset commands with immediate response
5084
5185
# Example
5286
```julia
53-
chip = GPIO.open_chip()
87+
chip = GPIO.open_gpio()
5488
enc = Encoder(chip, pin)
55-
tenc = ThreadedEncoder(enc, 1_000_000) # 1ms poll interval
89+
tenc = ThreadedEncoder(enc)
5690
57-
# Read current count
58-
put!(tenc.command, CMD_READ)
59-
count = take!(tenc.response)
91+
# Read current count (immediate response)
92+
count = read(tenc)
6093
6194
# Reset count (returns old count)
62-
put!(tenc.command, CMD_RESET)
63-
old_count = take!(tenc.response)
95+
old_count = reset!(tenc)
6496
6597
stop!(tenc)
6698
```
6799
"""
68100
mutable struct ThreadedEncoder
101+
count::Threads.Atomic{Int}
69102
command::Channel{EncoderCommand}
70103
response::Channel{Int}
71-
worker::Task
104+
event_worker::Task
105+
command_worker::Task
72106
running::Threads.Atomic{Bool}
73107
end
74108

75109
"""
76-
ThreadedEncoder(enc::Encoder, poll_interval_ns::Int)
110+
ThreadedEncoder(enc::Encoder)
77111
78-
Create a ThreadedEncoder that polls the encoder at the specified interval (in nanoseconds).
79-
The worker thread continuously polls the encoder and responds to commands.
112+
Create a ThreadedEncoder with separate event and command threads.
80113
"""
81-
function ThreadedEncoder(enc::Encoder, poll_interval_ns::Int)
114+
function ThreadedEncoder(enc::Encoder)
115+
count = Threads.Atomic{Int}(enc.count)
82116
command = Channel{EncoderCommand}(1)
83117
response = Channel{Int}(1)
84118
running = Threads.Atomic{Bool}(true)
85119

86-
worker = Threads.@spawn begin
87-
next_poll = time_ns()
120+
# Event thread: continuously drain all pending events
121+
event_worker = Threads.@spawn begin
122+
while running[]
123+
# Wait for at least one event (with timeout to check running flag)
124+
if GPIO.poll_event(enc.pin, 100)
125+
n = drain_events(enc.pin)
126+
Threads.atomic_add!(count, n)
127+
end
128+
end
129+
end
130+
131+
# Command thread: handle commands with immediate response
132+
command_worker = Threads.@spawn begin
88133
while running[]
89-
# Poll encoder
90-
now = time_ns()
91-
if now >= next_poll
92-
step!(enc)
93-
next_poll = now + poll_interval_ns
134+
cmd = try
135+
take!(command)
136+
catch e
137+
e isa InvalidStateException && break
138+
rethrow()
94139
end
95140

96-
# Check for commands (non-blocking)
97-
if isready(command)
98-
cmd = take!(command)
99-
if cmd == CMD_READ
100-
put!(response, enc.count)
101-
elseif cmd == CMD_RESET
102-
old_count = enc.count
103-
reset!(enc)
104-
put!(response, old_count)
105-
elseif cmd == CMD_STOP
106-
running[] = false
107-
break
108-
end
141+
if cmd == CMD_READ
142+
put!(response, count[])
143+
elseif cmd == CMD_RESET
144+
old_count = Threads.atomic_xchg!(count, 0)
145+
put!(response, old_count)
146+
elseif cmd == CMD_STOP
147+
running[] = false
148+
break
109149
end
110150
end
111151
end
112152

113-
return ThreadedEncoder(command, response, worker, running)
153+
return ThreadedEncoder(count, command, response, event_worker, command_worker, running)
114154
end
115155

116156
"""
@@ -136,14 +176,15 @@ end
136176
"""
137177
stop!(tenc::ThreadedEncoder)
138178
139-
Stop the worker thread.
179+
Stop both worker threads.
140180
"""
141181
function stop!(tenc::ThreadedEncoder)
142182
if tenc.running[]
143-
put!(tenc.command, CMD_STOP)
144-
wait(tenc.worker)
183+
tenc.running[] = false
184+
close(tenc.command) # Unblocks command worker
185+
wait(tenc.event_worker)
186+
wait(tenc.command_worker)
145187
end
146-
close(tenc.command)
147188
close(tenc.response)
148189
return nothing
149190
end

0 commit comments

Comments
 (0)