version: v0.6
[nit.git] / contrib / physical_interface_for_mpd_on_rpi.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2013 Alexis Laferrière <alexis.laf@xymus.net>
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 # This programs interprets the input of a physical interface thought the
18 # GPIO pins of a Raspberry Pi to control an MPD server.
19 #
20 # It suppot two inputs: a play/pause button and a rotary encoder to adjust
21 # the volume.
22 #
23 # The each data output of the volume control are connected to the board
24 # pins #3 and #5.
25 module physical_interface_for_mpd_on_rpi
26
27 import bcm2835
28 import mpd
29
30 redef class Object
31 fun mpd: MPDConnection do return once new MPDConnection("localhost", 6600, "password")
32 end
33
34 class RotaryEncoder
35 var pin_a: RPiPin
36 var pin_b: RPiPin
37 var old_a= false
38 var old_b= false
39
40 fun update: nullable Char
41 do
42 var new_a = pin_a.lev
43 var new_b = pin_b.lev
44 var res = null
45
46 if new_a != old_a or new_b != old_b then
47 if not old_a and not old_b then
48 # everything was on
49 if not new_a and new_b then
50 res = '<'
51 else if new_a and not new_b then
52 res = '>'
53 end
54 else if old_a and old_b then
55 # everything was off
56 if not new_a and new_b then
57 res = '>'
58 else if new_a and not new_b then
59 res = '<'
60 end
61 end
62
63 old_a = new_a
64 old_b = new_b
65 end
66
67 return res
68 end
69 end
70
71 class LCD
72 var rs: RPiPin
73 var en: RPiPin
74 var d4: RPiPin
75 var d5: RPiPin
76 var d6: RPiPin
77 var d7: RPiPin
78
79 var ds = new Array[RPiPin]# = [d4,d5,d6,d7]
80
81 # commands
82 fun flag_clear_display: Int do return 1
83 fun flag_return_home: Int do return 2
84 fun flag_entry_mode_set: Int do return 4
85 fun flag_display_control: Int do return 8
86 fun flag_cursor_shift: Int do return 16
87 fun flag_function_set: Int do return 32
88 fun flag_set_cgram_addr: Int do return 64
89 fun flag_set_ggram_addr: Int do return 128
90
91 # entry mode
92 fun flag_entry_right: Int do return 0
93 fun flag_entry_left: Int do return 2
94 fun flag_entry_shift_increment: Int do return 1
95 fun flag_entry_shift_decrement: Int do return 0
96
97 # display flags
98 fun flag_display_on: Int do return 4
99 fun flag_display_off: Int do return 0
100 fun flag_cursor_on: Int do return 2
101 fun flag_cursor_off: Int do return 0
102 fun flag_blink_on: Int do return 1
103 fun flag_blink_off: Int do return 0
104
105 # display/cursor shift
106 fun flag_display_move: Int do return 8
107 fun flag_cursor_move: Int do return 0
108 fun flag_move_right: Int do return 4
109 fun flag_move_left: Int do return 0
110
111 # function set
112 fun flag_8bit_mode: Int do return 16
113 fun flag_4bit_mode: Int do return 0
114 fun flag_2_lines: Int do return 8
115 fun flag_1_line: Int do return 0
116 fun flag_5x10_dots: Int do return 4
117 fun flag_5x8_dots: Int do return 0
118
119 fun function_set(bits, lines, dots_wide: Int)
120 do
121 var fs = flag_function_set
122 if bits == 8 then
123 fs = fs.bin_or(16)
124 else if bits != 4 then abort
125
126 if lines == 2 then
127 fs = fs.bin_or(8)
128 else if lines != 1 then abort
129
130 if dots_wide == 10 then
131 fs = fs.bin_or(4)
132 else if dots_wide != 8 then abort
133
134 write(true, fs)
135 end
136
137 fun display_control(on, cursor, blink: Bool)
138 do
139 var fs = flag_display_control
140
141 if on then
142 fs = fs.bin_or(flag_display_on)
143 else fs = fs.bin_or(flag_display_off)
144
145 if cursor then
146 fs = fs.bin_or(flag_cursor_on)
147 else fs = fs.bin_or(flag_cursor_off)
148
149 if blink then
150 fs = fs.bin_or(flag_blink_on)
151 else fs = fs.bin_or(flag_blink_off)
152
153 write(true, fs)
154 end
155
156 fun entry_mode(left, incr: Bool)
157 do
158 var fs = flag_entry_mode_set
159
160 if left then
161 fs = fs.bin_or(flag_entry_left)
162 else fs = fs.bin_or(flag_entry_right)
163
164 if incr then
165 fs = fs.bin_or(flag_entry_shift_increment)
166 else fs = fs.bin_or(flag_entry_shift_decrement)
167
168 write(true, fs)
169 end
170
171 fun setup
172 do
173 ds = [d4,d5,d6,d7]
174
175 rs.fsel = new FunctionSelect.outp
176 en.fsel = new FunctionSelect.outp
177 d4.fsel = new FunctionSelect.outp
178 d5.fsel = new FunctionSelect.outp
179 d6.fsel = new FunctionSelect.outp
180 d7.fsel = new FunctionSelect.outp
181
182 rs.write(false)
183 en.write(false)
184
185 # wait 20ms for power up
186 #50.bcm2835_delay
187
188 #write_4bits(true,true,false,false)
189 #write_4_bits(3)
190
191 #5.bcm2835_delay
192
193 #write_4bits(true,true,false,false)
194 #write_4_bits(3)
195
196 #5.bcm2835_delay
197
198 #write_4bits(true,true,false,false)
199 #write_4_bits(3)
200
201 #200.bcm2835_delay_micros
202
203 #write_4bits(false,true,false,false)
204 #write_4_bits(2)
205
206 # wait 5ms
207 #5.bcm2835_delay
208
209 # set interface
210 # 4bits, 2 lines
211 #write(true, flow)
212 #function_set(4, 2, 8)
213
214 # cursor
215 # don't shift & hide
216 #display_control(true, true, true)
217
218 # clear & home
219 #write(true, flag_)
220 #clear
221
222 # set cursor move direction
223 # move right
224 #write(true, 6)
225
226 # turn on display
227 #write(true, 4)
228
229 # set entry mode
230 #entry_mode(true, true)
231
232 write(true, "33".to_hex)
233 write(true, "32".to_hex)
234 write(true, "28".to_hex)
235 write(true, "0C".to_hex)
236 write(true, "01".to_hex)
237 end
238
239 fun write_4_bits(v: Int)
240 do
241 var lb = once [1,2,4,8]
242 for i in [0..4[ do
243 var b = lb[i]
244 var r = b.bin_and(v) != 0
245 var d = ds[i]
246 d.write(r)
247 end
248 pulse_enable
249 end
250
251 fun write_4bits(a,b,c,d:Bool)
252 do
253 d4.write(a)
254 d5.write(b)
255 d6.write(c)
256 d7.write(d)
257 pulse_enable
258 end
259
260 fun pulse_enable
261 do
262 en.write(false)
263 1.bcm2835_delay_micros
264 en.write(true)
265 100.bcm2835_delay_micros
266 en.write(false)
267 end
268
269 fun write(is_cmd: Bool, cmd: Int)
270 do
271 en.write(false)
272 rs.write(not is_cmd)
273
274 # high byte
275 var hb = once [16,32,64,128]
276 for i in [0..4[ do
277 var b = hb[i]
278 var r = b.bin_and(cmd) != 0
279 var d = ds[i]
280 d.write(r)
281 end
282
283 en.write(true)
284
285 # wait 450 ns
286 1.bcm2835_delay_micros
287
288 en.write(false)
289
290 if is_cmd then
291 # wait 5ms
292 5.bcm2835_delay
293 else
294 # wait 200us
295 200.bcm2835_delay_micros
296 end
297
298 # low byte
299 var lb = once [1,2,4,8]
300 for i in [0..4[ do
301 var b = lb[i]
302 var r = b.bin_and(cmd) != 0
303 var d = ds[i]
304 d.write(r)
305 end
306
307 en.write(true)
308
309 # wait 450ns
310 1.bcm2835_delay_micros
311
312 en.write(false)
313
314 if is_cmd then
315 # wait 5ms
316 5.bcm2835_delay
317 else
318 # wait 200us
319 200.bcm2835_delay_micros
320 end
321 end
322
323 fun clear
324 do
325 write(true,1)
326 2.bcm2835_delay
327 end
328
329 fun return_home
330 do
331 write(true,2)
332 2.bcm2835_delay
333 end
334
335 fun text=(v: String)
336 do
337 clear
338 return_home
339 for c in v do write(false, c.ascii)
340 end
341 end
342
343 fun hit_play_stop
344 do
345 # get current status
346 var status = mpd.status
347 var playing = false
348 if status != null then
349 playing = status.state == "play"
350 end
351
352 if playing then
353 # stop
354 print "playing -> stop"
355 mpd.pause
356 else
357 print "stopped -> play"
358 mpd.play
359 end
360 end
361
362 assert bcm2835_init else print "Failed to init"
363
364 # Debug LED
365 var out = new RPiPin.p1_11
366 out.fsel = new FunctionSelect.outp
367 out.write(false)
368
369 # Play button
370 var inp = new RPiPin.p1_13
371 inp.fsel = new FunctionSelect.inpt
372 inp.pud = new PUDControl.down
373
374 # Vol +
375 var vol3 = new RPiPin.p1_03
376 vol3.fsel = new FunctionSelect.inpt
377 vol3.pud = new PUDControl.up
378
379 # Vol -
380 var vol5 = new RPiPin.p1_05
381 vol5.fsel = new FunctionSelect.inpt
382 vol5.pud = new PUDControl.up
383
384 var vol = new RotaryEncoder(vol3,vol5)
385 var vol_step = 2
386
387 # LCD
388 var lcd_rs = new RPiPin.p1_23
389 var lcd_en = new RPiPin.p1_21
390 var lcd_d4 = new RPiPin.p1_19
391 var lcd_d5 = new RPiPin.p1_26
392 var lcd_d6 = new RPiPin.p1_24
393 var lcd_d7 = new RPiPin.p1_22
394 var lcd = new LCD(lcd_rs, lcd_en, lcd_d4, lcd_d5, lcd_d6, lcd_d7)
395 lcd.setup
396 #lcd.write(false, 'a'.to_i.to_ascii)
397 lcd.clear
398 lcd.write(false, 'a'.ascii)
399 lcd.write(false, 'C'.ascii)
400
401 var last_in = false
402 var led_on = false
403 var tick = 0
404 loop
405 # play button
406 var lev = inp.lev
407 if lev != last_in then
408 last_in = lev
409 if lev then
410 print "hps"
411 hit_play_stop
412 end
413 end
414
415 # volume
416 var s = vol.update
417 if s != null then
418 if s == '<' then
419 print "vol down"
420 mpd.relative_volume = -vol_step
421 else # >
422 print "vol up"
423 mpd.relative_volume = vol_step
424 end
425 end
426
427 if tick % 100 == 0 then
428 print tick
429 #var now_playing = mpd.status("")
430 #lcd.text = tick.to_s
431 end
432
433 10.bcm2835_delay
434 tick += 1
435 end