1
2
3 '''
4 Class that represents a 4 digit 7-segment display using the SAA1064 chip
5 Source of supply: Ebay or www.elv.de (order # 105697)
6
7 This software is part of the raspibrick module.
8 It is Open Source Free Software, so you may
9 - run the code for any purpose
10 - study how the code works and adapt it to your needs
11 - integrate all or parts of the code in your own programs
12 - redistribute copies of the code777
13 - improve the code and release your improvements to the public
14 However the use of the code is entirely your responsibility.
15 '''
16
17 from smbus import *
18 import RPi.GPIO as GPIO
19 from threading import Thread
20 import time
23
27 '''
28 Abstraction of the 4 digit 7-segment display based on the SAA1064 display driver.
29 If no display is found, all display methods return immediately. The static variable SharedConstants.PATTERN defines a dictionary
30 that maps ASCII characters to display patterns and can be modified by the user program.
31
32 Default:
33
34 The 7 segments have the following binary weights
35 1
36 -
37 32 | |2
38
39 64
40 -
41 16 | |4
42 -
43 8
44
45 The decimal points has weight 128.
46
47 '''
48
49 DEBUG = False
50 VERSION = "1.00 - April 2016"
51
52 '''
53 Character to binary value mapping for 4 digit 7 segment display
54 '''
55 PATTERN = {' ': 0, '!': 134, '"': 34, '#': 0, '$': 0, '%': 0, '&': 0, '\'': 2, '(': 0, ')': 0,
56 '*': 0, '+': 0, ',': 4, '-': 64, '.': 128, '/': 82, '0': 63, '1': 6, '2': 91, '3': 79,
57 '4': 102, '5': 109, '6': 125, '7': 7, '8': 127, '9': 111, ':': 0, ';': 0, '<': 0,
58 '=': 72, '>': 0, '?': 0, '@': 93, 'A': 119, 'B': 124, 'C': 57, 'D': 94, 'E': 121,
59 'F': 113, 'G': 61, 'H': 118, 'I': 48, 'J': 14, 'K': 112, 'L': 56, 'M': 85, 'N': 84,
60 'O': 63, 'P': 115, 'Q': 103, 'R': 80, 'S': 45, 'T': 120, 'U': 62, 'V': 54, 'W': 106,
61 'X': 73, 'Y': 110, 'Z': 27, '[': 57, '\\': 100, ']': 15, '^': 35, '_': 8, '`': 32,
62 'a': 119, 'b': 124, 'c': 88, 'd': 94, 'e': 121, 'f': 113, 'g': 61, 'h': 116, 'i': 16,
63 'j': 12, 'k': 112, 'l': 48, 'm': 85, 'n': 84, 'o': 92, 'p': 115, 'q': 103, 'r': 80, 's': 45,
64 't': 120, 'u': 28, 'v': 54, 'w': 106, 'x': 73, 'y': 110, 'z': 27, '{': 0, '|': 48, '}': 0, '~': 65}
65
66
67 @staticmethod
71
72 @staticmethod
75
76 @staticmethod
78 '''
79 Returns a string with all displayable characters taken from PATTERN dictionary.
80 @return: The character set that can be displayed
81 '''
82 s = "<SPACE>"
83 k = 33
84 while k < 127:
85 ch = chr(k)
86 if Py7Seg.PATTERN[ch] != 0:
87 s = s + ch
88 k += 1
89 return s
90
91 @staticmethod
93 '''
94 Returns a string with hex digits from given number (>0, any size).
95 @param number: the number to convert (must be positive)
96 @return: string of hex digits (uppercase), e.g. 0xFE
97 '''
98 return '%02x' % intValue
99
100 @staticmethod
102 '''
103 Returns a list of four byte values [byte#24-#31, byte#16-#23, byte#8-#15, byte#0-#7] of given integer.
104 @param number: an integer
105 @return: list with integers of 4 bytes [MSB,..., LSB]
106 '''
107 byte0 = intValue & 0xff
108 byte1 = (intValue >> 8) & 0xff
109 byte2 = (intValue >> 16) & 0xff
110 byte3 = (intValue >> 24) & 0xff
111 return [byte3, byte2, byte1, byte0]
112
113 @staticmethod
115 '''
116 Returns an integer from given hex string
117 @param number: a string with the number to convert, e.g. "FE" or "fe" or "0xFE" or "0XFE"
118 @return: integer number
119 '''
120 return int(hexValue, 16)
121
122 @staticmethod
124 time.sleep(timeout / 1000.0)
125
126
127 - def __init__(self, i2c_address = 0x38):
128 '''
129 Creates a display instance with display set to given i2c address (default: 0x38).
130 The display is NOT cleared. It is set to the lowest brightness level 1 (4 mA).
131 @param i2c_address: the i2c address (default: 0x38)
132 '''
133 self.i2c_address = i2c_address
134 self._bus = None
135 self._isReady = True
136 self._tickerThread = None
137 self._blinkerThread = None
138
139 try:
140 if GPIO.RPI_REVISION > 1:
141 self._bus = SMBus(1)
142 Py7Seg.debug("I2C at bus 1 detected")
143 else:
144 self._bus = SMBus(0)
145 Py7Seg.debug("I2C at bus 0 detected")
146 except:
147 Py7Seg.debug("Failed to detect I2C bus.")
148
149 if self._isReady:
150 self._startPos = -1
151 self.setBrightness(1)
152
153
155 '''
156 Sets the brightness of the display.
157 @param luminosity the brightness (0..7, 0: invisible)
158 '''
159 instruction_byte = 0x00
160 if brightness < 0:
161 brightness = 0
162 if brightness > 7:
163 brightness = 7
164 control_byte = 7 + (brightness << 4)
165
166
167
168
169
170
171 self._bus.write_byte_data(self.i2c_address, instruction_byte, control_byte)
172
174 '''
175 Sends 4 data bytes to the display
176 @param data list or tuple of integers whose lower byte are used (higher bytes
177 are ignored).
178 '''
179 if not self._isReady:
180 return
181 if not (type(data) == list or type(data) == tuple) or len(data) != 4:
182 raise Exception("Error in Py7Seg.writeData():\nWrong parameter type.")
183 for v in data:
184 if not (type(v) == int):
185 raise Exception("Error in Py7Seg.writeData():\nWrong parameter type.")
186 try:
187 cmd = 1
188 Py7Seg.debug("bus.write_i2c_block_data(" + str(self.i2c_address) + "," + str(cmd) + "," + str(data) + ")")
189 self._bus.write_i2c_block_data(self.i2c_address, cmd, data)
190 except IOError, err:
191 raise Exception("Py7Seg.writeData(). Can't access device at address 0x%02X" % self.i2c_address)
192
194 '''
195 Clears the display (all digits are turned off).
196 '''
197 Py7Seg.debug("Calling clear()")
198 if not self._isReady:
199 return
200 self._bus.write_i2c_block_data(self.i2c_address, 1, [0, 0, 0, 0])
201
202 - def showText(self, text, pos = 0, dp = [0, 0, 0, 0]):
203 '''
204 Displays 4 characters of the given text. The text is considered to be prefixed and postfixed by spaces
205 and the 4 character window is selected by the text pointer pos that determines the character displayed at the
206 leftmost digit, e.g. (_: empty):
207 showText("AbCdEF") -> AbCd
208 showText("AbCdEF", 1) -> bCdE
209 showText("AbCdEF", -1) ->_AbC
210 showText("AbCdEF", 4) -> EF__
211 @param text: the text to display (list, tuple, string or integer)
212 @param pos: the start value of the text pointer (character index positioned a leftmost digit)
213 @param dp: a list with one to four 1 or 0, if the decimal point is shown or not. For compatibility with
214 the 4tronix display, the following mapping is used:
215 The first element in list corresponds to dp at second digit from the right, the second element to dp
216 at third digit from the right, the third element to dp at leftmost digit, the forth element to the dp at
217 rightmost digit. More than 4 elements are ignored
218 @return: True, if successful; False, if the display is not available,
219 text or dp has illegal type or one of the characters can't be displayed
220 '''
221 Py7Seg.debug("showText(" + str(text) + "," + str(pos) + "," + str(dp) + ")")
222 if not self._isReady:
223 return False
224 if not (type(text) == int or type(text) == list or type(text) == tuple or type(text) == str):
225 return False
226 if not (type(dp) == list or type(dp) == tuple):
227 return False
228 self._startPos = pos
229 self._pos = pos
230 dpList = [0] * 4
231 for i in range(min(4, len(dp))):
232 dpList[i] = dp[i]
233 self._decimalPoint = [0] * 4
234 self._decimalPoint[0] = dpList[2]
235 self._decimalPoint[1] = dpList[1]
236 self._decimalPoint[2] = dpList[0]
237 self._decimalPoint[3] = dpList[3]
238 text = str(text)
239 self._text = [' '] * len(text)
240 for i in range(len(text)):
241 try:
242 self._text[i] = Py7Seg.PATTERN[text[i]]
243 except:
244 self._text[i] = 0
245 data = self._getData(self._pos)
246 self.writeData(data)
247 return True
248
263
278
280 '''
281 Shows the scrollable text at the start position by setting the text pointer to its start value.
282 @return: 0, if successful; -1, if error
283 '''
284 if not self._isReady:
285 return -1
286 if self._startPos == -1:
287 return -1
288 self._pos = self._startPos
289 data = self._getData(self._pos)
290 self.writeData(data)
291 return 0
292
293 - def showTicker(self, text, count = 1, speed = 2, blocking = False):
294 '''
295 Shows a ticker text that scroll to the left until the last 4 characters are displayed. The method blocks
296 until the ticker thread is successfully started and isTickerAlive() returns True.
297 @param text: the text to display, if short than 4 characters, scrolling is disabled
298 @param count: the number of repetitions (default: 1). For count = 0, infinite duration,
299 may be stopped by calling stopTicker().
300 @param speed: the speed number of scrolling operations per sec (default: 2)
301 @param blocking: if True, the method blocks until the ticker has finished; otherwise
302 it returns immediately (default: False)
303 '''
304 if not self._isReady:
305 return
306 self.clear();
307 if self._tickerThread != None:
308 self.stopTicker()
309 if self._blinkerThread != None:
310 self.stopBlinker()
311 self._tickerThread = TickerThread(self, text, count, speed)
312 if blocking:
313 while self.isTickerAlive():
314 continue
315
317 '''
318 Stops a running ticker.
319 The method blocks until the ticker thread is finished and isTickerAlive() returns False.
320 '''
321 if not self._isReady:
322 return
323 if self._tickerThread != None:
324 self._tickerThread.stop()
325 self._tickerThread = None
326
328 '''
329 @return: True, if the ticker is displaying; otherwise False
330 '''
331 if not self._isReady:
332 return False
333 Py7Seg.delay(1)
334 if self._tickerThread == None:
335 return False
336 return self._tickerThread._isAlive
337
338 - def showBlinker(self, text, dp = [0, 0, 0, 0], count = 3, speed = 1, blocking = False):
339 '''
340 Shows a ticker text that scroll to the left until the last 4 characters are displayed. The method blocks
341 until the ticker thread is successfully started and isTickerAlive() returns True.
342 @param text: the text to display, if short than 4 characters, scrolling is disabled
343 @param count: the number of repetitions (default: 2). For count = 0, infinite duration,
344 may be stopped by calling stopBlinker().
345 @param speed: the speed number of blinking operations per sec (default: 1)
346 @param blocking: if True, the method blocks until the blinker has finished; otherwise
347 it returns immediately (default: False)
348 '''
349 if not self._isReady:
350 return
351 self.clear();
352 if self._tickerThread != None:
353 self.stopTicker()
354 if self._blinkerThread != None:
355 self.stopBlinker()
356 self._blinkerThread = BlinkerThread(self, text, dp, count, speed)
357 if blocking:
358 while self.isBlinkerAlive():
359 continue
360
362 '''
363 Stops a running blinker.
364 The method blocks until the blinker thread is finished and isBlinkerAlive() returns False.
365 '''
366 if not self._isReady:
367 return
368 if self._blinkerThread != None:
369 self._blinkerThread.stop()
370 self._blinkerThread = None
371
373 '''
374 @return: True, if the blinker is displaying; otherwise False
375 '''
376 if not self._isReady:
377 return False
378 Py7Seg.delay(1)
379 if self._blinkerThread == None:
380 return False
381 return self._blinkerThread._isAlive
382
384 '''
385 Displays current version. Format X (three horz bars) + n.nn
386 '''
387 v = "X" + Py7Seg.VERSION.replace(".", "")
388 self.showText(v, pos = 0, dp = [0, 1])
389
391 '''
392 Check if device is available.
393 @return: True, if device can be accessed
394 '''
395 return self._isReady
396
397
399 if pos >= 0:
400 data = self._text[pos:pos + 4]
401 for i in range(4 - len(data)):
402 data.append(0)
403 else:
404 if 4 + pos >= 0:
405 data = self._text[0:4 + pos]
406 else:
407 data = []
408 data.reverse()
409 for i in range(4 - len(data)):
410 data.append(0)
411 data.reverse()
412 for i in range(4):
413 data[i] = data[i] + 128 * self._decimalPoint[i]
414 return data
415
418 - def __init__(self, display, text, count, speed):
419 Thread.__init__(self)
420 self._text = text
421 self._display = display
422 if speed <= 0:
423 speed = 1
424 self._period = int(1000.0 / speed)
425 self._count = count
426 self._isRunning = False
427 self._isAlive = True
428 self.start()
429 while not self._isRunning:
430 continue
431
433 Py7Seg.debug("TickerThread started")
434 self._display.showText(self._text)
435 nb = 0
436 self._isRunning = True
437 while self._isRunning:
438 startTime = time.time()
439 while time.time() - startTime < self._period / 1000.0 and self._isRunning:
440 Py7Seg.delay(1)
441 if not self._isRunning:
442 break
443 rc = self._display.scrollToLeft()
444 if rc == 4 and self._isRunning:
445 startTime = time.time()
446 while time.time() - startTime < 2 and self._isRunning:
447 Py7Seg.delay(10)
448 if not self._isRunning:
449 break
450 nb += 1
451 if nb == self._count:
452 break
453 self._display.setToStart()
454 if self._isRunning:
455 while time.time() - startTime < 2 and self._isRunning:
456 Py7Seg.delay(10)
457 self._display.clear()
458 Py7Seg.debug("TickerThread terminated")
459 self._isAlive = False
460
462 self._isRunning = False
463 while self._isAlive:
464 continue
465 Py7Seg.debug("Clearing display")
466 self._display.clear()
467
471 - def __init__(self, display, text, dp, count, speed):
472 Thread.__init__(self)
473 self._text = text
474 self._dp = dp
475 self._display = display
476 if speed <= 0:
477 speed = 1
478 self._period = int(1000.0 / speed)
479 self._count = count
480 self._isRunning = False
481 self._isAlive = True
482 self.start()
483 while not self._isRunning:
484 continue
485
487 Py7Seg.debug("BlinkerThread started")
488 nb = 0
489 self._isRunning = True
490 while self._isRunning:
491 self._display.showText(self._text, dp = self._dp)
492 startTime = time.time()
493 while time.time() - startTime < self._period / 1000.0 and self._isRunning:
494 Py7Seg.delay(1)
495 if self._isRunning == False:
496 break
497 self._display.clear()
498 startTime = time.time()
499 while time.time() - startTime < self._period / 1000.0 and self._isRunning:
500 Py7Seg.delay(1)
501 if self._isRunning == False:
502 break
503 nb += 1
504 if nb == self._count:
505 self._isRunning = False
506
507 startTime = time.time()
508 while time.time() - startTime < 1:
509 Py7Seg.delay(1)
510 Py7Seg.debug("BlinkerThread terminated")
511 self._isAlive = False
512
514 self._isRunning = False
515 while self._isAlive:
516 continue
517