Overview
In this project, we will build an Electronic Hourglass using Raspberry Pi Pico, Rotary Encoder & TM1637 Display. The Rotary encoder is used to set the time for the hourglass. The Raspberry Pi is programmed via MicroPython Code to simulate the function of an electronic hourglass.
An hourglass (or sandglass, sand timer, sand clock, or egg timer) is a device used to measure the passage of time. It comprises two glass bulbs connected vertically by a narrow neck that allows a regulated flow of a substance from the upper bulb to the lower one.
Components Required
In this guide, I used Elecrow Raspberry Pi Pico Starter Kit to test different Modules. You can buy the kit and perform some other operations as well. From this kit, you can use the following components.
1. Raspberry Pi Pico Board – 1
2. Rotary Encoder – 1
3. TM1637 4-Digit 7-Segment LED Display – 1
4. Breadboard – 1
5. Jumper Wires – 4
6. Micro-USB Cable – 1
Rotary Encoder
A rotary encoder, also called a shaft encoder, is an electro-mechanical device that converts the angular position or motion of a shaft or axle to analog or digital output signals.
There are two main types of rotary encoders: absolute and incremental. The output of an absolute encoder indicates the current shaft position, making it an angle transducer. The output of an incremental encoder provides information about the motion of the shaft, which typically is processed elsewhere into information such as position, speed, and distance.
How Rotary Encoder Works?
The encoder has a disk with evenly spaced contact zones that are connected to the common pin C and two other separate contact pins A and B, as illustrated below.
When the disk will start rotating step by step, pins A and B will start making contact with the common pin and the two square wave output signals will be generated accordingly.
Any of the two outputs can be used for determining the rotated position if we just count the pulses of the signal. However, if we want to determine the rotation direction as well, we need to consider both signals at the same time.
We can notice that the two output signals are displaced at 90 degrees out of phase from each other. If the encoder is rotating clockwise output A will be ahead of output B.
So if we count the steps each time the signal changes, from High to Low or from Low to High, we can notice at that time the two output signals have opposite values. Vice versa, if the encoder is rotating counter-clockwise, the output signals have equal values. So considering this, we can easily program our controller to read the encoder position and the rotation direction.
Electronic Hourglass with Raspberry Pi Pico & Rotary Encoder
Now let us interface Rotary Encoder & TM1637 LED Display with Raspberry Pi Pico to make an Electronic Hourglass. The connection diagram is fairly simple.
Connect the VCC, GND, CLK & DIO Pin of the TM1637 to the 3.3V, GND, GP4 & GP5 pin of the Raspberry Pi Pico respectively.
Similarly connect the VCC, GND, CLK, DT & SW pin of Rotary Encoder to VCC, GND, GP0, GP1 & GP2 pin of Raspberry Pi Pico repectively.
MicroPython Code/Program
The code is divided into two parts. One is tm1637o.py & the other is main.py. The tm1637.py is the library required by TM1637 LED Display.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
""" MicroPython TM1637 quad 7-segment LED display driver https://github.com/mcauser/micropython-tm1637 MIT License Copyright (c) 2016 Mike Causer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ from micropython import const from machine import Pin from time import sleep_us, sleep_ms TM1637_CMD1 = const(64) # 0x40 data command TM1637_CMD2 = const(192) # 0xC0 address command TM1637_CMD3 = const(128) # 0x80 display control command TM1637_DSP_ON = const(8) # 0x08 display on TM1637_DELAY = const(10) # 10us delay between clk/dio pulses TM1637_MSB = const(128) # msb is the decimal point or the colon depending on your display # 0-9, a-z, blank, dash, star _SEGMENTS = bytearray(b'\x3F\x06\x5B\x4F\x66\x6D\x7D\x07\x7F\x6F\x77\x7C\x39\x5E\x79\x71\x3D\x76\x06\x1E\x76\x38\x55\x54\x3F\x73\x67\x50\x6D\x78\x3E\x1C\x2A\x76\x6E\x5B\x00\x40\x63') class TM1637(object): """Library for quad 7-segment LED modules based on the TM1637 LED driver.""" def __init__(self, clk, dio, brightness=7): self.clk = clk self.dio = dio if not 0 <= brightness <= 7: raise ValueError("Brightness out of range") self._brightness = brightness self.clk.init(Pin.OUT, value=0) self.dio.init(Pin.OUT, value=0) sleep_us(TM1637_DELAY) self._write_data_cmd() self._write_dsp_ctrl() def _start(self): self.dio(0) sleep_us(TM1637_DELAY) self.clk(0) sleep_us(TM1637_DELAY) def _stop(self): self.dio(0) sleep_us(TM1637_DELAY) self.clk(1) sleep_us(TM1637_DELAY) self.dio(1) def _write_data_cmd(self): # automatic address increment, normal mode self._start() self._write_byte(TM1637_CMD1) self._stop() def _write_dsp_ctrl(self): # display on, set brightness self._start() self._write_byte(TM1637_CMD3 | TM1637_DSP_ON | self._brightness) self._stop() def _write_byte(self, b): for i in range(8): self.dio((b >> i) & 1) sleep_us(TM1637_DELAY) self.clk(1) sleep_us(TM1637_DELAY) self.clk(0) sleep_us(TM1637_DELAY) self.clk(0) sleep_us(TM1637_DELAY) self.clk(1) sleep_us(TM1637_DELAY) self.clk(0) sleep_us(TM1637_DELAY) def brightness(self, val=None): """Set the display brightness 0-7.""" # brightness 0 = 1/16th pulse width # brightness 7 = 14/16th pulse width if val is None: return self._brightness if not 0 <= val <= 7: raise ValueError("Brightness out of range") self._brightness = val self._write_data_cmd() self._write_dsp_ctrl() def write(self, segments, pos=0): """Display up to 6 segments moving right from a given position. The MSB in the 2nd segment controls the colon between the 2nd and 3rd segments.""" if not 0 <= pos <= 5: raise ValueError("Position out of range") self._write_data_cmd() self._start() self._write_byte(TM1637_CMD2 | pos) for seg in segments: self._write_byte(seg) self._stop() self._write_dsp_ctrl() def encode_digit(self, digit): """Convert a character 0-9, a-f to a segment.""" return _SEGMENTS[digit & 0x0f] def encode_string(self, string): """Convert an up to 4 character length string containing 0-9, a-z, space, dash, star to an array of segments, matching the length of the source string.""" segments = bytearray(len(string)) for i in range(len(string)): segments[i] = self.encode_char(string[i]) return segments def encode_char(self, char): """Convert a character 0-9, a-z, space, dash or star to a segment.""" o = ord(char) if o == 32: return _SEGMENTS[36] # space if o == 42: return _SEGMENTS[38] # star/degrees if o == 45: return _SEGMENTS[37] # dash if o >= 65 and o <= 90: return _SEGMENTS[o-55] # uppercase A-Z if o >= 97 and o <= 122: return _SEGMENTS[o-87] # lowercase a-z if o >= 48 and o <= 57: return _SEGMENTS[o-48] # 0-9 raise ValueError("Character out of range: {:d} '{:s}'".format(o, chr(o))) def hex(self, val): """Display a hex value 0x0000 through 0xffff, right aligned.""" string = '{:04x}'.format(val & 0xffff) self.write(self.encode_string(string)) def number(self, num): """Display a numeric value -999 through 9999, right aligned.""" # limit to range -999 to 9999 num = max(-999, min(num, 9999)) string = '{0: >4d}'.format(num) self.write(self.encode_string(string)) def numbers(self, num1, num2, colon=True): """Display two numeric values -9 through 99, with leading zeros and separated by a colon.""" num1 = max(-9, min(num1, 99)) num2 = max(-9, min(num2, 99)) segments = self.encode_string('{0:0>2d}{1:0>2d}'.format(num1, num2)) if colon: segments[1] |= 0x80 # colon on self.write(segments) def temperature(self, num): if num < -9: self.show('lo') # low elif num > 99: self.show('hi') # high else: string = '{0: >2d}'.format(num) self.write(self.encode_string(string)) self.write([_SEGMENTS[38], _SEGMENTS[12]], 2) # degrees C def show(self, string, colon=False): segments = self.encode_string(string) if len(segments) > 1 and colon: segments[1] |= 128 self.write(segments[:4]) def scroll(self, string, delay=250): segments = string if isinstance(string, list) else self.encode_string(string) data = [0] * 8 data[4:0] = list(segments) for i in range(len(segments) + 5): self.write(data[0+i:4+i]) sleep_ms(delay) class TM1637Decimal(TM1637): """Library for quad 7-segment LED modules based on the TM1637 LED driver. This class is meant to be used with decimal display modules (modules that have a decimal point after each 7-segment LED). """ def encode_string(self, string): """Convert a string to LED segments. Convert an up to 4 character length string containing 0-9, a-z, space, dash, star and '.' to an array of segments, matching the length of the source string.""" segments = bytearray(len(string.replace('.',''))) j = 0 for i in range(len(string)): if string[i] == '.' and j > 0: segments[j-1] |= TM1637_MSB continue segments[j] = self.encode_char(string[i]) j += 1 return segments |
main.py
Copy the following code and save it as main.py in the Raspberry Pi Pico.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
from machine import Pin from time import sleep import tm1637 from utime import sleep tm = tm1637.TM1637(clk=Pin(4), dio=Pin(5)) RoA_Pin = 0 # CLK RoB_Pin = 1 # DT Btn_Pin = 2 # SW globalCounter = 0 # counter value flag = 0 # Whether the rotation flag occurs Last_RoB_Status = 0 # DT state Current_RoB_Status = 0 # CLK state def setup(): global clk_RoA global dt_RoB global sw_BtN clk_RoA = Pin(RoA_Pin,Pin.IN) dt_RoB = Pin(RoB_Pin,Pin.IN) sw_BtN = Pin(Btn_Pin,Pin.IN, Pin.PULL_UP) # # Initialize the interrupt function, when the SW pin is 0, the interrupt is enabled sw_BtN.irq(trigger=Pin.IRQ_FALLING,handler=btnISR) # Rotation code direction bit judgment function def rotaryDeal(): global flag global Last_RoB_Status global Current_RoB_Status global globalCounter Last_RoB_Status = dt_RoB.value() # Judging the level change of the CLK pin to distinguish the direction while(not clk_RoA.value()): Current_RoB_Status = dt_RoB.value() flag = 1 # Rotation mark occurs if flag == 1: # The flag bit is 1 and a rotation has occurred flag = 0 # Reset flag bit if (Last_RoB_Status == 0) and (Current_RoB_Status == 1): globalCounter = globalCounter + 1 # counterclockwise, positive if (Last_RoB_Status == 1) and (Current_RoB_Status == 0): globalCounter = globalCounter - 1 # Clockwise, negative # Interrupt function, when the SW pin is 0, the interrupt is enabled def btnISR(chn): global globalCounter globalCounter = 0 print ('globalCounter = %d' % globalCounter) while True: # Define a counter that changes every 1 second tm.number(globalCounter) globalCounter = globalCounter - 1 sleep(1) if globalCounter == 0: break def loop(): global globalCounter tmp = 0 while True: rotaryDeal() if tmp != globalCounter: print ('globalCounter = %d' % globalCounter) tmp = globalCounter tm.number(globalCounter) if __name__ == '__main__': setup() loop() |
Now click on the Run button to run the library and main file.
Turn the encoder to set the time, and it will be displayed on the TM1637 4-Bits digital tube in real-time. Press the button and the electronic hourglass starts working.













