We need to modify the clock program to minimize flicker by only updating the parts of the display that change. This requires tracking the previous state and only redrawing digits that have changed. Here's how we can improve it:
The key improvements in this version include:
State Tracking: Added global variables to track the previous state of all display elements (digits, date, AM/PM, etc.)
Selective Updates: Only redraws elements that have changed since the last update:
Digits are only redrawn when their values change
The date is only updated when it changes
AM/PM indicator only updates when switching between AM and PM
The colon blinks by only updating its small area
Localized Clearing: Instead of clearing the entire screen:
Each digit area is cleared individually before redrawing
Text areas are cleared in their specific regions before updating
Initialization Handling: Added a screen_initialized flag to ensure the background is only drawn once at startup
Improved Update Rate: Reduced the sleep time to 0.1 seconds for smoother updates while still maintaining efficiency
These changes should significantly reduce the flicker since we're only updating the parts of the screen that actually change. The blue background will now only be drawn once at startup, and subsequent updates will be localized to the specific areas that need to change.
To use this improved version, simply replace the contents of your existing clock-main-with-flicker.py with this new code. The functionality remains the same, but the display should be much more stable with minimal flicker.
this is a great example of how selective updates can make a big difference in display quality. Instead of redrawing the entire screen 10 times per second, we're now only updating the specific segments that change.
For example, when displaying "12:59", only the ":59" portion will update frequently. The "12" will stay static until it changes to "1:00". The colons blink by just toggling those few pixels rather than redrawing everything.
This technique (known as "dirty rectangle" updating in graphics programming) is especially important for displays like the ST7735 where each pixel update takes a non-trivial amount of time. By minimizing the number of pixels we need to update, we get both better performance and a cleaner visual result.
frommachineimportPin,SPIfromutimeimportsleep,localtimeimportST7735importconfig# Initialize SPI and displayspi=machine.SPI(config.SPI_BUS,sck=Pin(config.SPI_SCL_PIN),mosi=Pin(config.SPI_SDA_PIN),baudrate=8000000)display=ST7735.ST7735(spi,rst=config.SPI_RESET_PIN,ce=config.SPI_CS_PIN,dc=config.SPI_DC_PIN)display.reset()display.begin()display.set_rotation(config.DISPLAY_ROTATION)# Define colors using RGB565 formatBACKGROUND_COLOR=display.rgb_to_565(0,0,255)# Blue backgroundDIGIT_COLOR=display.rgb_to_565(255,255,255)# White for digitsTEXT_COLOR=display.rgb_to_565(255,255,0)# Yellow for text# Previous state trackingprev_date=""prev_hour_ten=-1prev_hour_right=-1prev_minute_ten=-1prev_minute_right=-1prev_second=-1prev_am_pm=""screen_initialized=FalsesegmentMapping=[#a, b, c, d, e, f, g[1,1,1,1,1,1,0],# 0[0,1,1,0,0,0,0],# 1[1,1,0,1,1,0,1],# 2[1,1,1,1,0,0,1],# 3[0,1,1,0,0,1,1],# 4[1,0,1,1,0,1,1],# 5[1,0,1,1,1,1,1],# 6[1,1,1,0,0,0,0],# 7[1,1,1,1,1,1,1],# 8[1,1,1,1,0,1,1]# 9]defday_to_str(day_num):"""Convert a day number (0-6) to a three-letter day abbreviation."""days=['Mon','Tue','Wed','Thu','Fri','Sat','Sun']ifnot0<=day_num<=6:raiseValueError("Day number must be between 0 and 6")returndays[day_num]defmonth_to_str(month_num):"""Convert a month number (1-12) to a three-letter month abbreviation."""months=['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']ifnot1<=month_num<=12:raiseValueError("Month number must be between 1 and 12")returnmonths[month_num-1]defclear_digit_area(x,y,width,height,color):"""Clear the area where a digit was previously drawn."""display.draw_block(x,y,width,height,color)defdrawDigit(digit,x,y,width,height,thickness,color):"""Draw a seven-segment digit on the display."""ifdigit<0:return# Clear the area firstclear_digit_area(x,y,width,height,BACKGROUND_COLOR)segmentOn=segmentMapping[digit]# Draw horizontal segments (top, middle, bottom)foriin[0,3,6]:ifsegmentOn[i]:ifi==0:# topyOffset=0elifi==3:# bottomyOffset=height-thicknesselse:# middleyOffset=height//2-thickness//2display.draw_block(x,y+yOffset,width,thickness,color)# Draw vertical segmentsforiin[1,2,4,5]:ifsegmentOn[i]:ifi==1ori==5:# upper segmentsstartY=yendY=y+height//2else:# lower segmentsstartY=y+height//2endY=y+heightxOffset=0if(i==4ori==5)elsewidth-thicknessdisplay.draw_block(x+xOffset,startY,thickness,endY-startY,color)defdraw_colon(x,y,color):"""Draw the blinking colon between hours and minutes."""display.draw_block(x,y,4,4,color)display.draw_block(x,y+14,4,4,color)defupdate_screen(year,month,day,hour,minute,second,weekday):"""Update only the changing parts of the display."""globalprev_date,prev_hour_ten,prev_hour_right,prev_minute_tenglobalprev_minute_right,prev_second,prev_am_pm,screen_initialized# Initialize screen on first runifnotscreen_initialized:display.fill_screen(BACKGROUND_COLOR)screen_initialized=True# Calculate positionsleft_margin=-15y_offset=30digit_width=32digit_height=50digit_spacing=42digit_thickness=6# Convert 24-hour to 12-hour formatdisplay_hour=hourifhour<=12elsehour-12ifdisplay_hour==0:display_hour=12# Format date stringdate_str=f"{day_to_str(weekday)}{month_to_str(month)}{day}{year}"# Update date if changedifdate_str!=prev_date:display._color=TEXT_COLOR# Clear previous date areadisplay.draw_block(4,4,160,10,BACKGROUND_COLOR)display.p_string(4,4,date_str)prev_date=date_str# Split time into digitshour_ten=display_hour//10ifdisplay_hour>=10else-1hour_right=display_hour%10minute_ten=minute//10minute_right=minute%10# Update digits only if they've changedifhour_ten!=prev_hour_ten:drawDigit(hour_ten,left_margin,y_offset,digit_width,digit_height,digit_thickness,DIGIT_COLOR)prev_hour_ten=hour_tenifhour_right!=prev_hour_right:drawDigit(hour_right,left_margin+digit_spacing,y_offset,digit_width,digit_height,digit_thickness,DIGIT_COLOR)prev_hour_right=hour_rightifminute_ten!=prev_minute_ten:drawDigit(minute_ten,left_margin+2*digit_spacing+10,y_offset,digit_width,digit_height,digit_thickness,DIGIT_COLOR)prev_minute_ten=minute_tenifminute_right!=prev_minute_right:drawDigit(minute_right,left_margin+3*digit_spacing+10,y_offset,digit_width,digit_height,digit_thickness,DIGIT_COLOR)prev_minute_right=minute_right# Handle colon blinking - clear and redraw based on secondscolon_x=left_margin+digit_spacing+digit_width+8colon_y=y_offset+15ifsecond%2:draw_colon(colon_x,colon_y,DIGIT_COLOR)else:draw_colon(colon_x,colon_y,BACKGROUND_COLOR)# Update AM/PM indicator if neededam_pm_str="PM"ifhour>=12else"AM"ifam_pm_str!=prev_am_pm:display._color=TEXT_COLORdisplay.draw_block(left_margin+3*digit_spacing+digit_width,y_offset+60,20,10,BACKGROUND_COLOR)display.p_string(left_margin+3*digit_spacing+digit_width,y_offset+60,am_pm_str)prev_am_pm=am_pm_str# Update seconds display if changedifsecond!=prev_second:display._color=TEXT_COLORdisplay.draw_block(4,y_offset+digit_height+10,20,10,BACKGROUND_COLOR)display.p_string(4,y_offset+digit_height+10,f"{second:02d}")prev_second=second# Main loopwhileTrue:now=localtime()year=now[0]month=now[1]day=now[2]hour=now[3]minute=now[4]second=now[5]weekday=now[6]update_screen(year,month,day,hour,minute,second,weekday)sleep(0.1)# Shorter sleep for more responsive updates