From 76db5b4abe246af667a97cd25fcd7e5de0a670df Mon Sep 17 00:00:00 2001 From: Dan Mccreary Date: Wed, 25 Dec 2024 11:01:39 -0600 Subject: [PATCH] Deployed f057493 with MkDocs version: 1.6.1 --- 404.html | 4 + about/index.html | 4 + contact/index.html | 4 + course-description/index.html | 4 + faq/index.html | 4 + index.html | 4 + kits/gc9a01/01-hello-world/index.html | 4 + kits/gc9a01/02-display-time/index.html | 4 + kits/gc9a01/03-display-hand/index.html | 4 + kits/gc9a01/04-cycle-fonts/index.html | 4 + kits/gc9a01/05-draw-ticks/index.html | 4 + kits/gc9a01/07-draw-12-digits/index.html | 4 + .../gc9a01/08-draw-filled-triangle/index.html | 4 + kits/gc9a01/09-watch-face/index.html | 4 + kits/gc9a01/index.html | 4 + kits/ili9341/01-rainbow-test/index.html | 4 + kits/ili9341/02-config/index.html | 4 + kits/ili9341/03-multiple-fonts/index.html | 4 + kits/ili9341/index.html | 4 + kits/index.html | 4 + kits/led-strips/01-binary-clock/index.html | 4 + .../02-seven-segment-clock/index.html | 4 + kits/led-strips/index.html | 4 + kits/lilygo/index.html | 4 + kits/max7219/index.html | 4 + kits/pico-oled/01-hello-world/index.html | 4 + kits/pico-oled/02-display-time/index.html | 4 + kits/pico-oled/02-hello-config/index.html | 4 + .../pico-oled/03-display-localtime/index.html | 4 + .../04-display-localtime-updates/index.html | 4 + kits/pico-oled/05-clock-hands/index.html | 4 + .../10-draw-seven-segments/index.html | 4 + kits/pico-oled/11-draw-thinkness/index.html | 4 + kits/pico-oled/15-digital-clock/index.html | 4 + kits/pico-oled/index.html | 4 + kits/waveshare/01-hello-world/index.html | 4 + kits/waveshare/02-color-test/index.html | 4 + kits/waveshare/02-display-text/index.html | 4 + kits/waveshare/03-drawing-hands/index.html | 4 + kits/waveshare/04-5x8-font/index.html | 4 + kits/waveshare/index.html | 4 + lessons/00-hardware-config/index.html | 4 + lessons/01-drawing-coordinates/index.html | 4 + lessons/01-get-local-time/index.html | 4 + lessons/02-display-text/index.html | 4 + lessons/03-drawing-face/index.html | 4 + lessons/04-larger-fonts/index.html | 4 + lessons/05-buttons/index.html | 4 + lessons/06-draw-performance/index.html | 4 + lessons/07-drawing-digits/index.html | 4 + lessons/09-digit-functions/index.html | 4 + lessons/12-draw-time-functions/index.html | 4 + lessons/15-scale-testing/index.html | 4 + lessons/30-ili9341/index.html | 4 + lessons/40-machine-info/index.html | 4 + lessons/index.html | 4 + prompts/01-course-description/index.html | 4 + prompts/02-concept-enumeration/index.html | 4 + prompts/03-concept-dependency/index.html | 891 ++++++++++++ prompts/04-concept-taxonomy/index.html | 1162 ++++++++++++++++ prompts/05-taxonomy-assignment/index.html | 854 ++++++++++++ prompts/clocks-and-watches.csv | 116 ++ prompts/concept-dependency.csv | 116 ++ references/index.html | 4 + search/search_index.json | 2 +- setup/01-desktop/index.html | 4 + setup/02-purchsing-parts/index.html | 4 + setup/03-approach/index.html | 4 + setup/index.html | 4 + sims/4-digits/index.html | 31 +- sims/analog-face/index.html | 25 + sims/index.html | 32 +- sims/learning-graph/clocks-and-watches.json | 1205 +++++++++++++++++ sims/learning-graph/index.html | 36 + sims/learning-graph/view-graph.html | 111 ++ sims/learning-graph/view-graph.js | 132 ++ sims/seven-segments/index.html | 25 + sims/themes/index.html | 25 + sitemap.xml | 12 + sitemap.xml.gz | Bin 805 -> 839 bytes 80 files changed, 5020 insertions(+), 7 deletions(-) create mode 100644 prompts/03-concept-dependency/index.html create mode 100644 prompts/04-concept-taxonomy/index.html create mode 100644 prompts/05-taxonomy-assignment/index.html create mode 100644 prompts/clocks-and-watches.csv create mode 100644 prompts/concept-dependency.csv create mode 100644 sims/learning-graph/clocks-and-watches.json create mode 100644 sims/learning-graph/view-graph.html create mode 100644 sims/learning-graph/view-graph.js diff --git a/404.html b/404.html index f0394198..2a879af9 100644 --- a/404.html +++ b/404.html @@ -500,6 +500,10 @@ + + + + diff --git a/about/index.html b/about/index.html index 400324a6..c993ac30 100644 --- a/about/index.html +++ b/about/index.html @@ -614,6 +614,10 @@ + + + + diff --git a/contact/index.html b/contact/index.html index d12c2ae1..b6fd9e1b 100644 --- a/contact/index.html +++ b/contact/index.html @@ -511,6 +511,10 @@ + + + + diff --git a/course-description/index.html b/course-description/index.html index 567968f1..73c85877 100644 --- a/course-description/index.html +++ b/course-description/index.html @@ -521,6 +521,10 @@ + + + + diff --git a/faq/index.html b/faq/index.html index dd54813b..69732937 100644 --- a/faq/index.html +++ b/faq/index.html @@ -511,6 +511,10 @@ + + + + diff --git a/index.html b/index.html index a380e276..7d98008e 100644 --- a/index.html +++ b/index.html @@ -576,6 +576,10 @@ + + + + diff --git a/kits/gc9a01/01-hello-world/index.html b/kits/gc9a01/01-hello-world/index.html index dda2c00b..ee3d4b6c 100644 --- a/kits/gc9a01/01-hello-world/index.html +++ b/kits/gc9a01/01-hello-world/index.html @@ -1152,6 +1152,10 @@ + + + + diff --git a/kits/gc9a01/02-display-time/index.html b/kits/gc9a01/02-display-time/index.html index 3eaee6d7..128d48ac 100644 --- a/kits/gc9a01/02-display-time/index.html +++ b/kits/gc9a01/02-display-time/index.html @@ -1152,6 +1152,10 @@ + + + + diff --git a/kits/gc9a01/03-display-hand/index.html b/kits/gc9a01/03-display-hand/index.html index f0949175..d3640ff8 100644 --- a/kits/gc9a01/03-display-hand/index.html +++ b/kits/gc9a01/03-display-hand/index.html @@ -1152,6 +1152,10 @@ + + + + diff --git a/kits/gc9a01/04-cycle-fonts/index.html b/kits/gc9a01/04-cycle-fonts/index.html index 1e9de0fa..1a019e77 100644 --- a/kits/gc9a01/04-cycle-fonts/index.html +++ b/kits/gc9a01/04-cycle-fonts/index.html @@ -1191,6 +1191,10 @@ + + + + diff --git a/kits/gc9a01/05-draw-ticks/index.html b/kits/gc9a01/05-draw-ticks/index.html index 6b55301b..50582380 100644 --- a/kits/gc9a01/05-draw-ticks/index.html +++ b/kits/gc9a01/05-draw-ticks/index.html @@ -1191,6 +1191,10 @@ + + + + diff --git a/kits/gc9a01/07-draw-12-digits/index.html b/kits/gc9a01/07-draw-12-digits/index.html index 4d13dbd5..03fc2075 100644 --- a/kits/gc9a01/07-draw-12-digits/index.html +++ b/kits/gc9a01/07-draw-12-digits/index.html @@ -1200,6 +1200,10 @@ + + + + diff --git a/kits/gc9a01/08-draw-filled-triangle/index.html b/kits/gc9a01/08-draw-filled-triangle/index.html index 100a2069..360cca6a 100644 --- a/kits/gc9a01/08-draw-filled-triangle/index.html +++ b/kits/gc9a01/08-draw-filled-triangle/index.html @@ -1209,6 +1209,10 @@ + + + + diff --git a/kits/gc9a01/09-watch-face/index.html b/kits/gc9a01/09-watch-face/index.html index 24f68ee8..1b16d0b6 100644 --- a/kits/gc9a01/09-watch-face/index.html +++ b/kits/gc9a01/09-watch-face/index.html @@ -1152,6 +1152,10 @@ + + + + diff --git a/kits/gc9a01/index.html b/kits/gc9a01/index.html index acce6c40..4182769a 100644 --- a/kits/gc9a01/index.html +++ b/kits/gc9a01/index.html @@ -1142,6 +1142,10 @@ + + + + diff --git a/kits/ili9341/01-rainbow-test/index.html b/kits/ili9341/01-rainbow-test/index.html index 08c775de..767c13e0 100644 --- a/kits/ili9341/01-rainbow-test/index.html +++ b/kits/ili9341/01-rainbow-test/index.html @@ -1074,6 +1074,10 @@ + + + + diff --git a/kits/ili9341/02-config/index.html b/kits/ili9341/02-config/index.html index 44c2c980..6cf8b442 100644 --- a/kits/ili9341/02-config/index.html +++ b/kits/ili9341/02-config/index.html @@ -1074,6 +1074,10 @@ + + + + diff --git a/kits/ili9341/03-multiple-fonts/index.html b/kits/ili9341/03-multiple-fonts/index.html index 6c99b25e..5c267f0c 100644 --- a/kits/ili9341/03-multiple-fonts/index.html +++ b/kits/ili9341/03-multiple-fonts/index.html @@ -960,6 +960,10 @@ + + + + diff --git a/kits/ili9341/index.html b/kits/ili9341/index.html index 771cabba..241f29d5 100644 --- a/kits/ili9341/index.html +++ b/kits/ili9341/index.html @@ -1016,6 +1016,10 @@ + + + + diff --git a/kits/index.html b/kits/index.html index e97881f7..b5efe793 100644 --- a/kits/index.html +++ b/kits/index.html @@ -957,6 +957,10 @@ + + + + diff --git a/kits/led-strips/01-binary-clock/index.html b/kits/led-strips/01-binary-clock/index.html index 041bf9a3..9cc56f89 100644 --- a/kits/led-strips/01-binary-clock/index.html +++ b/kits/led-strips/01-binary-clock/index.html @@ -1074,6 +1074,10 @@ + + + + diff --git a/kits/led-strips/02-seven-segment-clock/index.html b/kits/led-strips/02-seven-segment-clock/index.html index 6d297f67..16565cc7 100644 --- a/kits/led-strips/02-seven-segment-clock/index.html +++ b/kits/led-strips/02-seven-segment-clock/index.html @@ -1065,6 +1065,10 @@ + + + + diff --git a/kits/led-strips/index.html b/kits/led-strips/index.html index 37eee588..62ddc4e3 100644 --- a/kits/led-strips/index.html +++ b/kits/led-strips/index.html @@ -1016,6 +1016,10 @@ + + + + diff --git a/kits/lilygo/index.html b/kits/lilygo/index.html index 29b53103..34ee9c23 100644 --- a/kits/lilygo/index.html +++ b/kits/lilygo/index.html @@ -969,6 +969,10 @@ + + + + diff --git a/kits/max7219/index.html b/kits/max7219/index.html index 544c018e..cc0f3a24 100644 --- a/kits/max7219/index.html +++ b/kits/max7219/index.html @@ -1102,6 +1102,10 @@ + + + + diff --git a/kits/pico-oled/01-hello-world/index.html b/kits/pico-oled/01-hello-world/index.html index 4b8de4ed..f7afefa3 100644 --- a/kits/pico-oled/01-hello-world/index.html +++ b/kits/pico-oled/01-hello-world/index.html @@ -1131,6 +1131,10 @@ + + + + diff --git a/kits/pico-oled/02-display-time/index.html b/kits/pico-oled/02-display-time/index.html index be7fed30..b46d891f 100644 --- a/kits/pico-oled/02-display-time/index.html +++ b/kits/pico-oled/02-display-time/index.html @@ -1170,6 +1170,10 @@ + + + + diff --git a/kits/pico-oled/02-hello-config/index.html b/kits/pico-oled/02-hello-config/index.html index 7a036543..10840ad6 100644 --- a/kits/pico-oled/02-hello-config/index.html +++ b/kits/pico-oled/02-hello-config/index.html @@ -1131,6 +1131,10 @@ + + + + diff --git a/kits/pico-oled/03-display-localtime/index.html b/kits/pico-oled/03-display-localtime/index.html index 4db2269d..2b858c21 100644 --- a/kits/pico-oled/03-display-localtime/index.html +++ b/kits/pico-oled/03-display-localtime/index.html @@ -1131,6 +1131,10 @@ + + + + diff --git a/kits/pico-oled/04-display-localtime-updates/index.html b/kits/pico-oled/04-display-localtime-updates/index.html index f4d1b400..63f38522 100644 --- a/kits/pico-oled/04-display-localtime-updates/index.html +++ b/kits/pico-oled/04-display-localtime-updates/index.html @@ -1131,6 +1131,10 @@ + + + + diff --git a/kits/pico-oled/05-clock-hands/index.html b/kits/pico-oled/05-clock-hands/index.html index 64a57303..39a09148 100644 --- a/kits/pico-oled/05-clock-hands/index.html +++ b/kits/pico-oled/05-clock-hands/index.html @@ -1131,6 +1131,10 @@ + + + + diff --git a/kits/pico-oled/10-draw-seven-segments/index.html b/kits/pico-oled/10-draw-seven-segments/index.html index 1d56aa12..aaf24a3b 100644 --- a/kits/pico-oled/10-draw-seven-segments/index.html +++ b/kits/pico-oled/10-draw-seven-segments/index.html @@ -1024,6 +1024,10 @@ + + + + diff --git a/kits/pico-oled/11-draw-thinkness/index.html b/kits/pico-oled/11-draw-thinkness/index.html index 301599f5..b58b1552 100644 --- a/kits/pico-oled/11-draw-thinkness/index.html +++ b/kits/pico-oled/11-draw-thinkness/index.html @@ -967,6 +967,10 @@ + + + + diff --git a/kits/pico-oled/15-digital-clock/index.html b/kits/pico-oled/15-digital-clock/index.html index 26fd59ce..a3a832d9 100644 --- a/kits/pico-oled/15-digital-clock/index.html +++ b/kits/pico-oled/15-digital-clock/index.html @@ -967,6 +967,10 @@ + + + + diff --git a/kits/pico-oled/index.html b/kits/pico-oled/index.html index 87fd7ea2..44f53cc2 100644 --- a/kits/pico-oled/index.html +++ b/kits/pico-oled/index.html @@ -1121,6 +1121,10 @@ + + + + diff --git a/kits/waveshare/01-hello-world/index.html b/kits/waveshare/01-hello-world/index.html index 3c14af49..4b8de26e 100644 --- a/kits/waveshare/01-hello-world/index.html +++ b/kits/waveshare/01-hello-world/index.html @@ -1137,6 +1137,10 @@ + + + + diff --git a/kits/waveshare/02-color-test/index.html b/kits/waveshare/02-color-test/index.html index 3a3aa2e7..833433c4 100644 --- a/kits/waveshare/02-color-test/index.html +++ b/kits/waveshare/02-color-test/index.html @@ -1137,6 +1137,10 @@ + + + + diff --git a/kits/waveshare/02-display-text/index.html b/kits/waveshare/02-display-text/index.html index b968d54e..5a333910 100644 --- a/kits/waveshare/02-display-text/index.html +++ b/kits/waveshare/02-display-text/index.html @@ -1089,6 +1089,10 @@ + + + + diff --git a/kits/waveshare/03-drawing-hands/index.html b/kits/waveshare/03-drawing-hands/index.html index b6d32bf3..f0ff83af 100644 --- a/kits/waveshare/03-drawing-hands/index.html +++ b/kits/waveshare/03-drawing-hands/index.html @@ -1173,6 +1173,10 @@ + + + + diff --git a/kits/waveshare/04-5x8-font/index.html b/kits/waveshare/04-5x8-font/index.html index 92e0fd10..a75e1eb7 100644 --- a/kits/waveshare/04-5x8-font/index.html +++ b/kits/waveshare/04-5x8-font/index.html @@ -1089,6 +1089,10 @@ + + + + diff --git a/kits/waveshare/index.html b/kits/waveshare/index.html index 51914b26..651dacbe 100644 --- a/kits/waveshare/index.html +++ b/kits/waveshare/index.html @@ -1079,6 +1079,10 @@ + + + + diff --git a/lessons/00-hardware-config/index.html b/lessons/00-hardware-config/index.html index a36b4a9d..f35349de 100644 --- a/lessons/00-hardware-config/index.html +++ b/lessons/00-hardware-config/index.html @@ -842,6 +842,10 @@ + + + + diff --git a/lessons/01-drawing-coordinates/index.html b/lessons/01-drawing-coordinates/index.html index b98c3be3..c145951d 100644 --- a/lessons/01-drawing-coordinates/index.html +++ b/lessons/01-drawing-coordinates/index.html @@ -832,6 +832,10 @@ + + + + diff --git a/lessons/01-get-local-time/index.html b/lessons/01-get-local-time/index.html index 34ca06f3..394fd72a 100644 --- a/lessons/01-get-local-time/index.html +++ b/lessons/01-get-local-time/index.html @@ -982,6 +982,10 @@ + + + + diff --git a/lessons/02-display-text/index.html b/lessons/02-display-text/index.html index d6344474..cf5e67d5 100644 --- a/lessons/02-display-text/index.html +++ b/lessons/02-display-text/index.html @@ -832,6 +832,10 @@ + + + + diff --git a/lessons/03-drawing-face/index.html b/lessons/03-drawing-face/index.html index 9ff76423..adb94e06 100644 --- a/lessons/03-drawing-face/index.html +++ b/lessons/03-drawing-face/index.html @@ -871,6 +871,10 @@ + + + + diff --git a/lessons/04-larger-fonts/index.html b/lessons/04-larger-fonts/index.html index 163ce078..1b41bd7b 100644 --- a/lessons/04-larger-fonts/index.html +++ b/lessons/04-larger-fonts/index.html @@ -871,6 +871,10 @@ + + + + diff --git a/lessons/05-buttons/index.html b/lessons/05-buttons/index.html index cd85d57b..d1ab80ec 100644 --- a/lessons/05-buttons/index.html +++ b/lessons/05-buttons/index.html @@ -871,6 +871,10 @@ + + + + diff --git a/lessons/06-draw-performance/index.html b/lessons/06-draw-performance/index.html index 3c4b339d..5a1f9aab 100644 --- a/lessons/06-draw-performance/index.html +++ b/lessons/06-draw-performance/index.html @@ -880,6 +880,10 @@ + + + + diff --git a/lessons/07-drawing-digits/index.html b/lessons/07-drawing-digits/index.html index c602b61e..4a3b14fc 100644 --- a/lessons/07-drawing-digits/index.html +++ b/lessons/07-drawing-digits/index.html @@ -907,6 +907,10 @@ + + + + diff --git a/lessons/09-digit-functions/index.html b/lessons/09-digit-functions/index.html index ccc6af4a..1e14141b 100644 --- a/lessons/09-digit-functions/index.html +++ b/lessons/09-digit-functions/index.html @@ -889,6 +889,10 @@ + + + + diff --git a/lessons/12-draw-time-functions/index.html b/lessons/12-draw-time-functions/index.html index 33a6ff74..0035f060 100644 --- a/lessons/12-draw-time-functions/index.html +++ b/lessons/12-draw-time-functions/index.html @@ -880,6 +880,10 @@ + + + + diff --git a/lessons/15-scale-testing/index.html b/lessons/15-scale-testing/index.html index 127204dd..2d263208 100644 --- a/lessons/15-scale-testing/index.html +++ b/lessons/15-scale-testing/index.html @@ -871,6 +871,10 @@ + + + + diff --git a/lessons/30-ili9341/index.html b/lessons/30-ili9341/index.html index 023930c7..6dcfdb6b 100644 --- a/lessons/30-ili9341/index.html +++ b/lessons/30-ili9341/index.html @@ -511,6 +511,10 @@ + + + + diff --git a/lessons/40-machine-info/index.html b/lessons/40-machine-info/index.html index 73a54d29..e4024a3f 100644 --- a/lessons/40-machine-info/index.html +++ b/lessons/40-machine-info/index.html @@ -898,6 +898,10 @@ + + + + diff --git a/lessons/index.html b/lessons/index.html index 9a7c79cf..32b6de33 100644 --- a/lessons/index.html +++ b/lessons/index.html @@ -822,6 +822,10 @@ + + + + diff --git a/prompts/01-course-description/index.html b/prompts/01-course-description/index.html index 6c0aa5e6..14b93e28 100644 --- a/prompts/01-course-description/index.html +++ b/prompts/01-course-description/index.html @@ -511,6 +511,10 @@ + + + + diff --git a/prompts/02-concept-enumeration/index.html b/prompts/02-concept-enumeration/index.html index 2d9205d4..8e93821e 100644 --- a/prompts/02-concept-enumeration/index.html +++ b/prompts/02-concept-enumeration/index.html @@ -511,6 +511,10 @@ + + + + diff --git a/prompts/03-concept-dependency/index.html b/prompts/03-concept-dependency/index.html new file mode 100644 index 00000000..45029e10 --- /dev/null +++ b/prompts/03-concept-dependency/index.html @@ -0,0 +1,891 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Concept Dependency - MicroPython Clocks and Watches + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Concept Dependency

+

``` prompt + I am creating a course on + "Building Clock and Watches with MicroPython".

+
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
The goal of this course is to provide high-school students a fun way to learn computational thinking.
+
+I have listed below about 150 concepts taught in this course in a CSV file.  The first column is the ConceptID and the second column is the ConceptLabel.
+
+Please create a fully connected concept dependency graph
+for the concepts in this course.
+
+In this step, for each Concept on this list below, create a new
+list of other concepts on which it depends.  If a concept
+depends on a specific concept that is not listed,
+then we will need to add it as a new concept.
+This will help us determine the teaching order of the Concepts.
+
+Return the new list as a CSV file using the following format:
+
+1. The first column in the CSV file is a numeric concept
+ID you assigned in the list above.
+2. The second column is the concept name.
+3. The third column is a pipe-delimited list of the Concept IDs that the concept depends upon.
+
+Check your results to make sure that all concepts are connected to at least one other concept.
+
+Foundation Concepts are concepts that have no dependant concepts in this set.  
+Foundation Concepts have an empty third column.
+
+ + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/prompts/04-concept-taxonomy/index.html b/prompts/04-concept-taxonomy/index.html new file mode 100644 index 00000000..d7a3b7d8 --- /dev/null +++ b/prompts/04-concept-taxonomy/index.html @@ -0,0 +1,1162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Concept Taxonomy - MicroPython Clocks and Watches + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Concept Taxonomy

+
+

Prompt

+
+

Given the concepts below, our next step is to create a 10-element taxonomy +of these concepts for the book "Building Clocks and Watches with MicroPython".

+

Your task is to organize these concepts into a taxonomy by grouping similar Concepts into one of ten categories. Each category should include concepts +that share similar characteristics, functions, or applications.

+

Please suggest 10 categories for the taxonomy. Attempt to +create categories that evenly distribute the concepts so that +each category has a similar number of concepts in it.

+

Ensure that the categories cover +a range of concept types, including abstract ideas, practical +applications, common misunderstandings, and real-world examples. +Avoid redundancy and ensure that each category is distinct.

+

You can use "Foundational Concepts" as your first category. +You can use "Terms and Definitions" as a second category. +You can use "Example Projects" as a 10th category.

+

ChatGPT o1 Response

+

Thought about taxonomy for course concepts for 2m 17s

+

Below is one possible 10‑category taxonomy that groups these 115 concepts into roughly even clusters. Each category highlights a distinct thematic area, from foundational ideas through advanced techniques and ending with example "capstone‑style" projects. Feel free to adjust or rename categories as needed for your curriculum goals.

+

Category 1: Foundational Concepts

+

These are the most basic building blocks and hardware essentials on which everything else depends.

+
    +
  1. (0) Timekeeping Components
  2. +
  3. (1) MicroPython
  4. +
  5. (2) Raspberry Pi Pico W
  6. +
  7. (3) Breadboard
  8. +
  9. (4) Power
  10. +
  11. (5) USB Power
  12. +
  13. (6) Battery Power
  14. +
  15. (7) Battery Backup
  16. +
  17. (8) Battery Drain Calculation
  18. +
  19. (10) Real-time Clocks
  20. +
  21. (11) The Thonny IDE
  22. +
  23. (91) Hardware Documentation
  24. +
+

Category 2: Terms & Definitions

+

Core programming syntax and "must-know" terminology used throughout the course.

+
    +
  1. (13) The localtime() Function
  2. +
  3. (14) MicroPython Syntax
  4. +
  5. (15) Loops
  6. +
  7. (16) Conditionals
  8. +
  9. (17) Functions
  10. +
  11. (18) Libraries
  12. +
  13. (20) Printing Time to the Console
  14. +
  15. (34) Using 16 Bits to Represent Color
  16. +
  17. (93) Using math Module for Geometry
  18. +
  19. (94) Time Zones & UTC Offsets
  20. +
  21. (95) Daylight Saving Time
  22. +
  23. (101) Converting Between datetime Objects and Timestamps
  24. +
+

Category 3: Displays

+

An overview of the different display types you might use in clocks/watches.

+
    +
  1. (9) LED Clock Displays
  2. +
  3. (21) Displays
  4. +
  5. (32) OLED Displays
  6. +
  7. (33) Smartwatch Displays
  8. +
  9. (53) Creating a 7-Segment Style Digital Display
  10. +
  11. (54) Custom Segment-Based Displays
  12. +
  13. (75) Using E-Paper Displays
  14. +
  15. (76) Partial Refresh on E-Paper
  16. +
  17. (77) Techniques to Minimize Ghosting on E-Paper
  18. +
  19. (78) Using TFT Displays (e.g., ST7735, ILI9341)
  20. +
  21. (79) Comparing Display Technologies
  22. +
  23. (103) Implementing Clock Themes
  24. +
+

Category 4: Basic Drawing & Font Tools

+

Fundamental graphics APIs and driver-loading concepts.

+
    +
  1. (19) Loading MicroPython Drivers
  2. +
  3. (22) Drawing Lines
  4. +
  5. (23) Drawing Text
  6. +
  7. (24) Drawing Pixels
  8. +
  9. (25) Drawing Rectangles
  10. +
  11. (26) Drawing Circles
  12. +
  13. (27) Drawing Ellipses
  14. +
  15. (28) Drawing Arcs
  16. +
  17. (29) Drawing Polygons
  18. +
  19. (30) Changing Fonts
  20. +
  21. (31) Using MicroPython Drivers
  22. +
  23. (43) Loading Custom Fonts
  24. +
+

Category 5: Advanced Graphics & Animations

+

More complex graphics techniques and math-based rendering.

+
    +
  1. (35) Framebuffer in MicroPython
  2. +
  3. (39) Drawing Hands on an Analog Clock
  4. +
  5. (40) Using math.sin() and math.cos()
  6. +
  7. (41) Drawing Tick Marks on a Watch Face
  8. +
  9. (42) Drawing Numbers on a Watch Face
  10. +
  11. (80) Color Animations on Watch Faces
  12. +
  13. (81) Flicker Reduction Techniques
  14. +
  15. (82) Double Buffering for Smooth Animations
  16. +
  17. (104) Fast Redraw Techniques (Blitting)
  18. +
  19. (105) Partial Screen Updates for Watch Faces
  20. +
  21. (106) Color Conversion & Palettes in MicroPython
  22. +
  23. (83) Using the micropython-ufont Library
  24. +
+

Category 6: Network, Asynchronous, & Concurrency

+

All about getting time from the internet, using Wi-Fi, and handling multiple tasks.

+
    +
  1. (12) Synchronizing Time from the PC
  2. +
  3. (45) Using Internal Timers on the Raspberry Pi Pico
  4. +
  5. (48) Network Time Synchronization (NTP)
  6. +
  7. (49) Setting Up WiFi on the Pico W
  8. +
  9. (50) Working the network module
  10. +
  11. (51) Parsing JSON
  12. +
  13. (65) Timer-Based Events
  14. +
  15. (84) Asynchronous Code with uasyncio
  16. +
  17. (85) Time-Based Tasks with uasyncio
  18. +
  19. (86) Handling Concurrency of Time Updates & UI
  20. +
  21. (108) WiFi-Based Weather & Forecast Updates
  22. +
  23. (114) World Clocks
  24. +
+

Category 7: Sensors & Modules

+

External hardware modules, from RTC chips to accelerometers and I²C sensors.

+
    +
  1. (46) External RTC Modules (DS1307, DS3231)
  2. +
  3. (47) Setting the System Clock from an External RTC
  4. +
  5. (69) Integrating External Sensors
  6. +
  7. (70) Temperature and Humidity
  8. +
  9. (71) Displaying Sensor Data
  10. +
  11. (72) Accelerometers for Orientation
  12. +
  13. (73) Tilt-Based Wake for Watch Display
  14. +
  15. (98) Using I²C Sensors & Modules
  16. +
  17. (109) Using Sensors for Step Counting (Pedometer)
  18. +
  19. (110) Adding a LiPo Charging Circuit
  20. +
  21. (90) Tools for Measuring Battery Usage
  22. +
  23. (100) Servo Motors for Mechanical Pointers
  24. +
+

Category 8: User Input & Time Functions

+

Handling buttons, debouncing, rotary encoders, and time-related features.

+
    +
  1. (52) Displaying Date and Day of Week
  2. +
  3. (57) Setting Time with Buttons
  4. +
  5. (58) Using Interrupts for Button Input
  6. +
  7. (59) Button Debouncing Techniques
  8. +
  9. (60) Using A Rotary Encoder
  10. +
  11. (61) Setting Alarms with the RTC
  12. +
  13. (96) Using the Built-In Filesystem on Pico
  14. +
  15. (97) Logging Time Data to an SD Card
  16. +
  17. (74) Ultra-Low Power
  18. +
  19. (99) Menu Systems for Watch Settings
  20. +
  21. (107) Handling 12-Hour vs. 24-Hour Formats
  22. +
  23. (111) Chronograph Functionality
  24. +
+

Category 9: Audio & Power Management

+

Generating sounds, mechanical movements, and minimizing power consumption.

+
    +
  1. (62) Connecting Speakers
  2. +
  3. (63) Playing a Sound
  4. +
  5. (64) Sound Encoding
  6. +
  7. (66) Playing Chimes
  8. +
  9. (67) Cuckoo Clocks
  10. +
  11. (68) Generating Beep or Alarm Tones
  12. +
  13. (102) Generating Waveforms for Audio
  14. +
  15. (55) Minimizing Power Consumption (Sleep Modes)
  16. +
  17. (56) Waking from Deep Sleep on RTC Alarm
  18. +
  19. (89) Sleep Scheduling
  20. +
  21. (87) Using a Watchdog Timer for Reliability
  22. +
  23. (88) Logging & Debugging Time Issues
  24. +
+

Category 10: Example Projects

+

A set of "capstone" or real-world style projects/enhancements.

+
    +
  1. (92) 3D Printing Watch Enclosures
  2. +
  3. (100) [If not placed elsewhere --- but here we used 100 in Category 7, so you can swap if you prefer]
  4. +
  5. (103) [If not used in Displays]
  6. +
  7. (112) Timer Project
  8. +
  9. (113) Stopwatch Project
  10. +
  11. (109) [If not in Sensors]
  12. +
  13. (99) [If not in User Input]
  14. +
  15. ( ... )
  16. +
+

Depending on how you ultimately shuffle things around, you can populate this "Example Projects" category with any concepts that serve well as culminating projects---for example, "Cuckoo Clocks," "Playing Chimes," "Implementing Clock Themes," "Using Sensors for Step Counting," or "Servo Motors for Mechanical Pointers."

+

Final Notes

+
    +
  1. +

    Balancing the Categories

    +
      +
    • Each category has ~11--12 items (with minor adjustments possible).
    • +
    • Some concepts (e.g., "Servo Motors," "Implementing Clock Themes," or "Cuckoo Clocks") could fit either under a main topic (like "Sensors & Modules" or "Audio & Power") or under "Example Projects," depending on whether you treat them as discrete lessons or culminating builds.
    • +
    • Avoiding Duplication
    • +
    +
  2. +
+

In a real curriculum, certain topics (e.g., "External RTC Modules" vs. "Setting Alarms with the RTC") might overlap across multiple categories. To keep it simple, each concept is listed once here.

+
    +
  1. Adapt as Needed
  2. +
+

This taxonomy is a starting point. You may decide to rename categories (e.g., split "Audio & Power Management" into two) or move individual concepts around to best match your teaching flow.

+

By defining these ten thematic groupings, you'll have a clear roadmap for both the sequence of lessons and the range of coverage in "Building Clocks and Watches with MicroPython."

+ + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/prompts/05-taxonomy-assignment/index.html b/prompts/05-taxonomy-assignment/index.html new file mode 100644 index 00000000..be5f5e11 --- /dev/null +++ b/prompts/05-taxonomy-assignment/index.html @@ -0,0 +1,854 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Taxonomy Assignment - MicroPython Clocks and Watches + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Taxonomy Assignment

+
+

Prompt

+

That is very good. Now please return a new CSV file that appends the TaxonomyID as the fourth column. The new CSV file you create now has the following format:

+
1
+2
+3
+4
+5
1. The first column in the CSV file is a numeric ConceptID
+ID you assigned in the CSV file above.
+2. The second column is the ConceptLabel from the CSV file above.
+3. The third column is a pipe-delimited Dependencies of the Concept IDs that the concept depends upon.
+4. The fourth column is the numeric TaxonomyID from the taxonomy you generated above.
+
+
+ + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/prompts/clocks-and-watches.csv b/prompts/clocks-and-watches.csv new file mode 100644 index 00000000..764b5083 --- /dev/null +++ b/prompts/clocks-and-watches.csv @@ -0,0 +1,116 @@ +ConceptID,ConceptLabel,Dependencies,TaxonomyID +0,Timekeeping Components,,1 +1,MicroPython,0,1 +2,Raspberry Pi Pico W,1,1 +3,Breadboard,0,1 +4,Power,0,1 +5,USB Power,4,1 +6,Battery Power,4,1 +7,Battery Backup,6,1 +8,Battery Drain Calculation,6,1 +9,LED Clock Displays,1,3 +10,Real-time Clocks,0,1 +11,The Thonny IDE,1,1 +12,Synchronizing Time from the PC,10|1|11,6 +13,The localtime() Function,1,2 +14,MicroPython Syntax,1,2 +15,Loops,14,2 +16,Conditionals,14,2 +17,Functions,14,2 +18,Libraries,14,2 +19,Loading MicroPython Drivers,18,4 +20,Printing Time to the Console,14,2 +21,Displays,0|19,3 +22,Drawing Lines,21,4 +23,Drawing Text,21,4 +24,Drawing Pixels,21,4 +25,Drawing Rectangles,21,4 +26,Drawing Circles,21,4 +27,Drawing Ellipses,21,4 +28,Drawing Arcs,21,4 +29,Drawing Polygons,21,4 +30,Changing Fonts,21,4 +31,Using MicroPython Drivers,19,4 +32,OLED Displays,21,3 +33,Smartwatch Displays,21,3 +34,Using 16 Bits to Represent Color,33,2 +35,Framebuffer in MicroPython,21,5 +36,SPI Interfaces,31,10 +37,Updating the Entire Screen via SPI,36,10 +38,Incremental Screen Drawing,37,10 +39,Drawing Hands on an Analog Clock,22|40|93|33,5 +40,Using math.sin() and math.cos(),14|33,5 +41,Drawing Tick Marks on a Watch Face,21|22|40,5 +42,Drawing Numbers on a Watch Face,21|23|40,5 +43,Loading Custom Fonts,30,4 +44,Using the time Module for Scheduling,14,10 +45,Using Internal Timers on the Raspberry Pi Pico,2|44,6 +46,External RTC Modules (DS1307 and DS3231),10|19,7 +47,Setting the System Clock from an External RTC,46,7 +48,Network Time Synchronization (NTP),50,6 +49,Setting Up WiFi on the Pico W,2|1,6 +50,Working the network module,49,6 +51,Parsing JSON,50,6 +52,Displaying Date and Day of Week,10,8 +53,Creating a 7-Segment Style Digital Display,21,3 +54,Custom Segment-Based Displays,53,3 +55,Minimizing Power Consumption (Sleep Modes),2|4,9 +56,Waking from Deep Sleep on RTC Alarm,55,9 +57,Setting Time with Buttons,58|59,8 +58,Using Interrupts for Button Input,14,8 +59,Button Debouncing Techniques,58,8 +60,Using A Rotary Encoder,58,8 +61,Setting Alarms with the RTC,10,8 +62,Connecting Speakers,2,9 +63,Playing a Sound,0|62,9 +64,Sound Encoding,63,9 +65,Timer-Based Events,44,6 +66,Playing Chimes,63|65,9 +67,Cuckoo Clocks,66,9 +68,Generating Beep or Alarm Tones,63,9 +69,Integrating External Sensors,19,7 +70,Temperature and Humidity,69,7 +71,Displaying Sensor Data,70|21,7 +72,Accelerometers for Orientation,69,7 +73,Tilt-Based Wake for Watch Display,72|55,7 +74,Ultra-Low Power,55,8 +75,Using E-Paper Displays,31,3 +76,Partial Refresh on E-Paper,75,3 +77,Techniques to Minimize Ghosting on E-Paper,76,3 +78,Using TFT Displays (e.g., ST7735, ILI9341),31,3 +79,Comparing Display Technologies,32|75|78,3 +80,Color Animations on Watch Faces,35|40,5 +81,Flicker Reduction Techniques,80,5 +82,Double Buffering for Smooth Animations,80,5 +83,Using the micropython-ufont Library,18,5 +84,Asynchronous Code with uasyncio,1|14,6 +85,Time-Based Tasks with uasyncio,84,6 +86,Handling Concurrency of Time Updates & UI,85|44,6 +87,Using a Watchdog Timer for Reliability,45,9 +88,Logging & Debugging Time Issues,44,9 +89,Sleep Scheduling,55,9 +90,Tools for Measuring Battery Usage,8,7 +91,Hardware Documentation,,1 +92,3D Printing Watch Enclosures,91,10 +93,Using math Module for Geometry,14,2 +94,Time Zones & UTC Offsets,44,2 +95,Daylight Saving Time,94,2 +96,Using the Built-In Filesystem on Pico,2|14,8 +97,Logging Time Data to an SD Card,96|44,8 +98,Using I²C Sensors & Modules,69,7 +99,Menu Systems for Watch Settings,14,8 +100,Servo Motors for Mechanical Pointers,2|19,7 +101,Converting Between datetime Objects and Timestamps,44,2 +102,Generating Waveforms for Audio,40|63,9 +103,Implementing Clock Themes,21,3 +104,Fast Redraw Techniques (Blitting),35,5 +105,Partial Screen Updates for Watch Faces,104,5 +106,Color Conversion & Palettes in MicroPython,21|34,5 +107,Handling 12-Hour vs. 24-Hour Formats,44,8 +108,WiFi-Based Weather & Forecast Updates,50|51,6 +109,Using Sensors for Step Counting (Pedometer),72,7 +110,Adding a LiPo Charging Circuit,6,7 +111,Chronograph Functionality,44,8 +112,Timer,44,10 +113,Stopwatch,44,10 +114,World Clocks,94,6 diff --git a/prompts/concept-dependency.csv b/prompts/concept-dependency.csv new file mode 100644 index 00000000..98d0f4ed --- /dev/null +++ b/prompts/concept-dependency.csv @@ -0,0 +1,116 @@ +ConceptID,ConceptLabel,DependsOn +0,Timekeeping Components +1,MicroPython,0 +2,Raspberry Pi Pico W,1 +3,Breadboard,0 +4,Power,0 +5,USB Power,4 +6,Battery Power,4 +7,Battery Backup,6 +8,Battery Drain Calculation,6 +9,LED Clock Displays,1 +10,Real-time Clocks,0 +11,The Thonny IDE,1 +12,Synchronizing Time from the PC,10|1|11 +13,The localtime() Function,1 +14,MicroPython Syntax,1 +15,Loops,14 +16,Conditionals,14 +17,Functions,14 +18,Libraries,14 +19,Loading MicroPython Drivers,18 +20,Printing Time to the Console,14 +21,Displays,0.19 +22,Drawing Lines,21 +23,Drawing Text,21 +24,Drawing Pixels,21 +25,Drawing Rectangles,21 +26,Drawing Circles,21 +27,Drawing Ellipses,21 +28,Drawing Arcs,21 +29,Drawing Polygons,21 +30,Changing Fonts,21 +31,Using MicroPython Drivers,19 +32,OLED Displays,21 +33,Smartwatch Displays,21 +34,Using 16 Bits to Represent Color,33 +35,Framebuffer in MicroPython,21 +36,SPI Interfaces,31 +37,Updating the Entire Screen via SPI,36 +38,Incremental Screen Drawing,37 +39,Drawing Hands on an Analog Clock,22|40|93|33 +40,Using math.sin() and math.cos(),14|33 +41,Drawing Tick Marks on a Watch Face,21|22|40 +42,Drawing Numbers on a Watch Face,21|23|40 +43,Loading Custom Fonts,30 +44,Using the time Module for Scheduling,14 +45,Using Internal Timers on the Raspberry Pi Pico,2|44 +46,External RTC Modules (DS1307 and DS3231),10|19 +47,Setting the System Clock from an External RTC,46 +48,Network Time Synchronization (NTP),50 +49,Setting Up WiFi on the Pico W,2|1 +50,Working the network module,49 +51,Parsing JSON,50 +52,Displaying Date and Day of Week,10 +53,Creating a 7-Segment Style Digital Display,21 +54,Custom Segment-Based Displays,53 +55,Minimizing Power Consumption (Sleep Modes),2|4 +56,Waking from Deep Sleep on RTC Alarm,55 +57,Setting Time with Buttons,58|59 +58,Using Interrupts for Button Input,14 +59,Button Debouncing Techniques,58 +60,Using A Rotary Encoder,58 +61,Setting Alarms with the RTC,10 +62,Connecting Speakers,2 +63,Playing a Sound,0|62 +64,Sound Encoding,63 +65,Timer-Based Events,44 +66,Playing Chimes,63|65 +67,Cuckoo Clocks,66 +68,Generating Beep or Alarm Tones,63 +69,Integrating External Sensors,19 +70,Temperature and Humidity,69 +71,Displaying Sensor Data,70|21 +72,Accelerometers for Orientation,69 +73,Tilt-Based Wake for Watch Display,72|55 +74,Ultra-Low Power,55 +75,Using E-Paper Displays,31 +76,Partial Refresh on E-Paper,75 +77,Techniques to Minimize Ghosting on E-Paper,76 +78,Using TFT Displays (e.g., ST7735, ILI9341),31 +79,Comparing Display Technologies,32|75|78 +80,Color Animations on Watch Faces,35|40 +81,Flicker Reduction Techniques,80 +82,Double Buffering for Smooth Animations,80 +83,Using the micropython-ufont Library,18 +84,Asynchronous Code with uasyncio,1|14 +85,Time-Based Tasks with uasyncio,84 +86,Handling Concurrency of Time Updates & UI,85|44 +87,Using a Watchdog Timer for Reliability,45 +88,Logging & Debugging Time Issues,44 +89,Sleep Scheduling,55 +90,Tools for Measuring Battery Usage,8 +91,Hardware Documentation, +92,3D Printing Watch Enclosures,91 +93,Using math Module for Geometry,14 +94,Time Zones & UTC Offsets,44 +95,Daylight Saving Time,94 +96,Using the Built-In Filesystem on Pico,2|14 +97,Logging Time Data to an SD Card,96|44 +98,Using I²C Sensors & Modules,69 +99,Menu Systems for Watch Settings,14 +100,Servo Motors for Mechanical Pointers,2|19 +101,Converting Between datetime Objects and Timestamps,44 +102,Generating Waveforms for Audio,40|63 +103,Implementing Clock Themes,21 +104,Fast Redraw Techniques (Blitting),35 +105,Partial Screen Updates for Watch Faces,104 +106,Color Conversion & Palettes in MicroPython,21|34 +107,Handling 12-Hour vs. 24-Hour Formats,44 +108,WiFi-Based Weather & Forecast Updates,50|51 +109,Using Sensors for Step Counting (Pedometer),72 +110,Adding a LiPo Charging Circuit,6 +111,Chronograph Functionality,44 +112,Timer,44 +113,Stopwatch,44 +114,World Clocks,94 diff --git a/references/index.html b/references/index.html index b6d1d115..5613f907 100644 --- a/references/index.html +++ b/references/index.html @@ -511,6 +511,10 @@ + + + + diff --git a/search/search_index.json b/search/search_index.json index ac72228a..64988935 100644 --- a/search/search_index.json +++ b/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"MicroPython Clocks and Watches","text":"

Welcome to the MicroPython Clocks and Watches website.

"},{"location":"#our-mission","title":"Our Mission","text":"

The mission of this website is to serve as a resource for teachers, mentors and students who want to learn how to use the popular MicroPython language to enable high-school students to design their own digital clocks and watches using Python. This website contains instructions for finding low-cost parts (under $20/student) and customizing your custom clock and watch displays.

These projects have only recently been possible due to the following developments:

  1. The 2021 release of the powerful $4 Raspberry Pi Pico microcontroller.
  2. The availability of low-cost high-contrast OLED and TFT displays for as low as $7 each. When we use the $4 Raspberry Pi Pico our classrooms can create a development system for under $15.
  3. The support of highly optimized drawing of complex ellipse and polygon shapes into the Framebuf by the MicroPython runtime. This only became available in version 20 of the standard MicroPython runtime released in April of 2023. Before this every clock and watch project used custom math functions that slowed drawing times.
  4. The ability of WiFi-enabled microcontrollers that can synchronize with centralized time services using standard WiFi networks. We use the $5 Raspberry Pi Pico W in many of our projects.

Our goal is to provide fun ways to teach computational thinking to a wide variety of students from 6th to 12th grade. If you can type we have fun lesson plans from drawing simple shapes to complex clock and watch face designs.

"},{"location":"#acknowledgments","title":"Acknowledgments","text":"

I want to thank everyone in the MicroPython community for sharing their code. Each of the displays requires MicroPython drivers that have special features to keep drawing fast. We could not have high-quality lesson plans without your contributions. If I have not referenced the cool features of your drivers, please let me know!

"},{"location":"#feedback-and-comments","title":"Feedback and Comments","text":"

If you have any comments or feedback, please feel free to post these to our GitHub Issues. I don't check these issues every day, so please be patient and connect with others in the MicroPython community if you have urgent questions for a classroom.

Good Luck! Dan McCreary on LinkedIn

Demo code for the Waveshare RP2040 watch display.

Waveshare RP2040-LCD-1.28

Ebay Listing for $21

"},{"location":"about/","title":"About the MicroPython Clocks and Watchs Site","text":"

My STEM students often hold a special facination with timekeeping. I don't know exactly what it is that gets them so excited about these projects. But the bottom line is they quickly become motivated to learn Python to create their own timekeeping devices that are customized to their interests. Owning their own devices and showing off these projects to their friends is also a big plus.

At the core of Project Based Learning (PBL) is the desire to allow students to pick their own projects. My role as a mentor is to have a rich set of projects they can get started on. They may not understand all the components, but to fix and modify the projects they end up learning faster than if I gave them a dozen lectures with PowerPoint.

"},{"location":"about/#the-timeless-allure-of-timekeeping-sparking-a-passion-for-coding-with-micropython-clocks-and-watches","title":"The Timeless Allure of Timekeeping: Sparking a Passion for Coding with MicroPython Clocks and Watches","text":"

In a world driven by digital innovation, the essence of time remains universally compelling. From the ancient sundials to the modern-day smartwatches, humans have always been fascinated by the pursuit of measuring time. This intrigue, particularly among the younger generation, is why a library of fun MicroPython Clocks and Watches serves as an exceptional entry point into the universe of coding and computational thinking.

"},{"location":"about/#tapping-into-the-childs-natural-curiosity","title":"Tapping into the Child's Natural Curiosity","text":"

Children possess an innate sense of wonder. The ticking of a clock or the changing digits on a digital watch captivates their imagination. They question how it works, what makes it tick, and how they can possibly create one of their own. By introducing them to a collection of engaging MicroPython timekeeping projects, we are not only answering their questions but also kindling a flame of creativity and innovation.

"},{"location":"about/#from-boring-lectures-to-tangible-results","title":"From Boring Lectures to Tangible Results","text":"

Traditional pedagogical methods, characterized by long hours of lectures, often fail to hold the attention of young learners. However, project-based learning flips this paradigm. When a child is given the tools and guidance to create their very own digital clock or watch, the learning process transforms. They're no longer passive recipients of information but active creators, brimming with ideas and enthusiasm.

A MicroPython Clock project, for instance, isn\u2019t just a coding endeavor; it's a journey. From understanding the concept, drafting the design, writing the first line of code, to finally witnessing their creation come to life \u2013 every step is a lesson in problem-solving, critical thinking, and perseverance.

"},{"location":"about/#building-blocks-of-computational-thinking","title":"Building Blocks of Computational Thinking","text":"

Computational thinking is a cornerstone of modern education. It\u2019s not just about writing code, but a way of approaching problems logically, breaking them down into smaller tasks, and finding efficient solutions. By creating a timekeeping device, children undergo a practical exercise in computational thinking. They decide the features they want, debug issues, and constantly refine their work, all while having fun!

"},{"location":"about/#social-construction-the-joy-of-shared-creation","title":"Social Construction: The Joy of Shared Creation","text":"

Imagine a part of every school library or a display case where clocks and watches are on display. Each devices can tell its unique story, representing a young coder's journey. Sharing these creations allows children to learn from each other, celebrate their achievements, and draw inspiration. Such a collaborative environment fosters not just coding skills but also empathy, teamwork, and a sense of community. Technically, we call group building projects Social constructivism. And there is ample research to show that students that learn in groups have higer participation rates and can usually develop better outcomes.

"},{"location":"about/#engineered-for-user-experience","title":"Engineered for User Experience","text":"

Great STEM projects don't just fall off a shelf into our laps. They need to be designed and tested carefully using the same agile processes that we use in software development. User Experience (UX) professionals need to help us observe our students using and learning from our lessons.

"},{"location":"about/#in-conclusion","title":"In Conclusion","text":"

A library of MicroPython Clocks and Watches isn't just a collection of projects; it\u2019s a treasure trove of experiences, learnings, and memories. By introducing children to the magic of timekeeping in the digital age, we are equipping them with skills for the future, fostering a love for coding, and most importantly, reminding them that learning can be both fun and profoundly meaningful. The timeless allure of time, coupled with the dynamic world of coding, creates a perfect blend to mold the innovators and thinkers of tomorrow.

"},{"location":"contact/","title":"Contact","text":"

Dan McCreary on LinkedIn

"},{"location":"course-description/","title":"Course Description","text":"

Course Title: Building Clocks and Watches with MicroPython Duration: 14 Weeks Target Audience: High School Students Learning Computational Thinking

Course Description: In this 14-week, hands-on course, high school students will learn to design and program functional timekeeping devices---from simple LED clocks to sophisticated stopwatches and web-connected displays---using MicroPython on the Raspberry Pi Pico W. Throughout the course, students will be guided by hands on labs, moving from foundational knowledge to creative invention.

  1. Remember: Students begin by identifying essential electronic components---breadboards, microcontrollers, buttons, knobs, and displays---and recalling the fundamental concepts of MicroPython programming.
  2. Understand: Through teacher-led demonstrations, students explore how timing functions, input/output operations, and hardware connections work together to create a basic LED clock. They deepen their grasp of computational thinking by examining concepts like abstraction (focusing on relevant details) and decomposition (breaking large problems into smaller parts).
  3. Apply: As their skills grow, students practice wiring and programming a 128\u00d764 OLED display, interfacing with web services to fetch real-time data. They also implement stopwatches and timers, applying algorithms and pattern-matching techniques to manage user inputs and display outputs accurately.
  4. Analyze: Learners diagnose and troubleshoot various clock behaviors, studying how code efficiency and hardware choices influence device performance. They dissect timing errors, lag issues, and integration problems, refining both their programming and problem-solving skills.
  5. Evaluate: Working in teams, students test multiple clock designs to compare accuracy, power consumption, and user experience. They assess trade-offs---such as display readability vs. complexity---and refine solutions based on feedback, cost, and practicality.
  6. Create: Ultimately, students design and assemble their own custom timekeeping projects, combining LEDs, OLED displays, push buttons, and encoders. This final challenge showcases their ability to unify hardware and code into a polished, functioning prototype.

By the end of the course, students will have built a diverse collection of digital clocks, stopwatches, and timers while gaining a rich understanding of electronics, computational thinking, and MicroPython. They will leave empowered to continue exploring the world of embedded systems and creative hardware projects.

"},{"location":"faq/","title":"STEM Robots FAQ","text":""},{"location":"faq/#can-we-print-our-own-chassis-with-a-3d-printer","title":"Can we print our own chassis with a 3D Printer?","text":"

Yes!

"},{"location":"references/","title":"References","text":"
  1. watchmaker - custom watch faces
  2. Makerfabs GC9A01 1.28 Inch Round LCD Module
  3. Dronebotworkshop Using GC9A01 Round LCD Modules
  4. DroneBot Workshop YouTube Video
  5. Mr. Volt YouTube Exotic Round Displays and How to Use Them - this videos demonstrates some very new round displays up to 5 inches. These would be ideal for robot faces but they cost up to $200 for the larger displays.
  6. NTP Pool Project - sites that run the network time protocol
  7. Pico Displays and Fonts
  8. Analog Clock Face Generator - a wonderful way to try out different designs. This web app allows you to change the format of the markers, digits and hands.
  9. Blocklayer Clock Face
  10. Mick Make's YouTube Video: Build a talking Alarm Clock synced to Google calendars - great example of how to use the Google API to access your personal calendar.
"},{"location":"references/#inspirational-electronic-clocks","title":"Inspirational Electronic Clocks","text":"

\"The Clock\" using 1,916 parts by Gislain Benoit

Divoom Times Gate Clock - Five 128x128 color LCD displays.

ClocTech Tube Shaped Nixie Clock available for $49 at WalMart. This clock shines an LED into a Plexiglass plate to give the appearance of a 3D Nixie Tube.

"},{"location":"kits/","title":"Introduction to Clock and Watch Kits","text":""},{"location":"kits/#standard-oled","title":"Standard OLED","text":""},{"location":"kits/#raspberry-pi-and-monchorme-oled","title":"Raspberry Pi and Monchorme OLED","text":""},{"location":"kits/#raspberry-pi-w-and-monchorme-oled","title":"Raspberry Pi W and Monchorme OLED","text":""},{"location":"kits/#lilygo-rp240","title":"LilyGo RP240","text":""},{"location":"kits/gc9a01/","title":"GC9A01 Display","text":"

The gc9a01 is a chip that drives a 240x240 round display that is connected to a microcontroller by an SPI display.

The current gc9a01 drivers are not compatable with the current release of framebuf in the standard MicroPython runtime. Therefore the standard framebuf functions such as ellipse() and poly() functions do not work. This limits the portability of many of or clock and watch example.

The good news is that you can do some drawing operations faster and your code does not have to run the show() command. Functions such as line() will draw directly to the display.

To connect we need to either use a firmware version or load the driver into the /lib directory and we can then use the following code:

from machine import Pin, SPI\nimport gc9a01 as gc9a01\n\n# hardware config\nSCL_PIN = 2\nSDA_PIN = 3\nDC_PIN = 4\nCS_PIN = 5\nRST_PIN = 6\nspi = SPI(0, baudrate=60000000, sck=Pin(SCL_PIN), mosi=Pin(SDA_PIN))\n\n# initialize the display\ntft = gc9a01.GC9A01(\n    spi,\n    dc=Pin(DC_PIN, Pin.OUT),\n    cs=Pin(CS_PIN, Pin.OUT),\n    reset=Pin(RST_PIN, Pin.OUT),\n    rotation=0)\n\ntft.fill(gc9a01.BLACK)\n\n# x, y, width, height\n# red\ntft.fill_rect(50,  75, 50, 60, gc9a01.color565(255,0,0))\n# green\ntft.fill_rect(100, 75, 50, 60, gc9a01.color565(0,255,0))\n# blue\ntft.fill_rect(150, 75, 50, 60, gc9a01.color565(0,0,255))\n
"},{"location":"kits/gc9a01/#rotation","title":"Rotation","text":"

The driver supports 8 different types of rotations:

In our labs we have the connector at the bottom so we use the Portrait rotation of 0 which is the default rotation.

"},{"location":"kits/gc9a01/#references","title":"References","text":"
  1. Russ Hughes - Russ provides firmware images that you can use for both the Pico and Pico W.
    1. Raspberry Pi Pico
    2. Raspberry Pi Pico W
"},{"location":"kits/gc9a01/01-hello-world/","title":"Hello World Test","text":"

We begin by carefully setting up the 7 wires that connect from the gc9a01 graphics controller to the breadboard that we have our Raspberry Pi Pico W connected.

# 01-hello-world-firmware.py\n# \nfrom machine import Pin, SPI\nimport gc9a01\nimport vga1_bold_16x32 as font\n\n# this uses the standard Dupont ribbon cable spanning rows 4-9 on our breadboard\nSCK_PIN = 2 # row 4\nSDA_PIN = 3\nDC_PIN = 4\nCS_PIN = 5\n# GND is row 8\nRST_PIN = 6\n\n# define the SPI intrface\nspi = SPI(0, baudrate=60000000, sck=Pin(SCK_PIN), mosi=Pin(SDA_PIN))\ntft = gc9a01.GC9A01(spi, 240, 240, reset=Pin(RST_PIN, Pin.OUT),\n    cs=Pin(CS_PIN, Pin.OUT), dc=Pin(DC_PIN, Pin.OUT), rotation=0\n)\n\ntft.init()\ntft.fill(0) # fill the screen with black\ntft.text(font, \"Hello world!\", 20, 100, gc9a01.color565(255,0,0), gc9a01.color565(0,0,255))\n
"},{"location":"kits/gc9a01/02-display-time/","title":"Display Time and Date","text":"

Here is a program that will display the time and date including the day-of-the-week and the name of the month.

We use the ``localtime()``` function to get the current time in an array of integers that looks like this:

(2023, 9, 30, 13, 18, 9, 5, 273)\n

The fields are:

  1. Year
  2. Month
  3. Day of Month
  4. Hour in 24-hour format
  5. Minutes
  6. Seconds
  7. Day of Week (Monday = 0, Sunday = 6)
  8. Day of the Year

We could convert each of these numbers to strings and display them. However, most people like to do a bit of formatting such as displaing a 12-hour am/pm format and returning the name of the month and day of the week. Here is the complete progam with the formatting.

# 02-display-time.py\n# \nfrom machine import Pin, SPI\nfrom utime import sleep, localtime\nimport gc9a01\nimport vga1_bold_16x32 as font\n\n# this uses the standard Dupont ribbon cable spanning rows 4-9 on our breadboard\nSCK_PIN = 2 # row 4\nSDA_PIN = 3\nDC_PIN = 4\nCS_PIN = 5\n# GND is row 8\nRST_PIN = 6\n\n# define the SPI intrface\nspi = SPI(0, baudrate=60000000, sck=Pin(SCK_PIN), mosi=Pin(SDA_PIN))\ntft = gc9a01.GC9A01(spi, 240, 240, reset=Pin(RST_PIN, Pin.OUT),\n    cs=Pin(CS_PIN, Pin.OUT), dc=Pin(DC_PIN, Pin.OUT), rotation=0\n)\n\ntft.init()\ntft.fill(0) # fill the screen with black\n\ndays = ('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday',\n        'Sunday')\n# I limit the month names to 5 characters max\nmonths = ('Jan', 'Feb', 'March', 'April', 'May', 'June', 'July',\n          'Aug', 'Sept', 'Oct', 'Nov', 'Dec')\nlabel = ('year', 'month', 'mday', 'hour', 'minute', 'second', 'weekday', 'yearday')\n\ndef format_time(now):\n    hour_number = now[3]\n    if hour_number < 13:\n        hour_12 = hour_number\n        am_pm = 'am'\n    else:\n        hour_12 = hour_number - 12\n        am_pm = 'pm'\n    minutes_num = now[4]\n    if minutes_num < 10:\n        min_leading_zero = '0'\n    else:\n        min_leading_zero = ''\n    seconds_num = now[5]\n    if seconds_num < 10:\n        sec_leading_zero = '0'\n    else:\n        sec_leading_zero = ''\n    return \"{}:{}{}:{}{} {}\".format(hour_12, min_leading_zero, minutes_num, sec_leading_zero, seconds_num, am_pm)\n\ndef day_of_week(now):\n    weekday_number = now[6]\n    day_name = days[weekday_number]\n    return day_name\n\ndef format_date(now):\n    month_number = now[1]\n    month_name = months[month_number - 1]\n    weekday_number = now[6]\n    day_name = days[weekday_number]\n    hour_number = now[3]\n    return \"{} {}, {}\".format(month_name, now[2], now[0])\n\ntft.fill(0) # erase the screen to black\nwhite = gc9a01.color565(255, 255, 255)\nwhile(True):\n    # get the time from the local real-time clock\n    now = localtime()\n    print(now)    \n    tft.text(font, format_time(now), 35, 50, white)\n    tft.text(font, day_of_week(now), 50, 80, white)\n    tft.text(font, format_date(now), 5, 110, white)\n    #tft.show()\n    sleep(1)\n
"},{"location":"kits/gc9a01/03-display-hand/","title":"Display Second Hand","text":"

In this lesson we will use a bit of trigonometry to display a second hand that ticks every second.

from machine import Pin, SPI\nfrom utime import sleep, localtime\nimport math\nimport gc9a01\n\n# this uses the standard Dupont ribbon cable spanning rows 4-9 on our breadboard\nSCK_PIN = 2 # row 4\nSDA_PIN = 3\nDC_PIN = 4\nCS_PIN = 5\n# GND is row 8\nRST_PIN = 6\n\n# define the SPI intrface\nspi = SPI(0, baudrate=60000000, sck=Pin(SCK_PIN), mosi=Pin(SDA_PIN))\ntft = gc9a01.GC9A01(spi, 240, 240, reset=Pin(RST_PIN, Pin.OUT),\n    cs=Pin(CS_PIN, Pin.OUT), dc=Pin(DC_PIN, Pin.OUT), rotation=0\n)\ntft.init()\n\nCENTER = 120\nHAND_LENGTH = 100\n\n# our counter will range from 0 to 59\n# A full circle is 2*Pi radians\nTWO_PI = 3.145175*2\ncounter = 0\nwhite = gc9a01.color565(255, 255, 255)\n\nwhile True:\n    radians = (counter/60)*TWO_PI\n    x = int(math.sin(radians)*HAND_LENGTH)\n    y = -int(math.cos(radians)*HAND_LENGTH)\n    print(radians, x, y)\n    tft.fill(0)\n    tft.line(CENTER, CENTER, CENTER+x,CENTER+y, white)\n    sleep(1)\n    counter += 1\n    # if we are at 60 we start over\n    if counter > 59:\n        counter = 0\n
"},{"location":"kits/gc9a01/04-cycle-fonts/","title":"Cycle Through the Builtin Fonts","text":"

This program will cycle through the built-in fonts showing greetings in different fonts, langauges and colors.

# Adapted from the hersey.py program\nfrom machine import Pin, SPI\nfrom utime import sleep, localtime\nimport random\nimport gc9a01\nimport vga1_bold_16x32 as font\n\n# this uses the standard Dupont ribbon cable spanning rows 4-9 on our breadboard\nSCK_PIN = 2 # row 4\nSDA_PIN = 3\nDC_PIN = 4\nCS_PIN = 5\n# GND is row 8\nRST_PIN = 6\n\n# define the SPI intrface\nspi = SPI(0, baudrate=60000000, sck=Pin(SCK_PIN), mosi=Pin(SDA_PIN))\ntft = gc9a01.GC9A01(spi, 240, 240, reset=Pin(RST_PIN, Pin.OUT),\n    cs=Pin(CS_PIN, Pin.OUT), dc=Pin(DC_PIN, Pin.OUT), rotation=0)\n\n# Load several frozen fonts from flash\n\nimport greeks\nimport italicc\nimport italiccs\nimport meteo\nimport romanc\nimport romancs\nimport romand\nimport romanp\nimport romans\nimport scriptc\nimport scripts\n\n\ndef cycle(p):\n    '''\n    return the next item in a list\n    '''\n    try:\n        len(p)\n    except TypeError:\n        cache = []\n        for i in p:\n            yield i\n            cache.append(i)\n        p = cache\n    while p:\n        yield from p\n\n\nCOLORS = cycle([0xe000, 0xece0, 0xe7e0, 0x5e0, 0x00d3, 0x7030])\n\nFONTS = cycle([\n    greeks, italicc, italiccs, meteo, romanc, romancs,\n    romand, romanp, romans, scriptc, scripts])\n\nGREETINGS = cycle([\n    \"bonjour\", \"buenas noches\", \"buenos dias\",\n    \"good day\", \"good morning\", \"hey\",\n    \"hi-ya\", \"hi\", \"how are you\", \"how goes it\",\n    \"howdy-do\", \"howdy\", \"shalom\", \"welcome\",\n    \"what's happening\", \"what's up\"])\n\ntft.init()\nrow = 120\n\nwhile True:\n    color = next(COLORS)\n    tft.fill(gc9a01.BLACK)\n    tft.draw(next(FONTS), next(GREETINGS), 0, row, color)\n    sleep(0.5)\n
"},{"location":"kits/gc9a01/04-cycle-fonts/#references","title":"References","text":"
  1. Russ Hughes Example
"},{"location":"kits/gc9a01/05-draw-ticks/","title":"Draw Clock Face Ticks","text":"

This program draws 12 short white radial lines around a watch face.

We use the same math we used to draw a single hand. However, in this example we just need to iterate through a range of 0 to 12.

for i in range(0,12):\n    radians = (i/12)*TWO_PI\n    x1 = int(math.sin(radians)*TICK_START)\n    y1 = -int(math.cos(radians)*TICK_START)\n    x2 = int(math.sin(radians)*TICK_END)\n    y2 = -int(math.cos(radians)*TICK_END)\n    tft.line(CENTER+x1, CENTER+y1, CENTER+x2, CENTER+y2, WHITE)\n

Instead of just drawing from the center to a point at (x,y), we need to calculate four numbers. These are the start and ending points of each tick.

"},{"location":"kits/gc9a01/05-draw-ticks/#full-program","title":"Full Program","text":"
from machine import Pin, SPI\nfrom utime import sleep, localtime\nimport math\nimport gc9a01\n\n# this uses the standard Dupont ribbon cable spanning rows 4-9 on our breadboard\nSCK_PIN = 2 # row 4\nSDA_PIN = 3\nDC_PIN = 4\nCS_PIN = 5\n# GND is row 8\nRST_PIN = 6\n\n# define the SPI intrface\nspi = SPI(0, baudrate=60000000, sck=Pin(SCK_PIN), mosi=Pin(SDA_PIN))\ntft = gc9a01.GC9A01(spi, 240, 240, reset=Pin(RST_PIN, Pin.OUT),\n    cs=Pin(CS_PIN, Pin.OUT), dc=Pin(DC_PIN, Pin.OUT), rotation=0\n)\ntft.init()\n\nCENTER = 120\nTICK_START = 80\nTICK_END = 100\n\n# our counter will range from 0 to 59\n# A full circle is 2*Pi radians\nTWO_PI = 3.145175*2\ncounter = 0\nWHITE = gc9a01.color565(255, 255, 255)\nBLACK = gc9a01.color565(0, 0, 0)\ntft.fill(BLACK)\nfor i in range(0,12):\n    radians = (i/12)*TWO_PI\n    x1 = int(math.sin(radians)*TICK_START)\n    y1 = -int(math.cos(radians)*TICK_START)\n    x2 = int(math.sin(radians)*TICK_END)\n    y2 = -int(math.cos(radians)*TICK_END)\n    print(i, radians, x1, y1, x2, y2)  \n    tft.line(CENTER+x1, CENTER+y1, CENTER+x2, CENTER+y2, WHITE)\n
"},{"location":"kits/gc9a01/07-draw-12-digits/","title":"Draw Clock Dial Digits","text":"

This program builds on the previous progam that draws the ticks. It also draws a ditit at the end of each tick.

We use the same math we used to draw a single hand. However, in this example we just need to iterate through a range of 0 to 12.

We have to remember to load our 16x32 fonts. Although this is a bit large for most watch faces, they are easy to read.

Here is the line we added:

import vga1_bold_16x32 as font\n

Note that there are other sizes we can use. A list of the other fonts is here:

GitHub Site for Russ Hughes for GC9a01 bitmap fonts

These fonts include the following sizes:

  1. 8x8
  2. 8x16
  3. 16x16
  4. 16x32

Here is the appropriate line for the normal 16x16:

import vga1_16x16 as font\n

if i == 0:\n    num_str = \"12\"\nelse:\n    num_str = str(i)\ntft.text(font, num_str, CENTER+x3-8, CENTER+y3-16, WHITE)\n
Note because the fonts are 16 wide and 32 high we move 8 to the left and 16 up.

"},{"location":"kits/gc9a01/07-draw-12-digits/#full-program","title":"Full Program","text":"
from machine import Pin, SPI\nfrom utime import sleep, localtime\nimport math\nimport gc9a01\nimport vga1_bold_16x32 as font\n\n\n# this uses the standard Dupont ribbon cable spanning rows 4-9 on our breadboard\nSCK_PIN = 2 # row 4\nSDA_PIN = 3\nDC_PIN = 4\nCS_PIN = 5\n# GND is row 8\nRST_PIN = 6\n\n# define the SPI intrface\nspi = SPI(0, baudrate=60000000, sck=Pin(SCK_PIN), mosi=Pin(SDA_PIN))\ntft = gc9a01.GC9A01(spi, 240, 240, reset=Pin(RST_PIN, Pin.OUT),\n    cs=Pin(CS_PIN, Pin.OUT), dc=Pin(DC_PIN, Pin.OUT), rotation=0\n)\ntft.init()\n\nCENTER = 120\nTICK_START = 50\nTICK_END = 80\nNUM_POS = 100\n\n# our counter will range from 0 to 59\n# A full circle is 2*Pi radians\nTWO_PI = 3.145175*2\ncounter = 0\nWHITE = gc9a01.color565(255, 255, 255)\nBLACK = gc9a01.color565(0, 0, 0)\nBLUE = gc9a01.color565(0, 0, 255)\ntft.fill(BLACK)\nfor i in range(0,12):\n    radians = (i/12)*TWO_PI\n    x1 = int(math.sin(radians)*TICK_START)\n    y1 = -int(math.cos(radians)*TICK_START)\n    x2 = int(math.sin(radians)*TICK_END)\n    y2 = -int(math.cos(radians)*TICK_END)\n    x3 = int(math.sin(radians)*NUM_POS)\n    y3 = -int(math.cos(radians)*NUM_POS)\n    print(i, radians, x1, y1, x2, y2)  \n    tft.line(CENTER+x1, CENTER+y1, CENTER+x2, CENTER+y2, BLUE)\n    if i == 0:\n        num_str = \"12\"\n    else:\n        num_str = str(i)\n    tft.text(font, num_str, CENTER+x3-8, CENTER+y3-16, WHITE)\n

If you use the 16x16 font the offset in the last line are:

    if i == 0:\n        num_str = \"12\"\n        xOffest = 16\n    else:\n        num_str = str(i)\n        xOffest = 8\n    tft.text(font, num_str, CENTER+x3-xOffest, CENTER+y3-8, WHITE)\n

The exception is for the \"12\" at the top of the dial. The \"12\" needed to be exactly centered above the top tick mark.

"},{"location":"kits/gc9a01/07-draw-12-digits/#full-code-for-16x16-clock-face-digits","title":"Full Code for 16x16 Clock Face Digits","text":"
from machine import Pin, SPI\nfrom utime import sleep, localtime\nimport math\nimport gc9a01\nimport vga1_16x16 as font\n\n\n# this uses the standard Dupont ribbon cable spanning rows 4-9 on our breadboard\nSCK_PIN = 2 # row 4\nSDA_PIN = 3\nDC_PIN = 4\nCS_PIN = 5\n# GND is row 8\nRST_PIN = 6\n\n# define the SPI intrface\nspi = SPI(0, baudrate=60000000, sck=Pin(SCK_PIN), mosi=Pin(SDA_PIN))\ntft = gc9a01.GC9A01(spi, 240, 240, reset=Pin(RST_PIN, Pin.OUT),\n    cs=Pin(CS_PIN, Pin.OUT), dc=Pin(DC_PIN, Pin.OUT), rotation=0\n)\ntft.init()\n\nCENTER = 120\nTICK_START = 70\nTICK_END = 100\nNUM_POS = 111\n\n# our counter will range from 0 to 59\n# A full circle is 2*Pi radians\nTWO_PI = 3.145175*2\ncounter = 0\nWHITE = gc9a01.color565(255, 255, 255)\nBLACK = gc9a01.color565(0, 0, 0)\nBLUE = gc9a01.color565(0, 0, 255)\ntft.fill(BLACK)\nfor i in range(0,12):\n    radians = (i/12)*TWO_PI\n    x1 = int(math.sin(radians)*TICK_START)\n    y1 = -int(math.cos(radians)*TICK_START)\n    x2 = int(math.sin(radians)*TICK_END)\n    y2 = -int(math.cos(radians)*TICK_END)\n    x3 = int(math.sin(radians)*NUM_POS)\n    y3 = -int(math.cos(radians)*NUM_POS)\n    print(i, radians, x1, y1, x2, y2)  \n    tft.line(CENTER+x1, CENTER+y1, CENTER+x2, CENTER+y2, BLUE)\n    if i == 0:\n        num_str = \"12\"\n        xOffest = 16\n    else:\n        num_str = str(i)\n        xOffest = 8\n    tft.text(font, num_str, CENTER+x3-xOffest, CENTER+y3-8, WHITE)\n
"},{"location":"kits/gc9a01/08-draw-filled-triangle/","title":"Draw a Filled Triangle","text":"

Analog clock hands can be draw with simple lines. But the display will be more pleasing if we use clock hands that are made of triangles.

If the framebuf functions were supported in the driver drawing a filled triangle would be easy. We would just put the three points in an array and call the poly() with a fill option.

However, the current driver does not support the framebuf functions. To overcome this limiation we will need to write our own function that will fill all the points in a triangle.

Our founction must take in the three points and a color and draw the traingle.

"},{"location":"kits/gc9a01/08-draw-filled-triangle/#draw-filled-triangle","title":"Draw Filled Triangle","text":"

Without going into too much detail, here is the algorithm we will use dto draw a filled triangle. Note that only the last line does drawing using the line() function. You will note that it must get the points in order before the main loops run. This version also checks for divide by zero errors.

def drawFilledTriangle(x1, y1, x2, y2, x3, y3, color=WHITE):\n    def swap(x, y):\n        return y, x\n\n    # get our points in order\n    if y1 > y2:\n        x1, x2 = swap(x1, x2)\n        y1, y2 = swap(y1, y2)\n    if y1 > y3:\n        x1, x3 = swap(x1, x3)\n        y1, y3 = swap(y1, y3)\n    if y2 > y3:\n        x2, x3 = swap(x2, x3)\n        y2, y3 = swap(y2, y3)\n\n    for y in range(y1, y3+1):\n        if y2 - y1 != 0 and y < y2:\n            xa = x1 + (x2 - x1) * (y - y1) // (y2 - y1)\n        elif y3 - y1 != 0:\n            xa = x1 + (x3 - x1) * (y - y1) // (y3 - y1)\n        else:\n            continue\n\n        if y3 - y2 != 0 and y >= y2:\n            xb = x2 + (x3 - x2) * (y - y2) // (y3 - y2)\n        elif y3 - y1 != 0:\n            xb = x1 + (x3 - x1) * (y - y1) // (y3 - y1)\n        else:\n            continue\n\n        if xa > xb:\n            xa, xb = swap(xa, xb)\n\n        for x in range(xa, xb+1):\n            # sleep(.1)\n            tft.line(x, y, x, y, color)\n
"},{"location":"kits/gc9a01/08-draw-filled-triangle/#full-test-program","title":"Full Test Program","text":"

To test our alforithm we can genrate three random points near the center of the display and then call the traingle fill on these points. If you would like to see how the algorithm does the drawing, you can uncomment the sleep function just before the tft.line() above.

# 01-display-test.py\n# \nfrom machine import Pin, SPI\nimport random\nimport gc9a01\nimport vga1_16x16 as font\nfrom utime import sleep\n\n# this uses the standard Dupont ribbon cable spanning rows 4-9 on our breadboard\nSCK_PIN = 2 # row 4\nSDA_PIN = 3\nDC_PIN = 4\nCS_PIN = 5\n# GND is row 8\nRST_PIN = 6\n\n# define the SPI intrface\nspi = SPI(0, baudrate=60000000, sck=Pin(SCK_PIN), mosi=Pin(SDA_PIN))\ntft = gc9a01.GC9A01(spi, 240, 240, reset=Pin(RST_PIN, Pin.OUT),\n    cs=Pin(CS_PIN, Pin.OUT), dc=Pin(DC_PIN, Pin.OUT), rotation=0\n)\n\nWHITE = gc9a01.color565(255,255,255)\nBLUE = gc9a01.color565(0,0,255)\nBLACK = gc9a01.color565(0,0,0)\n\ntft.init()\ntft.fill(0) # fill the screen with black\ntft.text(font, \"Draw Filled Triangle Test\", 20, 10, BLUE, BLACK)\n\n\n\ndef drawFilledTriangle(x1, y1, x2, y2, x3, y3, color=WHITE):\n    def swap(x, y):\n        return y, x\n\n    if y1 > y2:\n        x1, x2 = swap(x1, x2)\n        y1, y2 = swap(y1, y2)\n    if y1 > y3:\n        x1, x3 = swap(x1, x3)\n        y1, y3 = swap(y1, y3)\n    if y2 > y3:\n        x2, x3 = swap(x2, x3)\n        y2, y3 = swap(y2, y3)\n\n    for y in range(y1, y3+1):\n        if y2 - y1 != 0 and y < y2:\n            xa = x1 + (x2 - x1) * (y - y1) // (y2 - y1)\n        elif y3 - y1 != 0:\n            xa = x1 + (x3 - x1) * (y - y1) // (y3 - y1)\n        else:\n            continue\n\n        if y3 - y2 != 0 and y >= y2:\n            xb = x2 + (x3 - x2) * (y - y2) // (y3 - y2)\n        elif y3 - y1 != 0:\n            xb = x1 + (x3 - x1) * (y - y1) // (y3 - y1)\n        else:\n            continue\n\n        if xa > xb:\n            xa, xb = swap(xa, xb)\n\n        for x in range(xa, xb+1):\n            tft.line(x, y, x, y, color)\n\n\nmin = 50\nmax = 150\nwhile True:\n    tft.fill(BLACK)\n    x1 = random.randint(min, max)\n    y1 = random.randint(min, max)\n    x2 = random.randint(min, max)\n    y2 = random.randint(min, max)\n    x3 = random.randint(min, max)\n    y3 = random.randint(min, max)\n    drawFilledTriangle(x1, y1, x2, y2, x3, y3, WHITE)\n    sleep(.1)\n
"},{"location":"kits/gc9a01/08-draw-filled-triangle/#crazy-triangles","title":"Crazy Triangles","text":"
# lab 15: Filled Triangles\n# \nfrom machine import Pin, SPI\nimport random\nimport gc9a01\nimport vga1_16x16 as font\nfrom utime import sleep\n\n# this uses the standard Dupont ribbon cable spanning rows 4-9 on our breadboard\nSCK_PIN = 2 # row 4\nSDA_PIN = 3\nDC_PIN = 4\nCS_PIN = 5\n# GND is row 8\nRST_PIN = 6\n\n# define the SPI intrface\nspi = SPI(0, baudrate=60000000, sck=Pin(SCK_PIN), mosi=Pin(SDA_PIN))\ntft = gc9a01.GC9A01(spi, 240, 240, reset=Pin(RST_PIN, Pin.OUT),\n    cs=Pin(CS_PIN, Pin.OUT), dc=Pin(DC_PIN, Pin.OUT), rotation=0\n)\n\nWHITE = gc9a01.color565(255,255,255)\nBLUE = gc9a01.color565(0,0,255)\nBLACK = gc9a01.color565(0,0,0)\n\ntft.init()\ntft.fill(0) # fill the screen with black\ntft.text(font, \"Triangles\", 57, 22, BLUE, BLACK)\n\ndef drawFilledTriangle(x1, y1, x2, y2, x3, y3, color=WHITE):\n    def swap(x, y):\n        return y, x\n\n    if y1 > y2:\n        x1, x2 = swap(x1, x2)\n        y1, y2 = swap(y1, y2)\n    if y1 > y3:\n        x1, x3 = swap(x1, x3)\n        y1, y3 = swap(y1, y3)\n    if y2 > y3:\n        x2, x3 = swap(x2, x3)\n        y2, y3 = swap(y2, y3)\n\n    for y in range(y1, y3+1):\n        if y2 - y1 != 0 and y < y2:\n            xa = x1 + (x2 - x1) * (y - y1) // (y2 - y1)\n        elif y3 - y1 != 0:\n            xa = x1 + (x3 - x1) * (y - y1) // (y3 - y1)\n        else:\n            continue\n\n        if y3 - y2 != 0 and y >= y2:\n            xb = x2 + (x3 - x2) * (y - y2) // (y3 - y2)\n        elif y3 - y1 != 0:\n            xb = x1 + (x3 - x1) * (y - y1) // (y3 - y1)\n        else:\n            continue\n\n        if xa > xb:\n            xa, xb = swap(xa, xb)\n\n        for x in range(xa, xb+1):\n            # for wathing the drawing\n            # sleep(.001)\n            tft.line(x, y, x, y, color)\n\nmin = 40\nmax = 220\nwhile True:\n    # tft.fill(BLACK)\n    x1 = random.randint(min, max)\n    y1 = random.randint(min, max)\n    x2 = random.randint(min, max)\n    y2 = random.randint(min, max)\n    x3 = random.randint(min, max)\n    y3 = random.randint(min, max)\n    red = random.randint(0, 255)\n    green = random.randint(0, 255)\n    blue = random.randint(0, 255)\n    color = gc9a01.color565(red,green,blue)\n    drawFilledTriangle(x1, y1, x2, y2, x3, y3, color)\n    # slow down the drawing here\n    sleep(.1)\n
"},{"location":"kits/gc9a01/09-watch-face/","title":"Watch Face","text":"
  1. Ticks
  2. Second hand
  3. Minute hand
  4. Hour hand
from machine import Pin, SPI\nfrom utime import sleep, localtime\nimport math\nimport gc9a01\n\n# this uses the standard Dupont ribbon cable spanning rows 4-9 on our breadboard\nSCK_PIN = 2 # row 4\nSDA_PIN = 3\nDC_PIN = 4\nCS_PIN = 5\n# GND is row 8\nRST_PIN = 6\n\n# define the SPI intrface\nspi = SPI(0, baudrate=60000000, sck=Pin(SCK_PIN), mosi=Pin(SDA_PIN))\ntft = gc9a01.GC9A01(spi, 240, 240, reset=Pin(RST_PIN, Pin.OUT),\n    cs=Pin(CS_PIN, Pin.OUT), dc=Pin(DC_PIN, Pin.OUT), rotation=0\n)\ntft.init()\n# A full circle is 2*Pi radians\nTWO_PI = 3.1415926*2\nWHITE = gc9a01.color565(255, 255, 255)\nBLUE = gc9a01.color565(0, 0, 255)\nGREEN = gc9a01.color565(0, 255, 0)\nPURPLE = gc9a01.color565(255, 0, 255)\nBLACK = gc9a01.color565(0, 0, 0)\ntft.fill(BLACK)\nCENTER = 120\nMIN_TICK_START = 80\nMIN_TICK_END = 98\nSEC_TICK_START = 100\nSEC_TICK_END = 120\n\ndef drawFilledTriangle(x1, y1, x2, y2, x3, y3, color=WHITE):\n    def swap(x, y):\n        return y, x\n    if y1 > y2:\n        x1, x2 = swap(x1, x2)\n        y1, y2 = swap(y1, y2)\n    if y1 > y3:\n        x1, x3 = swap(x1, x3)\n        y1, y3 = swap(y1, y3)\n    if y2 > y3:\n        x2, x3 = swap(x2, x3)\n        y2, y3 = swap(y2, y3)\n    for y in range(y1, y3+1):\n        if y2 - y1 != 0 and y < y2:\n            xa = x1 + (x2 - x1) * (y - y1) // (y2 - y1)\n        elif y3 - y1 != 0:\n            xa = x1 + (x3 - x1) * (y - y1) // (y3 - y1)\n        else:\n            continue\n        if y3 - y2 != 0 and y >= y2:\n            xb = x2 + (x3 - x2) * (y - y2) // (y3 - y2)\n        elif y3 - y1 != 0:\n            xb = x1 + (x3 - x1) * (y - y1) // (y3 - y1)\n        else:\n            continue\n        if xa > xb:\n            xa, xb = swap(xa, xb)\n        for x in range(xa, xb+1):\n            tft.line(x, y, x, y, color)\n\ndef drawTicks():\n    for i in range(0,60):\n        radians = (i/60)*TWO_PI\n        x1 = int(math.sin(radians)*SEC_TICK_START)\n        y1 = -int(math.cos(radians)*SEC_TICK_START)\n        x2 = int(math.sin(radians)*SEC_TICK_END)\n        y2 = -int(math.cos(radians)*SEC_TICK_END)\n        # print(i, radians, x1, y1, x2, y2)  \n        tft.line(CENTER+x1, CENTER+y1, CENTER+x2, CENTER+y2, BLUE)\n\n    for i in range(0,12):\n        radians = (i/12)*TWO_PI\n        x1 = int(math.sin(radians)*MIN_TICK_START)\n        y1 = -int(math.cos(radians)*MIN_TICK_START)\n        x2 = int(math.sin(radians)*MIN_TICK_END)\n        y2 = -int(math.cos(radians)*MIN_TICK_END)\n        # print(i, radians, x1, y1, x2, y2)  \n        tft.line(CENTER+x1, CENTER+y1, CENTER+x2, CENTER+y2, GREEN)\n\nCENTER = 120\nSEC_HAND_LENGTH = 118\ndef drawSecondHand(sec):\n    # print('sec=', sec)\n    if sec ==0:\n        radians = 0\n    else: radians = (sec/60)*TWO_PI\n    x = int(math.sin(radians)*SEC_HAND_LENGTH)\n    y = -int(math.cos(radians)*SEC_HAND_LENGTH)\n    # print(radians, x, y)\n    tft.line(CENTER, CENTER, CENTER+x,CENTER+y, WHITE)\n    sleep(.1)\n    tft.line(CENTER, CENTER, CENTER+x,CENTER+y, BLACK)\n\n\nMIN_HAND_LENGTH = 90\nMIN_HAND_WIDTH = 5\ndef drawMinuteHand(min, color):\n    radians = (min/60)*TWO_PI\n    x1 = -int(math.cos(radians)*MIN_HAND_WIDTH)\n    y1 = -int(math.sin(radians)*MIN_HAND_WIDTH)\n    x2 = int(math.sin(radians)*MIN_HAND_LENGTH)\n    y2 = -int(math.cos(radians)*MIN_HAND_LENGTH)\n    x3 = int(math.cos(radians)*MIN_HAND_WIDTH)\n    y3 = int(math.sin(radians)*MIN_HAND_WIDTH)\n    # print('min:', x1, y1, x2, y2, x3, y3)\n    drawFilledTriangle(CENTER+x1, CENTER+y1, CENTER+x2, CENTER+y2, CENTER+x3, CENTER+y3, color)\n\nHOUR_HAND_LENGTH = 60\nHOUR_HAND_WIDTH = 6\ndef drawHourHand(hour, color):\n    radians = (hour/12)*TWO_PI\n    x1 = -int(math.cos(radians)*HOUR_HAND_WIDTH)\n    y1 = -int(math.sin(radians)*HOUR_HAND_WIDTH)\n    x2 = int(math.sin(radians)*HOUR_HAND_LENGTH)\n    y2 = -int(math.cos(radians)*HOUR_HAND_LENGTH)\n    x3 = int(math.cos(radians)*HOUR_HAND_WIDTH)\n    y3 = int(math.sin(radians)*HOUR_HAND_WIDTH)\n    # print('hour:', x1, y1, x2, y2, x3, y3)\n    drawFilledTriangle(CENTER+x1, CENTER+y1, CENTER+x2, CENTER+y2, CENTER+x3, CENTER+y3, color)\n\ncounter = 0\nmin = 58\nhour = 6\ndrawMinuteHand(min, GREEN)\ndrawHourHand(hour, PURPLE)\nhour = 6\nwhile True:\n    # this is the flicker\n    # tft.fill(BLACK)\n    drawTicks()\n\n    drawSecondHand(counter)\n    # if we are at 60 we start over\n    if counter > 59:\n        drawMinuteHand(min, BLACK)\n        counter = 0\n        min += 1\n        drawMinuteHand(min, GREEN)\n        if min > 59:\n            min=0\n            drawHourHand(hour, BLACK)\n            hour += 1\n            drawHourHand(hour, PURPLE)\n            if hour > 11:\n                hour = 0\n    counter += 1\n    # sleep(.5)\n
"},{"location":"kits/ili9341/","title":"ILI9341 Display","text":"

The ILI9341 is a low-cost color single-chip driver for a TFT liquid crystal display with a resolution of 240x320.

The board for the ILI9341 with a display costs around $9.00. Although these displays are not a high contrast as OLED displays, they are still adequate for many clock projects that would like to draw smooth circles on a clock face.

"},{"location":"kits/ili9341/#driver","title":"Driver","text":"

dagger GitHub Repo

"},{"location":"kits/ili9341/#characteristics","title":"Characteristics","text":"

Direct write No support for standard micropython framebuf

"},{"location":"kits/ili9341/#sample-lesson-plan","title":"Sample Lesson Plan","text":"

GPT-4 Generated Lesson Plan

"},{"location":"kits/ili9341/#source-code","title":"Source Code","text":"

Source Code

"},{"location":"kits/ili9341/01-rainbow-test/","title":"Rainbow Test","text":"
from ili9341 import Display, color565\nfrom machine import Pin, SPI\n\n# Use these PIN definitions.  SCK must be on 2 and data (SDL) on 3 for SPI bus 0\nSCK_PIN = 2\nMISO_PIN = 3 # labeled SDI(MOSI) on the back of the display\nDC_PIN = 4\nRESET_PIN = 5\nCS_PIN = 6\nROTATION = 90\n\nWIDTH=320\nHEIGHT=240\n\nspi = SPI(0, baudrate=40000000, sck=Pin(SCK_PIN), mosi=Pin(MISO_PIN))\ndisplay = Display(spi, dc=Pin(DC_PIN), cs=Pin(CS_PIN), rst=Pin(RESET_PIN), width=WIDTH, height=HEIGHT, rotation=ROTATION)\n\nRED = color565(255,0,0)\nORANGE = color565(255,128,0)\nYELLOW = color565(255,255,0)\nGREEN = color565(0,255,0)\nBLUE = color565(0,0,255)\nPURPLE = color565(255,0,255)\nWHITE = color565(255,255,255)\nBLACK = color565(0,0,0)\n\ndisplay.fill_rectangle(0,0, 50,HEIGHT, RED)\ndisplay.fill_rectangle(50,0, 50,HEIGHT, ORANGE)\ndisplay.fill_rectangle(100,0, 50,HEIGHT, YELLOW)\ndisplay.fill_rectangle(150,0, 50,HEIGHT, GREEN)\ndisplay.fill_rectangle(200,0, 50,HEIGHT, BLUE)\ndisplay.fill_rectangle(250,0, 50,HEIGHT, PURPLE)\ndisplay.fill_rectangle(300,0, 20,HEIGHT, WHITE)\n\nprint('Done')\n
"},{"location":"kits/ili9341/01-rainbow-test/#color-definitions","title":"Color Definitions","text":"

We can also use this same process for storing all of the common constants that we duplicate in our examples. For example all the named color defintions can be moved into a serate color-defs.py file like this:

Sample colors.py

from ili9341 import color565\n\nWHITE = color565(255,255,255)\nBLACK = color565(0,0,0)\nRED = color565(255,0,0)\nORANGE = color565(255,128,0)\nYELLOW = color565(255,255,0)\nGREEN = color565(0,255,0)\nBLUE = color565(0,0,255)\nCYAN = color565(0,255,255)\nPURPLE = color565(255,0,255)\n

This sample program imports both the config and the colors file:

from ili9341 import Display, color565\nfrom machine import Pin, SPI\nimport config\nimport colors\n\n# Use these PIN definitions.  SCK must be on 2 and data (SDL) on 3\nSCK_PIN = config.SCK_PIN\nMISO_PIN = config.MISO_PIN # labeled SDI(MOSI) on the back of the display\nDC_PIN = config.DC_PIN\nRESET_PIN = config.RESET_PIN\nCS_PIN = config.CS_PIN\n\nWIDTH=config.WIDTH\nHEIGHT=config.HEIGHT\nROTATION=config.ROTATION\n\n# mosi=Pin(23)\n# miso=Pin(MISO_PIN)\nspi = SPI(0, baudrate=40000000, sck=Pin(SCK_PIN), mosi=Pin(MISO_PIN))\ndisplay = Display(spi, dc=Pin(DC_PIN), cs=Pin(CS_PIN), rst=Pin(RESET_PIN), width=WIDTH, height=HEIGHT, rotation=ROTATION)\n\ndisplay.fill_rectangle(0,0, 50,HEIGHT, colors.RED)\ndisplay.fill_rectangle(50,0, 50,HEIGHT, colors.ORANGE)\ndisplay.fill_rectangle(100,0, 50,HEIGHT, colors.YELLOW)\ndisplay.fill_rectangle(150,0, 50,HEIGHT, colors.GREEN)\ndisplay.fill_rectangle(200,0, 50,HEIGHT, colors.BLUE)\ndisplay.fill_rectangle(250,0, 50,HEIGHT, colors.PURPLE)\ndisplay.fill_rectangle(300,0, 20,HEIGHT, colors.WHITE)\n\nprint('Done')\n

Note that the string colors. must appear before each color name. You can shorten this to be just c. if you want to keep your code smaller and easier to read.

"},{"location":"kits/ili9341/01-rainbow-test/#hiding-hardware-initialization","title":"Hiding Hardware Initialization","text":"

We could take this one step further and put the lines that setup the SPI and the Display into a separate function. However, in our labs we want to keep some of this code explicit so we will leave the SPI and Display initialization in our examples.

"},{"location":"kits/ili9341/02-config/","title":"Creating a Hardware Configuration File","text":"

Rather than hard-coding the pin numbers in every example, let's put all our hardware configuration data in a single file. All the examples from here on can use that configuration data.

"},{"location":"kits/ili9341/02-config/#hardware-configuration-file","title":"Hardware Configuration File","text":"
# Dan's Robot Labs configuration file for ILI9341 clock project\n# The colors on the SPI bus cable are:\n# 3.3v power - red\n# SCK - orange\n# MISO/Data - yellow\n# DC - green\n# RESET - blue\n# GND - black\n# CS - purple\nSCK_PIN = 2\nMISO_PIN = 3 # labeled SDI(MOSI) on the back of the display\nDC_PIN = 4\nRESET_PIN = 5\nCS_PIN = 6\nROTATION = 90\n\nWIDTH=320\nHEIGHT=240\n
"},{"location":"kits/ili9341/02-config/#sample-use-of-haredwre-configuration-file","title":"Sample Use of Haredwre Configuration File","text":"
# print out \"Hello World!\" using the rotation=3 using 32-bit high font\n# the default is white text on a black background\nfrom ili9341 import Display, color565\nfrom machine import Pin, SPI\nimport config\n\n# Use these PIN definitions.  SCK must be on 2 and data (SDL) on 3\nSCK_PIN = config.SCK_PIN\nMISO_PIN = config.MISO_PIN # labeled SDI(MOSI) on the back of the display\nDC_PIN = config.DC_PIN\nRESET_PIN = config.RESET_PIN\nCS_PIN = config.CS_PIN\n\nWIDTH=config.WIDTH\nHEIGHT=config.HEIGHT\nROTATION=config.ROTATION\n\n# mosi=Pin(23)\n# miso=Pin(MISO_PIN)\nspi = SPI(0, baudrate=40000000, sck=Pin(SCK_PIN), mosi=Pin(MISO_PIN))\ndisplay = Display(spi, dc=Pin(DC_PIN), cs=Pin(CS_PIN), rst=Pin(RESET_PIN), width=WIDTH, height=HEIGHT, rotation=ROTATION)\n\nRED = color565(255,0,0)\nORANGE = color565(255,128,0)\nYELLOW = color565(255,255,0)\nGREEN = color565(0,255,0)\nBLUE = color565(0,0,255)\nPURPLE = color565(255,0,255)\nWHITE = color565(255,255,255)\nBLACK = color565(0,0,0)\n\ndisplay.fill_rectangle(0,0, 50,HEIGHT, RED)\ndisplay.fill_rectangle(50,0, 50,HEIGHT, ORANGE)\ndisplay.fill_rectangle(100,0, 50,HEIGHT, YELLOW)\ndisplay.fill_rectangle(150,0, 50,HEIGHT, GREEN)\ndisplay.fill_rectangle(200,0, 50,HEIGHT, BLUE)\ndisplay.fill_rectangle(250,0, 50,HEIGHT, PURPLE)\ndisplay.fill_rectangle(300,0, 20,HEIGHT, WHITE)\n\nprint('Done')\n
"},{"location":"kits/led-strips/","title":"LED Strip Clocks","text":"

In this section we will present two clocks that use and LED strip.

  1. The first kit displays binary numbers. Hard to read but very geeky.
  2. The second requires building four seven-segment displays from the LED strip (or LED string)
"},{"location":"kits/led-strips/01-binary-clock/","title":"Binary Clock","text":"

This kit just needs a Pico and a short segment of an LED strip - about 14 pixels.

We can create a very simple binary clock using a single LED strip.

The strip will have three color bands:

  1. One binary number will be the hour (0 to 12) (4 red pixels)
  2. One binary number will be the minutes (0 to 59) (6 green pixels)
  3. One binary number will be the seconds (0 to 59) (6 blue pixels)

We can also put spaces between the bands

We can optionally also have the seconds displayed by:

  1. A single LED that blinks on and off
  2. Two decimal digits
  3. A single binary from (0 to 60)
"},{"location":"kits/led-strips/01-binary-clock/#sample-code","title":"Sample Code","text":"
from machine import Pin\nfrom neopixel import NeoPixel\nfrom utime import sleep, localtime\n# get the configuration information\nimport config\n\nnp = config.NUMBER_PIXELS\nstrip = NeoPixel(Pin(config.NEOPIXEL_PIN), np)\n\nsec_bits = [0,0,0,0,0,0]\nmin_bits = [0,0,0,0,0,0]\nhr_bits = [0,0,0,0,0,0]\n\ndef decimal_to_binary(n, a):\n    global sec_bits\n    for i in range(0,6):\n        if n % 2:\n            a[i] = 1\n        else:\n            a[i] = 0\n        ## n halfed\n        n //= 2\n\ndef display_binary(binary, index, color):\n    for i in range(0, 6):\n        # print(i, ' ', end='')\n        if binary[i] == 1:\n            strip[index+i] = color\n        else:\n            strip[index+i] = (0,0,0)\n    strip.write()\n\n# light mark and write\ndef display_mark(loc):\n    strip[loc] = (5,5,5)\n    strip.write()\n\n# update from the first time\n# sec\ndisplay_mark(0)\ndisplay_mark(7)\n\n# min\ndisplay_mark(9)\ndisplay_mark(16)\n\n# min\ndisplay_mark(19)\ndisplay_mark(26)\n\nnow = localtime()\nhour = now[3]\n# use AM/PM 12 hour time\nif hour > 12:\n    hour = hour - 12\nminute = now[4]\n\n# this is not working\ndecimal_to_binary(minute, min_bits)\nprint('initial min:', minute, min_bits)\ndisplay_binary(min_bits, 10, (0,10,0))\n\ndecimal_to_binary(hour, hr_bits)\nprint('initial hour:', hour, hr_bits)\ndisplay_binary(hr_bits, 20, (0,0,10))\n\nwhile True:\n    now = localtime()\n    hour = now[3]\n    # use AM/PM 12 hour time\n    if hour > 12:\n        hour = hour - 12\n    minute = now[4]\n    sec = now[5]\n    print(hour, ':', minute, ' ', sec, sep='')\n    strip.write()\n    decimal_to_binary(sec, sec_bits)\n    print('sec:', sec, sec_bits)\n    display_binary(sec_bits, 1, (10,0,0))\n    if sec == 60:\n        minute = minute + 1\n        sec = 0\n        decimal_to_binary(minute, min_bits)\n        print('min:', minute, min_bits)\n        display_binary(min_bits, 10, (0,10,0))\n        if minute == 60:\n            decimal_to_binary(hour, hr_bits)\n            print('hour:', hour, hr_bits)\n            display_binary(hr_bits, 20, (0,0,10))\n            hour = hour + 1\n            minute = 0\n            if hour == 24:\n                hour = 0\n    sleep(1)\n
"},{"location":"kits/led-strips/01-binary-clock/#references","title":"References","text":""},{"location":"kits/led-strips/02-seven-segment-clock/","title":"Seven Segment Clock","text":"

We can create a large, bright clock visible in a large classroom by lighting up selective pixels of an LED strip and arranging the pixels in the shape of seven segment displays. We can use three pixels per segment.

eBay 50 for $12

Drawing Digits

Lessons

We can create an array of the segments like this:

segmentMapping = [\n  #a, b, c, d, e, f, g\n  [1, 1, 1, 1, 1, 1, 0], # 0\n  [0, 1, 1, 0, 0, 0, 0], # 1\n  [1, 1, 0, 1, 1, 0, 1], # 2\n  [1, 1, 1, 1, 0, 0, 1], # 3\n  [0, 1, 1, 0, 0, 1, 1], # 4\n  [1, 0, 1, 1, 0, 1, 1], # 5\n  [1, 0, 1, 1, 1, 1, 1], # 6\n  [1, 1, 1, 0, 0, 0, 0], # 7\n  [1, 1, 1, 1, 1, 1, 1], # 8\n  [1, 1, 1, 1, 0, 1, 1]  # 9\n];\n
"},{"location":"kits/led-strips/02-seven-segment-clock/#digit-pixel-map","title":"Digit Pixel Map","text":"

TBD

def set_pixels(digit, value, color):\n    # look up start pixel for digit\n    # for value turn on pixels\n    n = startVals[digit]\n\n    segmentOn = segmentMapping[value];\n    for i in range(0, 9):\n        if (segmentOn[i]):\n            o = i*3 // offset\n            strip(n+o) = color\n            strip(n+o+1) = color\n            strip(n+o+2) = color\n
```

7-Segment Display Demo with P5.js

Moving Rainbow Arduino Sign

"},{"location":"kits/lilygo/","title":"LILYGO T-Display RP2040","text":"

LILYGO makes low-cost and high-quality microcontroller development boards that include small displays. Although most of their boards run on C programs on ESP-32 processors, they do have one that runs MicroPython on an RP2040. This \"kit\" is really just that development board placed on a breadboard. The device has two buttons on it which can be used to adjust the time.

This is a color 1.14 inch LCD display PS with 240*135 resolution. It uses the ST7789V chip that has an extreamly high quality driver created by Russ Hughes that allows for flicker-free drawing.

I purchased mine on Ebay for $10.66 and three dollars for shipping.

Although the display is too small for most robotic applications where the robot is on the floor and we are standing, it is a good example of how we can get both clocks and watches to look great. My hope is that LILYGO comes out with a larger display in the future.

Lilygo also sells their own \"wearable\" watch kits] for $35 to $45. However, I have not purchased any of these that can be programmed with an RP2040 and MicroPython yet. Here is a GitHub Page for the T-Watch that implies it might be on the way. Note that using this requires extensive knowledge of the ESP32 development system.

"},{"location":"kits/lilygo/#getting-started","title":"Getting Started","text":"

To use the ST7789V driver we MUST use a custom image provide by Rull Hughes. This is because the driver is written in low-level C code and the python driver requires it to be combiled into the firmware image.

I downloaded the custom image here:

T-DISPLAY RP2040 Firmware

I then held the Boot button down while I powered up the device.

I soldered my own header pins on the LILYGO and placed it on a breadboard. Unfortunatly this makes it impossible to hold down the boot button with the device on the breadboard.

"},{"location":"kits/lilygo/#pinouts","title":"Pinouts","text":"

The pinouts are very different from the Raspberry Pi Pico.

"},{"location":"kits/lilygo/#config-file","title":"Config File","text":"

This implementation puts the driver in a hidden C program, but it does have a configuration file that we must upload and place in the /lib directory.

Here is a Link to the File for the T-Display RP2040

\"\"\"TTGO T-Display RP2040 display\"\"\"\n\nfrom machine import Pin, SPI\nfrom time import sleep\nimport st7789\n\nTFA = 40    # top free area when scrolling\nBFA = 40    # bottom free area when scrolling\n\ndef config(rotation=0, buffer_size=0, options=0):\n\n    Pin(22, Pin.OUT, value=1)\n\n    spi = SPI(0,\n        baudrate=62500000,\n        polarity=1,\n        phase=0,\n        sck=Pin(2, Pin.OUT),\n        mosi=Pin(3, Pin.OUT),\n        miso=None)\n\n    return st7789.ST7789(\n        spi,\n        135,\n        240,\n        cs=Pin(5, Pin.OUT),\n        dc=Pin(1, Pin.OUT),\n        backlight=Pin(4, Pin.OUT),\n        rotation=rotation,\n        options=options,\n        buffer_size=buffer_size)\n
"},{"location":"kits/lilygo/#blink-the-onboard-led","title":"Blink The Onboard LED","text":"

This red LED is on the bottom of the board.

Blink Timer example:

from machine import Pin, Timer\nled = Pin(25,Pin.OUT)\ntim = Timer()\ndef tick(timer):\n    global led\n    led.toggle()\ntim.init(freq=2.5, mode=Timer.PERIODIC, callback=tick)\n
"},{"location":"kits/lilygo/#display-example","title":"Display Example","text":"
\"\"\"\ndisplay-test.py\nFills the screen with red, green and blue\n\"\"\"\n\nfrom utime import sleep\nimport st7789\nimport tft_config\ntft = tft_config.config(1)\ntft.init()\n\nwhile True:\n    tft.fill(st7789.RED)\n    sleep(1)\n    tft.fill(st7789.GREEN)\n    sleep(1)\n    tft.fill(st7789.BLUE)\n    sleep(1)\n
"},{"location":"kits/lilygo/#drawing-text","title":"Drawing Text","text":"

For this example to work, you will need to load a font library into the /lib directory.

import random\nimport utime\nimport st7789\nimport tft_config\nimport vga2_bold_16x32 as font\n\ntft = tft_config.config(1)\ntft.init()\n\n# draw text using a 16X32 font using blue text on a white background\ntft.text(\n        font,\n        'Hello World!',\n        tft.width()//2-100, # x position to start writing\n        tft.height()//2-50, # y position\n        st7789.BLUE, # font in blue\n        st7789.WHITE # background in white\n)\n
"},{"location":"kits/lilygo/#referneces","title":"Referneces","text":"

Item on Aliexpress Sample GitHub repo: https://github.com/Xinyuan-LilyGO/LILYGO-T-display-RP2040

ST7789V Submodule: Russ Hughes GitHub Repo

Config: Sample Config File

"},{"location":"kits/max7219/","title":"MAX7219 Clocks","text":"

The Analog Devices MAX7219 is a low-cost chip that communicates with a row of seven-segment displays using a single serial interface.

eBay search for MAX7219

"},{"location":"kits/max7219/#lesson-plan-building-a-clock-with-the-max7219-chip-using-micropython-and-raspberry-pi-pico","title":"Lesson Plan: Building a Clock with the MAX7219 Chip using MicroPython and Raspberry Pi Pico","text":""},{"location":"kits/max7219/#objective","title":"Objective:","text":"

Students will learn about basic electronics and programming by building a digital clock using the MAX7219 LED driver chip with a Raspberry Pi Pico programmed in MicroPython.

"},{"location":"kits/max7219/#materials","title":"Materials:","text":""},{"location":"kits/max7219/#duration","title":"Duration:","text":"

5 classes (45 minutes each)

"},{"location":"kits/max7219/#class-1-introduction-to-the-max7219-and-raspberry-pi-pico","title":"Class 1: Introduction to the MAX7219 and Raspberry Pi Pico","text":""},{"location":"kits/max7219/#class-2-basic-electronics-and-circuit-design","title":"Class 2: Basic Electronics and Circuit Design","text":""},{"location":"kits/max7219/#class-3-introduction-to-micropython","title":"Class 3: Introduction to MicroPython","text":"

understand MicroPython syntax and basic commands.

"},{"location":"kits/max7219/#class-4-programming-the-max7219-with-micropython","title":"Class 4: Programming the MAX7219 with MicroPython","text":""},{"location":"kits/max7219/#class-5-building-and-programming-the-clock","title":"Class 5: Building and Programming the Clock","text":""},{"location":"kits/max7219/#additional-notes","title":"Additional Notes:","text":""},{"location":"kits/max7219/#references","title":"References","text":"

DIY clock | Homemade 7 segments \"nixie\" tubes

"},{"location":"kits/pico-oled/","title":"Pico OLED Kits","text":"

These are our most popular kits. They use a bright monochrome 128x64 OLED with either the Raspberry Pi Pico or the Raspberry Pi Pico W.

The OLEDs are mounted on a piece of plastic with a half-size breaboard.

Each board has two buttons so you can adjust the time and alarm clock settings.

"},{"location":"kits/pico-oled/#individual-exercises","title":"Individual Exercises","text":""},{"location":"kits/pico-oled/#hello-world","title":"Hello World","text":"

Go to Hello World lab

"},{"location":"kits/pico-oled/01-hello-world/","title":"Hello World","text":"

To get started, let's just draw the string \"Hello world!\" on the display.

Here is the code:

import machine\nimport ssd1306\n\nSCL=machine.Pin(2) # SPI CLock\nSDA=machine.Pin(3) # SPI Data\n\nRES = machine.Pin(4) # Reset\nDC = machine.Pin(5) # Data/command\nCS = machine.Pin(6) # Chip Select\n\nspi=machine.SPI(0, sck=SCL, mosi=SDA)\noled = ssd1306.SSD1306_SPI(128, 64, spi, DC, RES, CS)\n\n# erase the entire screen with black\noled.fill(0)\n\n# place a hello message at point (0,0) in white\noled.text(\"Hello world!\", 0, 0, 1)\n\n# send the entire frame buffer to the display via the SPI bus\noled.show()\n

Challenges

  1. Can you change the message from \"Hello world!\" to have your name in it?
  2. Can you change the location of the text on the screen by changing the location point from (0,0) to another place on the screen? The screen is 128 pixels wide by 64 pixels high.
  3. How far down can you display the message without going off the screen?
  4. How many characters wide can a message be before it goes off the right edge of the dipsplay?
  5. Can you display multiple messages on different lines?
"},{"location":"kits/pico-oled/02-display-time/","title":"Display Time","text":""},{"location":"kits/pico-oled/02-display-time/#the-localtime-function","title":"The Localtime function","text":"
from time import localtime\n\nprint(localtime())\n
import machine\nimport ssd1306\nimport config\nfrom time import localtime\n\nSCL=machine.Pin(config.SCL_PIN) # SPI CLock\nSDA=machine.Pin(config.SDA_PIN) # SPI Data\n\nRES = machine.Pin(config.RESET_PIN) # Reset\nDC = machine.Pin(config.DC_PIN) # Data/command\nCS = machine.Pin(config.CS_PIN) # Chip Select\n\nspi=machine.SPI(config.SPI_BUS, sck=SCL, mosi=SDA, baudrate=100000)\noled = ssd1306.SSD1306_SPI(config.WIDTH, config.HEIGHT, spi, DC, RES, CS)\n\nyear = localtime()[0]\nmonth = localtime()[1]\nday = localtime()[2]\nhour = localtime()[3]\nminute = localtime()[4]\n\n# display the time in hour and minute on the first line\noled.text(str(hour) + \":\" + str(minute), 0, 0, 1)\n\n# display the date on the second line\noled.text(str(month) + \"/\" + str(day) + \"/\" + str(year), 0, 10, 1)\noled.show()\n
"},{"location":"kits/pico-oled/02-hello-config/","title":"Hello Configuraiton","text":""},{"location":"kits/pico-oled/03-display-localtime/","title":"Display Localtime","text":"
import machine\nimport ssd1306\nimport config\nfrom time import localtime\n\nSCL=machine.Pin(config.SCL_PIN) # SPI CLock\nSDA=machine.Pin(config.SDA_PIN) # SPI Data\n\nRES = machine.Pin(config.RESET_PIN) # Reset\nDC = machine.Pin(config.DC_PIN) # Data/command\nCS = machine.Pin(config.CS_PIN) # Chip Select\n\nspi=machine.SPI(config.SPI_BUS, sck=SCL, mosi=SDA, baudrate=100000)\noled = ssd1306.SSD1306_SPI(config.WIDTH, config.HEIGHT, spi, DC, RES, CS)\n\nyear = localtime()[0]\nmonth = localtime()[1]\nday = localtime()[2]\nhour = localtime()[3]\nminute = localtime()[4]\n\n# display the time in hour and minute on the first line\noled.text(str(hour) + \":\" + str(minute), 0, 0, 1)\n\n# display the date on the second line\noled.text(str(month) + \"/\" + str(day) + \"/\" + str(year), 0, 10, 1)\noled.show()\n

Challenges

  1. Change the display format of the date and time. Place the date and time in the center of the display.
  2. Add code to add a \"AM\" or \"PM\" string if the hour is greater than 12 noon
  3. Adding additional text to display if it is morning or evening
"},{"location":"kits/pico-oled/04-display-localtime-updates/","title":"Display Local Time with Updates","text":"

Now let's update the display every second.

import machine\nimport ssd1306\nimport config\nfrom time import localtime\n\nSCL=machine.Pin(config.SCL_PIN) # SPI CLock\nSDA=machine.Pin(config.SDA_PIN) # SPI Data\n\nRES = machine.Pin(config.RESET_PIN) # Reset\nDC = machine.Pin(config.DC_PIN) # Data/command\nCS = machine.Pin(config.CS_PIN) # Chip Select\n\nspi=machine.SPI(config.SPI_BUS, sck=SCL, mosi=SDA, baudrate=100000)\noled = ssd1306.SSD1306_SPI(config.WIDTH, config.HEIGHT, spi, DC, RES, CS)\n\nwhile True:\n    # clear the entire screen\n\n    oled.fill(0)\n    year = localtime()[0]\n    month = localtime()[1]\n    day = localtime()[2]\n    hour = localtime()[3]\n    minute = localtime()[4]\n    second = localtime()[5]\n\n    # display the time in hour and minute on the first line\n    oled.text(str(hour) + \":\" + str(minute) + \":\" + str(second) , 0, 0, 1)\n\n    # display the date on the second line\n    oled.text(str(month) + \"/\" + str(day) + \"/\" + str(year), 0, 10, 1)\n    # send the entire screen to the display via SPI\n    oled.show()\n
"},{"location":"kits/pico-oled/05-clock-hands/","title":"Clock Hands","text":"

Now lets display the hands on a clock. We will need to do a little math to calculate were on the clock face to draw the hands to from the center.

import machine\nimport ssd1306\nimport config\nfrom time import sleep, localtime\nimport math\n\nSCL=machine.Pin(config.SCL_PIN) # SPI CLock\nSDA=machine.Pin(config.SDA_PIN) # SPI Data\n\nRES = machine.Pin(config.RESET_PIN) # Reset\nDC = machine.Pin(config.DC_PIN) # Data/command\nCS = machine.Pin(config.CS_PIN) # Chip Select\n\nspi=machine.SPI(config.SPI_BUS, sck=SCL, mosi=SDA, baudrate=100000)\noled = ssd1306.SSD1306_SPI(config.WIDTH, config.HEIGHT, spi, DC, RES, CS)\nTWO_PI = 3.14159 * 2\n\nLENGTH = config.HEIGHT // 2\nCENTER_X = config.WIDTH // 2\nCENTER_Y = config.HEIGHT // 2\n\noled.fill(0)\n\nfor i in range(0,61):\n    radians = (i/60)*TWO_PI\n    x = int(math.sin(radians)*LENGTH)\n    y = -int(math.cos(radians)*LENGTH)\n    # print(i, radians, x, y, x, y)  \n    oled.line(CENTER_X, CENTER_Y, CENTER_X + x, CENTER_Y + y, 1)\n    oled.show()\n    sleep(.1)\n    oled.line(CENTER_X, CENTER_Y, CENTER_X + x, CENTER_Y + y, 0)\n
"},{"location":"kits/pico-oled/10-draw-seven-segments/","title":"Draw Seven Segments","text":""},{"location":"kits/pico-oled/10-draw-seven-segments/#dight-to-segment-data","title":"Dight to Segment Data","text":"
segmentMapping = [\n  #a, b, c, d, e, f, g\n  [1, 1, 1, 1, 1, 1, 0], # 0\n  [0, 1, 1, 0, 0, 0, 0], # 1\n  [1, 1, 0, 1, 1, 0, 1], # 2\n  [1, 1, 1, 1, 0, 0, 1], # 3\n  [0, 1, 1, 0, 0, 1, 1], # 4\n  [1, 0, 1, 1, 0, 1, 1], # 5\n  [1, 0, 1, 1, 1, 1, 1], # 6\n  [1, 1, 1, 0, 0, 0, 0], # 7\n  [1, 1, 1, 1, 1, 1, 1], # 8\n  [1, 1, 1, 1, 0, 1, 1]  # 9\n];\n
"},{"location":"kits/pico-oled/10-draw-seven-segments/#draw-segment-function","title":"Draw Segment Function","text":"

Parameters:

  1. The digit to display - an integer in the range 0-9
  2. x of the upper left corner
  3. y of the upper left corner
  4. the width of the digit (about 20 is a good value)
  5. the height of the digit
  6. the thickness of each segment (3 or 4 are good values)
  7. the color (0=black, 1=white)
# x and y are upper-left-corner\n# width and height are the dimensions of the digit\n# thickness is the width of the line segments\n# color is 1 for white and 0 for black\ndef drawDigit(digit, x, y, width, height, thickness, color):\n  # get a list of the segments that are on for this digit\n  segmentOn = segmentMapping[digit];\n\n  # Draw the horizontal segments: top, bottem, middle\n  for i in [0, 3, 6]:\n    if (segmentOn[i]):\n      if (i==0): # top row\n          yOffset = 0 \n      if (i==3):\n          yOffset = height - thickness # bottem element\n      if (i==6):\n          yOffset = height // 2 - thickness // 2# bottum\n      # oled.line(x - size, y+yOffset-size, x + size, y+yOffset-size, 1);\n      oled.fill_rect(x, y+yOffset, width, thickness, color)\n\n  # Draw the vertical segments ur, lr, ll, ul\n  for i in [1, 2, 4, 5]:\n    if (segmentOn[i]) :\n      # upper vertical lines\n      if (i==1 or i==5):\n          startY = y\n          endY = y + height // 2\n      # lower two vertical lines (2=lower right and 4=lower left)\n      if (i==2 or i==4):\n          startY = y + height // 2\n          endY = y + height\n      if (i==4 or i==5): xOffset = 0\n      if (i==1 or i==2): xOffset = width-thickness\n\n      oled.fill_rect(x+xOffset, startY, thickness, endY-startY, color)\n
"},{"location":"kits/pico-oled/10-draw-seven-segments/#full-program","title":"Full Program","text":"
# Clock Lab 20: Draw Seven Segments\n# this lab uses the fill_rect function to draw the segments\nimport machine\nimport utime\nimport ssd1306\nfrom utime import sleep, localtime\nled = machine.Pin(25, machine.Pin.OUT)\n\nSCL=machine.Pin(2) # SPI CLock\nSDA=machine.Pin(3) # SPI Data\nspi=machine.SPI(0, sck=SCL, mosi=SDA, baudrate=100000)\n\nRES = machine.Pin(4)\nDC = machine.Pin(5)\nCS = machine.Pin(6)\n\noled = ssd1306.SSD1306_SPI(128, 64, spi, DC, RES, CS)\n\nsegmentMapping = [\n  #a, b, c, d, e, f, g\n  [1, 1, 1, 1, 1, 1, 0], # 0\n  [0, 1, 1, 0, 0, 0, 0], # 1\n  [1, 1, 0, 1, 1, 0, 1], # 2\n  [1, 1, 1, 1, 0, 0, 1], # 3\n  [0, 1, 1, 0, 0, 1, 1], # 4\n  [1, 0, 1, 1, 0, 1, 1], # 5\n  [1, 0, 1, 1, 1, 1, 1], # 6\n  [1, 1, 1, 0, 0, 0, 0], # 7\n  [1, 1, 1, 1, 1, 1, 1], # 8\n  [1, 1, 1, 1, 0, 1, 1]  # 9\n];\n\n# x and y are upper-left-corner\n# width and height are the dimensions of the digit\n# thickness is the width of the line segments\n# color is 1 for white and 0 for black\ndef drawDigit(digit, x, y, width, height, thickness, color):\n  # get a list of the segments that are on for this digit\n  segmentOn = segmentMapping[digit];\n\n  # Draw the horizontal segments: top, bottem, middle\n  for i in [0, 3, 6]:\n    if (segmentOn[i]):\n      if (i==0): # top\n          yOffset = 0 \n      if (i==3):\n          yOffset = height - thickness # bottem element\n      if (i==6):\n          yOffset = height // 2 - thickness // 2# bottum\n      # oled.line(x - size, y+yOffset-size, x + size, y+yOffset-size, 1);\n      oled.fill_rect(x, y+yOffset, width, thickness, color)\n\n  # Draw the vertical segments ur, lr, ll, ul\n  for i in [1, 2, 4, 5]:\n    if (segmentOn[i]) :\n      # upper vertical lines\n      if (i==1 or i==5):\n          startY = y\n          endY = y + height // 2\n      # lower two vertical lines (2=lower right and 4=lower left)\n      if (i==2 or i==4):\n          startY = y + height // 2\n          endY = y + height\n      if (i==4 or i==5): xOffset = 0\n      if (i==1 or i==2): xOffset = width-thickness\n\n      oled.fill_rect(x+xOffset, startY, thickness, endY-startY, color)\n\noled.fill(0)\noled.text('Lab 12: rect', 0, 0, 1)\nx = 10 # upper left corner x\ny = 10 # upper left corner y\nw = 20 # digit width\nh = 30 # digit height\nt = 3\n\nwhile True:\n    for i in range(0, 10):\n\n        print(i)\n        # create an outline on px away from the drawing region\n        oled.rect(x-2, y-2, w+4, h+4, 1)\n        # draw one digit\n        drawDigit(i, x, y, w, h, t, 1)\n        # draw a second digit\n        #drawDigit(i, x + w + 4, w, h, t, 1)\n        oled.text(str(i), 0, 54, 1)\n        oled.show()\n        sleep(2)\n        oled.fill(0)\n
"},{"location":"kits/pico-oled/11-draw-thinkness/","title":"Draw Seven Segments with Thickness","text":""},{"location":"kits/pico-oled/15-digital-clock/","title":"OLED Digital Clock Display","text":""},{"location":"kits/waveshare/","title":"Waveshare RP2040","text":"

The Waveshare RP2040 1.28\" IPS LCD Board is a wonderful developent board with a build in RP2040 processor that currently sells for about $18. The board has a USB-C connector, a place for a LiPo battery connection and built in Accelerometer & Gyroscope. It is a great value and a wonderful way to start to learn Python for smart watches!

  1. Waveshare SKU: 22668
  2. Waveshare Part Number: RP2040-LCD-1.28
  3. Link to Waveshare site

Note that this watch does not have a real-time clock and has no ability to connect to time syhronization using WiFi. However, it is an ideal development tool for learning to program watch displays and integrate sensors.

"},{"location":"kits/waveshare/#micropython-version","title":"MicroPython Version","text":"

To use these lessions you much use MicroPython runtime v1.19.1-854-g35524a6fd released on 2023-02-07 or later. This version supports all the native framebuf drawing libraries (ellipse and polygon)

See the documentation here: MicroPython Framebuffer Functions

"},{"location":"kits/waveshare/#lessons","title":"Lessons","text":"
  1. Hello world! Lesson
  2. Color Lab
  3. Drawing Analog Hands
  4. 5x8 Fonts
"},{"location":"kits/waveshare/#detailed-components","title":"Detailed Components","text":"Component Description USB Type-C connector USB 1.1 with device and host support ETA6096 high efficiency Lithium battery recharge manager Battery Header MX1.25 header, for 3.7V Lithium battery, allows recharging the battery and powering the board at the same time QMI8658C IMU, includes a 3-axis gyroscope and a 3-axis accelerometer 1.27mm pitch headers Adapting all GPIO and Debug pins W25Q16JVUXIQ 2MB NOR-Flash RP2040 Dual-core processor, up to 133MHz operating frequency RESET Button Reset the processor BOOT Button press it when resetting to enter download mode"},{"location":"kits/waveshare/#references","title":"References","text":"

wiki

Instructable by Tony Goodhew - note that this version does not leverage the built-in drawing libraries that were made available in version 19 of the MicroPython release. See The MicroPython FrameBuf Library

Github Gist by Alasdair Allan

"},{"location":"kits/waveshare/01-hello-world/","title":"Waveshare Hello World","text":""},{"location":"kits/waveshare/01-hello-world/#step-1-load-the-library","title":"Step 1: Load the Library","text":""},{"location":"kits/waveshare/01-hello-world/#step-2-load-a-test-program","title":"Step 2: Load a Test Program","text":"
from LCD_1inch28 import LCD_1inch28\n\nLCD = LCD_1inch28()  \nLCD.set_bl_pwm(65535)\n\nLCD.fill(LCD.black)    \nLCD.text(\"Hello world!\", 50, 100, LCD.white)\nLCD.show()\n

You should see \"Hello world!\" in a small white font near the center of the screen.

Challenges

  1. Can you move the text around the screen by changing the x and y starting position of the text?
  2. Can you change the background fill from black to another color?
  3. Can you change the color of the text from white to be another color?
  4. Can you change the font size? (hint: this is not easy!)
"},{"location":"kits/waveshare/02-color-test/","title":"Color Tests","text":"

Now let's try to make the background screen change colors:

from LCD_1inch28 import LCD_1inch28\nfrom utime import sleep\n\nLCD = LCD_1inch28()  \nLCD.set_bl_pwm(65535)\n\nLCD.fill(LCD.black)\nLCD.show()\nsleep(1)\nLCD.fill(LCD.white)\nsleep(1)\nLCD.show()\nLCD.fill(LCD.red)\nLCD.show()\nsleep(1)\nLCD.fill(LCD.green)\nLCD.show()\nsleep(1)\nLCD.fill(LCD.blue)\nLCD.show()\nprint('done')\n

What happens when you change the color \"red\" to be \"orange\"? You shouild see:

Traceback (most recent call last):\n  File \"<stdin>\", line 10, in <module>\nAttributeError: 'LCD_1inch28' object has no attribute 'orange'\n

This shows you that although the driver knows about some basic colors, (black, white, red, green and blue), it has no understanding of other colors.

To draw these colors we need to add our own color lists.

"},{"location":"kits/waveshare/02-color-test/#custom-color-lists","title":"Custom Color Lists","text":"

In order to get more nuanced colors, we need to define them using the binary of their red, green and blue values. With this display, we order the bits, blue, red and green.

We can use the following binary notation to represent the colors:

# binary representations of colors B=Blue bits, R=Red bits, G=Green bits\n# color = 0bBBBBBRRRRRGGGGGG\n# Cyan has all the blue and green bits on and the red bits off\ncyan = cyan = 0b1111100000111111\n
from machine import Pin,I2C,SPI,PWM,ADC\nfrom time import sleep\nfrom LCD_1inch28 import LCD_1inch28, QMI8658\n\nCENTER = 120\nLCD = LCD_1inch28()\nLCD.set_bl_pwm(65535)\n\n# draw readability\nON = 1\nOFF = 0\nNO_FILL = 0\nFILL = 1\n# hex representation of colors\n# note that the bit order is blue, red, green\nblack = 0x0000\nred   = 0x07E0\ngreen = 0x001f\nblue  = 0xf800\nwhite = 0xffff\n\n# binary representations of colors\n# binary representations of colors B=5 Blue bits, R=5 Red bits, G=6 green bits\n# color = 0bBBBBBRRRRRGGGGGG\nyellow = 0b0000011111111111\norange = 0b0000001111000010\ncyan = 0b1111100000111111\nbrown = 0b0000000001000001\ngold = 0b0000001110011110\npurple = 0b1111111111000000\nmagenta = 0b0000001100011000\npink = 0b0000111111000010\nolive = 0b0000000001000010\ngray = 0b00001000010000100\nlightGreen = 0b0000100001111111\ndarkGreen  = 0b0000000000000001\n\nColorList =  (red,    green,   blue,   white,   yellow,  orange,\n              cyan, brown, gold, purple, magenta, pink, olive, gray, lightGreen, darkGreen)\nColorNames = ('red', 'green', 'blue', 'white', 'yellow', 'orange',\n              'cyan', 'brown', 'gold', 'purple', 'magenta', 'pink', 'olive', 'gray', 'lightGreen', 'darkGreen')\n\nradius = 120\nLCD.fill(LCD.black)\n\nfor i in range(0, len(ColorList)):\n    print(ColorNames[i])\n    # LCD.ellipse(CENTER, CENTER, radius, radius, ColorList[i], FILL)\n    LCD.fill(ColorList[i])\n    LCD.text(ColorNames[i], 100, 100, 1)\n    LCD.text(ColorNames[i], 100, 120, white)\n    LCD.show()\n    sleep(1)\n

Note that we are using the fill function to draw on all the pixels on the screen. We could have used the ellipse funtion to draw into the frame buffer, but the fill function is a bit easier.

"},{"location":"kits/waveshare/02-color-test/#converting-rgb888-to-brg556","title":"Converting RGB888 to BRG556","text":"
def convert_color_RGB888_RGB565(R,G,B): # Convert RGB888 to RGB565\n    return (((G&0b00011100)<<3) +((B&0b11111000)>>3)<<8) + (R&0b11111000)+((G&0b11100000)>>5)\n
"},{"location":"kits/waveshare/02-display-text/","title":"Display Time and Date in Text","text":"
from utime import localtime, sleep\nfrom LCD_1inch28 import LCD_1inch28\n\nLCD = LCD_1inch28()  \n\ndays = ('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday',\n        'Sunday')\nmonths = ('Jan', 'Feb', 'March', 'April', 'May', 'June', 'July',\n          'Aug', 'Sept', 'Oct', 'Nov', 'Dec')\nlabel = ('year', 'month', 'mday', 'hour', 'minute', 'second', 'weekday', 'yearday')\n\ndef format_time(now):\n    hour_number = now[3]\n    if hour_number < 13:\n        hour_12 = hour_number\n        am_pm = 'am'\n    else:\n        hour_12 = hour_number - 12\n        am_pm = 'pm'\n    minutes_num = now[4]\n    if minutes_num < 10:\n        min_leading_zero = '0'\n    else:\n        min_leading_zero = ''\n    seconds_num = now[5]\n    if seconds_num < 10:\n        sec_leading_zero = '0'\n    else:\n        sec_leading_zero = ''\n    return \"{}:{}{}:{}{} {}\".format(hour_12, min_leading_zero, minutes_num, sec_leading_zero, seconds_num, am_pm)\n\ndef format_date(now):\n    month_number = now[1]\n    month_name = months[month_number - 1]\n    weekday_number = now[6]\n    day_name = days[weekday_number]\n    hour_number = now[3]\n    return \"{} {} {}, {}\".format(day_name, month_name, now[2], now[0])\n\nwhile(True):\n    now = localtime()\n    # print(now)\n    LCD.fill(LCD.black)    \n    LCD.text(format_time(now), 77, 50, LCD.white)\n    LCD.text(format_date(now), 40, 80, LCD.white)\n    LCD.show()\n    sleep(1)\n
"},{"location":"kits/waveshare/03-drawing-hands/","title":"Drawing Analog Hands","text":"

Drawing a analog hand requirs drawing a line from the center of the screen to a point on the edge of the circle. The point positon varies periodically, just like the sine and cosine functions vary. We can demonstrate this will a counter that goes from 0 to 360 degrees.

Consider the following:

  1. Since the sine(0) = 0 we can use that function for the displacement from the center on the x-axis.
  2. Since the cosine(0) = 1, we can use that as the negative Y displacement from the center. Remember in drawing pixels, (0,0) is in the upper-left corner of the screen.

There is one other bit of math we need to review. The sine() and cosine() function take in a number called radians which is usually a number between 0 and two times Pi. They then return a value between 0 and 1. We need multiple both of these values by the length of the watch hand to get to the right part of the watch face.

from utime import sleep\nfrom LCD_1inch28 import LCD_1inch28\nimport math\n\nLCD = LCD_1inch28()\n\nCENTER = 120\nHAND_LENGTH = 100\n\n# our counter will range from 0 to 59\n# A full circle is 2*Pi radians\nTWO_PI = 3.145175*2\ncounter = 0\nwhile True:\n    radians = (counter/60)*TWO_PI\n    x = int(math.sin(radians)*HAND_LENGTH)\n    y = -int(math.cos(radians)*HAND_LENGTH)\n    print(radians, x, y)\n    LCD.fill(LCD.black)\n    LCD.line(CENTER, CENTER, CENTER+x,CENTER+y, LCD.white)\n    LCD.show()\n    sleep(1)\n    counter += 1\n    # if we are at 60 we start over\n    if counter > 59:\n        counter = 0\n

You should now see a narrow white line moving much like a second hand on a watch!

"},{"location":"kits/waveshare/03-drawing-hands/#adding-bling-to-your-hands","title":"Adding bling to your hands","text":"

Although drawing a single white line is a clean efficent design, many people like to add other features such as an arrow head at the tip of the hand. To do this we can use the poly function to draw the arrow. To get this right, we also need to orient the arrow in the right direction.

"},{"location":"kits/waveshare/03-drawing-hands/#drawing-a-triangle","title":"Drawing a Triangle","text":"

We can use the MicroPython standard poly function to draw a triangle. The poly

`FrameBuffer.poly(x, y, coords, c[, f])

This will draw an arbitrary polygon at the given x, y location using the given color (c).

The coords must be specified as a array of integers, e.g. array('h', [x0, y0, x1, y1, ... xn, yn]).

The optional f parameter can be set to True to fill the polygon. Otherwise just a one pixel outline is drawn.

Let's start with drawing a basic triangle in the center of the screen like this:

LCD.

# draw a triangle on a blue background\nfrom utime import sleep\nfrom LCD_1inch28 import LCD_1inch28\nimport math\nfrom array import array\nLCD = LCD_1inch28()\n\nCENTER = 120\nNO_FILL = 0 # just the border is drawn\nFILL = 1 # all pixels within the polygon are drawn\n# draw a blue background\nLCD.fill(LCD.blue)\n\n# distance from the center to the tip of the traiangle\nd = 50\nmy_array = array('B', [CENTER-d,CENTER+d, CENTER,CENTER-d, CENTER+d,CENTER+d])\nLCD.poly(0,0, my_array, LCD.white, FILL)\nLCD.show()\nprint('done')\n
"},{"location":"kits/waveshare/03-drawing-hands/#drawing-a-triangle-rotating","title":"Drawing a Triangle Rotating","text":"

Now we will modify the draw triangle program to rotate each of the three points. We do this by passing the CENTER and either a positve or negative value of the x and y which varies as we move around the circle.

Here is the line that is the most difficult to understand:

my_array = array('B', [CENTER-x,CENTER-y, CENTER+y,CENTER-x, CENTER+x,CENTER+y])\n

Note that the first point is in the lower left corner:

(CENTER-x, CENTER-y)\n

The second point is at the top of the trainagle and the X is initially zero (sine(0) = y)

(CENTER+y, CENTER-x)\n

The third point is to the lower right where we need to add to both the X and Y:

(CENTER+x, CENTER-y)\n

Here is the full program:

# draw rotating triangle\nfrom utime import localtime, sleep\nfrom LCD_1inch28 import LCD_1inch28\nfrom array import array\nimport math\nTWO_PI = 3.145175*2\n\nLCD = LCD_1inch28()\n\nCENTER = 120\nNO_FILL = 0 # just the border is drawn\nFILL = 1 # all pixels within the polygon are drawn\n\n# distance from the center to the tip of the traiangle\nd = 50\n\ncounter = 0\nwhile True:\n    LCD.fill(LCD.blue)\n    radians = (counter/60)*TWO_PI\n    x = int(math.cos(radians)*d)\n    y = int(math.sin(radians)*d)\n\n    # the three points of the triangle are rotated in a circle\n    my_array = array('B', [CENTER-x,CENTER-y, CENTER+y,CENTER-x, CENTER+x,CENTER+y])\n    print(CENTER-x, CENTER+y)\n\n    LCD.poly(0,0, my_array, LCD.white, FILL)\n    LCD.show()\n    sleep(.1)\n    counter += 1\n    # if we are at 60 we start over\n    if counter > 59:\n        counter = 0\n

You might have to stare at the code and the drawing for a while to get this figured out.

"},{"location":"kits/waveshare/03-drawing-hands/#create-a-draw-triangle-function","title":"Create a Draw Triangle Function","text":"

Now we are ready to package our triangle drawing experiment into a single function to make it easier to use. We will pass in four parameters:

  1. The center of the triangle's X and Y coordinates
  2. The size of the triangle measured from the center to the tip
  3. The number of seconds on the clock (0 to 59) which we will convert to radians. This becomes the agle of the triangle.
def drawTriangle(x, y, size, seconds):\n    radians = (seconds/60)*TWO_PI\n    # calculate the offsets\n    xo = int(math.cos(radians)*size)\n    yo = int(math.sin(radians)*size)\n    # build the array - use B if we have under 255 and h if over 255\n    arr = array('B', [x-xo,y-yo,  x+yo,y-xo,  x+xo,y+yo])\n    LCD.poly(0,0, arr, LCD.white, FILL)\n
"},{"location":"kits/waveshare/03-drawing-hands/#full-program","title":"Full Program","text":"
from utime import localtime, sleep\nfrom LCD_1inch28 import LCD_1inch28\nfrom array import array\nimport math\nTWO_PI = 3.145175*2\n\nLCD = LCD_1inch28()\n\nCENTER = 120\nNO_FILL = 0 # just the border is drawn\nFILL = 1 # all pixels within the polygon are drawn\nHAND_LENGTH = 100\nTRIANGLE_SIZE = 10\n\ndef drawTriangle(x, y, size, seconds):\n    radians = (seconds/60)*TWO_PI\n    # calculate the offsets\n    xo = int(math.cos(radians)*size)\n    yo = int(math.sin(radians)*size)\n    # build the array - use B if we have under 255 and h if over 255\n    arr = array('B', [x-xo,y-yo,  x+yo,y-xo,  x+xo,y+yo])\n    LCD.poly(0,0, arr, LCD.white, FILL)\n\ncounter = 0\nwhile True:\n    LCD.fill(LCD.blue)\n    radians = (counter/60)*TWO_PI\n    x = int(math.sin(radians)*HAND_LENGTH)\n    y = -int(math.cos(radians)*HAND_LENGTH)\n    LCD.line(CENTER, CENTER, CENTER+x,CENTER+y, LCD.white)\n    drawTriangle(CENTER+x, CENTER+y, TRIANGLE_SIZE, counter)\n    LCD.show()\n    sleep(.1)\n    counter += 1\n    # if we are at 60 we start over\n    if counter > 59:\n        counter = 0```\n
"},{"location":"kits/waveshare/03-drawing-hands/#drawing-x-and-y-axis","title":"Drawing X and Y Axis","text":"
# draw thin blue axis lines through the center\n# vertical line\nLCD.line(CENTER, 0, CENTER, 2*CENTER, blue)\n# horizontal line\nLCD.line(0, CENTER, 2*CENTER, CENTER, blue)\n
"},{"location":"kits/waveshare/04-5x8-font/","title":"5x8 Font Test","text":"

We can create custom functions for drawing larger fonts. Here we have a byte array of 5X8 fonts. The function character() draws individual characters one at a time.

from LCD_1inch28 import LCD_1inch28\n\nLCD = LCD_1inch28()  \n\n# Standard ASCII 5x8 font\n# https://gist.github.com/tdicola/229b3eeddc12d58fb0bc724a9062aa05\nFONT_HEIGHT = 8\nFONT_WIDTH = 5\nFONT = bytes([\n    0x00, 0x00, 0x00, 0x00, 0x00, # <space>\n    0x3E, 0x5B, 0x4F, 0x5B, 0x3E,\n    0x3E, 0x6B, 0x4F, 0x6B, 0x3E,\n    0x1C, 0x3E, 0x7C, 0x3E, 0x1C,\n    0x18, 0x3C, 0x7E, 0x3C, 0x18,\n    0x1C, 0x57, 0x7D, 0x57, 0x1C,\n    0x1C, 0x5E, 0x7F, 0x5E, 0x1C,\n    0x00, 0x18, 0x3C, 0x18, 0x00,\n    0xFF, 0xE7, 0xC3, 0xE7, 0xFF,\n    0x00, 0x18, 0x24, 0x18, 0x00,\n    0xFF, 0xE7, 0xDB, 0xE7, 0xFF,\n    0x30, 0x48, 0x3A, 0x06, 0x0E,\n    0x26, 0x29, 0x79, 0x29, 0x26,\n    0x40, 0x7F, 0x05, 0x05, 0x07,\n    0x40, 0x7F, 0x05, 0x25, 0x3F,\n    0x5A, 0x3C, 0xE7, 0x3C, 0x5A,\n    0x7F, 0x3E, 0x1C, 0x1C, 0x08,\n    0x08, 0x1C, 0x1C, 0x3E, 0x7F,\n    0x14, 0x22, 0x7F, 0x22, 0x14,\n    0x5F, 0x5F, 0x00, 0x5F, 0x5F,\n    0x06, 0x09, 0x7F, 0x01, 0x7F,\n    0x00, 0x66, 0x89, 0x95, 0x6A,\n    0x60, 0x60, 0x60, 0x60, 0x60,\n    0x94, 0xA2, 0xFF, 0xA2, 0x94,\n    0x08, 0x04, 0x7E, 0x04, 0x08, # UP\n    0x10, 0x20, 0x7E, 0x20, 0x10, # Down\n    0x08, 0x08, 0x2A, 0x1C, 0x08, # Right\n    0x08, 0x1C, 0x2A, 0x08, 0x08, # Left\n    0x1E, 0x10, 0x10, 0x10, 0x10,\n    0x0C, 0x1E, 0x0C, 0x1E, 0x0C,\n    0x30, 0x38, 0x3E, 0x38, 0x30,\n    0x06, 0x0E, 0x3E, 0x0E, 0x06,\n    0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x5F, 0x00, 0x00,\n    0x00, 0x07, 0x00, 0x07, 0x00,\n    0x14, 0x7F, 0x14, 0x7F, 0x14,\n    0x24, 0x2A, 0x7F, 0x2A, 0x12,\n    0x23, 0x13, 0x08, 0x64, 0x62,\n    0x36, 0x49, 0x56, 0x20, 0x50,\n    0x00, 0x08, 0x07, 0x03, 0x00,\n    0x00, 0x1C, 0x22, 0x41, 0x00,\n    0x00, 0x41, 0x22, 0x1C, 0x00,\n    0x2A, 0x1C, 0x7F, 0x1C, 0x2A,\n    0x08, 0x08, 0x3E, 0x08, 0x08,\n    0x00, 0x80, 0x70, 0x30, 0x00,\n    0x08, 0x08, 0x08, 0x08, 0x08,\n    0x00, 0x00, 0x60, 0x60, 0x00,\n    0x20, 0x10, 0x08, 0x04, 0x02,\n    0x3E, 0x51, 0x49, 0x45, 0x3E,\n    0x00, 0x42, 0x7F, 0x40, 0x00,\n    0x72, 0x49, 0x49, 0x49, 0x46,\n    0x21, 0x41, 0x49, 0x4D, 0x33,\n    0x18, 0x14, 0x12, 0x7F, 0x10,\n    0x27, 0x45, 0x45, 0x45, 0x39,\n    0x3C, 0x4A, 0x49, 0x49, 0x31,\n    0x41, 0x21, 0x11, 0x09, 0x07,\n    0x36, 0x49, 0x49, 0x49, 0x36,\n    0x46, 0x49, 0x49, 0x29, 0x1E,\n    0x00, 0x00, 0x14, 0x00, 0x00,\n    0x00, 0x40, 0x34, 0x00, 0x00,\n    0x00, 0x08, 0x14, 0x22, 0x41,\n    0x14, 0x14, 0x14, 0x14, 0x14,\n    0x00, 0x41, 0x22, 0x14, 0x08,\n    0x02, 0x01, 0x59, 0x09, 0x06,\n    0x3E, 0x41, 0x5D, 0x59, 0x4E,\n    0x7C, 0x12, 0x11, 0x12, 0x7C, # A\n    0x7F, 0x49, 0x49, 0x49, 0x36,\n    0x3E, 0x41, 0x41, 0x41, 0x22,\n    0x7F, 0x41, 0x41, 0x41, 0x3E,\n    0x7F, 0x49, 0x49, 0x49, 0x41,\n    0x7F, 0x09, 0x09, 0x09, 0x01,\n    0x3E, 0x41, 0x41, 0x51, 0x73,\n    0x7F, 0x08, 0x08, 0x08, 0x7F,\n    0x00, 0x41, 0x7F, 0x41, 0x00,\n    0x20, 0x40, 0x41, 0x3F, 0x01,\n    0x7F, 0x08, 0x14, 0x22, 0x41,\n    0x7F, 0x40, 0x40, 0x40, 0x40,\n    0x7F, 0x02, 0x1C, 0x02, 0x7F,\n    0x7F, 0x04, 0x08, 0x10, 0x7F,\n    0x3E, 0x41, 0x41, 0x41, 0x3E,\n    0x7F, 0x09, 0x09, 0x09, 0x06,\n    0x3E, 0x41, 0x51, 0x21, 0x5E,\n    0x7F, 0x09, 0x19, 0x29, 0x46,\n    0x26, 0x49, 0x49, 0x49, 0x32,\n    0x03, 0x01, 0x7F, 0x01, 0x03,\n    0x3F, 0x40, 0x40, 0x40, 0x3F,\n    0x1F, 0x20, 0x40, 0x20, 0x1F,\n    0x3F, 0x40, 0x38, 0x40, 0x3F,\n    0x63, 0x14, 0x08, 0x14, 0x63,\n    0x03, 0x04, 0x78, 0x04, 0x03,\n    0x61, 0x59, 0x49, 0x4D, 0x43,\n    0x00, 0x7F, 0x41, 0x41, 0x41,\n    0x02, 0x04, 0x08, 0x10, 0x20,\n    0x00, 0x41, 0x41, 0x41, 0x7F,\n    0x04, 0x02, 0x01, 0x02, 0x04,\n    0x40, 0x40, 0x40, 0x40, 0x40,\n    0x00, 0x03, 0x07, 0x08, 0x00,\n    0x20, 0x54, 0x54, 0x78, 0x40,\n    0x7F, 0x28, 0x44, 0x44, 0x38,\n    0x38, 0x44, 0x44, 0x44, 0x28,\n    0x38, 0x44, 0x44, 0x28, 0x7F,\n    0x38, 0x54, 0x54, 0x54, 0x18,\n    0x00, 0x08, 0x7E, 0x09, 0x02,\n    0x18, 0xA4, 0xA4, 0x9C, 0x78,\n    0x7F, 0x08, 0x04, 0x04, 0x78,\n    0x00, 0x44, 0x7D, 0x40, 0x00,\n    0x20, 0x40, 0x40, 0x3D, 0x00,\n    0x7F, 0x10, 0x28, 0x44, 0x00,\n    0x00, 0x41, 0x7F, 0x40, 0x00,\n    0x7C, 0x04, 0x78, 0x04, 0x78,\n    0x7C, 0x08, 0x04, 0x04, 0x78,\n    0x38, 0x44, 0x44, 0x44, 0x38,\n    0xFC, 0x18, 0x24, 0x24, 0x18,\n    0x18, 0x24, 0x24, 0x18, 0xFC,\n    0x7C, 0x08, 0x04, 0x04, 0x08,\n    0x48, 0x54, 0x54, 0x54, 0x24,\n    0x04, 0x04, 0x3F, 0x44, 0x24,\n    0x3C, 0x40, 0x40, 0x20, 0x7C,\n    0x1C, 0x20, 0x40, 0x20, 0x1C,\n    0x3C, 0x40, 0x30, 0x40, 0x3C,\n    0x44, 0x28, 0x10, 0x28, 0x44,\n    0x4C, 0x90, 0x90, 0x90, 0x7C,\n    0x44, 0x64, 0x54, 0x4C, 0x44,\n    0x00, 0x08, 0x36, 0x41, 0x00,\n    0x00, 0x00, 0x77, 0x00, 0x00,\n    0x00, 0x41, 0x36, 0x08, 0x00,\n    0x02, 0x01, 0x02, 0x04, 0x02,\n    0x3C, 0x26, 0x23, 0x26, 0x3C,\n    0x1E, 0xA1, 0xA1, 0x61, 0x12, # Extension starts here\n    0x3A, 0x40, 0x40, 0x20, 0x7A,\n    0x38, 0x54, 0x54, 0x55, 0x59,\n    0x21, 0x55, 0x55, 0x79, 0x41,\n    0x22, 0x54, 0x54, 0x78, 0x42, # a-umlaut\n    0x21, 0x55, 0x54, 0x78, 0x40,\n    0x20, 0x54, 0x55, 0x79, 0x40,\n    0x0C, 0x1E, 0x52, 0x72, 0x12,\n    0x39, 0x55, 0x55, 0x55, 0x59,\n    0x39, 0x54, 0x54, 0x54, 0x59,\n    0x39, 0x55, 0x54, 0x54, 0x58,\n    0x00, 0x00, 0x45, 0x7C, 0x41,\n    0x00, 0x02, 0x45, 0x7D, 0x42,\n    0x00, 0x01, 0x45, 0x7C, 0x40,\n    0x7D, 0x12, 0x11, 0x12, 0x7D, # A-umlaut\n    0xF0, 0x28, 0x25, 0x28, 0xF0,\n    0x7C, 0x54, 0x55, 0x45, 0x00,\n    0x20, 0x54, 0x54, 0x7C, 0x54,\n    0x7C, 0x0A, 0x09, 0x7F, 0x49,\n    0x32, 0x49, 0x49, 0x49, 0x32,\n    0x3A, 0x44, 0x44, 0x44, 0x3A, # o-umlaut\n    0x32, 0x4A, 0x48, 0x48, 0x30,\n    0x3A, 0x41, 0x41, 0x21, 0x7A,\n    0x3A, 0x42, 0x40, 0x20, 0x78,\n    0x00, 0x9D, 0xA0, 0xA0, 0x7D,\n    0x3D, 0x42, 0x42, 0x42, 0x3D, # O-umlaut\n    0x3D, 0x40, 0x40, 0x40, 0x3D,\n    0x3C, 0x24, 0xFF, 0x24, 0x24,\n    0x48, 0x7E, 0x49, 0x43, 0x66,\n    0x2B, 0x2F, 0xFC, 0x2F, 0x2B,\n    0xFF, 0x09, 0x29, 0xF6, 0x20,\n    0xC0, 0x88, 0x7E, 0x09, 0x03,\n    0x20, 0x54, 0x54, 0x79, 0x41,\n    0x00, 0x00, 0x44, 0x7D, 0x41,\n    0x30, 0x48, 0x48, 0x4A, 0x32,\n    0x38, 0x40, 0x40, 0x22, 0x7A,\n    0x00, 0x7A, 0x0A, 0x0A, 0x72,\n    0x7D, 0x0D, 0x19, 0x31, 0x7D,\n    0x26, 0x29, 0x29, 0x2F, 0x28,\n    0x26, 0x29, 0x29, 0x29, 0x26,\n    0x30, 0x48, 0x4D, 0x40, 0x20,\n    0x38, 0x08, 0x08, 0x08, 0x08,\n    0x08, 0x08, 0x08, 0x08, 0x38,\n    0x2F, 0x10, 0xC8, 0xAC, 0xBA,\n    0x2F, 0x10, 0x28, 0x34, 0xFA,\n    0x00, 0x00, 0x7B, 0x00, 0x00,\n    0x08, 0x14, 0x2A, 0x14, 0x22,\n    0x22, 0x14, 0x2A, 0x14, 0x08,\n    0x55, 0x00, 0x55, 0x00, 0x55, # 176 (25% block) missing in old code\n    0xAA, 0x55, 0xAA, 0x55, 0xAA, # 50% block\n    0xFF, 0x55, 0xFF, 0x55, 0xFF, # 75% block\n    0x00, 0x00, 0x00, 0xFF, 0x00,\n    0x10, 0x10, 0x10, 0xFF, 0x00,\n    0x14, 0x14, 0x14, 0xFF, 0x00,\n    0x10, 0x10, 0xFF, 0x00, 0xFF,\n    0x10, 0x10, 0xF0, 0x10, 0xF0,\n    0x14, 0x14, 0x14, 0xFC, 0x00,\n    0x14, 0x14, 0xF7, 0x00, 0xFF,\n    0x00, 0x00, 0xFF, 0x00, 0xFF,\n    0x14, 0x14, 0xF4, 0x04, 0xFC,\n    0x14, 0x14, 0x17, 0x10, 0x1F,\n    0x10, 0x10, 0x1F, 0x10, 0x1F,\n    0x14, 0x14, 0x14, 0x1F, 0x00,\n    0x10, 0x10, 0x10, 0xF0, 0x00,\n    0x00, 0x00, 0x00, 0x1F, 0x10,\n    0x10, 0x10, 0x10, 0x1F, 0x10,\n    0x10, 0x10, 0x10, 0xF0, 0x10,\n    0x00, 0x00, 0x00, 0xFF, 0x10,\n    0x10, 0x10, 0x10, 0x10, 0x10,\n    0x10, 0x10, 0x10, 0xFF, 0x10,\n    0x00, 0x00, 0x00, 0xFF, 0x14,\n    0x00, 0x00, 0xFF, 0x00, 0xFF,\n    0x00, 0x00, 0x1F, 0x10, 0x17,\n    0x00, 0x00, 0xFC, 0x04, 0xF4,\n    0x14, 0x14, 0x17, 0x10, 0x17,\n    0x14, 0x14, 0xF4, 0x04, 0xF4,\n    0x00, 0x00, 0xFF, 0x00, 0xF7,\n    0x14, 0x14, 0x14, 0x14, 0x14,\n    0x14, 0x14, 0xF7, 0x00, 0xF7,\n    0x14, 0x14, 0x14, 0x17, 0x14,\n    0x10, 0x10, 0x1F, 0x10, 0x1F,\n    0x14, 0x14, 0x14, 0xF4, 0x14,\n    0x10, 0x10, 0xF0, 0x10, 0xF0, # ok\n    0x00, 0x00, 0x1F, 0x10, 0x1F,\n    0x00, 0x00, 0x00, 0x1F, 0x14,\n#   0x00, 0x00, 0x00, 0xFC, 0x14, # any new after this line breaks\n    0x00, 0x00, 0x00, 0x00, 0x00  # 255 also a <space>\n])\n\ndef convert_color_RGB888_RGB565(R,G,B): # Convert RGB888 to RGB565\n    return (((G&0b00011100)<<3) + ((B&0b11111000)>>3)<<8) + (R&0b11111000) + ((G&0b11100000)>>5)\n\ndef character(asc, xt, yt, sz, r, g, b):  # Single character sz is size: 1 or 2\n    cc = convert_color_RGB888_RGB565(r,g,b)\n    code = asc * 5    # 5 bytes per character\n    for ii in range(5):\n        line = FONT[code + ii]\n        for yy in range(8):\n            if (line >> yy) & 0x1:\n                LCD.pixel(ii*sz+xt,yy*sz+yt,cc) \n                if sz > 1:\n                    LCD.pixel(ii*sz+xt+1,yy*sz+yt,cc)\n                    LCD.pixel(ii*sz+xt,yy*sz+yt+1,cc)\n                    LCD.pixel(ii*sz+xt+1,yy*sz+yt+1,cc)\n                if sz == 3:\n                    LCD.pixel(ii*sz+xt,  yy*sz+yt+2,cc)\n                    LCD.pixel(ii*sz+xt+1,yy*sz+yt+2,cc)\n                    LCD.pixel(ii*sz+xt+2,yy*sz+yt+2,cc)\n                    LCD.pixel(ii*sz+xt+2,yy*sz+yt,cc)\n                    LCD.pixel(ii*sz+xt+2,yy*sz+yt+1,cc)\n\n# Print a string\ndef prnt_st(asci, xx, yy, sz,r,g,b):  # Text string\n    if sz == 1: move = 6\n    if sz == 2: move = 11\n    if sz == 3: move = 17 \n    for letter in(asci):\n        asci = ord(letter)\n        character(asci,xx,yy,sz,r,g,b)\n        xx = xx + move\n\n# Center text string\ndef cntr_st(s,y,sz,r,g,b): # Centres text on line y\n    if sz == 1: w = 6\n    if sz == 2: w = 11\n    if sz == 3: w = 17 \n    gap = int((width - len(s) * w)/2)\n    prnt_st(s,gap,y,sz,r,g,b)\n\nLCD.fill(LCD.black)    \nprnt_st(\"Hello world! Red\", 20, 80, 2, 255, 0, 0)\nprnt_st(\"Hello world! Green\", 20, 100, 2, 0, 255, 0)\nprnt_st(\"Hello world! Blue\", 20, 120, 2, 0, 0, 255)\nprnt_st(\"Hello world! White\", 20, 140, 2, 255, 255, 255)\nLCD.show()\n
"},{"location":"lessons/","title":"Python Project-Based Learning: Building Clocks and Watches with Smart Watch Displays","text":"

These lessons are general to all of our kits. We often use the Pico OLED for many examples since that is the kit we have been using for the longest time.

These lessons focus on using functions like the MicroPython localtime() to get the time in your current timezone.

They also focus on drawing scalable numbers on different size displays and drawing times on larger color displays.

Here are some sample lesson plans for a 14-week semimester hich school course on learning computational thining by using MicroPython to write clock and smartwatch application. This course assumes no prior knowledge of programming.

"},{"location":"lessons/#weeks-1-2-introduction-to-python-and-basic-concepts","title":"Weeks 1-2: Introduction to Python and Basic Concepts","text":""},{"location":"lessons/#lesson-1-introduction-to-python","title":"Lesson 1: Introduction to Python","text":""},{"location":"lessons/#lesson-2-hello-world","title":"Lesson 2: Hello, World!","text":""},{"location":"lessons/#lesson-3-variables-and-data-types","title":"Lesson 3: Variables and Data Types","text":""},{"location":"lessons/#lesson-4-basic-operations","title":"Lesson 4: Basic Operations","text":""},{"location":"lessons/#weeks-3-4-control-structures-and-functions","title":"Weeks 3-4: Control Structures and Functions","text":""},{"location":"lessons/#lesson-5-conditionals","title":"Lesson 5: Conditionals","text":""},{"location":"lessons/#lesson-6-loops","title":"Lesson 6: Loops","text":""},{"location":"lessons/#lesson-7-functions","title":"Lesson 7: Functions","text":""},{"location":"lessons/#weeks-5-6-working-with-libraries-and-hardware","title":"Weeks 5-6: Working with Libraries and Hardware","text":""},{"location":"lessons/#lesson-8-importing-libraries","title":"Lesson 8: Importing Libraries","text":""},{"location":"lessons/#lesson-9-introduction-to-hardware-interaction","title":"Lesson 9: Introduction to Hardware Interaction","text":""},{"location":"lessons/#weeks-7-8-project-1-basic-digital-clock","title":"Weeks 7-8: Project 1: Basic Digital Clock","text":""},{"location":"lessons/#lesson-10-planning-the-project","title":"Lesson 10: Planning the Project","text":""},{"location":"lessons/#lesson-11-building-a-basic-digital-clock","title":"Lesson 11: Building a Basic Digital Clock","text":""},{"location":"lessons/#weeks-9-10-intermediate-python-concepts","title":"Weeks 9-10: Intermediate Python Concepts","text":""},{"location":"lessons/#lesson-12-lists-and-dictionaries","title":"Lesson 12: Lists and Dictionaries","text":""},{"location":"lessons/#lesson-13-error-handling","title":"Lesson 13: Error Handling","text":""},{"location":"lessons/#weeks-11-12-project-2-analog-clock-display","title":"Weeks 11-12: Project 2: Analog Clock Display","text":""},{"location":"lessons/#lesson-14-planning-the-project","title":"Lesson 14: Planning the Project","text":""},{"location":"lessons/#lesson-15-building-an-analog-clock-display","title":"Lesson 15: Building an Analog Clock Display","text":""},{"location":"lessons/#weeks-13-14-advanced-python-concepts-and-optimization","title":"Weeks 13-14: Advanced Python Concepts and Optimization","text":""},{"location":"lessons/#lesson-16-more-about-functions","title":"Lesson 16: More About Functions","text":""},{"location":"lessons/#lesson-17-optimization-techniques","title":"Lesson 17: Optimization Techniques","text":""},{"location":"lessons/#weeks-15-16-final-project-customizable-clockwatch","title":"Weeks 15-16: Final Project: Customizable Clock/Watch","text":""},{"location":"lessons/#lesson-18-planning-the-project","title":"Lesson 18: Planning the Project","text":""},{"location":"lessons/#lesson-19-building-a-customizable-clockwatch","title":"Lesson 19: Building a Customizable Clock/Watch","text":""},{"location":"lessons/#lesson-20-final-project-presentations","title":"Lesson 20: Final Project Presentations","text":""},{"location":"lessons/#ongoing-reflection-and-feedback","title":"Ongoing: Reflection and Feedback","text":"

This outline provides a balance between teaching Python concepts and applying them to real-world projects, which can keep students engaged and help them to better retain what they've learned. Tailor this outline to meet the specific needs and pace of your students, and ensure to provide plenty of opportunities for students to practice their skills, ask questions, and get feedback.

"},{"location":"lessons/00-hardware-config/","title":"Hardware Configuration Files","text":"

All our current displays use the SPI interface. The alternative microcontroller bus, I2C, is about 10x slower than SPI. The SPI intrface interface typically uses 7 wires, which can get a little complex. Just a single wire broken or two wires crossed can will cause the display to not work.

We have dozens of sample programs for each kit. And the exact connections on our kits vary. Rather than hard-coding the hardware pin numbers in each example, we can move them to a separate config.py file. We can then just import that file into each of our examples.

Here is a sample config.py file:

# Dan's Robot Labs configuration file for ILI9341 clock project\n# The colors on the SPI bus cable are:\n# 3.3v power - red\n# SCK - orange\n# MISO/Data - yellow\n# DC - green\n# RESET - blue\n# GND - black\n# CS - purple\n\nSCK_PIN = 2\nMISO_PIN = 3 # labeled SDI(MOSI) on the back of the display\nDC_PIN = 4\nRESET_PIN = 5\nCS_PIN = 6\nROTATION = 90\n\nWIDTH=320\nHEIGHT=240\nBUTTON_1_PIN = 14 # one up from lower-left corner\nBUTTON_2_PIN = 15 # lower left corner with USB on top\n

To use this configuration file you will need to do two things:

  1. import the config file (don't add the .py extension)
  2. Add the prefix config. to each value you would like to reference.
import config\n\nWIDTH=config.WIDTH\n
"},{"location":"lessons/01-drawing-coordinates/","title":"Drawing Coordinates","text":"

Screen Coordinates Lab on Robot Faces

"},{"location":"lessons/01-get-local-time/","title":"Getting Local Time","text":""},{"location":"lessons/01-get-local-time/#lesson-overview","title":"Lesson Overview","text":"

In this lesson, we will learn how to get the local date and time from the MicroPython runtime. We will learn the components of the list of numbers returned by these functions.

MicroPython provides a very convenient function called localtime() that will get the time from the host computer that you connect to via your USB cable. Your local computer has a clock and knows the time in the local timezone. This information is sent to MicroPython runtime when the computer is connected to the USB cable.

Note

The gmtime() function returns the time in UTC (Coordinated Universal Time) time. Use this if you want time stamps that are not dependent on time zone and daylight savings rules. For example, log files should use this function.

"},{"location":"lessons/01-get-local-time/#sample-code","title":"Sample Code","text":"
import time\n\nprint(time.localtime())\n
"},{"location":"lessons/01-get-local-time/#result","title":"Result","text":"

Local time returns a read-only tuple:

(2023, 2, 28, 18, 54, 7, 1, 59)\n
"},{"location":"lessons/01-get-local-time/#extracting-specific-datetime-components","title":"Extracting Specific Date/Time Components","text":"
from time import localtime\n\nnow = localtime()\nprint(now)\n\nprint('year:',    now[0])\nprint('month:',   now[1])\nprint('day:',     now[2])\nprint('hour:',    now[3])\nprint('minute:',  now[4])\nprint('sec:',     now[5])\nprint('weekday:', now[6])\nprint('day of year:', now[7])\n
"},{"location":"lessons/01-get-local-time/#format-of-the-localtime","title":"Format of the Localtime","text":"

The format is a \"8-tuple\" of integers in the format (year, month, mday, hour, minute, second, weekday, yearday)

Most of the values are pretty intuitive to figure out. There are a few notes.

  1. Hour is the hour in 24-hour format. If we want to display the hour in 12-hour format we will need to do some formatting.
  2. Weekday is a numeric code that runs from 0 for Monday to 6 for Sunday.
  3. Yearday is the day of the year. January 1st is the fist day of the year so it is a 1.
"},{"location":"lessons/01-get-local-time/#printing-formatted-dates","title":"Printing Formatted Dates","text":""},{"location":"lessons/01-get-local-time/#sample-code_1","title":"Sample Code","text":"
import time\n\nnow = time.localtime()\nlabel = ('year', 'month', 'mday', 'hour', 'minute', 'second', 'weekday', 'yearday')\nfor i in range(8):\n    print(label[i], ':', now[i])\n\nprint()\nprint(\"Date: {}/{}/{}\".format(now[1], now[2], now[0]))\nprint(\"Time: {}:{}\".format(now[3], now[4]))\n
"},{"location":"lessons/01-get-local-time/#results","title":"Results","text":"
year : 2023\nmonth : 2\nmday : 28\nhour : 19\nminute : 20\nsecond : 26\nweekday : 1\nyearday : 59\n\nDate: 2/28/2023\nTime: 19:20\n
"},{"location":"lessons/01-get-local-time/#label-formatting","title":"Label Formatting","text":"

We can also add labels to make the date and time easier to read.

import time\n\ndays = ('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday',\n        'Sunday')\nmonths = ('Jan', 'Feb', 'March', 'April', 'May', 'June', 'July',\n          'Aug', 'Sept', 'Oct', 'Nov', 'Dec')\n\nnow = time.localtime()\n\nlabel = ('year', 'month', 'mday', 'hour', 'minute', 'second', 'weekday', 'yearday')\n\nmonth_number = now[1]\nmonth_name = months[month_number]\nweekday_number = now[6]\nday_name = days[weekday_number]\nhour_number = now[3]\nif hour_number < 13:\n    hour_12 = hour_number\n    am_pm = 'am'\nelse:\n    hour_12 = hour_number - 12\n    am_pm = 'pm'\n\nprint()\nprint(\"Date: {} {} {}, {}\".format(day_name, month_name, now[2], now[0]))\nprint(\"Time: {}:{} {}\".format(hour_12, now[4], am_pm))\n
"},{"location":"lessons/01-get-local-time/#label-formatted-results","title":"Label Formatted Results","text":"
Date: Tuesday March 28, 2023\nTime: 7:46 pm\n
"},{"location":"lessons/01-get-local-time/#modifying-the-thonny-parameters","title":"Modifying the Thonny Parameters","text":"

By default Thonny syncs the host time at power up.

We can disable this.

import time\nprint(time.localtime())\n

(2021, 1, 1, 0, 0, 22, 4, 1)\n
This was printed 22 seconds after power up.

The Pico \"W\" can sync with the NTP functions.

from mpython import *\nimport ntptime\nimport secrets\n\n# summary\nmywifi=wifi()\nmywifi.connectWiFi(secrets.wifi_ssid, secrets.wifi_pass)\n\nprint(\"Local time before synchronization\uff1a%s\" %str(time.localtime()))\nntptime.settime()\nprint(\"Local time after synchronization\uff1a%s\" %str(time.localtime()))\n

Result:

Local time before synchronization\uff1a(2021, 1, 1, 0, 3, 31, 4, 1)\nLocal time after synchronization\uff1a(2023, 10, 16, 20, 16, 59, 0, 289)\n
"},{"location":"lessons/01-get-local-time/#references","title":"References","text":""},{"location":"lessons/02-display-text/","title":"Display Time and Date in Text","text":"
from utime import localtime, sleep\nfrom LCD_1inch28 import LCD_1inch28\n\nLCD = LCD_1inch28()  \n\ndays = ('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday',\n        'Sunday')\nmonths = ('Jan', 'Feb', 'March', 'April', 'May', 'June', 'July',\n          'Aug', 'Sept', 'Oct', 'Nov', 'Dec')\nlabel = ('year', 'month', 'mday', 'hour', 'minute', 'second', 'weekday', 'yearday')\n\ndef format_time(now):\n    hour_number = now[3]\n    if hour_number < 13:\n        hour_12 = hour_number\n        am_pm = 'am'\n    else:\n        hour_12 = hour_number - 12\n        am_pm = 'pm'\n    minutes_num = now[4]\n    if minutes_num < 10:\n        min_leading_zero = '0'\n    else:\n        min_leading_zero = ''\n    seconds_num = now[5]\n    if seconds_num < 10:\n        sec_leading_zero = '0'\n    else:\n        sec_leading_zero = ''\n    return \"{}:{}{}:{}{} {}\".format(hour_12, min_leading_zero, minutes_num, sec_leading_zero, seconds_num, am_pm)\n\ndef format_date(now):\n    month_number = now[1]\n    month_name = months[month_number - 1]\n    weekday_number = now[6]\n    day_name = days[weekday_number]\n    hour_number = now[3]\n    return \"{} {} {}, {}\".format(day_name, month_name, now[2], now[0])\n\nwhile(True):\n    now = localtime()\n    # print(now)\n    LCD.fill(LCD.black)    \n    LCD.text(format_time(now), 77, 50, LCD.white)\n    LCD.text(format_date(now), 40, 80, LCD.white)\n    LCD.show()\n    sleep(1)\n
"},{"location":"lessons/03-drawing-face/","title":"Drawing Watch Faces","text":"

An analog watch ususally has hands that are drawn from the center to the edge of the display.

The lines or marks that indicate the hours are often referred to as \"hour indices\" or \"hour markers.\"

The smaller lines that indicate the minutes are called \"minute indices\" or \"minute markers.\"

For simple lines, we can use the line(x1,y1, x2,y2,color) function which draws a sinle pixel line from the starting point at (x1,y1) to the end pint at (x2,y2) using the fifth color parameter. For monochrome clocks the color 1 is whtie and 0 is black.

For thinker lines we can use the display.rect() function.

Some watches have more complex hands that are composed of multiple geometries such as triangles.

For most modern display drivers we can use the MicroPython poly() function to draw polygons on the screen. These are part of the MicroPython framebuf function.

Some older drivers don't yet support these functions, so we might have to craft our own functions.

"},{"location":"lessons/03-drawing-face/#references","title":"References","text":"

Instructables on Filled Circles and Triangles by TonyGo2

"},{"location":"lessons/04-larger-fonts/","title":"Larger Fonts","text":"

Many display libraries use the MicroPython framebuf standards that only provide for a single font. This is sometimes fine for simple small displays, but it is sometimes insufficient for some projects where either a smaller or larger font is used.

The ssd1306 driver is a good example of the default behaviour of text drawing. Only a single font is suppied.

display.text('Hello, World!', 0, 0, 1)\ndisplay.show()\n

Some display drivers allow you to change the default font:

LCD160 Set Font

Font to Py Command

Sample Font Files

"},{"location":"lessons/04-larger-fonts/#references","title":"References","text":"

Peter Hinch's Font to Python Utility - a utility takes input a font file in ttf or otf form together with a height in pixels and outputs a Python source file containing the font as a bitmap.

Peter Hinch's Nano GUI

Les' Lab: Designing your own Pico fonts - Raspberry Pi Pico display fonts in MicroPython: How to design fonts for your Pi Pico project.

"},{"location":"lessons/05-buttons/","title":"Buttons","text":"

Our standard watch kit places two buttons on the breadboard. You can use these buttons to set the time.

The buttons are connected to the lower-left corner of the pico using GPIO 14 and GPIO 15.

Instructions on how to use these buttons are covered in our MicroPython class:

Using Buttons in MicroPython

We suggest using the top blue button to change the mode of operation. As you press this you cycle through various modes of your clock or watch. Here are some sample modes for a clock:

  1. Run mode
  2. Set current time hour
  3. Set current time minute
  4. Set alarm hour
  5. Set alarm minute

After you are in a given mode, the bottom button can be used to cycle through the options. Remember to get the current value for the cycle for each mode. So if you are adjusting the hour you have to make sure the cycle value starts at the current hour.

"},{"location":"lessons/05-buttons/#sample-button-mode-code","title":"Sample Button Mode Code","text":"
ifrom utime import sleep, ticks_ms\nfrom machine import Pin\n\n# Sample Raspberry Pi Pico MicroPython button press example with a debounce delay value of 200ms in the interrupt handler\n\n# Config\nMODE_BUTTON_PIN = 14\nCYCLE_BUTTON_PIN = 15\nmode = 0 # the count of times the button has been pressed\ncycle = 0\nlast_time = 0 # the last time we pressed the button\n\nbuiltin_led = machine.Pin(25, Pin.OUT)\n# The lower left corner of the Pico has a wire that goes through the buttons upper left and the lower right goes to the 3.3 rail\nmode_pin = machine.Pin(MODE_BUTTON_PIN, machine.Pin.IN, machine.Pin.PULL_UP)\ncycle_pin = machine.Pin(CYCLE_BUTTON_PIN, machine.Pin.IN, machine.Pin.PULL_UP)\n\n# This function gets called every time the button is pressed.  The parameter \"pin\" is not used.\ndef button_pressed_handler(pin):\n    global mode, cycle, last_time\n    new_time = ticks_ms()\n    # if it has been more that 1/5 of a second since the last event, we have a new event\n    if (new_time - last_time) > 200:\n        # this should be pin.id but it does not work\n        if '14' in str(pin):\n            mode +=1\n        else:\n            cycle +=1\n        # last, we update the last time we got an ISR here\n        last_time = new_time\n\n\n# now we register the handler function when the button is pressed\nmode_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\ncycle_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\n\n# This is for only printing when a new button press count value happens\nold_mode = 0\nold_cycle = 0 \n\nwhile True:\n    # only print on change in the button_presses value\n    if mode != old_mode:\n        print('New Mode:', mode)\n        builtin_led.toggle()\n        old_mode = mode\n    if cycle != old_cycle:\n        print('New Cycle:', cycle)\n        builtin_led.toggle()\n        old_cycle = cycle\n    sleep(.1)\n
"},{"location":"lessons/06-draw-performance/","title":"Drawing Performance","text":"

One of the challenges we face when updating the watch display is that refreshing an entire screen using a relatively slow SPI interface means that we need to be thoughtful about updating the displays.

By default, many screen drivers update every pixel of the screen when the user does a show() operation. For small monochrome screens this is not usually a problem. But for larger color screens the draw times can lead to slow updates.

Let's do a little math to see when drawing performance becomes a problem. Remember that the human eye can's really see screen updates that occur faster than about 30 frames per second. That is why most film movies were filled at 25 frames per second.

To calculate the full-screen draw time we need to calculate the total number of bits we need to send and then calculate the time it takes to send these bits. We can then check our math by looking at timestamps just before we draw and after we finish the drawing.

Let's start out with our favorite clock screen: the 128X64 monochrome OLED screen.

  1. Width = 128
  2. Height = 64
  3. Bits per Pixel = 1

Total bits = 128 * 64 * 1 = 8,192 bits = 1024 bytes

Now we need to also know the transfer speed of our display interface. Although there are both I2C and SPI versions of these displays, we usually prefer the SPI that should transfer data at about

Pi Pico SPI LCD using a frame buffer to get 30fps animation - ILI9341 and ST7789 MicroPython drivers

"},{"location":"lessons/06-draw-performance/#sample-timer-code","title":"Sample Timer Code","text":"

We can calculate the time to draw the full screen by recording the number of clock ticks in microseconds before and after we do a screen update.

from utime import ticks_us\n\nstart = ticks_us()\nscreen_update()\nend = ticks_us()\nprint('Execution time in microseconds:', end - start)\n

On the OLED screen, we get a result that is around 10 milliseconds which is 100 screen updates per second.

"},{"location":"lessons/06-draw-performance/#references","title":"References","text":"
  1. Notes on the very slow refresh rate for ST7735
"},{"location":"lessons/07-drawing-digits/","title":"Drawing Clock Digits","text":""},{"location":"lessons/07-drawing-digits/#the-seven-segments-of-a-numeric-digit","title":"The Seven Segments of a Numeric Digit","text":"

Since the early history of computing, numeric displays were created using a grouping of seven individual lights to display a single digit. These are called Seven Segment Displays. An example is shown in the image below:

The segments are labeled \"a\" through \"g\" starting at the top and going around in a clockwise direction. Note that the seventh segment is in the center element is the \"g\" segment.

Technically, many displays have an 8th segment for the decimal point (DP). To keep things simple we will just focus on the main seven segments in this lesson.

Clocks also usually have a colon that separates the hours and minutes and an AM/PM indicator for 12-hour displays. We will be treating these and independent drawing components in this lab. Many digital clocks have the colon flash on and off every second.

"},{"location":"lessons/07-drawing-digits/#the-segment-map","title":"The Segment Map","text":"

To start out, we need to create a \"digit-to-segment map\" data structure. We use this to help us figure out what segments to turn on for any given digit.

We will also need to convert the segment letters into integers. These integers will form the index of an array.

To write our code, we ask, for any given digit, is the segment on or off?

To do this, we can create an array of segments like this:

segmentMapping = [\n  #a, b, c, d, e, f, g\n  [1, 1, 1, 1, 1, 1, 0], # 0\n  [0, 1, 1, 0, 0, 0, 0], # 1\n  [1, 1, 0, 1, 1, 0, 1], # 2\n  [1, 1, 1, 1, 0, 0, 1], # 3\n  [0, 1, 1, 0, 0, 1, 1], # 4\n  [1, 0, 1, 1, 0, 1, 1], # 5\n  [1, 0, 1, 1, 1, 1, 1], # 6\n  [1, 1, 1, 0, 0, 0, 0], # 7\n  [1, 1, 1, 1, 1, 1, 1], # 8\n  [1, 1, 1, 1, 0, 1, 1]  # 9\n];\n

For any given digit like \"2\" we can then just pass the index of \"2\" to a Python list to get a list of the segments to turn on. That line of code looks like this:

segmentOn = segmentMapping[digit];\n

This will return the segments that are to be turned on for any given input digit. For example:

segmentOn = segmentMapping[2];\nprint(segmentOn)\n# returns:\n[1, 1, 0, 1, 1, 0, 1]\n

We now have the data structure to do the drawing. We will do this in two steps.

  1. Draw the three horizontal lines for a digit if they are on
  2. Draw all vertical lines for a given digit, keeping in mind that we need to only draw half the way down the digit for the upper left and upper right vertical segments (1 and 5)

We will start out with a simple digit drawing using a single-pixel width line. Later we will add a parameter to change the thickness of the line. But we want to take this one-step-at-a-time.

"},{"location":"lessons/07-drawing-digits/#drawing-horizontal-segments","title":"Drawing Horizontal Segments","text":"

This sample code will step through the horizontal lines for segments 0, 3 and 6. It will check using the segmentOn[i] line which will ONLY be true if that segment should be turned on for a given digit.

Next, we figure out how high to draw the horizontal line. To do this we will need to figure out the vertical offset (yOffset). The top line (segment 0) has no offset and the bottom line has the full height of the digit.

Unlike the vertical lines, the horizontal lines will span the entire width of the area. That makes our code simple because the line will have a horizontal length of exactly the x plus the width of the digit.

# reasonable test values for a small display\nx = 10\ny = 10\nwidth = 20\nheight = 30\n# Horizontal segments\n  for i in [0, 3, 6]:\n    # check if this digit has the segment on\n    if (segmentOn[i]):\n      if (i==0): # top\n          yOffset = 0 \n      if (i==3):\n          yOffset = height # bottom element\n      if (i==6):\n          yOffset = height // 2 # bottom\n      # draw a signal thin line\n      oled.line(x, y+yOffset, x + width, y+yOffset, 1)\n
"},{"location":"lessons/07-drawing-digits/#drawing-the-vertical-segments","title":"Drawing the Vertical Segments","text":"

Next, we need to create some code to draw the four vertical segments. This is a little more complex because the lines do NOT span the full height of the digit.

# Draw Vertical segments\nx = 10\ny = 10\nwidth = 20\nheight = 30\n# Draw the vertical segments ur, lr, ll, ul\nfor i in [1, 2, 4, 5]:\n    if (segmentOn[i]) :\n        # top two segments\n        if (i==1 or i==5):\n            startY = 0\n            endY = height // 2\n        if (i==2 or i==4):\n            startY = height // 2\n            endY = height\n        # left segments\n        if (i==4 or i==5): xOffset = 0\n        # right segments\n        if (i==1 or i==2): xOffset = width\n        oled.line(x+xOffset, y+startY, x+xOffset, y+endY, 1)\n
"},{"location":"lessons/07-drawing-digits/#testing-our-drawing","title":"Testing Our Drawing","text":"

We can now wrap the code above in a drawDigit() function, and we will test each of the digits 0-9 in a small test program.

Our first version of drawDigit is modeled after the rect() function. After we pass in the digit, it takes in the (x,y) and width and height parameters and the color value.

Draw Digit Function Parameters:

  1. The digit to display - an integer in the range 0-9
  2. x of the upper left corner
  3. y of the upper left corner
  4. the width of the digit (about 20 is a good value)
  5. the height of the digit
  6. the color (0=black, 1=white)

Here is our testing program that draws digits 0-9 in the center of a standard 128x64 monochrome OLED display:

# Lab 20: Draw Digits with just the line function\n# this lab uses the line() function to draw the segments\nimport machine\nimport utime\nimport ssd1306\nfrom utime import sleep, localtime\nled = machine.Pin(25, machine.Pin.OUT)\n\nSCL=machine.Pin(2) # SPI CLock\nSDA=machine.Pin(3) # SPI Data\nspi=machine.SPI(0, sck=SCL, mosi=SDA, baudrate=100000)\n\nRES = machine.Pin(4)\nDC = machine.Pin(5)\nCS = machine.Pin(6)\nWIDTH = 128\nHEIGHT = 64\n\noled = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)\n\nsegmentMapping = [\n  #a, b, c, d, e, f, g\n  [1, 1, 1, 1, 1, 1, 0], # 0\n  [0, 1, 1, 0, 0, 0, 0], # 1\n  [1, 1, 0, 1, 1, 0, 1], # 2\n  [1, 1, 1, 1, 0, 0, 1], # 3\n  [0, 1, 1, 0, 0, 1, 1], # 4\n  [1, 0, 1, 1, 0, 1, 1], # 5\n  [1, 0, 1, 1, 1, 1, 1], # 6\n  [1, 1, 1, 0, 0, 0, 0], # 7\n  [1, 1, 1, 1, 1, 1, 1], # 8\n  [1, 1, 1, 1, 0, 1, 1]  # 9\n];\n\n\n# x and y are the center of the digit, size is the center to edge\ndef drawDigit(digit, x, y, width, height, color):\n  # get a list of the segments that are on for this digit\n  segmentOn = segmentMapping[digit];\n\n  # Draw the horizontal segments: top, bottem, middle\n  for i in [0, 3, 6]:\n    if (segmentOn[i]):\n      if (i==0): # top\n          yOffset = 0 \n      if (i==3):\n          yOffset = height # bottom element\n      if (i==6):\n          yOffset = height // 2 # middle line\n      oled.line(x, y+yOffset, x + width, y+yOffset, 1)\n\n  # Draw the vertical segments ur, lr, ll, ul\n  for i in [1, 2, 4, 5]:\n    if (segmentOn[i]) :\n        # top two segments\n        if (i==1 or i==5):\n            startY = 0\n            endY = height // 2\n        if (i==2 or i==4):\n            startY = height // 2\n            endY = height\n        # left segments\n        if (i==4 or i==5): xOffset = 0\n        # right segments\n        if (i==1 or i==2): xOffset = width\n        oled.line(x+xOffset, y+startY, x+xOffset, y+endY, 1)\n\n\noled.fill(0)\noled.text('Lab 12: rect', 0, 0, 1)\nx = 10 # upper left corner x\ny = 10 # upper left corner y\nw = 20 # digit width\nh = 30 # digit height\n\nwhile True:\n    for i in range(0, 10):\n\n        print(i)\n        # create an outline one px away from the drawing region\n        oled.rect(x-2, y-2, w+5, h+5, 1)\n        # draw one digit\n        drawDigit(i, x, y, w, h, 1)\n        # draw a second digit\n        #drawDigit(i, x + w + 4, w, h, t, 1)\n        oled.text(str(i), 0, 54, 1)\n        oled.show()\n        sleep(2)\n        oled.fill(0)\n

This drawDigit() is very handy. We just need to give the function some parameters and it will draw the right digit at a location (x,y) as the upper left corner with the correct width and height!

But the lines are somewhat thin. A more robust implementation will also allow us the change the thickness of the segments. That will be more complex since we will have to adjust the starting point of where we draw each segment based on the thickness of the segment. We will also need to use a rectangle, not a line. This requires we figure out the exact width and height before we draw.

We explore that function in the next lesson.

"},{"location":"lessons/09-digit-functions/","title":"Creating Digit Functions","text":"

In the last section, we drew a seven-segment digit number using the line function. In this section, we will build a better function that will add a flexible new parameter for the thickness of the digit segments. We will then test the function under a variety of conditions and see how we can make the function easier to use.

"},{"location":"lessons/09-digit-functions/#new-thickness-parameters","title":"New Thickness Parameters","text":"

Placing the rectangles correctly within a seven-segment display when we have many drawing parameters requires some precision. We will use a decomposition process and take \"one step at a time\" to work out the details.

"},{"location":"lessons/09-digit-functions/#draw-segment-function","title":"Draw Segment Function","text":"

Here is our revised parameters list:

  1. The digit to display - an integer in the range 0-9
  2. x of the upper left corner
  3. y of the upper left corner
  4. the width of the digit (about 20 is a good value)
  5. the height of the digit
  6. (NEW) the thickness of each segment (3 or 4 are good values)
  7. the color (0=black, 1=white)

Here is our revised function for drawing a single digit with the segment thickness added.

# digit is the number to display\n# x and y are upper-left-corner\n# width and height are the dimensions of the digit\n# thickness is the width of the line segments\n# color is 1 for white and 0 for black\ndef drawDigit(digit, x, y, width, height, thickness, color):\n  # get a list of the segments that are on for this digit\n  segmentOn = segmentMapping[digit];\n\n  # Draw the horizontal segments: top, bottom, middle\n  for i in [0, 3, 6]:\n    if (segmentOn[i]):\n      if (i==0): # top\n          yOffset = 0 \n      if (i==3):\n          yOffset = height - thickness # bottom element\n      if (i==6):\n          yOffset = height // 2 - thickness // 2# bottom\n      # oled.line(x - size, y+yOffset-size, x + size, y+yOffset-size, 1);\n      oled.fill_rect(x, y+yOffset, width, thickness, color)\n\n  # Draw the vertical segments ur, lr, ll, ul\n  for i in [1, 2, 4, 5]:\n    if (segmentOn[i]) :\n      # upper vertical lines\n      if (i==1 or i==5):\n          startY = y\n          endY = y + height // 2\n      # lower two vertical lines (2=lower right and 4=lower left)\n      if (i==2 or i==4):\n          startY = y + height // 2\n          endY = y + height\n      if (i==4 or i==5): xOffset = 0\n      if (i==1 or i==2): xOffset = width-thickness\n\n      oled.fill_rect(x+xOffset, startY, thickness, endY-startY, color)\n

Let's now take a look at how we had to modify our code.

The first item to note is that when we calculate a starting point to draw, we can't just draw at the edge of the region. We need to back off our initial drawing point by the width of the segment.

Here is an example of subtracting the thickness from y when we draw the bottom segment:

yOffset = height - thickness # bottom element\n

"},{"location":"lessons/09-digit-functions/#full-test-program","title":"Full Test Program","text":"
# Clock Lab 20: Draw Seven Segments\n# this lab uses the fill_rect function to draw the segments\nimport machine\nimport utime\nimport ssd1306\nfrom utime import sleep, localtime\nled = machine.Pin(25, machine.Pin.OUT)\n\nSCL=machine.Pin(2) # SPI CLock\nSDA=machine.Pin(3) # SPI Data\nspi=machine.SPI(0, sck=SCL, mosi=SDA, baudrate=100000)\n\nRES = machine.Pin(4)\nDC = machine.Pin(5)\nCS = machine.Pin(6)\n\noled = ssd1306.SSD1306_SPI(128, 64, spi, DC, RES, CS)\n\nsegmentMapping = [\n  #a, b, c, d, e, f, g\n  [1, 1, 1, 1, 1, 1, 0], # 0\n  [0, 1, 1, 0, 0, 0, 0], # 1\n  [1, 1, 0, 1, 1, 0, 1], # 2\n  [1, 1, 1, 1, 0, 0, 1], # 3\n  [0, 1, 1, 0, 0, 1, 1], # 4\n  [1, 0, 1, 1, 0, 1, 1], # 5\n  [1, 0, 1, 1, 1, 1, 1], # 6\n  [1, 1, 1, 0, 0, 0, 0], # 7\n  [1, 1, 1, 1, 1, 1, 1], # 8\n  [1, 1, 1, 1, 0, 1, 1]  # 9\n];\n\n# digit is the numer to display\n# x and y are upper-left-corner\n# width and height are the dimensions of the digit\n# thickness is the width of the line segments\n# color is 1 for white and 0 for black\ndef drawDigit(digit, x, y, width, height, thickness, color):\n  # get a list of the segments that are on for this digit\n  segmentOn = segmentMapping[digit];\n\n  # Draw the horizontal segments: top, bottom, middle\n  for i in [0, 3, 6]:\n    if (segmentOn[i]):\n      if (i==0): # top\n          yOffset = 0 \n      if (i==3):\n          yOffset = height - thickness # bottom element\n      if (i==6):\n          yOffset = height // 2 - thickness // 2# bottom\n      # oled.line(x - size, y+yOffset-size, x + size, y+yOffset-size, 1);\n      oled.fill_rect(x, y+yOffset, width, thickness, color)\n\n  # Draw the vertical segments ur, lr, ll, ul\n  for i in [1, 2, 4, 5]:\n    if (segmentOn[i]) :\n      # upper vertical lines\n      if (i==1 or i==5):\n          startY = y\n          endY = y + height // 2\n      # lower two vertical lines (2=lower right and 4=lower left)\n      if (i==2 or i==4):\n          startY = y + height // 2\n          endY = y + height\n      if (i==4 or i==5): xOffset = 0\n      if (i==1 or i==2): xOffset = width-thickness\n\n      oled.fill_rect(x+xOffset, startY, thickness, endY-startY, color)\n\noled.fill(0)\noled.text('Lab 12: rect', 0, 0, 1)\nx = 10 # upper left corner x\ny = 10 # upper left corner y\nw = 20 # digit width\nh = 30 # digit height\nt = 3\n\nwhile True:\n    for i in range(0, 10):\n\n        print(i)\n        # create an outline on px away from the drawing region\n        oled.rect(x-2, y-2, w+4, h+4, 1)\n        # draw one digit\n        drawDigit(i, x, y, w, h, t, 1)\n        # draw a second digit\n        #drawDigit(i, x + w + 4, w, h, t, 1)\n        oled.text(str(i), 0, 54, 1)\n        oled.show()\n        sleep(2)\n        oled.fill(0)\n
"},{"location":"lessons/12-draw-time-functions/","title":"Draw a Digital Clock Time Function","text":"

In this section, we will create a new function called drawTime. The input will be the upper left starting point (x,w), width and height, hours and minutes and the format (12-hour or 24-hour) and the color (black or white).

This lab will build in the prior lab that displayed a single digit.

Let's first of all create a plan of how we will layout our digital clock display.

Our goal is to create a function that will be reusable over many types of displays with little or no modification. This will require us to think through the potential ways our users will use this function and anticipate their needs so they are not tempted to rewrite our code on their own.

"},{"location":"lessons/12-draw-time-functions/#12-hour-display-format","title":"12-Hour Display Format","text":"

Here is an example of our 12-hour clock layout:

Note that there are six horizontal x-dimension numbers and three vertical y-dimension numbers that you can adjust to create a pleasant interface.

"},{"location":"lessons/12-draw-time-functions/#automatically-scaling-the-layout","title":"Automatically Scaling the Layout","text":"

It would be tempting to just hard-code the layout using a single set of numbers. But what if the user wanted a smaller or larger portion of their display used to display the time?

As the user selects the height and width of their clock we would like to scale these numbers accordingly. The absolute values of these numbers is not too important now. We just want to get the relative values down. To start the process, I did a layout on an 11-inch wide drawing and I assigned the width of each digit to be 3.0.

# horizontal\nx1 = .73\nx2 = .73\nx3 = 3.0\nx4 = 1.35\nx5 = .5\nx6 = .35\nx7 = 1.5\nx8 = .35\nx9 = .5\n# Vertical\ny1 = 5.31\ny2 = 1.58\ny3 = .68\ny4 = .68\n

We need to make the width of the display be x1 + x2 + 3*x3 + x4 + x5 + x6 + x7 which is

# Scale Test\n\ndef drawTime12h(hour, minute, x, y, width, height):\n    # horizontal\n    x1 = .73\n    x2 = .73\n    x3 = 3.0 # digit width\n    x4 = 1.35\n    x5 = .5\n    x6 = .35\n    x7 = 1.5\n    x8 = .35\n    x9 = .5 # colon width and height\n\n    # vertical\n    y1 = 5.31\n    y2 = 1.58 # to top colon\n    y3 = .68 # between colons\n\n    total_width = x1 + x2 + 3*x3 + x4 + x5 + x6 + x7\n    print(\"total width:\", total_width)\n    total_height = y1\n\n    # calculate the scaling ratios\n    x_scale = width / total_width\n    y_scale = height / total_height \n\n    digit_width = x3 * x_scale\n    digit_height = y1 * y_scale\n    print(\"x scale:\", x_scale, \"y scale:\", y_scale)\n\n    if hour > 12:\n        hour12 = hour - 12\n\n    # hour tens\n    if hour12 > 10:\n        oled.rect(x,y,int(x1*x_scale),int(y1*y_scale))\n\n    # hour ones x,y,w,h\n    drawDigit(hour % 10, int((x + x1 + x2)*x_scale), y, int(x3*x_scale), int(y1*y_scale))\n\n    # minute tens ones digit, x,y,w,h\n    min_tens_x = int((x + x1 + x2 + x3 + x4)*x_scale)\n    drawDigit(minute // 10, min_tens_x, y, int(x3*x_scale), int(y1*y_scale))\n\n    # minute  ones x,y,w,h\n    min_ones_x = int((x + x1 + x2 + 2*x3 + x4 + x5)*x_scale)\n    drawDigit(minute % 10, min_ones_x, y, int(x3*x_scale), int(y1*y_scale))\n\n    # draw colon\n    colon_size = x9\n    # top colon\n    oled.rect(int((x+x1+x2+x3+x8)*x_scale), y+int(y2*y_scale), colon_size, colon_size)\n    # bottom colon\n    oled.rect(int((x+x1+x2+x3+x8)*x_scale), y+int((y2+y3)*y_scale), colon_size, colon_size)\n\n    # AM/PM\n    if hours < 12:\n        am_pm_text = 'am'\n    else:\n        am_pm_text = 'pm'\n    am_pm_x = min_ones_x + int((x3+x6)*x_scale)\n    oled.text(am_pm_text, am_pm_x, y + int(y1*y_scale)) \n\ndrawTime12h(hour, minute, 0, 0, 100, 50)\n
"},{"location":"lessons/15-scale-testing/","title":"Digital Clock Display Scale Testing","text":"

We would like to test our digital clock display at a large range of different sizes to make sure our scale functions work. For example, the function should work over a range of sizes from 20 to 110 pixels wide.

Here is a test driver for the drawTime12 function:

# test times\ntt = [[00,00], [12,59], [12,00], [8,33], [13,59], [23,59]]\n\nwhile True:\n    ## tti is the test time index\n    for tti in range(0,len(tt)):\n        for size in range(20, 110):\n            oled.fill(0) # clear screen\n            # bounding box for the entire screen\n            # oled.rect(0,0, WIDTH-1, HEIGHT-1, 1)\n\n            # bounding box for the time region\n            height = int(size*.5)\n            oled.rect(0, 0, size+20, height+5, 1)\n            # print(\"h=\", tt[tti][0], \"min:\", tt[tti][1])\n            drawTime12h(tt[tti][0], tt[tti][1], 2, 2, size, height, 1)\n            oled.text(str(tt[tti][0]) + ':' + str(tt[tti][1]), 0, 54, 1)\n            oled.show()\n            sleep(.1)\n
"},{"location":"lessons/15-scale-testing/#sample-code","title":"Sample Code","text":"
# Lab 42: Scale Test test v2\n\nfrom machine import Pin\nfrom time import sleep\nimport ssd1306\nimport config\n\nSCL=Pin(config.SCL_PIN) # SPI CLock\nSDA=Pin(config.SDA_PIN) # SPI Data\n\nRES = Pin(config.RESET_PIN) # Reset\nDC = Pin(config.DC_PIN) # Data/command\nCS = Pin(config.CS_PIN) # Chip Select\nWIDTH = config.WIDTH\nHEIGHT = config.HEIGHT\n\nspi=machine.SPI(config.SPI_BUS, sck=SCL, mosi=SDA, baudrate=100000)\noled = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)\n\nsegmentMapping = [\n  #a, b, c, d, e, f, g\n  [1, 1, 1, 1, 1, 1, 0], # 0\n  [0, 1, 1, 0, 0, 0, 0], # 1\n  [1, 1, 0, 1, 1, 0, 1], # 2\n  [1, 1, 1, 1, 0, 0, 1], # 3\n  [0, 1, 1, 0, 0, 1, 1], # 4\n  [1, 0, 1, 1, 0, 1, 1], # 5\n  [1, 0, 1, 1, 1, 1, 1], # 6\n  [1, 1, 1, 0, 0, 0, 0], # 7\n  [1, 1, 1, 1, 1, 1, 1], # 8\n  [1, 1, 1, 1, 0, 1, 1]  # 9\n];\n# digit is the numer to display\n# x and y are upper-left-corner\n# width and height are the dimensions of the digit\n# thickness is the width of the line segments\n# color is 1 for white and 0 for black\ndef drawDigit(digit, x, y, width, height, thickness, color):\n  # get a list of the segments that are on for this digit\n  if digit > 9 or digit < 0:\n      print('Error: digit out of range:', digit)\n      return -1\n  segmentOn = segmentMapping[digit];\n\n  # Draw the horizontal segments: top, bottom, middle\n  for i in [0, 3, 6]:\n    if (segmentOn[i]):\n      if (i==0): # top\n          yOffset = 0 \n      if (i==3):\n          yOffset = height - thickness # bottom element\n      if (i==6):\n          yOffset = height // 2 - thickness // 2# bottom\n      # oled.line(x - size, y+yOffset-size, x + size, y+yOffset-size, 1);\n      oled.fill_rect(x, y+yOffset, width, thickness, color)\n\n  # Draw the vertical segments ur, lr, ll, ul\n  for i in [1, 2, 4, 5]:\n    if (segmentOn[i]) :\n      # upper vertical lines\n      if (i==1 or i==5):\n          startY = y\n          endY = y + height // 2\n      # lower two vertical lines (2=lower right and 4=lower left)\n      if (i==2 or i==4):\n          startY = y + (height // 2)\n          endY = y + height\n      if (i==4 or i==5): xOffset = 0\n      if (i==1 or i==2): xOffset = width-thickness\n\n      oled.fill_rect(x+xOffset, startY, thickness, endY-startY, color)\n\ndef drawTime12h(hour, minute, x, y, width, height, color):\n\n    # this does not scale\n    am_pm_font_height = 8\n\n    # horizontal spacing\n    x1 = .73 # width of the hour tens digit (1 or off)\n    x2 = .25 # space between hour tens and hour ones\n    x3 = 3.0 # digit width\n    x4 = 1.35 # space beteen hour ones and minute tens\n    x5 = .5 # space between minute tens and minute ones\n    x6 = .35 # space between minute ones and am/pm text\n    x7 = 1.5 # an/pm text width\n\n    # colon x positioning\n    x8 = .35 # space between hour ones and colon\n    x9 = .5 # colon width and height\n\n    # vertical\n    y1 = 5.31 # digit height\n    y2 = (y1 // 2) - .1 # to top colon\n    y3 = 1.5 # space between colons\n\n    total_width = x1 + x2 + 3*x3 + x4 + x5 + x6 + x7\n    # print(\"total width:\", total_width)\n    total_height = y1\n\n    # calculate the scaling ratios\n    x_scale = width / total_width\n    y_scale = height / total_height \n\n    digit_width = x3 * x_scale\n    digit_height = y1 * y_scale\n    # print(\"x scale:\", x_scale, \"y scale:\", y_scale)\n\n\n    time_width = total_width * x_scale\n    # print(\"time_width:\", time_width)\n\n    # thickness calculation based on a fraction of the width\n    thickness = int(.25 * digit_width)\n\n    if hour > 12:\n        hour12 = hour - 12\n    else: hour12 = hour\n\n    hour_minute = hour % 10\n    if hour_minute == 0:\n        hour_minute = 2\n\n    # hour tens display \n    if hour12 == 0 or hour12 > 9:\n        oled.fill_rect(x,y,int(x1*x_scale),int(y1*y_scale), color)\n\n    # hour ones for the d, x,y,w,h, t,c\n    if hour12 == 0:\n        hour12 = 12\n    hour_ones_x = int((x + x1 + x2)*x_scale)\n    drawDigit(hour12 % 10, hour_ones_x, y, int(x3*x_scale), int(y1*y_scale), thickness, color)\n\n    # minute tens ones digit, x,y,w,h\n    min_tens_x = int((x + x1 + x2 + x3 + x4)*x_scale)\n    drawDigit(minute // 10, min_tens_x, y, int(x3*x_scale), int(y1*y_scale), thickness, color)\n\n    # minute ones digit d, x,y,w,h, t, c\n    min_ones_x = int((x + x1 + x2 + 2*x3 + x4 + x5)*x_scale)\n    drawDigit(minute % 10, min_ones_x, y, int(x3*x_scale), int(y1*y_scale), thickness, color)\n\n    # draw colon\n    colon_size = int(x9*x_scale)\n    # top colon\n    oled.fill_rect(int((x+x1+x2+x3+x8)*x_scale), y+int(y2*y_scale), colon_size, colon_size, color)\n    # bottom colon\n    oled.fill_rect(int((x+x1+x2+x3+x8)*x_scale), y+int((y2+y3)*y_scale), colon_size, colon_size, color)\n\n    # AM/PM\n    if hour < 12:\n        am_pm_text = 'am'\n    else:\n        am_pm_text = 'pm'\n    # but here.  It displays outside the width\n    am_pm_x = min_ones_x + int((x3+x6)*x_scale)\n    # print('am/pm x:', am_pm_x)\n    oled.text(am_pm_text, am_pm_x, y + int(y1*y_scale) - am_pm_font_height, color)\n\n    oled.show()\n\n# test times\ntt = [[00,00], [12,59], [12,00], [8,33], [13,59], [23,59]]\n\nwhile True:\n    ## tti is the test time index\n    for tti in range(0,len(tt)):\n        for size in range(20, 110):\n            oled.fill(0) # clear screen\n            # bounding box for the entire screen\n            # oled.rect(0,0, WIDTH-1, HEIGHT-1, 1)\n\n            # bounding box for the time region\n            height = int(size*.5)\n            oled.rect(0, 0, size+20, height+5, 1)\n            # print(\"h=\", tt[tti][0], \"min:\", tt[tti][1])\n            drawTime12h(tt[tti][0], tt[tti][1], 2, 2, size, height, 1)\n            oled.text(str(tt[tti][0]) + ':' + str(tt[tti][1]), 0, 54, 1)\n            oled.show()\n            sleep(.1)\n
"},{"location":"lessons/30-ili9341/","title":"ILI9341 Drawing","text":"

The ILI9341 chip is a 262144-color single-chip SOC driver for a TFT liquid crystal display with a resolution of 240x320 dots (RGB).

Source Code

"},{"location":"lessons/30-ili9341/#configuration-file","title":"Configuration file","text":"

These examples will all use the following config file:

config.py

SCK_PIN = 2\nMISO_PIN = 3 # labeled SDI(MOSI) on the back of the display\nDC_PIN = 4\nRESET_PIN = 5\nCS_PIN = 6\nROTATION = 90\n\nWIDTH=320\nHEIGHT=240\n

We also put the color names in a colors.py file that calculates the colors using a name.

from ili9341 import color565\n\nWHITE = color565(255,255,255)\nBLACK = color565(0,0,0)\nRED = color565(255,0,0)\nORANGE = color565(255,128,0)\nYELLOW = color565(255,255,0)\nGREEN = color565(0,255,0)\nBLUE = color565(0,0,255)\nCYAN = color565(0,255,255)\nPURPLE = color565(255,0,255)\n
"},{"location":"lessons/30-ili9341/#simple-drawing","title":"Simple Drawing","text":""},{"location":"lessons/40-machine-info/","title":"Machine Info","text":""},{"location":"lessons/40-machine-info/#modules","title":"Modules","text":"
help('modules')\n
"},{"location":"lessons/40-machine-info/#clock-frequency","title":"Clock Frequency","text":"
import machine\nprint('freq:', machine.freq())\n
"},{"location":"lessons/40-machine-info/#operating-system","title":"Operating System","text":"
os.uname()\n
os.statvfs(path)\n
"},{"location":"lessons/40-machine-info/#platform","title":"Platform","text":"
platform.platform()\n
"},{"location":"prompts/01-course-description/","title":"Generating a Course Description for the Clocks and Watches with MicroPython Course","text":"

Prompt

Please generate a course description for a 14-week course for high-school students called \"Building Clocks and Watches with MicroPython\".

The course is a hands on-course that uses the Raspberry Pi Pico W and low-cost displays to create a fun collection of projects for kids learning how to program.

We start with a simple clock that uses a LED clock display and then progress to using a web service and an 128x64 OLED display. We also have demonstrations of building stopwatches and timers by integrating buttons and knobs to adjust settings.

An underlying theme is to promote computational thinking including abstraction, decomposition, algorithms and pattern matching.

The course provides students with a breadboard, Raspberry Pi Pico Ws, LEDs and OLED graphical displays.

Use the 2001 Bloom Taxonomy to guild the course description.

"},{"location":"prompts/01-course-description/#response","title":"Response","text":"

Course Title: Building Clocks and Watches with MicroPython Duration: 14 Weeks Appropriate for: High School Students

Course Description: In this 14-week, hands-on course, high school students will learn to design and program functional timekeeping devices---from simple LED clocks to sophisticated stopwatches and web-connected displays---using MicroPython on the Raspberry Pi Pico W. Throughout the course, students will be guided by the 2001 revision of Bloom's Taxonomy, moving from foundational knowledge to creative invention.

  1. Remember: Students begin by identifying essential electronic components---breadboards, microcontrollers, buttons, knobs, and displays---and recalling the fundamental concepts of MicroPython programming.
  2. Understand: Through teacher-led demonstrations, students explore how timing functions, input/output operations, and hardware connections work together to create a basic LED clock. They deepen their grasp of computational thinking by examining concepts like abstraction (focusing on relevant details) and decomposition (breaking large problems into smaller parts).
  3. Apply: As their skills grow, students practice wiring and programming a 128\u00d764 OLED display, interfacing with web services to fetch real-time data. They also implement stopwatches and timers, applying algorithms and pattern-matching techniques to manage user inputs and display outputs accurately.
  4. Analyze: Learners diagnose and troubleshoot various clock behaviors, studying how code efficiency and hardware choices influence device performance. They dissect timing errors, lag issues, and integration problems, refining both their programming and problem-solving skills.
  5. Evaluate: Working in teams, students test multiple clock designs to compare accuracy, power consumption, and user experience. They assess trade-offs---such as display readability vs. complexity---and refine solutions based on feedback, cost, and practicality.
  6. Create: Ultimately, students design and assemble their own custom timekeeping projects, combining LEDs, OLED displays, push buttons, and encoders. This final challenge showcases their ability to unify hardware and code into a polished, functioning prototype.

By the end of the course, students will have built a diverse collection of digital clocks, stopwatches, and timers while gaining a rich understanding of electronics, computational thinking, and MicroPython. They will leave empowered to continue exploring the world of embedded systems and creative hardware projects.

"},{"location":"prompts/02-concept-enumeration/","title":"Concept Enumeration","text":"
  1. MicroPython
  2. Raspberry Pi Pico W
  3. Breadboard
  4. Powering Your Clocks and Watches
  5. USB Power
  6. Battery Power
  7. Battery Backup
  8. Battery Drain Calculation
  9. LED Clock Displays
  10. Real-time Clock
  11. The Thoney IDE
  12. Synchronizing time from the PC
  13. the localtime function
  14. MicroPython Syntax
  15. Loops
  16. Conditionals
  17. Functions
  18. Libraries
  19. Loading MicroPython Drivers
  20. Printing time to the console
  21. MicroPython Drawing Libraries
  22. Drawing Lines
  23. Drawing Text
  24. Drawing Pixels
  25. Drawing Rectangles
  26. Seven Segment Digits
  27. Drawing Circles
  28. Drawing Ellipses
  29. Drawing Arcs
  30. Drawing Polygons
  31. Changing Fonts
  32. Using MicroPython Drivers
  33. OLED Displays
  34. The Draw_Digit Function
  35. SmartWatch Displays
  36. Using 16 bits to represent color
  37. Framebuffer
  38. SPI interfaces
  39. Updating the entire screen with SPI
  40. Incremental screen drawing
  41. Drawing hands on an analog clock
  42. Using the sine and cosine functions
  43. Drawing tic marks on a watch face
  44. Drawing numbers on a watch face
  45. Loading Custom fonts
"},{"location":"prompts/02-concept-enumeration/#response","title":"Response","text":"

MicroPython Raspberry Pi Pico W Breadboard Powering Your Clocks and Watches USB Power Battery Power Battery Backup Battery Drain Calculation LED Clock Displays Real-time Clock (RTC) Overview The Thonny IDE Synchronizing Time from the PC The localtime() Function MicroPython Syntax Loops Conditionals Functions Libraries Loading MicroPython Drivers Printing Time to the Console MicroPython Drawing Libraries Drawing Lines Drawing Text Drawing Pixels Drawing Rectangles Drawing Circles Drawing Ellipses Drawing Arcs Drawing Polygons Changing Fonts Using MicroPython Drivers OLED Displays Smartwatch Displays Using 16 Bits to Represent Color Framebuffer in MicroPython SPI Interfaces Updating the Entire Screen via SPI Incremental Screen Drawing Drawing Hands on an Analog Clock Using math.sin() and math.cos() Drawing Tick Marks on a Watch Face Drawing Numbers on a Watch Face Loading Custom Fonts Using the time Module for Scheduling Using Internal Timers on the Raspberry Pi Pico External RTC Modules (e.g., DS1307, DS3231) Setting the System Clock from an External RTC Network Time Synchronization (NTP) Setting Up Wi\u2011Fi on the Pico W Working with MicroPython\u2019s network Module Parsing JSON (or Other) Time Data from APIs Displaying Date and Day of Week Creating a 7-Segment Style Digital Display Custom Segment-Based Displays Minimizing Power Consumption (Sleep Modes) Waking from Deep Sleep on RTC Alarm Setting Time with Buttons Using Interrupts for Button Input Button Debouncing Techniques Using A Rotary Encoder Setting Alarms with the RTC Connecting Speakers Playing a Sound Sound Encoding Timer-Based Events Playing Chimes Cuckoo Clocks Generating Beep or Alarm Tones Integrating External Sensors Temperature and Humidity Displaying Sensor Data Accelerometers for Orientation Tilt-Based Wake for Watch Display Ultra-Low Power Using E-Paper Displays Partial Refresh on E-Paper Techniques to Minimize Ghosting on E-Paper Using TFT Displays (e.g., ST7735, ILI9341) Comparing Display Technologies Color Animations on Watch Faces Flicker Reduction Techniques Double Buffering for Smooth Animations Using the micropython-ufont Library Asynchronous Code with uasyncio Time-Based Tasks with uasyncio Handling Concurrency of Time Updates & UI Using a Watchdog Timer for Reliability Logging & Debugging Time Issues Sleep Scheduling Tools for Measuring Battery Usage Hardware Documentation 3D Printing Watch Enclosures Using math Module for Geometry Time Zones & UTC Offsets Daylight Saving Time Using the Built-In Filesystem on Pico Logging Time Data to an SD Card Using I\u00b2C Sensors & Modules Menu Systems for Watch Settings Servo Motors for Mechanical Pointers Converting Between datetime Objects and Timestamps Generating Waveforms for Audio Implementing Clock Themes Fast Redraw Techniques (Blitting) Partial Screen Updates for Watch Faces Color Conversion & Palettes in MicroPython Handling 12-Hour vs. 24-Hour Formats Wi\u2011Fi\u2013Based Weather & Forecast Updates Using Sensors for Step Counting (Pedometer) Adding a LiPo Charging Circuit Chronograph Functionality Timer Stopwatch World Clocks

"},{"location":"setup/02-purchsing-parts/","title":"Parts Purchasing Guide","text":""},{"location":"setup/02-purchsing-parts/#microcontrollers","title":"MicroControllers","text":""},{"location":"setup/02-purchsing-parts/#raspberry-pi-pico","title":"Raspberry Pi Pico","text":""},{"location":"setup/02-purchsing-parts/#displays","title":"Displays","text":""},{"location":"setup/02-purchsing-parts/#oled-displays","title":"OLED Displays","text":""},{"location":"setup/02-purchsing-parts/#smartwatch-displays","title":"SmartWatch Displays","text":""},{"location":"setup/02-purchsing-parts/#led-strips","title":"LED Strips","text":""},{"location":"setup/02-purchsing-parts/#breadboards","title":"Breadboards","text":""},{"location":"setup/03-approach/","title":"Our Learning Approach","text":"

Our strategy for teaching is to provide a base set of examples of working clocks and watches. We then also provide a set of \"Challenges\" for students to go further.

We also provide a sample set of prompts you can use to feed generative AI systems to create other lesson plans and learning artifacts such as MicroSims to complement these resources.

"},{"location":"sims/","title":"Clocks and Watches Micro Simulations","text":""},{"location":"sims/4-digits/","title":"Example of a Four-Digit Digital Clock Face","text":""},{"location":"sims/analog-face/","title":"Analog Clock Face","text":""},{"location":"sims/learning-graph/","title":"Learning Graph for Clocks and Watches with MicroPython","text":""},{"location":"sims/themes/","title":"Index","text":"

Analog Clock Face Themes

"},{"location":"sims/themes/#background-color","title":"Background Color","text":""},{"location":"sims/themes/#color-selection","title":"Color Selection","text":""},{"location":"sims/themes/#hand-types","title":"Hand Types","text":""},{"location":"sims/themes/#tic-mark-types","title":"Tic Mark Types","text":""},{"location":"sims/themes/#digit-types","title":"Digit Types","text":""},{"location":"sims/themes/#four-digits","title":"Four Digits","text":""},{"location":"sims/themes/#12-digits","title":"12 Digits","text":""}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"MicroPython Clocks and Watches","text":"

Welcome to the MicroPython Clocks and Watches website.

"},{"location":"#our-mission","title":"Our Mission","text":"

The mission of this website is to serve as a resource for teachers, mentors and students who want to learn how to use the popular MicroPython language to enable high-school students to design their own digital clocks and watches using Python. This website contains instructions for finding low-cost parts (under $20/student) and customizing your custom clock and watch displays.

These projects have only recently been possible due to the following developments:

  1. The 2021 release of the powerful $4 Raspberry Pi Pico microcontroller.
  2. The availability of low-cost high-contrast OLED and TFT displays for as low as $7 each. When we use the $4 Raspberry Pi Pico our classrooms can create a development system for under $15.
  3. The support of highly optimized drawing of complex ellipse and polygon shapes into the Framebuf by the MicroPython runtime. This only became available in version 20 of the standard MicroPython runtime released in April of 2023. Before this every clock and watch project used custom math functions that slowed drawing times.
  4. The ability of WiFi-enabled microcontrollers that can synchronize with centralized time services using standard WiFi networks. We use the $5 Raspberry Pi Pico W in many of our projects.

Our goal is to provide fun ways to teach computational thinking to a wide variety of students from 6th to 12th grade. If you can type we have fun lesson plans from drawing simple shapes to complex clock and watch face designs.

"},{"location":"#acknowledgments","title":"Acknowledgments","text":"

I want to thank everyone in the MicroPython community for sharing their code. Each of the displays requires MicroPython drivers that have special features to keep drawing fast. We could not have high-quality lesson plans without your contributions. If I have not referenced the cool features of your drivers, please let me know!

"},{"location":"#feedback-and-comments","title":"Feedback and Comments","text":"

If you have any comments or feedback, please feel free to post these to our GitHub Issues. I don't check these issues every day, so please be patient and connect with others in the MicroPython community if you have urgent questions for a classroom.

Good Luck! Dan McCreary on LinkedIn

Demo code for the Waveshare RP2040 watch display.

Waveshare RP2040-LCD-1.28

Ebay Listing for $21

"},{"location":"about/","title":"About the MicroPython Clocks and Watchs Site","text":"

My STEM students often hold a special facination with timekeeping. I don't know exactly what it is that gets them so excited about these projects. But the bottom line is they quickly become motivated to learn Python to create their own timekeeping devices that are customized to their interests. Owning their own devices and showing off these projects to their friends is also a big plus.

At the core of Project Based Learning (PBL) is the desire to allow students to pick their own projects. My role as a mentor is to have a rich set of projects they can get started on. They may not understand all the components, but to fix and modify the projects they end up learning faster than if I gave them a dozen lectures with PowerPoint.

"},{"location":"about/#the-timeless-allure-of-timekeeping-sparking-a-passion-for-coding-with-micropython-clocks-and-watches","title":"The Timeless Allure of Timekeeping: Sparking a Passion for Coding with MicroPython Clocks and Watches","text":"

In a world driven by digital innovation, the essence of time remains universally compelling. From the ancient sundials to the modern-day smartwatches, humans have always been fascinated by the pursuit of measuring time. This intrigue, particularly among the younger generation, is why a library of fun MicroPython Clocks and Watches serves as an exceptional entry point into the universe of coding and computational thinking.

"},{"location":"about/#tapping-into-the-childs-natural-curiosity","title":"Tapping into the Child's Natural Curiosity","text":"

Children possess an innate sense of wonder. The ticking of a clock or the changing digits on a digital watch captivates their imagination. They question how it works, what makes it tick, and how they can possibly create one of their own. By introducing them to a collection of engaging MicroPython timekeeping projects, we are not only answering their questions but also kindling a flame of creativity and innovation.

"},{"location":"about/#from-boring-lectures-to-tangible-results","title":"From Boring Lectures to Tangible Results","text":"

Traditional pedagogical methods, characterized by long hours of lectures, often fail to hold the attention of young learners. However, project-based learning flips this paradigm. When a child is given the tools and guidance to create their very own digital clock or watch, the learning process transforms. They're no longer passive recipients of information but active creators, brimming with ideas and enthusiasm.

A MicroPython Clock project, for instance, isn\u2019t just a coding endeavor; it's a journey. From understanding the concept, drafting the design, writing the first line of code, to finally witnessing their creation come to life \u2013 every step is a lesson in problem-solving, critical thinking, and perseverance.

"},{"location":"about/#building-blocks-of-computational-thinking","title":"Building Blocks of Computational Thinking","text":"

Computational thinking is a cornerstone of modern education. It\u2019s not just about writing code, but a way of approaching problems logically, breaking them down into smaller tasks, and finding efficient solutions. By creating a timekeeping device, children undergo a practical exercise in computational thinking. They decide the features they want, debug issues, and constantly refine their work, all while having fun!

"},{"location":"about/#social-construction-the-joy-of-shared-creation","title":"Social Construction: The Joy of Shared Creation","text":"

Imagine a part of every school library or a display case where clocks and watches are on display. Each devices can tell its unique story, representing a young coder's journey. Sharing these creations allows children to learn from each other, celebrate their achievements, and draw inspiration. Such a collaborative environment fosters not just coding skills but also empathy, teamwork, and a sense of community. Technically, we call group building projects Social constructivism. And there is ample research to show that students that learn in groups have higer participation rates and can usually develop better outcomes.

"},{"location":"about/#engineered-for-user-experience","title":"Engineered for User Experience","text":"

Great STEM projects don't just fall off a shelf into our laps. They need to be designed and tested carefully using the same agile processes that we use in software development. User Experience (UX) professionals need to help us observe our students using and learning from our lessons.

"},{"location":"about/#in-conclusion","title":"In Conclusion","text":"

A library of MicroPython Clocks and Watches isn't just a collection of projects; it\u2019s a treasure trove of experiences, learnings, and memories. By introducing children to the magic of timekeeping in the digital age, we are equipping them with skills for the future, fostering a love for coding, and most importantly, reminding them that learning can be both fun and profoundly meaningful. The timeless allure of time, coupled with the dynamic world of coding, creates a perfect blend to mold the innovators and thinkers of tomorrow.

"},{"location":"contact/","title":"Contact","text":"

Dan McCreary on LinkedIn

"},{"location":"course-description/","title":"Course Description","text":"

Course Title: Building Clocks and Watches with MicroPython Duration: 14 Weeks Target Audience: High School Students Learning Computational Thinking

Course Description: In this 14-week, hands-on course, high school students will learn to design and program functional timekeeping devices---from simple LED clocks to sophisticated stopwatches and web-connected displays---using MicroPython on the Raspberry Pi Pico W. Throughout the course, students will be guided by hands on labs, moving from foundational knowledge to creative invention.

  1. Remember: Students begin by identifying essential electronic components---breadboards, microcontrollers, buttons, knobs, and displays---and recalling the fundamental concepts of MicroPython programming.
  2. Understand: Through teacher-led demonstrations, students explore how timing functions, input/output operations, and hardware connections work together to create a basic LED clock. They deepen their grasp of computational thinking by examining concepts like abstraction (focusing on relevant details) and decomposition (breaking large problems into smaller parts).
  3. Apply: As their skills grow, students practice wiring and programming a 128\u00d764 OLED display, interfacing with web services to fetch real-time data. They also implement stopwatches and timers, applying algorithms and pattern-matching techniques to manage user inputs and display outputs accurately.
  4. Analyze: Learners diagnose and troubleshoot various clock behaviors, studying how code efficiency and hardware choices influence device performance. They dissect timing errors, lag issues, and integration problems, refining both their programming and problem-solving skills.
  5. Evaluate: Working in teams, students test multiple clock designs to compare accuracy, power consumption, and user experience. They assess trade-offs---such as display readability vs. complexity---and refine solutions based on feedback, cost, and practicality.
  6. Create: Ultimately, students design and assemble their own custom timekeeping projects, combining LEDs, OLED displays, push buttons, and encoders. This final challenge showcases their ability to unify hardware and code into a polished, functioning prototype.

By the end of the course, students will have built a diverse collection of digital clocks, stopwatches, and timers while gaining a rich understanding of electronics, computational thinking, and MicroPython. They will leave empowered to continue exploring the world of embedded systems and creative hardware projects.

"},{"location":"faq/","title":"STEM Robots FAQ","text":""},{"location":"faq/#can-we-print-our-own-chassis-with-a-3d-printer","title":"Can we print our own chassis with a 3D Printer?","text":"

Yes!

"},{"location":"references/","title":"References","text":"
  1. watchmaker - custom watch faces
  2. Makerfabs GC9A01 1.28 Inch Round LCD Module
  3. Dronebotworkshop Using GC9A01 Round LCD Modules
  4. DroneBot Workshop YouTube Video
  5. Mr. Volt YouTube Exotic Round Displays and How to Use Them - this videos demonstrates some very new round displays up to 5 inches. These would be ideal for robot faces but they cost up to $200 for the larger displays.
  6. NTP Pool Project - sites that run the network time protocol
  7. Pico Displays and Fonts
  8. Analog Clock Face Generator - a wonderful way to try out different designs. This web app allows you to change the format of the markers, digits and hands.
  9. Blocklayer Clock Face
  10. Mick Make's YouTube Video: Build a talking Alarm Clock synced to Google calendars - great example of how to use the Google API to access your personal calendar.
"},{"location":"references/#inspirational-electronic-clocks","title":"Inspirational Electronic Clocks","text":"

\"The Clock\" using 1,916 parts by Gislain Benoit

Divoom Times Gate Clock - Five 128x128 color LCD displays.

ClocTech Tube Shaped Nixie Clock available for $49 at WalMart. This clock shines an LED into a Plexiglass plate to give the appearance of a 3D Nixie Tube.

"},{"location":"kits/","title":"Introduction to Clock and Watch Kits","text":""},{"location":"kits/#standard-oled","title":"Standard OLED","text":""},{"location":"kits/#raspberry-pi-and-monchorme-oled","title":"Raspberry Pi and Monchorme OLED","text":""},{"location":"kits/#raspberry-pi-w-and-monchorme-oled","title":"Raspberry Pi W and Monchorme OLED","text":""},{"location":"kits/#lilygo-rp240","title":"LilyGo RP240","text":""},{"location":"kits/gc9a01/","title":"GC9A01 Display","text":"

The gc9a01 is a chip that drives a 240x240 round display that is connected to a microcontroller by an SPI display.

The current gc9a01 drivers are not compatable with the current release of framebuf in the standard MicroPython runtime. Therefore the standard framebuf functions such as ellipse() and poly() functions do not work. This limits the portability of many of or clock and watch example.

The good news is that you can do some drawing operations faster and your code does not have to run the show() command. Functions such as line() will draw directly to the display.

To connect we need to either use a firmware version or load the driver into the /lib directory and we can then use the following code:

from machine import Pin, SPI\nimport gc9a01 as gc9a01\n\n# hardware config\nSCL_PIN = 2\nSDA_PIN = 3\nDC_PIN = 4\nCS_PIN = 5\nRST_PIN = 6\nspi = SPI(0, baudrate=60000000, sck=Pin(SCL_PIN), mosi=Pin(SDA_PIN))\n\n# initialize the display\ntft = gc9a01.GC9A01(\n    spi,\n    dc=Pin(DC_PIN, Pin.OUT),\n    cs=Pin(CS_PIN, Pin.OUT),\n    reset=Pin(RST_PIN, Pin.OUT),\n    rotation=0)\n\ntft.fill(gc9a01.BLACK)\n\n# x, y, width, height\n# red\ntft.fill_rect(50,  75, 50, 60, gc9a01.color565(255,0,0))\n# green\ntft.fill_rect(100, 75, 50, 60, gc9a01.color565(0,255,0))\n# blue\ntft.fill_rect(150, 75, 50, 60, gc9a01.color565(0,0,255))\n
"},{"location":"kits/gc9a01/#rotation","title":"Rotation","text":"

The driver supports 8 different types of rotations:

In our labs we have the connector at the bottom so we use the Portrait rotation of 0 which is the default rotation.

"},{"location":"kits/gc9a01/#references","title":"References","text":"
  1. Russ Hughes - Russ provides firmware images that you can use for both the Pico and Pico W.
    1. Raspberry Pi Pico
    2. Raspberry Pi Pico W
"},{"location":"kits/gc9a01/01-hello-world/","title":"Hello World Test","text":"

We begin by carefully setting up the 7 wires that connect from the gc9a01 graphics controller to the breadboard that we have our Raspberry Pi Pico W connected.

# 01-hello-world-firmware.py\n# \nfrom machine import Pin, SPI\nimport gc9a01\nimport vga1_bold_16x32 as font\n\n# this uses the standard Dupont ribbon cable spanning rows 4-9 on our breadboard\nSCK_PIN = 2 # row 4\nSDA_PIN = 3\nDC_PIN = 4\nCS_PIN = 5\n# GND is row 8\nRST_PIN = 6\n\n# define the SPI intrface\nspi = SPI(0, baudrate=60000000, sck=Pin(SCK_PIN), mosi=Pin(SDA_PIN))\ntft = gc9a01.GC9A01(spi, 240, 240, reset=Pin(RST_PIN, Pin.OUT),\n    cs=Pin(CS_PIN, Pin.OUT), dc=Pin(DC_PIN, Pin.OUT), rotation=0\n)\n\ntft.init()\ntft.fill(0) # fill the screen with black\ntft.text(font, \"Hello world!\", 20, 100, gc9a01.color565(255,0,0), gc9a01.color565(0,0,255))\n
"},{"location":"kits/gc9a01/02-display-time/","title":"Display Time and Date","text":"

Here is a program that will display the time and date including the day-of-the-week and the name of the month.

We use the ``localtime()``` function to get the current time in an array of integers that looks like this:

(2023, 9, 30, 13, 18, 9, 5, 273)\n

The fields are:

  1. Year
  2. Month
  3. Day of Month
  4. Hour in 24-hour format
  5. Minutes
  6. Seconds
  7. Day of Week (Monday = 0, Sunday = 6)
  8. Day of the Year

We could convert each of these numbers to strings and display them. However, most people like to do a bit of formatting such as displaing a 12-hour am/pm format and returning the name of the month and day of the week. Here is the complete progam with the formatting.

# 02-display-time.py\n# \nfrom machine import Pin, SPI\nfrom utime import sleep, localtime\nimport gc9a01\nimport vga1_bold_16x32 as font\n\n# this uses the standard Dupont ribbon cable spanning rows 4-9 on our breadboard\nSCK_PIN = 2 # row 4\nSDA_PIN = 3\nDC_PIN = 4\nCS_PIN = 5\n# GND is row 8\nRST_PIN = 6\n\n# define the SPI intrface\nspi = SPI(0, baudrate=60000000, sck=Pin(SCK_PIN), mosi=Pin(SDA_PIN))\ntft = gc9a01.GC9A01(spi, 240, 240, reset=Pin(RST_PIN, Pin.OUT),\n    cs=Pin(CS_PIN, Pin.OUT), dc=Pin(DC_PIN, Pin.OUT), rotation=0\n)\n\ntft.init()\ntft.fill(0) # fill the screen with black\n\ndays = ('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday',\n        'Sunday')\n# I limit the month names to 5 characters max\nmonths = ('Jan', 'Feb', 'March', 'April', 'May', 'June', 'July',\n          'Aug', 'Sept', 'Oct', 'Nov', 'Dec')\nlabel = ('year', 'month', 'mday', 'hour', 'minute', 'second', 'weekday', 'yearday')\n\ndef format_time(now):\n    hour_number = now[3]\n    if hour_number < 13:\n        hour_12 = hour_number\n        am_pm = 'am'\n    else:\n        hour_12 = hour_number - 12\n        am_pm = 'pm'\n    minutes_num = now[4]\n    if minutes_num < 10:\n        min_leading_zero = '0'\n    else:\n        min_leading_zero = ''\n    seconds_num = now[5]\n    if seconds_num < 10:\n        sec_leading_zero = '0'\n    else:\n        sec_leading_zero = ''\n    return \"{}:{}{}:{}{} {}\".format(hour_12, min_leading_zero, minutes_num, sec_leading_zero, seconds_num, am_pm)\n\ndef day_of_week(now):\n    weekday_number = now[6]\n    day_name = days[weekday_number]\n    return day_name\n\ndef format_date(now):\n    month_number = now[1]\n    month_name = months[month_number - 1]\n    weekday_number = now[6]\n    day_name = days[weekday_number]\n    hour_number = now[3]\n    return \"{} {}, {}\".format(month_name, now[2], now[0])\n\ntft.fill(0) # erase the screen to black\nwhite = gc9a01.color565(255, 255, 255)\nwhile(True):\n    # get the time from the local real-time clock\n    now = localtime()\n    print(now)    \n    tft.text(font, format_time(now), 35, 50, white)\n    tft.text(font, day_of_week(now), 50, 80, white)\n    tft.text(font, format_date(now), 5, 110, white)\n    #tft.show()\n    sleep(1)\n
"},{"location":"kits/gc9a01/03-display-hand/","title":"Display Second Hand","text":"

In this lesson we will use a bit of trigonometry to display a second hand that ticks every second.

from machine import Pin, SPI\nfrom utime import sleep, localtime\nimport math\nimport gc9a01\n\n# this uses the standard Dupont ribbon cable spanning rows 4-9 on our breadboard\nSCK_PIN = 2 # row 4\nSDA_PIN = 3\nDC_PIN = 4\nCS_PIN = 5\n# GND is row 8\nRST_PIN = 6\n\n# define the SPI intrface\nspi = SPI(0, baudrate=60000000, sck=Pin(SCK_PIN), mosi=Pin(SDA_PIN))\ntft = gc9a01.GC9A01(spi, 240, 240, reset=Pin(RST_PIN, Pin.OUT),\n    cs=Pin(CS_PIN, Pin.OUT), dc=Pin(DC_PIN, Pin.OUT), rotation=0\n)\ntft.init()\n\nCENTER = 120\nHAND_LENGTH = 100\n\n# our counter will range from 0 to 59\n# A full circle is 2*Pi radians\nTWO_PI = 3.145175*2\ncounter = 0\nwhite = gc9a01.color565(255, 255, 255)\n\nwhile True:\n    radians = (counter/60)*TWO_PI\n    x = int(math.sin(radians)*HAND_LENGTH)\n    y = -int(math.cos(radians)*HAND_LENGTH)\n    print(radians, x, y)\n    tft.fill(0)\n    tft.line(CENTER, CENTER, CENTER+x,CENTER+y, white)\n    sleep(1)\n    counter += 1\n    # if we are at 60 we start over\n    if counter > 59:\n        counter = 0\n
"},{"location":"kits/gc9a01/04-cycle-fonts/","title":"Cycle Through the Builtin Fonts","text":"

This program will cycle through the built-in fonts showing greetings in different fonts, langauges and colors.

# Adapted from the hersey.py program\nfrom machine import Pin, SPI\nfrom utime import sleep, localtime\nimport random\nimport gc9a01\nimport vga1_bold_16x32 as font\n\n# this uses the standard Dupont ribbon cable spanning rows 4-9 on our breadboard\nSCK_PIN = 2 # row 4\nSDA_PIN = 3\nDC_PIN = 4\nCS_PIN = 5\n# GND is row 8\nRST_PIN = 6\n\n# define the SPI intrface\nspi = SPI(0, baudrate=60000000, sck=Pin(SCK_PIN), mosi=Pin(SDA_PIN))\ntft = gc9a01.GC9A01(spi, 240, 240, reset=Pin(RST_PIN, Pin.OUT),\n    cs=Pin(CS_PIN, Pin.OUT), dc=Pin(DC_PIN, Pin.OUT), rotation=0)\n\n# Load several frozen fonts from flash\n\nimport greeks\nimport italicc\nimport italiccs\nimport meteo\nimport romanc\nimport romancs\nimport romand\nimport romanp\nimport romans\nimport scriptc\nimport scripts\n\n\ndef cycle(p):\n    '''\n    return the next item in a list\n    '''\n    try:\n        len(p)\n    except TypeError:\n        cache = []\n        for i in p:\n            yield i\n            cache.append(i)\n        p = cache\n    while p:\n        yield from p\n\n\nCOLORS = cycle([0xe000, 0xece0, 0xe7e0, 0x5e0, 0x00d3, 0x7030])\n\nFONTS = cycle([\n    greeks, italicc, italiccs, meteo, romanc, romancs,\n    romand, romanp, romans, scriptc, scripts])\n\nGREETINGS = cycle([\n    \"bonjour\", \"buenas noches\", \"buenos dias\",\n    \"good day\", \"good morning\", \"hey\",\n    \"hi-ya\", \"hi\", \"how are you\", \"how goes it\",\n    \"howdy-do\", \"howdy\", \"shalom\", \"welcome\",\n    \"what's happening\", \"what's up\"])\n\ntft.init()\nrow = 120\n\nwhile True:\n    color = next(COLORS)\n    tft.fill(gc9a01.BLACK)\n    tft.draw(next(FONTS), next(GREETINGS), 0, row, color)\n    sleep(0.5)\n
"},{"location":"kits/gc9a01/04-cycle-fonts/#references","title":"References","text":"
  1. Russ Hughes Example
"},{"location":"kits/gc9a01/05-draw-ticks/","title":"Draw Clock Face Ticks","text":"

This program draws 12 short white radial lines around a watch face.

We use the same math we used to draw a single hand. However, in this example we just need to iterate through a range of 0 to 12.

for i in range(0,12):\n    radians = (i/12)*TWO_PI\n    x1 = int(math.sin(radians)*TICK_START)\n    y1 = -int(math.cos(radians)*TICK_START)\n    x2 = int(math.sin(radians)*TICK_END)\n    y2 = -int(math.cos(radians)*TICK_END)\n    tft.line(CENTER+x1, CENTER+y1, CENTER+x2, CENTER+y2, WHITE)\n

Instead of just drawing from the center to a point at (x,y), we need to calculate four numbers. These are the start and ending points of each tick.

"},{"location":"kits/gc9a01/05-draw-ticks/#full-program","title":"Full Program","text":"
from machine import Pin, SPI\nfrom utime import sleep, localtime\nimport math\nimport gc9a01\n\n# this uses the standard Dupont ribbon cable spanning rows 4-9 on our breadboard\nSCK_PIN = 2 # row 4\nSDA_PIN = 3\nDC_PIN = 4\nCS_PIN = 5\n# GND is row 8\nRST_PIN = 6\n\n# define the SPI intrface\nspi = SPI(0, baudrate=60000000, sck=Pin(SCK_PIN), mosi=Pin(SDA_PIN))\ntft = gc9a01.GC9A01(spi, 240, 240, reset=Pin(RST_PIN, Pin.OUT),\n    cs=Pin(CS_PIN, Pin.OUT), dc=Pin(DC_PIN, Pin.OUT), rotation=0\n)\ntft.init()\n\nCENTER = 120\nTICK_START = 80\nTICK_END = 100\n\n# our counter will range from 0 to 59\n# A full circle is 2*Pi radians\nTWO_PI = 3.145175*2\ncounter = 0\nWHITE = gc9a01.color565(255, 255, 255)\nBLACK = gc9a01.color565(0, 0, 0)\ntft.fill(BLACK)\nfor i in range(0,12):\n    radians = (i/12)*TWO_PI\n    x1 = int(math.sin(radians)*TICK_START)\n    y1 = -int(math.cos(radians)*TICK_START)\n    x2 = int(math.sin(radians)*TICK_END)\n    y2 = -int(math.cos(radians)*TICK_END)\n    print(i, radians, x1, y1, x2, y2)  \n    tft.line(CENTER+x1, CENTER+y1, CENTER+x2, CENTER+y2, WHITE)\n
"},{"location":"kits/gc9a01/07-draw-12-digits/","title":"Draw Clock Dial Digits","text":"

This program builds on the previous progam that draws the ticks. It also draws a ditit at the end of each tick.

We use the same math we used to draw a single hand. However, in this example we just need to iterate through a range of 0 to 12.

We have to remember to load our 16x32 fonts. Although this is a bit large for most watch faces, they are easy to read.

Here is the line we added:

import vga1_bold_16x32 as font\n

Note that there are other sizes we can use. A list of the other fonts is here:

GitHub Site for Russ Hughes for GC9a01 bitmap fonts

These fonts include the following sizes:

  1. 8x8
  2. 8x16
  3. 16x16
  4. 16x32

Here is the appropriate line for the normal 16x16:

import vga1_16x16 as font\n

if i == 0:\n    num_str = \"12\"\nelse:\n    num_str = str(i)\ntft.text(font, num_str, CENTER+x3-8, CENTER+y3-16, WHITE)\n
Note because the fonts are 16 wide and 32 high we move 8 to the left and 16 up.

"},{"location":"kits/gc9a01/07-draw-12-digits/#full-program","title":"Full Program","text":"
from machine import Pin, SPI\nfrom utime import sleep, localtime\nimport math\nimport gc9a01\nimport vga1_bold_16x32 as font\n\n\n# this uses the standard Dupont ribbon cable spanning rows 4-9 on our breadboard\nSCK_PIN = 2 # row 4\nSDA_PIN = 3\nDC_PIN = 4\nCS_PIN = 5\n# GND is row 8\nRST_PIN = 6\n\n# define the SPI intrface\nspi = SPI(0, baudrate=60000000, sck=Pin(SCK_PIN), mosi=Pin(SDA_PIN))\ntft = gc9a01.GC9A01(spi, 240, 240, reset=Pin(RST_PIN, Pin.OUT),\n    cs=Pin(CS_PIN, Pin.OUT), dc=Pin(DC_PIN, Pin.OUT), rotation=0\n)\ntft.init()\n\nCENTER = 120\nTICK_START = 50\nTICK_END = 80\nNUM_POS = 100\n\n# our counter will range from 0 to 59\n# A full circle is 2*Pi radians\nTWO_PI = 3.145175*2\ncounter = 0\nWHITE = gc9a01.color565(255, 255, 255)\nBLACK = gc9a01.color565(0, 0, 0)\nBLUE = gc9a01.color565(0, 0, 255)\ntft.fill(BLACK)\nfor i in range(0,12):\n    radians = (i/12)*TWO_PI\n    x1 = int(math.sin(radians)*TICK_START)\n    y1 = -int(math.cos(radians)*TICK_START)\n    x2 = int(math.sin(radians)*TICK_END)\n    y2 = -int(math.cos(radians)*TICK_END)\n    x3 = int(math.sin(radians)*NUM_POS)\n    y3 = -int(math.cos(radians)*NUM_POS)\n    print(i, radians, x1, y1, x2, y2)  \n    tft.line(CENTER+x1, CENTER+y1, CENTER+x2, CENTER+y2, BLUE)\n    if i == 0:\n        num_str = \"12\"\n    else:\n        num_str = str(i)\n    tft.text(font, num_str, CENTER+x3-8, CENTER+y3-16, WHITE)\n

If you use the 16x16 font the offset in the last line are:

    if i == 0:\n        num_str = \"12\"\n        xOffest = 16\n    else:\n        num_str = str(i)\n        xOffest = 8\n    tft.text(font, num_str, CENTER+x3-xOffest, CENTER+y3-8, WHITE)\n

The exception is for the \"12\" at the top of the dial. The \"12\" needed to be exactly centered above the top tick mark.

"},{"location":"kits/gc9a01/07-draw-12-digits/#full-code-for-16x16-clock-face-digits","title":"Full Code for 16x16 Clock Face Digits","text":"
from machine import Pin, SPI\nfrom utime import sleep, localtime\nimport math\nimport gc9a01\nimport vga1_16x16 as font\n\n\n# this uses the standard Dupont ribbon cable spanning rows 4-9 on our breadboard\nSCK_PIN = 2 # row 4\nSDA_PIN = 3\nDC_PIN = 4\nCS_PIN = 5\n# GND is row 8\nRST_PIN = 6\n\n# define the SPI intrface\nspi = SPI(0, baudrate=60000000, sck=Pin(SCK_PIN), mosi=Pin(SDA_PIN))\ntft = gc9a01.GC9A01(spi, 240, 240, reset=Pin(RST_PIN, Pin.OUT),\n    cs=Pin(CS_PIN, Pin.OUT), dc=Pin(DC_PIN, Pin.OUT), rotation=0\n)\ntft.init()\n\nCENTER = 120\nTICK_START = 70\nTICK_END = 100\nNUM_POS = 111\n\n# our counter will range from 0 to 59\n# A full circle is 2*Pi radians\nTWO_PI = 3.145175*2\ncounter = 0\nWHITE = gc9a01.color565(255, 255, 255)\nBLACK = gc9a01.color565(0, 0, 0)\nBLUE = gc9a01.color565(0, 0, 255)\ntft.fill(BLACK)\nfor i in range(0,12):\n    radians = (i/12)*TWO_PI\n    x1 = int(math.sin(radians)*TICK_START)\n    y1 = -int(math.cos(radians)*TICK_START)\n    x2 = int(math.sin(radians)*TICK_END)\n    y2 = -int(math.cos(radians)*TICK_END)\n    x3 = int(math.sin(radians)*NUM_POS)\n    y3 = -int(math.cos(radians)*NUM_POS)\n    print(i, radians, x1, y1, x2, y2)  \n    tft.line(CENTER+x1, CENTER+y1, CENTER+x2, CENTER+y2, BLUE)\n    if i == 0:\n        num_str = \"12\"\n        xOffest = 16\n    else:\n        num_str = str(i)\n        xOffest = 8\n    tft.text(font, num_str, CENTER+x3-xOffest, CENTER+y3-8, WHITE)\n
"},{"location":"kits/gc9a01/08-draw-filled-triangle/","title":"Draw a Filled Triangle","text":"

Analog clock hands can be draw with simple lines. But the display will be more pleasing if we use clock hands that are made of triangles.

If the framebuf functions were supported in the driver drawing a filled triangle would be easy. We would just put the three points in an array and call the poly() with a fill option.

However, the current driver does not support the framebuf functions. To overcome this limiation we will need to write our own function that will fill all the points in a triangle.

Our founction must take in the three points and a color and draw the traingle.

"},{"location":"kits/gc9a01/08-draw-filled-triangle/#draw-filled-triangle","title":"Draw Filled Triangle","text":"

Without going into too much detail, here is the algorithm we will use dto draw a filled triangle. Note that only the last line does drawing using the line() function. You will note that it must get the points in order before the main loops run. This version also checks for divide by zero errors.

def drawFilledTriangle(x1, y1, x2, y2, x3, y3, color=WHITE):\n    def swap(x, y):\n        return y, x\n\n    # get our points in order\n    if y1 > y2:\n        x1, x2 = swap(x1, x2)\n        y1, y2 = swap(y1, y2)\n    if y1 > y3:\n        x1, x3 = swap(x1, x3)\n        y1, y3 = swap(y1, y3)\n    if y2 > y3:\n        x2, x3 = swap(x2, x3)\n        y2, y3 = swap(y2, y3)\n\n    for y in range(y1, y3+1):\n        if y2 - y1 != 0 and y < y2:\n            xa = x1 + (x2 - x1) * (y - y1) // (y2 - y1)\n        elif y3 - y1 != 0:\n            xa = x1 + (x3 - x1) * (y - y1) // (y3 - y1)\n        else:\n            continue\n\n        if y3 - y2 != 0 and y >= y2:\n            xb = x2 + (x3 - x2) * (y - y2) // (y3 - y2)\n        elif y3 - y1 != 0:\n            xb = x1 + (x3 - x1) * (y - y1) // (y3 - y1)\n        else:\n            continue\n\n        if xa > xb:\n            xa, xb = swap(xa, xb)\n\n        for x in range(xa, xb+1):\n            # sleep(.1)\n            tft.line(x, y, x, y, color)\n
"},{"location":"kits/gc9a01/08-draw-filled-triangle/#full-test-program","title":"Full Test Program","text":"

To test our alforithm we can genrate three random points near the center of the display and then call the traingle fill on these points. If you would like to see how the algorithm does the drawing, you can uncomment the sleep function just before the tft.line() above.

# 01-display-test.py\n# \nfrom machine import Pin, SPI\nimport random\nimport gc9a01\nimport vga1_16x16 as font\nfrom utime import sleep\n\n# this uses the standard Dupont ribbon cable spanning rows 4-9 on our breadboard\nSCK_PIN = 2 # row 4\nSDA_PIN = 3\nDC_PIN = 4\nCS_PIN = 5\n# GND is row 8\nRST_PIN = 6\n\n# define the SPI intrface\nspi = SPI(0, baudrate=60000000, sck=Pin(SCK_PIN), mosi=Pin(SDA_PIN))\ntft = gc9a01.GC9A01(spi, 240, 240, reset=Pin(RST_PIN, Pin.OUT),\n    cs=Pin(CS_PIN, Pin.OUT), dc=Pin(DC_PIN, Pin.OUT), rotation=0\n)\n\nWHITE = gc9a01.color565(255,255,255)\nBLUE = gc9a01.color565(0,0,255)\nBLACK = gc9a01.color565(0,0,0)\n\ntft.init()\ntft.fill(0) # fill the screen with black\ntft.text(font, \"Draw Filled Triangle Test\", 20, 10, BLUE, BLACK)\n\n\n\ndef drawFilledTriangle(x1, y1, x2, y2, x3, y3, color=WHITE):\n    def swap(x, y):\n        return y, x\n\n    if y1 > y2:\n        x1, x2 = swap(x1, x2)\n        y1, y2 = swap(y1, y2)\n    if y1 > y3:\n        x1, x3 = swap(x1, x3)\n        y1, y3 = swap(y1, y3)\n    if y2 > y3:\n        x2, x3 = swap(x2, x3)\n        y2, y3 = swap(y2, y3)\n\n    for y in range(y1, y3+1):\n        if y2 - y1 != 0 and y < y2:\n            xa = x1 + (x2 - x1) * (y - y1) // (y2 - y1)\n        elif y3 - y1 != 0:\n            xa = x1 + (x3 - x1) * (y - y1) // (y3 - y1)\n        else:\n            continue\n\n        if y3 - y2 != 0 and y >= y2:\n            xb = x2 + (x3 - x2) * (y - y2) // (y3 - y2)\n        elif y3 - y1 != 0:\n            xb = x1 + (x3 - x1) * (y - y1) // (y3 - y1)\n        else:\n            continue\n\n        if xa > xb:\n            xa, xb = swap(xa, xb)\n\n        for x in range(xa, xb+1):\n            tft.line(x, y, x, y, color)\n\n\nmin = 50\nmax = 150\nwhile True:\n    tft.fill(BLACK)\n    x1 = random.randint(min, max)\n    y1 = random.randint(min, max)\n    x2 = random.randint(min, max)\n    y2 = random.randint(min, max)\n    x3 = random.randint(min, max)\n    y3 = random.randint(min, max)\n    drawFilledTriangle(x1, y1, x2, y2, x3, y3, WHITE)\n    sleep(.1)\n
"},{"location":"kits/gc9a01/08-draw-filled-triangle/#crazy-triangles","title":"Crazy Triangles","text":"
# lab 15: Filled Triangles\n# \nfrom machine import Pin, SPI\nimport random\nimport gc9a01\nimport vga1_16x16 as font\nfrom utime import sleep\n\n# this uses the standard Dupont ribbon cable spanning rows 4-9 on our breadboard\nSCK_PIN = 2 # row 4\nSDA_PIN = 3\nDC_PIN = 4\nCS_PIN = 5\n# GND is row 8\nRST_PIN = 6\n\n# define the SPI intrface\nspi = SPI(0, baudrate=60000000, sck=Pin(SCK_PIN), mosi=Pin(SDA_PIN))\ntft = gc9a01.GC9A01(spi, 240, 240, reset=Pin(RST_PIN, Pin.OUT),\n    cs=Pin(CS_PIN, Pin.OUT), dc=Pin(DC_PIN, Pin.OUT), rotation=0\n)\n\nWHITE = gc9a01.color565(255,255,255)\nBLUE = gc9a01.color565(0,0,255)\nBLACK = gc9a01.color565(0,0,0)\n\ntft.init()\ntft.fill(0) # fill the screen with black\ntft.text(font, \"Triangles\", 57, 22, BLUE, BLACK)\n\ndef drawFilledTriangle(x1, y1, x2, y2, x3, y3, color=WHITE):\n    def swap(x, y):\n        return y, x\n\n    if y1 > y2:\n        x1, x2 = swap(x1, x2)\n        y1, y2 = swap(y1, y2)\n    if y1 > y3:\n        x1, x3 = swap(x1, x3)\n        y1, y3 = swap(y1, y3)\n    if y2 > y3:\n        x2, x3 = swap(x2, x3)\n        y2, y3 = swap(y2, y3)\n\n    for y in range(y1, y3+1):\n        if y2 - y1 != 0 and y < y2:\n            xa = x1 + (x2 - x1) * (y - y1) // (y2 - y1)\n        elif y3 - y1 != 0:\n            xa = x1 + (x3 - x1) * (y - y1) // (y3 - y1)\n        else:\n            continue\n\n        if y3 - y2 != 0 and y >= y2:\n            xb = x2 + (x3 - x2) * (y - y2) // (y3 - y2)\n        elif y3 - y1 != 0:\n            xb = x1 + (x3 - x1) * (y - y1) // (y3 - y1)\n        else:\n            continue\n\n        if xa > xb:\n            xa, xb = swap(xa, xb)\n\n        for x in range(xa, xb+1):\n            # for wathing the drawing\n            # sleep(.001)\n            tft.line(x, y, x, y, color)\n\nmin = 40\nmax = 220\nwhile True:\n    # tft.fill(BLACK)\n    x1 = random.randint(min, max)\n    y1 = random.randint(min, max)\n    x2 = random.randint(min, max)\n    y2 = random.randint(min, max)\n    x3 = random.randint(min, max)\n    y3 = random.randint(min, max)\n    red = random.randint(0, 255)\n    green = random.randint(0, 255)\n    blue = random.randint(0, 255)\n    color = gc9a01.color565(red,green,blue)\n    drawFilledTriangle(x1, y1, x2, y2, x3, y3, color)\n    # slow down the drawing here\n    sleep(.1)\n
"},{"location":"kits/gc9a01/09-watch-face/","title":"Watch Face","text":"
  1. Ticks
  2. Second hand
  3. Minute hand
  4. Hour hand
from machine import Pin, SPI\nfrom utime import sleep, localtime\nimport math\nimport gc9a01\n\n# this uses the standard Dupont ribbon cable spanning rows 4-9 on our breadboard\nSCK_PIN = 2 # row 4\nSDA_PIN = 3\nDC_PIN = 4\nCS_PIN = 5\n# GND is row 8\nRST_PIN = 6\n\n# define the SPI intrface\nspi = SPI(0, baudrate=60000000, sck=Pin(SCK_PIN), mosi=Pin(SDA_PIN))\ntft = gc9a01.GC9A01(spi, 240, 240, reset=Pin(RST_PIN, Pin.OUT),\n    cs=Pin(CS_PIN, Pin.OUT), dc=Pin(DC_PIN, Pin.OUT), rotation=0\n)\ntft.init()\n# A full circle is 2*Pi radians\nTWO_PI = 3.1415926*2\nWHITE = gc9a01.color565(255, 255, 255)\nBLUE = gc9a01.color565(0, 0, 255)\nGREEN = gc9a01.color565(0, 255, 0)\nPURPLE = gc9a01.color565(255, 0, 255)\nBLACK = gc9a01.color565(0, 0, 0)\ntft.fill(BLACK)\nCENTER = 120\nMIN_TICK_START = 80\nMIN_TICK_END = 98\nSEC_TICK_START = 100\nSEC_TICK_END = 120\n\ndef drawFilledTriangle(x1, y1, x2, y2, x3, y3, color=WHITE):\n    def swap(x, y):\n        return y, x\n    if y1 > y2:\n        x1, x2 = swap(x1, x2)\n        y1, y2 = swap(y1, y2)\n    if y1 > y3:\n        x1, x3 = swap(x1, x3)\n        y1, y3 = swap(y1, y3)\n    if y2 > y3:\n        x2, x3 = swap(x2, x3)\n        y2, y3 = swap(y2, y3)\n    for y in range(y1, y3+1):\n        if y2 - y1 != 0 and y < y2:\n            xa = x1 + (x2 - x1) * (y - y1) // (y2 - y1)\n        elif y3 - y1 != 0:\n            xa = x1 + (x3 - x1) * (y - y1) // (y3 - y1)\n        else:\n            continue\n        if y3 - y2 != 0 and y >= y2:\n            xb = x2 + (x3 - x2) * (y - y2) // (y3 - y2)\n        elif y3 - y1 != 0:\n            xb = x1 + (x3 - x1) * (y - y1) // (y3 - y1)\n        else:\n            continue\n        if xa > xb:\n            xa, xb = swap(xa, xb)\n        for x in range(xa, xb+1):\n            tft.line(x, y, x, y, color)\n\ndef drawTicks():\n    for i in range(0,60):\n        radians = (i/60)*TWO_PI\n        x1 = int(math.sin(radians)*SEC_TICK_START)\n        y1 = -int(math.cos(radians)*SEC_TICK_START)\n        x2 = int(math.sin(radians)*SEC_TICK_END)\n        y2 = -int(math.cos(radians)*SEC_TICK_END)\n        # print(i, radians, x1, y1, x2, y2)  \n        tft.line(CENTER+x1, CENTER+y1, CENTER+x2, CENTER+y2, BLUE)\n\n    for i in range(0,12):\n        radians = (i/12)*TWO_PI\n        x1 = int(math.sin(radians)*MIN_TICK_START)\n        y1 = -int(math.cos(radians)*MIN_TICK_START)\n        x2 = int(math.sin(radians)*MIN_TICK_END)\n        y2 = -int(math.cos(radians)*MIN_TICK_END)\n        # print(i, radians, x1, y1, x2, y2)  \n        tft.line(CENTER+x1, CENTER+y1, CENTER+x2, CENTER+y2, GREEN)\n\nCENTER = 120\nSEC_HAND_LENGTH = 118\ndef drawSecondHand(sec):\n    # print('sec=', sec)\n    if sec ==0:\n        radians = 0\n    else: radians = (sec/60)*TWO_PI\n    x = int(math.sin(radians)*SEC_HAND_LENGTH)\n    y = -int(math.cos(radians)*SEC_HAND_LENGTH)\n    # print(radians, x, y)\n    tft.line(CENTER, CENTER, CENTER+x,CENTER+y, WHITE)\n    sleep(.1)\n    tft.line(CENTER, CENTER, CENTER+x,CENTER+y, BLACK)\n\n\nMIN_HAND_LENGTH = 90\nMIN_HAND_WIDTH = 5\ndef drawMinuteHand(min, color):\n    radians = (min/60)*TWO_PI\n    x1 = -int(math.cos(radians)*MIN_HAND_WIDTH)\n    y1 = -int(math.sin(radians)*MIN_HAND_WIDTH)\n    x2 = int(math.sin(radians)*MIN_HAND_LENGTH)\n    y2 = -int(math.cos(radians)*MIN_HAND_LENGTH)\n    x3 = int(math.cos(radians)*MIN_HAND_WIDTH)\n    y3 = int(math.sin(radians)*MIN_HAND_WIDTH)\n    # print('min:', x1, y1, x2, y2, x3, y3)\n    drawFilledTriangle(CENTER+x1, CENTER+y1, CENTER+x2, CENTER+y2, CENTER+x3, CENTER+y3, color)\n\nHOUR_HAND_LENGTH = 60\nHOUR_HAND_WIDTH = 6\ndef drawHourHand(hour, color):\n    radians = (hour/12)*TWO_PI\n    x1 = -int(math.cos(radians)*HOUR_HAND_WIDTH)\n    y1 = -int(math.sin(radians)*HOUR_HAND_WIDTH)\n    x2 = int(math.sin(radians)*HOUR_HAND_LENGTH)\n    y2 = -int(math.cos(radians)*HOUR_HAND_LENGTH)\n    x3 = int(math.cos(radians)*HOUR_HAND_WIDTH)\n    y3 = int(math.sin(radians)*HOUR_HAND_WIDTH)\n    # print('hour:', x1, y1, x2, y2, x3, y3)\n    drawFilledTriangle(CENTER+x1, CENTER+y1, CENTER+x2, CENTER+y2, CENTER+x3, CENTER+y3, color)\n\ncounter = 0\nmin = 58\nhour = 6\ndrawMinuteHand(min, GREEN)\ndrawHourHand(hour, PURPLE)\nhour = 6\nwhile True:\n    # this is the flicker\n    # tft.fill(BLACK)\n    drawTicks()\n\n    drawSecondHand(counter)\n    # if we are at 60 we start over\n    if counter > 59:\n        drawMinuteHand(min, BLACK)\n        counter = 0\n        min += 1\n        drawMinuteHand(min, GREEN)\n        if min > 59:\n            min=0\n            drawHourHand(hour, BLACK)\n            hour += 1\n            drawHourHand(hour, PURPLE)\n            if hour > 11:\n                hour = 0\n    counter += 1\n    # sleep(.5)\n
"},{"location":"kits/ili9341/","title":"ILI9341 Display","text":"

The ILI9341 is a low-cost color single-chip driver for a TFT liquid crystal display with a resolution of 240x320.

The board for the ILI9341 with a display costs around $9.00. Although these displays are not a high contrast as OLED displays, they are still adequate for many clock projects that would like to draw smooth circles on a clock face.

"},{"location":"kits/ili9341/#driver","title":"Driver","text":"

dagger GitHub Repo

"},{"location":"kits/ili9341/#characteristics","title":"Characteristics","text":"

Direct write No support for standard micropython framebuf

"},{"location":"kits/ili9341/#sample-lesson-plan","title":"Sample Lesson Plan","text":"

GPT-4 Generated Lesson Plan

"},{"location":"kits/ili9341/#source-code","title":"Source Code","text":"

Source Code

"},{"location":"kits/ili9341/01-rainbow-test/","title":"Rainbow Test","text":"
from ili9341 import Display, color565\nfrom machine import Pin, SPI\n\n# Use these PIN definitions.  SCK must be on 2 and data (SDL) on 3 for SPI bus 0\nSCK_PIN = 2\nMISO_PIN = 3 # labeled SDI(MOSI) on the back of the display\nDC_PIN = 4\nRESET_PIN = 5\nCS_PIN = 6\nROTATION = 90\n\nWIDTH=320\nHEIGHT=240\n\nspi = SPI(0, baudrate=40000000, sck=Pin(SCK_PIN), mosi=Pin(MISO_PIN))\ndisplay = Display(spi, dc=Pin(DC_PIN), cs=Pin(CS_PIN), rst=Pin(RESET_PIN), width=WIDTH, height=HEIGHT, rotation=ROTATION)\n\nRED = color565(255,0,0)\nORANGE = color565(255,128,0)\nYELLOW = color565(255,255,0)\nGREEN = color565(0,255,0)\nBLUE = color565(0,0,255)\nPURPLE = color565(255,0,255)\nWHITE = color565(255,255,255)\nBLACK = color565(0,0,0)\n\ndisplay.fill_rectangle(0,0, 50,HEIGHT, RED)\ndisplay.fill_rectangle(50,0, 50,HEIGHT, ORANGE)\ndisplay.fill_rectangle(100,0, 50,HEIGHT, YELLOW)\ndisplay.fill_rectangle(150,0, 50,HEIGHT, GREEN)\ndisplay.fill_rectangle(200,0, 50,HEIGHT, BLUE)\ndisplay.fill_rectangle(250,0, 50,HEIGHT, PURPLE)\ndisplay.fill_rectangle(300,0, 20,HEIGHT, WHITE)\n\nprint('Done')\n
"},{"location":"kits/ili9341/01-rainbow-test/#color-definitions","title":"Color Definitions","text":"

We can also use this same process for storing all of the common constants that we duplicate in our examples. For example all the named color defintions can be moved into a serate color-defs.py file like this:

Sample colors.py

from ili9341 import color565\n\nWHITE = color565(255,255,255)\nBLACK = color565(0,0,0)\nRED = color565(255,0,0)\nORANGE = color565(255,128,0)\nYELLOW = color565(255,255,0)\nGREEN = color565(0,255,0)\nBLUE = color565(0,0,255)\nCYAN = color565(0,255,255)\nPURPLE = color565(255,0,255)\n

This sample program imports both the config and the colors file:

from ili9341 import Display, color565\nfrom machine import Pin, SPI\nimport config\nimport colors\n\n# Use these PIN definitions.  SCK must be on 2 and data (SDL) on 3\nSCK_PIN = config.SCK_PIN\nMISO_PIN = config.MISO_PIN # labeled SDI(MOSI) on the back of the display\nDC_PIN = config.DC_PIN\nRESET_PIN = config.RESET_PIN\nCS_PIN = config.CS_PIN\n\nWIDTH=config.WIDTH\nHEIGHT=config.HEIGHT\nROTATION=config.ROTATION\n\n# mosi=Pin(23)\n# miso=Pin(MISO_PIN)\nspi = SPI(0, baudrate=40000000, sck=Pin(SCK_PIN), mosi=Pin(MISO_PIN))\ndisplay = Display(spi, dc=Pin(DC_PIN), cs=Pin(CS_PIN), rst=Pin(RESET_PIN), width=WIDTH, height=HEIGHT, rotation=ROTATION)\n\ndisplay.fill_rectangle(0,0, 50,HEIGHT, colors.RED)\ndisplay.fill_rectangle(50,0, 50,HEIGHT, colors.ORANGE)\ndisplay.fill_rectangle(100,0, 50,HEIGHT, colors.YELLOW)\ndisplay.fill_rectangle(150,0, 50,HEIGHT, colors.GREEN)\ndisplay.fill_rectangle(200,0, 50,HEIGHT, colors.BLUE)\ndisplay.fill_rectangle(250,0, 50,HEIGHT, colors.PURPLE)\ndisplay.fill_rectangle(300,0, 20,HEIGHT, colors.WHITE)\n\nprint('Done')\n

Note that the string colors. must appear before each color name. You can shorten this to be just c. if you want to keep your code smaller and easier to read.

"},{"location":"kits/ili9341/01-rainbow-test/#hiding-hardware-initialization","title":"Hiding Hardware Initialization","text":"

We could take this one step further and put the lines that setup the SPI and the Display into a separate function. However, in our labs we want to keep some of this code explicit so we will leave the SPI and Display initialization in our examples.

"},{"location":"kits/ili9341/02-config/","title":"Creating a Hardware Configuration File","text":"

Rather than hard-coding the pin numbers in every example, let's put all our hardware configuration data in a single file. All the examples from here on can use that configuration data.

"},{"location":"kits/ili9341/02-config/#hardware-configuration-file","title":"Hardware Configuration File","text":"
# Dan's Robot Labs configuration file for ILI9341 clock project\n# The colors on the SPI bus cable are:\n# 3.3v power - red\n# SCK - orange\n# MISO/Data - yellow\n# DC - green\n# RESET - blue\n# GND - black\n# CS - purple\nSCK_PIN = 2\nMISO_PIN = 3 # labeled SDI(MOSI) on the back of the display\nDC_PIN = 4\nRESET_PIN = 5\nCS_PIN = 6\nROTATION = 90\n\nWIDTH=320\nHEIGHT=240\n
"},{"location":"kits/ili9341/02-config/#sample-use-of-haredwre-configuration-file","title":"Sample Use of Haredwre Configuration File","text":"
# print out \"Hello World!\" using the rotation=3 using 32-bit high font\n# the default is white text on a black background\nfrom ili9341 import Display, color565\nfrom machine import Pin, SPI\nimport config\n\n# Use these PIN definitions.  SCK must be on 2 and data (SDL) on 3\nSCK_PIN = config.SCK_PIN\nMISO_PIN = config.MISO_PIN # labeled SDI(MOSI) on the back of the display\nDC_PIN = config.DC_PIN\nRESET_PIN = config.RESET_PIN\nCS_PIN = config.CS_PIN\n\nWIDTH=config.WIDTH\nHEIGHT=config.HEIGHT\nROTATION=config.ROTATION\n\n# mosi=Pin(23)\n# miso=Pin(MISO_PIN)\nspi = SPI(0, baudrate=40000000, sck=Pin(SCK_PIN), mosi=Pin(MISO_PIN))\ndisplay = Display(spi, dc=Pin(DC_PIN), cs=Pin(CS_PIN), rst=Pin(RESET_PIN), width=WIDTH, height=HEIGHT, rotation=ROTATION)\n\nRED = color565(255,0,0)\nORANGE = color565(255,128,0)\nYELLOW = color565(255,255,0)\nGREEN = color565(0,255,0)\nBLUE = color565(0,0,255)\nPURPLE = color565(255,0,255)\nWHITE = color565(255,255,255)\nBLACK = color565(0,0,0)\n\ndisplay.fill_rectangle(0,0, 50,HEIGHT, RED)\ndisplay.fill_rectangle(50,0, 50,HEIGHT, ORANGE)\ndisplay.fill_rectangle(100,0, 50,HEIGHT, YELLOW)\ndisplay.fill_rectangle(150,0, 50,HEIGHT, GREEN)\ndisplay.fill_rectangle(200,0, 50,HEIGHT, BLUE)\ndisplay.fill_rectangle(250,0, 50,HEIGHT, PURPLE)\ndisplay.fill_rectangle(300,0, 20,HEIGHT, WHITE)\n\nprint('Done')\n
"},{"location":"kits/led-strips/","title":"LED Strip Clocks","text":"

In this section we will present two clocks that use and LED strip.

  1. The first kit displays binary numbers. Hard to read but very geeky.
  2. The second requires building four seven-segment displays from the LED strip (or LED string)
"},{"location":"kits/led-strips/01-binary-clock/","title":"Binary Clock","text":"

This kit just needs a Pico and a short segment of an LED strip - about 14 pixels.

We can create a very simple binary clock using a single LED strip.

The strip will have three color bands:

  1. One binary number will be the hour (0 to 12) (4 red pixels)
  2. One binary number will be the minutes (0 to 59) (6 green pixels)
  3. One binary number will be the seconds (0 to 59) (6 blue pixels)

We can also put spaces between the bands

We can optionally also have the seconds displayed by:

  1. A single LED that blinks on and off
  2. Two decimal digits
  3. A single binary from (0 to 60)
"},{"location":"kits/led-strips/01-binary-clock/#sample-code","title":"Sample Code","text":"
from machine import Pin\nfrom neopixel import NeoPixel\nfrom utime import sleep, localtime\n# get the configuration information\nimport config\n\nnp = config.NUMBER_PIXELS\nstrip = NeoPixel(Pin(config.NEOPIXEL_PIN), np)\n\nsec_bits = [0,0,0,0,0,0]\nmin_bits = [0,0,0,0,0,0]\nhr_bits = [0,0,0,0,0,0]\n\ndef decimal_to_binary(n, a):\n    global sec_bits\n    for i in range(0,6):\n        if n % 2:\n            a[i] = 1\n        else:\n            a[i] = 0\n        ## n halfed\n        n //= 2\n\ndef display_binary(binary, index, color):\n    for i in range(0, 6):\n        # print(i, ' ', end='')\n        if binary[i] == 1:\n            strip[index+i] = color\n        else:\n            strip[index+i] = (0,0,0)\n    strip.write()\n\n# light mark and write\ndef display_mark(loc):\n    strip[loc] = (5,5,5)\n    strip.write()\n\n# update from the first time\n# sec\ndisplay_mark(0)\ndisplay_mark(7)\n\n# min\ndisplay_mark(9)\ndisplay_mark(16)\n\n# min\ndisplay_mark(19)\ndisplay_mark(26)\n\nnow = localtime()\nhour = now[3]\n# use AM/PM 12 hour time\nif hour > 12:\n    hour = hour - 12\nminute = now[4]\n\n# this is not working\ndecimal_to_binary(minute, min_bits)\nprint('initial min:', minute, min_bits)\ndisplay_binary(min_bits, 10, (0,10,0))\n\ndecimal_to_binary(hour, hr_bits)\nprint('initial hour:', hour, hr_bits)\ndisplay_binary(hr_bits, 20, (0,0,10))\n\nwhile True:\n    now = localtime()\n    hour = now[3]\n    # use AM/PM 12 hour time\n    if hour > 12:\n        hour = hour - 12\n    minute = now[4]\n    sec = now[5]\n    print(hour, ':', minute, ' ', sec, sep='')\n    strip.write()\n    decimal_to_binary(sec, sec_bits)\n    print('sec:', sec, sec_bits)\n    display_binary(sec_bits, 1, (10,0,0))\n    if sec == 60:\n        minute = minute + 1\n        sec = 0\n        decimal_to_binary(minute, min_bits)\n        print('min:', minute, min_bits)\n        display_binary(min_bits, 10, (0,10,0))\n        if minute == 60:\n            decimal_to_binary(hour, hr_bits)\n            print('hour:', hour, hr_bits)\n            display_binary(hr_bits, 20, (0,0,10))\n            hour = hour + 1\n            minute = 0\n            if hour == 24:\n                hour = 0\n    sleep(1)\n
"},{"location":"kits/led-strips/01-binary-clock/#references","title":"References","text":""},{"location":"kits/led-strips/02-seven-segment-clock/","title":"Seven Segment Clock","text":"

We can create a large, bright clock visible in a large classroom by lighting up selective pixels of an LED strip and arranging the pixels in the shape of seven segment displays. We can use three pixels per segment.

eBay 50 for $12

Drawing Digits

Lessons

We can create an array of the segments like this:

segmentMapping = [\n  #a, b, c, d, e, f, g\n  [1, 1, 1, 1, 1, 1, 0], # 0\n  [0, 1, 1, 0, 0, 0, 0], # 1\n  [1, 1, 0, 1, 1, 0, 1], # 2\n  [1, 1, 1, 1, 0, 0, 1], # 3\n  [0, 1, 1, 0, 0, 1, 1], # 4\n  [1, 0, 1, 1, 0, 1, 1], # 5\n  [1, 0, 1, 1, 1, 1, 1], # 6\n  [1, 1, 1, 0, 0, 0, 0], # 7\n  [1, 1, 1, 1, 1, 1, 1], # 8\n  [1, 1, 1, 1, 0, 1, 1]  # 9\n];\n
"},{"location":"kits/led-strips/02-seven-segment-clock/#digit-pixel-map","title":"Digit Pixel Map","text":"

TBD

def set_pixels(digit, value, color):\n    # look up start pixel for digit\n    # for value turn on pixels\n    n = startVals[digit]\n\n    segmentOn = segmentMapping[value];\n    for i in range(0, 9):\n        if (segmentOn[i]):\n            o = i*3 // offset\n            strip(n+o) = color\n            strip(n+o+1) = color\n            strip(n+o+2) = color\n
```

7-Segment Display Demo with P5.js

Moving Rainbow Arduino Sign

"},{"location":"kits/lilygo/","title":"LILYGO T-Display RP2040","text":"

LILYGO makes low-cost and high-quality microcontroller development boards that include small displays. Although most of their boards run on C programs on ESP-32 processors, they do have one that runs MicroPython on an RP2040. This \"kit\" is really just that development board placed on a breadboard. The device has two buttons on it which can be used to adjust the time.

This is a color 1.14 inch LCD display PS with 240*135 resolution. It uses the ST7789V chip that has an extreamly high quality driver created by Russ Hughes that allows for flicker-free drawing.

I purchased mine on Ebay for $10.66 and three dollars for shipping.

Although the display is too small for most robotic applications where the robot is on the floor and we are standing, it is a good example of how we can get both clocks and watches to look great. My hope is that LILYGO comes out with a larger display in the future.

Lilygo also sells their own \"wearable\" watch kits] for $35 to $45. However, I have not purchased any of these that can be programmed with an RP2040 and MicroPython yet. Here is a GitHub Page for the T-Watch that implies it might be on the way. Note that using this requires extensive knowledge of the ESP32 development system.

"},{"location":"kits/lilygo/#getting-started","title":"Getting Started","text":"

To use the ST7789V driver we MUST use a custom image provide by Rull Hughes. This is because the driver is written in low-level C code and the python driver requires it to be combiled into the firmware image.

I downloaded the custom image here:

T-DISPLAY RP2040 Firmware

I then held the Boot button down while I powered up the device.

I soldered my own header pins on the LILYGO and placed it on a breadboard. Unfortunatly this makes it impossible to hold down the boot button with the device on the breadboard.

"},{"location":"kits/lilygo/#pinouts","title":"Pinouts","text":"

The pinouts are very different from the Raspberry Pi Pico.

"},{"location":"kits/lilygo/#config-file","title":"Config File","text":"

This implementation puts the driver in a hidden C program, but it does have a configuration file that we must upload and place in the /lib directory.

Here is a Link to the File for the T-Display RP2040

\"\"\"TTGO T-Display RP2040 display\"\"\"\n\nfrom machine import Pin, SPI\nfrom time import sleep\nimport st7789\n\nTFA = 40    # top free area when scrolling\nBFA = 40    # bottom free area when scrolling\n\ndef config(rotation=0, buffer_size=0, options=0):\n\n    Pin(22, Pin.OUT, value=1)\n\n    spi = SPI(0,\n        baudrate=62500000,\n        polarity=1,\n        phase=0,\n        sck=Pin(2, Pin.OUT),\n        mosi=Pin(3, Pin.OUT),\n        miso=None)\n\n    return st7789.ST7789(\n        spi,\n        135,\n        240,\n        cs=Pin(5, Pin.OUT),\n        dc=Pin(1, Pin.OUT),\n        backlight=Pin(4, Pin.OUT),\n        rotation=rotation,\n        options=options,\n        buffer_size=buffer_size)\n
"},{"location":"kits/lilygo/#blink-the-onboard-led","title":"Blink The Onboard LED","text":"

This red LED is on the bottom of the board.

Blink Timer example:

from machine import Pin, Timer\nled = Pin(25,Pin.OUT)\ntim = Timer()\ndef tick(timer):\n    global led\n    led.toggle()\ntim.init(freq=2.5, mode=Timer.PERIODIC, callback=tick)\n
"},{"location":"kits/lilygo/#display-example","title":"Display Example","text":"
\"\"\"\ndisplay-test.py\nFills the screen with red, green and blue\n\"\"\"\n\nfrom utime import sleep\nimport st7789\nimport tft_config\ntft = tft_config.config(1)\ntft.init()\n\nwhile True:\n    tft.fill(st7789.RED)\n    sleep(1)\n    tft.fill(st7789.GREEN)\n    sleep(1)\n    tft.fill(st7789.BLUE)\n    sleep(1)\n
"},{"location":"kits/lilygo/#drawing-text","title":"Drawing Text","text":"

For this example to work, you will need to load a font library into the /lib directory.

import random\nimport utime\nimport st7789\nimport tft_config\nimport vga2_bold_16x32 as font\n\ntft = tft_config.config(1)\ntft.init()\n\n# draw text using a 16X32 font using blue text on a white background\ntft.text(\n        font,\n        'Hello World!',\n        tft.width()//2-100, # x position to start writing\n        tft.height()//2-50, # y position\n        st7789.BLUE, # font in blue\n        st7789.WHITE # background in white\n)\n
"},{"location":"kits/lilygo/#referneces","title":"Referneces","text":"

Item on Aliexpress Sample GitHub repo: https://github.com/Xinyuan-LilyGO/LILYGO-T-display-RP2040

ST7789V Submodule: Russ Hughes GitHub Repo

Config: Sample Config File

"},{"location":"kits/max7219/","title":"MAX7219 Clocks","text":"

The Analog Devices MAX7219 is a low-cost chip that communicates with a row of seven-segment displays using a single serial interface.

eBay search for MAX7219

"},{"location":"kits/max7219/#lesson-plan-building-a-clock-with-the-max7219-chip-using-micropython-and-raspberry-pi-pico","title":"Lesson Plan: Building a Clock with the MAX7219 Chip using MicroPython and Raspberry Pi Pico","text":""},{"location":"kits/max7219/#objective","title":"Objective:","text":"

Students will learn about basic electronics and programming by building a digital clock using the MAX7219 LED driver chip with a Raspberry Pi Pico programmed in MicroPython.

"},{"location":"kits/max7219/#materials","title":"Materials:","text":""},{"location":"kits/max7219/#duration","title":"Duration:","text":"

5 classes (45 minutes each)

"},{"location":"kits/max7219/#class-1-introduction-to-the-max7219-and-raspberry-pi-pico","title":"Class 1: Introduction to the MAX7219 and Raspberry Pi Pico","text":""},{"location":"kits/max7219/#class-2-basic-electronics-and-circuit-design","title":"Class 2: Basic Electronics and Circuit Design","text":""},{"location":"kits/max7219/#class-3-introduction-to-micropython","title":"Class 3: Introduction to MicroPython","text":"

understand MicroPython syntax and basic commands.

"},{"location":"kits/max7219/#class-4-programming-the-max7219-with-micropython","title":"Class 4: Programming the MAX7219 with MicroPython","text":""},{"location":"kits/max7219/#class-5-building-and-programming-the-clock","title":"Class 5: Building and Programming the Clock","text":""},{"location":"kits/max7219/#additional-notes","title":"Additional Notes:","text":""},{"location":"kits/max7219/#references","title":"References","text":"

DIY clock | Homemade 7 segments \"nixie\" tubes

"},{"location":"kits/pico-oled/","title":"Pico OLED Kits","text":"

These are our most popular kits. They use a bright monochrome 128x64 OLED with either the Raspberry Pi Pico or the Raspberry Pi Pico W.

The OLEDs are mounted on a piece of plastic with a half-size breaboard.

Each board has two buttons so you can adjust the time and alarm clock settings.

"},{"location":"kits/pico-oled/#individual-exercises","title":"Individual Exercises","text":""},{"location":"kits/pico-oled/#hello-world","title":"Hello World","text":"

Go to Hello World lab

"},{"location":"kits/pico-oled/01-hello-world/","title":"Hello World","text":"

To get started, let's just draw the string \"Hello world!\" on the display.

Here is the code:

import machine\nimport ssd1306\n\nSCL=machine.Pin(2) # SPI CLock\nSDA=machine.Pin(3) # SPI Data\n\nRES = machine.Pin(4) # Reset\nDC = machine.Pin(5) # Data/command\nCS = machine.Pin(6) # Chip Select\n\nspi=machine.SPI(0, sck=SCL, mosi=SDA)\noled = ssd1306.SSD1306_SPI(128, 64, spi, DC, RES, CS)\n\n# erase the entire screen with black\noled.fill(0)\n\n# place a hello message at point (0,0) in white\noled.text(\"Hello world!\", 0, 0, 1)\n\n# send the entire frame buffer to the display via the SPI bus\noled.show()\n

Challenges

  1. Can you change the message from \"Hello world!\" to have your name in it?
  2. Can you change the location of the text on the screen by changing the location point from (0,0) to another place on the screen? The screen is 128 pixels wide by 64 pixels high.
  3. How far down can you display the message without going off the screen?
  4. How many characters wide can a message be before it goes off the right edge of the dipsplay?
  5. Can you display multiple messages on different lines?
"},{"location":"kits/pico-oled/02-display-time/","title":"Display Time","text":""},{"location":"kits/pico-oled/02-display-time/#the-localtime-function","title":"The Localtime function","text":"
from time import localtime\n\nprint(localtime())\n
import machine\nimport ssd1306\nimport config\nfrom time import localtime\n\nSCL=machine.Pin(config.SCL_PIN) # SPI CLock\nSDA=machine.Pin(config.SDA_PIN) # SPI Data\n\nRES = machine.Pin(config.RESET_PIN) # Reset\nDC = machine.Pin(config.DC_PIN) # Data/command\nCS = machine.Pin(config.CS_PIN) # Chip Select\n\nspi=machine.SPI(config.SPI_BUS, sck=SCL, mosi=SDA, baudrate=100000)\noled = ssd1306.SSD1306_SPI(config.WIDTH, config.HEIGHT, spi, DC, RES, CS)\n\nyear = localtime()[0]\nmonth = localtime()[1]\nday = localtime()[2]\nhour = localtime()[3]\nminute = localtime()[4]\n\n# display the time in hour and minute on the first line\noled.text(str(hour) + \":\" + str(minute), 0, 0, 1)\n\n# display the date on the second line\noled.text(str(month) + \"/\" + str(day) + \"/\" + str(year), 0, 10, 1)\noled.show()\n
"},{"location":"kits/pico-oled/02-hello-config/","title":"Hello Configuraiton","text":""},{"location":"kits/pico-oled/03-display-localtime/","title":"Display Localtime","text":"
import machine\nimport ssd1306\nimport config\nfrom time import localtime\n\nSCL=machine.Pin(config.SCL_PIN) # SPI CLock\nSDA=machine.Pin(config.SDA_PIN) # SPI Data\n\nRES = machine.Pin(config.RESET_PIN) # Reset\nDC = machine.Pin(config.DC_PIN) # Data/command\nCS = machine.Pin(config.CS_PIN) # Chip Select\n\nspi=machine.SPI(config.SPI_BUS, sck=SCL, mosi=SDA, baudrate=100000)\noled = ssd1306.SSD1306_SPI(config.WIDTH, config.HEIGHT, spi, DC, RES, CS)\n\nyear = localtime()[0]\nmonth = localtime()[1]\nday = localtime()[2]\nhour = localtime()[3]\nminute = localtime()[4]\n\n# display the time in hour and minute on the first line\noled.text(str(hour) + \":\" + str(minute), 0, 0, 1)\n\n# display the date on the second line\noled.text(str(month) + \"/\" + str(day) + \"/\" + str(year), 0, 10, 1)\noled.show()\n

Challenges

  1. Change the display format of the date and time. Place the date and time in the center of the display.
  2. Add code to add a \"AM\" or \"PM\" string if the hour is greater than 12 noon
  3. Adding additional text to display if it is morning or evening
"},{"location":"kits/pico-oled/04-display-localtime-updates/","title":"Display Local Time with Updates","text":"

Now let's update the display every second.

import machine\nimport ssd1306\nimport config\nfrom time import localtime\n\nSCL=machine.Pin(config.SCL_PIN) # SPI CLock\nSDA=machine.Pin(config.SDA_PIN) # SPI Data\n\nRES = machine.Pin(config.RESET_PIN) # Reset\nDC = machine.Pin(config.DC_PIN) # Data/command\nCS = machine.Pin(config.CS_PIN) # Chip Select\n\nspi=machine.SPI(config.SPI_BUS, sck=SCL, mosi=SDA, baudrate=100000)\noled = ssd1306.SSD1306_SPI(config.WIDTH, config.HEIGHT, spi, DC, RES, CS)\n\nwhile True:\n    # clear the entire screen\n\n    oled.fill(0)\n    year = localtime()[0]\n    month = localtime()[1]\n    day = localtime()[2]\n    hour = localtime()[3]\n    minute = localtime()[4]\n    second = localtime()[5]\n\n    # display the time in hour and minute on the first line\n    oled.text(str(hour) + \":\" + str(minute) + \":\" + str(second) , 0, 0, 1)\n\n    # display the date on the second line\n    oled.text(str(month) + \"/\" + str(day) + \"/\" + str(year), 0, 10, 1)\n    # send the entire screen to the display via SPI\n    oled.show()\n
"},{"location":"kits/pico-oled/05-clock-hands/","title":"Clock Hands","text":"

Now lets display the hands on a clock. We will need to do a little math to calculate were on the clock face to draw the hands to from the center.

import machine\nimport ssd1306\nimport config\nfrom time import sleep, localtime\nimport math\n\nSCL=machine.Pin(config.SCL_PIN) # SPI CLock\nSDA=machine.Pin(config.SDA_PIN) # SPI Data\n\nRES = machine.Pin(config.RESET_PIN) # Reset\nDC = machine.Pin(config.DC_PIN) # Data/command\nCS = machine.Pin(config.CS_PIN) # Chip Select\n\nspi=machine.SPI(config.SPI_BUS, sck=SCL, mosi=SDA, baudrate=100000)\noled = ssd1306.SSD1306_SPI(config.WIDTH, config.HEIGHT, spi, DC, RES, CS)\nTWO_PI = 3.14159 * 2\n\nLENGTH = config.HEIGHT // 2\nCENTER_X = config.WIDTH // 2\nCENTER_Y = config.HEIGHT // 2\n\noled.fill(0)\n\nfor i in range(0,61):\n    radians = (i/60)*TWO_PI\n    x = int(math.sin(radians)*LENGTH)\n    y = -int(math.cos(radians)*LENGTH)\n    # print(i, radians, x, y, x, y)  \n    oled.line(CENTER_X, CENTER_Y, CENTER_X + x, CENTER_Y + y, 1)\n    oled.show()\n    sleep(.1)\n    oled.line(CENTER_X, CENTER_Y, CENTER_X + x, CENTER_Y + y, 0)\n
"},{"location":"kits/pico-oled/10-draw-seven-segments/","title":"Draw Seven Segments","text":""},{"location":"kits/pico-oled/10-draw-seven-segments/#dight-to-segment-data","title":"Dight to Segment Data","text":"
segmentMapping = [\n  #a, b, c, d, e, f, g\n  [1, 1, 1, 1, 1, 1, 0], # 0\n  [0, 1, 1, 0, 0, 0, 0], # 1\n  [1, 1, 0, 1, 1, 0, 1], # 2\n  [1, 1, 1, 1, 0, 0, 1], # 3\n  [0, 1, 1, 0, 0, 1, 1], # 4\n  [1, 0, 1, 1, 0, 1, 1], # 5\n  [1, 0, 1, 1, 1, 1, 1], # 6\n  [1, 1, 1, 0, 0, 0, 0], # 7\n  [1, 1, 1, 1, 1, 1, 1], # 8\n  [1, 1, 1, 1, 0, 1, 1]  # 9\n];\n
"},{"location":"kits/pico-oled/10-draw-seven-segments/#draw-segment-function","title":"Draw Segment Function","text":"

Parameters:

  1. The digit to display - an integer in the range 0-9
  2. x of the upper left corner
  3. y of the upper left corner
  4. the width of the digit (about 20 is a good value)
  5. the height of the digit
  6. the thickness of each segment (3 or 4 are good values)
  7. the color (0=black, 1=white)
# x and y are upper-left-corner\n# width and height are the dimensions of the digit\n# thickness is the width of the line segments\n# color is 1 for white and 0 for black\ndef drawDigit(digit, x, y, width, height, thickness, color):\n  # get a list of the segments that are on for this digit\n  segmentOn = segmentMapping[digit];\n\n  # Draw the horizontal segments: top, bottem, middle\n  for i in [0, 3, 6]:\n    if (segmentOn[i]):\n      if (i==0): # top row\n          yOffset = 0 \n      if (i==3):\n          yOffset = height - thickness # bottem element\n      if (i==6):\n          yOffset = height // 2 - thickness // 2# bottum\n      # oled.line(x - size, y+yOffset-size, x + size, y+yOffset-size, 1);\n      oled.fill_rect(x, y+yOffset, width, thickness, color)\n\n  # Draw the vertical segments ur, lr, ll, ul\n  for i in [1, 2, 4, 5]:\n    if (segmentOn[i]) :\n      # upper vertical lines\n      if (i==1 or i==5):\n          startY = y\n          endY = y + height // 2\n      # lower two vertical lines (2=lower right and 4=lower left)\n      if (i==2 or i==4):\n          startY = y + height // 2\n          endY = y + height\n      if (i==4 or i==5): xOffset = 0\n      if (i==1 or i==2): xOffset = width-thickness\n\n      oled.fill_rect(x+xOffset, startY, thickness, endY-startY, color)\n
"},{"location":"kits/pico-oled/10-draw-seven-segments/#full-program","title":"Full Program","text":"
# Clock Lab 20: Draw Seven Segments\n# this lab uses the fill_rect function to draw the segments\nimport machine\nimport utime\nimport ssd1306\nfrom utime import sleep, localtime\nled = machine.Pin(25, machine.Pin.OUT)\n\nSCL=machine.Pin(2) # SPI CLock\nSDA=machine.Pin(3) # SPI Data\nspi=machine.SPI(0, sck=SCL, mosi=SDA, baudrate=100000)\n\nRES = machine.Pin(4)\nDC = machine.Pin(5)\nCS = machine.Pin(6)\n\noled = ssd1306.SSD1306_SPI(128, 64, spi, DC, RES, CS)\n\nsegmentMapping = [\n  #a, b, c, d, e, f, g\n  [1, 1, 1, 1, 1, 1, 0], # 0\n  [0, 1, 1, 0, 0, 0, 0], # 1\n  [1, 1, 0, 1, 1, 0, 1], # 2\n  [1, 1, 1, 1, 0, 0, 1], # 3\n  [0, 1, 1, 0, 0, 1, 1], # 4\n  [1, 0, 1, 1, 0, 1, 1], # 5\n  [1, 0, 1, 1, 1, 1, 1], # 6\n  [1, 1, 1, 0, 0, 0, 0], # 7\n  [1, 1, 1, 1, 1, 1, 1], # 8\n  [1, 1, 1, 1, 0, 1, 1]  # 9\n];\n\n# x and y are upper-left-corner\n# width and height are the dimensions of the digit\n# thickness is the width of the line segments\n# color is 1 for white and 0 for black\ndef drawDigit(digit, x, y, width, height, thickness, color):\n  # get a list of the segments that are on for this digit\n  segmentOn = segmentMapping[digit];\n\n  # Draw the horizontal segments: top, bottem, middle\n  for i in [0, 3, 6]:\n    if (segmentOn[i]):\n      if (i==0): # top\n          yOffset = 0 \n      if (i==3):\n          yOffset = height - thickness # bottem element\n      if (i==6):\n          yOffset = height // 2 - thickness // 2# bottum\n      # oled.line(x - size, y+yOffset-size, x + size, y+yOffset-size, 1);\n      oled.fill_rect(x, y+yOffset, width, thickness, color)\n\n  # Draw the vertical segments ur, lr, ll, ul\n  for i in [1, 2, 4, 5]:\n    if (segmentOn[i]) :\n      # upper vertical lines\n      if (i==1 or i==5):\n          startY = y\n          endY = y + height // 2\n      # lower two vertical lines (2=lower right and 4=lower left)\n      if (i==2 or i==4):\n          startY = y + height // 2\n          endY = y + height\n      if (i==4 or i==5): xOffset = 0\n      if (i==1 or i==2): xOffset = width-thickness\n\n      oled.fill_rect(x+xOffset, startY, thickness, endY-startY, color)\n\noled.fill(0)\noled.text('Lab 12: rect', 0, 0, 1)\nx = 10 # upper left corner x\ny = 10 # upper left corner y\nw = 20 # digit width\nh = 30 # digit height\nt = 3\n\nwhile True:\n    for i in range(0, 10):\n\n        print(i)\n        # create an outline on px away from the drawing region\n        oled.rect(x-2, y-2, w+4, h+4, 1)\n        # draw one digit\n        drawDigit(i, x, y, w, h, t, 1)\n        # draw a second digit\n        #drawDigit(i, x + w + 4, w, h, t, 1)\n        oled.text(str(i), 0, 54, 1)\n        oled.show()\n        sleep(2)\n        oled.fill(0)\n
"},{"location":"kits/pico-oled/11-draw-thinkness/","title":"Draw Seven Segments with Thickness","text":""},{"location":"kits/pico-oled/15-digital-clock/","title":"OLED Digital Clock Display","text":""},{"location":"kits/waveshare/","title":"Waveshare RP2040","text":"

The Waveshare RP2040 1.28\" IPS LCD Board is a wonderful developent board with a build in RP2040 processor that currently sells for about $18. The board has a USB-C connector, a place for a LiPo battery connection and built in Accelerometer & Gyroscope. It is a great value and a wonderful way to start to learn Python for smart watches!

  1. Waveshare SKU: 22668
  2. Waveshare Part Number: RP2040-LCD-1.28
  3. Link to Waveshare site

Note that this watch does not have a real-time clock and has no ability to connect to time syhronization using WiFi. However, it is an ideal development tool for learning to program watch displays and integrate sensors.

"},{"location":"kits/waveshare/#micropython-version","title":"MicroPython Version","text":"

To use these lessions you much use MicroPython runtime v1.19.1-854-g35524a6fd released on 2023-02-07 or later. This version supports all the native framebuf drawing libraries (ellipse and polygon)

See the documentation here: MicroPython Framebuffer Functions

"},{"location":"kits/waveshare/#lessons","title":"Lessons","text":"
  1. Hello world! Lesson
  2. Color Lab
  3. Drawing Analog Hands
  4. 5x8 Fonts
"},{"location":"kits/waveshare/#detailed-components","title":"Detailed Components","text":"Component Description USB Type-C connector USB 1.1 with device and host support ETA6096 high efficiency Lithium battery recharge manager Battery Header MX1.25 header, for 3.7V Lithium battery, allows recharging the battery and powering the board at the same time QMI8658C IMU, includes a 3-axis gyroscope and a 3-axis accelerometer 1.27mm pitch headers Adapting all GPIO and Debug pins W25Q16JVUXIQ 2MB NOR-Flash RP2040 Dual-core processor, up to 133MHz operating frequency RESET Button Reset the processor BOOT Button press it when resetting to enter download mode"},{"location":"kits/waveshare/#references","title":"References","text":"

wiki

Instructable by Tony Goodhew - note that this version does not leverage the built-in drawing libraries that were made available in version 19 of the MicroPython release. See The MicroPython FrameBuf Library

Github Gist by Alasdair Allan

"},{"location":"kits/waveshare/01-hello-world/","title":"Waveshare Hello World","text":""},{"location":"kits/waveshare/01-hello-world/#step-1-load-the-library","title":"Step 1: Load the Library","text":""},{"location":"kits/waveshare/01-hello-world/#step-2-load-a-test-program","title":"Step 2: Load a Test Program","text":"
from LCD_1inch28 import LCD_1inch28\n\nLCD = LCD_1inch28()  \nLCD.set_bl_pwm(65535)\n\nLCD.fill(LCD.black)    \nLCD.text(\"Hello world!\", 50, 100, LCD.white)\nLCD.show()\n

You should see \"Hello world!\" in a small white font near the center of the screen.

Challenges

  1. Can you move the text around the screen by changing the x and y starting position of the text?
  2. Can you change the background fill from black to another color?
  3. Can you change the color of the text from white to be another color?
  4. Can you change the font size? (hint: this is not easy!)
"},{"location":"kits/waveshare/02-color-test/","title":"Color Tests","text":"

Now let's try to make the background screen change colors:

from LCD_1inch28 import LCD_1inch28\nfrom utime import sleep\n\nLCD = LCD_1inch28()  \nLCD.set_bl_pwm(65535)\n\nLCD.fill(LCD.black)\nLCD.show()\nsleep(1)\nLCD.fill(LCD.white)\nsleep(1)\nLCD.show()\nLCD.fill(LCD.red)\nLCD.show()\nsleep(1)\nLCD.fill(LCD.green)\nLCD.show()\nsleep(1)\nLCD.fill(LCD.blue)\nLCD.show()\nprint('done')\n

What happens when you change the color \"red\" to be \"orange\"? You shouild see:

Traceback (most recent call last):\n  File \"<stdin>\", line 10, in <module>\nAttributeError: 'LCD_1inch28' object has no attribute 'orange'\n

This shows you that although the driver knows about some basic colors, (black, white, red, green and blue), it has no understanding of other colors.

To draw these colors we need to add our own color lists.

"},{"location":"kits/waveshare/02-color-test/#custom-color-lists","title":"Custom Color Lists","text":"

In order to get more nuanced colors, we need to define them using the binary of their red, green and blue values. With this display, we order the bits, blue, red and green.

We can use the following binary notation to represent the colors:

# binary representations of colors B=Blue bits, R=Red bits, G=Green bits\n# color = 0bBBBBBRRRRRGGGGGG\n# Cyan has all the blue and green bits on and the red bits off\ncyan = cyan = 0b1111100000111111\n
from machine import Pin,I2C,SPI,PWM,ADC\nfrom time import sleep\nfrom LCD_1inch28 import LCD_1inch28, QMI8658\n\nCENTER = 120\nLCD = LCD_1inch28()\nLCD.set_bl_pwm(65535)\n\n# draw readability\nON = 1\nOFF = 0\nNO_FILL = 0\nFILL = 1\n# hex representation of colors\n# note that the bit order is blue, red, green\nblack = 0x0000\nred   = 0x07E0\ngreen = 0x001f\nblue  = 0xf800\nwhite = 0xffff\n\n# binary representations of colors\n# binary representations of colors B=5 Blue bits, R=5 Red bits, G=6 green bits\n# color = 0bBBBBBRRRRRGGGGGG\nyellow = 0b0000011111111111\norange = 0b0000001111000010\ncyan = 0b1111100000111111\nbrown = 0b0000000001000001\ngold = 0b0000001110011110\npurple = 0b1111111111000000\nmagenta = 0b0000001100011000\npink = 0b0000111111000010\nolive = 0b0000000001000010\ngray = 0b00001000010000100\nlightGreen = 0b0000100001111111\ndarkGreen  = 0b0000000000000001\n\nColorList =  (red,    green,   blue,   white,   yellow,  orange,\n              cyan, brown, gold, purple, magenta, pink, olive, gray, lightGreen, darkGreen)\nColorNames = ('red', 'green', 'blue', 'white', 'yellow', 'orange',\n              'cyan', 'brown', 'gold', 'purple', 'magenta', 'pink', 'olive', 'gray', 'lightGreen', 'darkGreen')\n\nradius = 120\nLCD.fill(LCD.black)\n\nfor i in range(0, len(ColorList)):\n    print(ColorNames[i])\n    # LCD.ellipse(CENTER, CENTER, radius, radius, ColorList[i], FILL)\n    LCD.fill(ColorList[i])\n    LCD.text(ColorNames[i], 100, 100, 1)\n    LCD.text(ColorNames[i], 100, 120, white)\n    LCD.show()\n    sleep(1)\n

Note that we are using the fill function to draw on all the pixels on the screen. We could have used the ellipse funtion to draw into the frame buffer, but the fill function is a bit easier.

"},{"location":"kits/waveshare/02-color-test/#converting-rgb888-to-brg556","title":"Converting RGB888 to BRG556","text":"
def convert_color_RGB888_RGB565(R,G,B): # Convert RGB888 to RGB565\n    return (((G&0b00011100)<<3) +((B&0b11111000)>>3)<<8) + (R&0b11111000)+((G&0b11100000)>>5)\n
"},{"location":"kits/waveshare/02-display-text/","title":"Display Time and Date in Text","text":"
from utime import localtime, sleep\nfrom LCD_1inch28 import LCD_1inch28\n\nLCD = LCD_1inch28()  \n\ndays = ('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday',\n        'Sunday')\nmonths = ('Jan', 'Feb', 'March', 'April', 'May', 'June', 'July',\n          'Aug', 'Sept', 'Oct', 'Nov', 'Dec')\nlabel = ('year', 'month', 'mday', 'hour', 'minute', 'second', 'weekday', 'yearday')\n\ndef format_time(now):\n    hour_number = now[3]\n    if hour_number < 13:\n        hour_12 = hour_number\n        am_pm = 'am'\n    else:\n        hour_12 = hour_number - 12\n        am_pm = 'pm'\n    minutes_num = now[4]\n    if minutes_num < 10:\n        min_leading_zero = '0'\n    else:\n        min_leading_zero = ''\n    seconds_num = now[5]\n    if seconds_num < 10:\n        sec_leading_zero = '0'\n    else:\n        sec_leading_zero = ''\n    return \"{}:{}{}:{}{} {}\".format(hour_12, min_leading_zero, minutes_num, sec_leading_zero, seconds_num, am_pm)\n\ndef format_date(now):\n    month_number = now[1]\n    month_name = months[month_number - 1]\n    weekday_number = now[6]\n    day_name = days[weekday_number]\n    hour_number = now[3]\n    return \"{} {} {}, {}\".format(day_name, month_name, now[2], now[0])\n\nwhile(True):\n    now = localtime()\n    # print(now)\n    LCD.fill(LCD.black)    \n    LCD.text(format_time(now), 77, 50, LCD.white)\n    LCD.text(format_date(now), 40, 80, LCD.white)\n    LCD.show()\n    sleep(1)\n
"},{"location":"kits/waveshare/03-drawing-hands/","title":"Drawing Analog Hands","text":"

Drawing a analog hand requirs drawing a line from the center of the screen to a point on the edge of the circle. The point positon varies periodically, just like the sine and cosine functions vary. We can demonstrate this will a counter that goes from 0 to 360 degrees.

Consider the following:

  1. Since the sine(0) = 0 we can use that function for the displacement from the center on the x-axis.
  2. Since the cosine(0) = 1, we can use that as the negative Y displacement from the center. Remember in drawing pixels, (0,0) is in the upper-left corner of the screen.

There is one other bit of math we need to review. The sine() and cosine() function take in a number called radians which is usually a number between 0 and two times Pi. They then return a value between 0 and 1. We need multiple both of these values by the length of the watch hand to get to the right part of the watch face.

from utime import sleep\nfrom LCD_1inch28 import LCD_1inch28\nimport math\n\nLCD = LCD_1inch28()\n\nCENTER = 120\nHAND_LENGTH = 100\n\n# our counter will range from 0 to 59\n# A full circle is 2*Pi radians\nTWO_PI = 3.145175*2\ncounter = 0\nwhile True:\n    radians = (counter/60)*TWO_PI\n    x = int(math.sin(radians)*HAND_LENGTH)\n    y = -int(math.cos(radians)*HAND_LENGTH)\n    print(radians, x, y)\n    LCD.fill(LCD.black)\n    LCD.line(CENTER, CENTER, CENTER+x,CENTER+y, LCD.white)\n    LCD.show()\n    sleep(1)\n    counter += 1\n    # if we are at 60 we start over\n    if counter > 59:\n        counter = 0\n

You should now see a narrow white line moving much like a second hand on a watch!

"},{"location":"kits/waveshare/03-drawing-hands/#adding-bling-to-your-hands","title":"Adding bling to your hands","text":"

Although drawing a single white line is a clean efficent design, many people like to add other features such as an arrow head at the tip of the hand. To do this we can use the poly function to draw the arrow. To get this right, we also need to orient the arrow in the right direction.

"},{"location":"kits/waveshare/03-drawing-hands/#drawing-a-triangle","title":"Drawing a Triangle","text":"

We can use the MicroPython standard poly function to draw a triangle. The poly

`FrameBuffer.poly(x, y, coords, c[, f])

This will draw an arbitrary polygon at the given x, y location using the given color (c).

The coords must be specified as a array of integers, e.g. array('h', [x0, y0, x1, y1, ... xn, yn]).

The optional f parameter can be set to True to fill the polygon. Otherwise just a one pixel outline is drawn.

Let's start with drawing a basic triangle in the center of the screen like this:

LCD.

# draw a triangle on a blue background\nfrom utime import sleep\nfrom LCD_1inch28 import LCD_1inch28\nimport math\nfrom array import array\nLCD = LCD_1inch28()\n\nCENTER = 120\nNO_FILL = 0 # just the border is drawn\nFILL = 1 # all pixels within the polygon are drawn\n# draw a blue background\nLCD.fill(LCD.blue)\n\n# distance from the center to the tip of the traiangle\nd = 50\nmy_array = array('B', [CENTER-d,CENTER+d, CENTER,CENTER-d, CENTER+d,CENTER+d])\nLCD.poly(0,0, my_array, LCD.white, FILL)\nLCD.show()\nprint('done')\n
"},{"location":"kits/waveshare/03-drawing-hands/#drawing-a-triangle-rotating","title":"Drawing a Triangle Rotating","text":"

Now we will modify the draw triangle program to rotate each of the three points. We do this by passing the CENTER and either a positve or negative value of the x and y which varies as we move around the circle.

Here is the line that is the most difficult to understand:

my_array = array('B', [CENTER-x,CENTER-y, CENTER+y,CENTER-x, CENTER+x,CENTER+y])\n

Note that the first point is in the lower left corner:

(CENTER-x, CENTER-y)\n

The second point is at the top of the trainagle and the X is initially zero (sine(0) = y)

(CENTER+y, CENTER-x)\n

The third point is to the lower right where we need to add to both the X and Y:

(CENTER+x, CENTER-y)\n

Here is the full program:

# draw rotating triangle\nfrom utime import localtime, sleep\nfrom LCD_1inch28 import LCD_1inch28\nfrom array import array\nimport math\nTWO_PI = 3.145175*2\n\nLCD = LCD_1inch28()\n\nCENTER = 120\nNO_FILL = 0 # just the border is drawn\nFILL = 1 # all pixels within the polygon are drawn\n\n# distance from the center to the tip of the traiangle\nd = 50\n\ncounter = 0\nwhile True:\n    LCD.fill(LCD.blue)\n    radians = (counter/60)*TWO_PI\n    x = int(math.cos(radians)*d)\n    y = int(math.sin(radians)*d)\n\n    # the three points of the triangle are rotated in a circle\n    my_array = array('B', [CENTER-x,CENTER-y, CENTER+y,CENTER-x, CENTER+x,CENTER+y])\n    print(CENTER-x, CENTER+y)\n\n    LCD.poly(0,0, my_array, LCD.white, FILL)\n    LCD.show()\n    sleep(.1)\n    counter += 1\n    # if we are at 60 we start over\n    if counter > 59:\n        counter = 0\n

You might have to stare at the code and the drawing for a while to get this figured out.

"},{"location":"kits/waveshare/03-drawing-hands/#create-a-draw-triangle-function","title":"Create a Draw Triangle Function","text":"

Now we are ready to package our triangle drawing experiment into a single function to make it easier to use. We will pass in four parameters:

  1. The center of the triangle's X and Y coordinates
  2. The size of the triangle measured from the center to the tip
  3. The number of seconds on the clock (0 to 59) which we will convert to radians. This becomes the agle of the triangle.
def drawTriangle(x, y, size, seconds):\n    radians = (seconds/60)*TWO_PI\n    # calculate the offsets\n    xo = int(math.cos(radians)*size)\n    yo = int(math.sin(radians)*size)\n    # build the array - use B if we have under 255 and h if over 255\n    arr = array('B', [x-xo,y-yo,  x+yo,y-xo,  x+xo,y+yo])\n    LCD.poly(0,0, arr, LCD.white, FILL)\n
"},{"location":"kits/waveshare/03-drawing-hands/#full-program","title":"Full Program","text":"
from utime import localtime, sleep\nfrom LCD_1inch28 import LCD_1inch28\nfrom array import array\nimport math\nTWO_PI = 3.145175*2\n\nLCD = LCD_1inch28()\n\nCENTER = 120\nNO_FILL = 0 # just the border is drawn\nFILL = 1 # all pixels within the polygon are drawn\nHAND_LENGTH = 100\nTRIANGLE_SIZE = 10\n\ndef drawTriangle(x, y, size, seconds):\n    radians = (seconds/60)*TWO_PI\n    # calculate the offsets\n    xo = int(math.cos(radians)*size)\n    yo = int(math.sin(radians)*size)\n    # build the array - use B if we have under 255 and h if over 255\n    arr = array('B', [x-xo,y-yo,  x+yo,y-xo,  x+xo,y+yo])\n    LCD.poly(0,0, arr, LCD.white, FILL)\n\ncounter = 0\nwhile True:\n    LCD.fill(LCD.blue)\n    radians = (counter/60)*TWO_PI\n    x = int(math.sin(radians)*HAND_LENGTH)\n    y = -int(math.cos(radians)*HAND_LENGTH)\n    LCD.line(CENTER, CENTER, CENTER+x,CENTER+y, LCD.white)\n    drawTriangle(CENTER+x, CENTER+y, TRIANGLE_SIZE, counter)\n    LCD.show()\n    sleep(.1)\n    counter += 1\n    # if we are at 60 we start over\n    if counter > 59:\n        counter = 0```\n
"},{"location":"kits/waveshare/03-drawing-hands/#drawing-x-and-y-axis","title":"Drawing X and Y Axis","text":"
# draw thin blue axis lines through the center\n# vertical line\nLCD.line(CENTER, 0, CENTER, 2*CENTER, blue)\n# horizontal line\nLCD.line(0, CENTER, 2*CENTER, CENTER, blue)\n
"},{"location":"kits/waveshare/04-5x8-font/","title":"5x8 Font Test","text":"

We can create custom functions for drawing larger fonts. Here we have a byte array of 5X8 fonts. The function character() draws individual characters one at a time.

from LCD_1inch28 import LCD_1inch28\n\nLCD = LCD_1inch28()  \n\n# Standard ASCII 5x8 font\n# https://gist.github.com/tdicola/229b3eeddc12d58fb0bc724a9062aa05\nFONT_HEIGHT = 8\nFONT_WIDTH = 5\nFONT = bytes([\n    0x00, 0x00, 0x00, 0x00, 0x00, # <space>\n    0x3E, 0x5B, 0x4F, 0x5B, 0x3E,\n    0x3E, 0x6B, 0x4F, 0x6B, 0x3E,\n    0x1C, 0x3E, 0x7C, 0x3E, 0x1C,\n    0x18, 0x3C, 0x7E, 0x3C, 0x18,\n    0x1C, 0x57, 0x7D, 0x57, 0x1C,\n    0x1C, 0x5E, 0x7F, 0x5E, 0x1C,\n    0x00, 0x18, 0x3C, 0x18, 0x00,\n    0xFF, 0xE7, 0xC3, 0xE7, 0xFF,\n    0x00, 0x18, 0x24, 0x18, 0x00,\n    0xFF, 0xE7, 0xDB, 0xE7, 0xFF,\n    0x30, 0x48, 0x3A, 0x06, 0x0E,\n    0x26, 0x29, 0x79, 0x29, 0x26,\n    0x40, 0x7F, 0x05, 0x05, 0x07,\n    0x40, 0x7F, 0x05, 0x25, 0x3F,\n    0x5A, 0x3C, 0xE7, 0x3C, 0x5A,\n    0x7F, 0x3E, 0x1C, 0x1C, 0x08,\n    0x08, 0x1C, 0x1C, 0x3E, 0x7F,\n    0x14, 0x22, 0x7F, 0x22, 0x14,\n    0x5F, 0x5F, 0x00, 0x5F, 0x5F,\n    0x06, 0x09, 0x7F, 0x01, 0x7F,\n    0x00, 0x66, 0x89, 0x95, 0x6A,\n    0x60, 0x60, 0x60, 0x60, 0x60,\n    0x94, 0xA2, 0xFF, 0xA2, 0x94,\n    0x08, 0x04, 0x7E, 0x04, 0x08, # UP\n    0x10, 0x20, 0x7E, 0x20, 0x10, # Down\n    0x08, 0x08, 0x2A, 0x1C, 0x08, # Right\n    0x08, 0x1C, 0x2A, 0x08, 0x08, # Left\n    0x1E, 0x10, 0x10, 0x10, 0x10,\n    0x0C, 0x1E, 0x0C, 0x1E, 0x0C,\n    0x30, 0x38, 0x3E, 0x38, 0x30,\n    0x06, 0x0E, 0x3E, 0x0E, 0x06,\n    0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x5F, 0x00, 0x00,\n    0x00, 0x07, 0x00, 0x07, 0x00,\n    0x14, 0x7F, 0x14, 0x7F, 0x14,\n    0x24, 0x2A, 0x7F, 0x2A, 0x12,\n    0x23, 0x13, 0x08, 0x64, 0x62,\n    0x36, 0x49, 0x56, 0x20, 0x50,\n    0x00, 0x08, 0x07, 0x03, 0x00,\n    0x00, 0x1C, 0x22, 0x41, 0x00,\n    0x00, 0x41, 0x22, 0x1C, 0x00,\n    0x2A, 0x1C, 0x7F, 0x1C, 0x2A,\n    0x08, 0x08, 0x3E, 0x08, 0x08,\n    0x00, 0x80, 0x70, 0x30, 0x00,\n    0x08, 0x08, 0x08, 0x08, 0x08,\n    0x00, 0x00, 0x60, 0x60, 0x00,\n    0x20, 0x10, 0x08, 0x04, 0x02,\n    0x3E, 0x51, 0x49, 0x45, 0x3E,\n    0x00, 0x42, 0x7F, 0x40, 0x00,\n    0x72, 0x49, 0x49, 0x49, 0x46,\n    0x21, 0x41, 0x49, 0x4D, 0x33,\n    0x18, 0x14, 0x12, 0x7F, 0x10,\n    0x27, 0x45, 0x45, 0x45, 0x39,\n    0x3C, 0x4A, 0x49, 0x49, 0x31,\n    0x41, 0x21, 0x11, 0x09, 0x07,\n    0x36, 0x49, 0x49, 0x49, 0x36,\n    0x46, 0x49, 0x49, 0x29, 0x1E,\n    0x00, 0x00, 0x14, 0x00, 0x00,\n    0x00, 0x40, 0x34, 0x00, 0x00,\n    0x00, 0x08, 0x14, 0x22, 0x41,\n    0x14, 0x14, 0x14, 0x14, 0x14,\n    0x00, 0x41, 0x22, 0x14, 0x08,\n    0x02, 0x01, 0x59, 0x09, 0x06,\n    0x3E, 0x41, 0x5D, 0x59, 0x4E,\n    0x7C, 0x12, 0x11, 0x12, 0x7C, # A\n    0x7F, 0x49, 0x49, 0x49, 0x36,\n    0x3E, 0x41, 0x41, 0x41, 0x22,\n    0x7F, 0x41, 0x41, 0x41, 0x3E,\n    0x7F, 0x49, 0x49, 0x49, 0x41,\n    0x7F, 0x09, 0x09, 0x09, 0x01,\n    0x3E, 0x41, 0x41, 0x51, 0x73,\n    0x7F, 0x08, 0x08, 0x08, 0x7F,\n    0x00, 0x41, 0x7F, 0x41, 0x00,\n    0x20, 0x40, 0x41, 0x3F, 0x01,\n    0x7F, 0x08, 0x14, 0x22, 0x41,\n    0x7F, 0x40, 0x40, 0x40, 0x40,\n    0x7F, 0x02, 0x1C, 0x02, 0x7F,\n    0x7F, 0x04, 0x08, 0x10, 0x7F,\n    0x3E, 0x41, 0x41, 0x41, 0x3E,\n    0x7F, 0x09, 0x09, 0x09, 0x06,\n    0x3E, 0x41, 0x51, 0x21, 0x5E,\n    0x7F, 0x09, 0x19, 0x29, 0x46,\n    0x26, 0x49, 0x49, 0x49, 0x32,\n    0x03, 0x01, 0x7F, 0x01, 0x03,\n    0x3F, 0x40, 0x40, 0x40, 0x3F,\n    0x1F, 0x20, 0x40, 0x20, 0x1F,\n    0x3F, 0x40, 0x38, 0x40, 0x3F,\n    0x63, 0x14, 0x08, 0x14, 0x63,\n    0x03, 0x04, 0x78, 0x04, 0x03,\n    0x61, 0x59, 0x49, 0x4D, 0x43,\n    0x00, 0x7F, 0x41, 0x41, 0x41,\n    0x02, 0x04, 0x08, 0x10, 0x20,\n    0x00, 0x41, 0x41, 0x41, 0x7F,\n    0x04, 0x02, 0x01, 0x02, 0x04,\n    0x40, 0x40, 0x40, 0x40, 0x40,\n    0x00, 0x03, 0x07, 0x08, 0x00,\n    0x20, 0x54, 0x54, 0x78, 0x40,\n    0x7F, 0x28, 0x44, 0x44, 0x38,\n    0x38, 0x44, 0x44, 0x44, 0x28,\n    0x38, 0x44, 0x44, 0x28, 0x7F,\n    0x38, 0x54, 0x54, 0x54, 0x18,\n    0x00, 0x08, 0x7E, 0x09, 0x02,\n    0x18, 0xA4, 0xA4, 0x9C, 0x78,\n    0x7F, 0x08, 0x04, 0x04, 0x78,\n    0x00, 0x44, 0x7D, 0x40, 0x00,\n    0x20, 0x40, 0x40, 0x3D, 0x00,\n    0x7F, 0x10, 0x28, 0x44, 0x00,\n    0x00, 0x41, 0x7F, 0x40, 0x00,\n    0x7C, 0x04, 0x78, 0x04, 0x78,\n    0x7C, 0x08, 0x04, 0x04, 0x78,\n    0x38, 0x44, 0x44, 0x44, 0x38,\n    0xFC, 0x18, 0x24, 0x24, 0x18,\n    0x18, 0x24, 0x24, 0x18, 0xFC,\n    0x7C, 0x08, 0x04, 0x04, 0x08,\n    0x48, 0x54, 0x54, 0x54, 0x24,\n    0x04, 0x04, 0x3F, 0x44, 0x24,\n    0x3C, 0x40, 0x40, 0x20, 0x7C,\n    0x1C, 0x20, 0x40, 0x20, 0x1C,\n    0x3C, 0x40, 0x30, 0x40, 0x3C,\n    0x44, 0x28, 0x10, 0x28, 0x44,\n    0x4C, 0x90, 0x90, 0x90, 0x7C,\n    0x44, 0x64, 0x54, 0x4C, 0x44,\n    0x00, 0x08, 0x36, 0x41, 0x00,\n    0x00, 0x00, 0x77, 0x00, 0x00,\n    0x00, 0x41, 0x36, 0x08, 0x00,\n    0x02, 0x01, 0x02, 0x04, 0x02,\n    0x3C, 0x26, 0x23, 0x26, 0x3C,\n    0x1E, 0xA1, 0xA1, 0x61, 0x12, # Extension starts here\n    0x3A, 0x40, 0x40, 0x20, 0x7A,\n    0x38, 0x54, 0x54, 0x55, 0x59,\n    0x21, 0x55, 0x55, 0x79, 0x41,\n    0x22, 0x54, 0x54, 0x78, 0x42, # a-umlaut\n    0x21, 0x55, 0x54, 0x78, 0x40,\n    0x20, 0x54, 0x55, 0x79, 0x40,\n    0x0C, 0x1E, 0x52, 0x72, 0x12,\n    0x39, 0x55, 0x55, 0x55, 0x59,\n    0x39, 0x54, 0x54, 0x54, 0x59,\n    0x39, 0x55, 0x54, 0x54, 0x58,\n    0x00, 0x00, 0x45, 0x7C, 0x41,\n    0x00, 0x02, 0x45, 0x7D, 0x42,\n    0x00, 0x01, 0x45, 0x7C, 0x40,\n    0x7D, 0x12, 0x11, 0x12, 0x7D, # A-umlaut\n    0xF0, 0x28, 0x25, 0x28, 0xF0,\n    0x7C, 0x54, 0x55, 0x45, 0x00,\n    0x20, 0x54, 0x54, 0x7C, 0x54,\n    0x7C, 0x0A, 0x09, 0x7F, 0x49,\n    0x32, 0x49, 0x49, 0x49, 0x32,\n    0x3A, 0x44, 0x44, 0x44, 0x3A, # o-umlaut\n    0x32, 0x4A, 0x48, 0x48, 0x30,\n    0x3A, 0x41, 0x41, 0x21, 0x7A,\n    0x3A, 0x42, 0x40, 0x20, 0x78,\n    0x00, 0x9D, 0xA0, 0xA0, 0x7D,\n    0x3D, 0x42, 0x42, 0x42, 0x3D, # O-umlaut\n    0x3D, 0x40, 0x40, 0x40, 0x3D,\n    0x3C, 0x24, 0xFF, 0x24, 0x24,\n    0x48, 0x7E, 0x49, 0x43, 0x66,\n    0x2B, 0x2F, 0xFC, 0x2F, 0x2B,\n    0xFF, 0x09, 0x29, 0xF6, 0x20,\n    0xC0, 0x88, 0x7E, 0x09, 0x03,\n    0x20, 0x54, 0x54, 0x79, 0x41,\n    0x00, 0x00, 0x44, 0x7D, 0x41,\n    0x30, 0x48, 0x48, 0x4A, 0x32,\n    0x38, 0x40, 0x40, 0x22, 0x7A,\n    0x00, 0x7A, 0x0A, 0x0A, 0x72,\n    0x7D, 0x0D, 0x19, 0x31, 0x7D,\n    0x26, 0x29, 0x29, 0x2F, 0x28,\n    0x26, 0x29, 0x29, 0x29, 0x26,\n    0x30, 0x48, 0x4D, 0x40, 0x20,\n    0x38, 0x08, 0x08, 0x08, 0x08,\n    0x08, 0x08, 0x08, 0x08, 0x38,\n    0x2F, 0x10, 0xC8, 0xAC, 0xBA,\n    0x2F, 0x10, 0x28, 0x34, 0xFA,\n    0x00, 0x00, 0x7B, 0x00, 0x00,\n    0x08, 0x14, 0x2A, 0x14, 0x22,\n    0x22, 0x14, 0x2A, 0x14, 0x08,\n    0x55, 0x00, 0x55, 0x00, 0x55, # 176 (25% block) missing in old code\n    0xAA, 0x55, 0xAA, 0x55, 0xAA, # 50% block\n    0xFF, 0x55, 0xFF, 0x55, 0xFF, # 75% block\n    0x00, 0x00, 0x00, 0xFF, 0x00,\n    0x10, 0x10, 0x10, 0xFF, 0x00,\n    0x14, 0x14, 0x14, 0xFF, 0x00,\n    0x10, 0x10, 0xFF, 0x00, 0xFF,\n    0x10, 0x10, 0xF0, 0x10, 0xF0,\n    0x14, 0x14, 0x14, 0xFC, 0x00,\n    0x14, 0x14, 0xF7, 0x00, 0xFF,\n    0x00, 0x00, 0xFF, 0x00, 0xFF,\n    0x14, 0x14, 0xF4, 0x04, 0xFC,\n    0x14, 0x14, 0x17, 0x10, 0x1F,\n    0x10, 0x10, 0x1F, 0x10, 0x1F,\n    0x14, 0x14, 0x14, 0x1F, 0x00,\n    0x10, 0x10, 0x10, 0xF0, 0x00,\n    0x00, 0x00, 0x00, 0x1F, 0x10,\n    0x10, 0x10, 0x10, 0x1F, 0x10,\n    0x10, 0x10, 0x10, 0xF0, 0x10,\n    0x00, 0x00, 0x00, 0xFF, 0x10,\n    0x10, 0x10, 0x10, 0x10, 0x10,\n    0x10, 0x10, 0x10, 0xFF, 0x10,\n    0x00, 0x00, 0x00, 0xFF, 0x14,\n    0x00, 0x00, 0xFF, 0x00, 0xFF,\n    0x00, 0x00, 0x1F, 0x10, 0x17,\n    0x00, 0x00, 0xFC, 0x04, 0xF4,\n    0x14, 0x14, 0x17, 0x10, 0x17,\n    0x14, 0x14, 0xF4, 0x04, 0xF4,\n    0x00, 0x00, 0xFF, 0x00, 0xF7,\n    0x14, 0x14, 0x14, 0x14, 0x14,\n    0x14, 0x14, 0xF7, 0x00, 0xF7,\n    0x14, 0x14, 0x14, 0x17, 0x14,\n    0x10, 0x10, 0x1F, 0x10, 0x1F,\n    0x14, 0x14, 0x14, 0xF4, 0x14,\n    0x10, 0x10, 0xF0, 0x10, 0xF0, # ok\n    0x00, 0x00, 0x1F, 0x10, 0x1F,\n    0x00, 0x00, 0x00, 0x1F, 0x14,\n#   0x00, 0x00, 0x00, 0xFC, 0x14, # any new after this line breaks\n    0x00, 0x00, 0x00, 0x00, 0x00  # 255 also a <space>\n])\n\ndef convert_color_RGB888_RGB565(R,G,B): # Convert RGB888 to RGB565\n    return (((G&0b00011100)<<3) + ((B&0b11111000)>>3)<<8) + (R&0b11111000) + ((G&0b11100000)>>5)\n\ndef character(asc, xt, yt, sz, r, g, b):  # Single character sz is size: 1 or 2\n    cc = convert_color_RGB888_RGB565(r,g,b)\n    code = asc * 5    # 5 bytes per character\n    for ii in range(5):\n        line = FONT[code + ii]\n        for yy in range(8):\n            if (line >> yy) & 0x1:\n                LCD.pixel(ii*sz+xt,yy*sz+yt,cc) \n                if sz > 1:\n                    LCD.pixel(ii*sz+xt+1,yy*sz+yt,cc)\n                    LCD.pixel(ii*sz+xt,yy*sz+yt+1,cc)\n                    LCD.pixel(ii*sz+xt+1,yy*sz+yt+1,cc)\n                if sz == 3:\n                    LCD.pixel(ii*sz+xt,  yy*sz+yt+2,cc)\n                    LCD.pixel(ii*sz+xt+1,yy*sz+yt+2,cc)\n                    LCD.pixel(ii*sz+xt+2,yy*sz+yt+2,cc)\n                    LCD.pixel(ii*sz+xt+2,yy*sz+yt,cc)\n                    LCD.pixel(ii*sz+xt+2,yy*sz+yt+1,cc)\n\n# Print a string\ndef prnt_st(asci, xx, yy, sz,r,g,b):  # Text string\n    if sz == 1: move = 6\n    if sz == 2: move = 11\n    if sz == 3: move = 17 \n    for letter in(asci):\n        asci = ord(letter)\n        character(asci,xx,yy,sz,r,g,b)\n        xx = xx + move\n\n# Center text string\ndef cntr_st(s,y,sz,r,g,b): # Centres text on line y\n    if sz == 1: w = 6\n    if sz == 2: w = 11\n    if sz == 3: w = 17 \n    gap = int((width - len(s) * w)/2)\n    prnt_st(s,gap,y,sz,r,g,b)\n\nLCD.fill(LCD.black)    \nprnt_st(\"Hello world! Red\", 20, 80, 2, 255, 0, 0)\nprnt_st(\"Hello world! Green\", 20, 100, 2, 0, 255, 0)\nprnt_st(\"Hello world! Blue\", 20, 120, 2, 0, 0, 255)\nprnt_st(\"Hello world! White\", 20, 140, 2, 255, 255, 255)\nLCD.show()\n
"},{"location":"lessons/","title":"Python Project-Based Learning: Building Clocks and Watches with Smart Watch Displays","text":"

These lessons are general to all of our kits. We often use the Pico OLED for many examples since that is the kit we have been using for the longest time.

These lessons focus on using functions like the MicroPython localtime() to get the time in your current timezone.

They also focus on drawing scalable numbers on different size displays and drawing times on larger color displays.

Here are some sample lesson plans for a 14-week semimester hich school course on learning computational thining by using MicroPython to write clock and smartwatch application. This course assumes no prior knowledge of programming.

"},{"location":"lessons/#weeks-1-2-introduction-to-python-and-basic-concepts","title":"Weeks 1-2: Introduction to Python and Basic Concepts","text":""},{"location":"lessons/#lesson-1-introduction-to-python","title":"Lesson 1: Introduction to Python","text":""},{"location":"lessons/#lesson-2-hello-world","title":"Lesson 2: Hello, World!","text":""},{"location":"lessons/#lesson-3-variables-and-data-types","title":"Lesson 3: Variables and Data Types","text":""},{"location":"lessons/#lesson-4-basic-operations","title":"Lesson 4: Basic Operations","text":""},{"location":"lessons/#weeks-3-4-control-structures-and-functions","title":"Weeks 3-4: Control Structures and Functions","text":""},{"location":"lessons/#lesson-5-conditionals","title":"Lesson 5: Conditionals","text":""},{"location":"lessons/#lesson-6-loops","title":"Lesson 6: Loops","text":""},{"location":"lessons/#lesson-7-functions","title":"Lesson 7: Functions","text":""},{"location":"lessons/#weeks-5-6-working-with-libraries-and-hardware","title":"Weeks 5-6: Working with Libraries and Hardware","text":""},{"location":"lessons/#lesson-8-importing-libraries","title":"Lesson 8: Importing Libraries","text":""},{"location":"lessons/#lesson-9-introduction-to-hardware-interaction","title":"Lesson 9: Introduction to Hardware Interaction","text":""},{"location":"lessons/#weeks-7-8-project-1-basic-digital-clock","title":"Weeks 7-8: Project 1: Basic Digital Clock","text":""},{"location":"lessons/#lesson-10-planning-the-project","title":"Lesson 10: Planning the Project","text":""},{"location":"lessons/#lesson-11-building-a-basic-digital-clock","title":"Lesson 11: Building a Basic Digital Clock","text":""},{"location":"lessons/#weeks-9-10-intermediate-python-concepts","title":"Weeks 9-10: Intermediate Python Concepts","text":""},{"location":"lessons/#lesson-12-lists-and-dictionaries","title":"Lesson 12: Lists and Dictionaries","text":""},{"location":"lessons/#lesson-13-error-handling","title":"Lesson 13: Error Handling","text":""},{"location":"lessons/#weeks-11-12-project-2-analog-clock-display","title":"Weeks 11-12: Project 2: Analog Clock Display","text":""},{"location":"lessons/#lesson-14-planning-the-project","title":"Lesson 14: Planning the Project","text":""},{"location":"lessons/#lesson-15-building-an-analog-clock-display","title":"Lesson 15: Building an Analog Clock Display","text":""},{"location":"lessons/#weeks-13-14-advanced-python-concepts-and-optimization","title":"Weeks 13-14: Advanced Python Concepts and Optimization","text":""},{"location":"lessons/#lesson-16-more-about-functions","title":"Lesson 16: More About Functions","text":""},{"location":"lessons/#lesson-17-optimization-techniques","title":"Lesson 17: Optimization Techniques","text":""},{"location":"lessons/#weeks-15-16-final-project-customizable-clockwatch","title":"Weeks 15-16: Final Project: Customizable Clock/Watch","text":""},{"location":"lessons/#lesson-18-planning-the-project","title":"Lesson 18: Planning the Project","text":""},{"location":"lessons/#lesson-19-building-a-customizable-clockwatch","title":"Lesson 19: Building a Customizable Clock/Watch","text":""},{"location":"lessons/#lesson-20-final-project-presentations","title":"Lesson 20: Final Project Presentations","text":""},{"location":"lessons/#ongoing-reflection-and-feedback","title":"Ongoing: Reflection and Feedback","text":"

This outline provides a balance between teaching Python concepts and applying them to real-world projects, which can keep students engaged and help them to better retain what they've learned. Tailor this outline to meet the specific needs and pace of your students, and ensure to provide plenty of opportunities for students to practice their skills, ask questions, and get feedback.

"},{"location":"lessons/00-hardware-config/","title":"Hardware Configuration Files","text":"

All our current displays use the SPI interface. The alternative microcontroller bus, I2C, is about 10x slower than SPI. The SPI intrface interface typically uses 7 wires, which can get a little complex. Just a single wire broken or two wires crossed can will cause the display to not work.

We have dozens of sample programs for each kit. And the exact connections on our kits vary. Rather than hard-coding the hardware pin numbers in each example, we can move them to a separate config.py file. We can then just import that file into each of our examples.

Here is a sample config.py file:

# Dan's Robot Labs configuration file for ILI9341 clock project\n# The colors on the SPI bus cable are:\n# 3.3v power - red\n# SCK - orange\n# MISO/Data - yellow\n# DC - green\n# RESET - blue\n# GND - black\n# CS - purple\n\nSCK_PIN = 2\nMISO_PIN = 3 # labeled SDI(MOSI) on the back of the display\nDC_PIN = 4\nRESET_PIN = 5\nCS_PIN = 6\nROTATION = 90\n\nWIDTH=320\nHEIGHT=240\nBUTTON_1_PIN = 14 # one up from lower-left corner\nBUTTON_2_PIN = 15 # lower left corner with USB on top\n

To use this configuration file you will need to do two things:

  1. import the config file (don't add the .py extension)
  2. Add the prefix config. to each value you would like to reference.
import config\n\nWIDTH=config.WIDTH\n
"},{"location":"lessons/01-drawing-coordinates/","title":"Drawing Coordinates","text":"

Screen Coordinates Lab on Robot Faces

"},{"location":"lessons/01-get-local-time/","title":"Getting Local Time","text":""},{"location":"lessons/01-get-local-time/#lesson-overview","title":"Lesson Overview","text":"

In this lesson, we will learn how to get the local date and time from the MicroPython runtime. We will learn the components of the list of numbers returned by these functions.

MicroPython provides a very convenient function called localtime() that will get the time from the host computer that you connect to via your USB cable. Your local computer has a clock and knows the time in the local timezone. This information is sent to MicroPython runtime when the computer is connected to the USB cable.

Note

The gmtime() function returns the time in UTC (Coordinated Universal Time) time. Use this if you want time stamps that are not dependent on time zone and daylight savings rules. For example, log files should use this function.

"},{"location":"lessons/01-get-local-time/#sample-code","title":"Sample Code","text":"
import time\n\nprint(time.localtime())\n
"},{"location":"lessons/01-get-local-time/#result","title":"Result","text":"

Local time returns a read-only tuple:

(2023, 2, 28, 18, 54, 7, 1, 59)\n
"},{"location":"lessons/01-get-local-time/#extracting-specific-datetime-components","title":"Extracting Specific Date/Time Components","text":"
from time import localtime\n\nnow = localtime()\nprint(now)\n\nprint('year:',    now[0])\nprint('month:',   now[1])\nprint('day:',     now[2])\nprint('hour:',    now[3])\nprint('minute:',  now[4])\nprint('sec:',     now[5])\nprint('weekday:', now[6])\nprint('day of year:', now[7])\n
"},{"location":"lessons/01-get-local-time/#format-of-the-localtime","title":"Format of the Localtime","text":"

The format is a \"8-tuple\" of integers in the format (year, month, mday, hour, minute, second, weekday, yearday)

Most of the values are pretty intuitive to figure out. There are a few notes.

  1. Hour is the hour in 24-hour format. If we want to display the hour in 12-hour format we will need to do some formatting.
  2. Weekday is a numeric code that runs from 0 for Monday to 6 for Sunday.
  3. Yearday is the day of the year. January 1st is the fist day of the year so it is a 1.
"},{"location":"lessons/01-get-local-time/#printing-formatted-dates","title":"Printing Formatted Dates","text":""},{"location":"lessons/01-get-local-time/#sample-code_1","title":"Sample Code","text":"
import time\n\nnow = time.localtime()\nlabel = ('year', 'month', 'mday', 'hour', 'minute', 'second', 'weekday', 'yearday')\nfor i in range(8):\n    print(label[i], ':', now[i])\n\nprint()\nprint(\"Date: {}/{}/{}\".format(now[1], now[2], now[0]))\nprint(\"Time: {}:{}\".format(now[3], now[4]))\n
"},{"location":"lessons/01-get-local-time/#results","title":"Results","text":"
year : 2023\nmonth : 2\nmday : 28\nhour : 19\nminute : 20\nsecond : 26\nweekday : 1\nyearday : 59\n\nDate: 2/28/2023\nTime: 19:20\n
"},{"location":"lessons/01-get-local-time/#label-formatting","title":"Label Formatting","text":"

We can also add labels to make the date and time easier to read.

import time\n\ndays = ('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday',\n        'Sunday')\nmonths = ('Jan', 'Feb', 'March', 'April', 'May', 'June', 'July',\n          'Aug', 'Sept', 'Oct', 'Nov', 'Dec')\n\nnow = time.localtime()\n\nlabel = ('year', 'month', 'mday', 'hour', 'minute', 'second', 'weekday', 'yearday')\n\nmonth_number = now[1]\nmonth_name = months[month_number]\nweekday_number = now[6]\nday_name = days[weekday_number]\nhour_number = now[3]\nif hour_number < 13:\n    hour_12 = hour_number\n    am_pm = 'am'\nelse:\n    hour_12 = hour_number - 12\n    am_pm = 'pm'\n\nprint()\nprint(\"Date: {} {} {}, {}\".format(day_name, month_name, now[2], now[0]))\nprint(\"Time: {}:{} {}\".format(hour_12, now[4], am_pm))\n
"},{"location":"lessons/01-get-local-time/#label-formatted-results","title":"Label Formatted Results","text":"
Date: Tuesday March 28, 2023\nTime: 7:46 pm\n
"},{"location":"lessons/01-get-local-time/#modifying-the-thonny-parameters","title":"Modifying the Thonny Parameters","text":"

By default Thonny syncs the host time at power up.

We can disable this.

import time\nprint(time.localtime())\n

(2021, 1, 1, 0, 0, 22, 4, 1)\n
This was printed 22 seconds after power up.

The Pico \"W\" can sync with the NTP functions.

from mpython import *\nimport ntptime\nimport secrets\n\n# summary\nmywifi=wifi()\nmywifi.connectWiFi(secrets.wifi_ssid, secrets.wifi_pass)\n\nprint(\"Local time before synchronization\uff1a%s\" %str(time.localtime()))\nntptime.settime()\nprint(\"Local time after synchronization\uff1a%s\" %str(time.localtime()))\n

Result:

Local time before synchronization\uff1a(2021, 1, 1, 0, 3, 31, 4, 1)\nLocal time after synchronization\uff1a(2023, 10, 16, 20, 16, 59, 0, 289)\n
"},{"location":"lessons/01-get-local-time/#references","title":"References","text":""},{"location":"lessons/02-display-text/","title":"Display Time and Date in Text","text":"
from utime import localtime, sleep\nfrom LCD_1inch28 import LCD_1inch28\n\nLCD = LCD_1inch28()  \n\ndays = ('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday',\n        'Sunday')\nmonths = ('Jan', 'Feb', 'March', 'April', 'May', 'June', 'July',\n          'Aug', 'Sept', 'Oct', 'Nov', 'Dec')\nlabel = ('year', 'month', 'mday', 'hour', 'minute', 'second', 'weekday', 'yearday')\n\ndef format_time(now):\n    hour_number = now[3]\n    if hour_number < 13:\n        hour_12 = hour_number\n        am_pm = 'am'\n    else:\n        hour_12 = hour_number - 12\n        am_pm = 'pm'\n    minutes_num = now[4]\n    if minutes_num < 10:\n        min_leading_zero = '0'\n    else:\n        min_leading_zero = ''\n    seconds_num = now[5]\n    if seconds_num < 10:\n        sec_leading_zero = '0'\n    else:\n        sec_leading_zero = ''\n    return \"{}:{}{}:{}{} {}\".format(hour_12, min_leading_zero, minutes_num, sec_leading_zero, seconds_num, am_pm)\n\ndef format_date(now):\n    month_number = now[1]\n    month_name = months[month_number - 1]\n    weekday_number = now[6]\n    day_name = days[weekday_number]\n    hour_number = now[3]\n    return \"{} {} {}, {}\".format(day_name, month_name, now[2], now[0])\n\nwhile(True):\n    now = localtime()\n    # print(now)\n    LCD.fill(LCD.black)    \n    LCD.text(format_time(now), 77, 50, LCD.white)\n    LCD.text(format_date(now), 40, 80, LCD.white)\n    LCD.show()\n    sleep(1)\n
"},{"location":"lessons/03-drawing-face/","title":"Drawing Watch Faces","text":"

An analog watch ususally has hands that are drawn from the center to the edge of the display.

The lines or marks that indicate the hours are often referred to as \"hour indices\" or \"hour markers.\"

The smaller lines that indicate the minutes are called \"minute indices\" or \"minute markers.\"

For simple lines, we can use the line(x1,y1, x2,y2,color) function which draws a sinle pixel line from the starting point at (x1,y1) to the end pint at (x2,y2) using the fifth color parameter. For monochrome clocks the color 1 is whtie and 0 is black.

For thinker lines we can use the display.rect() function.

Some watches have more complex hands that are composed of multiple geometries such as triangles.

For most modern display drivers we can use the MicroPython poly() function to draw polygons on the screen. These are part of the MicroPython framebuf function.

Some older drivers don't yet support these functions, so we might have to craft our own functions.

"},{"location":"lessons/03-drawing-face/#references","title":"References","text":"

Instructables on Filled Circles and Triangles by TonyGo2

"},{"location":"lessons/04-larger-fonts/","title":"Larger Fonts","text":"

Many display libraries use the MicroPython framebuf standards that only provide for a single font. This is sometimes fine for simple small displays, but it is sometimes insufficient for some projects where either a smaller or larger font is used.

The ssd1306 driver is a good example of the default behaviour of text drawing. Only a single font is suppied.

display.text('Hello, World!', 0, 0, 1)\ndisplay.show()\n

Some display drivers allow you to change the default font:

LCD160 Set Font

Font to Py Command

Sample Font Files

"},{"location":"lessons/04-larger-fonts/#references","title":"References","text":"

Peter Hinch's Font to Python Utility - a utility takes input a font file in ttf or otf form together with a height in pixels and outputs a Python source file containing the font as a bitmap.

Peter Hinch's Nano GUI

Les' Lab: Designing your own Pico fonts - Raspberry Pi Pico display fonts in MicroPython: How to design fonts for your Pi Pico project.

"},{"location":"lessons/05-buttons/","title":"Buttons","text":"

Our standard watch kit places two buttons on the breadboard. You can use these buttons to set the time.

The buttons are connected to the lower-left corner of the pico using GPIO 14 and GPIO 15.

Instructions on how to use these buttons are covered in our MicroPython class:

Using Buttons in MicroPython

We suggest using the top blue button to change the mode of operation. As you press this you cycle through various modes of your clock or watch. Here are some sample modes for a clock:

  1. Run mode
  2. Set current time hour
  3. Set current time minute
  4. Set alarm hour
  5. Set alarm minute

After you are in a given mode, the bottom button can be used to cycle through the options. Remember to get the current value for the cycle for each mode. So if you are adjusting the hour you have to make sure the cycle value starts at the current hour.

"},{"location":"lessons/05-buttons/#sample-button-mode-code","title":"Sample Button Mode Code","text":"
ifrom utime import sleep, ticks_ms\nfrom machine import Pin\n\n# Sample Raspberry Pi Pico MicroPython button press example with a debounce delay value of 200ms in the interrupt handler\n\n# Config\nMODE_BUTTON_PIN = 14\nCYCLE_BUTTON_PIN = 15\nmode = 0 # the count of times the button has been pressed\ncycle = 0\nlast_time = 0 # the last time we pressed the button\n\nbuiltin_led = machine.Pin(25, Pin.OUT)\n# The lower left corner of the Pico has a wire that goes through the buttons upper left and the lower right goes to the 3.3 rail\nmode_pin = machine.Pin(MODE_BUTTON_PIN, machine.Pin.IN, machine.Pin.PULL_UP)\ncycle_pin = machine.Pin(CYCLE_BUTTON_PIN, machine.Pin.IN, machine.Pin.PULL_UP)\n\n# This function gets called every time the button is pressed.  The parameter \"pin\" is not used.\ndef button_pressed_handler(pin):\n    global mode, cycle, last_time\n    new_time = ticks_ms()\n    # if it has been more that 1/5 of a second since the last event, we have a new event\n    if (new_time - last_time) > 200:\n        # this should be pin.id but it does not work\n        if '14' in str(pin):\n            mode +=1\n        else:\n            cycle +=1\n        # last, we update the last time we got an ISR here\n        last_time = new_time\n\n\n# now we register the handler function when the button is pressed\nmode_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\ncycle_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\n\n# This is for only printing when a new button press count value happens\nold_mode = 0\nold_cycle = 0 \n\nwhile True:\n    # only print on change in the button_presses value\n    if mode != old_mode:\n        print('New Mode:', mode)\n        builtin_led.toggle()\n        old_mode = mode\n    if cycle != old_cycle:\n        print('New Cycle:', cycle)\n        builtin_led.toggle()\n        old_cycle = cycle\n    sleep(.1)\n
"},{"location":"lessons/06-draw-performance/","title":"Drawing Performance","text":"

One of the challenges we face when updating the watch display is that refreshing an entire screen using a relatively slow SPI interface means that we need to be thoughtful about updating the displays.

By default, many screen drivers update every pixel of the screen when the user does a show() operation. For small monochrome screens this is not usually a problem. But for larger color screens the draw times can lead to slow updates.

Let's do a little math to see when drawing performance becomes a problem. Remember that the human eye can's really see screen updates that occur faster than about 30 frames per second. That is why most film movies were filled at 25 frames per second.

To calculate the full-screen draw time we need to calculate the total number of bits we need to send and then calculate the time it takes to send these bits. We can then check our math by looking at timestamps just before we draw and after we finish the drawing.

Let's start out with our favorite clock screen: the 128X64 monochrome OLED screen.

  1. Width = 128
  2. Height = 64
  3. Bits per Pixel = 1

Total bits = 128 * 64 * 1 = 8,192 bits = 1024 bytes

Now we need to also know the transfer speed of our display interface. Although there are both I2C and SPI versions of these displays, we usually prefer the SPI that should transfer data at about

Pi Pico SPI LCD using a frame buffer to get 30fps animation - ILI9341 and ST7789 MicroPython drivers

"},{"location":"lessons/06-draw-performance/#sample-timer-code","title":"Sample Timer Code","text":"

We can calculate the time to draw the full screen by recording the number of clock ticks in microseconds before and after we do a screen update.

from utime import ticks_us\n\nstart = ticks_us()\nscreen_update()\nend = ticks_us()\nprint('Execution time in microseconds:', end - start)\n

On the OLED screen, we get a result that is around 10 milliseconds which is 100 screen updates per second.

"},{"location":"lessons/06-draw-performance/#references","title":"References","text":"
  1. Notes on the very slow refresh rate for ST7735
"},{"location":"lessons/07-drawing-digits/","title":"Drawing Clock Digits","text":""},{"location":"lessons/07-drawing-digits/#the-seven-segments-of-a-numeric-digit","title":"The Seven Segments of a Numeric Digit","text":"

Since the early history of computing, numeric displays were created using a grouping of seven individual lights to display a single digit. These are called Seven Segment Displays. An example is shown in the image below:

The segments are labeled \"a\" through \"g\" starting at the top and going around in a clockwise direction. Note that the seventh segment is in the center element is the \"g\" segment.

Technically, many displays have an 8th segment for the decimal point (DP). To keep things simple we will just focus on the main seven segments in this lesson.

Clocks also usually have a colon that separates the hours and minutes and an AM/PM indicator for 12-hour displays. We will be treating these and independent drawing components in this lab. Many digital clocks have the colon flash on and off every second.

"},{"location":"lessons/07-drawing-digits/#the-segment-map","title":"The Segment Map","text":"

To start out, we need to create a \"digit-to-segment map\" data structure. We use this to help us figure out what segments to turn on for any given digit.

We will also need to convert the segment letters into integers. These integers will form the index of an array.

To write our code, we ask, for any given digit, is the segment on or off?

To do this, we can create an array of segments like this:

segmentMapping = [\n  #a, b, c, d, e, f, g\n  [1, 1, 1, 1, 1, 1, 0], # 0\n  [0, 1, 1, 0, 0, 0, 0], # 1\n  [1, 1, 0, 1, 1, 0, 1], # 2\n  [1, 1, 1, 1, 0, 0, 1], # 3\n  [0, 1, 1, 0, 0, 1, 1], # 4\n  [1, 0, 1, 1, 0, 1, 1], # 5\n  [1, 0, 1, 1, 1, 1, 1], # 6\n  [1, 1, 1, 0, 0, 0, 0], # 7\n  [1, 1, 1, 1, 1, 1, 1], # 8\n  [1, 1, 1, 1, 0, 1, 1]  # 9\n];\n

For any given digit like \"2\" we can then just pass the index of \"2\" to a Python list to get a list of the segments to turn on. That line of code looks like this:

segmentOn = segmentMapping[digit];\n

This will return the segments that are to be turned on for any given input digit. For example:

segmentOn = segmentMapping[2];\nprint(segmentOn)\n# returns:\n[1, 1, 0, 1, 1, 0, 1]\n

We now have the data structure to do the drawing. We will do this in two steps.

  1. Draw the three horizontal lines for a digit if they are on
  2. Draw all vertical lines for a given digit, keeping in mind that we need to only draw half the way down the digit for the upper left and upper right vertical segments (1 and 5)

We will start out with a simple digit drawing using a single-pixel width line. Later we will add a parameter to change the thickness of the line. But we want to take this one-step-at-a-time.

"},{"location":"lessons/07-drawing-digits/#drawing-horizontal-segments","title":"Drawing Horizontal Segments","text":"

This sample code will step through the horizontal lines for segments 0, 3 and 6. It will check using the segmentOn[i] line which will ONLY be true if that segment should be turned on for a given digit.

Next, we figure out how high to draw the horizontal line. To do this we will need to figure out the vertical offset (yOffset). The top line (segment 0) has no offset and the bottom line has the full height of the digit.

Unlike the vertical lines, the horizontal lines will span the entire width of the area. That makes our code simple because the line will have a horizontal length of exactly the x plus the width of the digit.

# reasonable test values for a small display\nx = 10\ny = 10\nwidth = 20\nheight = 30\n# Horizontal segments\n  for i in [0, 3, 6]:\n    # check if this digit has the segment on\n    if (segmentOn[i]):\n      if (i==0): # top\n          yOffset = 0 \n      if (i==3):\n          yOffset = height # bottom element\n      if (i==6):\n          yOffset = height // 2 # bottom\n      # draw a signal thin line\n      oled.line(x, y+yOffset, x + width, y+yOffset, 1)\n
"},{"location":"lessons/07-drawing-digits/#drawing-the-vertical-segments","title":"Drawing the Vertical Segments","text":"

Next, we need to create some code to draw the four vertical segments. This is a little more complex because the lines do NOT span the full height of the digit.

# Draw Vertical segments\nx = 10\ny = 10\nwidth = 20\nheight = 30\n# Draw the vertical segments ur, lr, ll, ul\nfor i in [1, 2, 4, 5]:\n    if (segmentOn[i]) :\n        # top two segments\n        if (i==1 or i==5):\n            startY = 0\n            endY = height // 2\n        if (i==2 or i==4):\n            startY = height // 2\n            endY = height\n        # left segments\n        if (i==4 or i==5): xOffset = 0\n        # right segments\n        if (i==1 or i==2): xOffset = width\n        oled.line(x+xOffset, y+startY, x+xOffset, y+endY, 1)\n
"},{"location":"lessons/07-drawing-digits/#testing-our-drawing","title":"Testing Our Drawing","text":"

We can now wrap the code above in a drawDigit() function, and we will test each of the digits 0-9 in a small test program.

Our first version of drawDigit is modeled after the rect() function. After we pass in the digit, it takes in the (x,y) and width and height parameters and the color value.

Draw Digit Function Parameters:

  1. The digit to display - an integer in the range 0-9
  2. x of the upper left corner
  3. y of the upper left corner
  4. the width of the digit (about 20 is a good value)
  5. the height of the digit
  6. the color (0=black, 1=white)

Here is our testing program that draws digits 0-9 in the center of a standard 128x64 monochrome OLED display:

# Lab 20: Draw Digits with just the line function\n# this lab uses the line() function to draw the segments\nimport machine\nimport utime\nimport ssd1306\nfrom utime import sleep, localtime\nled = machine.Pin(25, machine.Pin.OUT)\n\nSCL=machine.Pin(2) # SPI CLock\nSDA=machine.Pin(3) # SPI Data\nspi=machine.SPI(0, sck=SCL, mosi=SDA, baudrate=100000)\n\nRES = machine.Pin(4)\nDC = machine.Pin(5)\nCS = machine.Pin(6)\nWIDTH = 128\nHEIGHT = 64\n\noled = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)\n\nsegmentMapping = [\n  #a, b, c, d, e, f, g\n  [1, 1, 1, 1, 1, 1, 0], # 0\n  [0, 1, 1, 0, 0, 0, 0], # 1\n  [1, 1, 0, 1, 1, 0, 1], # 2\n  [1, 1, 1, 1, 0, 0, 1], # 3\n  [0, 1, 1, 0, 0, 1, 1], # 4\n  [1, 0, 1, 1, 0, 1, 1], # 5\n  [1, 0, 1, 1, 1, 1, 1], # 6\n  [1, 1, 1, 0, 0, 0, 0], # 7\n  [1, 1, 1, 1, 1, 1, 1], # 8\n  [1, 1, 1, 1, 0, 1, 1]  # 9\n];\n\n\n# x and y are the center of the digit, size is the center to edge\ndef drawDigit(digit, x, y, width, height, color):\n  # get a list of the segments that are on for this digit\n  segmentOn = segmentMapping[digit];\n\n  # Draw the horizontal segments: top, bottem, middle\n  for i in [0, 3, 6]:\n    if (segmentOn[i]):\n      if (i==0): # top\n          yOffset = 0 \n      if (i==3):\n          yOffset = height # bottom element\n      if (i==6):\n          yOffset = height // 2 # middle line\n      oled.line(x, y+yOffset, x + width, y+yOffset, 1)\n\n  # Draw the vertical segments ur, lr, ll, ul\n  for i in [1, 2, 4, 5]:\n    if (segmentOn[i]) :\n        # top two segments\n        if (i==1 or i==5):\n            startY = 0\n            endY = height // 2\n        if (i==2 or i==4):\n            startY = height // 2\n            endY = height\n        # left segments\n        if (i==4 or i==5): xOffset = 0\n        # right segments\n        if (i==1 or i==2): xOffset = width\n        oled.line(x+xOffset, y+startY, x+xOffset, y+endY, 1)\n\n\noled.fill(0)\noled.text('Lab 12: rect', 0, 0, 1)\nx = 10 # upper left corner x\ny = 10 # upper left corner y\nw = 20 # digit width\nh = 30 # digit height\n\nwhile True:\n    for i in range(0, 10):\n\n        print(i)\n        # create an outline one px away from the drawing region\n        oled.rect(x-2, y-2, w+5, h+5, 1)\n        # draw one digit\n        drawDigit(i, x, y, w, h, 1)\n        # draw a second digit\n        #drawDigit(i, x + w + 4, w, h, t, 1)\n        oled.text(str(i), 0, 54, 1)\n        oled.show()\n        sleep(2)\n        oled.fill(0)\n

This drawDigit() is very handy. We just need to give the function some parameters and it will draw the right digit at a location (x,y) as the upper left corner with the correct width and height!

But the lines are somewhat thin. A more robust implementation will also allow us the change the thickness of the segments. That will be more complex since we will have to adjust the starting point of where we draw each segment based on the thickness of the segment. We will also need to use a rectangle, not a line. This requires we figure out the exact width and height before we draw.

We explore that function in the next lesson.

"},{"location":"lessons/09-digit-functions/","title":"Creating Digit Functions","text":"

In the last section, we drew a seven-segment digit number using the line function. In this section, we will build a better function that will add a flexible new parameter for the thickness of the digit segments. We will then test the function under a variety of conditions and see how we can make the function easier to use.

"},{"location":"lessons/09-digit-functions/#new-thickness-parameters","title":"New Thickness Parameters","text":"

Placing the rectangles correctly within a seven-segment display when we have many drawing parameters requires some precision. We will use a decomposition process and take \"one step at a time\" to work out the details.

"},{"location":"lessons/09-digit-functions/#draw-segment-function","title":"Draw Segment Function","text":"

Here is our revised parameters list:

  1. The digit to display - an integer in the range 0-9
  2. x of the upper left corner
  3. y of the upper left corner
  4. the width of the digit (about 20 is a good value)
  5. the height of the digit
  6. (NEW) the thickness of each segment (3 or 4 are good values)
  7. the color (0=black, 1=white)

Here is our revised function for drawing a single digit with the segment thickness added.

# digit is the number to display\n# x and y are upper-left-corner\n# width and height are the dimensions of the digit\n# thickness is the width of the line segments\n# color is 1 for white and 0 for black\ndef drawDigit(digit, x, y, width, height, thickness, color):\n  # get a list of the segments that are on for this digit\n  segmentOn = segmentMapping[digit];\n\n  # Draw the horizontal segments: top, bottom, middle\n  for i in [0, 3, 6]:\n    if (segmentOn[i]):\n      if (i==0): # top\n          yOffset = 0 \n      if (i==3):\n          yOffset = height - thickness # bottom element\n      if (i==6):\n          yOffset = height // 2 - thickness // 2# bottom\n      # oled.line(x - size, y+yOffset-size, x + size, y+yOffset-size, 1);\n      oled.fill_rect(x, y+yOffset, width, thickness, color)\n\n  # Draw the vertical segments ur, lr, ll, ul\n  for i in [1, 2, 4, 5]:\n    if (segmentOn[i]) :\n      # upper vertical lines\n      if (i==1 or i==5):\n          startY = y\n          endY = y + height // 2\n      # lower two vertical lines (2=lower right and 4=lower left)\n      if (i==2 or i==4):\n          startY = y + height // 2\n          endY = y + height\n      if (i==4 or i==5): xOffset = 0\n      if (i==1 or i==2): xOffset = width-thickness\n\n      oled.fill_rect(x+xOffset, startY, thickness, endY-startY, color)\n

Let's now take a look at how we had to modify our code.

The first item to note is that when we calculate a starting point to draw, we can't just draw at the edge of the region. We need to back off our initial drawing point by the width of the segment.

Here is an example of subtracting the thickness from y when we draw the bottom segment:

yOffset = height - thickness # bottom element\n

"},{"location":"lessons/09-digit-functions/#full-test-program","title":"Full Test Program","text":"
# Clock Lab 20: Draw Seven Segments\n# this lab uses the fill_rect function to draw the segments\nimport machine\nimport utime\nimport ssd1306\nfrom utime import sleep, localtime\nled = machine.Pin(25, machine.Pin.OUT)\n\nSCL=machine.Pin(2) # SPI CLock\nSDA=machine.Pin(3) # SPI Data\nspi=machine.SPI(0, sck=SCL, mosi=SDA, baudrate=100000)\n\nRES = machine.Pin(4)\nDC = machine.Pin(5)\nCS = machine.Pin(6)\n\noled = ssd1306.SSD1306_SPI(128, 64, spi, DC, RES, CS)\n\nsegmentMapping = [\n  #a, b, c, d, e, f, g\n  [1, 1, 1, 1, 1, 1, 0], # 0\n  [0, 1, 1, 0, 0, 0, 0], # 1\n  [1, 1, 0, 1, 1, 0, 1], # 2\n  [1, 1, 1, 1, 0, 0, 1], # 3\n  [0, 1, 1, 0, 0, 1, 1], # 4\n  [1, 0, 1, 1, 0, 1, 1], # 5\n  [1, 0, 1, 1, 1, 1, 1], # 6\n  [1, 1, 1, 0, 0, 0, 0], # 7\n  [1, 1, 1, 1, 1, 1, 1], # 8\n  [1, 1, 1, 1, 0, 1, 1]  # 9\n];\n\n# digit is the numer to display\n# x and y are upper-left-corner\n# width and height are the dimensions of the digit\n# thickness is the width of the line segments\n# color is 1 for white and 0 for black\ndef drawDigit(digit, x, y, width, height, thickness, color):\n  # get a list of the segments that are on for this digit\n  segmentOn = segmentMapping[digit];\n\n  # Draw the horizontal segments: top, bottom, middle\n  for i in [0, 3, 6]:\n    if (segmentOn[i]):\n      if (i==0): # top\n          yOffset = 0 \n      if (i==3):\n          yOffset = height - thickness # bottom element\n      if (i==6):\n          yOffset = height // 2 - thickness // 2# bottom\n      # oled.line(x - size, y+yOffset-size, x + size, y+yOffset-size, 1);\n      oled.fill_rect(x, y+yOffset, width, thickness, color)\n\n  # Draw the vertical segments ur, lr, ll, ul\n  for i in [1, 2, 4, 5]:\n    if (segmentOn[i]) :\n      # upper vertical lines\n      if (i==1 or i==5):\n          startY = y\n          endY = y + height // 2\n      # lower two vertical lines (2=lower right and 4=lower left)\n      if (i==2 or i==4):\n          startY = y + height // 2\n          endY = y + height\n      if (i==4 or i==5): xOffset = 0\n      if (i==1 or i==2): xOffset = width-thickness\n\n      oled.fill_rect(x+xOffset, startY, thickness, endY-startY, color)\n\noled.fill(0)\noled.text('Lab 12: rect', 0, 0, 1)\nx = 10 # upper left corner x\ny = 10 # upper left corner y\nw = 20 # digit width\nh = 30 # digit height\nt = 3\n\nwhile True:\n    for i in range(0, 10):\n\n        print(i)\n        # create an outline on px away from the drawing region\n        oled.rect(x-2, y-2, w+4, h+4, 1)\n        # draw one digit\n        drawDigit(i, x, y, w, h, t, 1)\n        # draw a second digit\n        #drawDigit(i, x + w + 4, w, h, t, 1)\n        oled.text(str(i), 0, 54, 1)\n        oled.show()\n        sleep(2)\n        oled.fill(0)\n
"},{"location":"lessons/12-draw-time-functions/","title":"Draw a Digital Clock Time Function","text":"

In this section, we will create a new function called drawTime. The input will be the upper left starting point (x,w), width and height, hours and minutes and the format (12-hour or 24-hour) and the color (black or white).

This lab will build in the prior lab that displayed a single digit.

Let's first of all create a plan of how we will layout our digital clock display.

Our goal is to create a function that will be reusable over many types of displays with little or no modification. This will require us to think through the potential ways our users will use this function and anticipate their needs so they are not tempted to rewrite our code on their own.

"},{"location":"lessons/12-draw-time-functions/#12-hour-display-format","title":"12-Hour Display Format","text":"

Here is an example of our 12-hour clock layout:

Note that there are six horizontal x-dimension numbers and three vertical y-dimension numbers that you can adjust to create a pleasant interface.

"},{"location":"lessons/12-draw-time-functions/#automatically-scaling-the-layout","title":"Automatically Scaling the Layout","text":"

It would be tempting to just hard-code the layout using a single set of numbers. But what if the user wanted a smaller or larger portion of their display used to display the time?

As the user selects the height and width of their clock we would like to scale these numbers accordingly. The absolute values of these numbers is not too important now. We just want to get the relative values down. To start the process, I did a layout on an 11-inch wide drawing and I assigned the width of each digit to be 3.0.

# horizontal\nx1 = .73\nx2 = .73\nx3 = 3.0\nx4 = 1.35\nx5 = .5\nx6 = .35\nx7 = 1.5\nx8 = .35\nx9 = .5\n# Vertical\ny1 = 5.31\ny2 = 1.58\ny3 = .68\ny4 = .68\n

We need to make the width of the display be x1 + x2 + 3*x3 + x4 + x5 + x6 + x7 which is

# Scale Test\n\ndef drawTime12h(hour, minute, x, y, width, height):\n    # horizontal\n    x1 = .73\n    x2 = .73\n    x3 = 3.0 # digit width\n    x4 = 1.35\n    x5 = .5\n    x6 = .35\n    x7 = 1.5\n    x8 = .35\n    x9 = .5 # colon width and height\n\n    # vertical\n    y1 = 5.31\n    y2 = 1.58 # to top colon\n    y3 = .68 # between colons\n\n    total_width = x1 + x2 + 3*x3 + x4 + x5 + x6 + x7\n    print(\"total width:\", total_width)\n    total_height = y1\n\n    # calculate the scaling ratios\n    x_scale = width / total_width\n    y_scale = height / total_height \n\n    digit_width = x3 * x_scale\n    digit_height = y1 * y_scale\n    print(\"x scale:\", x_scale, \"y scale:\", y_scale)\n\n    if hour > 12:\n        hour12 = hour - 12\n\n    # hour tens\n    if hour12 > 10:\n        oled.rect(x,y,int(x1*x_scale),int(y1*y_scale))\n\n    # hour ones x,y,w,h\n    drawDigit(hour % 10, int((x + x1 + x2)*x_scale), y, int(x3*x_scale), int(y1*y_scale))\n\n    # minute tens ones digit, x,y,w,h\n    min_tens_x = int((x + x1 + x2 + x3 + x4)*x_scale)\n    drawDigit(minute // 10, min_tens_x, y, int(x3*x_scale), int(y1*y_scale))\n\n    # minute  ones x,y,w,h\n    min_ones_x = int((x + x1 + x2 + 2*x3 + x4 + x5)*x_scale)\n    drawDigit(minute % 10, min_ones_x, y, int(x3*x_scale), int(y1*y_scale))\n\n    # draw colon\n    colon_size = x9\n    # top colon\n    oled.rect(int((x+x1+x2+x3+x8)*x_scale), y+int(y2*y_scale), colon_size, colon_size)\n    # bottom colon\n    oled.rect(int((x+x1+x2+x3+x8)*x_scale), y+int((y2+y3)*y_scale), colon_size, colon_size)\n\n    # AM/PM\n    if hours < 12:\n        am_pm_text = 'am'\n    else:\n        am_pm_text = 'pm'\n    am_pm_x = min_ones_x + int((x3+x6)*x_scale)\n    oled.text(am_pm_text, am_pm_x, y + int(y1*y_scale)) \n\ndrawTime12h(hour, minute, 0, 0, 100, 50)\n
"},{"location":"lessons/15-scale-testing/","title":"Digital Clock Display Scale Testing","text":"

We would like to test our digital clock display at a large range of different sizes to make sure our scale functions work. For example, the function should work over a range of sizes from 20 to 110 pixels wide.

Here is a test driver for the drawTime12 function:

# test times\ntt = [[00,00], [12,59], [12,00], [8,33], [13,59], [23,59]]\n\nwhile True:\n    ## tti is the test time index\n    for tti in range(0,len(tt)):\n        for size in range(20, 110):\n            oled.fill(0) # clear screen\n            # bounding box for the entire screen\n            # oled.rect(0,0, WIDTH-1, HEIGHT-1, 1)\n\n            # bounding box for the time region\n            height = int(size*.5)\n            oled.rect(0, 0, size+20, height+5, 1)\n            # print(\"h=\", tt[tti][0], \"min:\", tt[tti][1])\n            drawTime12h(tt[tti][0], tt[tti][1], 2, 2, size, height, 1)\n            oled.text(str(tt[tti][0]) + ':' + str(tt[tti][1]), 0, 54, 1)\n            oled.show()\n            sleep(.1)\n
"},{"location":"lessons/15-scale-testing/#sample-code","title":"Sample Code","text":"
# Lab 42: Scale Test test v2\n\nfrom machine import Pin\nfrom time import sleep\nimport ssd1306\nimport config\n\nSCL=Pin(config.SCL_PIN) # SPI CLock\nSDA=Pin(config.SDA_PIN) # SPI Data\n\nRES = Pin(config.RESET_PIN) # Reset\nDC = Pin(config.DC_PIN) # Data/command\nCS = Pin(config.CS_PIN) # Chip Select\nWIDTH = config.WIDTH\nHEIGHT = config.HEIGHT\n\nspi=machine.SPI(config.SPI_BUS, sck=SCL, mosi=SDA, baudrate=100000)\noled = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)\n\nsegmentMapping = [\n  #a, b, c, d, e, f, g\n  [1, 1, 1, 1, 1, 1, 0], # 0\n  [0, 1, 1, 0, 0, 0, 0], # 1\n  [1, 1, 0, 1, 1, 0, 1], # 2\n  [1, 1, 1, 1, 0, 0, 1], # 3\n  [0, 1, 1, 0, 0, 1, 1], # 4\n  [1, 0, 1, 1, 0, 1, 1], # 5\n  [1, 0, 1, 1, 1, 1, 1], # 6\n  [1, 1, 1, 0, 0, 0, 0], # 7\n  [1, 1, 1, 1, 1, 1, 1], # 8\n  [1, 1, 1, 1, 0, 1, 1]  # 9\n];\n# digit is the numer to display\n# x and y are upper-left-corner\n# width and height are the dimensions of the digit\n# thickness is the width of the line segments\n# color is 1 for white and 0 for black\ndef drawDigit(digit, x, y, width, height, thickness, color):\n  # get a list of the segments that are on for this digit\n  if digit > 9 or digit < 0:\n      print('Error: digit out of range:', digit)\n      return -1\n  segmentOn = segmentMapping[digit];\n\n  # Draw the horizontal segments: top, bottom, middle\n  for i in [0, 3, 6]:\n    if (segmentOn[i]):\n      if (i==0): # top\n          yOffset = 0 \n      if (i==3):\n          yOffset = height - thickness # bottom element\n      if (i==6):\n          yOffset = height // 2 - thickness // 2# bottom\n      # oled.line(x - size, y+yOffset-size, x + size, y+yOffset-size, 1);\n      oled.fill_rect(x, y+yOffset, width, thickness, color)\n\n  # Draw the vertical segments ur, lr, ll, ul\n  for i in [1, 2, 4, 5]:\n    if (segmentOn[i]) :\n      # upper vertical lines\n      if (i==1 or i==5):\n          startY = y\n          endY = y + height // 2\n      # lower two vertical lines (2=lower right and 4=lower left)\n      if (i==2 or i==4):\n          startY = y + (height // 2)\n          endY = y + height\n      if (i==4 or i==5): xOffset = 0\n      if (i==1 or i==2): xOffset = width-thickness\n\n      oled.fill_rect(x+xOffset, startY, thickness, endY-startY, color)\n\ndef drawTime12h(hour, minute, x, y, width, height, color):\n\n    # this does not scale\n    am_pm_font_height = 8\n\n    # horizontal spacing\n    x1 = .73 # width of the hour tens digit (1 or off)\n    x2 = .25 # space between hour tens and hour ones\n    x3 = 3.0 # digit width\n    x4 = 1.35 # space beteen hour ones and minute tens\n    x5 = .5 # space between minute tens and minute ones\n    x6 = .35 # space between minute ones and am/pm text\n    x7 = 1.5 # an/pm text width\n\n    # colon x positioning\n    x8 = .35 # space between hour ones and colon\n    x9 = .5 # colon width and height\n\n    # vertical\n    y1 = 5.31 # digit height\n    y2 = (y1 // 2) - .1 # to top colon\n    y3 = 1.5 # space between colons\n\n    total_width = x1 + x2 + 3*x3 + x4 + x5 + x6 + x7\n    # print(\"total width:\", total_width)\n    total_height = y1\n\n    # calculate the scaling ratios\n    x_scale = width / total_width\n    y_scale = height / total_height \n\n    digit_width = x3 * x_scale\n    digit_height = y1 * y_scale\n    # print(\"x scale:\", x_scale, \"y scale:\", y_scale)\n\n\n    time_width = total_width * x_scale\n    # print(\"time_width:\", time_width)\n\n    # thickness calculation based on a fraction of the width\n    thickness = int(.25 * digit_width)\n\n    if hour > 12:\n        hour12 = hour - 12\n    else: hour12 = hour\n\n    hour_minute = hour % 10\n    if hour_minute == 0:\n        hour_minute = 2\n\n    # hour tens display \n    if hour12 == 0 or hour12 > 9:\n        oled.fill_rect(x,y,int(x1*x_scale),int(y1*y_scale), color)\n\n    # hour ones for the d, x,y,w,h, t,c\n    if hour12 == 0:\n        hour12 = 12\n    hour_ones_x = int((x + x1 + x2)*x_scale)\n    drawDigit(hour12 % 10, hour_ones_x, y, int(x3*x_scale), int(y1*y_scale), thickness, color)\n\n    # minute tens ones digit, x,y,w,h\n    min_tens_x = int((x + x1 + x2 + x3 + x4)*x_scale)\n    drawDigit(minute // 10, min_tens_x, y, int(x3*x_scale), int(y1*y_scale), thickness, color)\n\n    # minute ones digit d, x,y,w,h, t, c\n    min_ones_x = int((x + x1 + x2 + 2*x3 + x4 + x5)*x_scale)\n    drawDigit(minute % 10, min_ones_x, y, int(x3*x_scale), int(y1*y_scale), thickness, color)\n\n    # draw colon\n    colon_size = int(x9*x_scale)\n    # top colon\n    oled.fill_rect(int((x+x1+x2+x3+x8)*x_scale), y+int(y2*y_scale), colon_size, colon_size, color)\n    # bottom colon\n    oled.fill_rect(int((x+x1+x2+x3+x8)*x_scale), y+int((y2+y3)*y_scale), colon_size, colon_size, color)\n\n    # AM/PM\n    if hour < 12:\n        am_pm_text = 'am'\n    else:\n        am_pm_text = 'pm'\n    # but here.  It displays outside the width\n    am_pm_x = min_ones_x + int((x3+x6)*x_scale)\n    # print('am/pm x:', am_pm_x)\n    oled.text(am_pm_text, am_pm_x, y + int(y1*y_scale) - am_pm_font_height, color)\n\n    oled.show()\n\n# test times\ntt = [[00,00], [12,59], [12,00], [8,33], [13,59], [23,59]]\n\nwhile True:\n    ## tti is the test time index\n    for tti in range(0,len(tt)):\n        for size in range(20, 110):\n            oled.fill(0) # clear screen\n            # bounding box for the entire screen\n            # oled.rect(0,0, WIDTH-1, HEIGHT-1, 1)\n\n            # bounding box for the time region\n            height = int(size*.5)\n            oled.rect(0, 0, size+20, height+5, 1)\n            # print(\"h=\", tt[tti][0], \"min:\", tt[tti][1])\n            drawTime12h(tt[tti][0], tt[tti][1], 2, 2, size, height, 1)\n            oled.text(str(tt[tti][0]) + ':' + str(tt[tti][1]), 0, 54, 1)\n            oled.show()\n            sleep(.1)\n
"},{"location":"lessons/30-ili9341/","title":"ILI9341 Drawing","text":"

The ILI9341 chip is a 262144-color single-chip SOC driver for a TFT liquid crystal display with a resolution of 240x320 dots (RGB).

Source Code

"},{"location":"lessons/30-ili9341/#configuration-file","title":"Configuration file","text":"

These examples will all use the following config file:

config.py

SCK_PIN = 2\nMISO_PIN = 3 # labeled SDI(MOSI) on the back of the display\nDC_PIN = 4\nRESET_PIN = 5\nCS_PIN = 6\nROTATION = 90\n\nWIDTH=320\nHEIGHT=240\n

We also put the color names in a colors.py file that calculates the colors using a name.

from ili9341 import color565\n\nWHITE = color565(255,255,255)\nBLACK = color565(0,0,0)\nRED = color565(255,0,0)\nORANGE = color565(255,128,0)\nYELLOW = color565(255,255,0)\nGREEN = color565(0,255,0)\nBLUE = color565(0,0,255)\nCYAN = color565(0,255,255)\nPURPLE = color565(255,0,255)\n
"},{"location":"lessons/30-ili9341/#simple-drawing","title":"Simple Drawing","text":""},{"location":"lessons/40-machine-info/","title":"Machine Info","text":""},{"location":"lessons/40-machine-info/#modules","title":"Modules","text":"
help('modules')\n
"},{"location":"lessons/40-machine-info/#clock-frequency","title":"Clock Frequency","text":"
import machine\nprint('freq:', machine.freq())\n
"},{"location":"lessons/40-machine-info/#operating-system","title":"Operating System","text":"
os.uname()\n
os.statvfs(path)\n
"},{"location":"lessons/40-machine-info/#platform","title":"Platform","text":"
platform.platform()\n
"},{"location":"prompts/01-course-description/","title":"Generating a Course Description for the Clocks and Watches with MicroPython Course","text":"

Prompt

Please generate a course description for a 14-week course for high-school students called \"Building Clocks and Watches with MicroPython\".

The course is a hands on-course that uses the Raspberry Pi Pico W and low-cost displays to create a fun collection of projects for kids learning how to program.

We start with a simple clock that uses a LED clock display and then progress to using a web service and an 128x64 OLED display. We also have demonstrations of building stopwatches and timers by integrating buttons and knobs to adjust settings.

An underlying theme is to promote computational thinking including abstraction, decomposition, algorithms and pattern matching.

The course provides students with a breadboard, Raspberry Pi Pico Ws, LEDs and OLED graphical displays.

Use the 2001 Bloom Taxonomy to guild the course description.

"},{"location":"prompts/01-course-description/#response","title":"Response","text":"

Course Title: Building Clocks and Watches with MicroPython Duration: 14 Weeks Appropriate for: High School Students

Course Description: In this 14-week, hands-on course, high school students will learn to design and program functional timekeeping devices---from simple LED clocks to sophisticated stopwatches and web-connected displays---using MicroPython on the Raspberry Pi Pico W. Throughout the course, students will be guided by the 2001 revision of Bloom's Taxonomy, moving from foundational knowledge to creative invention.

  1. Remember: Students begin by identifying essential electronic components---breadboards, microcontrollers, buttons, knobs, and displays---and recalling the fundamental concepts of MicroPython programming.
  2. Understand: Through teacher-led demonstrations, students explore how timing functions, input/output operations, and hardware connections work together to create a basic LED clock. They deepen their grasp of computational thinking by examining concepts like abstraction (focusing on relevant details) and decomposition (breaking large problems into smaller parts).
  3. Apply: As their skills grow, students practice wiring and programming a 128\u00d764 OLED display, interfacing with web services to fetch real-time data. They also implement stopwatches and timers, applying algorithms and pattern-matching techniques to manage user inputs and display outputs accurately.
  4. Analyze: Learners diagnose and troubleshoot various clock behaviors, studying how code efficiency and hardware choices influence device performance. They dissect timing errors, lag issues, and integration problems, refining both their programming and problem-solving skills.
  5. Evaluate: Working in teams, students test multiple clock designs to compare accuracy, power consumption, and user experience. They assess trade-offs---such as display readability vs. complexity---and refine solutions based on feedback, cost, and practicality.
  6. Create: Ultimately, students design and assemble their own custom timekeeping projects, combining LEDs, OLED displays, push buttons, and encoders. This final challenge showcases their ability to unify hardware and code into a polished, functioning prototype.

By the end of the course, students will have built a diverse collection of digital clocks, stopwatches, and timers while gaining a rich understanding of electronics, computational thinking, and MicroPython. They will leave empowered to continue exploring the world of embedded systems and creative hardware projects.

"},{"location":"prompts/02-concept-enumeration/","title":"Concept Enumeration","text":"
  1. MicroPython
  2. Raspberry Pi Pico W
  3. Breadboard
  4. Powering Your Clocks and Watches
  5. USB Power
  6. Battery Power
  7. Battery Backup
  8. Battery Drain Calculation
  9. LED Clock Displays
  10. Real-time Clock
  11. The Thoney IDE
  12. Synchronizing time from the PC
  13. the localtime function
  14. MicroPython Syntax
  15. Loops
  16. Conditionals
  17. Functions
  18. Libraries
  19. Loading MicroPython Drivers
  20. Printing time to the console
  21. MicroPython Drawing Libraries
  22. Drawing Lines
  23. Drawing Text
  24. Drawing Pixels
  25. Drawing Rectangles
  26. Seven Segment Digits
  27. Drawing Circles
  28. Drawing Ellipses
  29. Drawing Arcs
  30. Drawing Polygons
  31. Changing Fonts
  32. Using MicroPython Drivers
  33. OLED Displays
  34. The Draw_Digit Function
  35. SmartWatch Displays
  36. Using 16 bits to represent color
  37. Framebuffer
  38. SPI interfaces
  39. Updating the entire screen with SPI
  40. Incremental screen drawing
  41. Drawing hands on an analog clock
  42. Using the sine and cosine functions
  43. Drawing tic marks on a watch face
  44. Drawing numbers on a watch face
  45. Loading Custom fonts
"},{"location":"prompts/02-concept-enumeration/#response","title":"Response","text":"

MicroPython Raspberry Pi Pico W Breadboard Powering Your Clocks and Watches USB Power Battery Power Battery Backup Battery Drain Calculation LED Clock Displays Real-time Clock (RTC) Overview The Thonny IDE Synchronizing Time from the PC The localtime() Function MicroPython Syntax Loops Conditionals Functions Libraries Loading MicroPython Drivers Printing Time to the Console MicroPython Drawing Libraries Drawing Lines Drawing Text Drawing Pixels Drawing Rectangles Drawing Circles Drawing Ellipses Drawing Arcs Drawing Polygons Changing Fonts Using MicroPython Drivers OLED Displays Smartwatch Displays Using 16 Bits to Represent Color Framebuffer in MicroPython SPI Interfaces Updating the Entire Screen via SPI Incremental Screen Drawing Drawing Hands on an Analog Clock Using math.sin() and math.cos() Drawing Tick Marks on a Watch Face Drawing Numbers on a Watch Face Loading Custom Fonts Using the time Module for Scheduling Using Internal Timers on the Raspberry Pi Pico External RTC Modules (e.g., DS1307, DS3231) Setting the System Clock from an External RTC Network Time Synchronization (NTP) Setting Up Wi\u2011Fi on the Pico W Working with MicroPython\u2019s network Module Parsing JSON (or Other) Time Data from APIs Displaying Date and Day of Week Creating a 7-Segment Style Digital Display Custom Segment-Based Displays Minimizing Power Consumption (Sleep Modes) Waking from Deep Sleep on RTC Alarm Setting Time with Buttons Using Interrupts for Button Input Button Debouncing Techniques Using A Rotary Encoder Setting Alarms with the RTC Connecting Speakers Playing a Sound Sound Encoding Timer-Based Events Playing Chimes Cuckoo Clocks Generating Beep or Alarm Tones Integrating External Sensors Temperature and Humidity Displaying Sensor Data Accelerometers for Orientation Tilt-Based Wake for Watch Display Ultra-Low Power Using E-Paper Displays Partial Refresh on E-Paper Techniques to Minimize Ghosting on E-Paper Using TFT Displays (e.g., ST7735, ILI9341) Comparing Display Technologies Color Animations on Watch Faces Flicker Reduction Techniques Double Buffering for Smooth Animations Using the micropython-ufont Library Asynchronous Code with uasyncio Time-Based Tasks with uasyncio Handling Concurrency of Time Updates & UI Using a Watchdog Timer for Reliability Logging & Debugging Time Issues Sleep Scheduling Tools for Measuring Battery Usage Hardware Documentation 3D Printing Watch Enclosures Using math Module for Geometry Time Zones & UTC Offsets Daylight Saving Time Using the Built-In Filesystem on Pico Logging Time Data to an SD Card Using I\u00b2C Sensors & Modules Menu Systems for Watch Settings Servo Motors for Mechanical Pointers Converting Between datetime Objects and Timestamps Generating Waveforms for Audio Implementing Clock Themes Fast Redraw Techniques (Blitting) Partial Screen Updates for Watch Faces Color Conversion & Palettes in MicroPython Handling 12-Hour vs. 24-Hour Formats Wi\u2011Fi\u2013Based Weather & Forecast Updates Using Sensors for Step Counting (Pedometer) Adding a LiPo Charging Circuit Chronograph Functionality Timer Stopwatch World Clocks

"},{"location":"prompts/03-concept-dependency/","title":"Concept Dependency","text":"

``` prompt I am creating a course on \"Building Clock and Watches with MicroPython\".

The goal of this course is to provide high-school students a fun way to learn computational thinking.\n\nI have listed below about 150 concepts taught in this course in a CSV file.  The first column is the ConceptID and the second column is the ConceptLabel.\n\nPlease create a fully connected concept dependency graph\nfor the concepts in this course.\n\nIn this step, for each Concept on this list below, create a new\nlist of other concepts on which it depends.  If a concept\ndepends on a specific concept that is not listed,\nthen we will need to add it as a new concept.\nThis will help us determine the teaching order of the Concepts.\n\nReturn the new list as a CSV file using the following format:\n\n1. The first column in the CSV file is a numeric concept\nID you assigned in the list above.\n2. The second column is the concept name.\n3. The third column is a pipe-delimited list of the Concept IDs that the concept depends upon.\n\nCheck your results to make sure that all concepts are connected to at least one other concept.\n\nFoundation Concepts are concepts that have no dependant concepts in this set.  \nFoundation Concepts have an empty third column.\n
"},{"location":"prompts/04-concept-taxonomy/","title":"Concept Taxonomy","text":"

Prompt

Given the concepts below, our next step is to create a 10-element taxonomy of these concepts for the book \"Building Clocks and Watches with MicroPython\".

Your task is to organize these concepts into a taxonomy by grouping similar Concepts into one of ten categories. Each category should include concepts that share similar characteristics, functions, or applications.

Please suggest 10 categories for the taxonomy. Attempt to create categories that evenly distribute the concepts so that each category has a similar number of concepts in it.

Ensure that the categories cover a range of concept types, including abstract ideas, practical applications, common misunderstandings, and real-world examples. Avoid redundancy and ensure that each category is distinct.

You can use \"Foundational Concepts\" as your first category. You can use \"Terms and Definitions\" as a second category. You can use \"Example Projects\" as a 10th category.

"},{"location":"prompts/04-concept-taxonomy/#chatgpt-o1-response","title":"ChatGPT o1 Response","text":"

Thought about taxonomy for course concepts for 2m 17s

Below is one possible 10\u2011category taxonomy that groups these 115 concepts into roughly even clusters. Each category highlights a distinct thematic area, from foundational ideas through advanced techniques and ending with example \"capstone\u2011style\" projects. Feel free to adjust or rename categories as needed for your curriculum goals.

"},{"location":"prompts/04-concept-taxonomy/#category-1-foundational-concepts","title":"Category 1: Foundational Concepts","text":"

These are the most basic building blocks and hardware essentials on which everything else depends.

  1. (0) Timekeeping Components
  2. (1) MicroPython
  3. (2) Raspberry Pi Pico W
  4. (3) Breadboard
  5. (4) Power
  6. (5) USB Power
  7. (6) Battery Power
  8. (7) Battery Backup
  9. (8) Battery Drain Calculation
  10. (10) Real-time Clocks
  11. (11) The Thonny IDE
  12. (91) Hardware Documentation
"},{"location":"prompts/04-concept-taxonomy/#category-2-terms-definitions","title":"Category 2: Terms & Definitions","text":"

Core programming syntax and \"must-know\" terminology used throughout the course.

  1. (13) The localtime() Function
  2. (14) MicroPython Syntax
  3. (15) Loops
  4. (16) Conditionals
  5. (17) Functions
  6. (18) Libraries
  7. (20) Printing Time to the Console
  8. (34) Using 16 Bits to Represent Color
  9. (93) Using math Module for Geometry
  10. (94) Time Zones & UTC Offsets
  11. (95) Daylight Saving Time
  12. (101) Converting Between datetime Objects and Timestamps
"},{"location":"prompts/04-concept-taxonomy/#category-3-displays","title":"Category 3: Displays","text":"

An overview of the different display types you might use in clocks/watches.

  1. (9) LED Clock Displays
  2. (21) Displays
  3. (32) OLED Displays
  4. (33) Smartwatch Displays
  5. (53) Creating a 7-Segment Style Digital Display
  6. (54) Custom Segment-Based Displays
  7. (75) Using E-Paper Displays
  8. (76) Partial Refresh on E-Paper
  9. (77) Techniques to Minimize Ghosting on E-Paper
  10. (78) Using TFT Displays (e.g., ST7735, ILI9341)
  11. (79) Comparing Display Technologies
  12. (103) Implementing Clock Themes
"},{"location":"prompts/04-concept-taxonomy/#category-4-basic-drawing-font-tools","title":"Category 4: Basic Drawing & Font Tools","text":"

Fundamental graphics APIs and driver-loading concepts.

  1. (19) Loading MicroPython Drivers
  2. (22) Drawing Lines
  3. (23) Drawing Text
  4. (24) Drawing Pixels
  5. (25) Drawing Rectangles
  6. (26) Drawing Circles
  7. (27) Drawing Ellipses
  8. (28) Drawing Arcs
  9. (29) Drawing Polygons
  10. (30) Changing Fonts
  11. (31) Using MicroPython Drivers
  12. (43) Loading Custom Fonts
"},{"location":"prompts/04-concept-taxonomy/#category-5-advanced-graphics-animations","title":"Category 5: Advanced Graphics & Animations","text":"

More complex graphics techniques and math-based rendering.

  1. (35) Framebuffer in MicroPython
  2. (39) Drawing Hands on an Analog Clock
  3. (40) Using math.sin() and math.cos()
  4. (41) Drawing Tick Marks on a Watch Face
  5. (42) Drawing Numbers on a Watch Face
  6. (80) Color Animations on Watch Faces
  7. (81) Flicker Reduction Techniques
  8. (82) Double Buffering for Smooth Animations
  9. (104) Fast Redraw Techniques (Blitting)
  10. (105) Partial Screen Updates for Watch Faces
  11. (106) Color Conversion & Palettes in MicroPython
  12. (83) Using the micropython-ufont Library
"},{"location":"prompts/04-concept-taxonomy/#category-6-network-asynchronous-concurrency","title":"Category 6: Network, Asynchronous, & Concurrency","text":"

All about getting time from the internet, using Wi-Fi, and handling multiple tasks.

  1. (12) Synchronizing Time from the PC
  2. (45) Using Internal Timers on the Raspberry Pi Pico
  3. (48) Network Time Synchronization (NTP)
  4. (49) Setting Up WiFi on the Pico W
  5. (50) Working the network module
  6. (51) Parsing JSON
  7. (65) Timer-Based Events
  8. (84) Asynchronous Code with uasyncio
  9. (85) Time-Based Tasks with uasyncio
  10. (86) Handling Concurrency of Time Updates & UI
  11. (108) WiFi-Based Weather & Forecast Updates
  12. (114) World Clocks
"},{"location":"prompts/04-concept-taxonomy/#category-7-sensors-modules","title":"Category 7: Sensors & Modules","text":"

External hardware modules, from RTC chips to accelerometers and I\u00b2C sensors.

  1. (46) External RTC Modules (DS1307, DS3231)
  2. (47) Setting the System Clock from an External RTC
  3. (69) Integrating External Sensors
  4. (70) Temperature and Humidity
  5. (71) Displaying Sensor Data
  6. (72) Accelerometers for Orientation
  7. (73) Tilt-Based Wake for Watch Display
  8. (98) Using I\u00b2C Sensors & Modules
  9. (109) Using Sensors for Step Counting (Pedometer)
  10. (110) Adding a LiPo Charging Circuit
  11. (90) Tools for Measuring Battery Usage
  12. (100) Servo Motors for Mechanical Pointers
"},{"location":"prompts/04-concept-taxonomy/#category-8-user-input-time-functions","title":"Category 8: User Input & Time Functions","text":"

Handling buttons, debouncing, rotary encoders, and time-related features.

  1. (52) Displaying Date and Day of Week
  2. (57) Setting Time with Buttons
  3. (58) Using Interrupts for Button Input
  4. (59) Button Debouncing Techniques
  5. (60) Using A Rotary Encoder
  6. (61) Setting Alarms with the RTC
  7. (96) Using the Built-In Filesystem on Pico
  8. (97) Logging Time Data to an SD Card
  9. (74) Ultra-Low Power
  10. (99) Menu Systems for Watch Settings
  11. (107) Handling 12-Hour vs. 24-Hour Formats
  12. (111) Chronograph Functionality
"},{"location":"prompts/04-concept-taxonomy/#category-9-audio-power-management","title":"Category 9: Audio & Power Management","text":"

Generating sounds, mechanical movements, and minimizing power consumption.

  1. (62) Connecting Speakers
  2. (63) Playing a Sound
  3. (64) Sound Encoding
  4. (66) Playing Chimes
  5. (67) Cuckoo Clocks
  6. (68) Generating Beep or Alarm Tones
  7. (102) Generating Waveforms for Audio
  8. (55) Minimizing Power Consumption (Sleep Modes)
  9. (56) Waking from Deep Sleep on RTC Alarm
  10. (89) Sleep Scheduling
  11. (87) Using a Watchdog Timer for Reliability
  12. (88) Logging & Debugging Time Issues
"},{"location":"prompts/04-concept-taxonomy/#category-10-example-projects","title":"Category 10: Example Projects","text":"

A set of \"capstone\" or real-world style projects/enhancements.

  1. (92) 3D Printing Watch Enclosures
  2. (100) [If not placed elsewhere --- but here we used 100 in Category 7, so you can swap if you prefer]
  3. (103) [If not used in Displays]
  4. (112) Timer Project
  5. (113) Stopwatch Project
  6. (109) [If not in Sensors]
  7. (99) [If not in User Input]
  8. ( ... )

Depending on how you ultimately shuffle things around, you can populate this \"Example Projects\" category with any concepts that serve well as culminating projects---for example, \"Cuckoo Clocks,\" \"Playing Chimes,\" \"Implementing Clock Themes,\" \"Using Sensors for Step Counting,\" or \"Servo Motors for Mechanical Pointers.\"

"},{"location":"prompts/04-concept-taxonomy/#final-notes","title":"Final Notes","text":"
  1. Balancing the Categories

    • Each category has ~11--12 items (with minor adjustments possible).
    • Some concepts (e.g., \"Servo Motors,\" \"Implementing Clock Themes,\" or \"Cuckoo Clocks\") could fit either under a main topic (like \"Sensors & Modules\" or \"Audio & Power\") or under \"Example Projects,\" depending on whether you treat them as discrete lessons or culminating builds.
    • Avoiding Duplication

In a real curriculum, certain topics (e.g., \"External RTC Modules\" vs. \"Setting Alarms with the RTC\") might overlap across multiple categories. To keep it simple, each concept is listed once here.

  1. Adapt as Needed

This taxonomy is a starting point. You may decide to rename categories (e.g., split \"Audio & Power Management\" into two) or move individual concepts around to best match your teaching flow.

By defining these ten thematic groupings, you'll have a clear roadmap for both the sequence of lessons and the range of coverage in \"Building Clocks and Watches with MicroPython.\"

"},{"location":"prompts/05-taxonomy-assignment/","title":"Taxonomy Assignment","text":"

Prompt

That is very good. Now please return a new CSV file that appends the TaxonomyID as the fourth column. The new CSV file you create now has the following format:

1. The first column in the CSV file is a numeric ConceptID\nID you assigned in the CSV file above.\n2. The second column is the ConceptLabel from the CSV file above.\n3. The third column is a pipe-delimited Dependencies of the Concept IDs that the concept depends upon.\n4. The fourth column is the numeric TaxonomyID from the taxonomy you generated above.\n
"},{"location":"setup/02-purchsing-parts/","title":"Parts Purchasing Guide","text":""},{"location":"setup/02-purchsing-parts/#microcontrollers","title":"MicroControllers","text":""},{"location":"setup/02-purchsing-parts/#raspberry-pi-pico","title":"Raspberry Pi Pico","text":""},{"location":"setup/02-purchsing-parts/#displays","title":"Displays","text":""},{"location":"setup/02-purchsing-parts/#oled-displays","title":"OLED Displays","text":""},{"location":"setup/02-purchsing-parts/#smartwatch-displays","title":"SmartWatch Displays","text":""},{"location":"setup/02-purchsing-parts/#led-strips","title":"LED Strips","text":""},{"location":"setup/02-purchsing-parts/#breadboards","title":"Breadboards","text":""},{"location":"setup/03-approach/","title":"Our Learning Approach","text":"

Our strategy for teaching is to provide a base set of examples of working clocks and watches. We then also provide a set of \"Challenges\" for students to go further.

We also provide a sample set of prompts you can use to feed generative AI systems to create other lesson plans and learning artifacts such as MicroSims to complement these resources.

"},{"location":"sims/","title":"Clocks and Watches Micro Simulations","text":"

Learning Graph

"},{"location":"sims/4-digits/","title":"Example of a Four-Digit Digital Clock Face","text":""},{"location":"sims/analog-face/","title":"Analog Clock Face","text":""},{"location":"sims/learning-graph/","title":"Learning Graph for Clocks and Watches with MicroPython","text":"

View Learning Graph MicroSim

"},{"location":"sims/themes/","title":"Index","text":"

Analog Clock Face Themes

"},{"location":"sims/themes/#background-color","title":"Background Color","text":""},{"location":"sims/themes/#color-selection","title":"Color Selection","text":""},{"location":"sims/themes/#hand-types","title":"Hand Types","text":""},{"location":"sims/themes/#tic-mark-types","title":"Tic Mark Types","text":""},{"location":"sims/themes/#digit-types","title":"Digit Types","text":""},{"location":"sims/themes/#four-digits","title":"Four Digits","text":""},{"location":"sims/themes/#12-digits","title":"12 Digits","text":""}]} \ No newline at end of file diff --git a/setup/01-desktop/index.html b/setup/01-desktop/index.html index a79b518a..3cba3291 100644 --- a/setup/01-desktop/index.html +++ b/setup/01-desktop/index.html @@ -506,6 +506,10 @@ + + + + diff --git a/setup/02-purchsing-parts/index.html b/setup/02-purchsing-parts/index.html index 1c58b9e4..fc574d06 100644 --- a/setup/02-purchsing-parts/index.html +++ b/setup/02-purchsing-parts/index.html @@ -511,6 +511,10 @@ + + + + diff --git a/setup/03-approach/index.html b/setup/03-approach/index.html index e6ebad3b..095e0a5a 100644 --- a/setup/03-approach/index.html +++ b/setup/03-approach/index.html @@ -509,6 +509,10 @@ + + + + diff --git a/setup/index.html b/setup/index.html index 29189c7b..8e403fae 100644 --- a/setup/index.html +++ b/setup/index.html @@ -586,6 +586,10 @@ + + + + diff --git a/sims/4-digits/index.html b/sims/4-digits/index.html index af1ba068..22c8b2d9 100644 --- a/sims/4-digits/index.html +++ b/sims/4-digits/index.html @@ -15,7 +15,7 @@ - + @@ -513,6 +513,10 @@ + + + + @@ -562,6 +566,27 @@ + + +
  • + + + + + Learning Graph for Clocks and Watches with MicroPython + + + + +
  • + + + + + + + + @@ -943,7 +968,7 @@

    Example of a Four-Digit Digi