Skip to content

Commit af56d24

Browse files
committed
rotary encoder, volume, seek TESTED
1 parent dd960bc commit af56d24

File tree

3 files changed

+293
-16
lines changed

3 files changed

+293
-16
lines changed

examples/40-fm-tuner-rda5807/_steam_klub_test.py

+44-16
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,25 @@
1414
import time
1515
from machine import Pin, SoftI2C, RTC, PWM
1616
from math import floor
17-
# from dht import DHT11
1817

1918
# External modules
2019
import ssd1306 # OLED
2120
# from bh1750 import BH1750 # Lux meter
2221
from bmp180 import BMP180 # Pressure meter
2322
# from MPU6050 import MPU6050 # Accelerometer + gyroscope
2423
import rda5807 # FM radio module
24+
# Rotary encoder
25+
from rotary_irq import RotaryIRQ
26+
27+
rot = RotaryIRQ(pin_num_clk=32,
28+
pin_num_dt=35,
29+
min_val=0,
30+
max_val=15,
31+
pull_up=False,
32+
half_step=True,
33+
reverse=False,
34+
range_mode=RotaryIRQ.RANGE_BOUNDED)
35+
vol = rot.value()
2536

2637
# DHT11: Data - 4
2738
# Temperature + Humidity
@@ -53,9 +64,9 @@
5364
# Led & buttons
5465
led = Pin(2, Pin.OUT)
5566
led.off()
56-
btn_gr = Pin(19, Pin.IN, Pin.PULL_UP)
57-
btn_rd = Pin(18, Pin.IN, Pin.PULL_UP)
58-
btn_rot = Pin(25, Pin.IN, Pin.PULL_UP)
67+
btn_grn = Pin(19, Pin.IN, Pin.PULL_UP)
68+
btn_red = Pin(18, Pin.IN, Pin.PULL_UP)
69+
btn_rot = Pin(33, Pin.IN, Pin.PULL_UP)
5970

6071
# Accelero + gyroscope
6172
# mpu = MPU6050(i2c)
@@ -64,9 +75,8 @@
6475
radio = rda5807.Radio(i2c)
6576
time.sleep_ms(100) # Let the radio initialize!!! Without the sleep the module does not work!
6677
# init with default settings
67-
radio.set_volume(1) # 0-15
68-
radio.set_frequency_MHz(103.0) # 103: Radio Krokodyl (Brno)
69-
# 88.3: Kiss (Brno)
78+
radio.set_volume(vol) # 0-15
79+
radio.set_frequency_MHz(103.4) # 103.4 - Blanik
7080
radio.mute(False)
7181

7282
# Piezo buzzer 100ms beep
@@ -85,18 +95,37 @@ def floatToStr(f: float):
8595
try:
8696
# Main loop
8797
while True:
88-
if (btn_gr.value() == 0) or (btn_rd.value() == 0) or (btn_rot.value() == 0):
98+
display.fill(0)
99+
vol_new = rot.value()
100+
101+
if vol != vol_new:
102+
vol = vol_new
103+
# print('volume =', vol)
104+
radio.set_volume(vol) # 0-15
105+
display.text('vol: '+str(vol), 0, 16, 1)
106+
107+
if (btn_grn.value() == 0):
108+
led.on()
109+
radio.seek_up()
110+
elif (btn_red.value() == 0):
111+
led.on()
112+
radio.seek_down()
113+
elif (btn_rot.value() == 0):
89114
led.on()
90115
else:
91116
led.off()
92117

93-
display.fill(0)
94-
95118
# Lux + pressure meter + PIR + Capacitive touch
96119
# display.text(floatToStr(bh1750.measurement)+' lx', 0, 0, 1)
97-
# display.text(floatToStr(bmp180.pressure/1000)+' kPa', 0, 8, 1)
98-
# display.text(floatToStr(bmp180.temperature)+' \'C', 0, 16, 1)
120+
121+
pressure = f"{bmp180.pressure/100:.1f} hPa"
122+
display.text(pressure, 60, 16, 1)
123+
124+
temperature = f"{bmp180.temperature:.1f} C"
125+
display.text(temperature, 60, 24, 1)
126+
99127
display.text('PIR: '+str(pir.value()), 0, 24, 1)
128+
100129
# display.text('Touch: '+str(touch.value()), 56, 24, 1)
101130

102131
# Accelerometer
@@ -122,13 +151,12 @@ def floatToStr(f: float):
122151

123152
# FM Radio
124153
radio.update_rds()
125-
# print(radio.get_rds_block_group()) # How to decode RDS?
126154
radio_name = "".join(map(str, radio.station_name))
127-
print(f"Stanice: {radio_name}")
128155
display.text(str(radio_name), 0, 0, 1)
156+
# print(f"Stanice: {radio_name}")
129157

130158
radio_text = "".join(map(str, radio.radio_text))
131-
print(f"Text: {radio_text}")
159+
print(f"RDS text: {radio_text}")
132160

133161
display.text(str(radio.get_frequency_MHz())+' MHz', 0, 8, 1)
134162

@@ -137,7 +165,7 @@ def floatToStr(f: float):
137165
display.text(str(rssi)+' dBm', 85, 8, 1)
138166

139167
display.show()
140-
time.sleep_ms(10)
168+
time.sleep_ms(5)
141169

142170
except KeyboardInterrupt:
143171
# This part runs when Ctrl+C is pressed
+173
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
# MIT License (MIT)
2+
# Copyright (c) 2022 Mike Teachman
3+
# https://opensource.org/licenses/MIT
4+
5+
# Platform-independent MicroPython code for the rotary encoder module
6+
7+
# Documentation:
8+
# https://github.com/MikeTeachman/micropython-rotary
9+
10+
import micropython
11+
12+
_DIR_CW = const(0x10) # Clockwise step
13+
_DIR_CCW = const(0x20) # Counter-clockwise step
14+
15+
# Rotary Encoder States
16+
_R_START = const(0x0)
17+
_R_CW_1 = const(0x1)
18+
_R_CW_2 = const(0x2)
19+
_R_CW_3 = const(0x3)
20+
_R_CCW_1 = const(0x4)
21+
_R_CCW_2 = const(0x5)
22+
_R_CCW_3 = const(0x6)
23+
_R_ILLEGAL = const(0x7)
24+
25+
_transition_table = [
26+
27+
# |------------- NEXT STATE -------------| |CURRENT STATE|
28+
# CLK/DT CLK/DT CLK/DT CLK/DT
29+
# 00 01 10 11
30+
[_R_START, _R_CCW_1, _R_CW_1, _R_START], # _R_START
31+
[_R_CW_2, _R_START, _R_CW_1, _R_START], # _R_CW_1
32+
[_R_CW_2, _R_CW_3, _R_CW_1, _R_START], # _R_CW_2
33+
[_R_CW_2, _R_CW_3, _R_START, _R_START | _DIR_CW], # _R_CW_3
34+
[_R_CCW_2, _R_CCW_1, _R_START, _R_START], # _R_CCW_1
35+
[_R_CCW_2, _R_CCW_1, _R_CCW_3, _R_START], # _R_CCW_2
36+
[_R_CCW_2, _R_START, _R_CCW_3, _R_START | _DIR_CCW], # _R_CCW_3
37+
[_R_START, _R_START, _R_START, _R_START]] # _R_ILLEGAL
38+
39+
_transition_table_half_step = [
40+
[_R_CW_3, _R_CW_2, _R_CW_1, _R_START],
41+
[_R_CW_3 | _DIR_CCW, _R_START, _R_CW_1, _R_START],
42+
[_R_CW_3 | _DIR_CW, _R_CW_2, _R_START, _R_START],
43+
[_R_CW_3, _R_CCW_2, _R_CCW_1, _R_START],
44+
[_R_CW_3, _R_CW_2, _R_CCW_1, _R_START | _DIR_CW],
45+
[_R_CW_3, _R_CCW_2, _R_CW_3, _R_START | _DIR_CCW],
46+
[_R_START, _R_START, _R_START, _R_START],
47+
[_R_START, _R_START, _R_START, _R_START]]
48+
49+
_STATE_MASK = const(0x07)
50+
_DIR_MASK = const(0x30)
51+
52+
53+
def _wrap(value, incr, lower_bound, upper_bound):
54+
range = upper_bound - lower_bound + 1
55+
value = value + incr
56+
57+
if value < lower_bound:
58+
value += range * ((lower_bound - value) // range + 1)
59+
60+
return lower_bound + (value - lower_bound) % range
61+
62+
63+
def _bound(value, incr, lower_bound, upper_bound):
64+
return min(upper_bound, max(lower_bound, value + incr))
65+
66+
67+
def _trigger(rotary_instance):
68+
for listener in rotary_instance._listener:
69+
listener()
70+
71+
72+
class Rotary(object):
73+
74+
RANGE_UNBOUNDED = const(1)
75+
RANGE_WRAP = const(2)
76+
RANGE_BOUNDED = const(3)
77+
78+
def __init__(self, min_val, max_val, incr, reverse, range_mode, half_step, invert):
79+
self._min_val = min_val
80+
self._max_val = max_val
81+
self._incr = incr
82+
self._reverse = -1 if reverse else 1
83+
self._range_mode = range_mode
84+
self._value = min_val
85+
self._state = _R_START
86+
self._half_step = half_step
87+
self._invert = invert
88+
self._listener = []
89+
90+
def set(self, value=None, min_val=None, incr=None,
91+
max_val=None, reverse=None, range_mode=None):
92+
# disable DT and CLK pin interrupts
93+
self._hal_disable_irq()
94+
95+
if value is not None:
96+
self._value = value
97+
if min_val is not None:
98+
self._min_val = min_val
99+
if max_val is not None:
100+
self._max_val = max_val
101+
if incr is not None:
102+
self._incr = incr
103+
if reverse is not None:
104+
self._reverse = -1 if reverse else 1
105+
if range_mode is not None:
106+
self._range_mode = range_mode
107+
self._state = _R_START
108+
109+
# enable DT and CLK pin interrupts
110+
self._hal_enable_irq()
111+
112+
def value(self):
113+
return self._value
114+
115+
def reset(self):
116+
self._value = 0
117+
118+
def close(self):
119+
self._hal_close()
120+
121+
def add_listener(self, l):
122+
self._listener.append(l)
123+
124+
def remove_listener(self, l):
125+
if l not in self._listener:
126+
raise ValueError('{} is not an installed listener'.format(l))
127+
self._listener.remove(l)
128+
129+
def _process_rotary_pins(self, pin):
130+
old_value = self._value
131+
clk_dt_pins = (self._hal_get_clk_value() <<
132+
1) | self._hal_get_dt_value()
133+
134+
if self._invert:
135+
clk_dt_pins = ~clk_dt_pins & 0x03
136+
137+
# Determine next state
138+
if self._half_step:
139+
self._state = _transition_table_half_step[self._state &
140+
_STATE_MASK][clk_dt_pins]
141+
else:
142+
self._state = _transition_table[self._state &
143+
_STATE_MASK][clk_dt_pins]
144+
direction = self._state & _DIR_MASK
145+
146+
incr = 0
147+
if direction == _DIR_CW:
148+
incr = self._incr
149+
elif direction == _DIR_CCW:
150+
incr = -self._incr
151+
152+
incr *= self._reverse
153+
154+
if self._range_mode == self.RANGE_WRAP:
155+
self._value = _wrap(
156+
self._value,
157+
incr,
158+
self._min_val,
159+
self._max_val)
160+
elif self._range_mode == self.RANGE_BOUNDED:
161+
self._value = _bound(
162+
self._value,
163+
incr,
164+
self._min_val,
165+
self._max_val)
166+
else:
167+
self._value = self._value + incr
168+
169+
try:
170+
if old_value != self._value and len(self._listener) != 0:
171+
_trigger(self)
172+
except:
173+
pass
+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# MIT License (MIT)
2+
# Copyright (c) 2020 Mike Teachman
3+
# https://opensource.org/licenses/MIT
4+
5+
# Platform-specific MicroPython code for the rotary encoder module
6+
# ESP8266/ESP32 implementation
7+
8+
# Documentation:
9+
# https://github.com/MikeTeachman/micropython-rotary
10+
11+
from machine import Pin
12+
from rotary import Rotary
13+
from sys import platform
14+
15+
_esp8266_deny_pins = [16]
16+
17+
18+
class RotaryIRQ(Rotary):
19+
20+
def __init__(self, pin_num_clk, pin_num_dt, min_val=0, max_val=10, incr=1,
21+
reverse=False, range_mode=Rotary.RANGE_UNBOUNDED, pull_up=False, half_step=False, invert=False):
22+
23+
if platform == 'esp8266':
24+
if pin_num_clk in _esp8266_deny_pins:
25+
raise ValueError(
26+
'%s: Pin %d not allowed. Not Available for Interrupt: %s' %
27+
(platform, pin_num_clk, _esp8266_deny_pins))
28+
if pin_num_dt in _esp8266_deny_pins:
29+
raise ValueError(
30+
'%s: Pin %d not allowed. Not Available for Interrupt: %s' %
31+
(platform, pin_num_dt, _esp8266_deny_pins))
32+
33+
super().__init__(min_val, max_val, incr, reverse, range_mode, half_step, invert)
34+
35+
if pull_up == True:
36+
self._pin_clk = Pin(pin_num_clk, Pin.IN, Pin.PULL_UP)
37+
self._pin_dt = Pin(pin_num_dt, Pin.IN, Pin.PULL_UP)
38+
else:
39+
self._pin_clk = Pin(pin_num_clk, Pin.IN)
40+
self._pin_dt = Pin(pin_num_dt, Pin.IN)
41+
42+
self._enable_clk_irq(self._process_rotary_pins)
43+
self._enable_dt_irq(self._process_rotary_pins)
44+
45+
def _enable_clk_irq(self, callback=None):
46+
self._pin_clk.irq(
47+
trigger=Pin.IRQ_RISING | Pin.IRQ_FALLING,
48+
handler=callback)
49+
50+
def _enable_dt_irq(self, callback=None):
51+
self._pin_dt.irq(
52+
trigger=Pin.IRQ_RISING | Pin.IRQ_FALLING,
53+
handler=callback)
54+
55+
def _disable_clk_irq(self):
56+
self._pin_clk.irq(handler=None)
57+
58+
def _disable_dt_irq(self):
59+
self._pin_dt.irq(handler=None)
60+
61+
def _hal_get_clk_value(self):
62+
return self._pin_clk.value()
63+
64+
def _hal_get_dt_value(self):
65+
return self._pin_dt.value()
66+
67+
def _hal_enable_irq(self):
68+
self._enable_clk_irq(self._process_rotary_pins)
69+
self._enable_dt_irq(self._process_rotary_pins)
70+
71+
def _hal_disable_irq(self):
72+
self._disable_clk_irq()
73+
self._disable_dt_irq()
74+
75+
def _hal_close(self):
76+
self._hal_disable_irq()

0 commit comments

Comments
 (0)