Module py7seg
[hide private]
[frames] | no frames]

Source Code for Module py7seg

  1  # py7segeg.py 
  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 
21 22 -class SMBusException(Exception): pass
23
24 25 # ------------------- class Py7Seg ---------------------- 26 -class Py7Seg():
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 # ----------------------- static methods --------------------- 67 @staticmethod
68 - def debug(msg):
69 if Py7Seg.DEBUG: 70 print "Py7Seg debug->", msg
71 72 @staticmethod
73 - def getVersion():
74 return Py7Seg.VERSION
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
92 - def toHex(intValue):
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
101 - def toBytes(intValue):
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
114 - def toInt(hexValue):
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
123 - def delay(timeout):
124 time.sleep(timeout / 1000.0)
125 126 # ----------------------- Constructor ------------------------
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) # For revision 2 Raspberry Pi 142 Py7Seg.debug("I2C at bus 1 detected") 143 else: 144 self._bus = SMBus(0) # For revision 1 Raspberry Pi 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 # no text yet 151 self.setBrightness(1)
152 153 # ----------------------- Methods ----------------------
154 - def setBrightness(self, brightness):
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 # control_byte = 0b00010111 167 # b0 = 1: dynamic mode (autoincrement digits) 168 # b1 = 1: digit 1/3 not blanked, b2 = 1: digit 2/4 not blanked 169 # b4 = 1: 3 mA segment current 170 # write to control register 171 self._bus.write_byte_data(self.i2c_address, instruction_byte, control_byte)
172
173 - def writeData(self, data):
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 # start with digit 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
193 - def clear(self):
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) # convert digits to chars 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 # empty 245 data = self._getData(self._pos) 246 self.writeData(data) 247 return True
248
249 - def scrollToLeft(self):
250 ''' 251 Scrolls the current text one step to the left by increasing the text pointer. 252 @return: the number of characters hidden, but remaining to be displayed at the right (>=0); -1, if error 253 ''' 254 if not self._isReady: 255 return -1 256 if self._startPos == -1: # no text yet 257 return -1 258 self._pos += 1 259 data = self._getData(self._pos) 260 self.writeData(data) 261 nb = len(self._text) - self._pos 262 return max(0, nb)
263
264 - def scrollToRight(self):
265 ''' 266 Scrolls the current text one step to the left by decreasing the text pointer. 267 @return: the number of characters hidden, but remaining to be displayed at the left (>=0); -1, if error 268 ''' 269 if not self._isReady: 270 return -1 271 if self._startPos == -1: # no text yet 272 return -1 273 self._pos -= 1 274 pos = self._pos 275 data = self._getData(self._pos) 276 self.writeData(data) 277 return max(0, self._pos)
278
279 - def setToStart(self):
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: # no text yet 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
316 - def stopTicker(self):
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
327 - def isTickerAlive(self):
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
361 - def stopBlinker(self):
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
372 - def isBlinkerAlive(self):
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
383 - def showVersion(self):
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
390 - def isAvailable(self):
391 ''' 392 Check if device is available. 393 @return: True, if device can be accessed 394 ''' 395 return self._isReady
396 397 # -------------------- private methods ----------------------------
398 - def _getData(self, pos):
399 if pos >= 0: 400 data = self._text[pos:pos + 4] 401 for i in range(4 - len(data)): 402 data.append(0) # spaces 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) # spaces 411 data.reverse() 412 for i in range(4): 413 data[i] = data[i] + 128 * self._decimalPoint[i] # add decimal points 414 return data
415
416 # ------------------- class TickerThread ---------------------- 417 -class TickerThread(Thread):
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
432 - def run(self):
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: # terminated by number of count 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
461 - def stop(self):
462 self._isRunning = False 463 while self._isAlive: # Wait until thread is finished 464 continue 465 Py7Seg.debug("Clearing display") 466 self._display.clear()
467
468 469 # ------------------- class BlinkerThread ---------------------- 470 -class BlinkerThread(Thread):
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
486 - def run(self):
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
513 - def stop(self):
514 self._isRunning = False 515 while self._isAlive: # Wait until thread is finished 516 continue
517