Fast clocks, model railroads, LED displays, and more
Code Walkthrough
Imports (lines 1-4) of Listing 1 are how Python brings in external libraries. Here are the ones I use:
pygame
handles the graphics and key inputs on the Pi Zero.datetime
handles all of the functions to store and manipulate the time of the fast clock.RPi.GPIO
lets me access the physical GPIO pins on the Pi. Theas gpio
lets me access it later in Python with an easier name than typingRPi.GPIO
every time.time
has functions that measure and control the passing of real time.
Listing 1
Fast Clock Script
001 import pygame 002 import datetime 003 import RPi.GPIO as gpio 004 import time 005 006 class sevenSegment: 007 def __init__ ( self ): 008 #64 -- middle 009 #32 -- lower right 010 #16 -- upper right 011 #8 -- top 012 #4 -- upper left 013 #2 -- lower left 014 #1 -- bottom 015 016 self.digits = list() 017 self.digits.append ( 0b00111111 ) #Zero 018 self.digits.append ( 0b00110000 ) #One 019 self.digits.append ( 0b01011011 ) #Two 020 self.digits.append ( 0b01111001 ) #Three 021 self.digits.append ( 0b01110100 ) #Four 022 self.digits.append ( 0b01101101 ) #Five 023 self.digits.append ( 0b01101111 ) #Six 024 self.digits.append ( 0b00111000 ) #Seven 025 self.digits.append ( 0b01111111 ) #Eight 026 self.digits.append ( 0b01111101 ) #Nine 027 028 def ledVal ( self , number ): 029 LED = 0 030 numString = str ( number ) 031 for i in range ( 4 ): 032 digit = self.digits [ int ( numString [ i ] ) ] 033 digit <<= 7 * i 034 LED += digit 035 return LED 036 037 class fiftyfourfifty: 038 def __init__ ( self , clock , data ): 039 self.clock = clock 040 self.data = data 041 042 gpio.setmode(gpio.BCM) 043 gpio.setup ( clock , gpio.OUT ) 044 gpio.setup ( data , gpio.OUT ) 045 046 def pulseClock ( self ): 047 gpio.output ( self.clock , False ) 048 time.sleep ( .01 ) 049 gpio.output ( self.clock , True ) 050 time.sleep ( .01 ) 051 gpio.output ( self.clock , False ) 052 053 def startPulse ( self ): 054 gpio.output ( self.data , True ) 055 self.pulseClock() 056 057 def set ( self , LED ): 058 self.startPulse() 059 for i in range ( 34 ): 060 if ( LED & 1 ) == 1: 061 gpio.output ( self.data , True ) 062 else: 063 gpio.output ( self.data , False ) 064 LED >>= 1 065 066 self.pulseClock() 067 self.pulseClock() 068 069 class clock: 070 def __init__ ( self , screen , chip , display ): 071 self.screen = screen 072 self.chip = chip 073 self.display = display 074 self.dt = datetime.datetime.now() 075 self.second = datetime.timedelta ( 0 , 1 ) # One second interval 076 self.mode = "REAL TIME" 077 self.tickTime = 1000 078 079 pygame.font.init() 080 self.clockFont = pygame.font.SysFont ( "" , 300 ) 081 self.labelFont = pygame.font.SysFont ( "" , 25 ) 082 self.drawMode() 083 084 def tick ( self ): 085 self.dt += self.second 086 self.render() 087 088 def render ( self ): 089 timeSurf = self.clockFont.render ( "{0:02d}:{1:02d}:{2:02d}".format ( self.dt.hour , self.dt.minute , self.dt.second ) , True , ( 255 , 0 , 0 ) ) 090 091 self.screen.fill ( ( 0 , 0 , 0 ) ) 092 093 offset = self.screen.get_width() / 2 - timeSurf.get_width() / 2 094 self.screen.blit ( timeSurf , ( offset , 100 ) ) 095 self.drawMode() 096 097 self.chip.set ( self.display.ledVal ( "{0:02d}{1:02d}".format ( self.dt.hour , self.dt.minute ) ) ) 098 099 pygame.display.flip() 100 101 def drawMode ( self ): 102 pygame.draw.rect ( self.screen , ( 0 , 0 , 0 ) , ( 0 , 450 , 100 , 30 ) , 0 ) 103 mode = self.labelFont.render ( self.mode , True , ( 0 , 0 , 255 ) ) 104 self.screen.blit ( mode , ( 0 , 450 ) ) 105 pygame.display.flip() 106 107 def loop ( self ): 108 pygame.time.set_timer ( pygame.USEREVENT , self.tickTime ) 109 110 looping = True 111 while looping: 112 for event in pygame.event.get(): 113 if event.type == pygame.USEREVENT: self.tick() 114 if event.type == pygame.KEYDOWN: 115 if event.key == ord ( "1" ): 116 self.tickTime = 1000 117 self.mode = "1:1" 118 pygame.time.set_timer ( pygame.USEREVENT , self.tickTime ) 119 self.drawMode() 120 elif event.key == ord ( "2" ): 121 self.tickTime = 500 122 self.mode = "2x" 123 pygame.time.set_timer ( pygame.USEREVENT , self.tickTime ) 124 self.drawMode() 125 elif event.key == ord ( "4" ): 126 self.tickTime = 250 127 self.mode = "4x" 128 pygame.time.set_timer ( pygame.USEREVENT , self.tickTime ) 129 self.drawMode() 130 elif event.key == ord ( "8" ): 131 self.tickTime = 125 132 self.mode = "8x" 133 pygame.time.set_timer ( pygame.USEREVENT , self.tickTime ) 134 self.drawMode() 135 elif event.key == ord ( "0" ): 136 self.tickTime = 0 137 self.mode = "PAUSED" 138 pygame.time.set_timer ( pygame.USEREVENT , self.tickTime ) 139 self.drawMode() 140 elif event.key == ord ( "r" ): 141 self.dt = datetime.datetime.now() 142 self.tickTime = 1000 143 self.mode = "REAL TIME" 144 pygame.time.set_timer ( pygame.USEREVENT , self.tickTime ) 145 self.drawMode() 146 elif event.key == ord ( "q" ): 147 self.dt += datetime.timedelta ( 0 , 0 , 0 , 0 , 0 , 1 ) 148 self.render() 149 elif event.key == ord ( "a" ): 150 self.dt -= datetime.timedelta ( 0 , 0 , 0 , 0 , 0 , 1 ) 151 self.render() 152 elif event.key == ord ( "w" ): 153 self.dt += datetime.timedelta ( 0 , 0 , 0 , 0 , 1 ) 154 self.render() 155 elif event.key == ord ( "s" ): 156 self.dt -= datetime.timedelta ( 0 , 0 , 0 , 0 , 1 ) 157 self.render() 158 elif event.key == ord ( "e" ): 159 self.dt += datetime.timedelta ( 0 , 1 ) 160 self.render() 161 elif event.key == ord ( "d" ): 162 self.dt -= datetime.timedelta ( 0 , 1 ) 163 self.render() 164 165 clockPin = 14 166 dataPin = 15 167 chip = fiftyfourfifty ( clockPin , dataPin ) 168 display = sevenSegment() 169 170 pygame.display.init() 171 screen = pygame.display.set_mode ( ( 800 , 480 ) ) 172 clk = clock ( screen , chip , display ) 173 clk.loop()
The sevenSegment
class (lines 6-36) represents the LED segments in the remote displays.
The __init__
function (lines 7-26) shows comments where I note the mapping between bits in a Python variable and segments in the seven-segment display. Line 16 initializes a list
to store bit patterns, then lines 17-26 define the bit patterns needed to display each digit.
In the ledVal
function (lines 28-35), the 5450 is expecting a string of 34 bits to tell it what to display. ledVal
takes a four-digit integer and translates it into a very large (34-bit) integer. This bit pattern represents the provided 4 digits in LED form.
Additionally, LED
(line 29) is the integer that will store the result of the calculation. numString
(line 30) is the provided number
converted to a string. Line 31 sets up a loop to deal with each digit in the number individually, and line 32 retrieves the single-digit bit pattern from self.digits
.
Because the first seven-segment display is on 5450 outputs 1-7, the next is on outputs 8-14, and so on. Line 33 shifts the retrieved bit pattern into the appropriate position so that it lines up with its associated display. Then, line 34 adds this to LED
. When it's all done, line 35 returns LED
, which will be an integer that represents the required bits (discrete LEDs) that will display those digits on the remote display.
The fiftyfourfifty
class (lines 37-67) handles talking to the MM5450 LED driver chip via the GPIO port. Here, the __init__
function (lines 38-44) sets up the GPIO. Lines 39 and 40 take the function arguments clock
and data
and store them in their class equivalents. Line 42 tells the gpio
library that I will be using chip numbering to address the pins. Lines 43 and 44 set both clock and data pins to outputs.
The pulseClock
function (lines 46-51) sets the clock GPIO pin low (line 47), waits 1/100th of a second (line 48), sets the clock GPIO pin high (line 49), waits another 1/100th of a second (line 50), and then returns the clock to low (line 51). This function will be called whenever a clock pulse is needed.
The startPulse
function (lines 53-55) sets data high (line 54) and then calls self.pulseClock
(line 55). This step will send a digital 1
to the 5450.
Next, the set
function (lines 57-67) sends a complete sequence to the 5450. It starts with a self.startPulse
(line 58) and then loops over each bit in LED
to send it to the 5450 (line 59). The LED & 1
on line 60 compares only the first bit of LED, which will either be 0 or 1. Lines 61 and 63 set the data pin high or low appropriately. Finally, line 64 shifts the contents of the LED
variable one bit to the right. After pulsing the clock (line 66), the entire process repeats. Once the loop has sent all 34 bits of data to the 5450, self.pulseClock
(line 67) sends one more clock to load the data.
The clock
class (lines 69-163) maintains the fast clock's time, displays the clock on the Pi Zero's screen, and tracks the update rate (how fast the fast clock runs).
The __init__ class (lines 70-82) copies the function parameters into class variables. Line 74 initializes self.dt
as the current date and time from datetime.datetime.now
. Line 75 creates a datetime.timedelta
object containing one second. Then, timedelta
objects can be added and subtracted from datetime
objects as needed. Line 76 initializes self.mode
to "REAL TIME"
, and line 77 sets self.tickTime
to 1000
(milliseconds). This sets up the clock to run in real time (and at real speed) until adjusted otherwise.
Line 79 initializes the pygame.font
module, then lines 80 and 81 create the fonts used to draw on the screen. The values 300 and 25 are the size in pixels the font should draw onscreen. Finally, line 82 calls self.drawMode
to display the current clock mode in the corner of the screen.
The tick
function (lines 84-86) adds one second to the fast clock (line 85) and then redraws the screen (self.render
). Next, the render
function (lines 88-99) is responsible for drawing the clock on the screen whenever it gets updated. It also sends updated values to the remote displays.
Line 89 calls self.clockFont.render
to create a PyGame surface with the current time. Line 91 clears the screen by filling it with black, then line 93 calculates the offset needed to center the clock on the screen. Next, self.screen.blit
draws timeSurf
onto the screen, and self.drawMode
on line 95 displays the current clock speed in the corner of the display.
Line 97 sends the fast clock hour and minute to the 5450 to be displayed on all of the remote displays. Finally, line 99 flips the display so all of the changes are visible on the Pi Zero's monitor.
The drawMode
function (lines 101-105) draws the current clock speed in the corner of the display. Line 102 erases the old mode on the screen by drawing a black rectangle over the corner. Line 103 renders the mode into the variable also called mode
. Then, line 104 blits the mode
surface onto the screen. Line 105 flips the display so everything is displayed on screen.
The loop function (lines 107-163) is the main loop of the fast clock. Line 108 starts a PyGame timer to fire every self.tickTime
milliseconds. Initially this will be 1,000 for real-time updates. Line 110 sets looping
to True
, then line 111 starts a loop watching for PyGame events (line 112). Line 113 calls self.tick
every time the PyGame timer is fired.
Line 114 checks whether the PyGame event is a keypress. If so, one of the if/elif
blocks on lines 115-164 will handle it. If the key
is 1
, 2
, 4
, 8
, or 0
, then the speed of the fast clock is changing. I'll look at "1"
(lines 115-119) for this example. Line 116 sets self.tickTime
to the new update rate. Line 117 sets self.mode
to the new clock mode. Finally, line 118 updates the PyGame timer to run at the new update rate, and line 119 calls self.drawMode
so the new speed is shown onscreen. Lines 120 through 139 work the same way with the different speeds.
Lines 140-145 handle the special case for real time. Line 141 updates self.dt
– the fast clock's current time – to the current system time (real time). Lines 146-163 adjust the hours, minutes, or seconds of the clock so that it can be set to any time. To add an hour, for example, a datetime.timedelta
object is initialized holding 1 hour and adds it to self.dt
(line 147). Then, self.render
is called (line 148) to show the new value. The remainder of the function works the same way with different keys, adding/subtracting hours, minutes, and seconds.
Starting Everything
So far, I've defined a number of functions and classes, but nothing has been told to do anything. That all happens on lines 165-173.
First, I define the clock and data pins (lines 165 and 166). Then, I create an instance of the fiftyfourfifty
class, passing in the clock and data pins (line 167). Line 168 creates an instance of sevenSegment
.
Lines 170-171 initialize the PyGame display. Then, line 172 creates an instance of the fast clock, passing in the screen
, chip
, and display
variables so the class can access them directly. Finally, clk.loop
starts the clock running.
« Previous 1 2 3 4 Next »
Buy this article as PDF
Pages: 8
(incl. VAT)