1- include ( joinpath ( @__DIR__ , " gpio.jl " ) )
1+ # Uses GPIO module (must be included before this file )
22using . GPIO
33
44#= ============================================================================
55 Encoder Struct and Basic Operations
66=============================================================================#
77
88mutable struct Encoder
9- line :: GPIO.GPIOLine
9+ pin :: GPIO.GPIOPin
1010 count:: Int
11- state:: Bool
1211end
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 )
1922end
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
2962end
3063
3164function reset! (e:: Encoder )
3265 e. count = 0
3366end
3467
3568function close! (e:: Encoder )
36- GPIO . release_line (e. line )
69+ close (e. pin )
3770end
3871
3972#= ============================================================================
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 ()
5488enc = 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
6597stop!(tenc)
6698```
6799"""
68100mutable 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}
73107end
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)
114154end
115155
116156"""
@@ -136,14 +176,15 @@ end
136176"""
137177 stop!(tenc::ThreadedEncoder)
138178
139- Stop the worker thread .
179+ Stop both worker threads .
140180"""
141181function 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
149190end
0 commit comments