diff --git a/search/search_index.json b/search/search_index.json index 1d839132..9514fac5 100644 --- a/search/search_index.json +++ b/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Designing and Building Clocks and Watches with MicroPython and AI","text":"

Welcome to the Clocks and Watches website!

This website is a resource for teachers, mentors and students who want to have fun hands-on ways to learn computational thinking using the popular Python language. This course gives junior high and high school students the tools to design and build their own digital clocks and smart watches using low-cost components.

This website contains detailed instructions for finding low-cost parts (many under $20/student) and customizing your custom clock and watch displays using MicroPython.

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

  1. The 2021 release of the powerful $4 Raspberry Pi Pico microcontroller. It had almost 100x the power of the Arduino Uno and sold for 1/8th the price.
  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 frame buffer 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.
  5. The availability of low-cost yet ultra-precise real-time clock chips like the DS3231.

Our goal is to provide fun ways to teach computational thinking to a wide variety of students from 6th to 12th grades. 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

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

Innovation often occurs at the seams of specialties. This book fits at the intersection of timekeeping, art and teaching computational thinking.

This book can serve as a hands-on guide to introducing students to the fun world of building your own customized clocks and watches with low-cost parts and some MicroPython code to make it all work together.

"},{"location":"about/#why-we-created-this-book","title":"Why We Created This Book","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/#the-evolution-of-learning","title":"The Evolution of Learning","text":"

The above image was my attempt in 2014 to use a breadboard and the original ATmega328P microcontroller to build a clock. This required deep knowledge of the ATmega328P microcontroller and programming in raw low-level C code. Just setting up the integrated development environment could take days. The code needed to be complied and the entire program downloaded into the memory just to run a simple test. You were limited to just 2K or RAM and the clock ran at a slow 16MhZ.

Today the Raspberry Pi has enough RAM to run a full Python interpreter and 264K of RAM just for program memory. It can run up to 150MhZ and has multiple cores that can be working on different tasks such as reading sensors and updating displays.

In short, this power translates into fun projects that are easy to write. Combining these powerful microcontrollers with AI assistants means even novice programmers can quickly achieve great things! Building DIY projects has never been so fun!

"},{"location":"about/#great-way-to-teach-prompt-engineering","title":"Great Way to Teach Prompt Engineering","text":"

This course has hundreds of sample prompts you can use with generative AI tools to create custom code or to generate your own intelligent textbook. You are strongly encouraged to load your config.py file into the projects area of these tool or include the config.py file as part of your prompt.

We hope that you use this course as a proactive way to teach prompt engineering to your students.

Make sure you use the search tool in the page header with the word \"prompt\" to help you get the best prompt for your task.

For example:

Prompt

I have a csv file of birthdays for my family and friends. Please add birthday reminders to the clock display.

"},{"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. Our goal is to create fun projects that teach computational thinking.

  1. Remember: Students begin by identifying essential electronic components---breadboards, microcontrollers, buttons, knobs, real-time clocks, 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). They proceed to use these concepts to customize clock and watch faces using graphic displays.
  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. They can also work with AI to design new clocks.
  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 that blend into the AI-powered home.

"},{"location":"course-description/#computational-thinking","title":"Computational Thinking","text":"

A structured problem-solving approach that uses computer science principles to formulate solutions by breaking down complex tasks into logical, repeatable steps that can be understood by both humans and machines Example: Breaking down the task of making a peanut butter sandwich into discrete steps: \"open jar\", \"grasp knife\", \"scoop peanut butter\".

The main concepts of computational thinking are:

"},{"location":"course-description/#decomposition","title":"Decomposition","text":"

The process of breaking a complex problem into smaller, more manageable parts.

Example: Dividing a clock program into separate functions for displaying time, handling buttons, and managing alarms.

"},{"location":"course-description/#pattern-recognition","title":"Pattern Recognition","text":"

The ability to identify similarities, trends, and regularities in data or problems.

Example: Noticing that both analog and digital clocks need similar time calculation functions despite different display methods.

"},{"location":"course-description/#abstraction","title":"Abstraction","text":"

Focusing on essential details while filtering out irrelevant information to create a generalized solution

Example: Creating a display_time() function that works with any type of display by hiding the specific implementation details.

"},{"location":"course-description/#algorithmic-thinking","title":"Algorithmic Thinking","text":"

Creating a set of ordered steps that will solve a problem or achieve a goal.

Example: Developing a sequence of steps to synchronize a clock with an internet time server.

These concepts work together:

  1. First, decompose the problem
  2. Look for patterns in the smaller pieces
  3. Abstract away unnecessary details
  4. Create step-by-step solutions with algorithms
"},{"location":"faq/","title":"Building Clocks and Watches with MicroPython - Frequently Asked Questions","text":""},{"location":"faq/#general-course-information","title":"General Course Information","text":""},{"location":"faq/#what-prior-knowledge-do-i-need-for-this-course","title":"What prior knowledge do I need for this course?","text":"

No specific electronics or programming experience is required. However, basic computer literacy and comfort with mathematics at a high school level will be helpful. The course starts with fundamentals and builds up gradually.

"},{"location":"faq/#how-long-is-each-class-session","title":"How long is each class session?","text":"

While the course runs for 14 weeks, specific class session lengths are determined by your school's schedule. Contact your instructor for the exact timing of your sessions.

"},{"location":"faq/#do-i-need-to-purchase-my-own-equipment","title":"Do I need to purchase my own equipment?","text":"

Check with your instructor about equipment policies. Generally, core components like the Raspberry Pi Pico W and basic electronics will be provided, but you may want to purchase additional components for your final project.

"},{"location":"faq/#what-software-do-i-need-to-install","title":"What software do I need to install?","text":"

You'll need the Thonny IDE (a Python editor designed for MicroPython), which is free and works on Windows, Mac, and Linux. Your instructor will guide you through the installation process during the first week.

"},{"location":"faq/#technical-questions","title":"Technical Questions","text":""},{"location":"faq/#what-is-micropython-and-how-is-it-different-from-regular-python","title":"What is MicroPython and how is it different from regular Python?","text":"

MicroPython is a streamlined version of Python designed to run on microcontrollers. While it maintains Python's core features and syntax, it's optimized for hardware control and has special libraries for working with electronic components.

"},{"location":"faq/#what-is-a-raspberry-pi-pico-w","title":"What is a Raspberry Pi Pico W?","text":"

The Raspberry Pi Pico W is a small, affordable microcontroller board with built-in Wi-Fi. It's perfect for learning because it's user-friendly, well-documented, and has plenty of input/output pins for connecting different components.

"},{"location":"faq/#will-we-be-doing-any-soldering","title":"Will we be doing any soldering?","text":"

Most projects will use breadboards for non-permanent connections. Some optional advanced projects might involve soldering, but this will be optional and supervised.

"},{"location":"faq/#how-accurate-will-our-clocks-be","title":"How accurate will our clocks be?","text":"

The accuracy depends on the components used. With the basic oscillator, clocks might drift a few seconds per day. When we add a Real-Time Clock (RTC) module, accuracy improves significantly to within a few seconds per month.

"},{"location":"faq/#project-related-questions","title":"Project-Related Questions","text":""},{"location":"faq/#what-kind-of-displays-will-we-use","title":"What kind of displays will we use?","text":"

We'll work with several types: - Simple LEDs for basic digital displays - 128\u00d764 OLED displays for more sophisticated interfaces - Other display options for final projects

"},{"location":"faq/#can-i-create-my-own-custom-clock-design-for-the-final-project","title":"Can I create my own custom clock design for the final project?","text":"

Yes! The final project is designed to be creative and personal. You can combine different components and features we've learned about to create something unique.

"},{"location":"faq/#how-will-we-handle-time-zones-and-daylight-saving-time","title":"How will we handle time zones and daylight saving time?","text":"

We'll learn to sync time using web services (when Wi-Fi is available) and program time zone adjustments. Managing daylight saving time will be covered as part of the advanced features.

"},{"location":"faq/#learning-and-assessment","title":"Learning and Assessment","text":""},{"location":"faq/#how-will-i-be-graded","title":"How will I be graded?","text":"

Assessment typically includes:

"},{"location":"faq/#what-happens-if-my-clock-doesnt-work","title":"What happens if my clock doesn't work?","text":"

Troubleshooting is a crucial part of the learning process. You'll have support from instructors and peers, and we'll learn systematic debugging approaches. Making mistakes and fixing them is how we learn!

"},{"location":"faq/#can-i-work-with-ai-tools-during-the-course","title":"Can I work with AI tools during the course?","text":"

Yes, part of the course includes learning how to effectively use AI tools for design and problem-solving. We'll cover best practices for AI collaboration while ensuring you understand the fundamental concepts.

"},{"location":"faq/#hardware-and-components","title":"Hardware and Components","text":""},{"location":"faq/#what-happens-if-i-accidentally-damage-a-component","title":"What happens if I accidentally damage a component?","text":"

Don't worry! Learning electronics involves some trial and error. Basic components are relatively inexpensive and replaceable. Just inform your instructor if something breaks.

"},{"location":"faq/#how-do-breadboards-work","title":"How do breadboards work?","text":"

A breadboard is a reusable platform for creating temporary electrical connections. We'll spend time early in the course understanding how they work and practicing proper connection techniques.

"},{"location":"faq/#whats-the-difference-between-digital-and-analog-inputsoutputs","title":"What's the difference between digital and analog inputs/outputs?","text":"

Digital signals are either on (1) or off (0), while analog signals can have many values in between. We'll use both types: digital for buttons and simple LEDs, analog for sensors and variable controls.

"},{"location":"faq/#programming-concepts","title":"Programming Concepts","text":""},{"location":"faq/#do-i-need-to-memorize-all-the-micropython-commands","title":"Do I need to memorize all the MicroPython commands?","text":"

No, you don't need to memorize everything. We'll provide reference materials, and part of learning is knowing how to find and use documentation effectively.

"},{"location":"faq/#what-if-i-miss-a-class-and-fall-behind","title":"What if I miss a class and fall behind?","text":"

Each concept builds on previous learning, but we'll provide catch-up resources and extra help sessions. Talk to your instructor as soon as possible if you fall behind.

"},{"location":"faq/#how-will-we-debug-our-code","title":"How will we debug our code?","text":"

We'll learn several debugging techniques:

"},{"location":"faq/#safety-and-best-practices","title":"Safety and Best Practices","text":""},{"location":"faq/#is-there-any-risk-of-electric-shock","title":"Is there any risk of electric shock?","text":"

The Pico W and components we use operate at very low voltages (3.3V or 5V) which are safe to handle. However, we'll still learn and follow proper safety procedures.

"},{"location":"faq/#how-do-i-protect-the-electronic-components","title":"How do I protect the electronic components?","text":"

We'll cover basic handling procedures including:

"},{"location":"faq/#advanced-topics-and-extensions","title":"Advanced Topics and Extensions","text":""},{"location":"faq/#can-we-add-features-like-alarms-or-stopwatch-functions","title":"Can we add features like alarms or stopwatch functions?","text":"

Yes! As you progress through the course, you'll learn to add various features including:

"},{"location":"faq/#will-we-learn-about-power-management","title":"Will we learn about power management?","text":"

Yes, we'll cover power management including:

"},{"location":"faq/#how-long-will-the-cr2032-coin-cell-battery-last","title":"How long will the CR2032 coin cell battery last?","text":"

The typical operational duration in real-time clock circuits ranges from 2 to 10 years, depending on temperature conditions and current draw.

The DS3231 RTC module used in our kits draw 3\u03bcA at room temperature typically operates for 8 years on a single CR2032 battery. The key factors affecting battery life are:

"},{"location":"faq/#if-the-clock-is-plugged-into-the-wall-will-the-backup-cr2032-battery-be-drained","title":"If the clock is plugged into the wall will the backup CR2032 battery be drained?","text":"

No. When external power is connected, the DS3231 RTC board automatically switches to the main power supply and isolates the backup battery circuit, preventing battery drain. The CR2032 is only used when main power is disconnected.

This power switching is handled by internal circuitry using diodes that prevent current from flowing to/from the backup battery during normal operation.

"},{"location":"faq/#future-applications","title":"Future Applications","text":""},{"location":"faq/#can-i-use-these-skills-for-other-projects","title":"Can I use these skills for other projects?","text":"

Absolutely! The skills you'll learn apply to many other electronics and programming projects:

"},{"location":"faq/#how-does-this-course-prepare-me-for-further-study","title":"How does this course prepare me for further study?","text":"

This course builds valuable skills for:

"},{"location":"faq/#tools-and-resources","title":"Tools and Resources","text":""},{"location":"faq/#what-online-resources-can-we-use","title":"What online resources can we use?","text":"

We'll use various resources including:

"},{"location":"faq/#will-we-use-version-control-like-git","title":"Will we use version control like Git?","text":"

While not required, we'll introduce basic version control concepts and how they can help manage your code as projects become more complex. Many of the clock kits will have all the sample code preloaded into the Raspberry Pi Pico file system.

"},{"location":"faq/#collaboration-and-community","title":"Collaboration and Community","text":""},{"location":"faq/#can-we-work-in-teams","title":"Can we work in teams?","text":"

Yes! Many activities will involve pair programming or team collaboration. The final project can be individual or team-based, depending on complexity and scope.

"},{"location":"faq/#how-can-we-share-our-projects-with-others","title":"How can we share our projects with others?","text":"

We'll discuss ways to:

"},{"location":"faq/#getting-help","title":"Getting Help","text":""},{"location":"faq/#what-if-i-get-stuck-on-a-problem","title":"What if I get stuck on a problem?","text":"

Multiple support options are available:

Note that you can load sample programs and yur config.py file into the generative AI projects area and get high quality results that use your local configuration.

"},{"location":"faq/#can-i-get-extra-help-outside-of-class","title":"Can I get extra help outside of class?","text":"

Yes, instructors will have office hours and may offer additional lab time for students who want extra practice or help.

"},{"location":"faq/#course-materials","title":"Course Materials","text":""},{"location":"faq/#what-should-i-bring-to-class","title":"What should I bring to class?","text":"

Typically you'll need:

"},{"location":"faq/#how-should-i-store-and-transport-my-projects","title":"How should I store and transport my projects?","text":"

We'll provide guidance on:

"},{"location":"faq/#final-projects","title":"Final Projects","text":""},{"location":"faq/#what-makes-a-good-final-project","title":"What makes a good final project?","text":"

Strong final projects typically:

"},{"location":"faq/#can-i-continue-working-on-my-project-after-the-course-ends","title":"Can I continue working on my project after the course ends?","text":"

Yes! You'll have the knowledge to continue developing and improving your projects. We'll discuss ways to extend your learning and continue exploring electronics and programming.

"},{"location":"glossary/","title":"Glossary","text":""},{"location":"glossary/#12-hour-vs-24-hour-formats","title":"12-Hour vs 24-Hour Formats","text":"

Methods for displaying time in either 12-hour (AM/PM) or 24-hour (military) format, including conversion between the two.

Example: Converting 13:00 to 1:00 PM and handling the AM/PM indicator.

"},{"location":"glossary/#3d-printing-clock-and-watch-enclosures","title":"3D Printing Clock and Watch Enclosures","text":"

A process of creating custom protective cases for watch components using additive manufacturing technology.

We will not cover these topics in this course. Our focus in this website is using clock and watch projects to teach computational thinking.

"},{"location":"glossary/#abstraction","title":"Abstraction","text":"

A technique for managing complexity by hiding unnecessary details while keeping essential features visible.

Example: Using a display_time() function that accepts hours and minutes without needing to know how individual LED segments are controlled.

Abstraction is one of the four key computational thinking topics we teach in this course.

"},{"location":"glossary/#accelerometer","title":"Accelerometer","text":"

A sensor that measures physical acceleration and orientation, useful for tilt-based display activation.

Example: Detecting when a user lifts their wrist to view the watch face.

"},{"location":"glossary/#alarm","title":"Alarm","text":"

A scheduled alert that triggers audio, visual, or vibration notifications at specific times.

Example: Setting multiple daily alarms using the RTC module.

"},{"location":"glossary/#analog-clock-face","title":"Analog Clock Face","text":"

A traditional clock display using rotating hands to show time on a circular dial.

Example: Drawing hour and minute hands using math.sin() and math.cos() for position calculations.

"},{"location":"glossary/#animation","title":"Animation","text":"

The process of creating movement by rapidly updating display contents.

Example: Smoothly rotating second hands or creating blinking colons between hours and minutes.

"},{"location":"glossary/#asynchronous-programming","title":"Asynchronous Programming","text":"

A coding technique that allows multiple tasks to run concurrently without blocking each other.

Example: Using uasyncio to handle display updates while monitoring button presses.

"},{"location":"glossary/#battery-backup","title":"Battery Backup","text":"

A power system that maintains time and settings when main power is disconnected.

"},{"location":"glossary/#battery-drain-calculation","title":"Battery Drain Calculation","text":"

The process of measuring and estimating power consumption to determine battery life.

Example: Measuring current draw during different display update modes.

"},{"location":"glossary/#battery-power","title":"Battery Power","text":"

Portable electrical energy storage used to operate mobile timepieces.

Example: Calculating power consumption to estimate battery life.

"},{"location":"glossary/#battery-tools","title":"Battery Tools","text":"

Instruments and methods for measuring power consumption and battery health.

Example: Using multimeters to measure current draw in different modes.

"},{"location":"glossary/#blitting","title":"Blitting","text":"

A technique for rapidly copying rectangular portions of memory to update displays efficiently.

Example: Using framebuffer operations to reduce screen flicker.

"},{"location":"glossary/#breadboard","title":"Breadboard","text":"

A reusable plastic board with holes and internal connections for prototyping electronic circuits without soldering.

Example: Connecting LED displays and buttons to the Pico W using jumper wires on a breadboard.

"},{"location":"glossary/#button-debouncing","title":"Button Debouncing","text":"

A technique to prevent false button readings caused by mechanical switch contacts bouncing when pressed or released.

Example: debounce_time = 100 in button configuration prevents multiple accidental triggers.

"},{"location":"glossary/#button-input-handling","title":"Button Input Handling","text":"

Software techniques for detecting and responding to button presses.

Example: Using interrupts and debouncing for reliable input detection.

"},{"location":"glossary/#callback-function","title":"Callback Function","text":"

A function passed as an argument to another function, which will be executed when a specific event occurs.

Example: button_mode_irq function that runs whenever the mode button is pressed.

"},{"location":"glossary/#changing-fonts","title":"Changing Fonts","text":"

The process of modifying text appearance by loading and applying different typefaces.

Example: Using the micropython-ufont library to load compact digital number styles.

"},{"location":"glossary/#chimes","title":"Chimes","text":"

Musical or tone sequences played at specific intervals or times.

Example: Playing Westminster quarter-hour chimes using PWM audio output.

"},{"location":"glossary/#chronograph","title":"Chronograph","text":"

A timepiece with both timekeeping and stopwatch capabilities.

"},{"location":"glossary/#color-animations","title":"Color Animations","text":"

Dynamic changes in display colors to create visual effects or indicate status.

Example: Gradually shifting background colors throughout the day.

"},{"location":"glossary/#color-conversion","title":"Color Conversion","text":"

The process of transforming between different color representation systems.

Example: Converting RGB colors to 16-bit format for TFT displays.

"},{"location":"glossary/#color-conversion-palettes","title":"Color Conversion & Palettes","text":"

Methods for transforming between different color representations and managing limited color sets.

Example: Converting RGB colors to display-specific formats and creating custom color themes.

"},{"location":"glossary/#color-depth","title":"Color Depth","text":"

The number of bits used to represent colors in a display.

Example: Using 16 bits per pixel to show 65,536 different colors.

"},{"location":"glossary/#computational-thinking","title":"Computational Thinking","text":"

A structured problem-solving approach that uses computer science principles to formulate solutions by breaking down complex tasks into logical, repeatable steps that can be understood by both humans and machines Example: Breaking down the task of making a peanut butter sandwich into discrete steps: \"open jar\", \"grasp knife\", \"scoop peanut butter\".

The main concepts of computational thinking are:

"},{"location":"glossary/#decomposition","title":"Decomposition","text":"

The process of breaking a complex problem into smaller, more manageable parts Example: Dividing a clock program into separate functions for displaying time, handling buttons, and managing alarms.

"},{"location":"glossary/#pattern-recognition","title":"Pattern Recognition","text":"

The ability to identify similarities, trends, and regularities in data or problems Example: Noticing that both analog and digital clocks need similar time calculation functions despite different display methods.

"},{"location":"glossary/#abstraction_1","title":"Abstraction","text":"

Focusing on essential details while filtering out irrelevant information to create a generalized solution Example: Creating a display_time() function that works with any type of display by hiding the specific implementation details.

"},{"location":"glossary/#algorithmic-thinking","title":"Algorithmic Thinking","text":"

Creating a set of ordered steps that will solve a problem or achieve a goal Example: Developing a sequence of steps to synchronize a clock with an internet time server.

These concepts work together:

  1. First, decompose the problem
  2. Look for patterns in the smaller pieces
  3. Abstract away unnecessary details
  4. Create step-by-step solutions with algorithms
"},{"location":"glossary/#concurrency","title":"Concurrency","text":"

The ability to handle multiple tasks or events simultaneously in a program.

Example: Updating the display while checking for button presses.

"},{"location":"glossary/#conditionals","title":"Conditionals","text":"

Programming statements that perform different actions based on whether a condition is true or false.

Example: Switching between 12-hour and 24-hour time formats.

"},{"location":"glossary/#connecting-speakers","title":"Connecting Speakers","text":"

Methods for attaching and controlling audio output devices to create sound effects and alarms.

Example: Wiring a speaker to a PWM-capable GPIO pin for generating tones for an alarm clock.

"},{"location":"glossary/#console-output","title":"Console Output","text":"

Text-based information displayed in the development environment for debugging.

Example: print(\"Current time:\", hours, \":\", minutes)

"},{"location":"glossary/#cuckoo-clock","title":"Cuckoo Clock","text":"

A timepiece that marks specific hours with sound and optional mechanical movement.

Example: Playing bird sounds and activating a servo at the top of each hour.

"},{"location":"glossary/#custom-font","title":"Custom Font","text":"

A specialized set of character designs loaded into memory for display purposes.

Example: Loading compact number fonts using the micropython-ufont library.

"},{"location":"glossary/#custom-segment-display","title":"Custom Segment Display","text":"

A specialized arrangement of LED segments for showing numbers or characters.

Example: Creating unique digit patterns using individual LED segments.

"},{"location":"glossary/#digital-to-analog-converter-dac","title":"Digital to Analog Converter (DAC)","text":"

An integrated circuit that transforms digital data into a continuous analog voltage. For example we can generate sound using a microcontroller to play an alarm sounds.

In our labs we use the MicroPython I2S (Inter-IC Sound) protocol for generating high-fidelity audio output.

For example we have a lab that generates a sine wave to create audible tones through a speaker.

Specifically our class uses the PCM5102A DAC Module to generate Sound.

"},{"location":"glossary/#date-and-calendar-display","title":"Date and Calendar Display","text":"

Showing current date information including day of week, month, and year.

Example: Formatting and displaying full date alongside time information.

"},{"location":"glossary/#datetime-objects","title":"datetime Objects","text":"

Programming structures that store date and time information together.

Example: Converting between timestamp integers and datetime format.

"},{"location":"glossary/#daylight-saving-time","title":"Daylight Saving Time","text":"

A seasonal time adjustment typically involving a one-hour shift.

Example: Automatically adjusting clock time based on DST rules.

"},{"location":"glossary/#deep-sleep","title":"Deep Sleep","text":"

A low-power mode that disables most system functions to conserve energy.

Example: Entering sleep mode when the watch face isn't being viewed.

"},{"location":"glossary/#digital-display","title":"Digital Display","text":"

An electronic output device that shows numbers, letters, or symbols using discrete segments or pixels.

Example: Four-digit seven-segment LED display showing hours and minutes.

"},{"location":"glossary/#display-driver","title":"Display Driver","text":"

Software that controls how information is shown on a specific type of display.

Example: Using the ST7735 driver for TFT LCD screens.

"},{"location":"glossary/#display-technology-comparison","title":"Display Technology Comparison","text":"

Analysis of different screen types' advantages and limitations.

Example: Evaluating power consumption versus update speed for various displays.

"},{"location":"glossary/#double-buffering","title":"Double Buffering","text":"

A technique using two memory areas to prepare the next frame while displaying the current one.

Example: Creating smooth animations without visible drawing operations.

"},{"location":"glossary/#drawing-arcs","title":"Drawing Arcs","text":"

Creating curved lines as portions of circles on displays.

Example: Drawing round clock face elements and decorative features.

"},{"location":"glossary/#drawing-circles","title":"Drawing Circles","text":"

Creating perfect circular shapes on displays.

Example: Drawing clock face outlines and hour markers.

"},{"location":"glossary/#drawing-ellipses","title":"Drawing Ellipses","text":"

Creating oval shapes on displays.

Example: Designing stylized clock faces and decorative elements.

"},{"location":"glossary/#drawing-hands","title":"Drawing Hands","text":"

Creating moving indicators for hours, minutes, and seconds.

Example: Using line drawing functions to show analog time.

"},{"location":"glossary/#drawing-libraries","title":"Drawing Libraries","text":"

Software collections that provide functions for creating visual elements.

Example: Using built-in graphics functions to draw clock hands and numbers.

"},{"location":"glossary/#drawing-lines","title":"Drawing Lines","text":"

Creating straight line segments on displays.

Example: Drawing clock hand indicators and markings.

"},{"location":"glossary/#drawing-numbers","title":"Drawing Numbers","text":"

Displaying numerical values on screen.

Example: Showing digital time values and markers.

"},{"location":"glossary/#drawing-pixels","title":"Drawing Pixels","text":"

Setting individual display points for custom graphics.

Example: Creating fine details in watch face designs.

"},{"location":"glossary/#drawing-polygons","title":"Drawing Polygons","text":"

Creating shapes with multiple straight sides.

Example: Making custom hour markers and decorative elements.

"},{"location":"glossary/#drawing-primitives","title":"Drawing Primitives","text":"

Basic shapes and elements used to create more complex visual displays.

Example: Using lines and arcs to create clock hands.

"},{"location":"glossary/#drawing-rectangles","title":"Drawing Rectangles","text":"

Creating four-sided shapes with right angles.

Example: Drawing display borders and menu backgrounds.

"},{"location":"glossary/#drawing-text","title":"Drawing Text","text":"

Displaying characters and strings on screen.

Example: Showing time, date, and menu options.

"},{"location":"glossary/#drawing-tick-marks","title":"Drawing Tick Marks","text":"

Creating small indicators around a clock face.

Example: Marking hours and minutes on an analog display.

"},{"location":"glossary/#ds1307","title":"DS1307","text":""},{"location":"glossary/#ds3231","title":"DS3231","text":"

A high-precision real-time clock (RTC) integrated circuit with temperature compensation for accurate timekeeping.

Example: Using the DS3231 to maintain time accuracy within seconds per month.

"},{"location":"glossary/#e-paper-display","title":"E-Paper Display","text":"

A low-power screen technology that maintains its image without constant power.

Example: Using partial updates to change only modified portions of the display.

"},{"location":"glossary/#epoch","title":"Epoch","text":"

A fixed point in time chosen as a reference for measuring or calculating elapsed time in a computer system or data structure.

Example: The Unix epoch begins at midnight UTC on January 1, 1970, while the NTP epoch begins at midnight UTC on January 1, 1900. The MicroPython epoch is often set to be January 1 2000. On the Raspberry Pi Pico the epoch is January 1st, 2021.

"},{"location":"glossary/#error-handling-and-recovery","title":"Error Handling and Recovery","text":"

Techniques for detecting and recovering from timing errors, power issues, or communication failures.

Example: Implementing watchdog timers and automatic resynchronization after power loss.#### Event Handler A function that responds to specific occurrences like button presses or timer updates.

Example: The button_mode_irq function handles mode button press events.

"},{"location":"glossary/#external-rtc","title":"External RTC","text":"

A separate timekeeping chip that maintains accurate time independent of the main processor.

Example: Using a DS3231 module for precise timekeeping.

"},{"location":"glossary/#external-sensors","title":"External Sensors","text":"

Additional hardware components that measure environmental conditions.

Example: Reading temperature and humidity for weather display.

"},{"location":"glossary/#fast-redraw","title":"Fast Redraw","text":"

Techniques for updating displays quickly to maintain smooth animation.

Example: Using hardware acceleration for screen updates.

"},{"location":"glossary/#filesystem","title":"Filesystem","text":"

A system for storing and organizing files on the microcontroller.

Example: Saving configuration settings and logs to internal storage.

"},{"location":"glossary/#flicker-reduction","title":"Flicker Reduction","text":"

Methods to minimize visible display instability during updates.

Example: Using double buffering to prevent partial frame displays.

"},{"location":"glossary/#framebuffer","title":"Framebuffer","text":"

A region of memory that holds the complete contents of a display.

Example: Modifying pixel data before updating the screen.

"},{"location":"glossary/#functions","title":"Functions","text":"

Reusable blocks of code that perform specific tasks.

Example: Creating a function to convert between 12-hour and 24-hour formats.

"},{"location":"glossary/#generating-waveforms-for-audio","title":"Generating Waveforms for Audio","text":"

Creating electrical signals for producing sounds and tones using digital-to-analog conversion or PWM.

Example: Synthesizing different frequencies for alarm sounds and hourly chimes.

"},{"location":"glossary/#ghosting","title":"Ghosting","text":"

A visual artifact where previous images remain partially visible on certain display types.

Example: Using display clearing techniques on e-paper screens.

"},{"location":"glossary/#gpio-pin","title":"GPIO Pin","text":"

General Purpose Input/Output connection on a microcontroller that can be programmed to send or receive electrical signals.

Example: Using GPIO pin 16 for the mode button: mode_pin = Pin(16, Pin.IN, Pin.PULL_UP)

"},{"location":"glossary/#hardware-documentation","title":"Hardware Documentation","text":"

Technical specifications and usage instructions for electronic components.

Example: Consulting pinout diagrams for display connections.

"},{"location":"glossary/#i2c","title":"I2C","text":"

A medium speed 4-wire communication protocol that allows multiple digital components to exchange data using just two wires plus power and ground.

I2C also has many different connectors that allow you to connect I2C components without the need for soldering.

"},{"location":"glossary/#i2c-sensors","title":"I2C Sensors","text":"

Digital components that communicate using the I2C protocol to provide measurements.

Example: Reading temperature and humidity data for weather display.

"},{"location":"glossary/#i2s","title":"I2S","text":"

The Inter-sound protocol we use to transmit sound data to components such as a Digital to Analog Converter.

Inter-IC Sound bus protocol is a synchronous serial protocol used to connect digital audio devices.

I2S is a communication protocol that allows multiple digital components to exchange data using just two wires plus power and ground.

MicroPython 1.21 has builtin support for the I2S bus.

"},{"location":"glossary/#implementing-clock-themes","title":"Implementing Clock Themes","text":"

Creating customizable visual styles for clock displays, including colors, fonts, and layouts.

Example: Allowing users to switch between day/night themes or seasonal variations.

"},{"location":"glossary/#incremental-drawing","title":"Incremental Drawing","text":"

Updating only the necessary portions of a display to improve efficiency.

Example: Refreshing only the seconds indicator rather than the full screen.

"},{"location":"glossary/#interrupt","title":"Interrupt","text":"

A signal that temporarily pauses normal program execution to handle important events.

Example: Responding immediately to button presses.

"},{"location":"glossary/#json","title":"JSON","text":"

A text-based data format commonly used for exchanging information with web services.

Example: Parsing weather data from online APIs.

"},{"location":"glossary/#led-clock-displays","title":"LED Clock Displays","text":"

A digital timepiece display using light-emitting diodes arranged in segments or matrices to show time information.

Example: Using a four-digit seven-segment LED display to show hours and minutes.

"},{"location":"glossary/#led-matrix","title":"LED Matrix","text":"

An array of light-emitting diodes arranged in rows and columns that can display patterns, numbers, or text.

"},{"location":"glossary/#libraries","title":"Libraries","text":"

Collections of pre-written code that provide useful functions and features.

Example: Using the math module for trigonometric calculations.

"},{"location":"glossary/#lipo-charging","title":"LiPo Charging","text":"

The process of safely recharging Lithium Polymer batteries.

Example: Implementing USB charging circuits with protection features.

"},{"location":"glossary/#lipo-charging-circuit","title":"LiPo Charging Circuit","text":"

Electronic system for safely charging Lithium Polymer batteries while protecting against overcharge and overdischarge.

Example: Implementing USB charging with voltage regulation and protection features.

"},{"location":"glossary/#loading-drivers","title":"Loading Drivers","text":"

Installing and initializing software that controls specific hardware components.

Example: Importing and configuring TFT display drivers.

"},{"location":"glossary/#localtime-function","title":"localtime() Function","text":"

A MicroPython function that returns the current time as a tuple of values.

Example: year, month, day, hour, minute, second = localtime()

"},{"location":"glossary/#logging","title":"Logging","text":"

The practice of recording program events and data for debugging or analysis.

Example: Saving timing discrepancies to investigate accuracy issues.

"},{"location":"glossary/#logging-time-data-to-an-sd-card","title":"Logging Time Data to an SD Card","text":"

Recording timestamped information to external storage for debugging or data collection.

Example: Saving temperature readings with timestamps every hour.

"},{"location":"glossary/#loops","title":"Loops","text":"

Programming structures that repeat code blocks multiple times.

Example: Continuously updating the display every second.

"},{"location":"glossary/#math-module","title":"Math Module","text":"

A collection of mathematical functions for complex calculations.

Example: Using trigonometry to position clock hands.

"},{"location":"glossary/#mathsin-and-mathcos","title":"math.sin() and math.cos()","text":"

Trigonometric functions used for calculating positions on circular displays.

Example: Computing analog clock hand coordinates.

"},{"location":"glossary/#menu-system","title":"Menu System","text":"

An interface allowing users to navigate options and settings.

Example: Creating hierarchical settings menus for watch configuration.

"},{"location":"glossary/#menu-systems-for-watch-settings","title":"Menu Systems for Watch Settings","text":"

Hierarchical interface structures for configuring watch parameters and features.

Example: Creating nested menus for time, alarm, and display settings.

"},{"location":"glossary/#micropython","title":"MicroPython","text":"

A streamlined version of Python programming language designed to run on microcontrollers and embedded systems.

"},{"location":"glossary/#micropython-drawing","title":"MicroPython Drawing","text":"

Built-in functions for creating visual elements on displays.

Example: Using framebuf methods to draw shapes and text.

"},{"location":"glossary/#micropython-network","title":"MicroPython network","text":"

A module providing Wi-Fi and network connectivity functions.

Example: Connecting to wireless networks for time synchronization.

"},{"location":"glossary/#micropython-syntax","title":"MicroPython Syntax","text":"

The specific programming language rules and structure for MicroPython code.

Example: Using Python-style indentation for code blocks.

"},{"location":"glossary/#micropython-ufont-library","title":"micropython-ufont Library","text":"

A specialized library for handling compact font rendering in MicroPython applications.

Example: Loading custom digit fonts optimized for small displays.

"},{"location":"glossary/#neopixel","title":"NeoPixel","text":"

A programmable light-emitting diode that combines red, green, and blue elements in a single package with built-in control circuitry.

NeoPixel's strips only require three wires: GND, 5V and data. It makes creating clock display patterns very easy.

Example: Creating a ring of 12 individually addressable RGB LEDs to mark hour positions on a clock face.

"},{"location":"glossary/#network-time-protocol-ntp","title":"Network Time Protocol (NTP)","text":"

A method for synchronizing time over the internet.

Example: Updating the RTC from online time servers.

"},{"location":"glossary/#oled-display","title":"OLED Display","text":"

A thin, bright display that uses organic light-emitting diodes to show text and graphics without a backlight.

"},{"location":"glossary/#partial-refresh","title":"Partial Refresh","text":"

Updating only changed portions of a display to improve efficiency.

Example: Refreshing only the seconds digits each update.

"},{"location":"glossary/#partial-screen-updates","title":"Partial Screen Updates","text":"

Techniques for refreshing only the changed portions of a display to improve efficiency and reduce flicker.

Example: Updating only the seconds digits while leaving hours and minutes static.

"},{"location":"glossary/#pedometer","title":"Pedometer","text":"

A feature that counts steps using motion sensor data.

Example: Calculating daily step counts from accelerometer readings.

"},{"location":"glossary/#pcm5102a-dac","title":"PCM5102A DAC","text":"

An audio Digital to Analog chip we use in our clock projects.

"},{"location":"glossary/#pin","title":"Pin","text":"

A metal connector on electronic components that carries electrical signals or power.

"},{"location":"glossary/#power-management","title":"Power Management","text":"

Techniques for minimizing and controlling energy consumption.

Example: Using sleep modes and efficient display updates.

"},{"location":"glossary/#power-monitoring","title":"Power Monitoring","text":"

Methods for measuring and tracking power consumption in battery-operated devices.

Example: Logging battery voltage and current draw to optimize device lifetime.

"},{"location":"glossary/#pull-up-resistor","title":"Pull-up Resistor","text":"

An electronic component that ensures a consistent voltage level on input pins when buttons or switches are not pressed.

Example: Using internal pull-up resistors with Pin.PULL_UP for button inputs.

"},{"location":"glossary/#pwm","title":"PWM","text":"

Pulse Width Modulation - a technique for creating varying levels of brightness or speed by rapidly switching a signal on and off.

"},{"location":"glossary/#pwm-audio","title":"PWM Audio","text":"

Using Pulse Width Modulation to generate sounds and tones.

Example: Creating alarm beeps and hourly chimes.

"},{"location":"glossary/#raspberry-pi-pico-w","title":"Raspberry Pi Pico W","text":"

A small, low-cost microcontroller board with built-in wireless capabilities, designed for learning and DIY projects.

"},{"location":"glossary/#real-time-clock-rtc","title":"Real-Time Clock (RTC)","text":"

A specialized chip or circuit that keeps accurate time even when main power is removed.

Example: Using the RTC module to maintain accurate time: rtc = RTC()

"},{"location":"glossary/#real-time-clock-rtc-overview","title":"Real-time Clock (RTC) Overview","text":"

A comprehensive explanation of how real-time clock modules maintain accurate time, including initialization, synchronization, and backup power considerations.

Example: Understanding how the RTC maintains time even when main power is disconnected.

"},{"location":"glossary/#rotary-encoder","title":"Rotary Encoder","text":"

A input device that converts rotational movement into digital signals.

Example: Using encoder rotation to adjust time settings.

"},{"location":"glossary/#screen-tearing-prevention","title":"Screen Tearing Prevention","text":"

Techniques to avoid visual artifacts caused by updating display content while it's being refreshed.

Example: Using vsync or double buffering to ensure clean display updates.

"},{"location":"glossary/#screen-update","title":"Screen Update","text":"

The process of refreshing display contents to show new information.

Example: Efficiently updating only changed portions of the time display.

"},{"location":"glossary/#screen-via-spi","title":"Screen Via SPI","text":"

High-speed serial communication method for updating displays.

Example: Sending framebuffer data to TFT screens efficiently.

"},{"location":"glossary/#servo-motor","title":"Servo Motor","text":"

An actuator that can rotate to precise positions.

Example: Moving physical clock hands on a hybrid display.

"},{"location":"glossary/#setting-system-clock","title":"Setting System Clock","text":"

Configuring the internal timekeeping system with accurate time.

Example: Updating RTC time from an external time source.

"},{"location":"glossary/#setting-time-with-buttons","title":"Setting Time with Buttons","text":"

Interface design and implementation for adjusting clock time using physical buttons.

Example: Using mode, increment, and decrement buttons to set hours and minutes.

"},{"location":"glossary/#setting-up-i2c-communication","title":"Setting Up I\u00b2C Communication","text":"

Configuring and using the Inter-Integrated Circuit (I\u00b2C) protocol for connecting multiple devices.

Example: Initializing I\u00b2C bus for communicating with RTC and sensor modules.

"},{"location":"glossary/#setting-up-wifi-on-the-pico-w","title":"Setting Up Wi\u2011Fi on the Pico W","text":"

Process of configuring wireless network connectivity on the Raspberry Pi Pico W microcontroller.

Example: Connecting to a local network using SSID and password credentials.

"},{"location":"glossary/#seven-segment-display","title":"Seven-Segment Display","text":"

A display made of seven LED segments plus a decimal point that can show numbers and some letters.

Example: Creating patterns for digits using segments 'a' through 'g': SEGMENTS = {'a': 4, 'b': 3, 'c': 2, 'd': 7, 'e': 6, 'f': 5, 'g': 1}

"},{"location":"glossary/#shift-register","title":"Shift Register","text":"

An integrated circuit that converts serial data into parallel outputs, useful for controlling multiple LEDs with fewer pins.

Example: Using SR74HC595 shift register to control display segments.

"},{"location":"glossary/#sleep-mode","title":"Sleep Mode","text":"

A low-power state that reduces energy consumption.

Example: Entering deep sleep between display updates.

"},{"location":"glossary/#sleep-scheduling","title":"Sleep Scheduling","text":"

Managing when the device enters and exits low-power states.

Example: Programming wake-up times for hourly updates.

"},{"location":"glossary/#smartwatch-displays","title":"Smartwatch Displays","text":"

Advanced display modules designed specifically for wearable devices, optimizing size, power consumption, and readability.

Example: Using a small, high-resolution display with automatic brightness adjustment.

"},{"location":"glossary/#sound-encoding","title":"Sound Encoding","text":"

Methods for storing and playing audio data for alarms and notifications.

Example: Converting WAV files to suitable format for playback.

"},{"location":"glossary/#spi","title":"SPI","text":"

Serial Peripheral Interface - a fast communication protocol for connecting digital components using multiple signal lines.

"},{"location":"glossary/#spi-interface","title":"SPI Interface","text":"

A high-speed serial communication protocol for connecting displays and sensors.

Example: Transferring data to TFT displays efficiently.

"},{"location":"glossary/#state-machine","title":"State Machine","text":"

A programming concept where a system can be in one of several defined states, with specific rules for transitioning between them.

Example: Clock modes including \"run\", \"set hour\", \"set minute\", and \"set AM/PM\".

"},{"location":"glossary/#step-counting-pedometer","title":"Step Counting (Pedometer)","text":"

Using motion sensors to detect and count walking steps for fitness tracking.

Example: Processing accelerometer data to identify step patterns and maintain daily count.

"},{"location":"glossary/#stopwatch","title":"Stopwatch","text":"

A timing device that measures elapsed time from a starting point, typically with precision to fractions of a second.

Example: Implementing start, stop, and lap timing functions with millisecond accuracy.

"},{"location":"glossary/#synchronizing-time-from-the-pc","title":"Synchronizing Time from the PC","text":"

The process of setting a microcontroller's clock using the connected computer's time.

Example: Using Thonny IDE to automatically update the Pico's RTC when uploading code.

"},{"location":"glossary/#temperature-and-humidity","title":"Temperature and Humidity","text":"

Integration of environmental sensors to display current conditions alongside time.

Example: Reading DHT22 sensor data to show temperature and humidity with clock display.

"},{"location":"glossary/#temperature-sensor","title":"Temperature Sensor","text":"

A component that measures ambient temperature.

Example: Displaying current temperature alongside time.

"},{"location":"glossary/#tft-displays","title":"TFT Displays","text":"

Thin-film-transistor liquid crystal displays for showing color graphics.

Example: Using ST7735 or ILI9341 displays for watch faces.

"},{"location":"glossary/#thonny-ide","title":"Thonny IDE","text":"

An integrated development environment optimized for MicroPython programming.

Example: Using Thonny to upload code and debug timing issues.

"},{"location":"glossary/#time-module","title":"Time Module","text":"

MicroPython library for handling time-related operations.

Example: Using scheduling functions for regular updates.

"},{"location":"glossary/#time-synchronization","title":"Time Synchronization","text":"

Process of updating device time from an accurate external source.

Example: Getting current time from network time servers.

"},{"location":"glossary/#time-zone","title":"Time Zone","text":"

A region that observes a uniform standard time.

Example: Converting between local time and UTC.

"},{"location":"glossary/#time-based-tasks-with-uasyncio","title":"Time-Based Tasks with uasyncio","text":"

Scheduling and managing time-dependent operations using MicroPython's asynchronous I/O framework.

Example: Updating multiple display elements at different intervals without blocking.

"},{"location":"glossary/#timer","title":"Timer","text":"

A system resource that generates regular time-based events.

Example: Creating precise one-second update intervals.

"},{"location":"glossary/#timer-based-events","title":"Timer-Based Events","text":"

Actions triggered by internal timing mechanisms.

Example: Updating display elements at specific intervals.

"},{"location":"glossary/#ultra-low-power","title":"Ultra-Low Power","text":"

Operating modes and techniques that minimize energy consumption.

Example: Using sleep modes and efficient screen updates.

"},{"location":"glossary/#usb-power","title":"USB Power","text":"

A 5-volt power source available through Universal Serial Bus connections, commonly used for powering small electronic projects.

"},{"location":"glossary/#using-16-bits-to-represent-color","title":"Using 16 Bits to Represent Color","text":"

A color depth specification that allocates 16 bits per pixel, typically with 5 bits for red, 6 for green, and 5 for blue (RGB565 format).

Example: Converting RGB colors to 16-bit format for efficient storage and display.

"},{"location":"glossary/#utc","title":"UTC","text":"

Coordinated Universal Time, the primary time standard for global time coordination.

Example: Converting local time to UTC for synchronization.

"},{"location":"glossary/#wake-on-alarm","title":"Wake on Alarm","text":"

Feature that activates the device from sleep mode at specific times.

Example: Waking the display for scheduled notifications.

"},{"location":"glossary/#watch-face-design","title":"Watch Face Design","text":"

Principles and techniques for creating visually appealing and functional clock displays.

Example: Balancing aesthetics and readability in analog and digital layouts.

"},{"location":"glossary/#watchdog-timer","title":"Watchdog Timer","text":"

A hardware feature that resets the system if the program stops responding.

Example: Ensuring reliability in long-running clock applications.

"},{"location":"glossary/#weather-updates","title":"Weather Updates","text":"

Real-time environmental data obtained through internet services.

Example: Displaying current conditions alongside time.

"},{"location":"glossary/#web-services","title":"Web Services","text":"

Online resources that provide data or functionality through standard internet protocols.

Example: Fetching current time from an internet time server for clock synchronization.

"},{"location":"glossary/#wi-fi-module","title":"Wi-Fi Module","text":"

Hardware that enables wireless network connectivity.

Example: Connecting to the internet for time synchronization.

"},{"location":"glossary/#wifibased-weather-updates","title":"Wi\u2011Fi\u2013Based Weather Updates","text":"

Retrieving and displaying current weather conditions using wireless internet connectivity.

Example: Fetching temperature, humidity, and forecast data from online weather services.

"},{"location":"glossary/#world-clock","title":"World Clock","text":"

A timepiece that displays times for multiple time zones simultaneously.

Example: Showing local time alongside other major cities' times.

"},{"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":""},{"location":"references/#data-sheets","title":"Data Sheets","text":""},{"location":"references/#battery-management","title":"Battery Management","text":"

[HT4928S - T6845C Data sheet][https://components101.com/sites/default/files/component_datasheet/T6845C-Datasheet.pdf] 8 pin battery management Chip

https://www.youtube.com/watch?v=np4NRMKOG6U

"},{"location":"kits/","title":"Introduction to Clock and Watch Project and Kits","text":"

We have grouped information on specific projects together in structures that allow you to find what you are looking for and customize both the hardware and software. We focus on using generative AI tools to customize the code to accelerate your development an enhance your understanding of how each project works.

"},{"location":"kits/#project-structure","title":"Project Structure","text":"

Each kit has the following structure:

"},{"location":"kits/#1-introduction","title":"1. Introduction","text":"

A brief overview of the project including a image of the clock, key features, approximate costs and possible variations.

"},{"location":"kits/#2-purchasing-and-assembly","title":"2. Purchasing and Assembly","text":"

This section shows how to purchase and assemble the clock. Most of the clocks require at a minimum a Raspberry Pi Pico (\\(4), a breadboard (\\)2) and a display. In addition you might add one or more of the following items:

  1. A real-time clock with a coin-cell battery backup to remember the time between power resets.
  2. Buttons to manually set the date and time.
  3. A wireless Pico W to allow your clock to connect to the internet.
  4. A speaker for alarms.
  5. A photodetector for detecting the lighting conditions.
  6. Additional controls such as a knob for adjusting the time.

Since these features can be used in combination, we also provide you with generative AI prompts to customize the code to run these clocks and watches

"},{"location":"kits/#3-code","title":"3. Code","text":"

This kit section provides you with a walkthrough of the core pa project has a brief overview of the project, a separate page for the physical assembly of the clock and a detailed guide to program the clock. The documentation may also have sample generative AI prompts you can use to generate your own code using a tool such as OpenAI's ChatGPT or Anthropic Claude. In general, the more precise your prompt is, the higher the odds that the code generated will be correct.

Each project also has separate folder in the GitHub \"src/kits\" folder. The running clock programs (called the \"mains\") come in several variations. One uses manual buttons (main-buttons.py) to set the time after a power outage. The other version called the \"main-rtc.py\" file name uses a real-time-clock to remember the time between power outages. They require an additional RTC clock that uses a coin-cell battery to hold the current time. If you find a \"main-w.py\" file that uses the secrets.py file to lookup your local wifi login to get the time from the internet. These versions don't need buttons or a RTC to get the correct time. They only need internet access.

"},{"location":"kits/#4-generative-ai-prompts","title":"4. Generative AI Prompts","text":""},{"location":"kits/#led-clocks","title":"LED Clocks","text":""},{"location":"kits/#standard-tm1637-led-clock","title":"Standard TM1637 LED Clock","text":"

This is a great starter clock. The display is low-cost ($1-2), and it only requires four wires to connect it up. It is easy to program LED clock with 4 7-segment displays.

TM1637 LED Clock Kit

"},{"location":"kits/#tinyssd1306-oled-with-an-i2c-interface","title":"TinySSD1306 OLED with an I2C Interface","text":"

This small low-cost OLED display is easy to hook up with just 4 wires. The combination of low-cost and easy hookup makes it an ideal starter project.

We hav purchased these OLEDs on eBay for under $4 each. Some of they have the first 20 rows yellow and the remaining 44 rows blue.

Tiny SSD1306 OLED with I2C Interface

"},{"location":"kits/#larger-242-oled-ssd1306-with-spi-interface","title":"Larger 2.42\" OLED SSD1306 with SPI Interface","text":"

Larger OLED

"},{"location":"kits/#neopixel-clocks","title":"NeoPixel Clocks","text":""},{"location":"kits/#binary-clock","title":"Binary Clock","text":"

Binary Clock

"},{"location":"kits/#seven-segment-clock","title":"Seven Segment Clock","text":""},{"location":"kits/#fibonacci-clock","title":"Fibonacci Clock","text":"

Fibonacci Clock

"},{"location":"kits/#lilygo-rp2040","title":"LilyGo RP2040","text":"

LilyGo RP2040 Kit

"},{"location":"kits/#smartwatch-displays","title":"SmartWatch Displays","text":"

GC9a01 Smartwatch Display

Waveshare LCD Smartwatch Display

"},{"location":"kits/#e-paper-clocks","title":"E-Paper Clocks","text":"

Waveshare e-Paper

"},{"location":"kits/gallery/","title":"Gallery of Clock and Watch Projects","text":""},{"location":"kits/alarm-clock/","title":"Alarm Clocks","text":"

Many changeling features requires attention to detail. Setting requires thoughtfulness of features.

"},{"location":"kits/alarm-clock/#using-buttons","title":"Using Buttons","text":""},{"location":"kits/alarm-clock/#setting-an-alarm","title":"Setting an Alarm","text":""},{"location":"kits/alarm-clock/#modes","title":"Modes","text":"

The Set Button

"},{"location":"kits/alarm-clock/#set-menu","title":"Set Menu","text":""},{"location":"kits/alarm-clock/#set-alarm","title":"Set Alarm","text":""},{"location":"kits/alarm-clock/#stop-alarm","title":"Stop Alarm","text":""},{"location":"kits/alarm-clock/#doze-button","title":"Doze Button","text":""},{"location":"kits/alarm-clock/#multiple-alarms","title":"Multiple Alarms","text":""},{"location":"kits/alarm-clock/#multiple-tones","title":"Multiple Tones","text":""},{"location":"kits/alarm-clock/#custom-wake-music","title":"Custom Wake Music","text":""},{"location":"kits/alarm-clock/display-alarm-icon/","title":"Display Alarm Icon","text":"
from array import array\n\ndef draw_alarm_icon(display, x, y, alarm_hour, alarm_minute, size=24):\n    \"\"\"\n    Draw an alarm bell icon with time display using polygons.\n\n    Args:\n        display: SSD1306 display instance\n        x, y: Top-left position for the icon\n        alarm_hour: Hour of the alarm (1-12)\n        alarm_minute: Minute of the alarm (0-59)\n        size: Base size of the icon (default 24 pixels)\n    \"\"\"\n    # Scale factors\n    scale = size / 24  # Base size is 24 pixels\n\n    # Bell body coordinates (scaled from base design)\n    bell_body = array('B', [\n        int(4 * scale),  int(0 * scale),    # Top left of dome\n        int(20 * scale), int(0 * scale),    # Top right of dome\n        int(24 * scale), int(12 * scale),   # Bottom right curve\n        int(22 * scale), int(18 * scale),   # Right side\n        int(2 * scale),  int(18 * scale),   # Left side\n        int(0 * scale),  int(12 * scale),   # Bottom left curve\n    ])\n\n    # Bell base coordinates\n    bell_base = array('B', [\n        int(2 * scale),  int(18 * scale),   # Top left\n        int(22 * scale), int(18 * scale),   # Top right\n        int(20 * scale), int(20 * scale),   # Bottom right\n        int(4 * scale),  int(20 * scale),   # Bottom left\n    ])\n\n    # Clapper coordinates\n    clapper = array('B', [\n        int(11 * scale), int(20 * scale),   # Top\n        int(13 * scale), int(20 * scale),   # Top right\n        int(14 * scale), int(24 * scale),   # Bottom right\n        int(10 * scale), int(24 * scale),   # Bottom left\n    ])\n\n    # Sound wave lines (using array for consistency)\n    left_wave = array('B', [\n        int(0 * scale),  int(12 * scale),\n        int(-3 * scale), int(12 * scale),\n        int(-4 * scale), int(14 * scale),\n        int(-3 * scale), int(16 * scale),\n    ])\n\n    right_wave = array('B', [\n        int(24 * scale), int(12 * scale),\n        int(27 * scale), int(12 * scale),\n        int(28 * scale), int(14 * scale),\n        int(27 * scale), int(16 * scale),\n    ])\n\n    # Draw the components\n    display.poly(x, y, bell_body, 1, 1)  # Filled bell body\n    display.poly(x, y, bell_base, 1, 1)  # Filled bell base\n    display.poly(x, y, clapper, 1, 1)    # Filled clapper\n\n    # Draw the sound waves\n    if size >= 20:  # Only draw waves if icon is large enough\n        display.poly(x, y, left_wave, 1, 0)   # Left sound wave\n        display.poly(x, y, right_wave, 1, 0)  # Right sound wave\n\n    # Draw the alarm time below the bell\n    time_str = f\"{alarm_hour:2d}:{alarm_minute:02d}\"\n    # Center the time string under the bell\n    text_x = x + (int(24 * scale) - len(time_str) * 6) // 2\n    text_y = y + int(26 * scale)\n    display.text(time_str, text_x, text_y, 1)\n\ndef demo_alarm_icons(display):\n    \"\"\"\n    Demonstrate the alarm icon at different sizes and positions\n    \"\"\"\n    # Clear the display\n    display.fill(0)\n\n    # Draw three different sized bells\n    draw_alarm_icon(display, 0, 0, 7, 30, size=20)    # Small bell\n    draw_alarm_icon(display, 35, 0, 8, 15, size=28)   # Medium bell\n    draw_alarm_icon(display, 80, 0, 6, 45, size=36)   # Large bell\n\n    # Update the display\n    display.show()\n\n# Example usage:\n\"\"\"\noled = ssd1306.SSD1306_SPI(128, 64, spi, DC, RES, CS)\ndemo_alarm_icons(oled)\n\"\"\"\n
"},{"location":"kits/e-paper/","title":"E-Ink Displays","text":"

Prompt

Please suggest a low-cost e-ink display that has strong support for a microPython driver.

ChatGPT

Thought about e-ink display and MicroPython for 5 seconds

If you're looking for a budget-friendly e-ink module that's well-documented and easy to use in MicroPython projects, a great place to start is with the smaller Waveshare e-Paper displays. Specifically, the Waveshare 1.54-inch e-Paper module is often cited for its low cost and good MicroPython support. Here are some details:

"},{"location":"kits/e-paper/#chatgpt-response","title":"ChatGPT Response","text":"

1) Waveshare 1.54-inch e-Paper Module

Why it's a good pick

Where to find drivers

Key specs

"},{"location":"kits/e-paper/#2-waveshare-213-inch-e-paper-slightly-larger-option","title":"2) Waveshare 2.13-inch e-Paper (Slightly Larger Option)","text":"

If you need a bit more display real estate, the 2.13-inch Waveshare e-Paper module is another solid choice. It's still relatively low cost (somewhere around \\(25--\\)30) but gives you higher resolution (around 250 \u00d7 122 or 212 \u00d7 104, depending on the version) for more text or small graphics.

"},{"location":"kits/e-paper/#3-lilygo-t5-series-all-in-one-board","title":"3) LilyGo T5 Series (All-in-One Board)","text":"

Another cost-effective route (if you don't mind an all-in-one solution rather than a separate module) is the LilyGo T5 board series. These boards come with an ESP32 already integrated and a small e-ink display (like 1.54\", 2.13\", 2.7\", 4.7\" versions).

If you already have a microcontroller board and want to keep costs low, a stand-alone Waveshare display will likely be cheaper. However, if you're starting from scratch, the LilyGo T5 might save you some setup time (fewer wiring hassles, etc.).

"},{"location":"kits/e-paper/#summary","title":"Summary","text":"

All of these displays have active communities, plenty of tutorials, and code examples you can adapt for MicroPython, making them a great fit if you're just getting started with e-ink in your microcontroller projects.

o1

"},{"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-displays/","title":"LED Display Clock","text":""},{"location":"kits/led-displays/#simple-tm1637","title":"Simple TM1637","text":"

Simple TM1637

"},{"location":"kits/led-displays/#shift-register-displays","title":"Shift Register Displays","text":"

Shift Register Displays

We can use the TM1637 4-Digit LED Display Module which can be purchased on e-Bay for a few dollars. This means that your clock will cost under $10 total.

Sample Listing on EBay

"},{"location":"kits/led-displays/#hookup-instructions","title":"Hookup Instructions","text":"

We can hook this up to the Raspberry Pi.

|Name|Pin|| |5 Volts|VBUS or VSYS| |GND|Any GND Pin| |CLK|Pins 4| |Data|Pin 5 |

"},{"location":"kits/led-displays/#micropython-driver","title":"MicroPython Driver","text":"

Mike Causer's TM1637 GitHub Driver

"},{"location":"kits/led-displays/#sample-code","title":"Sample Code","text":""},{"location":"kits/led-displays/#python-imports","title":"Python Imports","text":"
import tm1637\nfrom machine import Pin\nfrom utime import sleep\ntm = tm1637.TM1637(clk=Pin(0), dio=Pin(1))\n
"},{"location":"kits/led-displays/#test-program","title":"Test Program","text":"
import tm1637\nfrom machine import Pin\nfrom utime import sleep\ntm = tm1637.TM1637(clk=Pin(0), dio=Pin(1))\n\nwhile True:\n    # all LEDS on \"88:88\"\n    tm.write([127, 255, 127, 127])\n    sleep(1)\n\n    # all LEDS off\n    tm.write([0, 0, 0, 0])\n    sleep(1)\n\n    # show \"0123\"\n    tm.write([63, 6, 91, 79])\n    sleep(1)\n\n    # show \"COOL\"\n    tm.write([0b00111001, 0b00111111, 0b00111111, 0b00111000])\n    sleep(1)\n\n    # show \"HELP\"\n    tm.show('help')\n    sleep(1)\n\n    # display \"dEAd\", \"bEEF\"\n    tm.hex(0xdead)\n    sleep(1)\n\n    tm.hex(0xbeef)\n    sleep(1)\n\n    # show \"12:59\"\n    tm.numbers(12, 59)\n    sleep(1)\n\n    # show \"-123\"\n    tm.number(-123)\n    sleep(1)\n\n    # show temperature '24*C'\n    tm.temperature(24)\n    sleep(1)\n

Next, will will use the localtime() function to build a simple clock.

Simple Clock

"},{"location":"kits/led-displays/#references","title":"References","text":""},{"location":"kits/led-displays/clock-simple/","title":"A Simple Clock with the TM1637 LED Display","text":"

Here is a simple clock program that will display the hours and minutes from the localtime() function. It will also turn the colon LEDs on and off every second using the modulo function to test for the even/odd property of the second.

"},{"location":"kits/led-displays/clock-simple/#the-localtime-function","title":"The Localtime function","text":"
# display hours and minutes on the TM1637 LED display\n# make the colon go on and off every second\nimport tm1637\nfrom machine import Pin\nfrom utime import sleep, localtime\ntm = tm1637.TM1637(clk=Pin(0), dio=Pin(1))\n\nwhile True:\n    now = localtime() # returns 8 inits for date and time\n    hours = now[3]\n    minutes = now[4]\n    seconds = now[5]\n    print(hours, \":\", minutes, ' ', seconds)\n    # flash the colon on and off every second\n    if (seconds % 2): # modulo 2 will be true for odd numbers\n        tm.numbers(hours, minutes, True)\n    else:\n        tm.numbers(hours, minutes, False)\n    sleep(1)\n

That is it! Pretty cool that you can write an entire clock program in that few lines of code. But it is not perfect. Let's find some ways to make the clock work a little better.

Note that the hours is always in 24 hour time. If you want to use 12 hour time you will need to subtract 12 from the hours if the hours is greater than 12.

We can add the following lines of code to make the display better for people that use a 12-hour clock.

if hours > 12:\n    hours = now[3]\n    hours = hours - 12\n

This will work, but it has the disadvantage of displaying the leading zero before the ones digit. Unfortunately, this is the way that the numbers() function was written in the TM1637 module. Let's take a look at how we can clean this up a bit.

"},{"location":"kits/led-displays/clock-simple/#removing-leading-zeros","title":"Removing Leading Zeros","text":"

In order to fix the leading zeros problem in the hours place, we need to write our own version of the numbers() function that changes '0' to be a space (all 7 segments off) if the hours is less than 10.

Here is the original numbers() function taken directly from the driver:

def numbers(self, num1, num2, colon=True):\n    \"\"\"Display two numeric values -9 through 99, with leading zeros\n    and separated by a colon.\"\"\"\n    num1 = max(-9, min(num1, 99))\n    num2 = max(-9, min(num2, 99))\n    segments = self.encode_string('{0:0>2d}{1:0>2d}'.format(num1, num2))\n    if colon:\n        segments[1] |= 0x80 # colon on\n    self.write(segments)\n

You can see that the author used the Python .format function to display the first number using a leading zero. This is fine for our minutes, but not a standard for the hours.

We will need to modify this code to put in a space character in if the hours is less than 10 and to only display the hours number without a leading zero. The format {0:0>2d} will be changed to be: {prefix}{num1:d} where:

prefix = ' ' if num1 < 10 else ''\n
def numbers_nlz(num1, num2, colon=True):\n    \"\"\"Display two numeric values -9 through 99, with a leading space before\n    single-digit first numbers and separated by a colon.\"\"\"\n    num1 = max(-9, min(num1, 99))\n    num2 = max(-9, min(num2, 99))\n    prefix = ' ' if num1 < 10 else ''\n    print(f'\"{prefix}{num1:d}{num2:0>2d}\"')\n    segments = tm.encode_string(f'{prefix}{num1:d}{num2:0>2d}')\n    if colon:\n        segments[1] |= 0x80  # colon on\n    tm.write(segments)\n

Now the display will work as most normal digital clocks.

So as long as the localtime() function is working, this clock should work fine. An as long as your device is connected to your computer via an USB cable it will be fine.

What if you would like your clock to work without being connected to a computer. We have two options:

  1. Used a Raspberry Pi Pico W to get time from a central time service over Wifi
  2. or use a local clock and set the time manually
"},{"location":"kits/led-displays/reference/","title":"Programming Reference for the TM1637 Clock LED Driver","text":""},{"location":"kits/led-displays/reference/#seven-segment-font","title":"Seven Segment Font","text":"

They are called 7-segment displays as there are 7 LEDs for each digit (segment). One byte (7 lower bits) for each segment. The 8th bit (MSB) is for the colon and only on the 2nd segment.

      A\n     ---\n  F |   | B   *\n     -G-      H (on 2nd segment)\n  E |   | C   *\n     ---\n      D\n\n  HGFEDCBA\n0b01101101 = 0x6D = 109 = show \"5\"\n
Display Bin Hex Dec 0 0b00111111 0x3F 63 1 0b00000110 0x06 6 2 0b01011011 0x5B 91 3 0b01001111 0x4F 79 4 0b01100110 0x66 102 5 0b01101101 0x6D 109 6 0b01111101 0x7D 125 7 0b00000111 0x07 7 8 0b01111111 0x7F 127 9 0b01101111 0x6F 111 A 0b01110111 0x77 119 b 0b01111100 0x7C 124 C 0b00111001 0x39 57 d 0b01011110 0x5E 94 E 0b01111001 0x79 121 F 0b01110001 0x71 113 G 0b00111101 0x3D 61 H 0b01110110 0x76 118 I 0b00000110 0x06 6 J 0b00011110 0x1E 30 K 0b01110110 0x76 118 L 0b00111000 0x38 56 M 0b01010101 0x55 85 n 0b01010100 0x54 84 O 0b00111111 0x3F 63 P 0b01110011 0x73 115 q 0b01100111 0x67 103 r 0b01010000 0x50 80 S 0b01101101 0x6D 109 t 0b01111000 0x78 120 U 0b00111110 0x3E 62 v 0b00011100 0x1C 28 W 0b00101010 0x2A 42 X 0b01110110 0x76 118 y 0b01101110 0x6E 110 Z 0b01011011 0x5B 91 blank 0b00000000 0x00 0 - 0b01000000 0x40 64 * 0b01100011 0x63 99"},{"location":"kits/led-displays/reference/#methods","title":"Methods","text":""},{"location":"kits/led-displays/reference/#set-brightneww","title":"Set Brightneww","text":"

Get or set brightness.

brightness(val=None)\n

"},{"location":"kits/led-displays/reference/#write-segments","title":"Write Segments","text":"

Write one or more segments at a given offset.

write(segments, pos=0)\n

"},{"location":"kits/led-displays/reference/#write-two-numbers-and-colon","title":"Write Two Numbers and Colon","text":"

Write Two Numbers and Colon

numbers(num1, num2, colon=True):\n
"},{"location":"kits/led-displays/shift-register-displays/","title":"Shift Register Displays","text":"

The 74hc595 is a chip that takes a clock and data stream in and turns many LEDs on or off. 74hc595 chips can be connected in series to control 7 segments on 4 digits.

I'll create a detailed walkthrough of the clock-driver.py program, breaking it down into digestible sections with accompanying labs. This will help students understand both the code and the underlying concepts.

"},{"location":"kits/led-displays/shift-register-displays/#1-core-components-and-imports","title":"1. Core Components and Imports","text":"
from machine import Pin, RTC \nfrom sr74hc595 import SR74HC595_BITBANG\nfrom utime import sleep, localtime, ticks_ms\n

This section imports the necessary libraries. The program uses: - machine.Pin: Controls individual GPIO pins on the Pico - RTC: Real-Time Clock for keeping time - sr74hc595: Manages the shift register that controls the display - utime: Provides timing functions

"},{"location":"kits/led-displays/shift-register-displays/#lab-1-understanding-gpio-pins","title":"Lab 1: Understanding GPIO Pins","text":"

Have students create a simple LED blink program:

from machine import Pin\nfrom utime import sleep\n\nled = Pin(25, Pin.OUT)  # Built-in LED on Pico\nwhile True:\n    led.value(1)  # Turn on\n    sleep(1)\n    led.value(0)  # Turn off\n    sleep(1)\n
"},{"location":"kits/led-displays/shift-register-displays/#2-rtc-initialization","title":"2. RTC Initialization","text":"
def wait_for_rtc():\n    print(\"Waiting for RTC to be ready...\")\n    rtc = RTC()\n    while True:\n        current_time = rtc.datetime()\n        if current_time[0] != 2021 or current_time[1] != 1 or current_time[2] != 1:\n            print(\"RTC is ready!\")\n            return\n        print(\"RTC not ready yet...\")\n        sleep(1)\n

This function ensures the RTC has been set to a valid time before proceeding.

"},{"location":"kits/led-displays/shift-register-displays/#lab-2-rtc-basics","title":"Lab 2: RTC Basics","text":"

Have students experiment with reading and setting the RTC:

from machine import RTC\nfrom utime import sleep\n\nrtc = RTC()\n# Set the time (year, month, day, weekday, hour, minute, second, subsecond)\nrtc.datetime((2024, 12, 27, 5, 14, 30, 0, 0))\n\nwhile True:\n    current_time = rtc.datetime()\n    print(f\"Current time: {current_time[4]:02d}:{current_time[5]:02d}:{current_time[6]:02d}\")\n    sleep(1)\n
"},{"location":"kits/led-displays/shift-register-displays/#3-seven-segment-display-setup","title":"3. Seven-Segment Display Setup","text":"

class FourDigitClock:\n    ALL_OFF = 0xFF  # All segments off (inverted logic)\n\n    def __init__(self, ser_pin, srclk_pin, rclk_pin):\n        self.ser = Pin(ser_pin, Pin.OUT)\n        self.srclk = Pin(srclk_pin, Pin.OUT) \n        self.rclk = Pin(rclk_pin, Pin.OUT)\n
This class manages the four-digit display. It uses three pins to control the shift register.

"},{"location":"kits/led-displays/shift-register-displays/#lab-3-seven-segment-pattern-display","title":"Lab 3: Seven-Segment Pattern Display","text":"

Have students create a simple program to display a single digit:

from machine import Pin\nfrom utime import sleep\n\n# Create a simple version that lights up segments manually\nsegments = {\n    'a': Pin(2, Pin.OUT),\n    'b': Pin(3, Pin.OUT),\n    'c': Pin(4, Pin.OUT),\n    # ... add more segments\n}\n\ndef display_number(number):\n    # Pattern for number 1\n    if number == 1:\n        segments['b'].value(1)\n        segments['c'].value(1)\n    # Add more numbers...\n

"},{"location":"kits/led-displays/shift-register-displays/#4-digit-patterns","title":"4. Digit Patterns","text":"

self.SEGMENTS = {\n    'a': 4, 'b': 3, 'c': 2, \n    'd': 7, 'e': 6, 'f': 5, 'g': 1\n}\nself.DIGIT_SEGMENTS = {\n    0: 'abcdef', 1: 'bc', 2: 'abged', 3: 'abgcd',\n    4: 'fbcg', 5: 'afgcd', 6: 'afedcg', \n    7: 'abc', 8: 'abcdefg', 9: 'abfgcd'\n}\n
This section defines which segments should be lit for each number.

"},{"location":"kits/led-displays/shift-register-displays/#lab-4-pattern-design","title":"Lab 4: Pattern Design","text":"

Have students draw and design their own custom characters using the seven segments. They can create: - Letters (A, b, C, d, E, F) - Custom symbols - Animated patterns

"},{"location":"kits/led-displays/shift-register-displays/#5-time-display-logic","title":"5. Time Display Logic","text":"
def display_time(self, hour, minute, colon_state):\n    # Convert to 12-hour format\n    if hour > 12:\n        hour -= 12\n    elif hour == 0:  \n        hour = 12\n
"},{"location":"kits/led-displays/shift-register-displays/#lab-5-time-format-conversion","title":"Lab 5: Time Format Conversion","text":"

Have students write a program that converts between 24-hour and 12-hour time formats:

def convert_time(hour24):\n    if hour24 > 12:\n        return hour24 - 12, \"PM\"\n    elif hour24 == 0:\n        return 12, \"AM\"\n    elif hour24 == 12:\n        return 12, \"PM\"\n    else:\n        return hour24, \"AM\"\n
"},{"location":"kits/led-displays/shift-register-displays/#advanced-labs-and-extensions","title":"Advanced Labs and Extensions:","text":""},{"location":"kits/led-displays/shift-register-displays/#1-alarm-clock-lab","title":"1. Alarm Clock Lab","text":"

Modify the clock to add alarm functionality: - Add a button to set alarm time - Add a buzzer for the alarm - Implement snooze functionality

"},{"location":"kits/led-displays/shift-register-displays/#2-temperature-display-lab","title":"2. Temperature Display Lab","text":"

Alternate between showing time and temperature: - Add a temperature sensor - Display temperature for 3 seconds every minute - Add a button to toggle between time and temperature

"},{"location":"kits/led-displays/shift-register-displays/#3-custom-animation-lab","title":"3. Custom Animation Lab","text":"

Create animations for the display: - Make digits spin when changing - Create a \"snake\" animation for the top of each hour - Design transitions between numbers

"},{"location":"kits/led-displays/shift-register-displays/#4-world-clock-lab","title":"4. World Clock Lab","text":"

Modify the clock to show multiple time zones: - Add buttons to cycle through different time zones - Show timezone abbreviation - Store favorite time zones

"},{"location":"kits/led-displays/shift-register-displays/#5-stopwatch-lab","title":"5. Stopwatch Lab","text":"

Add stopwatch functionality: - Use buttons to start/stop/reset - Display tenths of seconds - Store lap times

"},{"location":"kits/led-displays/shift-register-displays/#summary","title":"Summary","text":"

These labs progressively build upon the base code while introducing new concepts and challenges. Each lab reinforces different programming concepts:

This allows students to learn both programming and hardware concepts in a hands-on, engaging way while creating something practical and visible.

"},{"location":"kits/lilygo/","title":"LILYGO T-Display RP2040 Kit","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/neopixel/","title":"NeoPixel LED Strip Clock Kits","text":"

In this section we will present three clocks that use a string of low-cost RGB LEDs commonly called NeoPixels. The name of this standard is technically called the WS2812B addressable LED standards. NeoPixels are frequently used in art-clock projects because they are:

  1. Easy to hook up - just three wires can drive up to 400 RGB LED
  2. Low cost - typically they sell for about $5/meter (60 pixels per meter) on eBay
  3. Available in many variations - they come is strips of various densities, rings, matrices, string and fairy light configurations

We have an entire on-line course on how to used these fun and colorful strips at our Moving Rainbow website. That site includes hundreds of step-by-step lesson plans and sample programs for kids ranging from age 8 to 18.

In this book we have several examples of clocks built with NeoPixels.

  1. Binary Clock This kit displays the time using binary numbers. It is a little hard to read at first, but these kits are extremely popular. Link to the Binary Clock Project. There are also several alternate layouts of how to display a binary clock. There are six different NeoPixel strips, each strip requires about five minutes of soldering, so the assembly takes about an hour. The total parts cost about $11.
  2. Seven Segment Clock The second requires building four seven-segment displays from the LED strip (or LED string) so the assembly time is significantly longer - about three hours if you are good with a soldering iron. Link to the Seven Segment Clock Project
  3. Fibonacci Clock This clock is unusual since it requires a bit of time to be able to read the time. However, it has what many consider the most elegant simple display. Link to the Fibonacci Clock Project.
"},{"location":"kits/neopixel/binary-clock/","title":"Binary Clock","text":"

The binary clock is a popular project where binary numbers are used to display the digits of a clock.

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

We can create a very simple binary clock using a single NeoPixel LED strip with just 12 pixels.

"},{"location":"kits/neopixel/binary-clock/#microsim","title":"MicroSim","text":"

You can see a simulation of the clock here:

Note that you can click the \"Show Text Time\" and \"Show Values\" to get a decoded human-understandable view of how to tell time with the clock.

"},{"location":"kits/neopixel/binary-clock/alternative-layouts/","title":"Alternative Binary Clock Layouts","text":"

The strip will have two rows of six pixels each:

  1. The first row will be the binary hour (0 to 24) (5 green pixels) and one pixel that flashes the second
  2. The second row will show the minutes (0 to 59) (6 blue pixels)
"},{"location":"kits/neopixel/binary-clock/alternative-layouts/#three-row-clock","title":"Three Row Clock","text":""},{"location":"kits/neopixel/binary-clock/alternative-layouts/#sample-code-for-three-rows-with-second-counter","title":"Sample Code for Three Rows with Second Counter","text":"
  1. Row 1 is five pixels with the hours (0-24)
  2. Row 2 is the minutes with the minutes (0-60)
  3. Row 3 is the seconds (0-60)
from machine import Pin\nfrom neopixel import NeoPixel\nfrom utime import sleep, localtime\n\nNEOPIXEL_PIN = 0\nNUMBER_PIXELS = 18\nstrip = NeoPixel(Pin(NEOPIXEL_PIN), NUMBER_PIXELS)\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 is halfed doing a divide by 2\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/neopixel/binary-clock/alternative-layouts/#references","title":"References","text":""},{"location":"kits/neopixel/binary-clock/assembly/","title":"Binary Clock Assembly","text":""},{"location":"kits/neopixel/binary-clock/assembly/#led-strip-layout","title":"LED Strip Layout","text":""},{"location":"kits/neopixel/binary-clock/code/","title":"Binary Clock Code","text":""},{"location":"kits/neopixel/binary-clock/code/#colum-configuration-values","title":"Colum Configuration Values","text":"
# Column configuration (start_index, height, color)\nCOLUMN_CONFIG = {\n    'hours_tens':   (18, 2, HOURS_COLOR),   # Column 1: 2 pixels (0-2)\n    'hours_ones':   (14, 4, HOURS_COLOR),   # Column 2: 4 pixels (0-9)\n    'minutes_tens': (11, 3, MINUTES_COLOR), # Column 3: 3 pixels (0-5)\n    'minutes_ones': (7, 4, MINUTES_COLOR),  # Column 4: 4 pixels (0-9)\n    'seconds_tens': (4, 3, SECONDS_COLOR),  # Column 5: 3 pixels (0-5)\n    'seconds_ones': (0, 4, SECONDS_COLOR),  # Column 6: 4 pixels (0-9)\n}\n
from machine import Pin\nfrom neopixel import NeoPixel\nfrom utime import localtime, sleep\n\n# Configuration\nNEOPIXEL_PIN = 0\nNUM_PIXELS = 20  # Total number of pixels (18 + 2)\n\n# Colors (RGB values) - using light/pastel versions\nHOURS_COLOR = (10, 50, 10)     # Light green\nMINUTES_COLOR = (10, 10, 60)   # Light cyan\nSECONDS_COLOR = (30, 40, 0)    # Light yellow\nOFF_COLOR = (0, 0, 0)          # Off\n\n# Column configuration (start_index, height, color)\nCOLUMN_CONFIG = {\n    'hours_tens':   (18, 2, HOURS_COLOR),   # Column 1: 2 pixels (0-2)\n    'hours_ones':   (14, 4, HOURS_COLOR),   # Column 2: 4 pixels (0-9)\n    'minutes_tens': (11, 3, MINUTES_COLOR), # Column 3: 3 pixels (0-5)\n    'minutes_ones': (7, 4, MINUTES_COLOR),  # Column 4: 4 pixels (0-9)\n    'seconds_tens': (4, 3, SECONDS_COLOR),  # Column 5: 3 pixels (0-5)\n    'seconds_ones': (0, 4, SECONDS_COLOR),  # Column 6: 4 pixels (0-9)\n}\n\n# Initialize NeoPixels\npixels = NeoPixel(Pin(NEOPIXEL_PIN), NUM_PIXELS)\n\ndef int_to_binary_column(number, num_bits):\n    \"\"\"Convert a number to binary and return list of bits.\"\"\"\n    binary = []\n    for i in range(num_bits):\n        binary.append(1 if number & (1 << i) else 0)\n    return binary  # LSB first\n\ndef set_column(start_index, height, color, number):\n    \"\"\"Set the LEDs for a specific column based on the number.\"\"\"\n    binary = int_to_binary_column(number, height)\n\n    # Set each LED in the column\n    for bit_pos in range(height):\n        pixel_index = start_index + bit_pos\n        pixels[pixel_index] = color if binary[bit_pos] else OFF_COLOR\n\ndef update_display(hours, minutes, seconds):\n    \"\"\"Update all columns with current time.\"\"\"\n    # Hours\n    set_column(*COLUMN_CONFIG['hours_tens'], hours // 10)\n    set_column(*COLUMN_CONFIG['hours_ones'], hours % 10)\n\n    # Minutes\n    set_column(*COLUMN_CONFIG['minutes_tens'], minutes // 10)\n    set_column(*COLUMN_CONFIG['minutes_ones'], minutes % 10)\n\n    # Seconds\n    set_column(*COLUMN_CONFIG['seconds_tens'], seconds // 10)\n    set_column(*COLUMN_CONFIG['seconds_ones'], seconds % 10)\n\n    pixels.write()  # Update the NeoPixels\n\ndef main():\n    print(\"Binary Clock Started\")\n    print(\"Columns from right to left:\")\n    print(\"1. Seconds ones (4 bits) - Light Yellow\")\n    print(\"2. Seconds tens (3 bits) - Light Yellow\")\n    print(\"3. Minutes ones (4 bits) - Light Cyan\")\n    print(\"4. Minutes tens (3 bits) - Light Cyan\")\n    print(\"5. Hours ones (4 bits) - Light Green\")\n    print(\"6. Hours tens (2 bits) - Light Green\")\n    print(\"LSB at bottom of each column\")\n\n    while True:\n        # Get current time - replace this line with the RTC call in the final version\n        t = localtime()\n        hours, minutes, seconds = t[3], t[4], t[5]\n\n        # Update the display\n        update_display(hours, minutes, seconds)\n\n        # Print current time for debugging\n        print(f\"{hours:02d}:{minutes:02d}:{seconds:02d}\")\n\n        # Wait before next update\n        sleep(1)\n\nif __name__ == \"__main__\":\n    main()\n
"},{"location":"kits/neopixel/binary-clock/prompt/","title":"Prompt","text":"

Prompt

I would like your help designing a binary clock using NeoPixels with MicroPython The clock will have six columns. Column 1 is for the hours tens digits. Column 2 is for the hours one digits. Column 3 is for the minutes tens digits Column 4 is for the minutes ones digits Column 5 is for the seconds tens digits Column 6 is for the seconds ones digits

Column 6 (seconds ones) starts a index=0 and is 4 pixels high Column 5 (seconds tens) starts index=4 and is 3 pixels high Column 4 (minutes ones) starts at index-7 and is 4 pixels high Column 3 (minutes tens) starts at index=11 and is 3 pixels high Column 2 (hours ones) starts at index=14 and is 4 pixels high Column 1 (hour tens) starts at index=18 and is 2 pixels high

Put the least significant bit at the bottom of the column. Please generate a MicroPython application that shows the time. Use the following preamble.

"},{"location":"kits/neopixel/binary-clock/prompt/#preamble","title":"Preamble","text":"
# Configuration\nNEOPIXEL_PIN = 0\nNUM_PIXELS = 20  # Total number of pixels (18 + 2)\n\n# Column configuration (start_index, height)\nCOLUMN_CONFIG = {\n    'hours_tens':   (18, 2),  # Column 1: 2 pixels (0-2)\n    'hours_ones':   (14, 4),  # Column 2: 4 pixels (0-9)\n    'minutes_tens': (11, 3),  # Column 3: 3 pixels (0-5)\n    'minutes_ones': (7, 4),   # Column 4: 4 pixels (0-9)\n    'seconds_tens': (4, 3),   # Column 5: 3 pixels (0-5)\n    'seconds_ones': (0, 4),   # Column 6: 4 pixels (0-9)\n}\n\n# Initialize NeoPixels\npixels = NeoPixel(Pin(NEOPIXEL_PIN), NUM_PIXELS)\n\n# Colors (RGB values)\nON_COLOR = (0, 20, 20)    # Cyan, dimmed for less power usage\nOFF_COLOR = (0, 0, 0)     # Off\n
"},{"location":"kits/neopixel/binary-clock/prompt/#full-program-generated","title":"Full Program Generated","text":"
from machine import Pin\nfrom neopixel import NeoPixel\nfrom utime import localtime, sleep\n\n# Configuration\nNEOPIXEL_PIN = 0\nNUM_PIXELS = 20  # Total number of pixels (18 + 2)\n\n# Column configuration (start_index, height)\nCOLUMN_CONFIG = {\n    'hours_tens':   (18, 2),  # Column 1: 2 pixels (0-2)\n    'hours_ones':   (14, 4),  # Column 2: 4 pixels (0-9)\n    'minutes_tens': (11, 3),  # Column 3: 3 pixels (0-5)\n    'minutes_ones': (7, 4),   # Column 4: 4 pixels (0-9)\n    'seconds_tens': (4, 3),   # Column 5: 3 pixels (0-5)\n    'seconds_ones': (0, 4),   # Column 6: 4 pixels (0-9)\n}\n\n# Initialize NeoPixels\npixels = NeoPixel(Pin(NEOPIXEL_PIN), NUM_PIXELS)\n\n# Colors (RGB values)\nON_COLOR = (0, 20, 20)    # Cyan, dimmed for less power usage\nOFF_COLOR = (0, 0, 0)     # Off\n\ndef int_to_binary_column(number, num_bits):\n    \"\"\"Convert a number to binary and return list of bits.\"\"\"\n    binary = []\n    for i in range(num_bits):\n        binary.append(1 if number & (1 << i) else 0)\n    return binary  # LSB first\n\ndef set_column(start_index, height, number):\n    \"\"\"Set the LEDs for a specific column based on the number.\"\"\"\n    binary = int_to_binary_column(number, height)\n\n    # Set each LED in the column\n    for bit_pos in range(height):\n        pixel_index = start_index + bit_pos\n        pixels[pixel_index] = ON_COLOR if binary[bit_pos] else OFF_COLOR\n\ndef update_display(hours, minutes, seconds):\n    \"\"\"Update all columns with current time.\"\"\"\n    # Hours\n    set_column(*COLUMN_CONFIG['hours_tens'], hours // 10)\n    set_column(*COLUMN_CONFIG['hours_ones'], hours % 10)\n\n    # Minutes\n    set_column(*COLUMN_CONFIG['minutes_tens'], minutes // 10)\n    set_column(*COLUMN_CONFIG['minutes_ones'], minutes % 10)\n\n    # Seconds\n    set_column(*COLUMN_CONFIG['seconds_tens'], seconds // 10)\n    set_column(*COLUMN_CONFIG['seconds_ones'], seconds % 10)\n\n    pixels.write()  # Update the NeoPixels\n\ndef main():\n    print(\"Binary Clock Started\")\n    print(\"Columns from right to left:\")\n    print(\"1. Seconds ones (4 bits)\")\n    print(\"2. Seconds tens (3 bits)\")\n    print(\"3. Minutes ones (4 bits)\")\n    print(\"4. Minutes tens (3 bits)\")\n    print(\"5. Hours ones (4 bits)\")\n    print(\"6. Hours tens (2 bits)\")\n    print(\"LSB at bottom of each column\")\n\n    while True:\n        # Get current time\n        t = localtime()\n        hours, minutes, seconds = t[3], t[4], t[5]\n\n        # Update the display\n        update_display(hours, minutes, seconds)\n\n        # Print current time for debugging\n        print(f\"{hours:02d}:{minutes:02d}:{seconds:02d}\")\n\n        # Wait before next update\n        sleep(1)\n\nif __name__ == \"__main__\":\n    main()\n
"},{"location":"kits/neopixel/fibonacci-clock/","title":"Fibonacci Clock","text":"

Assembly

Programming Code

Generative AI Prompt

"},{"location":"kits/neopixel/fibonacci-clock/assembly/","title":"Assembly","text":""},{"location":"kits/neopixel/fibonacci-clock/code/","title":"Code","text":"
# Fibonacci time function\ndef fib_time(hours, minutes):\n    vals = [1, 1, 2, 3, 5]\n    state = [0, 0, 0, 0, 0]\n\n    # Calculate Fibonacci representation for hours\n    remaining_hours = hours\n    idx = len(vals) - 1\n    for v in vals[::-1]:\n        if remaining_hours == 0 or idx < 0: break\n        if remaining_hours >= v:\n            state[idx] += 1\n            remaining_hours -= v\n        idx -= 1\n\n    # Calculate Fibonacci representation for minutes (in increments of 5)\n    remaining_minutes = math.floor(minutes / 5)\n    idx = len(vals) - 1\n    for v in vals[::-1]:\n        if remaining_minutes == 0 or idx < 0: break\n        if remaining_minutes >= v:\n            state[idx] += 2\n            remaining_minutes -= v\n        idx -= 1\n\n    return state\n
"},{"location":"kits/neopixel/seven-segment-clock/","title":"Neopixel 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.

"},{"location":"kits/neopixel/seven-segment-clock/#seven-segment-digits","title":"Seven Segment Digits","text":"

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/neopixel/seven-segment-clock/#digit-pixel-map","title":"Digit Pixel Map","text":"

Assume that a digit starts at pixel n and that each segment has three pixels. To turn on the segments, here is the mapping:

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
"},{"location":"kits/neopixel/seven-segment-clock/#clock-code","title":"Clock Code","text":"

Here is some working Python code that displays the time on the clock.

```python from machine import Pin from neopixel import NeoPixel from utime import sleep, localtime

NUMBER_PIXELS = 74 strip = NeoPixel(Pin(0), NUMBER_PIXELS)

"},{"location":"kits/neopixel/seven-segment-clock/#define-segment-patterns-for-digits-0-9","title":"Define segment patterns for digits 0-9","text":""},{"location":"kits/neopixel/seven-segment-clock/#segments-in-order-abcdefg","title":"Segments in order [a,b,c,d,e,f,g]:","text":""},{"location":"kits/neopixel/seven-segment-clock/#a-top-012","title":"a: top (0,1,2)","text":""},{"location":"kits/neopixel/seven-segment-clock/#b-top-right-345","title":"b: top right (3,4,5)","text":""},{"location":"kits/neopixel/seven-segment-clock/#c-bottom-right-678","title":"c: bottom right (6,7,8)","text":""},{"location":"kits/neopixel/seven-segment-clock/#d-bottom-91011","title":"d: bottom (9,10,11)","text":""},{"location":"kits/neopixel/seven-segment-clock/#e-bottom-left-121314","title":"e: bottom left (12,13,14)","text":""},{"location":"kits/neopixel/seven-segment-clock/#f-upper-left-151617","title":"f: upper left (15,16,17)","text":""},{"location":"kits/neopixel/seven-segment-clock/#g-middle-181920","title":"g: middle (18,19,20)","text":"

DIGITS = { # a,b,c,d,e,f,g 0: [1,1,1,1,1,1,0], # all but middle 1: [0,1,1,0,0,0,0], # right side only 2: [1,1,0,1,1,0,1], # all but bottom right and upper left 3: [1,1,1,1,0,0,1], # all but left side 4: [0,1,1,0,0,1,1], # both right, upper left, and middle 5: [1,0,1,1,0,1,1], # all but top right and bottom left 6: [1,0,1,1,1,1,1], # all but top right 7: [1,1,1,0,0,0,0], # top and both right segments 8: [1,1,1,1,1,1,1], # all segments 9: [1,1,1,1,0,1,1] # all but bottom left }

"},{"location":"kits/neopixel/seven-segment-clock/#color-definitions-rgb","title":"Color definitions (RGB)","text":"

ON_COLOR = (10, 10, 10) # Dim white OFF_COLOR = (0, 0, 0) # Off

def set_segment_pixels(start_idx, segment_pattern): \"\"\"Set three pixels for a segment based on pattern\"\"\" for i in range(3): strip[start_idx + i] = ON_COLOR if segment_pattern else OFF_COLOR

def display_digit(digit, start_pixel): \"\"\"Display a single digit starting at the specified pixel\"\"\" if digit not in DIGITS: return

pattern = DIGITS[digit]\n# Map the array positions to the physical segments in correct order\nsegments = [\n    start_pixel + 0,   # a: top (0,1,2)\n    start_pixel + 3,   # b: top right (3,4,5)\n    start_pixel + 6,   # c: bottom right (6,7,8)\n    start_pixel + 9,   # d: bottom (9,10,11)\n    start_pixel + 12,  # e: bottom left (12,13,14)\n    start_pixel + 15,  # f: upper left (15,16,17)\n    start_pixel + 18   # g: middle (18,19,20)\n]\n\n# Set pixels for each segment based on pattern\nfor segment_index, segment_on in enumerate(pattern):\n    set_segment_pixels(segments[segment_index], segment_on)\n

def set_colon(on=True): \"\"\"Set colon pixels (42 and 43)\"\"\" color = ON_COLOR if on else OFF_COLOR strip[42] = color strip[43] = color

def display_time(hour, minute, colon_on): \"\"\"Display time on the LED strip\"\"\" # Convert 24-hour to 12-hour format hour = hour if hour <= 12 else hour - 12 if hour == 0: hour = 12

# Set hours \"1\" digit if needed (pixels 65-73)\nif hour >= 10:\n    for i in range(65, 74):\n        strip[i] = ON_COLOR\nelse:\n    for i in range(65, 74):\n        strip[i] = OFF_COLOR\n\n# Display ones digit of hours (pixels 44-64)\ndisplay_digit(hour % 10, 44)\n\n# Display tens digit of minutes (pixels 21-41)\ndisplay_digit(minute // 10, 21)\n\n# Display ones digit of minutes (pixels 0-20)\ndisplay_digit(minute % 10, 0)\n\n# Set colon (pixels 42-43)\nset_colon(colon_on)\n\nstrip.write()\n

def clear_display(): \"\"\"Turn off all pixels\"\"\" for i in range(NUMBER_PIXELS): strip[i] = OFF_COLOR strip.write()

"},{"location":"kits/neopixel/seven-segment-clock/#main-loop","title":"Main loop","text":"

colon_state = True clear_display()

while True: current_time = localtime() hour = current_time[3] minute = current_time[4] if hour > 12: display_hour = hour - 12

display_time(hour, minute, colon_state)\ncolon_state = not colon_state\nprint(f'{display_hour}:{minute}')\nsleep(1)\n

```

7-Segment Display Demo with P5.js

Moving Rainbow Arduino Sign

"},{"location":"kits/neopixel/seven-segment-clock/03-light-diffusion/","title":"NeoPixel Light Diffusion","text":"

To make a nice clock we need to design an effective diffusion system for NeoPixels that balances visibility and brightness.

For optimal diffusion while maintaining brightness, we recommend a multi-layer approach:

  1. Primary diffusion layer: Use a thin (1-3mm) sheet of white acrylic with a light frost or matte finish, positioned about 5-10mm away from the NeoPixels. This distance allows enough space for the light to blend while keeping individual pixels distinguishable.

  2. Air gap: The space between the NeoPixels and the diffusion layer is crucial. Too close and you'll see harsh spots; too far and you'll lose brightness. Start with 8mm and adjust based on your needs.

  3. Optional secondary diffusion: If you need more smoothing, add a very thin (0.5-1mm) layer of standard white diffusion film about 2-3mm in front of the primary layer.

Some specific materials to consider:

To maximize brightness while maintaining the pixel effect, you can also:

https://www.youtube.com/watch?v=Y2tWdKKYAak

"},{"location":"kits/neopixel/seven-segment-clock/assembly/","title":"Assembling Your Seven Segment NeoPixel Clock","text":""},{"location":"kits/neopixel/seven-segment-clock/assembly/#time-estimate","title":"Time Estimate","text":"

Total = 476 + 8 = 168 + 8 = 174 solder connections

Per connection (average times):

Total: 260 minutes or 4.3 hours

Total: 180

"},{"location":"kits/neopixel/seven-segment-clock/code/","title":"Code for the NeoPixel Seven Segment Clock","text":""},{"location":"kits/neopixel/seven-segment-clock/prompt/","title":"Sample Prompt to Generate NeoPixel Clock Code","text":""},{"location":"kits/neopixel/seven-segment-clock/prompt/#sample-prompt","title":"Sample Prompt","text":"

Prompt

We created a clock display using a 74 pixel WS2811B LED strip. The clock has three full digits and a \"1\" for the tens digit as well as two pixels for the colon. Each digit has seven segments with three pixels per segment. The right-most ones digit minutes digit starts at 0 and goes to 20. The second digit for the tens of minutes goes from 21 to 41. The colons are 42 and 43. The third digit is for the one hours and goes from 44 to 64. The hours ten single 1 goes from 65 to 73. Within a digit, here are the segment pixels Segment a on the top are pixels (0,1,2). Segment b on the upper right are pixels (3,4,5) Segment c on the lower right are pixels (6,7,8) Segment d on the bottom are pixels (9,10,11) Segment e on the lower left are pixels (12, 13, 14) Segment f on the upper left are pixels (15, 16, 17) Segment g in the middle are pixels (18,19 and 20)

Please write a clock program in MicroPython that displays the hours and minutes of localtime() function but the hours range from 1 to 12. Make the colon turn on and off every second.

Here is the preamble

from machine import Pin from neopixel import NeoPixel from utime import sleep

NUMBER_PIXELS = 74 strip = NeoPixel(Pin(0), NUMBER_PIXELS)

"},{"location":"kits/neopixel/seven-segment-clock/prompt/#result","title":"Result","text":"
from machine import Pin\nfrom neopixel import NeoPixel\nfrom utime import sleep, localtime\n\nNUMBER_PIXELS = 74\nstrip = NeoPixel(Pin(0), NUMBER_PIXELS)\n\n# Define segment patterns for digits 0-9\n# Segments in order [a,b,c,d,e,f,g]:\n# a: top (0,1,2)\n# b: top right (3,4,5)\n# c: bottom right (6,7,8)\n# d: bottom (9,10,11)\n# e: bottom left (12,13,14)\n# f: upper left (15,16,17)\n# g: middle (18,19,20)\nDIGITS = {\n    #    a,b,c,d,e,f,g\n    0: [1,1,1,1,1,1,0],  # all but middle\n    1: [0,1,1,0,0,0,0],  # right side only\n    2: [1,1,0,1,1,0,1],  # all but bottom right and upper left\n    3: [1,1,1,1,0,0,1],  # all but left side\n    4: [0,1,1,0,0,1,1],  # both right, upper left, and middle\n    5: [1,0,1,1,0,1,1],  # all but top right and bottom left\n    6: [1,0,1,1,1,1,1],  # all but top right\n    7: [1,1,1,0,0,0,0],  # top and both right segments\n    8: [1,1,1,1,1,1,1],  # all segments\n    9: [1,1,1,1,0,1,1]   # all but bottom left\n}\n\n# Color definitions (RGB)\nON_COLOR = (10, 10, 10)  # Dim white\nOFF_COLOR = (0, 0, 0)    # Off\n\ndef set_segment_pixels(start_idx, segment_pattern):\n    \"\"\"Set three pixels for a segment based on pattern\"\"\"\n    for i in range(3):\n        strip[start_idx + i] = ON_COLOR if segment_pattern else OFF_COLOR\n\ndef display_digit(digit, start_pixel):\n    \"\"\"Display a single digit starting at the specified pixel\"\"\"\n    if digit not in DIGITS:\n        return\n\n    pattern = DIGITS[digit]\n    # Map the array positions to the physical segments in correct order\n    segments = [\n        start_pixel + 0,   # a: top (0,1,2)\n        start_pixel + 3,   # b: top right (3,4,5)\n        start_pixel + 6,   # c: bottom right (6,7,8)\n        start_pixel + 9,   # d: bottom (9,10,11)\n        start_pixel + 12,  # e: bottom left (12,13,14)\n        start_pixel + 15,  # f: upper left (15,16,17)\n        start_pixel + 18   # g: middle (18,19,20)\n    ]\n\n    # Set pixels for each segment based on pattern\n    for segment_index, segment_on in enumerate(pattern):\n        set_segment_pixels(segments[segment_index], segment_on)\n\n    # Set pixels for each segment based on pattern\n    for segment_index, segment_on in enumerate(pattern):\n        set_segment_pixels(segments[segment_index], segment_on)\n\ndef set_colon(on=True):\n    \"\"\"Set colon pixels (42 and 43)\"\"\"\n    color = ON_COLOR if on else OFF_COLOR\n    strip[42] = color\n    strip[43] = color\n\ndef display_time(hour, minute, colon_on):\n    \"\"\"Display time on the LED strip\"\"\"\n    # Convert 24-hour to 12-hour format\n    hour = hour if hour <= 12 else hour - 12\n    if hour == 0:\n        hour = 12\n\n    # Set hours \"1\" digit if needed (pixels 65-73)\n    if hour >= 10:\n        for i in range(65, 74):\n            strip[i] = ON_COLOR\n    else:\n        for i in range(65, 74):\n            strip[i] = OFF_COLOR\n\n    # Display ones digit of hours (pixels 44-64)\n    display_digit(hour % 10, 44)\n\n    # Display tens digit of minutes (pixels 21-41)\n    display_digit(minute // 10, 21)\n\n    # Display ones digit of minutes (pixels 0-20)\n    display_digit(minute % 10, 0)\n\n    # Set colon (pixels 42-43)\n    set_colon(colon_on)\n\n    strip.write()\n\ndef clear_display():\n    \"\"\"Turn off all pixels\"\"\"\n    for i in range(NUMBER_PIXELS):\n        strip[i] = OFF_COLOR\n    strip.write()\n\n# Main loop\ncolon_state = True\nclear_display()\n\nwhile True:\n    current_time = localtime()\n    hour = current_time[3]\n    minute = current_time[4]\n    if hour > 12:\n        display_hour = hour - 12   \n    display_time(hour, minute, colon_state)\n    colon_state = not colon_state\n    print(f'{display_hour}:{minute}')\n    sleep(1)\n
"},{"location":"kits/oled-large/","title":"Large OLED Kit","text":"

The Large OLED Kit is one of our most versatile kits.

It uses a bright monochrome 128x64 SSD1306 OLED with the Raspberry Pi Pico W and a DS3231 real-time clock to keep time accurate to within 2 seconds per month.

The OLEDs are mounted on a piece of acrylic (Plexiglass) with a half-size breadboard.

Each board has three buttons so you can adjust the time and alarm clock settings manually if you are not getting the time from your WiFi network.

"},{"location":"kits/oled-large/#component-list","title":"Component List","text":""},{"location":"kits/oled-large/#kit-assembly","title":"Kit Assembly","text":"

If you have received a clock kit you can proceed directly to the Assembly Instructions.

Once your kit is assembled you can the start to program your clock.

"},{"location":"kits/oled-large/#programming","title":"Programming","text":""},{"location":"kits/oled-large/#step-1-setting-up-your-desktop-and-loading-micropython","title":"Step 1: Setting up your Desktop and Loading MicroPython","text":"

We cover the desktop setup and loading MicroPython in our Desktop Setup

"},{"location":"kits/oled-large/#blink-the-builtin-led","title":"Blink the Builtin LED","text":"

Go to the Blink Lab

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

Your first program is our \"Hello World!\" program for this kit. This is a great way to tell if your display connections are all working. We have one version that prints to the Thonny console and one version that prints to the OLED display.

Go to the Hello World! Program

"},{"location":"kits/oled-large/#display-config","title":"Display Config","text":"

Most of our sample programs read the hardware settings from a configuration file called config.py. This program shows you how to show the configuration parameters on the display.

Go to the Display Config Lab

"},{"location":"kits/oled-large/#display-raw-localtime","title":"Display Raw Localtime","text":"

Date and time information is stored in a set of 6-8 integers. This first program just shows the raw integers on the display from the localtime() function. Not very pretty, but the program is pretty short and simple.

Go to the Display Localtime Raw

"},{"location":"kits/oled-large/#set-the-rtc-from-localtime","title":"Set the RTC from Localtime","text":"

Once you add your RTC to the kit you will need to set the time inside the RTC memory. The best way to do this is to just grab the time from the MicroPython localtime() which is set from your local computer when you connect your Pico using Thonny.

Set the RTC from Localtime

After you run this program your clock will be accurate to within two seconds per month. The backup lithium battery will remember the time even when the clock is not plugged in or there is a power outage. The battery will last about 8 years.

"},{"location":"kits/oled-large/#display-single-large-digit","title":"Display Single Large Digit","text":"

Display Single Digit

"},{"location":"kits/oled-large/00-blink/","title":"Blink The Onboard LED","text":"

Our first program will just blink the on-board LED on the Raspberry Pi Pico. It is a great way to tell if you got Thonny installed correctly and that your computer can connect to the Raspberry Pi Pico. Use the \"copy\" icon in the upper right corner to copy the code below into your pastebuffer. Then open Thonny and paste it into a new file in your Thonny editor (or other Python programming tool). Then press the \"Run\" button. You should see the small green LED on the Pico flashing on and off every second.

# Setup - run once\nfrom machine import Pin # Get the Pin function from the machine module.\nfrom time import sleep # Get the sleep library from the time module.\n\n# This is the built-in green LED on the standard Pico.\n# BUILT_IN_LED_PIN = 25\n# On the Pico \"W\" we use the symbolic \"name\" of \"LED\", not a pin number\nBUILT_IN_LED_PIN = Pin(\"LED\", Pin.OUT)\n\n# The line below indicates we are configuring this as an output (not input)\nled = Pin(BUILT_IN_LED_PIN, Pin.OUT)\n\n# Main loop: Repeat the forever...\nwhile True:\n    led.high() # turn on the LED\n    sleep(0.5) # leave it on for 1/2 second\n    led.low()  # Turn off the LED\n    sleep(0.5) # leave it off for 1/2 second\n

When you run this program, the built-in LED on the Pico will blink every second.

Challenges

  1. Change the delay time to be 0.25 seconds on and 0.25 second off.
  2. How fast can you make the LED blink?
  3. Go to the original MicroPython for Kids website and search for the term \"blink\" in the search form in the upper right. What variations of blink are there in other kits?
  4. If you have any LEDs and 330 ohm resistors, try adding them to the breadboard and getting the external LED to blink. See this lab for sample code.
"},{"location":"kits/oled-large/00-blink/#debugging-tips","title":"Debugging Tips.","text":"

If you are not getting any response when you click the \"Run\" button, try checking that your desktop computer is communicating with the Raspberry Pi Pico through the USB cable.

See the Setup Desktop Debugging Tips page for tips on how to make sure your connection is working.

"},{"location":"kits/oled-large/01-assembly/","title":"Large OLED Assembly","text":"

Here are your assembly instructions for the Large OLED clock kit. This kit comes with a Raspberry Pi W, a nice large 2.24\" OLED display and a DS3132 real-time clock. You can also optionally mount a speaker for an alarm clock. Time can be set using three buttons (mode-set, increment (+) and decrement (-))

"},{"location":"kits/oled-large/01-assembly/#kit-parts-list","title":"Kit Parts List","text":"
  1. Plexiglass base with holes in front
  2. 4 felt pads for the feet
  3. Breadboard - 1/2 size - 30 rows - 400 ties
  4. Raspberry Pi Pico W with header pins installed
  5. OLED display (2.42\" 128X64)
  6. 7 wire display cable (20cm M-F)
  7. Real time clock (DS3132)
  8. CR2032 coin-cell battery
  9. 4-wire real-time clock cable (20cm M-F)
  10. 3 momentary push buttons - long for set, short for +/-
  11. 5 black ground wires for buttons
  12. 3 button wires (yellow, blue and green)
  13. 1 red power jumper (3.3 to bus)
  14. 3 cable ties
  15. USB connector (A to micro)
  16. 5-volt USB power adapter
"},{"location":"kits/oled-large/01-assembly/#assembly-steps","title":"Assembly Steps","text":""},{"location":"kits/oled-large/01-assembly/#prepare-plexiglass-base","title":"Prepare Plexiglass Base","text":"

Peal the protective backing off of both sides of the plexiglass base. You might need to use your fingernails or a knife to peal up the corner.

"},{"location":"kits/oled-large/01-assembly/#attach-felt-pads-to-base","title":"Attach Felt Pads to Base","text":"

Attach the 4 felt pads to the corners of the base by pealing the release liner off each pad and placing each one in each corner. If you are using cable ties on the display, make sure you leave room for the cable ties and positions the felt pads in about 1/4 of an inch from the side edges.

This will be the bottom of your clock.

"},{"location":"kits/oled-large/01-assembly/#attach-breadboard-to-base","title":"Attach Breadboard to Base","text":"

Peal off the release liner for the solderless 1/2 breadboard and place it in the center of the top base with row 1 on the right and row 30 on the left as you face from the front of the clock. Note that the top of the breadboard is row 1 and the bottom is row 30.

"},{"location":"kits/oled-large/01-assembly/#install-pico-w-on-breadboard","title":"Install Pico W on Breadboard","text":"

Put the Raspberry Pi Pico W into the breadboard making sure that USB connector is at the top of the breadboard (row 1) and the pins are aligned like the diagram. The header pins on the Raspberry Pi Pico W will be in columns \"c\" and \"h\".

"},{"location":"kits/oled-large/01-assembly/#33-v-red-bus-jumper","title":"3.3 V Red Bus Jumper","text":"

Use the red jumper wire to connect right row 5 (3.3 OUT) to the red power bus on the right. Both the OLED display and the real-time clock will connect their power to this right power rail.

"},{"location":"kits/oled-large/01-assembly/#black-ground-jumper","title":"Black Ground Jumper","text":"

Attach the black jumper wire from row 8 on the right to the blue bus GND. Note all rows that end in the digit 3 or 8 are GND (3,8,13,18). Your breadboard may have a black mark on the breadboard on these rows. This connection allows the entire left power rail to be GND.

"},{"location":"kits/oled-large/01-assembly/#add-buttons","title":"Add Buttons","text":"

Place the three buttons on the breadboard over the center trough oriented so that the legs of the buttons are on opposite sides of the center trough and closing the switch will close the connection between the rows of the breadboard when pressed.

The internal connections of the buttons is shown below:

"},{"location":"kits/oled-large/01-assembly/#wire-buttons","title":"Wire Buttons","text":"

We are now ready to wire the three buttons up. Their job is to connect the pins on the lower left corner of the Pico W to GND when they are pressed. Each button has GND on the lower-left pin and signal on the upper-left corner of the button. The signal of the top button will go to row 17, the middle button to row 19 and the bottom button will go to row 20.

A logical diagram of the button circuit is shown below:

Here are the pin assignments will use in later labs:

Breadboard Row GPIO Pin Function Row 17 GPIO 13 Set (Mode) Row 19 GPIO 14 Increment Row 20 GPIO 15 Decrement

Note

If you have your software loaded, you can run the 'buttons-test-print.py' to verify that the buttons are all connected correctly. This program will print the name of the button being pressed to the console. This is a great way to tell if you have missed any button connections.

"},{"location":"kits/oled-large/01-assembly/#display-cable-to-oled","title":"Display Cable to OLED","text":"

Locate the 7-wire display cable harness. It has female pins on one end and make pins on the other end. The female pins will go into the OLED display. Note that you must hoop up the black wire to the GND connection and the purple wire to the CS connection. It is very easy to get this connection reversed, so double check the orientation.

"},{"location":"kits/oled-large/01-assembly/#display-cable-to-breadboard","title":"Display Cable to Breadboard","text":"

Attached the display cable to the breadboard in the left side from rows 3 to 9. The orange wire should be in row 4, yellow in 5, green in 6, blue in 7, black in 8 and purple in 9.

Next, connect the red wire to the right side red power rail (3.3 volts)

Double check that the black wire is on the GND pin (row 8). Make sure that the red wire is attached to right red positive power rail.

"},{"location":"kits/oled-large/01-assembly/#install-the-coin-cell-battery-in-the-rtc","title":"Install the Coin Cell Battery in the RTC","text":"

Place the CR2032 coin cell battery in the real-time clock (RTC) making sure the positive side is on the top. The coin cell battery needs to be tucked under the metal connector and pressed down. You may need a small screw driver to pry the batter back up to change it. Most

"},{"location":"kits/oled-large/01-assembly/#rtc-cable-to-rtc","title":"RTC Cable to RTC","text":"

Attach the 4-wire cable to there real time clock making sure that the black GND and the red VCC are connect to the correct pins, SDA is yellow and SCL is orange. The SQW and 32K pins are not used.

"},{"location":"kits/oled-large/01-assembly/#rtc-cable-to-breadboard","title":"RTC Cable to Breadboard","text":"

Attach the other of the 4-wire real-time clock cable to the breadboard. Connect yellow to row 1 on the left (GPIO 0) and orange on row 2 on the left (GPIO 1). Connect the red wire to the red power rail on the right and the black wire to the GND on the right.

"},{"location":"kits/oled-large/01-assembly/#cable-ties","title":"Cable Ties","text":"

(Optional) You can use cable ties or twist ties to connect the display and the RTC to the base through the holes. If you are planning to build your own case you can skip this step.

"},{"location":"kits/oled-large/01-assembly/#final-tests","title":"Final Tests","text":"

After you assemble your clock kit you will be abel to test the connections using MicroPython. Here are some of the tests you will run:

  1. Use the blink to verify that your USB connection is not blocked by your PC/Mac and you have downloaded the correct version of the MicroPython runtime.

  2. Use the i2c scanner to verify that your real-time clock is connected correctly.

  3. Use the hello world program to test if your display is connected correctly.

  4. Use the button test program to test to see if your buttons are connected correctly.

"},{"location":"kits/oled-large/01-assembly/#troubleshooting","title":"Troubleshooting","text":"

You can use generative AI tool like ChatGPT or Anthropic Claude to help you debug the clock. Make sure you give a complete description of your problem using the following prompt template.

Prompt

Please help me diagnose a problem I am having with MicroPython program running on the Raspberry Pi Pico W. I am using a Mac to connect to the Pico with Thonny. I am using Thonny version 4.1.6 and Mac OS 15.0.1. When I run ls /dev/cu* I can see the device listed. When I press the Stop/Restart backend button I see the MicroPython version X on the console.

However, when I run X I get the following error.

"},{"location":"kits/oled-large/01-hello-world/","title":"Hello World","text":""},{"location":"kits/oled-large/01-hello-world/#displaying-hello-world-on-your-console","title":"Displaying \"Hello World!\" on your console","text":"

Here is the classic \"Hello World\" in MicroPython

print(\"Hello World!\")\n

You can type this program into Thonny and run it to see if your USB is connected and the MicroPython runtime is loaded correctly.

"},{"location":"kits/oled-large/01-hello-world/#displaying-hello-word-on-your-display","title":"Displaying Hello Word On your Display","text":"

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

Here is the code:

import machine\nimport ssd1306\n\n# customise these numbers if your hardware config is different\nSCL=machine.Pin(2) # SPI CLock\nSDA=machine.Pin(3) # SPI Data\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 (0=black)\noled.fill(0)\n\n# place a hello message at point (0,0) in white (1=white text)\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/oled-large/02-display-config/","title":"Display Configuration File Data","text":"

This program will display the configuration file (config.py) parameters on your display.

from machine import Pin\nimport ssd1306\nimport config\n\nSCL=Pin(config.SPI_SCL_PIN)\nSDA=Pin(config.SPI_SDA_PIN)\nDC = Pin(config.SPI_DC_PIN)\nRES = Pin(config.SPI_RESET_PIN)\nCS = Pin(config.SPI_CS_PIN)\nSPI_BUS = config.SPI_BUS\nWIDTH = config.DISPLAY_WIDTH\nHEIGHT = config.DISPLAY_HEIGHT\n\nspi=machine.SPI(SPI_BUS, sck=SCL, mosi=SDA, baudrate=1000000)\noled = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)\n\n# place a hello message at point (0,0) in white\noled.text(\"Config:\", 0, 0, 1)\n\n# row 2\noled.text(\"SCL:\" + str(config.SPI_SCL_PIN), 0, 10, 1)\noled.text(\"SDA:\" + str(config.SPI_SDA_PIN), 50, 10, 1)\n\n# row 3\noled.text(\"DC:\" + str(config.SPI_DC_PIN),     0, 20, 1)\noled.text(\"RES:\" + str(config.SPI_RESET_PIN), 50, 20, 1)\n\n# row 4\noled.text(\"CS:\" + str(config.SPI_CS_PIN), 0, 30, 1)\noled.text(\"SPI BUS:\" + str(config.SPI_BUS), 50, 30, 1)\n\n# row 5\noled.text(\"Width:\" + str(config.DISPLAY_WIDTH), 0, 40, 1)\noled.text(\"Height:\" + str(config.DISPLAY_HEIGHT), 0, 50, 1)\n\noled.show()\n
"},{"location":"kits/oled-large/02-i2c-scanner/","title":"I2C Scanner Test","text":"

This program will verify that your RTC clock is connected correctly to your Raspberry Pi Pico.

from machine import I2C, Pin\nimport config\n\nDST_PIN = 0\nCLK_PIN = 1\n\nprint(\"Data DST on pin: \", DST_PIN)\nprint(\"Clock CLK on pin: \", CLK_PIN)\n\ni2c = I2C(0, scl=Pin(CLK_PIN), sda=Pin(DST_PIN), freq=800000)\n\nscan_result = i2c.scan()\n\nprint(scan_result)\n\nprint(\"I2C addresses found:\", [hex(device_address) for device_address in i2c.scan()])\n

This will return a list of the I2C addresses found on your I2C Bus 0. If your DS3231 real-time clock has an address of 0x68 (hex) which is 104 decimal.

You will also see a second I2C address for the EEPROM which is 0x50 (hex) and 80 decimal. These numbers may vary if you purchased different versions of a real-time clock.

For details see the section on Real Time Clocks.

"},{"location":"kits/oled-large/02-set-rtc-from-localtime/","title":"Set RTC form the localtime","text":"

When you plug your Pico into your PC and use the Thonny IDE, the MicroPython runtime will startup and get the time from your desktop or laptop computer's clock and set the MicroPython runtimes localtime() with this time.

Note

You can disable the Thonny localtime if you want to test the RTC functions.

"},{"location":"kits/oled-large/02-set-rtc-from-localtime/#setting-the-rtc-using-the-ds3231","title":"Setting The RTC using the DS3231","text":"

This is a full raw way to set the DS3231 RTC from the MicroPython localtime. You should only need to run this program when you first connect your RTC chip. After you run it the clock should be accurate within 2-seconds per month. In many cases when the clock is at room temperature the accuracy will be higher.

from machine import Pin, I2C\nfrom utime import localtime\n\n# Constants\nDS3231_ADDR = 0x68\nSTATUS_REG = 0x0F  # Status register address\n\n# I2C setup\nsda = Pin(0)\nscl = Pin(1)\ni2c = I2C(0, scl=scl, sda=sda, freq=100000)\n\ndef dec2bcd(dec):\n    \"\"\"Convert decimal to binary coded decimal.\"\"\"\n    return (dec // 10) << 4 | (dec % 10)\n\ndef check_osf():\n    \"\"\"Check the oscillator stop flag.\"\"\"\n    status = i2c.readfrom_mem(DS3231_ADDR, STATUS_REG, 1)[0]\n    return bool(status >> 7)\n\ndef reset_osf():\n    \"\"\"Clear the oscillator stop flag.\"\"\"\n    status = bytearray(1)\n    i2c.readfrom_mem_into(DS3231_ADDR, STATUS_REG, status)\n    i2c.writeto_mem(DS3231_ADDR, STATUS_REG, bytearray([status[0] & 0x7f]))\n\ndef set_ds3231():\n    \"\"\"Set the DS3231 RTC time and ensure oscillator is running.\"\"\"\n    now = localtime()\n    year = now[0] % 100  # Convert to 2-digit year\n    month = now[1]\n    day = now[2]\n    hour = now[3]\n    minute = now[4]\n    second = now[5]\n\n    # First check if oscillator is stopped\n    if check_osf():\n        print(\"Oscillator was stopped. Resetting OSF flag...\")\n        reset_osf()\n\n    data = bytearray([\n        dec2bcd(second),\n        dec2bcd(minute),\n        dec2bcd(hour),\n        dec2bcd(now[6] + 1),  # Convert weekday from 0-6 to 1-7\n        dec2bcd(day),\n        dec2bcd(month),\n        dec2bcd(year)\n    ])\n\n    i2c.writeto_mem(DS3231_ADDR, 0x00, data)\n    print(f\"RTC set to: {month}/{day}/{now[0]} {hour:02d}:{minute:02d}:{second:02d}\")\n\n    # Verify oscillator is running\n    if check_osf():\n        print(\"Warning: Oscillator still shows stopped state!\")\n    else:\n        print(\"Oscillator running normally\")\n\nif __name__ == \"__main__\":\n    set_ds3231()\n
"},{"location":"kits/oled-large/02-set-rtc-from-localtime/#setting-the-rtc-using-the-ds1307","title":"Setting the RTC using the DS1307","text":"
# set the localtime from the DS1307 RTC\nfrom machine import Pin, I2C\nfrom utime import localtime\nfrom ds1307 import DS1307\nimport config\n\nsda = Pin(config.I2C_SDA_PIN)\nscl = Pin(config.I2C_SCL_PIN)\nI2C_BUS = config.I2C_BUS\nRTC_TYPE = config.RTC_TYPE\nRTC_I2C_ADDR = config.RTC_I2C_ADDR\n\n# I2C setup\ni2c = I2C(I2C_BUS, scl=scl, sda=sda, freq=3000000)\nprint(i2c.scan())\nrtc = DS1307(addr=RTC_I2C_ADDR, i2c=i2c)\nprint(\"DS1307 is on I2C address 0x{0:02x}\".format(rtc.addr))\nprint(\"Before setting the time the RTC clock had: \", rtc.datetime)\nprint(\"Localtime: \", localtime())\n\n# Set the local time\nrtc.datetime = localtime()\n\nprint(\"After setting the time from local time the RTC had: \", rtc.datetime)\n\n# Print the date and time in ISO8601 format: 2023-04-18T21:14:22\nprint(\"Today is {:04d}-{:02d}-{:02d}T{:02d}:{:02d}:{:02d} weekday:{}\".format(\n    rtc.year, rtc.month, rtc.day,\n    rtc.hour, rtc.minute, rtc.second, rtc.weekday))\n
"},{"location":"kits/oled-large/03-display-localtime-raw/","title":"Display the Raw Localtime","text":"
from machine import Pin, SPI\nimport ssd1306\nfrom time import localtime, sleep\nimport config\n\n# get the configuration data\nSCL=Pin(config.SPI_SCL_PIN)\nSDA=Pin(config.SPI_SDA_PIN)\nDC = Pin(config.SPI_DC_PIN)\nRES = Pin(config.SPI_RESET_PIN)\nCS = Pin(config.SPI_CS_PIN)\nSPI_BUS = config.SPI_BUS\nWIDTH = config.DISPLAY_WIDTH\nHEIGHT = config.DISPLAY_HEIGHT\n\nspi=machine.SPI(SPI_BUS, sck=SCL, mosi=SDA, baudrate=1000000)\noled = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)\n\nwhile True:\n    # display the raw data from localtime\n    oled.fill(0)\n    oled.text(str(localtime()[0]), 0, 0, 1)\n    oled.text(str(localtime()[1]), 0, 10, 1)\n    oled.text(str(localtime()[2]), 0, 20, 1)\n    oled.text(str(localtime()[3]), 0, 30, 1)\n    oled.text(str(localtime()[4]), 0, 40, 1)\n    oled.text(str(localtime()[5]), 0, 50, 1)\n    oled.show()\n    sleep(1)\n

Challenges

  1. What numbers are fixed and what numbers arc chainging?
  2. What do you think each of the numbers mean?
  3. Why is it hard to read the time using this \"raw\" unformatted way?
"},{"location":"kits/oled-large/04-display-localtime-formatted/","title":"Display Local Time Formatted","text":"

That last program did display the correct local time, but it was hard to read.

In this version we will extract data time values, and then display a formatted version of both the date and the time.

from machine import Pin, SPI\nimport ssd1306\nfrom time import localtime\nimport config\n\nSCL=Pin(config.SPI_SCL_PIN)\nSDA=Pin(config.SPI_SDA_PIN)\nDC = Pin(config.SPI_DC_PIN)\nRES = Pin(config.SPI_RESET_PIN)\nCS = Pin(config.SPI_CS_PIN)\nSPI_BUS = config.SPI_BUS\nWIDTH = config.DISPLAY_WIDTH\nHEIGHT = config.DISPLAY_HEIGHT\n\nspi=machine.SPI(SPI_BUS, sck=SCL, mosi=SDA, baudrate=1000000)\noled = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)\n\nwhile True:\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 in 12 hour time paying special attention with hour == 0 and hour == 12\n    if hour == 0:\n        # Midnight edge case\n        hour = 12\n        am_pm = 'AM'\n    elif hour == 12:\n        # Noon edge case\n        am_pm = 'PM'\n    elif hour > 12:\n        # Afternoon hours\n        hour -= 12\n        am_pm = 'PM'\n    else:\n        # Morning hours\n        am_pm = 'AM'\n\n    oled.fill(0)\n    # display the date on the first line\n    oled.text(str(month) + \"/\" + str(day) + \"/\" + str(year), 0, 0, 1)\n\n    # display the time in hours, minute and seconds on the second line\n    # note that the \":02\" indicates printing in two columns with leading zeros\n    oled.text(f\"{hour}:{minute:02}:{second:02} \" + am_pm, 0, 10, 1)\n\n    oled.show()\n
"},{"location":"kits/oled-large/04-display-localtime-formatted/#time-formatting","title":"Time formatting","text":"

When we print out minutes and seconds we want to make sure that we print them in two digits with a leading zero.

f\"{minute:02}:{second:02}\"\n

The python modulo operator % is used to find the remainder of the hour after dividing by 12. This converts the 24-hour time to 12-hour time.

"},{"location":"kits/oled-large/05-button-test/","title":"Testing Your Buttons","text":"

The diagram above shows three buttons below the Pico that will connect GPIO pins 13, 14 and 15 to GND when they are pressed. When they are not pressed the signals on 13, 14 and 15 must be \"pulled up\" to 5 volts using the internal pull-up resistor within the Pico. We configure this when we setup the code.

"},{"location":"kits/oled-large/05-button-test/#sample-code","title":"Sample Code","text":""},{"location":"kits/oled-large/05-button-test/#single-button-test","title":"Single Button Test","text":"
from utime import sleep\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# Buttons\nBUTTON_1_PIN = 13\n\nbutton_1_pin = Pin(BUTTON_1_PIN, Pin.IN, Pin.PULL_UP)\n\n# This function gets called every time the button is pressed.  The parameter \"pin\" is not used.\ndef button_1_pressed(pin):\n    print('Button 1 pressed')\n\n# now we register the button handler function using the irq setter method for this pin\nbutton_1_pin.irq(trigger=Pin.IRQ_FALLING, handler = button_1_pressed)\n\nwhile True:\n    sleep(1)\n
"},{"location":"kits/oled-large/05-button-test/#three-button-test","title":"Three Button Test","text":"
from utime import sleep\nfrom machine import Pin\n\n# Sample Raspberry Pi Pico MicroPython three button press example\n\n# Button Pin GPIO Configuration\nBUTTON_1_PIN = 13\nBUTTON_2_PIN = 14\nBUTTON_3_PIN = 15\n\n# Create 3 button objects as inputs with pullup resistors\nbutton_1_pin = Pin(BUTTON_1_PIN, Pin.IN, Pin.PULL_UP)\nbutton_2_pin = Pin(BUTTON_2_PIN, Pin.IN, Pin.PULL_UP)\nbutton_3_pin = Pin(BUTTON_3_PIN, Pin.IN, Pin.PULL_UP)\n\n# These functions gets called every time a button is pressed.\ndef button_1_pressed(pin):\n    print('Button 1 pressed')\ndef button_2_pressed(pin):\n    print('Button 2 pressed')\ndef button_3_pressed(pin):\n    print('Button 3 pressed')\n\n# now we register the button handler functions using the irq setter method for each pin\nbutton_1_pin.irq(trigger=Pin.IRQ_FALLING, handler = button_1_pressed)\nbutton_2_pin.irq(trigger=Pin.IRQ_FALLING, handler = button_2_pressed)\nbutton_3_pin.irq(trigger=Pin.IRQ_FALLING, handler = button_3_pressed)\n\n# we don't do anything in the main loop but sleep\nwhile True:\n    sleep(1)\n
"},{"location":"kits/oled-large/05-rtc/","title":"Working with the DS3232 Real Time Clock","text":""},{"location":"kits/oled-large/05-rtc/#printing-the-raw-time-data","title":"Printing The Raw Time Data","text":"
from machine import Pin, I2C\nimport ds3231\n\n# it is more portable to read the pins from the config.py file\ni2c = I2C(0, sda=Pin(0), scl=Pin(1))\nrtc = ds3231.DS3231(i2c)\n\nprint(rtc.datetime())\n

Result:

(2025, 1, 31, 5, 11, 18, 13, 0)\n

Note that this is an 8-value tuple format, not the standard 7 values.

"},{"location":"kits/oled-large/05-rtc/#using-the-hardware-config-file","title":"Using the Hardware Config File","text":"
from machine import Pin, I2C\nimport ds3231\nimport config\n\ni2c = I2C(0, sda=Pin(config.I2C_SDA_PIN), scl=Pin(config.I2C_SCL_PIN))\nrtc = ds3231.DS3231(i2c)\n\nprint(rtc.datetime())\n
"},{"location":"kits/oled-large/05-rtc/#formatting-time-in-iso-format","title":"Formatting Time in ISO format","text":"
from machine import Pin, I2C\nimport ds3231\nimport config\n\ni2c = I2C(0, sda=Pin(config.I2C_SDA_PIN), scl=Pin(config.I2C_SCL_PIN))\nrtc = ds3231.DS3231(i2c)\n\ndef format_time(datetime_tuple):\n    # Unpack the 8-value tuple\n    year, month, day, hour, minute, second, subseconds, weekday = datetime_tuple\n\n    # Convert to 12-hour format\n    am_pm = \"AM\" if hour < 12 else \"PM\"\n    hour_12 = hour if hour <= 12 else hour - 12\n    if hour_12 == 0:\n        hour_12 = 12\n\n    # Create day and month lookup tables\n    days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']\n    months = ['January', 'February', 'March', 'April', 'May', 'June', \n              'July', 'August', 'September', 'October', 'November', 'December']\n\n    # Use 'd' instead of '02d' for hours to remove leading zero\n    return f\"{days[weekday]}, {months[month-1]} {day}, {year} {hour_12}:{minute:02d}:{second:02d} {am_pm}\"\n\ndatetime = rtc.datetime()\nprint(format_time(datetime))\n
"},{"location":"kits/oled-large/05-rtc/#setting-the-ds3232-trc-from-localtime","title":"Setting the DS3232 TRC from Localtime","text":"

Prompt

Please write a detailed step-by-step explanation of the set-ds3231-rtc-from-localtime.py program. Assume your reader is a junior high student that is just learning Python for the first time.

I'll explain how this program works step by step, focusing on what each part does in simple terms.

"},{"location":"kits/oled-large/05-rtc/#1-setting-up-the-rtc-real-time-clock","title":"1. Setting Up the RTC (Real-Time Clock):","text":"

from machine import Pin, I2C\nimport ds3231\nimport config\nfrom time import localtime\n\ni2c = I2C(config.I2C_BUS, sda=Pin(config.I2C_SDA_PIN), scl=Pin(config.I2C_SCL_PIN))\nrtc = ds3231.DS3231(i2c)\n
This part connects to a special clock chip called DS3231 using two wires (I2C communication). Think of it like plugging in a digital clock.

"},{"location":"kits/oled-large/05-rtc/#2-the-format_time-function","title":"2. The format_time Function:","text":"

def format_time(datetime_tuple):\n    year, month, day, weekday, hour, minute, second, subseconds = datetime_tuple\n
This function takes time information and makes it look nice for humans to read. For example, instead of showing \"1\" for January, it shows \"January\".

The function converts 24-hour time to 12-hour time with AM/PM:

am_pm = \"AM\" if hour < 12 else \"PM\"\nhour_12 = hour if hour <= 12 else hour - 12\nif hour_12 == 0:\n    hour_12 = 12\n
So 13:00 becomes 1:00 PM.

"},{"location":"kits/oled-large/05-rtc/#3-the-verify_time-function","title":"3. The verify_time Function:","text":"

def verify_time(local_time):\n    l_year, l_month, l_day, l_hour, l_min, l_sec, l_wday, l_yday = local_time\n
This function is like a safety check. It makes sure the time makes sense: - Years must be between 2000 and 2099 - Months must be between 1 and 12 - Hours must be between 0 and 23 - And so on

"},{"location":"kits/oled-large/05-rtc/#4-setting-the-time","title":"4. Setting the Time:","text":"

local = localtime()\nprint(\"\\nSystem local time:\")\nprint(f\"Year: {local[0]}, Month: {local[1]}, Day: {local[2]}\")\n
The program gets the current time from your computer or microcontroller and shows it to you.

"},{"location":"kits/oled-large/05-rtc/#5-final-step-updating-the-rtc","title":"5. Final Step - Updating the RTC:","text":"
if verify_time(local):\n    rtc_weekday = local[6] + 1\n    new_time = (\n        local[0],     # year\n        local[1],     # month\n        local[2],     # day\n        local[3],     # hour\n        local[4],     # minute\n        local[5],     # second\n        rtc_weekday   # weekday (1-7, Monday=1)\n    )\n    rtc.datetime(new_time)\n

If all the safety checks pass, it updates the RTC chip with the new time. The program then shows you the new time to confirm it worked.

This program is like setting a digital clock, but instead of pressing buttons, it automatically sets the time using your computer's time. The RTC chip will keep tracking time even if you unplug your project, just like how a digital clock keeps time even when unplugged (it uses a small battery).

"},{"location":"kits/oled-large/05-rtc/#full-program-listing","title":"Full Program Listing","text":"
from machine import Pin, I2C\nimport ds3231\nimport config\nfrom time import localtime\n\n# Initialize I2C and RTC\ni2c = I2C(config.I2C_BUS, sda=Pin(config.I2C_SDA_PIN), scl=Pin(config.I2C_SCL_PIN))\nrtc = ds3231.DS3231(i2c)\n\ndef format_time(datetime_tuple):\n    # Unpack the 8-value tuple returned by rtc.datetime()\n    # Format is: year, month, day, weekday, hour, minutes, seconds, subseconds\n    year, month, day, weekday, hour, minute, second, subseconds = datetime_tuple\n\n    # Convert to 12-hour format\n    am_pm = \"AM\" if hour < 12 else \"PM\"\n    hour_12 = hour if hour <= 12 else hour - 12\n    if hour_12 == 0:\n        hour_12 = 12\n\n    # Create day and month lookup tables\n    # weekday in RTC is 1-7 (Monday=1, Sunday=7)\n    days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']\n    months = ['January', 'February', 'March', 'April', 'May', 'June', \n              'July', 'August', 'September', 'October', 'November', 'December']\n\n    # Adjust weekday to 0-based index for days list\n    weekday_index = weekday - 1\n\n    return f\"{days[weekday_index]}, {months[month-1]} {day}, {year} {hour_12}:{minute:02d}:{second:02d} {am_pm}\"\n\ndef verify_time(local_time):\n    \"\"\"\n    Verify that local_time values are within acceptable bounds\n    Returns: True if time appears valid, False otherwise\n    \"\"\"\n    # Unpack tuple\n    l_year, l_month, l_day, l_hour, l_min, l_sec, l_wday, l_yday = local_time\n\n    # Basic bounds checking\n    if not (2000 <= l_year <= 2099):\n        print(\"Error: Year out of range (2000-2099)\")\n        return False\n    if not (1 <= l_month <= 12):\n        print(\"Error: Month out of range (1-12)\")\n        return False\n    if not (1 <= l_day <= 31):\n        print(\"Error: Day out of range (1-31)\")\n        return False\n    if not (0 <= l_hour <= 23):\n        print(\"Error: Hour out of range (0-23)\")\n        return False\n    if not (0 <= l_min <= 59):\n        print(\"Error: Minute out of range (0-59)\")\n        return False\n    if not (0 <= l_sec <= 59):\n        print(\"Error: Second out of range (0-59)\")\n        return False\n\n    return True\n\n# Get current RTC time and print it\nprint(\"\\nCurrent RTC time before setting:\")\ncurrent_rtc_time = rtc.datetime()\nprint(format_time(current_rtc_time))\n\n# Get local time\nlocal = localtime()\nprint(\"\\nSystem local time:\")\nprint(f\"Year: {local[0]}, Month: {local[1]}, Day: {local[2]}\")\nprint(f\"Hour: {local[3]}, Minute: {local[4]}, Second: {local[5]}\")\nprint(f\"Weekday: {local[6]} (0=Monday, 6=Sunday)\")\n\n# Verify the time data\nif verify_time(local):\n    # Convert weekday from localtime (0-6, Monday=0) to RTC format (1-7, Monday=1)\n    rtc_weekday = local[6] + 1\n\n    # Create datetime tuple in correct order for rtc.datetime()\n    # According to the driver docstring:\n    # datetime : tuple, (0-year, 1-month, 2-day, 3-hour, 4-minutes[, 5-seconds[, 6-weekday]])\n    new_time = (\n        local[0],     # year\n        local[1],     # month\n        local[2],     # day\n        local[3],     # hour\n        local[4],     # minute\n        local[5],     # second\n        rtc_weekday   # weekday (1-7, Monday=1)\n    )\n\n    # Set the RTC\n    rtc.datetime(new_time)\n    print(\"\\nRTC time set successfully!\")\n\n    # Read back and print the new time\n    print(\"\\nNew RTC time after setting:\")\n    new_rtc_time = rtc.datetime()\n    print(format_time(new_rtc_time))\nelse:\n    print(\"\\nError: Time verification failed. RTC not set.\")\n
"},{"location":"kits/oled-large/06-display-single-digit/","title":"Display a Single 7-Segment Digit","text":"
# Seven segment display configuration\nsegment_mapping = [\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\ndef draw_digit(digit, x, y, width, height, thickness, color):\n    if digit < 0:\n        return\n    segment_on = segment_mapping[digit]\n\n    # Horizontal segments\n    for i in [0, 3, 6]:\n        if segment_on[i]:\n            if i == 0:  # top\n                y_offset = 0\n            elif i == 3:  # bottom\n                y_offset = height - thickness\n            else:  # middle\n                y_offset = height // 2 - thickness // 2\n            oled.fill_rect(x, y + y_offset, width, thickness, color)\n\n    # Vertical segments\n    for i in [1, 2, 4, 5]:\n        if segment_on[i]:\n            if i == 1 or i == 5:  # upper\n                start_y = y\n                end_y = y + height // 2\n            else:  # lower\n                start_y = y + height // 2\n                end_y = y + height\n            x_offset = 0 if (i == 4 or i == 5) else width - thickness\n            oled.fill_rect(x + x_offset, start_y, thickness, end_y - start_y, color)\n
# Clock Lab 20: Draw Seven Segments\n# this lab uses the fill_rect function to draw the segments\nfrom machine import Pin\nimport utime\nimport ssd1306\nfrom utime import sleep, localtime\nimport config\n\nled = machine.Pin(25, machine.Pin.OUT)\n\nSCL=machine.Pin(config.SPI_SCL_PIN) # SPI CLock\nSDA=machine.Pin(config.SPI_SDA_PIN) # SPI Data\n\nRES = machine.Pin(config.SPI_RESET_PIN) # Reset\nDC = machine.Pin(config.SPI_DC_PIN) # Data/command\nCS = machine.Pin(config.SPI_CS_PIN) # Chip Select\n\nWIDTH = config.DISPLAY_WIDTH\nHEIGHT = config.DISPLAY_HEIGHT\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\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)\nw = 40 # digit width\nh = 64 # digit height\nx = (WIDTH // 2) - w // 2 # center - width\ny = 0 # upper left corner y\nt = 8\n\nwhile True:\n    for i in range(0, 10):\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/oled-large/07-set-rtc-time-with-buttons/","title":"Set the DS3231 RTC using Buttons","text":"

Prompt

Please create a new program that allows the user to set the time in the DS3231 RTC using the three buttons. Use the main-v1.py as your starting point and merge in the code from 08-button-mode-with-debounce.py to change the mode from:

mode 0 - clock running mode 1 - set hour with hour digits on screen flashing every 500ms mode 2 - set minute with minute digits on screen flashing every 500ms mode 3 - set am/pm with am/pm text flashing every 500ms

"},{"location":"kits/oled-large/07-set-rtc-time-with-buttons/#response","title":"Response","text":"

Key changes made:

  1. Integrated button handling with debounce from 08-button-mode-with-debounce.py
  2. Added flashing display elements based on current mode
  3. Modified time adjustment logic to work with RTC
  4. Simplified display code while maintaining core functionality
  5. Added proper button pin configuration from config.py

Test the code by:

  1. Connect buttons to specified GPIO pins
  2. Press mode button to cycle through modes
  3. Use increment/decrement buttons to adjust time
  4. Verify display flashing in each mode

```python

"},{"location":"kits/oled-large/10-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/oled-large/10-draw-seven-segments/","title":"Draw Seven Segments","text":""},{"location":"kits/oled-large/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/oled-large/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/oled-large/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/oled-large/11-draw-thickness/","title":"Draw Seven Segments with Thickness","text":""},{"location":"kits/oled-large/13-three-button-set/","title":"Three Button Set","text":""},{"location":"kits/oled-large/13-three-button-set/#set-time-modes","title":"Set Time Modes","text":"
mode_names = ['run', 'set hour', 'set minute', 'set AM/PM']\n

We will create a new temporary array of date time values called current_time. This is a tuple of the values that the rtc.datetime() function needs to set a new time. We will change it as the user presses the increment and decrement buttons.

"},{"location":"kits/oled-large/13-three-button-set/#update-strategy","title":"Update Strategy","text":"

Remember that I2C communication is very slow. I2C calls should never be allowed in an IRQ context. IRQ job is to set globals and finish without delay. Moving update_rtc() from within an IRQ to the main loop is better than calling it in the IRQ. By setting a pending_update flag in the IRQ and checking it in the main loop, we can manage updates without blocking operations like this:

# main loop\nwhile True:\n    # If we're in run mode and there's a pending update, update the RTC\n    if mode == 0 and pending_update:\n        update_rtc()  # This will update the RTC and clear the flag\n

We ONLY should update the RTC when we are going from mode=3 to mode=0 and we know that the user changed the hour, minute or AM/PM. If these were not changed then we don't update the RTC.

The code to update the RTC is in the update_rtc() which has access to two global variables, the current_time and another Boolean variable called pending_update. When any of the hour, minute or AM/PM is changed, the pending_update is set to True. Calling update_rtc() if pending_update is False does not change the RTC.

Note that the format of the data coming OUT of the RTC is different than the format of the data we use to set the RTC. To set the clock correctly we need to rearrange the tuple to match the expected format:

def update_rtc():\n    \"\"\"Update RTC with current time state\"\"\"\n    global current_time, pending_update\n    if pending_update:\n        # Rearrange the tuple to match the expected format:\n        # From: (year, month, day, weekday, hour, minutes, seconds, subseconds)\n        # To:   (year, month, day, hour, minutes, seconds, weekday)\n        rtc_tuple = (\n            current_time[0],  # year\n            current_time[1],  # month\n            current_time[2],  # day\n            current_time[4],  # hour\n            current_time[5],  # minutes\n            current_time[6],  # seconds\n            current_time[3]   # weekday\n        )\n        rtc.datetime(rtc_tuple)\n        pending_update = False\n
"},{"location":"kits/oled-large/13-three-button-set/#mode-button-pressed-irq","title":"Mode Button Pressed IRQ","text":"
def mode_button_pressed(pin):\n    global mode, last_mode_press, pending_update\n    current_ms = ticks_ms()\n    if ticks_diff(current_ms, last_mode_press) > DEBOUNCE_TIME_MS:\n        last_mode_press = current_ms\n\n        # Increment the mode first\n        mode = (mode + 1) % len(mode_names)\n\n        print(f\"Mode button pressed - New mode: {mode_names[mode]}\")\n

Warning

There should be no calls to functions with I2C calls like update_rtc() which calls rtc.datetime() which is a I2C call.

"},{"location":"kits/oled-large/13-three-button-set/#flash-digits-being-set","title":"Flash Digits Being Set","text":"

flash_state is a Boolean that tells us if the digit is flashing. During normal running flash_state = False

# in the update_screen function\n    # Draw digits with flashing based on mode\n    if mode != 1 or flash_state:\n        if hour_ten > 0:  # Only show tens digit if non-zero\n            draw_digit(hour_ten, left_margin, y_offset, digit_width, digit_height, digit_thickness, 1)\n        draw_digit(hour_one, left_margin + digit_spacing - 2, y_offset, digit_width, digit_height, digit_thickness, 1)\n\n    if mode != 2 or flash_state:\n        draw_digit(minute_ten, left_margin + 2 * digit_spacing, y_offset, digit_width, digit_height, digit_thickness, 1)\n        draw_digit(minute_one, left_margin + 3 * digit_spacing, y_offset, digit_width, digit_height, digit_thickness, 1)\n\n    # Draw AM/PM indicator\n    if mode != 3 or flash_state:\n        oled.text(\"PM\" if is_pm(hour) else \"AM\", 112, 55, 1)\n
"},{"location":"kits/oled-large/13-three-button-set/#full-listing","title":"Full Listing","text":"
from machine import Pin, I2C, SPI\nfrom utime import sleep, ticks_ms, ticks_diff\nfrom ds3231 import DS3231\nimport ssd1306\nimport config\n\n# Display setup\nSCL = Pin(config.SPI_SCL_PIN)\nSDA = Pin(config.SPI_SDA_PIN)\nDC = Pin(config.SPI_DC_PIN)\nRES = Pin(config.SPI_RESET_PIN)\nCS = Pin(config.SPI_CS_PIN)\nspi = SPI(config.SPI_BUS, sck=SCL, mosi=SDA, baudrate=1000000)\noled = ssd1306.SSD1306_SPI(config.DISPLAY_WIDTH, config.DISPLAY_HEIGHT, spi, DC, RES, CS)\n\n# RTC setup\ni2c = I2C(config.I2C_BUS, sda=Pin(config.I2C_SDA_PIN), scl=Pin(config.I2C_SCL_PIN))\nrtc = DS3231(i2c)\n\n# Button setup\nmode_button = Pin(config.BUTTON_MODE_PIN, Pin.IN, Pin.PULL_UP)\nincrement_button = Pin(config.BUTTON_INCREMENT_PIN, Pin.IN, Pin.PULL_UP)\ndecrement_button = Pin(config.BUTTON_DECREMENT_PIN, Pin.IN, Pin.PULL_UP)\n\n# Global variables\nmode = 0  # 0=run, 1=set hour, 2=set minute, 3=set AM/PM\nlast_mode_press = 0\nlast_increment_press = 0\nlast_decrement_press = 0\nDEBOUNCE_TIME_MS = 200\nmode_names = ['run', 'set hour', 'set minute', 'set AM/PM']\nflash_state = False\nlast_flash_toggle = 0\nFLASH_INTERVAL_MS = 500\n\n# Current time state\ncurrent_time = list(rtc.datetime())\npending_update = False\n\n# Seven segment display configuration\nsegment_mapping = [\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\ndef update_rtc():\n    \"\"\"Update RTC with current time state\"\"\"\n    global current_time, pending_update\n    if pending_update:\n        # Rearrange the tuple to match the expected format:\n        # From: (year, month, day, weekday, hour, minutes, seconds, subseconds)\n        # To:   (year, month, day, hour, minutes, seconds, weekday)\n        rtc_tuple = (\n            current_time[0],  # year\n            current_time[1],  # month\n            current_time[2],  # day\n            current_time[4],  # hour\n            current_time[5],  # minutes\n            current_time[6],  # seconds\n            current_time[3]   # weekday\n        )\n        rtc.datetime(rtc_tuple)\n        pending_update = False\n\ndef refresh_time():\n    \"\"\"Read current time from RTC\"\"\"\n    global current_time\n    current_time = list(rtc.datetime())\n\ndef convert_to_12h(hour):\n    \"\"\"Convert 24h hour to 12h hour\"\"\"\n    if hour == 0:\n        return 12  # Midnight is 12 AM\n    elif hour > 12:\n        return hour - 12\n    return hour\n\ndef is_pm(hour):\n    \"\"\"Check if hour is PM in 24h format\"\"\"\n    return hour >= 12\n\ndef toggle_am_pm(hour):\n    \"\"\"Toggle between AM and PM for a given hour\"\"\"\n    if hour < 12:\n        return hour + 12\n    return hour - 12\n\ndef draw_digit(digit, x, y, width, height, thickness, color):\n    if digit < 0:\n        return\n    segment_on = segment_mapping[digit]\n\n    # Horizontal segments\n    for i in [0, 3, 6]:\n        if segment_on[i]:\n            if i == 0:  # top\n                y_offset = 0\n            elif i == 3:  # bottom\n                y_offset = height - thickness\n            else:  # middle\n                y_offset = height // 2 - thickness // 2\n            oled.fill_rect(x, y + y_offset, width, thickness, color)\n\n    # Vertical segments\n    for i in [1, 2, 4, 5]:\n        if segment_on[i]:\n            if i == 1 or i == 5:  # upper\n                start_y = y\n                end_y = y + height // 2\n            else:  # lower\n                start_y = y + height // 2\n                end_y = y + height\n            x_offset = 0 if (i == 4 or i == 5) else width - thickness\n            oled.fill_rect(x + x_offset, start_y, thickness, end_y - start_y, color)\n\n\ndef update_screen():\n    global flash_state, current_time\n    oled.fill(0)\n\n    # Display settings\n    left_margin = -28\n    y_offset = 11\n    digit_width = 33\n    digit_height = 40\n    digit_spacing = 41\n    digit_thickness = 5\n\n    hour = current_time[4]\n    minute = current_time[5]\n    second = current_time[6]\n\n    # Convert to 12-hour format for display\n    display_hour = convert_to_12h(hour)\n\n    # Calculate digits for display\n    hour_ten = display_hour // 10 if display_hour >= 10 else -1\n    hour_one = display_hour % 10\n    minute_ten = minute // 10\n    minute_one = minute % 10\n\n    print(f\"Debug display - Raw Hour:{hour} Display Hour:{display_hour} Min:{minute} \" + \n          f\"Digits: {hour_ten}|{hour_one} : {minute_ten}|{minute_one} {('AM', 'PM')[is_pm(hour)]}\")\n\n    # Draw digits with flashing based on mode\n    if mode != 1 or flash_state:\n        if hour_ten > 0:  # Only show tens digit if non-zero\n            draw_digit(hour_ten, left_margin, y_offset, digit_width, digit_height, digit_thickness, 1)\n        draw_digit(hour_one, left_margin + digit_spacing - 2, y_offset, digit_width, digit_height, digit_thickness, 1)\n\n    if mode != 2 or flash_state:\n        draw_digit(minute_ten, left_margin + 2 * digit_spacing, y_offset, digit_width, digit_height, digit_thickness, 1)\n        draw_digit(minute_one, left_margin + 3 * digit_spacing, y_offset, digit_width, digit_height, digit_thickness, 1)\n\n    # Draw colon (flashing)\n    if second % 2:\n        oled.fill_rect(47, 20, 3, 3, 1)\n        oled.fill_rect(47, 20 + 14, 3, 3, 1)\n\n    # Draw AM/PM indicator\n    if mode != 3 or flash_state:\n        oled.text(\"PM\" if is_pm(hour) else \"AM\", 112, 55, 1)\n\n    # Draw seconds\n    oled.text(f\"{second:02d}\", 0, 54, 1)\n    oled.show()\n\ndef mode_button_pressed(pin):\n    global mode, last_mode_press, pending_update\n    current_ms = ticks_ms()\n    if ticks_diff(current_ms, last_mode_press) > DEBOUNCE_TIME_MS:\n        last_mode_press = current_ms\n\n        # Increment the mode first\n        mode = (mode + 1) % len(mode_names)\n\n        print(f\"Mode button pressed - New mode: {mode_names[mode]}\")\n\ndef increment_button_pressed(pin):\n    global last_increment_press, current_time, pending_update\n    current_ms = ticks_ms()\n    if ticks_diff(current_ms, last_increment_press) > DEBOUNCE_TIME_MS:\n        last_increment_press = current_ms\n        print(\"Increment button pressed\")\n\n        if mode == 1:  # Hour\n            hour = current_time[4]\n            new_hour = (hour + 1) if hour < 23 else 0\n            current_time[4] = new_hour\n            pending_update = True\n            print(f\"Hour adjusted to: {new_hour}\")\n\n        elif mode == 2:  # Minute\n            minute = current_time[5]\n            new_minute = (minute + 1) if minute < 59 else 0\n            current_time[5] = new_minute\n            current_time[6] = 0  # Reset seconds when setting minutes\n            pending_update = True\n            print(f\"Minute adjusted to: {new_minute}\")\n\n        elif mode == 3:  # AM/PM\n            hour = current_time[4]\n            new_hour = toggle_am_pm(hour)\n            current_time[4] = new_hour\n            pending_update = True\n            print(f\"Hour adjusted to: {new_hour} ({('AM', 'PM')[is_pm(new_hour)]})\")\n\ndef decrement_button_pressed(pin):\n    global last_decrement_press, current_time, pending_update\n    current_ms = ticks_ms()\n    if ticks_diff(current_ms, last_decrement_press) > DEBOUNCE_TIME_MS:\n        last_decrement_press = current_ms\n        print(\"Decrement button pressed\")\n\n        if mode == 1:  # Hour\n            hour = current_time[4]\n            new_hour = (hour - 1) if hour > 0 else 23\n            current_time[4] = new_hour\n            pending_update = True\n            print(f\"Hour adjusted to: {new_hour}\")\n\n        elif mode == 2:  # Minute\n            minute = current_time[5]\n            new_minute = (minute - 1) if minute > 0 else 59\n            current_time[5] = new_minute\n            current_time[6] = 0  # Reset seconds when setting minutes\n            pending_update = True\n            print(f\"Minute adjusted to: {new_minute}\")\n\n        elif mode == 3:  # AM/PM\n            hour = current_time[4]\n            new_hour = toggle_am_pm(hour)\n            current_time[4] = new_hour\n            pending_update = True\n            print(f\"Hour adjusted to: {new_hour} ({('AM', 'PM')[is_pm(new_hour)]})\")\n\n# Setup button interrupts\nmode_button.irq(trigger=Pin.IRQ_FALLING, handler=mode_button_pressed)\nincrement_button.irq(trigger=Pin.IRQ_FALLING, handler=increment_button_pressed)\ndecrement_button.irq(trigger=Pin.IRQ_FALLING, handler=decrement_button_pressed)\n\nprint(\"Clock started in mode: run\")\n\n# Initialize time state\nrefresh_time()\n\n# Main loop\n# Main loop\nwhile True:\n    current_ms = ticks_ms()\n\n    # Update flash state every FLASH_INTERVAL_MS\n    if ticks_diff(current_ms, last_flash_toggle) >= FLASH_INTERVAL_MS:\n        flash_state = not flash_state\n        last_flash_toggle = current_ms\n\n    # If we're in run mode and there's a pending update, update the RTC\n    if mode == 0 and pending_update:\n        update_rtc()  # This will update the RTC and clear the flag\n\n    # In run mode, use RTC for timekeeping only\n    if mode == 0 and not pending_update:\n        current_time = list(rtc.datetime())\n\n    # Update display\n    update_screen()\n    sleep(0.5)\n
"},{"location":"kits/oled-large/15-digital-clock/","title":"OLED Digital Clock Display","text":"

In this section we will do a full walk through of a program that uses the real-time clock to get the time and displays the time using the four-digit drawing function we created.

Note that if you don't have your real-time clock connected or working you can still use this program to display the time using the localtime() function.

Change the following line:

From: ```python now = rtc.

"},{"location":"kits/oled-wireless/","title":"OLED Wireless Weather Kit","text":""},{"location":"kits/sh1106/","title":"SH1108 OLED Driver Kit","text":""},{"location":"kits/sh1106/#testing-the-oled-connections","title":"Testing the OLED Connections","text":"
from machine import Pin, I2C\nfrom sh1106 import SH1106_I2C\n\n# OLED DATA Pin on GPIO 0\nOLED_SH11306_SDA_PIN = 0\n# OLED Clock Pin on GPIO 1\nOLED_SH11306_SCL_PIN = 1\n\nsda=Pin(OLED_SH11306_SDA_PIN, Pin.OUT)\nscl=Pin(OLED_SH11306_SCL_PIN, Pin.OUT)\n\n# freq=198000 to 3600000 seem to work.  Use 3000000 as a safe option.\ni2c = I2C(0, scl=scl, sda=sda, freq=100000)\n\n# Initialize display (128x64 pixels)\noled = SH1106_I2C(128, 64, i2c)\noled.rotate(180)\n\n# Clear display\noled.fill(0)\n\n# Write text\noled.text(\"MicroPython\", 0, 0)\noled.text(\"Rocks!\", 20, 20)\n\n# Show the display\noled.show()\n
"},{"location":"kits/sh1106/#displaying-clock-digits","title":"Displaying Clock Digits","text":"

Note that the pins are on the bottom, so for our example we had to do a oled.rotate(180)

from machine import Pin, I2C\nfrom sh1106 import SH1106_I2C\nfrom utime import sleep, localtime\n\n# OLED DATA Pin on GPIO 0\nOLED_SH11306_SDA_PIN = 0\n# OLED Clock Pin on GPIO 1\nOLED_SH11306_SCL_PIN = 1\n\nsda=Pin(OLED_SH11306_SDA_PIN, Pin.OUT)\nscl=Pin(OLED_SH11306_SCL_PIN, Pin.OUT)\n\n# freq=30000 (30K) to 4000000 (4M) seem to work.  Use 3000000 (3M) as a safe option.\ni2c = I2C(0, scl=scl, sda=sda, freq=3000000)\n\n# Initialize display (128x64 pixels)\noled = SH1106_I2C(128, 64, i2c)\noled.rotate(180)\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 upper-left-corner\n# width and height are the dimensions of the digit\n# thinkness is the width of the line segments\n# color is 1 for white and 0 for black\ndef drawDigit(digit, x, y, width, height, thinkness, color):\n  if digit < 0:\n      return\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 - thinkness # bottem element\n      if (i==6):\n          yOffset = height // 2 - thinkness // 2# bottum\n      # oled.line(x - size, y+yOffset-size, x + size, y+yOffset-size, 1);\n      oled.fill_rect(x, y+yOffset, width, thinkness, 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-thinkness\n\n      oled.fill_rect(x+xOffset, startY, thinkness, endY-startY, color)\n\ndef update_screen(year, month, day, hour, minute, am_pm, colon_on):   \n\n    ## Adjust these to fit the display\n    left_margin = -28\n    y_offset = 11\n    digit_width = 33\n    digit_height = 40\n    digit_spacing = 41\n    digit_thickness = 5\n    oled.fill(0)\n    date_str = f\"{month}/{day}/{year}\"\n    oled.text(date_str, 0, 0, 1)\n    if hour > 12:\n        hour = hour - 12\n    if hour > 10:\n        hour_ten = 1\n    else:\n        hour_ten = -1\n    hour_right = hour % 10\n\n    minute_ten = minute // 10\n    minute_right = minute % 10\n\n    # left digit will be 1 or blank\n    drawDigit(hour_ten, left_margin,  y_offset, digit_width, digit_height, digit_thickness, 1)\n    drawDigit(hour_right, left_margin + 1*digit_spacing-2,  y_offset, digit_width, digit_height, digit_thickness, 1)\n    drawDigit(minute_ten, left_margin + 2*digit_spacing,  y_offset, digit_width, digit_height, digit_thickness, 1)\n    drawDigit(minute_right, left_margin + 3*digit_spacing, y_offset, digit_width, digit_height, digit_thickness, 1)\n\n    if colon_on:\n        draw_colon(47,20)\n\n    pm_xoffset = 106\n    pm_yoffset = 55\n\n    oled.text(am_pm, pm_xoffset, pm_yoffset, 1)\n\n    oled.show()\n\ndef draw_colon(x,y):\n    oled.fill_rect(x, y,    3, 3, 1)\n    oled.fill_rect(x, y+14, 3, 3, 1)\n\ncounter = 0\nwhile True:\n    now = localtime()\n    year = now[0]\n    month = now[1]\n    day = now[2]\n    hour = now[3]\n    if hour > 12:\n        am_pm = \"PM\"\n    else:\n        am_pm = \"AM\"\n    minute = now[4]\n\n    update_screen(year, month, day, hour, minute, am_pm, True)\n    sleep(1)\n    update_screen(year, month, day, hour, minute, am_pm, False)\n    sleep(1)\n    counter += 1\n    if counter > 9:\n        counter = 0\n
"},{"location":"kits/shift-register-clock/","title":"74HC595 Shift Register Clock","text":"

The display has five wires. In addition to power and ground we have:

ser_pin=2,    # DIO\nsrclk_pin=1,  # SCLK\nrclk_pin=0    # RCLK\n

The shift register takes in a set of 16 bit commands. The first byte is the segment information with one bit for each of the 7 segments and one bit for the colon. The second bit indicates what digit should be turned on.

Note that for a segment to be on, the bit must be \"0\".

Here are the letters associated with each of the 7 segments:

 aaaa\nf    b\nf    b\n gggg\ne    c\ne    c\n dddd\n
# bit 0 is the right-most significant bit and is used to turn on the colon.\nself.SEGMENTS = {\n    'a': 4,  # bit 4\n    'b': 3,  # bit 3\n    'c': 2,  # bit 2\n    'd': 7,  # bit 7\n    'e': 6,  # bit 6\n    'f': 5,  # bit 5\n    'g': 1   # bit 1\n}\n

\"d\" First byte: 00110000 Second byte: 00010000

This is where \"a\" is missing. a is the fourth bit. First byte: 00010000

This is where all the segments are on except f. \"f\" is the 3rd bit First byte: 00100000 \"A\" is First byte: 10000000

"},{"location":"kits/shift-register-clock/#sample-test","title":"Sample Test","text":"

The following program will cycle through the digits 0 to 9 on the right most digit to the left most digit.

from machine import Pin\nfrom sr74hc595 import SR74HC595_BITBANG\nfrom utime import sleep, localtime\n\nclass FourDigitClock:\n    # Class constants\n    ALL_OFF = 0xFF            # All segments off (inverted logic)\n    SECOND_BYTE = 0b00010000  # Keep bit 4 set in second byte\n\n    def __init__(self, ser_pin, srclk_pin, rclk_pin):\n        # Initialize pins\n        self.ser = Pin(ser_pin, Pin.OUT)      # Serial data\n        self.srclk = Pin(srclk_pin, Pin.OUT)  # Shift register clock\n        self.rclk = Pin(rclk_pin, Pin.OUT)    # Storage register clock\n\n        self.sr = SR74HC595_BITBANG(self.ser, self.srclk, self.rclk)\n\n        # Segment bit positions (0 turns segment on)\n        # a is the top, b in the upper right, c is the lower right\n        # d is the bottom, e is the lower left, f is the upper right\n        # g is the middle segment\n        self.SEGMENTS = {\n            'a': 4,  # bit 4\n            'b': 3,  # bit 3\n            'c': 2,  # bit 2\n            'd': 7,  # bit 7\n            'e': 6,  # bit 6\n            'f': 5,  # bit 5\n            'g': 1   # bit 1\n        }\n\n        # Define segments needed for each digit\n        self.DIGIT_SEGMENTS = {\n            0: 'abcdef',   # 0 needs all but g\n            1: 'bc',       # 1 needs just b and c\n            2: 'abged',    # 2 needs all but c and f\n            3: 'abgcd',    # 3 needs all but e and f\n            4: 'fbcg',     # 4 needs these four\n            5: 'afgcd',    # 5 needs all but e and b\n            6: 'afedcg',   # 6 needs all but b\n            7: 'abc',      # 7 needs just these three\n            8: 'abcdefg',  # 8 needs all segments\n            9: 'abfgcd'    # 9 needs all but e\n        }\n\n        # Pre-calculate patterns for all digits\n        self.DIGIT_PATTERNS = {\n            digit: self.create_pattern(segments) \n            for digit, segments in self.DIGIT_SEGMENTS.items()\n        }\n\n    def create_pattern(self, segments):\n        \"\"\"Create bit pattern from segment letters with inverted logic\"\"\"\n        pattern = self.ALL_OFF\n        for segment in segments:\n            if segment in self.SEGMENTS:\n                pattern &= ~(1 << self.SEGMENTS[segment])\n        return pattern\n\n    def display_pattern(self, first_byte, digit_select_byte):\n        \"\"\"Display a pattern with specified digit selection\"\"\"\n        self.sr.bits(first_byte, 8)\n        self.sr.bits(digit_select_byte, 8)\n        self.sr.latch()\n\n    def test_all_positions(self):\n        \"\"\"Test counting 0-9 on all digit positions\"\"\"\n        print(\"Testing all digit positions...\")\n\n        # Digit selection patterns to try\n        digit_patterns = [\n            (0, 0b00010000, \"Rightmost digit\"),\n            (1, 0b00100000, \"Second digit\"),\n            (2, 0b01000000, \"Third digit\"),\n            (3, 0b10000000, \"Leftmost digit\")\n        ]\n\n        # Test each digit position\n        for position, digit_select, position_name in digit_patterns:\n            print(f\"\\nTesting {position_name}\")\n\n            # Count 0-9 on this position\n            for number in range(10):\n                print(f\"Displaying {number} on {position_name}\")\n\n                # Display the number\n                self.display_pattern(self.DIGIT_PATTERNS[number], digit_select)\n                sleep(.25)\n\n                # Clear display between numbers\n                self.display_pattern(self.ALL_OFF, self.ALL_OFF)\n                sleep(0.05)\n\n            # Wait for user input before moving to next position\n            #input(f\"Press Enter to test next position...\")\n\ndef run_position_test():\n    \"\"\"Run position testing\"\"\"\n    clock = FourDigitClock(\n        ser_pin=2,    # DIO\n        srclk_pin=1,  # SCLK\n        rclk_pin=0    # RCLK\n    )\n\n    print(\"Starting position tests...\")\n    clock.test_all_positions()\n\n# Run the position test\nif __name__ == '__main__':\n    run_position_test()\n
"},{"location":"kits/shift-register-clock/#references","title":"References","text":"

74HC595 Shift Register

"},{"location":"kits/shift-register-clock/clock-main/","title":"Clock Main","text":""},{"location":"kits/shift-register-clock/clock-main/#details","title":"Details","text":"

This program use 12 hour time and also flashes the colon ever 1/2 second. It also is designed to work with a real-time clock that might take a bit of time to startup with the Pico first powers up.

from machine import Pin, RTC \nfrom sr74hc595 import SR74HC595_BITBANG\nfrom utime import sleep, localtime, ticks_ms\n\ndef wait_for_rtc():\n    print(\"Waiting for RTC to be ready...\")\n    rtc = RTC()\n    while True:\n        current_time = rtc.datetime()\n        # On Pico, default RTC starts at (2021, 1, 1, 0, 0, 0, 0, 0)\n        # So we check if it's been set to something else\n        if current_time[0] != 2021 or current_time[1] != 1 or current_time[2] != 1:\n            print(\"RTC is ready!\")\n            return\n        print(\"RTC not ready yet...\")\n        sleep(1)  # Check every second\n\nclass FourDigitClock:\n    ALL_OFF = 0xFF  # All segments off (inverted logic)\n\n    def __init__(self, ser_pin, srclk_pin, rclk_pin):\n        self.ser = Pin(ser_pin, Pin.OUT)\n        self.srclk = Pin(srclk_pin, Pin.OUT) \n        self.rclk = Pin(rclk_pin, Pin.OUT)\n        self.sr = SR74HC595_BITBANG(self.ser, self.srclk, self.rclk)\n\n        self.SEGMENTS = {\n            'a': 4, 'b': 3, 'c': 2, \n            'd': 7, 'e': 6, 'f': 5, 'g': 1\n        }\n        self.DIGIT_SEGMENTS = {\n            0: 'abcdef', 1: 'bc', 2: 'abged', 3: 'abgcd',\n            4: 'fbcg', 5: 'afgcd', 6: 'afedcg', \n            7: 'abc', 8: 'abcdefg', 9: 'abfgcd'\n        }\n        self.DIGIT_PATTERNS = {\n            digit: self.create_pattern(segments)\n            for digit, segments in self.DIGIT_SEGMENTS.items()\n        }\n        self.DIGIT_SELECT = [\n            0b00010000,  # Rightmost digit\n            0b00100000,  # Second digit \n            0b01000000,  # Third digit\n            0b10000000   # Leftmost digit  \n        ]\n\n    def create_pattern(self, segments):\n        pattern = self.ALL_OFF\n        for segment in segments:\n            if segment in self.SEGMENTS:\n                pattern &= ~(1 << self.SEGMENTS[segment]) \n        return pattern\n\n    def display_digit(self, digit, position, dp=False):\n        pattern = self.DIGIT_PATTERNS[digit]\n        if dp:\n            pattern &= ~(1 << 0)  # Turn on decimal point\n        self.sr.bits(pattern, 8)\n        self.sr.bits(self.DIGIT_SELECT[position], 8)\n        self.sr.latch()\n\n    def display_time(self, hour, minute, colon_state):\n        # Convert to 12-hour format\n        if hour > 12:\n            hour -= 12\n        elif hour == 0:  \n            hour = 12\n\n        digits = [hour // 10, hour % 10, minute // 10, minute % 10]\n\n        for position, digit in enumerate(reversed(digits)):\n            self.display_digit(digit, position, dp=(position == 1 and colon_state))\n            sleep(0.002)  # Reduced persistence timing\n\n\ndef run_clock():\n    # wait for a good time to come from localtime()\n    wait_for_rtc()\n\n    clock = FourDigitClock(ser_pin=2, srclk_pin=1, rclk_pin=0)\n    print(\"Starting clock...\")\n\n    last_second = -1\n    colon_state = True\n    last_toggle = ticks_ms()\n\n    while True:\n        current_time = localtime()\n        hour = current_time[3]\n        minute = current_time[4]\n        current_second = current_time[5]\n\n        if ticks_ms() - last_toggle >= 500:\n            colon_state = not colon_state\n            last_toggle = ticks_ms()\n\n        clock.display_time(hour, minute, colon_state)\n\n\nif __name__ == '__main__':\n    run_clock()\n
"},{"location":"kits/shift-register-clock/sample-prompt/","title":"Sample ChatGPT Prompt","text":"

Prompt

Please help me write a micropython program for a 4-digit clock. I have a digital clock display with 4 digits I purchased on eBay. Each of the 4 digits has 7 segments and there is also a colon between the 2nd and 3rd digits. The part says they use 74hc595 shift registers. The names of the connectors are:

  1. GND
  2. VCC
  3. RCLK
  4. SCLK
  5. DIO

I found a micropython driver called: sr74hc595. When I run a test program the various segments do light up, but I don't see a pattern. Can you give me some suggestions on how to write a driver that writes the correct time like 12:34?

Here is the code that is working:

# SPDX-FileCopyrightText: 2021 Mike Causer <https://github.com/mcauser>\n# SPDX-License-Identifier: MIT\n\n\"\"\"\nMicroPython 74HC595 8-Bit Shift Register\nhttps://github.com/mcauser/micropython-74hc595\n\"\"\"\n\nfrom machine import Pin\n# from sr74hc595 import SR74HC595_BITBANG\nfrom sr74hc595 import SR74HC595_BITBANG\nfrom utime import sleep\n\nser = Pin(2, Pin.OUT)\nrclk = Pin(0, Pin.OUT)\nsrclk = Pin(1, Pin.OUT)\n\n# construct without optional pins\nsr = SR74HC595_BITBANG(ser, srclk, rclk)\n\n#sr.clear()  # raises RuntimeError because you haven't provide srclr pin\n#sr.enable()  # raises RuntimeError because you haven't provide oe pin\n\n# reconstruct with all pins\noe = Pin(3, Pin.OUT, value=0)  # low enables output\nsrclr = Pin(3, Pin.OUT, value=1)  # pulsing low clears data\n\nsr = SR74HC595_BITBANG(ser, srclk, rclk, srclr, oe)\n\nwhile True:\n\n    sr.bit(1)  # send high bit, do not latch yet\n    sr.bit(0)  # send low bit, do not latch yet\n    sr.latch()  # latch outputs, outputs=0000_0010\n\n    sr.bit(1, 1)  # send high bit and latch, outputs=0000_0101\n    sr.bit(0, 1)  # send low bit and latch, outputs=0000_1010\n\n    sr.bits(0xFF, 4)  # send 4 lowest bits of 0xff (sends 0x0f), outputs=1010_1111\n    sr.clear(0)  # clear the memory but don't latch yet\n    sr.latch()  # next latch shows the outputs have been reset\n    sr.bits(0b1010_1010, 8)  # write some bits\n    sr.clear()  # clear the memory and latch, outputs have been reset\n\n    sr.enable()  # outputs enabled\n    sr.enable(0)  # outputs disabled\n\n    sleep(1)\n\n    sr.bits(0b1111_1111, 8)  # write some bits\n    sr.clear()  # clear the memory and latch, outputs have been reset\n\n    sr.enable()  # outputs enabled\n    sr.enable(0)  # outputs disabled\n\n    sleep(1)\n\n    sr.bits(0b0000_0011, 8)  # write some bits\n    sr.clear()  # clear the memory and latch, outputs have been reset\n\n    sr.enable()  # outputs enabled\n    sr.enable(0)  # outputs disabled\n    sleep(1)\n
"},{"location":"kits/ssd1306-i2c/","title":"A Tiny SSD1306 OLED Clock on an I2C Interface","text":"

This clock is an ideal starter clock since you can get the displays for just $3-4 for the small 1\" displays. Because they have a simple four-wire I2C connection they are also easy to wire up.

Note that the I2C bus can be slower than the faster SPI bus. However, for a clock that only updates once per second, this is not usually an issue since we don't need the 20 frames per second updates for real-time animation.

"},{"location":"kits/ssd1306-i2c/#testing-the-i2c-bus","title":"Testing the I2C Bus","text":"

We start by putting SDA on pin 0 and clock on pin 1. We can leave the freq parameter out or we can set the clock to a frequency of 3M or 3000000. I have found values from freq=198000 to 3600000 seem to work. Use 3000000 as a safe option.

from machine import I2C, Pin\n\n# OLED DATA Pin\nOLED_SSD1306_SDA_PIN = 0\n# OLED Clock Pin\nOLED_SSD1306_SCL_PIN = 1\n\nsda=Pin(OLED_SSD1306_SDA_PIN, Pin.OUT)\nscl=Pin(OLED_SSD1306_SCL_PIN, Pin.OUT)\n\ni2c = I2C(0, scl=scl, sda=sda, freq=3000000)\nfirst_device = i2c.scan()[0]\n# print(\"dec:\", first_device, \"hex:\", hex(first_device))\n\nif first_device == 60:\n    print(\"PASS: OLED Found on dec: 60 hex: 0x3c\")\nelse:\n    print(\"FAIL: OLED not found on expected address dec: 60 hex: 0x3c\")\n
"},{"location":"kits/ssd1306-i2c/#testing-the-oled-driver-and-connections","title":"Testing the OLED Driver and Connections","text":"
from machine import Pin, I2C\nfrom ssd1306 import SSD1306_I2C\n\n# OLED DATA Pin on GPIO 0\nOLED_SSD1306_SDA_PIN = 0\n# OLED Clock Pin on GPIO 1\nOLED_SSD1306_SCL_PIN = 1\n\nsda=Pin(OLED_SSD1306_SDA_PIN, Pin.OUT)\nscl=Pin(OLED_SSD1306_SCL_PIN, Pin.OUT)\n\n# freq=198000 to 3600000 seem to work.  Use 3000000 as a safe option.\ni2c = I2C(0, scl=scl, sda=sda, freq=3000000)\n\n# Initialize display (128x64 pixels)\noled = SSD1306_I2C(128, 64, i2c)\n\n# Clear display\noled.fill(0)\n\n# Write text\noled.text(\"MicroPython\", 0, 0)\noled.text(\"Rocks!\", 20, 20)\n\n# Show the display\noled.show()\n
"},{"location":"kits/ssd1306-i2c/#display-the-localtime","title":"Display the Localtime","text":"
from machine import Pin, I2C\nfrom ssd1306 import SSD1306_I2C\nfrom utime import localtime\n\n# OLED DATA Pin on GPIO 0\nOLED_SSD1306_SDA_PIN = 0\n# OLED Clock Pin on GPIO 1\nOLED_SSD1306_SCL_PIN = 1\n\nsda=Pin(OLED_SSD1306_SDA_PIN, Pin.OUT)\nscl=Pin(OLED_SSD1306_SCL_PIN, Pin.OUT)\n\n# freq=198000 to 3600000 seem to work.  Use 3000000 as a safe option.\ni2c = I2C(0, scl=scl, sda=sda, freq=3000000)\n\n# Initialize display (128x64 pixels)\noled = SSD1306_I2C(128, 64, i2c)\n\n# Clear display\noled.fill(0)\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), 20, 20, 1)\noled.show()\n
"},{"location":"kits/ssd1306-i2c/adjusting-digit-display/","title":"Adjusting the Clock Digit Layout","text":"

You have the ability to adjust the clock digit layouts by changing the following parameters:

  1. left_margin - were the left-most digit drawing starts
  2. y_offset - how far down the screen to begin to draw the top line of the digits
  3. digit_width - how wide each digit is
  4. digit_height - how high each digit is
  5. digit_spacing - the spacing between the left edges of each digit - not the space between the digits
  6. digit_thickness - how wide each bar of each segment is

In addition you can change the placement of the colon and the AM/PM indicator.

from machine import Pin, I2C\nfrom ssd1306 import SSD1306_I2C\nfrom utime import localtime, sleep\n\n# OLED DATA Pin on GPIO 0\nOLED_SSD1306_SDA_PIN = 0\n# OLED Clock Pin on GPIO 1\nOLED_SSD1306_SCL_PIN = 1\n\nsda=Pin(OLED_SSD1306_SDA_PIN, Pin.OUT)\nscl=Pin(OLED_SSD1306_SCL_PIN, Pin.OUT)\n\n# freq=198000 to 3600000 seem to work.  Use 3000000 as a safe option.\ni2c = I2C(0, scl=scl, sda=sda, freq=3000000)\n\n# Initialize display (128x64 pixels)\noled = SSD1306_I2C(128, 64, i2c)\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 upper-left-corner\n# width and height are the dimensions of the digit\n# thinkness is the width of the line segments\n# color is 1 for white and 0 for black\ndef drawDigit(digit, x, y, width, height, thinkness, 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 - thinkness # bottem element\n      if (i==6):\n          yOffset = height // 2 - thinkness // 2# bottum\n      # oled.line(x - size, y+yOffset-size, x + size, y+yOffset-size, 1);\n      oled.fill_rect(x, y+yOffset, width, thinkness, 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-thinkness\n\n      oled.fill_rect(x+xOffset, startY, thinkness, endY-startY, color)\n\ndef update_screen(digit_val):\n    global counter\n\n    ## Adjust these to fit the display\n    left_margin = -15\n    y_offset = 16\n    digit_width = 30\n    digit_height = 37\n    digit_spacing = 36\n    digit_thickness = 6\n    oled.fill(0)\n    oled.text('Clock Digits Lab', 0, 0, 1)\n\n    # left digit will be 1 or blank\n    drawDigit(1, left_margin,  y_offset, digit_width, digit_height, digit_thinkness, 1)\n    drawDigit(digit_val, left_margin + 1*digit_spacing,  y_offset, digit_width, digit_height, digit_thickness, 1)\n    drawDigit(digit_val, left_margin + 2*digit_spacing,  y_offset, digit_width, digit_height, digit_thickness, 1)\n    drawDigit(digit_val, left_margin + 3*digit_spacing, y_offset, digit_width, digit_height, digit_thickness, 1)\n\n    draw_colon(53,26)\n\n    pm_xoffset = 103\n    pm_yoffset = 55\n    if (counter % 2):\n        # 112 is the max right for the am/pm text\n        oled.text(\"am\", pm_xoffset, pm_yoffset, 1)\n\n    else:\n        oled.text(\"pm\", pm_xoffset, pm_yoffset, 1)\n    oled.text(str(digit_val), 0, 54, 1)\n\n    oled.show()\n\ndef draw_colon(x,y):\n    oled.fill_rect(x, y, 2, 2,1)\n    oled.fill_rect(x, y+8, 2, 2,1)\n\ncounter = 0\nwhile True:\n    update_screen(counter)\n    sleep(1)\n    counter += 1\n    if counter > 9:\n        counter = 0\n
"},{"location":"kits/ssd1306-i2c/integrating-localtime/","title":"Integrating the Local Time","text":"
from machine import Pin, I2C\nfrom ssd1306 import SSD1306_I2C\nfrom utime import localtime, sleep\n\n# OLED DATA Pin on GPIO 0\nOLED_SSD1306_SDA_PIN = 0\n# OLED Clock Pin on GPIO 1\nOLED_SSD1306_SCL_PIN = 1\n\nsda=Pin(OLED_SSD1306_SDA_PIN, Pin.OUT)\nscl=Pin(OLED_SSD1306_SCL_PIN, Pin.OUT)\n\n# freq=198000 to 3600000 seem to work.  Use 3000000 as a safe option.\ni2c = I2C(0, scl=scl, sda=sda, freq=3000000)\n\n# Initialize display (128x64 pixels)\noled = SSD1306_I2C(128, 64, i2c)\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 upper-left-corner\n# width and height are the dimensions of the digit\n# thinkness is the width of the line segments\n# color is 1 for white and 0 for black\ndef drawDigit(digit, x, y, width, height, thinkness, color):\n  if digit < 0:\n      return\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 - thinkness # bottem element\n      if (i==6):\n          yOffset = height // 2 - thinkness // 2# bottum\n      # oled.line(x - size, y+yOffset-size, x + size, y+yOffset-size, 1);\n      oled.fill_rect(x, y+yOffset, width, thinkness, 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-thinkness\n\n      oled.fill_rect(x+xOffset, startY, thinkness, endY-startY, color)\n\ndef update_screen(year, month, day, hour, minute, am_pm, colon_on):   \n\n    ## Adjust these to fit the display\n    left_margin = -15\n    y_offset = 16\n    digit_width = 30\n    digit_height = 37\n    digit_spacing = 36\n    digit_thickness = 5\n    oled.fill(0)\n    date_str = f\"{month}/{day}/{year}\"\n    oled.text(date_str, 0, 0, 1)\n    if hour > 12:\n        hour = hour - 12\n    if hour > 10:\n        hour_ten = 1\n    else:\n        hour_ten = -1\n    hour_right = hour % 10\n\n    minute_ten = minute // 10\n    minute_right = minute % 10\n\n    # left digit will be 1 or blank\n    drawDigit(hour_ten, left_margin,  y_offset, digit_width, digit_height, digit_thickness, 1)\n    drawDigit(hour_right, left_margin + 1*digit_spacing,  y_offset, digit_width, digit_height, digit_thickness, 1)\n    drawDigit(minute_ten, left_margin + 2*digit_spacing,  y_offset, digit_width, digit_height, digit_thickness, 1)\n    drawDigit(minute_right, left_margin + 3*digit_spacing, y_offset, digit_width, digit_height, digit_thickness, 1)\n\n    if colon_on:\n        draw_colon(53,26)\n\n    pm_xoffset = 106\n    pm_yoffset = 55\n\n    oled.text(am_pm, pm_xoffset, pm_yoffset, 1)\n\n    oled.show()\n\ndef draw_colon(x,y):\n    oled.fill_rect(x, y, 2, 2,1)\n    oled.fill_rect(x, y+8, 2, 2,1)\n\ncounter = 0\nwhile True:\n    now = localtime()\n    year = now[0]\n    month = now[1]\n    day = now[2]\n    hour = now[3]\n    if hour > 12:\n        am_pm = \"PM\"\n    else:\n        am_pm = \"AM\"\n    minute = now[4]\n\n    update_screen(year, month, day, hour, minute, am_pm, True)\n    sleep(1)\n    update_screen(year, month, day, hour, minute, am_pm, False)\n    sleep(1)\n    counter += 1\n    if counter > 9:\n        counter = 0\n
"},{"location":"kits/ssd1306-i2c/prompt/","title":"Prompt for the SSD1306 I2C Clock","text":""},{"location":"kits/ssd1306-i2c/setting-time/","title":"Manually Setting Time on Power Up","text":"

This clock has two buttons on a breadboard for setting the time. When the clock is powered up or reset, the clock goes into a \"Power-Up\" mode where all the digits are flashing.

Here are the modes we will need:

  1. Normal Clock Running
  2. Setting the Hour
  3. Setting the Minute
  4. Setting the AM/PM
"},{"location":"kits/st7735/","title":"Digital Clock Using the ST7735 LCD","text":""},{"location":"kits/st7735/#overview","title":"Overview","text":"

The ST7735 is a low-cost color LCD with a 160x120 screen resolution. I found the 1.8 inch version on eBay for about $3.50. Because of the low-cost it makes an ideal starter clock display for clocks kits under $10.

The ST7735 uses a standard SPI 7-wire interface with one additional wire for powering the backlight of the display. It works on 3.3 volts.

"},{"location":"kits/st7735/#benefits-of-the-st7735-clock-kit","title":"Benefits of the ST7735 Clock Kit","text":""},{"location":"kits/st7735/#color-display-capabilities","title":"Color Display Capabilities","text":""},{"location":"kits/st7735/#flexible-display-options","title":"Flexible Display Options","text":""},{"location":"kits/st7735/#hardware-integration","title":"Hardware Integration","text":""},{"location":"kits/st7735/#software-features","title":"Software Features","text":""},{"location":"kits/st7735/#cost-effective","title":"Cost-Effective","text":"

The code demonstrates these benefits through features like:

"},{"location":"kits/st7735/dirty-drawing/","title":"Dirty Drawing","text":"

We need to modify the clock program to minimize flicker by only updating the parts of the display that change. This requires tracking the previous state and only redrawing digits that have changed. Here's how we can improve it:

The key improvements in this version include:

  1. State Tracking: Added global variables to track the previous state of all display elements (digits, date, AM/PM, etc.)

  2. Selective Updates: Only redraws elements that have changed since the last update:

  3. Digits are only redrawn when their values change
  4. The date is only updated when it changes
  5. AM/PM indicator only updates when switching between AM and PM
  6. The colon blinks by only updating its small area

  7. Localized Clearing: Instead of clearing the entire screen:

  8. Each digit area is cleared individually before redrawing
  9. Text areas are cleared in their specific regions before updating

  10. Initialization Handling: Added a screen_initialized flag to ensure the background is only drawn once at startup

  11. Improved Update Rate: Reduced the sleep time to 0.1 seconds for smoother updates while still maintaining efficiency

These changes should significantly reduce the flicker since we're only updating the parts of the screen that actually change. The blue background will now only be drawn once at startup, and subsequent updates will be localized to the specific areas that need to change.

To use this improved version, simply replace the contents of your existing clock-main-with-flicker.py with this new code. The functionality remains the same, but the display should be much more stable with minimal flicker.

this is a great example of how selective updates can make a big difference in display quality. Instead of redrawing the entire screen 10 times per second, we're now only updating the specific segments that change. For example, when displaying \"12:59\", only the \":59\" portion will update frequently. The \"12\" will stay static until it changes to \"1:00\". The colons blink by just toggling those few pixels rather than redrawing everything.

This technique (known as \"dirty rectangle\" updating in graphics programming) is especially important for displays like the ST7735 where each pixel update takes a non-trivial amount of time. By minimizing the number of pixels we need to update, we get both better performance and a cleaner visual result.

from machine import Pin, SPI\nfrom utime import sleep, localtime\nimport ST7735\nimport config\n\n# Initialize SPI and display\nspi = machine.SPI(config.SPI_BUS, \n                  sck=Pin(config.SPI_SCL_PIN),\n                  mosi=Pin(config.SPI_SDA_PIN),\n                  baudrate=8000000)\n\ndisplay = ST7735.ST7735(spi, \n                        rst=config.SPI_RESET_PIN,\n                        ce=config.SPI_CS_PIN,\n                        dc=config.SPI_DC_PIN)\ndisplay.reset()\ndisplay.begin()\ndisplay.set_rotation(config.DISPLAY_ROTATION)\n\n# Define colors using RGB565 format\nBACKGROUND_COLOR = display.rgb_to_565(0, 0, 255)  # Blue background\nDIGIT_COLOR = display.rgb_to_565(255, 255, 255)   # White for digits\nTEXT_COLOR = display.rgb_to_565(255, 255, 0)      # Yellow for text\n\n# Previous state tracking\nprev_date = \"\"\nprev_hour_ten = -1\nprev_hour_right = -1\nprev_minute_ten = -1\nprev_minute_right = -1\nprev_second = -1\nprev_am_pm = \"\"\nscreen_initialized = False\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\ndef day_to_str(day_num):\n    \"\"\"Convert a day number (0-6) to a three-letter day abbreviation.\"\"\"\n    days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']\n    if not 0 <= day_num <= 6:\n        raise ValueError(\"Day number must be between 0 and 6\")\n    return days[day_num]\n\ndef month_to_str(month_num):\n    \"\"\"Convert a month number (1-12) to a three-letter month abbreviation.\"\"\"\n    months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', \n             'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']\n    if not 1 <= month_num <= 12:\n        raise ValueError(\"Month number must be between 1 and 12\")\n    return months[month_num - 1]\n\ndef clear_digit_area(x, y, width, height, color):\n    \"\"\"Clear the area where a digit was previously drawn.\"\"\"\n    display.draw_block(x, y, width, height, color)\n\ndef drawDigit(digit, x, y, width, height, thickness, color):\n    \"\"\"Draw a seven-segment digit on the display.\"\"\"\n    if digit < 0:\n        return\n\n    # Clear the area first\n    clear_digit_area(x, y, width, height, BACKGROUND_COLOR)\n\n    segmentOn = segmentMapping[digit]\n\n    # Draw horizontal segments (top, middle, bottom)\n    for i in [0, 3, 6]:\n        if segmentOn[i]:\n            if i == 0:  # top\n                yOffset = 0\n            elif i == 3:  # bottom\n                yOffset = height - thickness\n            else:  # middle\n                yOffset = height // 2 - thickness // 2\n            display.draw_block(x, y + yOffset, width, thickness, color)\n\n    # Draw vertical segments\n    for i in [1, 2, 4, 5]:\n        if segmentOn[i]:\n            if i == 1 or i == 5:  # upper segments\n                startY = y\n                endY = y + height // 2\n            else:  # lower segments\n                startY = y + height // 2\n                endY = y + height\n            xOffset = 0 if (i == 4 or i == 5) else width - thickness\n            display.draw_block(x + xOffset, startY, thickness, endY - startY, color)\n\ndef draw_colon(x, y, color):\n    \"\"\"Draw the blinking colon between hours and minutes.\"\"\"\n    display.draw_block(x, y, 4, 4, color)\n    display.draw_block(x, y + 14, 4, 4, color)\n\ndef update_screen(year, month, day, hour, minute, second, weekday):\n    \"\"\"Update only the changing parts of the display.\"\"\"\n    global prev_date, prev_hour_ten, prev_hour_right, prev_minute_ten\n    global prev_minute_right, prev_second, prev_am_pm, screen_initialized\n\n    # Initialize screen on first run\n    if not screen_initialized:\n        display.fill_screen(BACKGROUND_COLOR)\n        screen_initialized = True\n\n    # Calculate positions\n    left_margin = -15\n    y_offset = 30\n    digit_width = 32\n    digit_height = 50\n    digit_spacing = 42\n    digit_thickness = 6\n\n    # Convert 24-hour to 12-hour format\n    display_hour = hour if hour <= 12 else hour - 12\n    if display_hour == 0:\n        display_hour = 12\n\n    # Format date string\n    date_str = f\"{day_to_str(weekday)} {month_to_str(month)} {day} {year}\"\n\n    # Update date if changed\n    if date_str != prev_date:\n        display._color = TEXT_COLOR\n        # Clear previous date area\n        display.draw_block(4, 4, 160, 10, BACKGROUND_COLOR)\n        display.p_string(4, 4, date_str)\n        prev_date = date_str\n\n    # Split time into digits\n    hour_ten = display_hour // 10 if display_hour >= 10 else -1\n    hour_right = display_hour % 10\n    minute_ten = minute // 10\n    minute_right = minute % 10\n\n    # Update digits only if they've changed\n    if hour_ten != prev_hour_ten:\n        drawDigit(hour_ten, left_margin, y_offset, digit_width, digit_height, digit_thickness, DIGIT_COLOR)\n        prev_hour_ten = hour_ten\n\n    if hour_right != prev_hour_right:\n        drawDigit(hour_right, left_margin + digit_spacing, y_offset, digit_width, digit_height, digit_thickness, DIGIT_COLOR)\n        prev_hour_right = hour_right\n\n    if minute_ten != prev_minute_ten:\n        drawDigit(minute_ten, left_margin + 2*digit_spacing+10, y_offset, digit_width, digit_height, digit_thickness, DIGIT_COLOR)\n        prev_minute_ten = minute_ten\n\n    if minute_right != prev_minute_right:\n        drawDigit(minute_right, left_margin + 3*digit_spacing+10, y_offset, digit_width, digit_height, digit_thickness, DIGIT_COLOR)\n        prev_minute_right = minute_right\n\n    # Handle colon blinking - clear and redraw based on seconds\n    colon_x = left_margin + digit_spacing + digit_width + 8\n    colon_y = y_offset + 15\n    if second % 2:\n        draw_colon(colon_x, colon_y, DIGIT_COLOR)\n    else:\n        draw_colon(colon_x, colon_y, BACKGROUND_COLOR)\n\n    # Update AM/PM indicator if needed\n    am_pm_str = \"PM\" if hour >= 12 else \"AM\"\n    if am_pm_str != prev_am_pm:\n        display._color = TEXT_COLOR\n        display.draw_block(left_margin + 3*digit_spacing + digit_width, y_offset + 60, 20, 10, BACKGROUND_COLOR)\n        display.p_string(left_margin + 3*digit_spacing + digit_width, y_offset + 60, am_pm_str)\n        prev_am_pm = am_pm_str\n\n    # Update seconds display if changed\n    if second != prev_second:\n        display._color = TEXT_COLOR\n        display.draw_block(4, y_offset + digit_height + 10, 20, 10, BACKGROUND_COLOR)\n        display.p_string(4, y_offset + digit_height + 10, f\"{second:02d}\")\n        prev_second = second\n\n# Main loop\nwhile True:\n    now = localtime()\n    year = now[0]\n    month = now[1]\n    day = now[2]\n    hour = now[3]\n    minute = now[4]\n    second = now[5]\n    weekday = now[6]\n\n    update_screen(year, month, day, hour, minute, second, weekday)\n    sleep(0.1)  # Shorter sleep for more responsive updates\n
"},{"location":"kits/st7735/drawing-four-digits/","title":"Drawing Digits","text":"

Let's now draw four digits on the screen. This will have a lot of flicker which we will clean up in the next version.

from machine import Pin, SPI\nfrom utime import sleep, localtime\nimport ST7735\nimport config\n\n# Initialize SPI and display\nspi = machine.SPI(config.SPI_BUS, \n                  sck=Pin(config.SPI_SCL_PIN),\n                  mosi=Pin(config.SPI_SDA_PIN),\n                  baudrate=8000000)\n\ndisplay = ST7735.ST7735(spi, \n                        rst=config.SPI_RESET_PIN,\n                        ce=config.SPI_CS_PIN,\n                        dc=config.SPI_DC_PIN)\ndisplay.reset()\ndisplay.begin()\ndisplay.set_rotation(config.DISPLAY_ROTATION)\n\n# Define colors using RGB565 format\nBACKGROUND_COLOR = display.rgb_to_565(0, 0, 255)  # Blue background\nDIGIT_COLOR = display.rgb_to_565(255, 255, 255)   # White for digits\nTEXT_COLOR = display.rgb_to_565(255, 255, 0)      # Yellow for text\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\ndef day_to_str(day_num):\n    \"\"\"Convert a day number (0-6) to a three-letter day abbreviation.\"\"\"\n    days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']\n    if not 0 <= day_num <= 6:\n        raise ValueError(\"Day number must be between 0 and 6\")\n    return days[day_num]\n\ndef month_to_str(month_num):\n    \"\"\"Convert a month number (1-12) to a three-letter month abbreviation.\"\"\"\n    months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', \n             'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']\n    if not 1 <= month_num <= 12:\n        raise ValueError(\"Month number must be between 1 and 12\")\n    return months[month_num - 1]\n\ndef drawDigit(digit, x, y, width, height, thickness, color):\n    \"\"\"Draw a seven-segment digit on the display.\"\"\"\n    if digit < 0:\n        return\n    segmentOn = segmentMapping[digit]\n\n    # Draw horizontal segments (top, middle, bottom)\n    for i in [0, 3, 6]:\n        if segmentOn[i]:\n            if i == 0:  # top\n                yOffset = 0\n            elif i == 3:  # bottom\n                yOffset = height - thickness\n            else:  # middle\n                yOffset = height // 2 - thickness // 2\n            display.draw_block(x, y + yOffset, width, thickness, color)\n\n    # Draw vertical segments\n    for i in [1, 2, 4, 5]:\n        if segmentOn[i]:\n            if i == 1 or i == 5:  # upper segments\n                startY = y\n                endY = y + height // 2\n            else:  # lower segments\n                startY = y + height // 2\n                endY = y + height\n            xOffset = 0 if (i == 4 or i == 5) else width - thickness\n            display.draw_block(x + xOffset, startY, thickness, endY - startY, color)\n\ndef draw_colon(x, y, color):\n    \"\"\"Draw the blinking colon between hours and minutes.\"\"\"\n    display.draw_block(x, y, 4, 4, color)\n    display.draw_block(x, y + 14, 4, 4, color)\n\ndef update_screen(year, month, day, hour, minute, second, weekday):\n    \"\"\"Update the entire display with the current time and date.\"\"\"\n    # Clear screen with background color\n    display.fill_screen(BACKGROUND_COLOR)\n\n    # Set text color for the date\n    display._color = TEXT_COLOR\n\n    # Display the date at the top\n    date_str = f\"{day_to_str(weekday)} {month_to_str(month)} {day} {year}\"\n    display.p_string(4, 4, date_str)\n\n    # Convert 24-hour to 12-hour format\n    display_hour = hour if hour <= 12 else hour - 12\n    if display_hour == 0:\n        display_hour = 12\n\n    # Calculate digit positions\n    left_margin = -15\n    y_offset = 30\n    digit_width = 32\n    digit_height = 50\n    digit_spacing = 42\n    digit_thickness = 6\n\n    # Split hours and minutes into digits\n    hour_ten = display_hour // 10 if display_hour >= 10 else -1\n    hour_right = display_hour % 10\n    minute_ten = minute // 10\n    minute_right = minute % 10\n\n    # Draw all digits\n    drawDigit(hour_ten, left_margin, y_offset, digit_width, digit_height, digit_thickness, DIGIT_COLOR)\n    drawDigit(hour_right, left_margin + digit_spacing, y_offset, digit_width, digit_height, digit_thickness, DIGIT_COLOR)\n    drawDigit(minute_ten, left_margin + 2*digit_spacing+10, y_offset, digit_width, digit_height, digit_thickness, DIGIT_COLOR)\n    drawDigit(minute_right, left_margin + 3*digit_spacing+10, y_offset, digit_width, digit_height, digit_thickness, DIGIT_COLOR)\n\n    # Draw the colon if it's an even second (for blinking effect)\n    if second % 2:\n        draw_colon(left_margin + digit_spacing + digit_width + 8, y_offset + 15, DIGIT_COLOR)\n\n    # Display AM/PM indicator\n    display._color = TEXT_COLOR\n    am_pm_str = \"PM\" if hour >= 12 else \"AM\"\n    display.p_string(left_margin + 3*digit_spacing + digit_width, y_offset + 60, am_pm_str)\n\n    # Display seconds\n    display.p_string(4, y_offset + digit_height + 10, f\"{second}\")\n\n# Main loop\nwhile True:\n    now = localtime()\n    year = now[0]\n    month = now[1]\n    day = now[2]\n    hour = now[3]\n    minute = now[4]\n    second = now[5]\n    weekday = now[6]\n\n    update_screen(year, month, day, hour, minute, second, weekday)\n    sleep(1)\n
"},{"location":"kits/st7735/hello-world/","title":"Hello World","text":"
# ST7735 LCD Test\nimport machine\nimport ST7735\n\n# SCL=2 and SDK=3 implied for bus 0\nspi = machine.SPI(0, baudrate=8000000)\nd = ST7735.ST7735(spi, rst=4, ce=6, dc=5)\nd.reset()\nd.begin()\nd.set_rotation(1)\nd._bground = 0xffff\n# white\nd.fill_screen(d._bground)\n# make background all white\nd._color = 0x0000 # black ink\nd.p_string(10,10,'Hello World!')\n
"},{"location":"kits/st7735/hello-world/#config-file","title":"Config File","text":"
# Hardware configuration file for ST7735 clock\n\nSPI_BUS = 0\nSPI_SCL_PIN = 2 # Clock\nSPI_SDA_PIN = 3 # labeled SDI(MOSI) on the back of the display\nSPI_RESET_PIN = 4 # Reset\nSPI_DC_PIN = 5 # Data/command\nSPI_CS_PIN = 6 # Chip Select\n\n# Screen Dimensions\nDISPLAY_WIDTH=160\nDISPLAY_HEIGHT=128\nDISPLAY_ROTATION = 1\n\n# Use builtin pull ups\nMODE_PIN = 14 # one up from lower-left corner\nINCREMENT_PIN = 15 # lower left corner with USB on top\nDECREMENT_PIN = 16 # lower left corner with USB on top\n
"},{"location":"kits/st7735/multiple-fonts/","title":"Multiple Fonts","text":"

Looking at the provided code, I can help explain the font situation and suggest some improvements for working with larger fonts on the ST7735 display.

Currently, the code is using a basic 5x7 pixel font (\"font5x7.fnt\") which is indeed quite small and pixelated. There are several ways to handle larger, better-looking fonts on this display:

"},{"location":"kits/st7735/multiple-fonts/#1-bdfpcf-fonts","title":"1. BDF/PCF Fonts:","text":"

You could convert standard BDF/PCF fonts into a custom format that works with your display. Here's how we could implement this:

To use this improved font system, you would:

  1. Convert a BDF font to our binary format using the convert_bdf_to_binary() function
  2. Load and use the font in your main code like this:
from custom_font_handler import FontHandler\n\n# Initialize your display as before\nspi = machine.SPI(0, baudrate=8000000)\nd = ST7735.ST7735(spi, rst=4, ce=6, dc=5)\nd.reset()\nd.begin()\n\n# Create font handler and load font\nfont_handler = FontHandler(d)\nfont_handler.load_font('your_converted_font.bin')\n\n# Use the font\nfont_handler.text(10, 10, \"Hello World!\", d.rgb_to_565(255, 255, 255))\n
"},{"location":"kits/st7735/multiple-fonts/#2-bitmap-fonts","title":"2. Bitmap Fonts:","text":"

Another approach would be to store pre-rendered characters as bitmap images and display them using your existing bmp() function. This would give you the best quality but would use more storage space.

"},{"location":"kits/st7735/multiple-fonts/#3-vector-fonts","title":"3. Vector Fonts:","text":"

While more complex, you could implement basic vector font rendering. This would give you scalable text but would require more processing power.

For the best balance of quality and performance, I recommend going with the BDF font approach (option 1). You can find many BDF fonts online, including larger sizes that would look better on your display. Some good fonts to start with would be:

To get started, you would:

  1. Download a BDF font
  2. Convert it using the provided converter
  3. Copy the binary font file to your Pico
  4. Use the FontHandler class to render text

Would you like me to provide an example using a specific BDF font, or would you prefer help implementing one of the other approaches?

"},{"location":"kits/st7735/scaling-fonts/","title":"Scaling Fonts","text":"

We can modify the code to use a larger font for the date. Looking at the ST7735.py code, I notice it's using a built-in font5x7 font, but there's no built-in option for larger fonts. However, we can draw larger text by scaling up each character.

from machine import Pin, SPI\nfrom utime import sleep, localtime\nimport ST7735\nimport config\n\n# Initialize SPI and display\nspi = machine.SPI(config.SPI_BUS, \n                  sck=Pin(config.SPI_SCL_PIN),\n                  mosi=Pin(config.SPI_SDA_PIN),\n                  baudrate=8000000)\n\ndisplay = ST7735.ST7735(spi, \n                        rst=config.SPI_RESET_PIN,\n                        ce=config.SPI_CS_PIN,\n                        dc=config.SPI_DC_PIN)\ndisplay.reset()\ndisplay.begin()\ndisplay.set_rotation(config.DISPLAY_ROTATION)\n\n# Define colors using RGB565 format\nBACKGROUND_COLOR = display.rgb_to_565(0, 0, 255)  # Blue background\nDIGIT_COLOR = display.rgb_to_565(255, 255, 255)   # White for digits\nTEXT_COLOR = display.rgb_to_565(255, 255, 0)      # Yellow for text\n\n# Scaled text drawing functions\ndef draw_scaled_char(display, x, y, ch, scale=1):\n    \"\"\"Draw a single character at the specified scale\"\"\"\n    fp = (ord(ch)-0x20) * 5\n    f = open('font5x7.fnt','rb')\n    f.seek(fp)\n    b = f.read(5)\n    char_buf = bytearray(b)\n    char_buf.append(0)\n\n    # Scale up the character\n    char_image = bytearray()\n    for bit in range(8):\n        # Repeat each row scale times\n        for _ in range(scale):\n            for c in range(6):\n                # Repeat each pixel scale times\n                pixel = ((char_buf[c]>>bit) & 1)>0\n                for _ in range(scale):\n                    if pixel:\n                        char_image.append(display._color >> 8)\n                        char_image.append(display._color & 0xff)\n                    else:\n                        char_image.append(display._bground >> 8)\n                        char_image.append(display._bground & 0xff)\n    display.draw_bmp(x, y, 6*scale, 8*scale, char_image)\n    f.close()\n\ndef draw_scaled_string(display, x, y, text, scale=1):\n    \"\"\"Draw a string of text at the specified scale\"\"\"\n    for ch in text:\n        draw_scaled_char(display, x, y, ch, scale)\n        x += 6 * scale\n\n# Previous state tracking\nprev_date = \"\"\nprev_hour_ten = -1\nprev_hour_right = -1\nprev_minute_ten = -1\nprev_minute_right = -1\nprev_second = -1\nprev_am_pm = \"\"\nscreen_initialized = False\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\ndef day_to_str(day_num):\n    \"\"\"Convert a day number (0-6) to a three-letter day abbreviation.\"\"\"\n    days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']\n    if not 0 <= day_num <= 6:\n        raise ValueError(\"Day number must be between 0 and 6\")\n    return days[day_num]\n\ndef month_to_str(month_num):\n    \"\"\"Convert a month number (1-12) to a three-letter month abbreviation.\"\"\"\n    months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', \n             'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']\n    if not 1 <= month_num <= 12:\n        raise ValueError(\"Month number must be between 1 and 12\")\n    return months[month_num - 1]\n\ndef clear_digit_area(x, y, width, height, color):\n    \"\"\"Clear the area where a digit was previously drawn.\"\"\"\n    display.draw_block(x, y, width, height, color)\n\ndef drawDigit(digit, x, y, width, height, thickness, color):\n    \"\"\"Draw a seven-segment digit on the display.\"\"\"\n    if digit < 0:\n        return\n\n    # Clear the area first\n    clear_digit_area(x, y, width, height, BACKGROUND_COLOR)\n\n    segmentOn = segmentMapping[digit]\n\n    # Draw horizontal segments (top, middle, bottom)\n    for i in [0, 3, 6]:\n        if segmentOn[i]:\n            if i == 0:  # top\n                yOffset = 0\n            elif i == 3:  # bottom\n                yOffset = height - thickness\n            else:  # middle\n                yOffset = height // 2 - thickness // 2\n            display.draw_block(x, y + yOffset, width, thickness, color)\n\n    # Draw vertical segments\n    for i in [1, 2, 4, 5]:\n        if segmentOn[i]:\n            if i == 1 or i == 5:  # upper segments\n                startY = y\n                endY = y + height // 2\n            else:  # lower segments\n                startY = y + height // 2\n                endY = y + height\n            xOffset = 0 if (i == 4 or i == 5) else width - thickness\n            display.draw_block(x + xOffset, startY, thickness, endY - startY, color)\n\ndef draw_colon(x, y, color):\n    \"\"\"Draw the blinking colon between hours and minutes.\"\"\"\n    display.draw_block(x, y, 4, 4, color)\n    display.draw_block(x, y + 14, 4, 4, color)\n\ndef update_screen(year, month, day, hour, minute, second, weekday):\n    \"\"\"Update only the changing parts of the display.\"\"\"\n    global prev_date, prev_hour_ten, prev_hour_right, prev_minute_ten\n    global prev_minute_right, prev_second, prev_am_pm, screen_initialized\n\n    # Initialize screen on first run\n    if not screen_initialized:\n        display.fill_screen(BACKGROUND_COLOR)\n        screen_initialized = True\n\n    # Calculate positions\n    left_margin = -15\n    y_offset = 30\n    digit_width = 32\n    digit_height = 50\n    digit_spacing = 42\n    digit_thickness = 6\n\n    # Convert 24-hour to 12-hour format\n    display_hour = hour if hour <= 12 else hour - 12\n    if display_hour == 0:\n        display_hour = 12\n\n    # Format date string - use 2-digit year to save space\n    date_str = f\"{day_to_str(weekday)} {month_to_str(month)} {day} {str(year)[2:]}\"\n\n    # Update date if changed\n    if date_str != prev_date:\n        display._color = TEXT_COLOR\n        # Clear previous date area (increased height for larger font)\n        display.draw_block(4, 4, 160, 20, BACKGROUND_COLOR)\n        draw_scaled_string(display, 4, 4, date_str, scale=2)  # Scale up the date text\n        prev_date = date_str\n\n    # Split time into digits\n    hour_ten = display_hour // 10 if display_hour >= 10 else -1\n    hour_right = display_hour % 10\n    minute_ten = minute // 10\n    minute_right = minute % 10\n\n    # Update digits only if they've changed\n    if hour_ten != prev_hour_ten:\n        drawDigit(hour_ten, left_margin, y_offset, digit_width, digit_height, digit_thickness, DIGIT_COLOR)\n        prev_hour_ten = hour_ten\n\n    if hour_right != prev_hour_right:\n        drawDigit(hour_right, left_margin + digit_spacing, y_offset, digit_width, digit_height, digit_thickness, DIGIT_COLOR)\n        prev_hour_right = hour_right\n\n    if minute_ten != prev_minute_ten:\n        drawDigit(minute_ten, left_margin + 2*digit_spacing+10, y_offset, digit_width, digit_height, digit_thickness, DIGIT_COLOR)\n        prev_minute_ten = minute_ten\n\n    if minute_right != prev_minute_right:\n        drawDigit(minute_right, left_margin + 3*digit_spacing+10, y_offset, digit_width, digit_height, digit_thickness, DIGIT_COLOR)\n        prev_minute_right = minute_right\n\n    # Handle colon blinking - clear and redraw based on seconds\n    colon_x = left_margin + digit_spacing + digit_width + 8\n    colon_y = y_offset + 15\n    if second % 2:\n        draw_colon(colon_x, colon_y, DIGIT_COLOR)\n    else:\n        draw_colon(colon_x, colon_y, BACKGROUND_COLOR)\n\n    # Update AM/PM indicator if needed\n    am_pm_str = \"PM\" if hour >= 12 else \"AM\"\n    if am_pm_str != prev_am_pm:\n        display._color = TEXT_COLOR\n        display.draw_block(left_margin + 3*digit_spacing + digit_width, y_offset + 60, 20, 10, BACKGROUND_COLOR)\n        display.p_string(left_margin + 3*digit_spacing + digit_width, y_offset + 60, am_pm_str)\n        prev_am_pm = am_pm_str\n\n    # Update seconds display if changed\n    if second != prev_second:\n        display._color = TEXT_COLOR\n        display.draw_block(4, y_offset + digit_height + 10, 20, 10, BACKGROUND_COLOR)\n        display.p_string(4, y_offset + digit_height + 10, f\"{second:02d}\")\n        prev_second = second\n\n# Main loop\nwhile True:\n    now = localtime()\n    year = now[0]\n    month = now[1]\n    day = now[2]\n    hour = now[3]\n    minute = now[4]\n    second = now[5]\n    weekday = now[6]\n\n    update_screen(year, month, day, hour, minute, second, weekday)\n    sleep(0.1)  # Shorter sleep for more responsive updates\n

We have made several changes to support larger text:

Now the date should be displayed at double the original size. The scaling works by multiplying each pixel in the original font, so a scale of 2 means each pixel becomes a 2x2 block of pixels, making the text larger while maintaining its proportions.

You can adjust the scale factor by changing the scale=2 parameter in the p_string call. For example:

scale=1 is the original size scale=2 doubles the size scale=3 triples the size

"},{"location":"kits/st7735/set-mode-buttons/","title":"Set the Mode Buttons","text":"

We need a way to monitor the Set/Mode, Increment and Decrement buttons to manually set the time. Here is a program that will monitor the three buttons and change the mode.

from utime import sleep, ticks_ms, ticks_diff\nfrom machine import Pin\n\n# Global Variable Default Settings\nmode = 0\nhour = 6\nminute = 30\npm_indicator = True\nmode_names = ['run', 'set hour', 'set minute', 'set AM/PM']\nmode_count = len(mode_names)\n\n# Button Pin GPIO Configuration\nCHANGE_MODE_PIN = 13\nINCREMENT_PIN = 14\nDECREMENT_PIN = 15\nDEBOUNCE_TIME_MS = 200\n\n# Create 3 button objects as inputs with pullup resistors\nchange_mode_pin = Pin(CHANGE_MODE_PIN, Pin.IN, Pin.PULL_UP)\nincrement_pin = Pin(INCREMENT_PIN, Pin.IN, Pin.PULL_UP)\ndecrement_pin = Pin(DECREMENT_PIN, Pin.IN, Pin.PULL_UP)\n\n# Variables to track last button press times for debouncing\nlast_mode_press = 0\nlast_increment_press = 0\nlast_decrement_press = 0\n\n# These functions gets called every time a button is pressed\ndef mode_button_pressed(pin):\n    global mode, last_mode_press\n    current_time = ticks_ms()\n    if ticks_diff(current_time, last_mode_press) > DEBOUNCE_TIME_MS:\n        mode += 1\n        mode = mode % mode_count\n        last_mode_press = current_time\n\ndef increment_button_pressed(pin):\n    global mode, hour, minute, pm_indicator, last_increment_press\n    current_time = ticks_ms()\n    if ticks_diff(current_time, last_increment_press) > DEBOUNCE_TIME_MS:\n        if mode == 1:\n            hour = (hour % 12) + 1  # Increment hour from 1-12 properly\n        if mode == 2:\n            minute += 1\n            minute = minute % 60  # Fixed: minute should go from 0 to 59\n        if mode == 3:\n            pm_indicator = not pm_indicator\n        last_increment_press = current_time\n\ndef decrement_button_pressed(pin):\n    global mode, hour, minute, pm_indicator, last_decrement_press\n    current_time = ticks_ms()\n    if ticks_diff(current_time, last_decrement_press) > DEBOUNCE_TIME_MS:\n        if mode == 1:\n            hour -= 1\n            if hour <= 0:  # Handle wrapping from 1 to 12\n                hour = 12\n        if mode == 2:\n            minute -= 1\n            if minute < 0:  # Handle wrapping from 0 to 59\n                minute = 59\n        if mode == 3:\n            pm_indicator = not pm_indicator\n        last_decrement_press = current_time\n\n# Register the button handler functions using the irq setter method for each pin\nchange_mode_pin.irq(trigger=Pin.IRQ_FALLING, handler=mode_button_pressed)\nincrement_pin.irq(trigger=Pin.IRQ_FALLING, handler=increment_button_pressed)\ndecrement_pin.irq(trigger=Pin.IRQ_FALLING, handler=decrement_button_pressed)\n\n# Only print on change - these variables store the old values\nlast_mode = mode\nlast_hour = hour\nlast_minute = minute\nlast_pm_indicator = pm_indicator\nprint(\"Default values: mode:\", mode, \"hour:\", hour, \"minute:\", minute, \"pm indicator:\", pm_indicator)\n\nwhile True:\n    if mode != last_mode:\n        print(\"new mode:\", mode, mode_names[mode])\n        last_mode = mode\n    if hour != last_hour:\n        print(\"new hour:\", hour)\n        last_hour = hour\n    if minute != last_minute:\n        print(\"new minute:\", minute)\n        last_minute = minute\n    if pm_indicator != last_pm_indicator:\n        print(\"new pm indicator:\", pm_indicator)\n        last_pm_indicator = pm_indicator\n    # sleep(.01)\n
"},{"location":"kits/st7735/single-digit/","title":"Draw a Single Digit on the ST7735 LCD","text":"
# Draw a single digit on the ST7735 LCD\nimport machine\nimport ST7735\nfrom utime import sleep\n\n# Initialize SPI and display\nspi = machine.SPI(0, baudrate=8000000)\nd = ST7735.ST7735(spi, rst=4, ce=6, dc=5)\nd.reset()\nd.begin()\nd.set_rotation(1)  # Rotate to landscape mode\n\n# Define colors using RGB565 format\nBLUE_BACKGROUND = d.rgb_to_565(0, 0, 255)  # Blue background\nWHITE_DIGIT = d.rgb_to_565(255, 255, 255)  # White for digits\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\ndef drawDigit(digit, x, y, width, height, thickness, color):\n    # Get segments that are on for this digit\n    segmentOn = segmentMapping[digit]\n\n    # Draw 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            elif i == 3:  # bottom\n                yOffset = height - thickness\n            else:  # middle\n                yOffset = height // 2 - thickness // 2\n\n            d.draw_block(x, y + yOffset, width, thickness, color)\n\n    # Draw vertical segments (upper right, lower right, lower left, upper left)\n    for i in [1, 2, 4, 5]:\n        if segmentOn[i]:\n            # Set vertical position\n            if i == 1 or i == 5:  # upper segments\n                startY = y\n                endY = y + height // 2\n            else:  # lower segments\n                startY = y + height // 2\n                endY = y + height\n\n            # Set horizontal position\n            if i == 4 or i == 5:  # left segments\n                xOffset = 0\n            else:  # right segments\n                xOffset = width - thickness\n\n            d.draw_block(x + xOffset, startY, thickness, endY - startY, color)\n\ndef update_screen(digit_val):\n    # Clear screen with blue background\n    d.fill_screen(BLUE_BACKGROUND)\n\n    # Draw the digit in white\n    drawDigit(digit_val, 50, 40, 40, 60, 8, WHITE_DIGIT)\n\n    # Add text label\n    d._color = WHITE_DIGIT  # Set text color to white\n    d.p_string(10, 10, f'Digit: {digit_val}')\n\n# Main loop\ncounter = 0\nwhile True:\n    update_screen(counter)\n    sleep(1)\n    counter = (counter + 1) % 10\n
"},{"location":"kits/stopwatch/","title":"Stopwatch","text":""},{"location":"kits/stopwatch/#overview","title":"Overview","text":"

Here is a two-button stopwatch that uses the OLED display. The first button is called the Start/Stop button. When you first press start it starts to add up the time since it was pressed. Pressing it again will stop collecting time but still remember the total accumulated time while the stopwatch was running.

The second button is the Reset button. When you press Reset the total time accumulated will be reset back to 0.

"},{"location":"kits/stopwatch/#the-microsimulation","title":"The MicroSimulation","text":""},{"location":"kits/stopwatch/#program-structure","title":"Program Structure","text":"

Let me break down this stopwatch program in a way that's easy to understand!

Let's think of this like building a digital stopwatch step by step:

"},{"location":"kits/stopwatch/#step-1-the-parts-hardware-setup","title":"STEP 1 - The Parts (\"Hardware Setup\")","text":""},{"location":"kits/stopwatch/#step-2-setting-up-our-variables-global-variables","title":"STEP 2 - Setting Up Our Variables (\"Global Variables\")","text":""},{"location":"kits/stopwatch/#step-3-the-startstop-button-function","title":"STEP 3 - The Start/Stop Button Function","text":""},{"location":"kits/stopwatch/#step-4-the-reset-button-function","title":"STEP 4 - The Reset Button Function","text":""},{"location":"kits/stopwatch/#step-5-making-time-look-nice-format_time","title":"STEP 5 - Making Time Look Nice (\"format_time\")","text":""},{"location":"kits/stopwatch/#step-6-updating-the-display","title":"STEP 6 - Updating the Display","text":""},{"location":"kits/stopwatch/#step-7-the-main-program-loop","title":"STEP 7 - The Main Program Loop","text":"

Think of it like this: Imagine you're timing a race with a regular stopwatch. You: 1. Press start when the race begins (Start button) 2. Press stop when someone finishes (Stop button) 3. Can pause and restart for multiple racers (Start/Stop button again) 4. Clear it to zero for a new race (Reset button)

This program does all that, but digitally! The main difference is that it's much more precise - it can measure down to milliseconds (thousandths of a second), which would be impossible with a regular handheld stopwatch.

"},{"location":"kits/stopwatch/#sample-code","title":"Sample Code","text":"
from mp_button import Button\nfrom machine import Pin, SPI\nimport ssd1306\nfrom utime import sleep, ticks_ms, ticks_diff\n\n# lower right corner - button closes path to GND\n# HARDWARE PIN CONFIGURATION\n# LEDs\nPICO_ONBOARD_LED_PIN = 25\n# Button pins\nSTOPWATCH_START_STOP_PIN = 14\nSTOPWATCH_RESET_PIN = 15\n# Display pins\nDISPLAY_SCL_PIN = 2\nDISPLAY_SDA_PIN = 3\nDISPLAY_RES_PIN = 4\nDISPLAY_DC_PIN = 5\nDISPLAY_CS_PIN = 6\n\n# configure the pins\nstart_stop_pin = Pin(STOPWATCH_START_STOP_PIN, Pin.IN, Pin.PULL_UP)\nreset_pin = Pin(STOPWATCH_RESET_PIN, Pin.IN, Pin.PULL_UP)\n\nled = machine.Pin(PICO_ONBOARD_LED_PIN, machine.Pin.OUT)\n\nSCL=Pin(DISPLAY_SCL_PIN)\nSDA=Pin(DISPLAY_SDA_PIN)\nspi=SPI(0, sck=SCL, mosi=SDA)\nRES = Pin(DISPLAY_RES_PIN)\nDC = Pin(DISPLAY_DC_PIN)\nCS = machine.Pin(DISPLAY_CS_PIN)\noled = ssd1306.SSD1306_SPI(128, 64, spi, DC, RES, CS)\n\n# Global variables\nSTOPPED = const(0)\nRUNNING = const(1)\nstopwatch_state = STOPPED\nstopwatch_starttime = 0\nstopwatch_elapsed_time = 0  # Renamed from stopwatch_resume_time for clarity\nlast_start_stop_press = 0\nlast_reset_press = 0\nDEBOUNCE_MS = 250\n\ndef start_stop_irq(pin):\n    global last_start_stop_press, stopwatch_state, stopwatch_starttime, stopwatch_elapsed_time\n    current_time = ticks_ms()\n    if ticks_diff(current_time, last_start_stop_press) > DEBOUNCE_MS:\n        last_start_stop_press = current_time\n\n        if stopwatch_state == STOPPED:\n            stopwatch_state = RUNNING\n            stopwatch_starttime = ticks_ms()\n        else:\n            # Calculate the time elapsed since last start\n            stopwatch_elapsed_time += ticks_diff(ticks_ms(), stopwatch_starttime)\n            stopwatch_state = STOPPED\n\ndef reset_irq(pin):\n    global last_reset_press, stopwatch_state, stopwatch_elapsed_time\n    current_time = ticks_ms()\n    if ticks_diff(current_time, last_reset_press) > DEBOUNCE_MS:\n        last_reset_press = current_time\n        stopwatch_state = STOPPED\n        stopwatch_elapsed_time = 0\n\n# Here are the Interupt handlers\nstart_stop_pin.irq(trigger=Pin.IRQ_RISING, handler=start_stop_irq)\nreset_pin.irq(trigger=Pin.IRQ_FALLING, handler=reset_irq)\n\n\ndef format_time(milliseconds):\n    \"\"\"Convert milliseconds to formatted time string (MM:SS.mmm)\"\"\"\n    seconds = milliseconds // 1000\n    ms = milliseconds % 1000\n    minutes = seconds // 60\n    seconds = seconds % 60\n    return f\"{minutes:02d}:{seconds:02d}.{ms:03d}\"\n\ndef update_screen(state, elapsed_time):\n    global stopwatch_starttime\n    oled.fill(0)\n    oled.text(\"stopwatch lab\", 0, 0, 1)\n    state_text = \"RUNNING\" if state == RUNNING else \"STOPPED\"\n    oled.text(state_text, 0, 20, 1)\n\n    if state == RUNNING:\n        current_time = elapsed_time + ticks_diff(ticks_ms(), stopwatch_starttime)\n        oled.text(format_time(current_time), 0, 40, 1)\n    else:\n        oled.text(format_time(elapsed_time), 0, 40, 1)\n    oled.show()\n\n# Main loop\nwhile True:\n    update_screen(stopwatch_state, stopwatch_elapsed_time)\n    sleep(0.1)\n
"},{"location":"kits/tm1637/","title":"TM1637 LED Display","text":"

Setting the Time

The TM1637 LED digital display is an incredibly popular and cost-effective 4-digit 7-segment display module widely used in hobbyist electronics and educational projects. At around 30 cents per unit when purchased in bulk, it offers exceptional value for clock projects, counters, and basic numeric displays.

Key features: - 4 digits with decimal points - Built-in controller chip that handles multiplexing - Simple 2-wire interface (clock and data) - 3.3V or 5V compatible - Brightness control - Colon indicator for clock displays

What makes the TM1637 particularly appealing for MicroPython projects is its straightforward programming interface. Using just two pins (clock and data), you can control all segments and digits through a simple serial protocol. The module handles all the multiplexing internally, making it much easier to use than raw 7-segment displays.

The TM1637 LED Clock is a low-cost way to get started. It is simple to setup and use and only requires the user to make 4 connections.

"},{"location":"kits/tm1637/#parts-list","title":"Parts List","text":"
  1. 1/2 Size 400 tie solderless Breadboard
  2. 4 Male-Female 20cm jumper wires (Dupont Cables)
  3. Raspberry Pi Pico (or Pico W)
  4. TM1637 LED Clock display
  5. Real time clock
"},{"location":"kits/tm1637/#connection-diagram","title":"Connection Diagram","text":""},{"location":"kits/tm1637/#the-micropython-preamble","title":"The MicroPython Preamble","text":"
import tm1637\nfrom machine import Pin, RTC\nfrom utime import sleep, localtime, ticks_ms\n\n# Initialize display\ntm = tm1637.TM1637(clk=Pin(0), dio=Pin(1))\n

Here's some example MicroPython code to test various display features using your preamble:

# Basic number display\ntm.numbers(12, 59)  # Display 12:59\nsleep(1)\n\n# Show temperature with decimal\ntm.temperature(23.4)  # Display 23.4\nsleep(1)\n\n# Control brightness (0-7)\ntm.brightness(2)  # Set to medium brightness\n\n# Blank the display\ntm.show('    ')\nsleep(1)\n\n# Show individual digits\ntm.write([0, 1, 2, 3])  # Display 0123\nsleep(1)\n\n# Toggle colon\ntm.show('1234', True)  # Show 12:34 with colon\nsleep(1)\ntm.show('1234', False)  # Show 1234 without colon\nsleep(1)\n\n# Scroll some numbers\nfor i in range(10):\n    tm.number(i * 111)\n    sleep(0.5)\n\n# Test all segments\ntm.write([0x7f, 0x7f, 0x7f, 0x7f])  # All segments on\nsleep(1)\n

Given its popularity, there are several MicroPython libraries available for the TM1637. The example above uses one of the most common libraries. The display's simplicity and reliability have made it a staple in DIY electronics, particularly for clock projects, temperature displays, and basic counters. You'll find it used in countless Arduino and Raspberry Pi Pico projects, making it one of the most recognizable LED display modules in the maker community.

"},{"location":"kits/tm1637/manual-time-setting/","title":"Manually Setting the Time","text":"

Setting the Time

Next, we will create a complete program that implements a clock with manual time setting using the three buttons. This is a great practical example that teaches state management and user input handling.

import tm1637\nfrom machine import Pin, RTC\nfrom utime import sleep, localtime, ticks_ms\n\n# Initialize display and RTC\ntm = tm1637.TM1637(clk=Pin(0), dio=Pin(1))\nrtc = RTC()\n\n# Initialize buttons with pull-up resistors\nmode_btn = Pin(2, Pin.IN, Pin.PULL_UP)\nnext_btn = Pin(3, Pin.IN, Pin.PULL_UP)\nprev_btn = Pin(4, Pin.IN, Pin.PULL_UP)\n\n# Clock modes\nRUNNING = 0\nSET_HOUR = 1\nSET_MINUTE = 2\nSET_AMPM = 3\n\nclass Clock:\n    def __init__(self):\n        self.mode = RUNNING\n        self.hours = 12\n        self.minutes = 0\n        self.is_pm = False\n        self.colon_on = True\n        self.last_button_time = ticks_ms()\n        self.button_debounce = 200  # milliseconds\n        self.load_time_from_rtc()\n\n    def load_time_from_rtc(self):\n        \"\"\"Get current time from RTC\"\"\"\n        _, _, _, _, hours, minutes, _, _ = rtc.datetime()\n        self.hours = hours % 12\n        if self.hours == 0:\n            self.hours = 12\n        self.is_pm = hours >= 12\n        self.minutes = minutes\n\n    def save_time_to_rtc(self):\n        \"\"\"Save current time to RTC\"\"\"\n        current_time = list(rtc.datetime())\n        hours = self.hours\n        if self.is_pm and hours != 12:\n            hours += 12\n        elif not self.is_pm and hours == 12:\n            hours = 0\n        current_time[4] = hours  # Set hours\n        current_time[5] = self.minutes  # Set minutes\n        rtc.datetime(tuple(current_time))\n\n    def debounce(self):\n        \"\"\"Handle button debouncing\"\"\"\n        current_time = ticks_ms()\n        if current_time - self.last_button_time < self.button_debounce:\n            return False\n        self.last_button_time = current_time\n        return True\n\n    def handle_buttons(self):\n        \"\"\"Process button inputs\"\"\"\n        if not self.debounce():\n            return\n\n        # Mode button cycles through modes\n        if mode_btn.value() == 0:  # Button pressed (active low)\n            self.mode = (self.mode + 1) % 4\n            if self.mode == RUNNING:\n                self.save_time_to_rtc()\n\n        # Next/Previous buttons modify current setting\n        elif next_btn.value() == 0 or prev_btn.value() == 0:\n            increment = -1 if prev_btn.value() == 0 else 1\n\n            if self.mode == SET_HOUR:\n                self.hours = ((self.hours + increment - 1) % 12) + 1\n            elif self.mode == SET_MINUTE:\n                self.minutes = (self.minutes + increment) % 60\n            elif self.mode == SET_AMPM:\n                self.is_pm = not self.is_pm\n\n    def update_display(self):\n        \"\"\"Update the TM1637 display based on current mode and time\"\"\"\n        if self.mode == RUNNING:\n            # Normal time display with blinking colon\n            self.colon_on = not self.colon_on\n        else:\n            # Setting mode - flash the active component\n            flash_on = (ticks_ms() // 500) % 2 == 0\n\n            if self.mode == SET_HOUR:\n                if not flash_on:\n                    tm.show('    ')\n                    return\n            elif self.mode == SET_MINUTE:\n                if not flash_on:\n                    tm.numbers(self.hours, 0)\n                    return\n            elif self.mode == SET_AMPM:\n                if flash_on:\n                    tm.show(' ' + ('P' if self.is_pm else 'A') + ' ')\n                    return\n\n        # Update display\n        tm.numbers(self.hours, self.minutes, colon=self.colon_on)\n\n    def run(self):\n        \"\"\"Main clock loop\"\"\"\n        tm.brightness(2)  # Set initial brightness\n\n        while True:\n            self.handle_buttons()\n\n            if self.mode == RUNNING:\n                self.load_time_from_rtc()\n\n            self.update_display()\n            sleep(0.1)  # Small delay to prevent display flicker\n\n# Create and run the clock\nif __name__ == '__main__':\n    clock = Clock()\n    print(\"Starting clock... Use buttons to set time:\")\n    print(\"Mode: Switch between run/set hour/set minute/set AM,PM\")\n    print(\"Next/Prev: Adjust current setting\")\n    clock.run()\n

Key concepts and features of this implementation:

  1. Button Handling
  2. Uses pull-up resistors (buttons connect to ground when pressed)
  3. Implements debouncing to prevent multiple triggers
  4. Buttons are active-low (0 when pressed, 1 when released)

  5. Mode System

  6. RUNNING: Normal clock operation
  7. SET_HOUR: Adjust hours (1-12)
  8. SET_MINUTE: Adjust minutes (0-59)
  9. SET_AMPM: Toggle between AM and PM

  10. Visual Feedback

  11. Selected component flashes when being set
  12. Colon blinks in running mode
  13. Special AM/PM display during setting

  14. Time Management

  15. Maintains time in 12-hour format internally
  16. Converts to/from 24-hour format for RTC
  17. Handles midnight/noon edge cases

  18. State Management

  19. Uses a class to organize state and behavior
  20. Separates display, button handling, and time management

Common challenges students might encounter:

  1. Button Debouncing
  2. Understanding why debouncing is necessary
  3. Adjusting debounce timing for reliable operation

  4. Time Format Conversion

  5. Converting between 12/24 hour formats
  6. Handling edge cases (12 AM/PM)

  7. Display Updates

  8. Managing display refresh rate
  9. Creating smooth visual feedback

Suggested exercises for students:

  1. Add a temperature display mode
  2. Implement a brightness adjustment feature
  3. Add an alarm setting mode
  4. Save settings to flash memory
  5. Add a battery backup indicator

Would you like me to explain any part in more detail or provide examples of these extensions?

"},{"location":"kits/tm1637/running-clock/","title":"Full Running Clock Using the TM1637","text":"

I'll create a detailed example of a running clock using the TM1637 display. This builds on some of the concepts shown in the clock-driver.py file you shared, but simplified for the TM1637.

import tm1637\nfrom machine import Pin, RTC\nfrom utime import sleep, localtime\n\n# Initialize the display\ntm = tm1637.TM1637(clk=Pin(0), dio=Pin(1))\n\n# Initialize the RTC (Real Time Clock)\nrtc = RTC()\n\ndef set_initial_time():\n    \"\"\"\n    Set an initial time if RTC isn't configured.\n    In a real project, you'd sync this with a time server or user input.\n    \"\"\"\n    # Format: (year, month, day, weekday, hours, minutes, seconds, subseconds)\n    rtc.datetime((2024, 12, 28, 5, 12, 0, 0, 0))\n\ndef format_time(hours, minutes):\n    \"\"\"\n    Convert 24-hour time to 12-hour format\n    Returns hours and whether it's PM\n    \"\"\"\n    is_pm = hours >= 12\n    if hours > 12:\n        hours -= 12\n    elif hours == 0:\n        hours = 12\n    return hours, is_pm\n\ndef run_clock():\n    # Set initial brightness (0-7)\n    tm.brightness(2)\n\n    # Track colon state for blinking\n    colon_on = True\n\n    while True:\n        # Get current time from RTC\n        year, month, day, weekday, hours, minutes, seconds, _ = rtc.datetime()\n\n        # Convert to 12-hour format\n        display_hours, is_pm = format_time(hours, minutes)\n\n        # Toggle colon every second\n        if seconds != prev_seconds:\n            colon_on = not colon_on\n            prev_seconds = seconds\n\n        # Display the time\n        tm.numbers(display_hours, minutes, colon=colon_on)\n\n        # Brief pause to prevent display flicker\n        sleep(0.1)\n\nif __name__ == '__main__':\n    set_initial_time()  # Only needed if RTC isn't set\n    print(\"Starting clock...\")\n    run_clock()\n
"},{"location":"kits/tm1637/running-clock/#core-concepts","title":"Core Concepts","text":"

Let's break down the key concepts students need to understand:

"},{"location":"kits/tm1637/running-clock/#1-real-time-clock-rtc","title":"1. Real-Time Clock (RTC)","text":""},{"location":"kits/tm1637/running-clock/#2-time-formats","title":"2. Time Formats","text":""},{"location":"kits/tm1637/running-clock/#3-display-multiplexing","title":"3. Display Multiplexing","text":""},{"location":"kits/tm1637/running-clock/#4-state-management","title":"4. State Management","text":""},{"location":"kits/tm1637/running-clock/#5-program-structure","title":"5. Program Structure","text":""},{"location":"kits/tm1637/running-clock/#common-challenges","title":"Common challenges","text":"

Here are some challenges students might encounter:

"},{"location":"kits/tm1637/running-clock/#1-time-drift","title":"1. Time Drift","text":""},{"location":"kits/tm1637/running-clock/#2-power-management","title":"2. Power Management","text":""},{"location":"kits/tm1637/running-clock/#3-user-interface","title":"3. User Interface","text":""},{"location":"kits/tm1637/running-clock/#extensions","title":"Extensions","text":"

Here are some additional projects that students could try:

  1. Add a temperature display that alternates with the time
  2. Implement automatic brightness control using a light sensor
  3. Add alarm functionality with a buzzer
  4. Create a menu system for setting the time
  5. Add a battery backup system

For reference, this code builds on the concepts shown in our clock-driver.py file, but simplifies the implementation thanks to the TM1637's built-in controller.

"},{"location":"kits/waveshare-e-paper/","title":"Waveshare E-Paper Display","text":"

Black 1.54 inch e-Paper/e-Ink Display Module 152x152 for Arduino,Raspberry Pi

1.54inch e-Paper Module (C)

Data Sheet

BuyDisplay

"},{"location":"kits/waveshare-e-paper/#description","title":"Description","text":"

ER-EPM0154-1B is e-paper/e-ink display module that is 1.54 inch e-paper display panel attached with a breakout board, 152x152 resolution with white/black color, UC8151C controller,running both 3.3V and 5V power supply,super wide viewing angle,sunlight readable and easily controlled by MCU such as PIC, AVR, ARDUINO,ARM and Raspberry PI. One fantastic aspect of the ePaper display is the fact they don't need any power to keep the image on the screen. Just like a Kindle, you can read whatever is on your screen in daylight without any reflection. It's optional for 3-wire or 4-wire serial interface.

We prepared the library and examples for arduino due,mega 2560,uno,nano and for raspberry pi or raspberry pi zero.

"},{"location":"kits/waveshare-e-paper/#waveshare-micropython-driver","title":"Waveshare MicroPython Driver","text":"

The device I purchased is 152x152.

"},{"location":"kits/waveshare-e-paper/#connections","title":"Connections","text":"Pin Name Description 1 VDD Power Supply (square) 2 VSS Ground (round) 3 SDA Serial Data Pin Input/Output (SPI) 4 SCL Serial Clock Pin Input (SPI) 5 /CS Chip Select Input Pin 6 D/C Data /Command Control Pin 7 /RST Reset Input. Low: Reset. 8 BUSY Busy State Output Pin
from machine import Pin\nimport epd1in54b\n\nmosi = Pin(0)\nclk = Pin(1)\nreset = Pin(2)\ndc = Pin(3)\nbusy = Pin(4)\ncs = Pin(5)\n\nepd = epd1in54b.EPD(reset, dc, busy, cs, clk, mosi)\nepd.init()\n\n# initialize the frame buffer\nfb_size = int(epd.width * epd.height / 8)\nframe_black = bytearray(fb_size)\n\nepd.clear_frame(frame_black, frame_red)\n\n# For simplicity, the arguments are explicit numerical coordinates\nepd.draw_rectangle(frame_black, 10, 60, 50, 110, epd1in54b.COLORED)\nepd.draw_line(frame_black, 10, 60, 50, 110, epd1in54b.COLORED)\nepd.draw_line(frame_black, 50, 60, 10, 110, epd1in54b.COLORED)\nepd.draw_circle(frame_black, 120, 80, 30, epd1in54b.COLORED)\n\nepd.display_frame(frame_black)\n
"},{"location":"kits/waveshare-lcd/","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-lcd/#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-lcd/#lessons","title":"Lessons","text":"
  1. Hello world! Lesson
  2. Color Lab
  3. Drawing Analog Hands
  4. 5x8 Fonts
"},{"location":"kits/waveshare-lcd/#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-lcd/#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-lcd/01-hello-world/","title":"Waveshare Hello World","text":""},{"location":"kits/waveshare-lcd/01-hello-world/#step-1-load-the-library","title":"Step 1: Load the Library","text":""},{"location":"kits/waveshare-lcd/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-lcd/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-lcd/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-lcd/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-lcd/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-lcd/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-lcd/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-lcd/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-lcd/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-lcd/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-lcd/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-lcd/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-lcd/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":"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 high 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-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 using the standard localtime() function. We will learn about how to use the eight integers returned by the localtime() functions.

MicroPython provides a very convenient function called localtime() that will get the time from the host computer in your local timezone 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 usually 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 (not 0).
"},{"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.

However, to test that our code is setting the correct local time we will need to disable this in future labs.

After we do this, the localtime() will return the number of seconds after midnight on January 1st, 2021.

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

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

We will used this when we use a RTC and use the Pico \"W\" to 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/01-timekeeping-architecture/","title":"The Architecture of Timekeeping Devices","text":"

Throughout history people have designed clocks that were not just functional, but were things of beauty to be admired. The example above is the famous Astronomical Clock called The Orloj which was built in Prague around 1400. It remains an important tourist attraction in the city.

Today, creating precise feature rich clocks is within the reach of anyone with $10 and a bit of curiosity. Despite the fact that everyone uses their mobile phones to check the time, the landscape of creative and beautiful timekeeping devices continues to expand. For example the market for beautiful Nixie tube clocks is perhaps larger than it has ever been due to the dedication of craftspeople like those people from Millclock in Ukraine.

I personally think that the Nixie Tube clocks, especially those created by Millclock are some of the most pleasing forms of handcrafted artwork I have seen. The amount of engineering that goes into manufacturing these 70 mm high retro-vacuum tubs is incredible. Each wire in the tubes are made and assembled by hand, which is why a price of almost $2,000 for each clock is not surprising.

It is interesting to note that many of the clocks in this course have features similar to the Millclock clocks such as syncing with time servers to get accurate times. So you don't need a huge budget to get a precise clock these days!

Regardless of your interest in timekeeping be it art, science, astronomy, being a maker, programming or AI, there are projects here to help you build your own highly personalized and customized timepiece using low cost components starting around $10. These devices can be a platform for fun, learning computational thinking and artistic expression.

One of the key things we learn about architecture choices is that there are few solutions that fit everyone's needs. There are always trade offs. You will find simple designs that are low-cost and easy to assemble in an hour. You will also find complex designs that use exotic displays and integrate light and motion sensors to create a personalized experience for your device.

First let take a look at the key architectural components of a timekeeping device.

"},{"location":"lessons/01-timekeeping-architecture/#core-components","title":"Core Components","text":"

Let's start with a high-level view of the core components of an electronic clock. In the diagram above you see the following components:

  1. Sensors - These are things buttons and knobs that help us configure the time and settings of a clock.
  2. Microcontroller - This is the \"brain\" of our clock and where the programs run. In our case we will be using the Raspberry Pi Pico and the Raspberry Pi Pico W (wireless) in our projects.
  3. Communications Bus - this is how we communicated between the microcontroller and the display. In our labs the bus we will use is either a I2C (slower) or the faster SPI bus.
  4. Display - We will be using many types of displays from simple LEDs to complete graphical OLED displays.
  5. Power - Most of our projects are powered by a 5 volt USB cable or batteries.

This diagram skims over a lot of the details like how we add wireless, photosensors to control brightness or adding speakers for sound for our alarm clocks. But all our clocks will share these same basic components or very similar components.

The architecture for our clocks and watches is the same architecture used in a field of computing called Physical Computing where computers read information from the real-world (incoming sensor data) and make changes to the world through actuators (usually things like motors and servos). In the world of timekeeping, most of our output is the display and the speaker. However, there are many fun clock projects that drive motors and servos to change the hands on a clock or change the visibility of a segment in a digit.

Now let's do a little deeper dive into each of these components.

"},{"location":"lessons/01-timekeeping-architecture/#details-of-our-components","title":"Details of our Components","text":""},{"location":"lessons/01-timekeeping-architecture/#sensors-our-inputs","title":"Sensors - Our Inputs","text":""},{"location":"lessons/01-timekeeping-architecture/#buttons","title":"Buttons","text":"

Buttons are the most fundamental input devices in our clock projects. They serve multiple purposes including setting the time, changing modes, and configuring alarms. While they may seem simple, buttons require careful programming consideration to handle issues like switch bounce -- the tendency for mechanical switches to generate multiple signals when pressed once.

In our projects, we typically use momentary push buttons that are normally open, meaning they only complete the circuit when pressed. These buttons are connected to the Pico's GPIO pins configured with internal pull-up resistors. This configuration means the pin reads HIGH normally and LOW when the button is pressed, simplifying our circuit design by eliminating the need for external resistors.

Button debouncing is handled in our code using both hardware (capacitors) and software (timing delays) techniques. The code in our projects typically implements a debounce delay of 50-200 milliseconds, preventing multiple accidental triggers from a single press. This is particularly important when setting time values, where accuracy is crucial.

"},{"location":"lessons/01-timekeeping-architecture/#rotary-encoder-knobs","title":"Rotary Encoder Knobs","text":"

Rotary encoders provide an intuitive way to adjust values like hours and minutes in our clock projects. Unlike potentiometers that have fixed start and end positions, rotary encoders can rotate continuously in either direction. They work by generating two square wave signals (often called A and B) that are offset by 90 degrees, allowing us to determine both the direction and speed of rotation.

The key advantage of rotary encoders in clock projects is their natural mapping to time adjustment -- clockwise rotation increases values while counterclockwise decreases them. Many of our projects use encoders with built-in push buttons, combining rotational input with selection capability in a single component. This allows users to both select what to adjust (by pressing) and make the adjustment (by rotating) with one control.

Programming rotary encoders requires careful attention to interrupt handling, as we need to monitor both signal pins simultaneously to determine direction. Our code typically uses hardware interrupts to detect changes on either pin, then compares the states to determine rotation direction. We also implement acceleration, where faster rotation results in larger value changes, making it efficient to make both small adjustments and large changes.

"},{"location":"lessons/01-timekeeping-architecture/#photosensors","title":"Photosensors","text":"

Photosensors add environmental awareness to our clock projects, enabling automatic brightness adjustment based on ambient light conditions. The most common type we use is the Light Dependent Resistor (LDR), whose resistance varies inversely with light intensity. This simple component allows our displays to be clearly visible in bright daylight while avoiding harsh glare in darkness.

Implementation of photosensors requires understanding analog-to-digital conversion, as these sensors output varying voltage levels rather than digital on/off signals. The Raspberry Pi Pico's ADC capabilities make it straightforward to read these analog values and convert them to usable brightness levels. Our projects typically map the sensor's full range to appropriate brightness values for our specific display type.

Many of our advanced projects combine photosensor data with time-based algorithms to create sophisticated brightness control. For example, we might implement hysteresis to prevent rapid brightness changes in fluctuating light conditions, or combine time-of-day information with light levels to optimize display visibility while conserving power.

"},{"location":"lessons/01-timekeeping-architecture/#the-microcontroller","title":"The MicroController","text":"

The microcontroller serves as the brain of our clock projects, executing the MicroPython code that manages timekeeping, input handling, and display updates. While simple clocks could be built with basic logic circuits, using a microcontroller gives us tremendous flexibility in features and functionality while actually reducing the component count.

Our projects primarily use the RP2040 microcontroller, which offers an excellent balance of processing power, memory, and input/output capabilities. Its dual-core architecture allows us to handle time-critical tasks like display updates on one core while managing user input and other features on the second core. The built-in programmable I/O (PIO) state machines are particularly useful for driving complex displays and handling precise timing requirements.

One of the key advantages of using a microcontroller is the ability to easily add new features through software updates. Want to add a new alarm type or change how the display animates? Simply modify the code and upload it. This flexibility extends to handling different types of time synchronization, from manual setting to NTP time servers when using the wireless-enabled Pico W.

"},{"location":"lessons/01-timekeeping-architecture/#the-raspberry-pi-pico-and-the-rp2040","title":"The Raspberry Pi Pico and the RP2040","text":"

The Raspberry Pi Pico, built around the RP2040 chip, has become a go-to platform for DIY clock projects due to its robust feature set and low cost. The board provides 26 GPIO pins, hardware support for common interfaces like I2C and SPI, and built-in temperature sensing -- all features we leverage in our various clock designs.

What makes the Pico particularly suitable for clock projects is its reliable performance and excellent power management capabilities. The board can run in several power modes, from full-speed operation to low-power sleep modes that help extend battery life in portable projects. The built-in USB programming interface makes development straightforward, while the extensive MicroPython support provides access to a rich ecosystem of libraries and examples.

For more advanced projects, the Pico W variant adds wireless connectivity, enabling features like automatic time synchronization, weather display, and even remote configuration through web interfaces. The board's 2MB of flash storage provides ample space for our MicroPython code and additional features like custom fonts or sound files for alarms.

"},{"location":"lessons/01-timekeeping-architecture/#real-time-clocks-rtc","title":"Real-Time Clocks (RTC)","text":"

Real-Time Clocks are specialized timekeeping devices that maintain accurate time even when the main power is removed. While the Raspberry Pi Pico includes an internal RTC, it resets when power is lost. External RTC modules like the DS3231 provide continuous timekeeping through battery backup, making them essential for reliable clock projects.

The DS3231 is particularly popular in our projects due to its high accuracy and temperature compensation. It communicates with the Pico using the I2C protocol, making it easy to integrate. The module includes a CR2032 battery backup that can maintain accurate time for several years without main power. Beyond basic timekeeping, it provides additional features like programmable alarms and a temperature sensor that helps compensate for timing variations caused by temperature changes.

Working with external RTCs requires understanding both hardware connections and software protocols. Our projects typically use MicroPython's machine.RTC class for the internal RTC, but switch to specific libraries for external RTC modules. This allows us to leverage advanced features like temperature readings and sophisticated alarm settings. The combination of an external RTC with the Pico's internal RTC can provide both accuracy and flexibility, with the external RTC serving as the authoritative time source while the internal RTC handles temporary timing needs.

"},{"location":"lessons/01-timekeeping-architecture/#other-microcontrollers-that-run-micropython","title":"Other Microcontrollers that Run MicroPython","text":"

While our course focuses on the Raspberry Pi Pico, many other microcontrollers support MicroPython and can be used for clock projects. Popular alternatives include the ESP32 family, which offers built-in WiFi and Bluetooth capabilities, and various STM32 boards known for their processing power and extensive peripheral options.

Each platform offers unique advantages. The ESP32, for instance, excels in wireless applications and includes a real-time clock (RTC) with battery backup capabilities. STM32 boards often provide more sophisticated analog interfaces and higher processing speeds, useful for complex display effects or audio generation.

The key advantage of using MicroPython across these platforms is code portability. Many of our basic clock functions can be transferred between different microcontrollers with minimal modification, allowing students to experiment with different hardware while building on their existing knowledge.

"},{"location":"lessons/01-timekeeping-architecture/#our-display","title":"Our Display","text":""},{"location":"lessons/01-timekeeping-architecture/#ledx","title":"LEDx","text":"

LED displays form the foundation of many clock projects, ranging from simple seven-segment displays to complex LED matrices. The most basic projects use individual LEDs for time indication, while more advanced designs employ multiplexed seven-segment displays that can show hours, minutes, and even seconds with minimal microcontroller pins.

Seven-segment displays remain popular due to their excellent visibility and straightforward programming interface. Each digit consists of seven LEDs arranged in a figure-8 pattern, plus an optional decimal point. By controlling which segments are lit, we can display numbers 0-9 and some letters. Multiple digits are typically multiplexed -- rapidly switching between digits faster than the human eye can detect -- to create the illusion of simultaneous display while using fewer GPIO pins.

For more advanced projects, we often use LED matrices that provide greater flexibility in displaying both numbers and custom graphics. These can be controlled through shift registers or specialized LED driver ICs, reducing the number of required microcontroller pins while enabling complex animations and transitions.

"},{"location":"lessons/01-timekeeping-architecture/#graphic-displays-oled","title":"Graphic Displays (OLED)","text":"

OLED displays represent a significant step up in capabilities, offering full graphic control with excellent contrast and wide viewing angles. These displays communicate with the microcontroller via I2C or SPI protocols and can show both text and graphics. The most common resolution in our projects is 128x64 pixels, providing ample space for creative clock faces.

The key advantage of OLED displays is their versatility. They can simulate traditional analog clock faces, display digital time in various fonts and sizes, and show additional information like temperature, humidity, or weather data. The high contrast ratio of OLED technology ensures excellent readability in various lighting conditions, while their low power consumption in displays with dark backgrounds makes them ideal for battery-powered projects.

Programming OLED displays requires understanding basic graphics concepts like framebuffers and drawing primitives. Our projects use libraries that abstract these details while still allowing direct pixel control when needed. We can create smooth animations, implement multiple clock face designs, and even build interactive menus for settings and configuration.

"},{"location":"lessons/01-timekeeping-architecture/#speakers","title":"Speakers","text":"

In clock projects, speakers primarily serve for alarms and hourly chimes, though some advanced projects include more complex audio features. The simplest implementation uses a piezoelectric buzzer directly connected to a GPIO pin, capable of producing basic tones through PWM (Pulse Width Modulation) signals.

For better sound quality, some projects employ small magnetic speakers with audio amplifier circuits. These can reproduce more complex sounds and music, though they require more sophisticated driving circuits and careful power management. The Raspberry Pi Pico's PWM capabilities make it possible to generate various frequencies and even simple melodies without additional hardware.

Advanced projects might include multiple sound effects, customizable alarm tones, or even voice announcements. These typically require additional components like audio DACs (Digital-to-Analog Converters) or dedicated audio modules, along with careful consideration of timing to ensure smooth playback while maintaining accurate timekeeping.

"},{"location":"lessons/01-timekeeping-architecture/#analog-to-digital-converters","title":"Analog to Digital Converters","text":"

Analog to Digital Converters (ADCs) play a crucial role in our clock projects, primarily for reading sensors and implementing volume control for audio features. The Raspberry Pi Pico includes three ADC channels, providing 12-bit resolution for accurate measurements of analog signals.

In clock applications, ADCs are most commonly used with photoresistors for automatic brightness control and potentiometers for user adjustments. The conversion process involves sampling the analog voltage at regular intervals and converting it to digital values our code can process. The Pico's ADC can sample at up to 500,000 samples per second, far exceeding our typical needs for clock projects.

Understanding ADC principles is essential for handling real-world inputs effectively. Our projects implement techniques like oversampling to improve accuracy and filtering to reduce noise in measurements. This knowledge becomes particularly valuable when working with analog sensors for environmental monitoring or audio input processing.

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

Our standard watch kits places two or three 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":"
from 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/05-buttons/#three-button-set","title":"Three Button Set","text":"
from machine import Pin\nimport time\n\n# Pin setup\nmode_pin = Pin(16, Pin.IN, Pin.PULL_UP)\nnext_pin = Pin(17, Pin.IN, Pin.PULL_UP)\nprevious_pin = Pin(18, Pin.IN, Pin.PULL_UP)\n\n# Time state\nhour = 12\nminute = 0\nis_pm = False\nmode = 0\nmode_names = [\"run\", \"set hour\", \"set minute\", \"set AM/PM\"]\nmode_count = len(mode_names)\n\n# Debounce state\nlast_mode_press = 0\nlast_next_press = 0\nlast_prev_press = 0\nDEBOUNCE_MS = 200\n\ndef format_time():\n    return f\"{hour:02d}:{minute:02d} {'PM' if is_pm else 'AM'}\"\n\ndef handle_mode(pin):\n    global mode, last_mode_press\n    current_time = time.ticks_ms()\n    if time.ticks_diff(current_time, last_mode_press) > DEBOUNCE_MS:\n        mode = (mode + 1) % mode_count\n        print(f\"Mode: {mode_names[mode]}\")\n        last_mode_press = current_time\n\ndef handle_next(pin):\n    global hour, minute, is_pm, last_next_press\n    current_time = time.ticks_ms()\n    if time.ticks_diff(current_time, last_next_press) > DEBOUNCE_MS:\n        if mode == 1:  # Set hour\n            hour = (hour % 12) + 1\n        elif mode == 2:  # Set minute\n            minute = (minute + 1) % 60\n        elif mode == 3:  # Toggle AM/PM\n            is_pm = not is_pm\n\n        if mode != 0:\n            print(format_time())\n        last_next_press = current_time\n\ndef handle_previous(pin):\n    global hour, minute, is_pm, last_prev_press\n    current_time = time.ticks_ms()\n    if time.ticks_diff(current_time, last_prev_press) > DEBOUNCE_MS:\n        if mode == 1:  # Set hour\n            hour = ((hour - 2) % 12) + 1\n        elif mode == 2:  # Set minute\n            minute = (minute - 1) % 60\n        elif mode == 3:  # Toggle AM/PM\n            is_pm = not is_pm\n\n        if mode != 0:\n            print(format_time())\n        last_prev_press = current_time\n\n# Set up interrupts\nmode_pin.irq(trigger=Pin.IRQ_FALLING, handler=handle_mode)\nnext_pin.irq(trigger=Pin.IRQ_FALLING, handler=handle_next)\nprevious_pin.irq(trigger=Pin.IRQ_FALLING, handler=handle_previous)\n\n# Main loop\nprint(\"Clock started. Press mode button to change settings.\")\nwhile True:\n    if mode == 0:  # Only update display in run mode\n        print(format_time())\n        time.sleep(1)\n
"},{"location":"lessons/06-setting-time/","title":"Manually Setting Time","text":"

We need a program that will use three buttons to set the time.

"},{"location":"lessons/06-setting-time/#the-mode-button","title":"The Mode Button","text":"

The first button is called the \"mode\" button. It will cycle through four internal \"states\" of our clock. Here are the modes:

  1. Mode 0: Clock Running - this is the normal mode of operation
  2. Mode 1: Setting the Hour
  3. Mode 2: Setting the Minute
  4. Mode 3: Setting AM/PM

Here is a walkthrough of the button-mode-test.py program, breaking it down into clear, digestible concepts for everyone new to MicroPython.

"},{"location":"lessons/06-setting-time/#code-walkthrough","title":"Code Walkthrough","text":""},{"location":"lessons/06-setting-time/#1-basic-setup-and-imports","title":"1. Basic Setup and Imports","text":"
from mp_button import Button\nfrom time import localtime, sleep\nfrom machine import Pin\n

Let's understand what these lines do: - These are import statements that bring in code we need to use - Button is a special module that helps us work with physical buttons - localtime and sleep are time-related functions - Pin lets us work with the physical pins on our Raspberry Pi Pico

"},{"location":"lessons/06-setting-time/#2-setting-up-the-pins","title":"2. Setting Up the Pins","text":"
mode_pin = Pin(16, Pin.IN, Pin.PULL_UP)\nnext_pin = Pin(17, Pin.IN, Pin.PULL_UP)\nprevious_pin = Pin(18, Pin.IN, Pin.PULL_UP)\n

Here's what's happening: - We're setting up three different pins (16, 17, and 18) on the Pico - Each pin is set as an input (Pin.IN) - meaning it receives signals rather than sends them - Pin.PULL_UP means the pin is naturally \"high\" (1) until a button press makes it \"low\" (0) - Think of it like a light switch that's normally on, and pressing the button turns it off

"},{"location":"lessons/06-setting-time/#3-global-variables","title":"3. Global Variables","text":"
counter_pressed = 0\ncounter_released = 0\nmode = 0  # default clock running\nmode_names = [\"run\",\"set hour\",\"set minute\",\"set AM/PM\"]\nmode_count = len(mode_names)\nnow = localtime()\nhours = now[3]\nminutes = now[4]\nam_pm = 0\n

These are our program's variables: - counter_pressed and counter_released keep track of button presses - mode tells us which setting we're currently adjusting (starts at 0) - mode_names is a list of the different modes our clock can be in - mode_count is how many modes we have (4 in this case) - now gets the current time from the Pico - hours and minutes store the current time values - am_pm keeps track of whether it's morning (0) or afternoon (1)

"},{"location":"lessons/06-setting-time/#4-button-handler-functions","title":"4. Button Handler Functions","text":"
def button_mode_irq(button, event):\n    global mode, hours, minutes\n    if event == Button.PRESSED:\n        mode += 1\n        mode = mode % mode_count\n        print('new mode:', mode, mode_names[mode])\n

This is our mode button handler: - global tells Python we want to change variables outside this function - When the button is pressed, we increase the mode by 1 - The % (modulo) operator helps us cycle back to 0 after reaching the last mode - For example: if mode is 3 and we add 1, 4 % 4 = 0, so we go back to the first mode

"},{"location":"lessons/06-setting-time/#5-next-and-previous-button-handlers","title":"5. Next and Previous Button Handlers","text":"
def button_next_irq(button, event):\n    global mode, hours, minutes, am_pm\n    if event == Button.PRESSED:\n        if mode == 1:\n            hours += 1\n        if mode == 2:\n            minutes += 1\n        if mode == 3:\n            am_pm = 1 if am_pm == 0 else 0\n

The next/previous buttons: - They only work when we're in a setting mode (not mode 0) - Mode 1: adjust hours - Mode 2: adjust minutes - Mode 3: toggle between AM and PM - The previous button does the same thing but decreases values

"},{"location":"lessons/06-setting-time/#6-creating-button-objects","title":"6. Creating Button Objects","text":"
button_mode = Button(16, False, button_mode_irq, internal_pullup = True, debounce_time = 100)\nbutton_next = Button(17, False, button_next_irq, internal_pullup = True, debounce_time = 100)\nbutton_previous = Button(18, False, button_previous_irq, internal_pullup = True, debounce_time = 100)\n

Here we create our button objects: - Each button gets a pin number (16, 17, or 18) - We tell it which function to call when pressed (the _irq functions) - debounce_time = 100 prevents multiple triggers from one press - Think of debouncing like waiting a moment to make sure someone really pressed the button once

"},{"location":"lessons/06-setting-time/#7-main-loop","title":"7. Main Loop","text":"
while(True):\n    button_mode.update()\n    button_next.update()\n    button_previous.update()\n

This is our main program loop: - It runs forever (that's what while True means) - Each time through the loop, we check if any buttons were pressed - The update() function handles all the button checking for us

"},{"location":"lessons/06-setting-time/#full-program-listing","title":"Full Program Listing","text":"
from mp_button import Button\nfrom time import localtime, sleep\nfrom machine import Pin\n\nmode_pin = Pin(16, Pin.IN, Pin.PULL_UP)\nnext_pin = Pin(17, Pin.IN, Pin.PULL_UP)\nprevious_pin = Pin(18, Pin.IN, Pin.PULL_UP)\n\n# we create a counter to increment as we press\n# and one to increment as we release\ncounter_pressed = 0\ncounter_released = 0\nmode = 0 # default clock running\nmode_names = [\"run\",\"set hour\",\"set minute\",\"set AM/PM\"]\nmode_count = len(mode_names)\nnow = localtime()\nhours = now[3]\nminutes = now[4]\nam_pm = 0\n\n# the following method (function) will be invoked\n# when the button changes state\n# the Button module expects a callback to handle \n# - pin number\n# - event (Button.PRESSED | Button.RELEASED)\n# the event contains a string 'pressed' or 'released'\n# which can be used in your code to act upon\ndef button_mode_irq(button, event):\n    global mode, hours, minutes\n    if event == Button.PRESSED:\n        mode +=1\n        # cycle back to zero if greater than mode_count\n        mode =  mode % mode_count\n        print('new mode:', mode, mode_names[mode])\n\ndef button_next_irq(button, event):\n    global mode, hours, minutes, am_pm\n    if event == Button.PRESSED:\n        if mode == 1:\n            hours += 1\n        if mode == 2:\n            minutes += 1\n        if mode == 3:\n            if am_pm == 0:\n                am_pm = 1\n            else:\n                am_pm = 0\n        print('next button:', hours, minutes, am_pm)\n\ndef button_previous_irq(button, event):\n    global mode, hours, minutes, am_pm\n    if event == Button.PRESSED:\n        if mode == 1:\n            hours -= 1\n        if mode == 2:\n            minutes -= 1\n        if mode == 3:\n            if am_pm == 0:\n                am_pm = 1\n            else:\n                am_pm = 0\n        print('prev button:', hours, minutes, am_pm)\n\nbutton_mode = Button(16, False, button_mode_irq, internal_pullup = True, debounce_time = 100)\nbutton_next = Button(17, False, button_next_irq, internal_pullup = True, debounce_time = 100)\nbutton_previous = Button(18, False, button_previous_irq, internal_pullup = True, debounce_time = 100)\n\nprint(\"year:\", now[0], \"month:\", now[1], \"day-of-month:\", now[2], \"hours\", now[3], \"minutes:\", now[4])\nwhile(True):\n    button_mode.update()\n    button_next.update()\n    button_previous.update()\n
"},{"location":"lessons/06-setting-time/#the-decrement-time","title":"The Decrement Time","text":"

The following line need some explanation:

hour = ((hour - 2) % 12) + 1\n
Let me break down that line, which handles decrementing hours while staying within the 1-12 range:

Let's walk through it step by step:

  1. First, we subtract 2 from the current hour: (hour - 2)
  2. We subtract 2 (not 1) because we'll add 1 back at the end
  3. This shift is necessary because we want to work with 0-11 for the modulo operation

  4. Then we take modulo 12: % 12

  5. This ensures our number wraps around within 0-11
  6. For example, if hour was 1, then (1-2) = -1, and -1 % 12 = 11

  7. Finally, we add 1: + 1

  8. This shifts our range from 0-11 back to 1-12

Here's an example sequence to show how it works: - Starting at hour = 1: - (1 - 2) = -1 - -1 % 12 = 11 - 11 + 1 = 12 - Starting at hour = 12: - (12 - 2) = 10 - 10 % 12 = 10 - 10 + 1 = 11

This gives us the desired behavior of decrementing through the sequence: 12 \u2192 11 \u2192 10 \u2192 ... \u2192 1 \u2192 12

"},{"location":"lessons/06-setting-time/#updating-the-display","title":"Updating the Display","text":"
from machine import Pin\nfrom utime import localtime, sleep, ticks_ms, ticks_diff\nimport tm1637\n\n# Pin setup\nCLK_PIN = 0\nDST_PIN = 1\nPM_PIN = 25\n\nmode_pin = Pin(16, Pin.IN, Pin.PULL_UP)\nnext_pin = Pin(17, Pin.IN, Pin.PULL_UP)\nprevious_pin = Pin(18, Pin.IN, Pin.PULL_UP)\n# LED value 0 indicates AM, value 1 indicates PM\npm_pin = Pin(PM_PIN, Pin.OUT)\n\n# Time state\nnow = localtime()\nhour = now[3]\nminute = now[4]\nsecond = now[5]\n\ntm = tm1637.TM1637(clk=Pin(CLK_PIN), dio=Pin(DST_PIN))\n\nmode = 0\nmode_names = [\"run\", \"set hour\", \"set minute\", \"set AM/PM\"]\nmode_count = len(mode_names)\n\n# Debounce state\nlast_mode_press = 0\nlast_next_press = 0\nlast_prev_press = 0\nDEBOUNCE_MS = 100\n\ndef format_time():\n    return f\"{hour:d}:{minute:02d}:{second:02d} {'PM' if is_pm else 'AM'}\"\n\ndef set_pm():\n    if hour < 12:\n        is_pm = False\n        pm_pin.value(0)\n    else:\n        is_pm = True\n        pm_pin.value(1)\n\ndef handle_mode(pin):\n    global mode, last_mode_press\n    current_time = ticks_ms()\n    if ticks_diff(current_time, last_mode_press) > DEBOUNCE_MS:\n        mode = (mode + 1) % mode_count\n        print(f\"Mode: {mode_names[mode]}\")\n        last_mode_press = current_time\n\ndef handle_next(pin):\n    global hour, minute, is_pm, last_next_press\n    current_time = ticks_ms()\n    if ticks_diff(current_time, last_next_press) > DEBOUNCE_MS:\n        if mode == 1:  # Set hour\n            hour = (hour % 12) + 1\n        elif mode == 2:  # Set minute\n            minute = (minute + 1) % 60\n        elif mode == 3:  # Toggle AM/PM\n            is_pm = not is_pm\n\n        if mode != 0:\n            print(format_time())\n        last_next_press = current_time\n\ndef handle_previous(pin):\n    global hour, minute, is_pm, last_prev_press\n    current_time = ticks_ms()\n    if ticks_diff(current_time, last_prev_press) > DEBOUNCE_MS:\n        if mode == 1:  # Set hour\n            hour = ((hour - 2) % 12) + 1\n        elif mode == 2:  # Set minute\n            minute = (minute - 1) % 60\n        elif mode == 3:  # Toggle AM/PM\n            is_pm = not is_pm\n\n        if mode != 0:\n            print(format_time())\n        last_prev_press = current_time\n\n# numbers modified to not dispaly the leading zero\ndef numbers_nlz(num1, num2, colon=True):\n    \"\"\"Display two numeric values -9 through 99, with a leading space before\n    single-digit first numbers and separated by a colon.\"\"\"\n    num1 = max(-9, min(num1, 99))\n    num2 = max(-9, min(num2, 99))\n    prefix = ' ' if num1 < 10 else ''\n    # print(f'\"{prefix}{num1:d}{num2:0>2d}\"')\n    segments = tm.encode_string(f'{prefix}{num1:d}{num2:0>2d}')\n    if colon:\n        segments[1] |= 0x80  # colon on\n    tm.write(segments)\n\n# Set up interrupts\nmode_pin.irq(trigger=Pin.IRQ_FALLING, handler=handle_mode)\nnext_pin.irq(trigger=Pin.IRQ_FALLING, handler=handle_next)\nprevious_pin.irq(trigger=Pin.IRQ_FALLING, handler=handle_previous)\n\n# Main loop\nprint(\"Clock started. Press mode button to change settings.\")\n\nwhile True:\n    second = localtime()[5]\n    if mode == 0:  # Only update display in run mode\n        print(format_time())\n        # flash the colon on and off every second\n        if (second % 2): # modulo 2 will be true for odd numbers\n            numbers_nlz(hour, minute, True)\n        else:\n            numbers_nlz(hour, minute, False)\n        set_pm()\n        sleep(1)\n
"},{"location":"lessons/10-i2c-bus/","title":"The I2C Bus Standard","text":"

Most microcontrollers use the I2C bus as a standard way to communicate with peripheral devices such as sensors, real-time clocks and displays. In this section we cover how to connect your I2C devices to the main microcontroller and test that the connections are working.

Note that I2C is a two-way bus. Although in many of our display examples, there is only data moving from the microcontroller to the device. When we study real-time clocks we will see data going back and forth from the microcontroller to peripheral device.

"},{"location":"lessons/10-i2c-bus/#connections","title":"Connections","text":"

The I2C bus has four connections:

  1. GND - Ground
  2. VCC - Power - (usually 3.3 or 5 volts)
  3. SDA - Data
  4. SCL - Clock
"},{"location":"lessons/10-i2c-bus/#ground-gnd","title":"Ground (GND)","text":"

The common reference point for electrical signals that completes the circuit and ensures stable voltage measurements.

Connecting the display's GND pin to the microcontroller's GND pin provides a shared zero-voltage reference.

"},{"location":"lessons/10-i2c-bus/#power-vcc","title":"Power (VCC)","text":"

The voltage supply line that provides electrical power to operate the display, typically accepting either 3.3V or 5V depending on the model.

Connecting a 3.3V supply from the microcontroller to power a small OLED display.

"},{"location":"lessons/10-i2c-bus/#serial-data-sda","title":"Serial Data (SDA)","text":"

A bidirectional line that carries data bits between devices using a specific protocol for addressing and acknowledgment.

In the example code above, we use GPIO pin 0 to transmit display content and receive status information.

"},{"location":"lessons/10-i2c-bus/#serial-clock-scl","title":"Serial Clock (SCL)","text":"

A timing signal line generated by the master device that synchronizes data transfer and defines when the data line should be read or written. In our work, the master device is the microcontroller which sends the clock signal to the display.

In the sample code, we use GPIO pin 1 to coordinate data transfers at rates typically between 100kHz and 400kHz.

Some important points about these connections:

  1. Both SDA and SCL require pull-up resistors (typically 4.7k\u03a9)
  2. All devices share the same GND connection
  3. Multiple I2C devices can share the same SDA and SCL lines
  4. Power must match the display's voltage requirements (check datasheet)
"},{"location":"lessons/10-i2c-bus/#sample-i2c-initialization-code","title":"Sample I2C Initialization Code","text":"
from machine import Pin, I2C\nfrom ssd1306 import SSD1306_I2C\n\ni2c = I2C(0, sda=Pin(0), scl=Pin(1))\ndisplay = SSD1306_I2C(128, 64, i2c)\n\n# drawing code here\ndisplay.fill(0)\ndisplay.text(\"MicroPython\", 0, 0)\ndisplay.text(\"Rocks!\", 20, 20)\ndisplay.show()\n

Let's break down this I2C initialization code:

This code sets up a communication interface to control an OLED display using I2C (Inter-Integrated Circuit) protocol instead of SPI. Here's what each part does:

  1. The imports:
from machine import Pin, I2C            # Gets I2C and Pin control classes\nfrom ssd1306 import SSD1306_I2C         # Gets the OLED display driver\n
  1. The I2C bus initialization:
i2c = I2C(0,                           # Use I2C bus 0 \n          sda=Pin(0),                  # Data line on GPIO pin 0\n          scl=Pin(1))                  # Clock line on GPIO pin 1\n
  1. The display initialization:
display = SSD1306_I2C(128,             # Display width in pixels\n                      64,              # Display height in pixels \n                      i2c)             # I2C bus we created\n

Key differences from the SPI version: - Uses only 2 pins instead of 5 (no CS, DC, or RES needed) - Simpler initialization code - Slightly slower than SPI but adequate for most uses - Can share bus with other I2C devices using different addresses

Worth noting that I2C is often preferred for simple display projects because it requires fewer pins and simpler wiring, even though it's not as fast as SPI.

A common issue to watch for is making sure your I2C connections have appropriate pull-up resistors, though many development boards include these built-in.

"},{"location":"lessons/10-i2c-bus/#testing-your-connections","title":"Testing Your Connections","text":"

The I2C Scanner

"},{"location":"lessons/10-i2c-bus/#common-i2c-addresses-for-clock-and-watches","title":"Common I2C Addresses for Clock and Watches","text":""},{"location":"lessons/10-i2c-bus/#real-time-clocks","title":"Real Time Clocks","text":"

We use two types of Real-Time clocks

"},{"location":"lessons/10-i2c-bus/#real-time-clock-eeprom","title":"Real Time Clock EEPROM","text":""},{"location":"lessons/10-i2c-bus/#displays","title":"Displays","text":"

Our I2C OLED displays are often found at

"},{"location":"lessons/10-i2c-bus/#connector-standards","title":"Connector Standards","text":""},{"location":"lessons/11-spi-bus/","title":"The SPI Bus","text":"

The SPI Bus is the fastest serial bus that is used with most microcontrollers. Although it is fast, most displays need seven wires to connect a display. This is in contrast to the I2C bus that only uses four wires.

from machine import Pin, I2C\nimport ssd1306\n\nSCL=machine.Pin(2) # SPI CLock\nSDA=machine.Pin(3) # SPI Data\n\n# configure the SPI bus\n# for clocks with 20cm connectors 3MBS is a good choice\nspi=machine.SPI(0, sck=SCL, mosi=SDA, baudrate=3_000_000)\n\nRES = machine.Pin(4)\nDC = machine.Pin(5)\nCS = machine.Pin(6)\n\n# configure the display\ndisp = ssd1306.SSD1306_SPI(128, 64, spi, DC, RES, CS)\n

The underscore in 3_000_000 is just Python syntax for making large numbers more readable - it's equivalent to writing 3000000. This is a helpful convention when working with clock speeds and baudrates.

"},{"location":"lessons/11-spi-bus/#spi-bus","title":"SPI Bus","text":"

A serial communication interface that operates in a master-slave configuration using separate clock and data lines to synchronize high-speed data transfer between electronic components.

Example: Configuring an SPI bus with a 3 MHz clock rate to drive an OLED display.

"},{"location":"lessons/11-spi-bus/#oled-display","title":"OLED Display","text":"

A self-illuminating electronic visual output device that uses organic light-emitting diodes arranged in a matrix to show text, numbers, or graphics.

Example: A 128x64 pixel monochrome display showing time and temperature readings.

"},{"location":"lessons/11-spi-bus/#chip-select-cs","title":"Chip Select (CS)","text":"

A control signal that activates or deactivates a specific integrated circuit in a system with multiple devices sharing the same communication bus.

Example: We can change the state of th CS pin to enable communication with the display while keeping other SPI devices inactive. This allows you to use a single bus to communicated with multiple displays.

"},{"location":"lessons/11-spi-bus/#datacommand-dc","title":"Data/Command (DC)","text":"

A control line that indicates whether the transmitted information represents display commands or visual data to be shown.

Example: Using Pin 5 to distinguish between instructions for display configuration and pixel data.

"},{"location":"lessons/11-spi-bus/#reset-res","title":"Reset (RES)","text":"

A control signal that returns an electronic device to its initial state, clearing all registers and settings to default values.

Example: You can use the RESET to delay the startup of a display to make sure that the display has powered up in a coherent way and will start in a consistent state.

"},{"location":"lessons/11-spi-bus/#clock-line-scl","title":"Clock Line (SCL)","text":"

A signal wire that provides timing synchronization pulses to coordinate data transfer between electronic devices.

Example: We use the Clock Line to generate 3 million clock pulses per second for display updates.

"},{"location":"lessons/11-spi-bus/#data-line-sdamosi","title":"Data Line (SDA/MOSI)","text":"

A signal wire that carries information serially from a controlling device to a receiving device.

Example: We use the SDA pin to transmit display content one bit at a time.

"},{"location":"lessons/11-spi-bus/#tuning-the-baudrate","title":"Tuning the Baudrate","text":"

The baudrate parameter determines the transfer between thn microcontroller and the peripheral device.

The baudrate=3_000_000 (3 megabits per second) setting is a reasonable balance for this application. Digital clock displays are often only updated once per second.

For SPI OLED displays with short connections (~20cm or less), a 3 MHz clock rate provides:

"},{"location":"lessons/11-spi-bus/#when-higher-speeds-matter","title":"When Higher Speeds Matter","text":"

Simulating an analog clock face on a color watch display will require a large amount of data being transmitted between the microcontroller and the display. For these situations you will frequently keep the wires between the microcontroller and the display short and then turn up the baudrate on the SPI interface.

"},{"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":"lessons/55-shift-registers/","title":"Shift Registers","text":""},{"location":"lessons/55-shift-registers/#sample-prompt","title":"Sample Prompt","text":"

Prompt

Please create a detailed tutorial about using shift registers that would be understood by a 9th grade high school student. Assume that the student already knows about using the Raspberry Pi Pico to turn on and off LEDs,but does not have enough output pins to drive all 4 digits on a seven-segment display clock. Suggest where figures should be added and what the figures should contain. Discuss the 74HC595 chip, what its inputs and outputs are and what some basic MicroPython code would look like to turn on and off some LEDs controlled by the 74HC595. Return the tutorial in markdown format.

"},{"location":"lessons/55-shift-registers/#sample-response","title":"Sample Response","text":""},{"location":"lessons/55-shift-registers/#using-shift-registers-to-control-more-leds-with-your-pico","title":"Using Shift Registers to Control More LEDs with Your Pico","text":"

Have you ever wanted to build a digital clock but ran out of pins on your Raspberry Pi Pico? A shift register can help! Think of a shift register like a magical helper that turns a few Pico pins into many output pins. Let's learn how to use one!

"},{"location":"lessons/55-shift-registers/#what-is-a-shift-register","title":"What is a Shift Register?","text":"

A shift register is like a row of eight boxes that can each hold either a 1 (on) or 0 (off). The cool part is that you can control all eight outputs using just three pins from your Pico! The 74HC595 is a popular and easy-to-use shift register.

"},{"location":"lessons/55-shift-registers/#the-important-pins-on-the-74hc595","title":"The Important Pins on the 74HC595","text":""},{"location":"lessons/55-shift-registers/#detailed-pin-descriptions","title":"Detailed Pin Descriptions","text":""},{"location":"lessons/55-shift-registers/#srclk","title":"SRCLK","text":"

Shift Register Clock or Input Clock - A digital input signal that controls the shifting of data through a serial-to-parallel register by generating a transition from low to high voltage level.

Example: Pulsing SRCLK from 0V to 3.3V causes each bit to move one position in the shift register.

"},{"location":"lessons/55-shift-registers/#rclk","title":"RCLK","text":"

Register Clock or Latch Pin A digital input signal that transfers the contents of the shift register to the storage register by generating a transition from low to high voltage level.

Example: Pulsing RCLK from 0V to 3.3V updates all output pins simultaneously with new values. The term \"latch pin\" is more descriptive of its function (it \"latches\" or locks in the data), while RCLK is the technical abbreviation used in datasheets. On the 74HC595 chip, this is pin 12, and it's also sometimes labeled as STCP (STorage Clock Pulse).

"},{"location":"lessons/55-shift-registers/#wiring-it-up","title":"Wiring It Up","text":"

[Figure 3: Show a wiring diagram with:

Here's how to connect everything:

  1. Connect VCC to 3.3V on the Pico
  2. Connect GND to ground
  3. Connect Data DS to GPIO 16
  4. Connect Clock (SRCLK) to GPIO 17
  5. Connect Latch (RCLK) to GPIO 18
  6. Connect LEDs with resistors to outputs Q0-Q7
"},{"location":"lessons/55-shift-registers/#the-code","title":"The Code","text":"

Here's some simple MicroPython code to control your LEDs:

from machine import Pin\nfrom utime import sleep\n\n# Set up our Pico pins\ndata_pin = Pin(16, Pin.OUT)\nclock_pin = Pin(17, Pin.OUT)\nlatch_pin = Pin(18, Pin.OUT)\n\ndef shift_byte(byte_val):\n    # Send each bit one at a time\n    for i in range(8):\n        # Get the current bit (0 or 1)\n        bit = (byte_val >> (7-i)) & 1\n\n        # Set the data pin\n        data_pin.value(bit)\n\n        # Pulse the clock\n        clock_pin.value(1)\n        clock_pin.value(0)\n\n    # Pulse the latch to show the new pattern\n    latch_pin.value(1)\n    latch_pin.value(0)\n\n# Example: Light up alternate LEDs\nwhile True:\n    shift_byte(0b10101010)  # Turn on LEDs 7,5,3,1\n    sleep(1)\n    shift_byte(0b01010101)  # Turn on LEDs 6,4,2,0\n    sleep(1)\n
"},{"location":"lessons/55-shift-registers/#how-it-works","title":"How It Works","text":"

[Figure 4: Show an animation or series of diagrams demonstrating how data shifts through the register]

  1. The shift_byte function takes a number between 0 and 255
  2. It converts this number into eight separate bits (1s and 0s)
  3. For each bit:
  4. It puts the bit on the data pin
  5. Pulses the clock to shift it in
  6. After all 8 bits are sent, it pulses the latch to update all LEDs at once
"},{"location":"lessons/55-shift-registers/#using-multiple-shift-registers","title":"Using Multiple Shift Registers","text":"

[Figure 5: Show how to chain two 74HC595s together]

Need even more outputs? You can chain shift registers together! Connect the Q7' (serial out) pin of the first shift register to the DS (data) pin of the second one. They'll share the same clock and latch pins.

"},{"location":"lessons/55-shift-registers/#practice-project-binary-counter","title":"Practice Project: Binary Counter","text":"

Try this: Make a program that counts from 0 to 255, showing each number in binary on your LEDs. Here's a starter:

for number in range(256):\n    shift_byte(number)\n    sleep(0.1)  # Wait a bit between numbers\n

This will help you understand how binary numbers work while practicing with your shift register!

"},{"location":"lessons/55-shift-registers/#common-problems-and-solutions","title":"Common Problems and Solutions","text":"
  1. LEDs not lighting up?
  2. Check your connections
  3. Make sure your LEDs have resistors
  4. Verify your ground connections

  5. Random LED patterns?

  6. Make sure your clock and latch pulses are complete
  7. Check if your data is sending in the right order
"},{"location":"lessons/55-shift-registers/#next-steps","title":"Next Steps","text":"

Once you're comfortable with this, you can: 1. Control a 4-digit display for your clock 2. Make LED light patterns 3. Build a binary game 4. Create a VU meter for music

Remember: A shift register is just a tool to help you control more things with fewer pins. With practice, you'll find lots of cool ways to use it in your projects!

[Note: All figures should be clear, well-labeled, and include captions explaining what they show. Consider using color coding for different types of connections (power, data, ground) in the diagrams.]

"},{"location":"lessons/55-shift-registers/#example-74hc595","title":"Example: 74HC595","text":""},{"location":"lessons/55-shift-registers/#references","title":"References","text":""},{"location":"lessons/60-sound/","title":"Adding Sound to Your Clock","text":""},{"location":"lessons/60-sound/#why-add-sound","title":"Why Add Sound","text":""},{"location":"lessons/60-sound/#feedback-when-setting-time","title":"Feedback When Setting Time","text":"

Using sounds allows you to use low-cost momentary push buttons that don't have a clear \"clink\" when pressed.

"},{"location":"lessons/60-sound/#alarm-clock-sounds","title":"Alarm Clock Sounds","text":""},{"location":"lessons/60-sound/#timers-and-reminders","title":"Timers and Reminders","text":""},{"location":"lessons/60-sound/#adding-a-speaker","title":"Adding a Speaker","text":""},{"location":"lessons/60-sound/#generating-simple-sounds-with-pwm","title":"Generating Simple Sounds with PWM","text":"

Play a Tune with PWM

"},{"location":"lessons/60-sound/#using-a-digital-to-analog-converter","title":"Using a Digital to Analog Converter","text":""},{"location":"lessons/60-sound/#references","title":"References","text":"

Prof. Albrecht Schmidt Wiki

"},{"location":"lessons/60-sound/play-tune/","title":"Play a Tune Using PWM","text":"

Below is an example MicroPython program that uses the provided note frequencies and plays the Mario melody on a PWM pin (GPIO 16 in this example). You can adjust timing and duty cycle to get the sound you want. Simply copy this code onto your MicroPython device (e.g., Raspberry Pi Pico running MicroPython) and run it.

from machine import Pin, PWM\nfrom utime import sleep\n\n# Initialize speaker on GPIO16\nspeaker = PWM(Pin(16))\n\n# Define the tone (note) frequency dictionary\ntones = {\n    \"B0\": 31,\"C1\": 33,\"CS1\": 35,\"D1\": 37,\"DS1\": 39,\"E1\": 41,\"F1\": 44,\"FS1\": 46,\n    \"G1\": 49,\"GS1\": 52,\"A1\": 55,\"AS1\": 58,\"B1\": 62,\"C2\": 65,\n    \"CS2\": 69,\"D2\": 73,\"DS2\": 78,\"E2\": 82,\"F2\": 87,\"FS2\": 93,\"G2\": 98,\n    \"GS2\": 104,\"A2\": 110,\"AS2\": 117,\"B2\": 123,\"C3\": 131,\"CS3\": 139,\n    \"D3\": 147,\"DS3\": 156,\"E3\": 165,\"F3\": 175,\"FS3\": 185,\n    \"G3\": 196,\"GS3\": 208,\"A3\": 220,\"AS3\": 233,\"B3\": 247,\"C4\": 262,\"CS4\": 277,\"D4\": 294,\"DS4\": 311,\n    \"E4\": 330,\"F4\": 349,\"FS4\": 370,\"G4\": 392,\"GS4\": 415,\"A4\": 440,\"AS4\": 466,\"B4\": 494,\"C5\": 523,\"CS5\": 554,\"D5\": 587,\"DS5\": 622,\"E5\": 659,\"F5\": 698,\n    \"FS5\": 740,\"G5\": 784,\"GS5\": 831,\"A5\": 880,\"AS5\": 932,\"B5\": 988,\"C6\": 1047,\"CS6\": 1109,\"D6\": 1175,\"DS6\": 1245,\"E6\": 1319,\"F6\": 1397,\"FS6\": 1480,\"G6\": 1568,\"GS6\": 1661,\n    \"A6\": 1760,\"AS6\": 1865,\"B6\": 1976,\"C7\": 2093,\"CS7\": 2217,\"D7\": 2349,\"DS7\": 2489,\"E7\": 2637,\"F7\": 2794,\"FS7\": 2960,\"G7\": 3136,\"GS7\": 3322,\"A7\": 3520,\n    \"AS7\": 3729,\"B7\": 3951,\"C8\": 4186,\"CS8\": 4435,\"D8\": 4699,\"DS8\": 4978\n}\n\n# Mario melody notes\nmario = [\n    \"E7\", \"E7\", 0, \"E7\", 0, \"C7\", \"E7\", 0, \"G7\", 0, 0, 0, \"G6\", 0, 0, 0,\n    \"C7\", 0, 0, \"G6\", 0, 0, \"E6\", 0, 0, \"A6\", 0, \"B6\", 0, \"AS6\", \"A6\", 0,\n    \"G6\", \"E7\", 0, \"G7\", \"A7\", 0, \"F7\", \"G7\", 0, \"E7\", 0, \"C7\", \"D7\", \"B6\", 0, 0,\n    \"C7\", 0, 0, \"G6\", 0, 0, \"E6\", 0, 0, \"A6\", 0, \"B6\", 0, \"AS6\", \"A6\", 0,\n    \"G6\", \"E7\", 0, \"G7\", \"A7\", 0, \"F7\", \"G7\", 0, \"E7\", 0, \"C7\", \"D7\", \"B6\", 0, 0\n]\n\ndef play_tone(note, duration=0.15):\n    \"\"\"\n    Plays a single tone for the specified duration.\n    If note is 0, it is treated as a rest (no sound).\n    Adjust durations as needed for the best result.\n    \"\"\"\n    if note == 0:\n        # Rest (no sound)\n        speaker.duty_u16(0)\n        sleep(duration)\n    else:\n        # Play the specified note\n        freq = tones[note]\n        speaker.freq(freq)\n        # Set a duty cycle (volume), 0 to 65535\n        speaker.duty_u16(30000)\n        sleep(duration)\n        # Turn the sound off between notes\n        speaker.duty_u16(0)\n        sleep(0.05)\n\n# Play the entire Mario melody\nfor n in mario:\n    play_tone(n)\n\n# Turn speaker off at the end\n# Note that the note still plays on stop/interrupt!\nspeaker.duty_u16(0)\n
"},{"location":"lessons/60-sound/play-tune/#adjust-the-tempo","title":"Adjust the Tempo","text":"
duration=0.15\n
"},{"location":"lessons/60-sound/play-tune/#adding-interrupt-handling","title":"Adding Interrupt Handling","text":"
# Main execution\ntry:\n    print(\"Playing Mario theme...\")\n    play_mario()\n    # Clean up\n    speaker.deinit()\n    print(\"Done!\")\nexcept KeyboardInterrupt:\n    # Clean up if interrupted\n    speaker.deinit()\n    print(\"\\nStopped by user\")\n
from machine import Pin, PWM\nfrom utime import sleep_ms\n\n# Initialize speaker on GPIO 16\nspeaker = PWM(Pin(16))\n\n# Note frequencies in Hz\ntones = {\n    \"B0\": 31, \"C1\": 33, \"CS1\": 35, \"D1\": 37, \"DS1\": 39, \"E1\": 41, \"F1\": 44, \"FS1\": 46,\n    \"G1\": 49, \"GS1\": 52, \"A1\": 55, \"AS1\": 58, \"B1\": 62, \"C2\": 65,\n    \"CS2\": 69, \"D2\": 73, \"DS2\": 78, \"E2\": 82, \"F2\": 87, \"FS2\": 93, \"G2\": 98,\n    \"GS2\": 104, \"A2\": 110, \"AS2\": 117, \"B2\": 123, \"C3\": 131, \"CS3\": 139,\n    \"D3\": 147, \"DS3\": 156, \"E3\": 165, \"F3\": 175, \"FS3\": 185,\n    \"G3\": 196, \"GS3\": 208, \"A3\": 220, \"AS3\": 233, \"B3\": 247, \"C4\": 262, \"CS4\": 277, \"D4\": 294, \"DS4\": 311,\n    \"E4\": 330, \"F4\": 349, \"FS4\": 370, \"G4\": 392, \"GS4\": 415, \"A4\": 440, \"AS4\": 466, \"B4\": 494, \"C5\": 523, \"CS5\": 554, \"D5\": 587, \"DS5\": 622, \"E5\": 659, \"F5\": 698,\n    \"FS5\": 740, \"G5\": 784, \"GS5\": 831, \"A5\": 880, \"AS5\": 932, \"B5\": 988, \"C6\": 1047, \"CS6\": 1109, \"D6\": 1175, \"DS6\": 1245, \"E6\": 1319, \"F6\": 1397, \"FS6\": 1480, \"G6\": 1568, \"GS6\": 1661,\n    \"A6\": 1760, \"AS6\": 1865, \"B6\": 1976, \"C7\": 2093, \"CS7\": 2217, \"D7\": 2349, \"DS7\": 2489, \"E7\": 2637, \"F7\": 2794, \"FS7\": 2960, \"G7\": 3136, \"GS7\": 3322, \"A7\": 3520,\n    \"AS7\": 3729, \"B7\": 3951, \"C8\": 4186, \"CS8\": 4435, \"D8\": 4699, \"DS8\": 4978\n}\n\nmario = [\"E7\", \"E7\", 0, \"E7\", 0, \"C7\", \"E7\", 0, \"G7\", 0, 0, 0, \"G6\", 0, 0, 0, \"C7\", 0, 0, \"G6\",\n         0, 0, \"E6\", 0, 0, \"A6\", 0, \"B6\", 0, \"AS6\", \"A6\", 0, \"G6\", \"E7\", 0, \"G7\", \"A7\", 0, \"F7\", \"G7\",\n         0, \"E7\", 0, \"C7\", \"D7\", \"B6\", 0, 0, \"C7\", 0, 0, \"G6\", 0, 0, \"E6\", 0, 0, \"A6\", 0, \"B6\", 0,\n         \"AS6\", \"A6\", 0, \"G6\", \"E7\", 0, \"G7\", \"A7\", 0, \"F7\", \"G7\", 0, \"E7\", 0, \"C7\", \"D7\", \"B6\", 0, 0]\n\ndef play_tone(frequency, duration=100):\n    \"\"\"Play a tone at the given frequency for the specified duration\"\"\"\n    if frequency > 0:\n        speaker.freq(frequency)  # Set frequency\n        speaker.duty_u16(32768)  # 50% duty cycle (32768 is half of 65535)\n        sleep_ms(duration)\n        speaker.duty_u16(0)     # Turn off tone\n    else:\n        sleep_ms(duration)      # Rest for the specified duration\n\ndef play_mario():\n    \"\"\"Play the Mario theme song\"\"\"\n    # Tempo control\n    tempo = 150  # Adjust this value to control speed (lower = faster)\n\n    # Play each note\n    for note in mario:\n        if note == 0:\n            # Rest\n            play_tone(0, tempo)\n        else:\n            # Play the note\n            play_tone(tones[note], tempo)\n\n        # Brief pause between notes to separate them\n        sleep_ms(50)\n\n# Main execution\ntry:\n    print(\"Playing Mario theme...\")\n    play_mario()\n    # Clean up\n    speaker.deinit()\n    print(\"Done!\")\nexcept KeyboardInterrupt:\n    # Clean up if interrupted\n    speaker.deinit()\n    print(\"\\nStopped by user\")\n
"},{"location":"lessons/70-power/","title":"Powering Your Clocks and Watches","text":""},{"location":"lessons/70-power/#powering-your-raspberry-pi-pico","title":"Powering Your Raspberry Pi Pico","text":"

Understanding how VSYS works and how VSYS is different from VBUS.

Using 3.3 volts for your displays and peripherals

"},{"location":"lessons/70-power/#wall-power-vs-portability","title":"Wall Power vs. Portability","text":""},{"location":"lessons/70-power/#backup-power-when-power-fails","title":"Backup Power When Power Fails","text":""},{"location":"lessons/70-power/#coin-cells-for-real-time-clocks","title":"Coin Cells for Real-time Clocks","text":""},{"location":"lessons/70-power/#usb-power","title":"USB Power","text":""},{"location":"lessons/70-power/#aa-battery-packs","title":"AA Battery Packs","text":""},{"location":"lessons/70-power/#usb-battery-packs","title":"USB Battery Packs","text":""},{"location":"lessons/70-power/#lipo-batteries","title":"LiPo Batteries","text":""},{"location":"lessons/70-power/#lipo-battery","title":"LiPo Battery","text":""},{"location":"lessons/digital-to-analog-converters/","title":"Digital to Analog Converters and the I2S Protocol","text":""},{"location":"lessons/digital-to-analog-converters/#the-pcm5102-dac-board","title":"The PCM5102 DAC Board","text":""},{"location":"lessons/digital-to-analog-converters/#connections","title":"Connections","text":"

Hooking the up PCM5102 board is pretty easy for the general case:

"},{"location":"lessons/digital-to-analog-converters/#pico-connections","title":"Pico Connections","text":"

Use the lower right connections

"},{"location":"lessons/digital-to-analog-converters/#sampling-rates","title":"Sampling Rates","text":"

We can also lower sample rates. The PCM5102A supports the following standard sample rates:

"},{"location":"lessons/digital-to-analog-converters/#references","title":"References","text":"

Todbot Blog: Cheap stereo line out I2S DAC

"},{"location":"lessons/digital-to-analog-converters/code/","title":"Using a DAC to Generate Sounds","text":"
from machine import Pin, I2S\nimport struct\nimport math\nfrom utime import sleep_ms, ticks_ms, ticks_diff\n\n# Pin Definitions\nBCK_PIN = 16      # Connect to BCK on DAC (Bit Clock)\nWS_PIN = 17       # Connect to LCK on DAC (Word Select)\nSD_PIN = 18       # Connect to DIN on DAC (Data Input)\n\n# Audio parameters\nSAMPLE_RATE = 16000\nBUFFER_SIZE = 512  # Larger buffer for smoother playback\n\n# Note frequencies\nNOTES = {\n    'C4': 261.63,\n    'D4': 293.66,\n    'E4': 329.63,\n    'F4': 349.23,\n    'G4': 392.00,\n    'A4': 440.00,\n    'B4': 493.88,\n    'C5': 523.25,\n    'REST': 0\n}\n\n# Simple melody: each tuple contains (note_name, duration_ms)\nMELODY = [\n    ('C4', 300), ('E4', 300), ('G4', 300),  # Ascending arpeggio\n    ('C5', 600),                            # Hold high note\n    ('G4', 200), ('E4', 200), ('C4', 600),  # Descending\n    ('REST', 300),                          # Pause\n    ('G4', 300), ('F4', 300), ('E4', 300),  # Walking down\n    ('D4', 300), ('C4', 600),               # End phrase\n]\n\ndef apply_envelope(value, i, buffer_size, attack_samples=100, release_samples=100):\n    \"\"\"Apply attack and release envelope to reduce clicks and pops\"\"\"\n    if i < attack_samples:\n        return value * (i / attack_samples)\n    elif i > buffer_size - release_samples:\n        return value * ((buffer_size - i) / release_samples)\n    return value\n\ndef make_tone_buffer(frequency):\n    \"\"\"Create a buffer with complete cycles of the sine wave\"\"\"\n    if frequency == 0:  # For REST\n        return bytearray(BUFFER_SIZE * 2)\n\n    # Calculate samples for complete cycles\n    samples_per_cycle = SAMPLE_RATE / frequency\n    num_cycles = max(1, int(BUFFER_SIZE / samples_per_cycle))\n    adjusted_buffer_size = int(num_cycles * samples_per_cycle)\n    if adjusted_buffer_size > BUFFER_SIZE:\n        adjusted_buffer_size = BUFFER_SIZE\n\n    buffer = bytearray(adjusted_buffer_size * 2)\n\n    # Generate a smoother waveform\n    amplitude = 0.15  # Reduced amplitude for cleaner sound\n\n    for i in range(0, adjusted_buffer_size * 2, 2):\n        # Basic sine wave\n        sample_pos = (i // 2) / samples_per_cycle * 2 * math.pi\n        raw_value = math.sin(sample_pos)\n\n        # Apply envelope and amplitude\n        value = int(32767 * amplitude * \n                   apply_envelope(raw_value, i//2, adjusted_buffer_size))\n\n        # Pack into buffer\n        struct.pack_into(\"<h\", buffer, i, value)\n\n    return buffer\n\n# Configure I2S with higher sample precision\naudio_out = I2S(\n    0,\n    sck=Pin(BCK_PIN),\n    ws=Pin(WS_PIN),\n    sd=Pin(SD_PIN),\n    mode=I2S.TX,\n    bits=16,\n    format=I2S.MONO,\n    rate=SAMPLE_RATE,\n    ibuf=2048  # Larger internal buffer\n)\n\nprint(\"Playing melody... Press Ctrl+C to stop\")\n\ntry:\n    while True:\n        for note_name, duration in MELODY:\n            print(f\"\\nPlaying {note_name} for {duration}ms\")\n\n            # Create buffer for this frequency\n            frequency = NOTES[note_name]\n            buffer = make_tone_buffer(frequency)\n\n            # Play the note for specified duration\n            start_time = ticks_ms()\n            while ticks_diff(ticks_ms(), start_time) < duration:\n                audio_out.write(buffer)\n                sleep_ms(10)\n                print(\".\", end=\"\")\n\n            # Longer pause between notes for clearer separation\n            sleep_ms(70)\n\n        # Pause between repetitions\n        sleep_ms(1000)\n\nexcept KeyboardInterrupt:\n    print(\"\\nStopping...\")\nfinally:\n    audio_out.deinit()\n    print(\"Test complete\")\n
"},{"location":"lessons/digital-to-analog-converters/prompt/","title":"Prompt","text":"

Prompt

I need to add sound to my clock. To do this I purchased the following:

Interface I2S PCM5102A DAC Decoder GY-PCM5102 I2S Player Module For Raspberry

It has the following edge connections:

GND - Connected to GND V IN - Connected to 3.3 V LCK - Connected to GPIO 15 D IN - Connected to GPIO 14 BCK - Connected to GPIO 13 SCK - Connected to GPIO 12 LINE OUT - Connected to an 8-ohm speaker

Please help me write a MicroPython program for the Raspberry Pi Pico that can help me test this device.

"},{"location":"lessons/digital-to-analog-converters/prompt/#response","title":"Response","text":"

I'll help you write a MicroPython program to test the I2S DAC module with your Pico. We'll create a simple test that generates a sine wave tone to verify the setup is working correctly.

I2S DAC Test Program

This program provides a basic test of your I2S DAC setup. Here's how it works:

  1. It configures the I2S interface with the pins you've connected:
  2. It creates functions to:
  3. The test sequence plays each note for 500ms.

To use this program:

  1. Save it to your Pico as main.py
  2. When you run it, you should hear a sequence of ascending notes through your speaker
  3. If you don't hear anything:

You can modify the test_sequence() function to play different frequencies or durations. For reference, here are some common note frequencies:

python\nCopy\n\n`NOTE_FREQUENCIES ={'C4':262,'D4':294,'E4':330,'F4':349,'G4':392,'A4':440,'B4':494,'C5':523}`\n

Would you like me to modify the program to include different test patterns or features?

CopyRetry

"},{"location":"lessons/ntp/","title":"Network Time Protocol (NTP)","text":""},{"location":"lessons/ntp/#part-1-understanding-time-synchronization","title":"Part 1: Understanding Time Synchronization","text":""},{"location":"lessons/ntp/#why-do-we-need-ntp","title":"Why Do We Need NTP?","text":"
  1. Computer clocks tend to drift over time
  2. Different devices need to agree on the exact time for:
  3. Coordinating events
  4. Logging activities
  5. Securing network communications
  6. Scheduling tasks
"},{"location":"lessons/ntp/#how-ntp-works","title":"How NTP Works","text":""},{"location":"lessons/ntp/#client-server-model","title":"Client-Server Model","text":""},{"location":"lessons/ntp/#time-server-hierarchy","title":"Time Server Hierarchy","text":""},{"location":"lessons/ntp/#part-2-basic-ntp-communication","title":"Part 2: Basic NTP Communication","text":""},{"location":"lessons/ntp/#the-ntp-request-process","title":"The NTP Request Process","text":"
  1. Your device sends a tiny message to an NTP server
  2. The server adds its timestamp
  3. Your device calculates the time difference, accounting for network delay
  4. Your clock gets adjusted
"},{"location":"lessons/ntp/#understanding-time-formats","title":"Understanding Time Formats","text":"
  1. NTP uses seconds since January 1, 1900
  2. Modern computers use seconds since:
  3. January 1, 1970 (Unix time)
  4. January 1, 2000 (Some embedded systems)
  5. We need to convert between these formats
"},{"location":"lessons/ntp/sample-ntp-function/","title":"NTP Function","text":"

Here is a step-by-step walk though of our NTP function in MicroPython

"},{"location":"lessons/ntp/sample-ntp-function/#function-listing","title":"Function Listing","text":"
# Adapted from official ntptime by Peter Hinch July 2022\n# The main aim is portability:\n# Detects host device's epoch and returns time relative to that.\n# Basic approach to local time: add offset in hours relative to UTC.\n# Timeouts return a time of 0. These happen: caller should check for this.\n# Replace socket timeout with select.poll as per docs:\n# http://docs.micropython.org/en/latest/library/socket.html#socket.socket.settimeout\n\nimport socket\nimport struct\nimport select\nfrom time import gmtime\n\n# (date(2000, 1, 1) - date(1900, 1, 1)).days * 24*60*60\n# (date(1970, 1, 1) - date(1900, 1, 1)).days * 24*60*60\nNTP_DELTA = 3155673600 if gmtime(0)[0] == 2000 else 2208988800\n\n# The NTP host can be configured at runtime by doing: ntptime.host = 'myhost.org'\nhost = \"pool.ntp.org\"\n\ndef time(hrs_offset=0):  # Local time offset in hrs relative to UTC\n    NTP_QUERY = bytearray(48)\n    NTP_QUERY[0] = 0x1B\n    try:\n        addr = socket.getaddrinfo(host, 123)[0][-1]\n    except OSError:\n        return 0\n    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n    poller = select.poll()\n    poller.register(s, select.POLLIN)\n    try:\n        s.sendto(NTP_QUERY, addr)\n        if poller.poll(1000):  # time in milliseconds\n            msg = s.recv(48)\n            val = struct.unpack(\"!I\", msg[40:44])[0]  # Can return 0\n            return max(val - NTP_DELTA + hrs_offset * 3600, 0)\n    except OSError:\n        pass  # LAN error\n    finally:\n        s.close()\n    return 0  # Timeout or LAN error occurred\n

Let's break down the key parts of this code.

# Time difference constants\nNTP_DELTA = 3155673600 if gmtime(0)[0] == 2000 else 2208988800\n
This line determines which epoch your device uses (2000 or 1970) and sets the correct conversion factor.

# Create the NTP request packet\nNTP_QUERY = bytearray(48)\nNTP_QUERY[0] = 0x1B\n
This creates the special message format that NTP servers expect. The 0x1B tells the server this is a time request.

# Connect to NTP server\naddr = socket.getaddrinfo(host, 123)[0][-1]\ns = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n
This code: 1. Looks up the NTP server address 2. Creates a UDP socket for communication 3. Port 123 is the standard NTP port

# Send request and get response\ns.sendto(NTP_QUERY, addr)\nif poller.poll(1000):  # wait up to 1 second\n    msg = s.recv(48)\n    val = struct.unpack(\"!I\", msg[40:44])[0]\n
This section: 1. Sends our time request 2. Waits for up to 1 second for a response 3. Extracts the timestamp from the response

"},{"location":"lessons/ntp/sample-ntp-function/#part-4-hands-on-exercise","title":"Part 4: Hands-on Exercise","text":"

Let's use this code to synchronize a clock:

  1. Save the code as ntp_sync.py
  2. Create a simple clock program:
from machine import RTC\nimport ntp_sync\n\ndef sync_time():\n    # Get time with UTC offset for your timezone\n    # Example: -4 for EDT\n    current_time = ntp_sync.time(-4)  \n    if current_time > 0:\n        # Convert to time tuple and set RTC\n        rtc = RTC()\n        rtc.datetime(gmtime(current_time))\n        print(\"Time synchronized!\")\n    else:\n        print(\"Time sync failed\")\n\n# Run the synchronization\nsync_time()\n
"},{"location":"lessons/ntp/sample-ntp-function/#part-5-understanding-results","title":"Part 5: Understanding Results","text":"

After running the code, you might see: 1. Success: Your device's time is now synchronized 2. Failure: Common causes include: - No internet connection - NTP server timeout - Network restrictions

Remember that in real applications, you should: - Handle errors gracefully - Implement periodic resynchronization - Consider time zones and daylight saving time - Add timeout handling for reliability

"},{"location":"lessons/ntp/sample-ntp-function/#references","title":"References","text":"

[Peter Hinch NTP Function](https://github.com/peterhinch/micropython-samples/blob/master/ntptime/ntptime.py0

"},{"location":"lessons/rtc/","title":"Real Time Clocks","text":"

The DS3231 is a highly accurate real-time clock (RTC) integrated circuit that maintains precise time using an internal temperature-compensated crystal oscillator (TCXO).

Key features:

The device stores time data in BCD format across multiple registers:

0x00: Seconds 0x01: Minutes 0x02: Hours 0x03: Day 0x04: Date 0x05: Month 0x06: Year

Example: Reading hour register 0x02 returns value 0x15 in BCD, representing 15:00 (3:00 PM).

"},{"location":"lessons/rtc/#how-long-will-the-battery-last","title":"How Long Will The Battery Last","text":"

The typical operational duration of the backup coin-cell battery in real-time clock circuits ranges from 2 to 10 years, depending on temperature conditions and current draw.

The DS3231 RTC module used in our kits draw 3\u03bcA at room temperature typically operates for 8 years on a single CR2032 battery. The key factors affecting battery life are:

"},{"location":"lessons/rtc/13-real-time-clocks/","title":"Real Time Clocks","text":"

Note

Minimal real-time clock support has only recently been added to the core MicroPython libraries. Make sure you are using the latest release of MicroPython to use the features built into the MicroPython Runtime.

See The MicroPython RTC Documentation to make sure you have the right release.

Note that the built-in MicroPython library does not setting alarms, turning on and off the oscillator stop flag or accessing the temperature of the RTC (available only on some RTCs like the DS3231). These functions must be done through a library or by providing your own functions.

"},{"location":"lessons/rtc/13-real-time-clocks/#the-ds1307-and-the-ds3231","title":"The DS1307 and the DS3231","text":"

Our lessons use two different RTC chips. Let's do a comparison of these RTC modules and explain their implementation with the MicroPython.

"},{"location":"lessons/rtc/13-real-time-clocks/#ds1307","title":"DS1307","text":"

This is the original battery-backed real-time clock with I2C interface that maintains basic timekeeping functions introduced by Maxim (then Dallas Semiconductor) around 2004-2005. For example, the DS1307 can be used for tracking hours, minutes, seconds with \u00b12 seconds/day accuracy at C 77\u00b0F (25\u00b0). This is appropriate for room temperature household clocks that have a uniform temperature. Large swings in temperature change crystal vibration frequency which can impact clock accuracy.

"},{"location":"lessons/rtc/13-real-time-clocks/#ds3231","title":"DS3231","text":"

This is a newer temperature-compensated real-time clock Released by Maxim Integrated around 2009-2010. With integrated crystal and I2C interface that maintains highly accurate timekeeping under varying conditions.

The DS3231 represented a significant advancement in accuracy and temperature compensation over the earlier DS1307. The improved technology helped make the DS3231 the preferred choice for precision timekeeping applications, despite its higher cost. The DS3231m typically maintains an accuracy of \u00b12 seconds each month (not day) across an incredible range of -40\u00b0F to 185\u00b0F (-40\u00b0C to +85\u00b0C). The DS3231 also has an on-board register you can access to get the current temperature of the device.

Key Technical and Financial Differences:

  1. Accuracy:

  2. DS1307: \u00b12 seconds/day

  3. DS3231: \u00b12 seconds/month due to temperature compensation

  4. Temperature Operation:

  5. DS1307: 32\u00b0F to 158\u00b0F (0\u00b0C to +70\u00b0C)

  6. DS3231: -40\u00b0F to 185\u00b0F (-40\u00b0C to +85\u00b0C) with compensation

  7. Clock Output:

  8. DS1307: Programmable square wave (1Hz, 4kHz, 8kHz, 32kHz)

  9. DS3231: 32kHz output plus programmable square wave

  10. Based on typical retail prices as of 2024:

  11. DS1307: $1-2 USD

  12. DS3231: $3-5 USD
"},{"location":"lessons/rtc/13-real-time-clocks/#implementation-with-micropython","title":"Implementation with MicroPython","text":"

The coding for each of these chips in almost identical since they implement the same I2C interface.

from machine import I2C, Pin\n\n# Both modules use same I2C interface setup\ni2c = I2C(0, sda=Pin(0), scl=Pin(1))\n\n# DS1307 implementation\nDS1307_ADDR = 0x68\ndef ds1307_get_time():\n    return i2c.readfrom_mem(DS1307_ADDR, 0x00, 7)\n\n# DS3231 implementation\nDS3231_ADDR = 0x68\ndef ds3231_get_time():\n    return i2c.readfrom_mem(DS3231_ADDR, 0x00, 7)\n\n# Both modules store time in BCD format\ndef decode_bcd(bcd):\n    return (bcd & 0xF) + ((bcd >> 4) * 10)\n

The code structure remains similar for both modules since they share the I2C interface and address. The DS3231 provides additional registers for temperature data and aging offset, which can be accessed for more precise timekeeping.

For classroom use, the DS3231 is recommended due to its superior accuracy and temperature compensation, though it typically costs more than the DS1307.

"},{"location":"lessons/rtc/13-real-time-clocks/#detecting-the-rtc-type","title":"Detecting the RTC Type","text":"

Both the DS1307 and the DS3231 appear at exactly the same address 0x68. So how can you tell which device you have? The answer is that the DS3231 has additional status, control and temperature registers we can look for. If it has these values we know it is the good stuff!

from machine import I2C, Pin\nimport time\n\n# I2C setup\ni2c = I2C(0, sda=Pin(0), scl=Pin(1))\n\n# Device addresses\nRTC_ADDR = 0x68  # Both DS1307 and DS3231 use 0x68\n\ndef identify_rtc():\n    \"\"\"\n    Identify whether the RTC is a DS1307 or DS3231\n    Returns: String identifying the RTC type\n    \"\"\"\n    try:\n        # Try to read the status register (0x0F) - only exists on DS3231\n        i2c.writeto(RTC_ADDR, b'\\x0F')\n        status = i2c.readfrom(RTC_ADDR, 1)[0]\n\n        # Try to read control register (0x0E) - only exists on DS3231\n        i2c.writeto(RTC_ADDR, b'\\x0E')\n        control = i2c.readfrom(RTC_ADDR, 1)[0]\n\n        # If we got here, it's almost certainly a DS3231\n        # Try reading temperature registers as final confirmation\n        i2c.writeto(RTC_ADDR, b'\\x11')\n        temp_data = i2c.readfrom(RTC_ADDR, 2)\n\n        return \"DS3231 (Temperature-compensated RTC)\"\n\n    except Exception as e:\n        # If we couldn't read those registers, it's probably a DS1307\n        # Let's verify by trying to read the control register (0x07) of DS1307\n        try:\n            i2c.writeto(RTC_ADDR, b'\\x07')\n            control = i2c.readfrom(RTC_ADDR, 1)[0]\n            return \"DS1307 (Basic RTC)\"\n        except:\n            return \"Unknown RTC device\"\n\ndef main():\n    print(\"\\nRTC Model Identifier\")\n    print(\"-\" * 40)\n\n    # First check if any device is present at RTC address\n    devices = i2c.scan()\n    if RTC_ADDR not in devices:\n        print(f\"No RTC found at address 0x{RTC_ADDR:02X}\")\n        return\n\n    # Identify the RTC\n    rtc_type = identify_rtc()\n    print(f\"Found: {rtc_type}\")\n\n    if \"DS3231\" in rtc_type:\n        # Read temperature for DS3231\n        i2c.writeto(RTC_ADDR, b'\\x11')\n        temp_data = i2c.readfrom(RTC_ADDR, 2)\n        temp_msb = temp_data[0]\n        temp_lsb = (temp_data[1] >> 6) * 25  # 0.25\u00b0C precision\n        temp_c = temp_msb + (temp_lsb / 100.0)\n        temp_f = (temp_c * 9/5) + 32\n        print(f\"Temperature: {temp_c:.2f}\u00b0C ({temp_f:.2f}\u00b0F)\")\n\nif __name__ == \"__main__\":\n    main()\n
"},{"location":"lessons/rtc/13-real-time-clocks/#rtcs-and-eeprom","title":"RTCs and EEPROM","text":"

Some RTCs also include a small 4K EEPROM to store information such as what time zone you are in and what the clock skew was for the last period. If your i2c scanner shows something at digital 80 (0x50) when you add your RTC, this is your EEPROM.

The device at 80 (0x50) is almost certainly an AT24C32 EEPROM (Electrically Erasable Programmable Read-Only Memory). It's commonly included on DS3231 RTC modules to provide non-volatile storage. This EEPROM can store about 4KB of data and is often used to store configuration settings.

You can get a dump of its contents by doing the following detection code:

from machine import I2C, Pin\nimport time\n\n# I2C setup\ni2c = I2C(0, sda=Pin(0), scl=Pin(1))\n\n# Device addresses\nDS3231_ADDR = 0x68  # 104 decimal\nEEPROM_ADDR = 0x50  # 80 decimal\n\ndef read_ds3231_temp():\n    \"\"\"Read temperature from DS3231\"\"\"\n    try:\n        # First ensure we're reading fresh temperature data\n        # Write to the control register (0x0E) to force a temperature conversion\n        i2c.writeto(DS3231_ADDR, b'\\x0E\\x20')  # Set CONV bit\n        time.sleep(0.2)  # Wait for conversion\n\n        # Now read temperature registers (0x11 and 0x12)\n        i2c.writeto(DS3231_ADDR, b'\\x11')\n        temp_data = i2c.readfrom(DS3231_ADDR, 2)\n\n        # MSB is the integer part\n        temp_msb = temp_data[0]\n\n        # LSB holds two bits for decimal part\n        temp_lsb = (temp_data[1] >> 6) * 25  # Convert to decimal (0.25\u00b0C precision)\n\n        # Handle negative temperatures (2's complement)\n        if temp_msb & 0x80:\n            temp_msb = -(~(temp_msb - 1) & 0xFF)\n\n        temp_c = temp_msb + (temp_lsb / 100.0)  # Combine integer and decimal parts\n        temp_f = (temp_c * 9/5) + 32\n\n        return temp_c, temp_f\n    except Exception as e:\n        return f\"Error reading temperature: {str(e)}\"\n\ndef read_ds3231_time():\n    \"\"\"Read current time from DS3231\"\"\"\n    try:\n        # Start reading from register 0x00\n        i2c.writeto(DS3231_ADDR, b'\\x00')\n        data = i2c.readfrom(DS3231_ADDR, 7)\n\n        # Convert BCD to decimal\n        def bcd2dec(bcd):\n            return (bcd & 0x0F) + ((bcd >> 4) * 10)\n\n        second = bcd2dec(data[0])\n        minute = bcd2dec(data[1])\n        hour = bcd2dec(data[2])\n        day = bcd2dec(data[4])\n        month = bcd2dec(data[5] & 0x1F)\n        year = bcd2dec(data[6]) + 2000\n\n        return f\"{year:04d}-{month:02d}-{day:02d} {hour:02d}:{minute:02d}:{second:02d}\"\n    except Exception as e:\n        return f\"Error reading time: {str(e)}\"\n\ndef read_eeprom(start_addr=0, length=32):\n    \"\"\"Read data from EEPROM\"\"\"\n    try:\n        # Create buffer for address\n        addr_buf = bytearray(2)\n        addr_buf[0] = (start_addr >> 8) & 0xFF  # High byte\n        addr_buf[1] = start_addr & 0xFF         # Low byte\n\n        # Write address to EEPROM\n        i2c.writeto(EEPROM_ADDR, addr_buf)\n\n        # Read data\n        data = i2c.readfrom(EEPROM_ADDR, length)\n        return data\n    except Exception as e:\n        return f\"Error reading EEPROM: {str(e)}\"\n\ndef main():\n    \"\"\"Main program to read and display device information\"\"\"\n    print(\"\\nI2C Device Information Reader\")\n    print(\"-\" * 40)\n\n    # Scan for devices\n    devices = i2c.scan()\n    print(f\"Found devices at addresses: {[hex(x) for x in devices]}\")\n    print(\"-\" * 40)\n\n    # Read and display DS3231 information\n    # Note that the DS1307 does not have any temperature registers\n    if DS3231_ADDR in devices:\n        print(\"DS3231 RTC Information:\")\n        print(f\"Current Time: {read_ds3231_time()}\")\n        temp_c, temp_f = read_ds3231_temp()\n        print(f\"Temperature: {temp_c:.2f}\u00b0C ({temp_f:.2f}\u00b0F)\")\n    else:\n        print(\"DS3231 not found!\")\n\n    print(\"-\" * 40)\n\n    # Read and display EEPROM contents\n    if EEPROM_ADDR in devices:\n        print(\"AT24C32 EEPROM Contents (first 32 bytes):\")\n        eeprom_data = read_eeprom(0, 32)\n\n        # Display as hex\n        print(\"Hex dump:\")\n        for i in range(0, len(eeprom_data), 16):\n            chunk = eeprom_data[i:i+16]\n            hex_values = ' '.join([f'{x:02X}' for x in chunk])\n            ascii_values = ''.join([chr(x) if 32 <= x <= 126 else '.' for x in chunk])\n            print(f\"{i:04X}: {hex_values:<48} {ascii_values}\")\n\n        # Try to interpret as ASCII\n        print(\"\\nASCII interpretation (if printable):\")\n        ascii_text = ''.join([chr(x) if 32 <= x <= 126 else '.' for x in eeprom_data])\n        print(ascii_text)\n    else:\n        print(\"EEPROM not found!\")\n\nif __name__ == \"__main__\":\n    main()\n
"},{"location":"lessons/rtc/13-real-time-clocks/#results-of-scan-on-rtm-with-eeprom","title":"Results of Scan on RTM with EEPROM","text":"
AT24C32 EEPROM Contents (first 32 bytes):\nHex dump:\n0000: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF  ................\n0010: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF  ................\nASCII interpretation (if printable):\n................................\n
"},{"location":"lessons/rtc/13-real-time-clocks/#dump-eeprom-address","title":"Dump EEPROM Address","text":"

The AT24C32/AT24C64 EEPROM typically is located at 87 (0x57).

Here is a script to dump this memory.

import time\n\n# I2C setup\ni2c = I2C(0, sda=Pin(0), scl=Pin(1))\n\n# Device addresses\nRTC_ADDR = 0x68  # Both DS1307 and DS3231 use 0x68\n# EEPROM address\nEEPROM_ADDR = 0x57\n\ndef dump_eeprom():\n   # Read first 100 bytes in chunks of 8\n   for addr in range(0, 100, 8):\n       data = i2c.readfrom_mem(EEPROM_ADDR, addr, 8)\n       print(f\"0x{addr:02x}:\", \" \".join([f\"{x:02x}\" for x in data]))\n       time.sleep(0.1)  # Small delay between reads\n\ndump_eeprom()\n
"},{"location":"lessons/rtc/13-real-time-clocks/#setting-thr-rtc-from-localtime","title":"Setting thr RTC from localtime","text":"

After you get your RTC clock working it is time to set the correct time in the RTC. You will notice that when the RTC first comes up the date and time will be set to January 1st 2015 at midnight. We now want to update this time with the time from your local laptop or desktop. If you use Thonny, the localtime in the Pico is automatically set to the time on computer when Thonny starts up and connects to the Raspberry Pi Pico.

Now we just need a small program to take the localtime and write it to the RTC's clock. Here is tha line:

i2c.writeto_mem(DS3231_ADDR, 0x00, data)\n

Where data is a binary coded list of binary-coded integers that represent the datetime structure. Here is an example program called set-rtc-from-localtime.py

One other note. The DS3231 has an internal oscillator that can get turned off. After you set the time it is important to make sure that the oscillator is running correctly again. The function reset_osf() will clear the stop flag to make sure that the clock is ticking.

from machine import Pin, I2C\nfrom utime import localtime\n\n# Constants\nDS3231_ADDR = 0x68\nSTATUS_REG = 0x0F  # Status register address\n\n# I2C setup\nsda = Pin(0, Pin.OUT)\nscl = Pin(1, Pin.OUT)\ni2c = I2C(0, scl=scl, sda=sda, freq=3000000)\n\ndef dec2bcd(dec):\n    \"\"\"Convert decimal to binary coded decimal.\"\"\"\n    return (dec // 10) << 4 | (dec % 10)\n\ndef check_osf():\n    \"\"\"Check the oscillator stop flag.\"\"\"\n    status = i2c.readfrom_mem(DS3231_ADDR, STATUS_REG, 1)[0]\n    return bool(status >> 7)\n\ndef reset_osf():\n    \"\"\"Clear the oscillator stop flag.\"\"\"\n    status = bytearray(1)\n    i2c.readfrom_mem_into(DS3231_ADDR, STATUS_REG, status)\n    i2c.writeto_mem(DS3231_ADDR, STATUS_REG, bytearray([status[0] & 0x7f]))\n\ndef set_ds3231():\n    \"\"\"Set the DS3231 RTC time and ensure oscillator is running.\"\"\"\n    now = localtime()\n    year = now[0] % 100  # Convert to 2-digit year\n    month = now[1]\n    day = now[2]\n    hour = now[3]\n    minute = now[4]\n    second = now[5]\n\n    # First check if oscillator is stopped\n    if check_osf():\n        print(\"Oscillator was stopped. Resetting OSF flag...\")\n        reset_osf()\n\n    data = bytearray([\n        dec2bcd(second),\n        dec2bcd(minute),\n        dec2bcd(hour),\n        dec2bcd(now[6] + 1),  # Convert weekday from 0-6 to 1-7\n        dec2bcd(day),\n        dec2bcd(month),\n        dec2bcd(year)\n    ])\n\n    i2c.writeto_mem(DS3231_ADDR, 0x00, data)\n    print(f\"RTC set to: {month}/{day}/{now[0]} {hour:02d}:{minute:02d}:{second:02d}\")\n\n    # Verify oscillator is running\n    if check_osf():\n        print(\"Warning: Oscillator still shows stopped state!\")\n    else:\n        print(\"Oscillator running normally\")\n\nif __name__ == \"__main__\":\n    set_ds3231()\n
"},{"location":"lessons/rtc/13-real-time-clocks/#bcd-format","title":"BCD Format","text":"
def dec2bcd(dec):\n    \"\"\"Convert decimal to binary coded decimal.\"\"\"\n    return (dec // 10) << 4 | (dec % 10)\n

This converts regular numbers to Binary Coded Decimal format that the DS3231 requires. For example, 23 becomes 0x23.

BCD (Binary-Coded Decimal) is used in the DS3231 RTC for three main reasons:

  1. Readability - Each digit maps directly to its binary value (e.g., 23 becomes 0x23), making it easier to read register values when debugging.
  2. Easy conversion - Converting between BCD and human-readable time formats requires minimal processing, important for simple microcontrollers
  3. Historical compatibility - Many early digital clock chips used BCD, and the format persisted in RTC design for consistency

In BCD, each decimal digit (0-9) is stored in 4 bits, with two digits per byte. For example:

Decimal 23 \u2192 BCD 0010 0011 Decimal 59 \u2192 BCD 0101 1001

"},{"location":"lessons/rtc/13-real-time-clocks/#avoid-the-builtin-machinertc-library","title":"Avoid the Builtin Machine.RTC Library","text":"

The machine.RTC() on the Pico uses an internal oscillator that loses time on power loss, unlike the DS3231 which maintains time with its battery backup. The DS3231 is also more accurate (\u00b12ppm vs \u00b110% for the internal RTC).

Unless you need absolute minimal components, stick with the DS3231 for better accuracy and battery backup capability.

The internal RTC example on the MicroPython RP2 site would work functionally, but would need resetting after each power cycle.

"},{"location":"lessons/rtc/13-real-time-clocks/#reading-the-temperature-from-the-ds3231-real-time-clock","title":"Reading the Temperature from the DS3231 Real Time Clock","text":"

The math of reading the temperature from the DS3231 is unfortunately rather complicated. Here are the steps

  1. Strip off the sign bit by doing a Boolean AND with 0x7f
  2. If the MSB is set we need to make the temperature negative
  3. Then we add the fraction from the LSB
  4. Finally, we convert the temperature in C to F
def read_temperature():\n    \"\"\"Read temperature from DS3231 RTC.\"\"\"\n    # Read temperature registers\n    i2c.writeto(DS3231_ADDR, b'\\x11')\n    temp_data = i2c.readfrom(DS3231_ADDR, 2)\n    temp_msb = temp_data[0]\n    temp_lsb = temp_data[1]\n\n    # Get raw temp value (ignoring sign bit)\n    raw_temp = temp_msb & 0x7F  # Strip off sign bit\n\n    # 0xD7 & 0x7F = 0x57 = 87 decimal (original value minus sign bit)\n    # If sign bit was set, make it negative\n    if temp_msb & 0x80:\n        raw_temp = raw_temp ^ 0x7F  # Invert the bits\n        raw_temp = -(raw_temp + 1)  # Two's complement\n\n    # Add fraction from LSB\n    frac = (temp_lsb >> 6) * 0.25\n    temp_c = raw_temp + frac\n\n    # Convert to Fahrenheit\n    temp_f = (temp_c * 9.0 / 5.0) + 32.0\n\n    print(f\"Raw temp (after sign bit removal): {raw_temp}\")\n    print(f\"Temperature: {temp_c}\u00b0C = {temp_f}\u00b0F\")\n\n    return temp_f\n
"},{"location":"lessons/rtc/13-rtc-explained/","title":"RTC Explained","text":""},{"location":"lessons/rtc/13-rtc-explained/#explaining-rtc-to-students","title":"Explaining RTC to Students","text":"

Prompt

You are a friendly and fun science teacher working with a group of high-school students taking a course called \"Programming Clocks and Watches\". Today your lecture is on \"Real time Clocks\" like the old DS1307 and the newer super accurate DS3231. Give the full text of a 15-minute lecture before the hands-on lab and mention where images should be inserted.

Hello everyone, and welcome to today's exciting lecture on Real Time Clocks! In our \"Programming Clocks and Watches\" course, we're going to dive into the fascinating world of the good old DS1307 used in our classrooms and the new super accurate DS3231.

First, let's talk about what a Real Time Clock (RTC) is. An RTC is a computer chip that keeps track of the current time, even when the main power is off. It's like a tiny, dedicated clock inside your electronic devices. RTCs are found in computers, smartphones, appliances, and many other gadgets that need to keep accurate time.

Here is how the DS1307 is typically used in a microcontroller project.

The DS1307 is one of the most well-known RTCs. We have a LOT of them lying around the lab. This little chip was first introduced in 2005 and has been widely used in various electronic projects. The DS1307 keeps track of seconds, minutes, hours, days, months, and years. It communicates with the main processor using a protocol called I2C (Inter-Integrated Circuit).

Now, let's take a look at how the DS1307 works. It has a built-in crystal oscillator that vibrates at a specific frequency, acting as a precise clock source. The chip counts these oscillations to keep track of time. The DS1307 also has a small amount of battery-backed memory, allowing it to maintain the time even when the main power is off.

Speaking of power, the DS1307 uses a small coin cell battery to keep the clock running when the main power is disconnected. This battery can last for several years, ensuring that your device always has the correct time.

Now, let's move on to the newer and more accurate DS3231. Released in 2011, the DS3231 is known for its incredible precision. It has a temperature-compensated crystal oscillator (TCXO), which means it can maintain accurate timekeeping even when the temperature changes. This is important because temperature fluctuations can affect the frequency of regular crystal oscillators, causing the clock to drift over time.

Device Strength Accuracy (PPM) Seconds Drift Per Month Price DS1307 Low Cost \u00b120 \u00b152 $1 DS3231 Accuracy \u00b12 \u00b15 $3

The DS1307 uses a standard crystal oscillator while the DS3231 uses a temperature-compensated crystal oscillator (TCXO), explaining the accuracy difference. How accurate is the DS3231, you might ask? Well, it has a typical accuracy of \u00b12 parts per million (ppm), which translates to a drift of about 1 minute per year! In comparison, the DS1307 has an accuracy of around \u00b120 ppm, which means it can drift by about 10 minutes per year. That's a significant difference!

[Insert image of I2C communication]

Just like the DS1307, the DS3231 communicates with the main processor using the I2C protocol. It has a simple register-based interface that allows you to read and set the time easily.

Now, let's talk about some fun applications of RTCs. One cool project you can build is a digital clock that always displays the correct time, even if it's been unplugged for a while. You can also create a data logger that records temperature, humidity, or other sensor readings along with precise timestamps. The possibilities are endless!

In our hands-on lab today, we'll be working with both the DS1307 and the DS3231. You'll learn how to wire them up, set the time, and read the current time using a microcontroller. We'll also explore some practical examples and discuss how to choose the right RTC for your projects.

So, get ready to have some fun with Real Time Clocks! Remember, whether you're building a simple clock or a complex data logging system, an RTC is an essential component that keeps everything running on time.

Let's head to the lab and start tinkering with these amazing little chips!

"},{"location":"lessons/rtc/13-rtc-explained/#testing-the-ds1307-and-ds3232-real-time-clocks","title":"Testing the DS1307 and DS3232 Real Time Clocks","text":""},{"location":"lessons/rtc/13-rtc-explained/#hardware-configuration","title":"Hardware Configuration","text":""},{"location":"lessons/rtc/13-rtc-explained/#ds1307-connections","title":"DS1307 Connections","text":"
  1. CLK - clock
  2. DST - Data (incoming to Pico)
  3. RST - Reset
  4. VCC - must be 5 volts, not 3.2
  5. GND - ground

We will use the lower right pins on the Pico so that the display can use the pins in the upper-right corner.

These pin assignments are

  1. GP16 - far lower right - CLK
  2. GP17 - second from the bottom - DST
  3. GP18 - fourth pin up - RST

Contents of config.py

CLK_PIN = 16\nDST_PIN = 17\nRST_PIN = 18\n
"},{"location":"lessons/rtc/13-rtc-explained/#sample-ds1307-micropython-test-program-for-the-raspberry-pi-pico","title":"Sample DS1307 MicroPython Test Program for the Raspberry Pi Pico","text":"
from machine import I2C, Pin\nimport config\n\nCLK_PIN = config.CLK_PIN\nDST_PIN = config.DST_PIN\nDS_PIN = config.DS_PIN\n\nprint(\"Clock on pin:\", CLK_PIN)\nprint(\"Data on pin:\", DST_PIN)\nprint(\"Data Select on pin:\", DS_PIN)\n\ni2c = I2C(0, scl=Pin(CLK_PIN), sda=Pin(DST_PIN), freq=100000)\nscan_result = i2c.scan()\nprint(\"I2C addresses found:\", [hex(device_address) for device_address in scan_result])\n\nif 104 in scan_result:\n    print(\"PASS: DS1307 FOUND\")\nelse:\n    print(\"FAIL: DS1307 NOT FOUND\")\n
"},{"location":"lessons/rtc/13-rtc-explained/#sample-test-result","title":"Sample Test Result","text":"
Clock on pin: 17\nData on pin: 16\nData Select on pin: 18\nI2C addresses found: ['0x50', '0x68']\nPASS: DS1307 FOUND\n
"},{"location":"lessons/rtc/13-rtc-explained/#full-test-of-ds1307","title":"Full Test of DS1307","text":"
#!/usr/bin/env python3\n# -*- coding: UTF-8 -*-\n\n\"\"\"I2C DS1307 showcase\"\"\"\n\nfrom ds1307 import DS1307\nfrom machine import I2C, Pin\nfrom utime import gmtime, sleep, time\nimport config\nCLK_PIN = config.CLK_PIN\nDST_PIN = config.DST_PIN\nDS_PIN = config.DS_PIN\n\n# DS1307 on 0x68\nI2C_ADDR = 0x68     # DEC 104, HEX 0x68\n\n# define custom I2C interface, default is 'I2C(0)'\n# check the docs of your device for further details and pin infos\n# this are the pins for the Raspberry Pi Pico adapter board\ni2c = I2C(0, scl=Pin(CLK_PIN), sda=Pin(DST_PIN), freq=800000)\nprint(i2c.scan())\nds1307 = DS1307(addr=I2C_ADDR, i2c=i2c)\n\n# get LCD infos/properties\nprint(\"DS1307 is on I2C address 0x{0:02x}\".format(ds1307.addr))\nprint(\"Weekday start is {}\".format(ds1307.weekday_start))\n\n# get the current RTC time\nprint(\"Current RTC time: {}\".format(ds1307.datetime))\n\n# set the RTC time to the current system time\nnow = gmtime(time())\nds1307.datetime = now\n\n# Print the date and time in ISO8601 format: 2023-04-18T21:14:22\nprint(\"Today is {:04d}-{:02d}-{:02d}T{:02d}:{:02d}:{:02d}\".format(\n    ds1307.year, ds1307.month, ds1307.day,\n    ds1307.hour, ds1307.minute, ds1307.second))\n\n# check whether this year is a leap year\nprint(\"Is this year a leap year? {}\".format(ds1307.is_leap_year(ds1307.year)))\n\n# get the day of the year\nprint(\"Today is day {} of {}\".format(\n    ds1307.day_of_year(year=ds1307.year, month=ds1307.month, day=ds1307.day),\n    ds1307.year))\n\n# halt the oscillator\nprint(\"The oscillator is currently active at {}? {}\".format(\n    ds1307.datetime, ds1307.halt))\nprint(\"Halt the oscillator and wait for 5 seconds ...\")\nds1307.halt = True\nsleep(5)\n\nprint(\"Current RTC time: {}\".format(ds1307.datetime))\n\nprint(\"Enable the oscillator and wait for 5 seconds ...\")\nds1307.halt = False\nsleep(5)\nprint(\"Current RTC time: {}\".format(ds1307.datetime))\n
"},{"location":"lessons/rtc/13-rtc-explained/#test-result","title":"Test Result","text":"
[80, 104]\nDS1307 is on I2C address 0x68\nWeekday start is 0\nCurrent RTC time: (2024, 12, 27, 20, 29, 47, 4, 362)\nToday is 2024-12-27T20:29:51\nIs this year a leap year? True\nToday is day 362 of 2024\nThe oscillator is currently active at (2024, 12, 27, 20, 29, 51, 4, 362)? False\nHalt the oscillator and wait for 5 seconds ...\nCurrent RTC time: (2024, 12, 27, 20, 29, 51, 4, 362)\nEnable the oscillator and wait for 5 seconds ...\nCurrent RTC time: (2024, 12, 27, 20, 29, 56, 4, 362)\n
"},{"location":"lessons/rtc/13-rtc-explained/#localtime-and-gmtimetime","title":"Localtime and gmtime(time())","text":"
from utime import gmtime, time, localtime\nprint(\"time()\", time())\nprint(\"   localtime()\", localtime())\nprint(\"gmtime(time())\", gmtime(time()))\n

Result

time() 1735332067\n   localtime() (2024, 12, 27, 20, 41, 7, 4, 362)\ngmtime(time()) (2024, 12, 27, 20, 41, 7, 4, 362)\n
This code is running on MicroPython on the Raspberry Pi Pico (RP2040) and demonstrates the usage of time-related functions from the utime module.

Let's break it down:

  1. from utime import gmtime, time, localtime: This line imports the gmtime, time, and localtime functions from the utime module, which is MicroPython's equivalent of the time module in standard Python.
  2. print(\"time()\", time()): This line prints the label \"time()\" followed by the result of calling the time() function. The time() function returns the number of seconds elapsed since the Unix epoch (January 1, 1970, 00:00:00 UTC) as an integer value. In your output, time() returned 1735332067, which represents the current timestamp.
  3. print(\" localtime()\", localtime()): This line prints the label \"localtime()\" followed by the result of calling the localtime() function. The localtime() function takes no arguments and returns a tuple representing the current local time. The tuple contains the following elements in order: (year, month, day, hour, minute, second, weekday, yearday). In your output, localtime() returned (2024, 12, 27, 20, 41, 7, 4, 362), indicating the current local time on the Raspberry Pi Pico.
  4. print(\"gmtime(time())\", gmtime(time())): This line prints the label \"gmtime(time())\" followed by the result of calling the gmtime() function with the current timestamp obtained from time(). The gmtime() function takes a timestamp as an argument and returns a tuple representing the corresponding UTC time. The tuple has the same format as the one returned by localtime(). In your output, gmtime(time()) returned (2024, 12, 27, 20, 41, 7, 4, 362), which represents the current UTC time.

What does the number that time() returns represent?

The number returned by time() represents the number of seconds that have elapsed since the Unix epoch (January 1, 1970, 00:00:00 UTC). This value is commonly known as the Unix timestamp or epoch time. It is a widely used standard for representing points in time and is independent of time zones.

In our example, time() returned 1735332067. This timestamp corresponds to the date and time shown in the localtime() and gmtime(time()) outputs, which is December 27, 2024, at 20:41:07 UTC.

The Unix timestamp is a useful representation of time because it allows for easy arithmetic operations on timestamps and can be converted to human-readable formats using functions like localtime() and gmtime(). It is widely used in various programming languages and systems for time-related operations.

It's important to note that the accuracy and synchronization of the time on the Raspberry Pi Pico depend on its internal clock and any time synchronization mechanisms used. Without external time synchronization, the Pico's internal clock may drift over time, resulting in slight inaccuracies compared to the actual current time.

"},{"location":"lessons/rtc/13-rtc-explained/#accuracy-of-the-clock-on-the-raspberry-pi-pico","title":"Accuracy of the Clock on the Raspberry Pi Pico**","text":"

The Raspberry Pi Pico does not have a real-time clock (RTC) built into its hardware. Its clock is derived from the internal oscillator, which may be subject to drift and is not highly accurate for long-term timekeeping. The accuracy depends on the quality of the oscillator and the environmental conditions, such as temperature.

\\[ 1\\% \\text{ of 24 hours} = 0.01 \\times 24 \\times 3600 \\approx 864 \\, \\text{seconds off per day}. \\]

Thus, the clock on the Pico can drift up to \u00b1864 seconds per day without correction.

For improved accuracy, you can add an external RTC module to the Pico.

Accuracy of the Clock on the DS1307

The DS1307 is a low-cost RTC chip widely used in electronics projects. Its accuracy is based on an external 32.768 kHz quartz crystal oscillator, which is sensitive to factors like temperature and crystal quality. \u2022 Drift: The DS1307 typically drifts by \u00b120 ppm (parts per million) at 25\u00b0C. \u2022 Seconds off per day: With a drift of 20 ppm:

\\[ 20 \\, \\text{ppm} \\times 24 \\times 3600 \\, \\text{seconds} = 1.728 \\, \\text{seconds off per day}. \\]

The DS1307 clock may be off by approximately \u00b11.7 seconds per day under ideal conditions.

For more accurate timekeeping, consider using a higher-quality RTC like the DS3231, which has a drift of \u00b12 ppm (about \u00b10.17 seconds per day) due to its temperature compensation feature.

"},{"location":"lessons/rtc/13-rtc-explained/#references","title":"References","text":""},{"location":"lessons/rtc/50-ds1307-breadboard/","title":"Using the DS1307 IC On A Breadboard","text":"

You can use the DS1307 IC directly on a breadboard without a development board. Here's what you'll need:

  1. DS1307 IC (8-pin DIP package)
  2. 32.768 kHz crystal oscillator
  3. CR2032 battery holder and battery (for backup power)
  4. Two 4.7k\u03a9 pull-up resistors (for I2C)
  5. 0.1\u00b5F decoupling capacitor
  6. Standard through-hole breadboard

The basic connections are:

DS1307 Pinout:\nPin 1 (X1) -> Crystal\nPin 2 (X2) -> Crystal\nPin 3 (VBAT) -> Battery positive\nPin 4 (GND) -> Ground\nPin 5 (SDA) -> I2C Data (needs pull-up)\nPin 6 (SCL) -> I2C Clock (needs pull-up)\nPin 7 (SQW) -> Optional square wave output\nPin 8 (VCC) -> 5V power`\n

The biggest advantages of using the raw IC are:

Just remember that the DS1307 requires 5V power (unlike its successor DS3231 which can work with 3.3V), so make sure your microcontroller's I2C lines can handle 5V or use a level shifter if necessary.

"},{"location":"lessons/rtc/testing-the-rtc-type/","title":"Testing the RTC Type","text":""},{"location":"lessons/rtc/testing-the-rtc-type/#two-types","title":"Two Types","text":""},{"location":"lessons/rtc/testing-the-rtc-type/#tests","title":"Tests","text":""},{"location":"lessons/rtc/testing-the-rtc-type/#tests-specific-register-patterns-that-are-unique-to-the-ds3231","title":"Tests specific register patterns that are unique to the DS3231:","text":"
-   Control register bit patterns\n-   Status register bit patterns\n-   Temperature register behavior\n
"},{"location":"lessons/rtc/testing-the-rtc-type/#performs-consistency-checks","title":"Performs consistency checks:","text":"
-   Verifies temperature readings are in valid range (-40\u00b0C to +85\u00b0C)\n-   Checks if temperature readings make sense\n-   Verifies register bit patterns match datasheet specifications\n
"},{"location":"lessons/rtc/testing-the-rtc-type/#better-ds1307-detection","title":"Better DS1307 detection:","text":"
-   Checks the DS1307's control register pattern\n-   Verifies the register behaves as expected\n
from machine import Pin, I2C\nfrom utime import sleep\n\ndef test_ds3231_features(i2c, addr):\n    \"\"\"\n    More thoroughly test if the device is actually a DS3231\n    Returns: (is_ds3231, error_message)\n    \"\"\"\n    try:\n        # Test 1: Check control and status registers with specific masks\n        # DS3231 control register (0x0E) should have bits 6,5,4,2 readable/writable\n        # Other bits should be 0\n        control = i2c.readfrom_mem(addr, 0x0E, 1)[0]\n        if control & 0x83 != 0:  # Bits 7,1,0 should be 0\n            return False, \"Control register pattern doesn't match DS3231\"\n\n        # Test 2: Status register (0x0F) should only have bits 7,3,2,1,0 possibly set\n        status = i2c.readfrom_mem(addr, 0x0F, 1)[0]\n        if status & 0x70 != 0:  # Bits 6,5,4 should be 0\n            return False, \"Status register pattern doesn't match DS3231\"\n\n        # Test 3: Try temperature register consistency check\n        # Read temperature twice with a forced conversion between\n        temp1 = i2c.readfrom_mem(addr, 0x11, 2)\n\n        # Force conversion\n        control |= (1 << 5)  # Set CONV bit\n        i2c.writeto_mem(addr, 0x0E, bytes([control]))\n        sleep(0.01)  # Wait for conversion\n\n        temp2 = i2c.readfrom_mem(addr, 0x11, 2)\n\n        # The readings should be similar and in a reasonable range (-40\u00b0C to +85\u00b0C)\n        msb1 = temp1[0]\n        msb2 = temp2[0]\n\n        # Check if temperatures are in valid range\n        if abs(msb1) > 85 or abs(msb2) > 85:\n            return False, \"Temperature readings out of valid range\"\n\n        return True, \"DS3231 verified\"\n\n    except Exception as e:\n        return False, f\"Error testing DS3231 features: {str(e)}\"\n\ndef identify_rtc():\n    \"\"\"\n    Identify whether the RTC is a DS1307 or DS3231\n    \"\"\"\n    # Initialize I2C\n    i2c = I2C(0, sda=Pin(8), scl=Pin(9), freq=100000)\n\n    RTC_ADDR = 0x68\n\n    # First check if any device is present\n    devices = i2c.scan()\n    if RTC_ADDR not in devices:\n        return \"No RTC found at address 0x68\"\n\n    # Test for DS3231 features\n    is_ds3231, message = test_ds3231_features(i2c, RTC_ADDR)\n\n    if is_ds3231:\n        # Get temperature if it's really a DS3231\n        try:\n            temp_msb = i2c.readfrom_mem(RTC_ADDR, 0x11, 1)[0]\n            temp_lsb = i2c.readfrom_mem(RTC_ADDR, 0x12, 1)[0] >> 6\n\n            if temp_msb & 0x80:\n                temp_msb = -(~temp_msb & 0x7F) - 1\n\n            temp_c = temp_msb + ((temp_lsb & 0x03) * 0.25)\n            temp_f = (temp_c * 9/5) + 32\n\n            return f\"DS3231 found! Current temperature: {temp_c:.2f}\u00b0C ({temp_f:.2f}\u00b0F)\"\n        except Exception as e:\n            return f\"DS3231 found but error reading temperature: {str(e)}\"\n    else:\n        # Test for DS1307\n        try:\n            # DS1307 has a unique control register at 0x07\n            # It should only have bit 4 (OUT) possibly set\n            control = i2c.readfrom_mem(RTC_ADDR, 0x07, 1)[0]\n            if control & 0xEF == 0:  # All bits except bit 4 should be 0\n                return \"DS1307 found!\"\n            else:\n                return \"Found RTC at 0x68 but cannot definitively identify type\"\n        except:\n            return \"Unknown RTC device type\"\n\ndef main():\n    print(\"\\nRTC Identifier\")\n    print(\"-\" * 40)\n\n    result = identify_rtc()\n    print(result)\n\nif __name__ == \"__main__\":\n    main()\n
"},{"location":"lessons/screen-drawing/01-drawing-coordinates/","title":"Drawing Coordinates","text":"

Screen Coordinates Lab on Robot Faces

"},{"location":"lessons/screen-drawing/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/screen-drawing/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/screen-drawing/03-drawing-face/#references","title":"References","text":"

Instructables on Filled Circles and Triangles by TonyGo2

"},{"location":"lessons/screen-drawing/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/screen-drawing/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/screen-drawing/05-drawing-polygons/","title":"Drawing Polygons","text":""},{"location":"lessons/screen-drawing/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/screen-drawing/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/screen-drawing/06-draw-performance/#references","title":"References","text":"
  1. Notes on the very slow refresh rate for ST7735
"},{"location":"lessons/screen-drawing/07-drawing-digits/","title":"Drawing Clock Digits","text":""},{"location":"lessons/screen-drawing/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/screen-drawing/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/screen-drawing/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/screen-drawing/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/screen-drawing/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, 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 # 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 the line width function in the next lesson.

"},{"location":"lessons/screen-drawing/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/screen-drawing/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/screen-drawing/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/screen-drawing/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/screen-drawing/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/screen-drawing/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/screen-drawing/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/screen-drawing/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/screen-drawing/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/screen-drawing/16-drawing-alarm-icon/","title":"Drawing a Scalable Alarm Bell Icon","text":""},{"location":"lessons/screen-drawing/16-drawing-alarm-icon/#sample-prompt","title":"Sample Prompt","text":"

Prompt

Using the MicroPython poly function, please create a Python function that draws an alarm icon on a 22x24 grid. The function should have three parameters: x, y and scale where x and y are the upper left corner coordinates. Just draw the bell body and the clapper.

Test the icon at scales from 12 to 36.

"},{"location":"lessons/screen-drawing/16-drawing-alarm-icon/#sample-result","title":"Sample Result","text":"
from machine import Pin, I2C, SPI\nfrom utime import sleep, localtime\nfrom ds1307 import DS1307\nfrom array import array\n\nimport ssd1306\nimport config\n\nSCL=Pin(config.SPI_SCL_PIN)\nSDA=Pin(config.SPI_SDA_PIN)\nDC = Pin(config.SPI_DC_PIN)\nRES = Pin(config.SPI_RESET_PIN)\nCS = Pin(config.SPI_CS_PIN)\nSPI_BUS = config.SPI_BUS\nWIDTH = config.DISPLAY_WIDTH\nHEIGHT = config.DISPLAY_HEIGHT\n\nspi=SPI(SPI_BUS, sck=SCL, mosi=SDA, baudrate=1000000)\noled = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)\n\ni2c_sda = Pin(config.I2C_SDA_PIN)\ni2c_scl = Pin(config.I2C_SCL_PIN)\nI2C_BUS = config.I2C_BUS\nRTC_TYPE = config.RTC_TYPE\nRTC_I2C_ADDR = config.RTC_I2C_ADDR\n\n# I2C setup\ni2c = I2C(I2C_BUS, scl=i2c_scl, sda=i2c_sda, freq=3000000)\nrtc = DS1307(addr=RTC_I2C_ADDR, i2c=i2c)\n\ndef draw_alarm_icon(display, x, y, size=24):\n    \"\"\"\n    Draw an alarm bell icon with time display using polygons.\n\n    Args:\n        display: SSD1306 display instance\n        x, y: Top-left position for the icon\n        size: Base size of the icon (default 24 pixels)\n    \"\"\"\n    # Scale factors\n    scale = size / 24  # Base size is 24 pixels\n\n    # Bell body coordinates (scaled from base design 22 wide and 20 high)\n    bell_body = array('B', [\n        # start at the top right and go 0,4,10,17 and 20 down\n        int(14 * scale), int(0 * scale),    # Top right of dome top row right\n        int(17 * scale), int(4 * scale),    # Top right of dome 2nd row\n        int(19 * scale), int(10 * scale),   # Bottom right curve\n        int(18 * scale), int(16 * scale),   # Right side indent\n        # bottom row\n        int(22 * scale), int(20 * scale),   # Right bottom corner\n        int(0 * scale),  int(20 * scale),   # Bottom left corner\n        # left side\n        int(4 * scale),  int(16 * scale),   # Left up indent\n        int(3 * scale),  int(10 * scale),   # Left up\n        int(5 * scale),  int(4 * scale),    # Top left of dome 2nd row\n        int(8 * scale),  int(0 * scale),    # Top left of dome top row left\n    ])\n\n    # Clapper coordinates\n    clapper = array('B', [\n        int(10 * scale), int(20 * scale),   # Top left\n        int(12 * scale), int(20 * scale),   # Top right\n        int(14 * scale), int(24 * scale),   # Bottom right\n        int(8 * scale), int(24 * scale),   # Bottom left\n    ])\n\n    # Draw the components\n    display.poly(x, y, bell_body, 1, 1)  # Filled bell body\n    display.poly(x, y, clapper, 1, 1)    # Filled clapper\n\ndef demo_alarm_icons(display):\n    \"\"\"\n    Demonstrate the alarm icon at different sizes and positions\n    \"\"\"\n    # Clear the display\n    display.fill(0)\n\n    # Draw three different sized bells\n    draw_alarm_icon(display, 0,  0, size=10)    # Very small bell 10\n    draw_alarm_icon(display, 20, 0, size=14)    # Very small bell 14 \n    draw_alarm_icon(display, 40, 0, size=16)    # Very small bell 16\n    draw_alarm_icon(display, 60, 0, size=20)    # Small bell 20\n    draw_alarm_icon(display, 80, 0, size=24)    # Standard bell 24\n\n    draw_alarm_icon(display, 0,  24, size=28)   # Medium bell 28\n    draw_alarm_icon(display, 30, 24, size=32)   # Medium bell 28\n    draw_alarm_icon(display, 60, 24, size=36)   # Large bell 36\n\n\n    # Update the display\n    display.show()\n\n\ndemo_alarm_icons(oled)\n
"},{"location":"prompts/","title":"Prompts","text":""},{"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 \"Designing Building Clocks and Watches with AI and MicroPython\".

The course is a hands on-course that uses the Raspberry Pi Pico W, low-cost displays, and real-time clocks 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 graphic 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, jumper wires, a Raspberry Pi Pico W, LEDs, a SSD1307 128x64 OLED graphical display, buttons, a DS3231 real-time clock and a speaker.

Use the 2001 Bloom Taxonomy to guild the course description.

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

Course Title: Designing Building Clocks and Watches with AI and 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 moving from foundational knowledge, and terms 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":"

Prompt

You are an expert at generating concept lists for online training courses. Please generate a list of the 200 core concepts that need to be covered in this course and the order in which they should be taught.

Return numbered list of these core concepts using a single line per concept. For each concept, just return a short concept name and use acronyms or abbreviations when appropriate. They will be used as labels in a graph network. Make sure that the concepts are distinct not duplicated in the list.

Use title case for the concept names.

Start with the most basic foundational concepts and then proceed to the most complex concepts.

"},{"location":"prompts/02-concept-enumeration/#response","title":"Response","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_1","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.

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.

"},{"location":"prompts/03-concept-dependency/#result","title":"Result","text":""},{"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-graphics-asynchronous-concurrency","title":"Category 6: Graphics, 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

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.\"

Clock and Watches

"},{"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":"prompts/05-taxonomy-assignment/#response","title":"Response","text":"

Clock and Watches

"},{"location":"prompts/06-banner-image/","title":"Generate a Banner Image","text":"

Prompt

Please generate a banner image for my new website called \"Designing and Building Clocks and Watches with MicroPython and AI\". Make the banner features high school age students with diverse skin tone having fun working together and building clock and watch projects in a school. Use bright colors but no text. Use images of both digital and analog clocks and smartwatches. Place breadboards, wire, microcontrollers digital watch displays, smartwatch displays and speakers on the table.

"},{"location":"prompts/06-banner-image/#sample-result","title":"Sample Result","text":""},{"location":"prompts/07-faq/","title":"FAQ Prompt","text":"

Prompt

Take a look at the course description and the concepts lists file in this project. Now create a FAQ of the 100 most likely questions that students will ask.

Return the FAQ in Markdown format with the question in level 4 headers and the answers in the body text. Group the FAQs in level 2 headers.

"},{"location":"prompts/07-faq/#result","title":"Result","text":""},{"location":"prompts/07-faq/#building-clocks-and-watches-with-micropython-frequently-asked-questions","title":"Building Clocks and Watches with MicroPython - Frequently Asked Questions","text":""},{"location":"prompts/07-faq/#general-course-information","title":"General Course Information","text":""},{"location":"prompts/07-faq/#what-prior-knowledge-do-i-need-for-this-course","title":"What prior knowledge do I need for this course?","text":"

No specific electronics or programming experience is required. However, basic computer literacy and comfort with mathematics at a high school level will be helpful. The course starts with fundamentals and builds up gradually.

"},{"location":"prompts/07-faq/#how-long-is-each-class-session","title":"How long is each class session?","text":"

While the course runs for 14 weeks, specific class session lengths are determined by your school's schedule. Contact your instructor for the exact timing of your sessions.

"},{"location":"prompts/07-faq/#do-i-need-to-purchase-my-own-equipment","title":"Do I need to purchase my own equipment?","text":"

Check with your instructor about equipment policies. Generally, core components like the Raspberry Pi Pico W and basic electronics will be provided, but you may want to purchase additional components for your final project.

"},{"location":"prompts/07-faq/#what-software-do-i-need-to-install","title":"What software do I need to install?","text":"

You'll need the Thonny IDE (a Python editor designed for MicroPython), which is free and works on Windows, Mac, and Linux. Your instructor will guide you through the installation process during the first week.

"},{"location":"prompts/07-faq/#technical-questions","title":"Technical Questions","text":""},{"location":"prompts/07-faq/#what-is-micropython-and-how-is-it-different-from-regular-python","title":"What is MicroPython and how is it different from regular Python?","text":"

MicroPython is a streamlined version of Python designed to run on microcontrollers. While it maintains Python's core features and syntax, it's optimized for hardware control and has special libraries for working with electronic components.

"},{"location":"prompts/07-faq/#what-is-a-raspberry-pi-pico-w","title":"What is a Raspberry Pi Pico W?","text":"

The Raspberry Pi Pico W is a small, affordable microcontroller board with built-in Wi-Fi. It's perfect for learning because it's user-friendly, well-documented, and has plenty of input/output pins for connecting different components.

"},{"location":"prompts/07-faq/#will-we-be-doing-any-soldering","title":"Will we be doing any soldering?","text":"

Most projects will use breadboards for non-permanent connections. Some optional advanced projects might involve soldering, but this will be optional and supervised.

"},{"location":"prompts/07-faq/#how-accurate-will-our-clocks-be","title":"How accurate will our clocks be?","text":"

The accuracy depends on the components used. With the basic oscillator, clocks might drift a few seconds per day. When we add a Real-Time Clock (RTC) module, accuracy improves significantly to within a few seconds per month.

"},{"location":"prompts/07-faq/#project-related-questions","title":"Project-Related Questions","text":""},{"location":"prompts/07-faq/#what-kind-of-displays-will-we-use","title":"What kind of displays will we use?","text":"

We'll work with several types: - Simple LEDs for basic digital displays - 128\u00d764 OLED displays for more sophisticated interfaces - Other display options for final projects

"},{"location":"prompts/07-faq/#can-i-create-my-own-custom-clock-design-for-the-final-project","title":"Can I create my own custom clock design for the final project?","text":"

Yes! The final project is designed to be creative and personal. You can combine different components and features we've learned about to create something unique.

"},{"location":"prompts/07-faq/#how-will-we-handle-time-zones-and-daylight-saving-time","title":"How will we handle time zones and daylight saving time?","text":"

We'll learn to sync time using web services (when Wi-Fi is available) and program time zone adjustments. Managing daylight saving time will be covered as part of the advanced features.

"},{"location":"prompts/07-faq/#learning-and-assessment","title":"Learning and Assessment","text":""},{"location":"prompts/07-faq/#how-will-i-be-graded","title":"How will I be graded?","text":"

Assessment typically includes: - Participation in class activities and discussions - Completion of hands-on labs and exercises - Quality and functionality of projects - Final project presentation and documentation

"},{"location":"prompts/07-faq/#what-happens-if-my-clock-doesnt-work","title":"What happens if my clock doesn't work?","text":"

Troubleshooting is a crucial part of the learning process. You'll have support from instructors and peers, and we'll learn systematic debugging approaches. Making mistakes and fixing them is how we learn!

"},{"location":"prompts/07-faq/#can-i-work-with-ai-tools-during-the-course","title":"Can I work with AI tools during the course?","text":"

Yes, part of the course includes learning how to effectively use AI tools for design and problem-solving. We'll cover best practices for AI collaboration while ensuring you understand the fundamental concepts.

"},{"location":"prompts/07-faq/#hardware-and-components","title":"Hardware and Components","text":""},{"location":"prompts/07-faq/#what-happens-if-i-accidentally-damage-a-component","title":"What happens if I accidentally damage a component?","text":"

Don't worry! Learning electronics involves some trial and error. Basic components are relatively inexpensive and replaceable. Just inform your instructor if something breaks.

"},{"location":"prompts/07-faq/#how-do-breadboards-work","title":"How do breadboards work?","text":"

A breadboard is a reusable platform for creating temporary electrical connections. We'll spend time early in the course understanding how they work and practicing proper connection techniques.

"},{"location":"prompts/07-faq/#whats-the-difference-between-digital-and-analog-inputsoutputs","title":"What's the difference between digital and analog inputs/outputs?","text":"

Digital signals are either on (1) or off (0), while analog signals can have many values in between. We'll use both types: digital for buttons and simple LEDs, analog for sensors and variable controls.

"},{"location":"prompts/07-faq/#programming-concepts","title":"Programming Concepts","text":""},{"location":"prompts/07-faq/#do-i-need-to-memorize-all-the-micropython-commands","title":"Do I need to memorize all the MicroPython commands?","text":"

No, you don't need to memorize everything. We'll provide reference materials, and part of learning is knowing how to find and use documentation effectively.

"},{"location":"prompts/07-faq/#what-if-i-miss-a-class-and-fall-behind","title":"What if I miss a class and fall behind?","text":"

Each concept builds on previous learning, but we'll provide catch-up resources and extra help sessions. Talk to your instructor as soon as possible if you fall behind.

"},{"location":"prompts/07-faq/#how-will-we-debug-our-code","title":"How will we debug our code?","text":"

We'll learn several debugging techniques: - Using print statements for troubleshooting - Reading error messages - Using the Thonny debugger - Systematic problem-solving approaches

"},{"location":"prompts/07-faq/#safety-and-best-practices","title":"Safety and Best Practices","text":""},{"location":"prompts/07-faq/#is-there-any-risk-of-electric-shock","title":"Is there any risk of electric shock?","text":"

The Pico W and components we use operate at very low voltages (3.3V or 5V) which are safe to handle. However, we'll still learn and follow proper safety procedures.

"},{"location":"prompts/07-faq/#how-do-i-protect-the-electronic-components","title":"How do I protect the electronic components?","text":"

We'll cover basic handling procedures including: - Static electricity precautions - Proper connection techniques - Component storage and handling - Avoiding common mistakes that could damage components

"},{"location":"prompts/07-faq/#advanced-topics-and-extensions","title":"Advanced Topics and Extensions","text":""},{"location":"prompts/07-faq/#can-we-add-features-like-alarms-or-stopwatch-functions","title":"Can we add features like alarms or stopwatch functions?","text":"

Yes! As you progress through the course, you'll learn to add various features including: - Multiple alarms - Stopwatch and timer functions - Custom sounds and alerts - Special display effects

"},{"location":"prompts/07-faq/#will-we-learn-about-power-management","title":"Will we learn about power management?","text":"

Yes, we'll cover power management including: - Battery operation - Sleep modes - Power-efficient programming - Battery life optimization

"},{"location":"prompts/07-faq/#future-applications","title":"Future Applications","text":""},{"location":"prompts/07-faq/#can-i-use-these-skills-for-other-projects","title":"Can I use these skills for other projects?","text":"

Absolutely! The skills you'll learn apply to many other electronics and programming projects: - Other types of electronic devices - Home automation projects - Sensor-based systems - IoT (Internet of Things) devices

"},{"location":"prompts/07-faq/#how-does-this-course-prepare-me-for-further-study","title":"How does this course prepare me for further study?","text":"

This course builds valuable skills for: - Computer science courses - Engineering programs - Electronics projects - General problem-solving - Understanding modern technology

"},{"location":"prompts/07-faq/#tools-and-resources","title":"Tools and Resources","text":""},{"location":"prompts/07-faq/#what-online-resources-can-we-use","title":"What online resources can we use?","text":"

We'll use various resources including: - MicroPython documentation - Raspberry Pi Pico documentation - Component datasheets - Online simulators - Programming reference guides

"},{"location":"prompts/07-faq/#will-we-use-version-control-like-git","title":"Will we use version control like Git?","text":"

While not required, we'll introduce basic version control concepts and how they can help manage your code as projects become more complex.

"},{"location":"prompts/07-faq/#collaboration-and-community","title":"Collaboration and Community","text":""},{"location":"prompts/07-faq/#can-we-work-in-teams","title":"Can we work in teams?","text":"

Yes! Many activities will involve pair programming or team collaboration. The final project can be individual or team-based, depending on complexity and scope.

"},{"location":"prompts/07-faq/#how-can-we-share-our-projects-with-others","title":"How can we share our projects with others?","text":"

We'll discuss ways to: - Document your work - Create project presentations - Share code responsibly - Collaborate with other students - Showcase your projects

"},{"location":"prompts/07-faq/#getting-help","title":"Getting Help","text":""},{"location":"prompts/07-faq/#what-if-i-get-stuck-on-a-problem","title":"What if I get stuck on a problem?","text":"

Multiple support options are available: - In-class help from instructors - Peer support - Office hours - Online resources - Lab assistance sessions

"},{"location":"prompts/07-faq/#can-i-get-extra-help-outside-of-class","title":"Can I get extra help outside of class?","text":"

Yes, instructors will have office hours and may offer additional lab time for students who want extra practice or help.

"},{"location":"prompts/07-faq/#course-materials","title":"Course Materials","text":""},{"location":"prompts/07-faq/#what-should-i-bring-to-class","title":"What should I bring to class?","text":"

Typically you'll need: - Notebook for taking notes - Computer (if not provided) - Any assigned reading materials - Your project components once distributed

"},{"location":"prompts/07-faq/#how-should-i-store-and-transport-my-projects","title":"How should I store and transport my projects?","text":"

We'll provide guidance on: - Component storage - Project organization - Safe transport methods - Protecting delicate components

"},{"location":"prompts/07-faq/#final-projects","title":"Final Projects","text":""},{"location":"prompts/07-faq/#what-makes-a-good-final-project","title":"What makes a good final project?","text":"

Strong final projects typically: - Solve a real problem or fulfill a genuine need - Demonstrate technical skills learned in the course - Show creativity and originality - Are well-documented - Function reliably

"},{"location":"prompts/07-faq/#can-i-continue-working-on-my-project-after-the-course-ends","title":"Can I continue working on my project after the course ends?","text":"

Yes! You'll have the knowledge to continue developing and improving your projects. We'll discuss ways to extend your learning and continue exploring electronics and programming.

"},{"location":"prompts/08-glossary/","title":"Generating a Glossary of Terms","text":""},{"location":"prompts/08-glossary/#prompt","title":"Prompt","text":"

Prompt

Prompt Name: Glossary for Clocks and Watches with MicroPython Book

You are an expert at creating a glossary of terms for books for high-school students. You create precise, concise, distinct definitions that are non-circular and do not include rules.

Your terms are written for the 9th grade reading level.

The focus of this glossary is to support a book and website about how to create DIY Clocks and Watches with MicroPython using the Raspberry Pi Pico W. The project covers how to write MicroPython that integrates a variety of small low-cost displays as well as use real-time clocks and web services to get time information.

For each term, return a term label in a level 4 markdown header. Place a precise, concise, distinct, non-circular definition after the header in a separate paragraph. Do not include the term in the definitions.

Go to the project area and use the file MicroPython_Clock_Concepts.csv for a complete list of concepts that need definitions.

If appropriate create an example of how that term might be\nused in the website.  Use **Example:** with no newline after it.\n\nReturn the entire glossary of terms in a single Markdown file\nfor all concepts in alphabetical order.\n\n## Project Instructions\n\n!!! prompt\n    When I ask you to create a definition for a ter, please create a glossary term\n    entry that complies with the ISO/IEC 11179 metadata registry\n    guidelines for a definition.  These definitions are precise, concise, distinct\n    definitions that are non-circular and do not include rules.  Do not include the\n    term name in the definition itself.  Your terms are written for the 9th grade reading level.\n\n    Return the term in a level 4 markdown header.\n    Return the definition in the body text.\n    If appropriate, give an example using the **Example:** format without a newline.\n
"},{"location":"prompts/09-oled-test/","title":"OLED Test Prompt","text":"

We can use generative AI tools to build test programs. But in order to get working programs, we need to tell the generative AI about what our hardware is and what our configuration is. We can leverage the config.py file to tell the generative AI tools about our local configuration.

"},{"location":"prompts/09-oled-test/#sample-prompt","title":"Sample Prompt","text":"

Prompt

You are an expert at helping high-school kids learn computational thinking using MicroPython. I am working on writing a test program written in MicroPython. I have a 128x64 OLED using ssd1306 display driver chip connected to a Raspberry Pi Pico W running MicroPython version 1.24.1 using an SPI bus. I have the ssd1306.py driver loaded into the /lib directory.

Please write a MicroPython program that prints \"Hello World!\" on the OLED display. Use the config.py program in the project to setup the display.

If your AI does not have a project feature, you can include the config.py in the prompt:

# MicroPython hardware configuration file for Raspberry Pi Pico W\n# Using an SPI OLED Display, a DS3231 RTC with EEPROM, and three buttons\n\nSPI_BUS = 0\nSPI_SCL_PIN = 2 # Clock\nSPI_SDA_PIN = 3 # labeled SDI(MOSI) on the back of the display\nSPI_RESET_PIN = 4 # Reset\nSPI_DC_PIN = 5 # Data/command\nSPI_CS_PIN = 6 # Chip Select\n\n# I2C Bus for the DS3231 RTC\nI2C_BUS = 0\nI2C_DT_PIN = 0 # Data\nI2C_SCK_PIN = 1 # Clock\nRTC_ADDRESS = 0x68\nEEPROM_ADDRESS = 0x57\n\n# OLED Screen Dimensions\nDISPLAY_WIDTH=128\nDISPLAY_HEIGHT=64\n\n# Button Pins where the buttons are between the GPIO and GND\n# Make sure to configure the pull-up resistor in the code\nBUTTON_MODE_PIN = 14\nBUTTON_INCREMENT_PIN = 15\nBUTTON_DECREMENT_PIN = 16\n
"},{"location":"setup/","title":"A Guide to Setting up Your Clocks and Watches Labs","text":"

This section is intended for parents, mentors, teachers and curriculum developers who are responsible for creating a great experience for your students. Within this audience we have typically seen two extremes.

  1. Value-based purchasers with limited budgets who are good at long-term planning. They pre-order parts form China on eBay and Alibaba in bulk and avoid high-cost shipping fees.

  2. Parents in high-disposable income households with limited time and large budgets. They just want to click on a few links and have the parts shipped overnight.

This Setup Guide attempts to provide information for both of these groups. Note that we have used generative AI to help you source the lowest costs parts. We encourage you to ask your generative AI chatbot about sourcing strategies that balance low-cost and fast delivery.

"},{"location":"setup/#minimal-setup","title":"Minimal Setup","text":"

The minimal setup consists of just four parts:

  1. A 1/2 size solderless breadboard - about $2
  2. 20cm jumper wires (male-to-female) - also known as Dupont connectors - about $1
  3. A Raspberry Pi Pico - about $4 or $5 with headers presoldered - you can get the \"W\" if you want to go wireless and use WiFi to sync the time
  4. A OLED display - which range from $5 to $20 depending on the size

If you are patient and clever you can purchase these parts in bulk and keep the kit price under $10 - although the OLED display will be hard to read from more than a few feet away.

Image Caption: An example of a 2.42\" OLED display connected to a 400-tie breadboard holding a Raspberry Pi W with two buttons for changing the display settings.

"},{"location":"setup/#sample-amazon-links","title":"Sample Amazon Links","text":"

Here are the Amazon Links for these parts:

  1. Half Size Solderless Breadboard Search on Amazon
  2. Sample 4-Pack for $6
  3. 20cm male-to-female Dupont Connectors $4 for 40 connectors
  4. Raspberry Pi Pico $8
  5. Amazon Keyword Search for 2.42\" OLED Display 128*64 SPI SSD1309
  6. Amazon Prime 2.42\" OLED Display for $14 in 4 Colors

Note that MicroCenter sells the Pico for $3.99. So you are paying about double on Amazon for some of these parts.

"},{"location":"setup/#sample-e-bay-links","title":"Sample E-Bay Links","text":"
  1. Half Size Solderless Breadboard Search on EBay
  2. 10X 400 Point Solderless Breadboard for $14
  3. 20cm male-to-female dupont connectors
  4. 10/20/30CM MM, MF, FF Dupont Wire Jumper Cable 40PIN Dupont Line Connector for $4
  5. 2.42\" OLED Display 128*64 SPI SSD1309
"},{"location":"setup/#other-components","title":"Other Components","text":""},{"location":"setup/#display-cable-harness","title":"Display Cable Harness","text":"

Display Cable Harness

"},{"location":"setup/#real-time-clocks","title":"Real Time Clocks","text":"

Technically you can get a clock or watch running without a real-time clock (RTC). The problem is that the clock will not be very accurate unless you continually sync the time using WiFi or with your host computer.

However, learning how to use the a RTC is a key learning concept and learning how to use the I2C serial interface is also a good concept to know. So it is optional but strongly encouraged.

"},{"location":"setup/#the-ds1307","title":"The DS1307","text":"

Although this board is old, it is a simple and low-cost part that is easy to use. Most of the development boards come with their own crystal and an I2C interface.

"},{"location":"setup/#the-ds3231","title":"The DS3231","text":"

The DS3231 is one of the most commonly used real-time clock (RTC) modules paired with microcontrollers like the Raspberry Pi Pico. It's popular because it:

  1. Has high accuracy (temperature-compensated crystal oscillator)
  2. Maintains accuracy over a wide temperature range
  3. Has built-in temperature compensation
  4. Uses the I2C interface, which is easy to implement
  5. Includes a battery backup option
  6. Is relatively inexpensive
  7. Has extensive library support across different platforms

The second most common is probably the DS1307, which is an older and simpler version. While less accurate than the DS3231, it's even less expensive and still perfectly suitable for many basic timekeeping applications.

For microcontrollers in particular, the DS3231 tends to be favored because its accuracy doesn't depend on the microcontroller's clock, and it maintains accurate time even when the main microcontroller is reset or loses power.

"},{"location":"setup/01-desktop/","title":"Desktop Setup","text":"

In this lesson, we will review the steps to get your PC or Mac setup to use the free open source Thonny Python Integrated Development Environment (IDE). We will assume you are using either the Raspberry Pi Pico or the Raspberry Pi Pico \"W\" for thee lessons.

"},{"location":"setup/01-desktop/#about-thonny","title":"About Thonny","text":"

Thonny is an ideal tool for writing MicroPython code for students just getting started. It is free, simple to use, and it runs on PCs running Windows, Macs and Linux systems such as the Raspberry Pi.

"},{"location":"setup/01-desktop/#step-1-install-thonny","title":"Step 1: Install Thonny","text":"

The installation of Thonny will vary depending on the type of computer you are using. For both Windows and Mac, you must first download an installer program. The links to these downloads are on the main Thonny page:

For all the versions of Linux, you can open the command shell and type in the correct installation command. For people using the Raspberry Pi operating system, Thonny may already be included in your software.

"},{"location":"setup/01-desktop/#thonny-versions","title":"Thonny Versions","text":"

These screen images were done with Thonny Version 4.0.4. We also tested the steps on Thonny version 4.1.2 and the seem to work fine.

"},{"location":"setup/01-desktop/#step-2-configure-the-micropython-firmware","title":"Step 2: Configure the MicroPython Firmware","text":"

After you first plug in a Raspberry Pi Pico, you will need to install the correct runtime library for the Pico. This just takes a minute and you will only have to do it once for each microcontroller.

Since version 4.0.0 Thonny can detect the microcontroller you are using and download the firmware.

"},{"location":"setup/01-desktop/#manually-downloading-the-firmware","title":"Manually Downloading the Firmware","text":"

There are occasions when the automatic firmware download in Thonny may not work. In this case, you will need to manually download the firmware from the MicroPython website.

Here are the links to the Pico Firmware (called RP2-pico):

Raspberry Pi Pico Firmware

"},{"location":"setup/01-desktop/#step-3-configure-interpreter","title":"Step 3: Configure Interpreter","text":"

Thonny is designed to automatically detect the correct device and configure the right interpreter. You can verify this by using the Thonny Tools -> Options menu and move to the Interperter tab.

Note that the MicroPython (Raspberry Pi Pico) is selected.

"},{"location":"setup/01-desktop/#thonny-firmware-list","title":"Thonny Firmware List","text":"

You can also get a list of all the different versions of firmware by going directly to the MicroPython site. Here is what that list will look like:

After you select a runtime version, Thonny will give you the details about this version.

After Thonny installs the new firmware on the Pico it instructs the Pico to \"restart\" using the new firmware. This makes the USB drive appear to be removed without a disconnect and results in an error. This is normal so don't worry.

"},{"location":"setup/01-desktop/#debugging-tips","title":"Debugging Tips","text":"

When debugging your Thonny installation, it is important to understand the steps that happen when you plug your Raspberry Pi Pico into the computer.

Step 1: Plugging the USB Device The Pico appears as a USB device on your computer when you plug it in. The operating system should send a request to the USB device asking it for its ID.

Step 2: Probing for the USB ID The Pico will respond with a device ID number. This number reflects two items: the company that made the device (Vendor ID) and the device product type (Product ID). If you are using a power-only USB cable this number will never get to the operating system. This means that the data wires are not present in your USB cable or one of the wires is broken or not connecting.

Step 3: Looking Up the USB Driver The operating system will take this USB ID and then look-up the right software and then run that software. In UNIX and Mac systems this will amount to making the USB device appear in the /dev/cu* area. On Windows it will be a COM port.

ls -l /dev/cu.usb*\n\ncrw-rw-rw-  1 root  wheel  0x9000001 Dec 25 08:41 /dev/cu.usbmodem101\n

Note that the exact port name might change. The example above is cu.usbmodem101.

If this device does not immediately appear, then you should double-check your cable and make sure the operating system registers the USB device.

If you know your cable works fine by testing it on other devices then you may have to reboot your computer.

"},{"location":"setup/01-desktop/#setting-usb-permissions-on-macos","title":"Setting USB Permissions on MacOS","text":"

In many classrooms with old Macs, the new security settings for USB connections can get in the way of a good experience for our students. Since these Macs don't leave our classrooms, we have decided to permanently allow USB access for your Raspberry Pi Pico on MacOS Sonoma. Here are the steps we use.

  1. On the Mac, go to System Settings > Privacy & Security
  2. Scroll to Security
  3. Find \"Allow accessories to connect\" and select \"always\"
"},{"location":"setup/01-desktop/#debugging-your-usb-connection","title":"Debugging your USB Connection","text":"

Our students have reported many frustrating experiences getting their Raspberry Pi Pico to connect to their MacOS system. Most of these problems can be quickly solved by checking the security settings on your MacOS.

After you plug in the USB it should be listed in the /dev/cu* area:

ls /dev/cu*\n

A typical response includes this device:

/dev/cu.usbmodem1101\n

If the usbmodem11001 or similar is not listed then THonny will not connect. This is the time to check your cables and connectors. Older USB cables frequently have broken wires.

"},{"location":"setup/01-desktop/#getting-usb-diagnostic-information-on-the-macos","title":"Getting USB Diagnostic Information on the MacOS","text":"

You can also get information about how much current will be provided to the USB using the following shell commnad:

You can get the full information on your device using this command:

system_profiler SPUSBDataType | grep -B 6 -A 5 \"MicroPython\"\n

This command prints the 6 lines before and five lines after the string \"MicroPython\" appears in the output of the system_profiler command.

  Product ID: 0x0005\n  Vendor ID: 0x2e8a\n  Version: 1.00\n  Serial Number: e66178c1276d562d\n  Speed: Up to 12 Mb/s\n  Manufacturer: MicroPython\n  Location ID: 0x01100000 / 1\n  Current Available (mA): 500\n  Current Required (mA): 250\n  Extra Operating Current (mA): 0\n
"},{"location":"setup/01-desktop/#using-the-macos-io-registry-command","title":"Using the MacOS I/O Registry Command","text":"

The MacOS shell command ioreg can also be useful for monitoring USB device status. Here is a command that lists the information of all USB devices. The I/O Registry is a hierarchical database of all the devices and drivers on your Mac. The ioreg shell command is powerful utility to examine system hardware and device information.

ioreg -p IOUSB -w0 -l\n

Here are the parameters: - ioreg:

-   The command-line tool used to display the I/O Registry, a hierarchical database of all the devices and drivers on your Mac. It's a powerful utility to examine system hardware and device information.\n

+-o Board in FS mode@01100000

{\n  \"sessionID\" = 37899561861259\n  \"USBSpeed\" = 1\n  \"idProduct\" = 5\n  \"iManufacturer\" = 1\n  \"bDeviceClass\" = 239\n  \"IOPowerManagement\" = {\"PowerOverrideOn\"=Yes,\"DevicePowerState\"=2,\"CurrentPowerState\"=2,\"CapabilityFlags\"=32768,\"MaxPowerState\"=2,\"DriverPowerState\"=0}\n  \"bcdDevice\" = 256\n  \"bMaxPacketSize0\" = 64\n  \"iProduct\" = 2\n  \"iSerialNumber\" = 3\n  \"bNumConfigurations\" = 1\n  \"UsbDeviceSignature\" = <8a2e0500000165363631373863313237366435363264ef02010202000a0000>\n  \"USB Product Name\" = \"Board in FS mode\"\n  \"locationID\" = 17825792\n  \"bDeviceSubClass\" = 2\n  \"bcdUSB\" = 512\n  \"kUSBSerialNumberString\" = \"e66178c1276d562d\"\n  \"USB Address\" = 1\n  \"IOCFPlugInTypes\" = {\"9dc7b780-9ec0-11d4-a54f-000a27052861\"=\"IOUSBHostFamily.kext/Contents/PlugIns/IOUSBLib.bundle\"}\n  \"kUSBCurrentConfiguration\" = 1\n  \"bDeviceProtocol\" = 1\n  \"USBPortType\" = 0\n  \"IOServiceDEXTEntitlements\" = ((\"com.apple.developer.driverkit.transport.usb\"))\n  \"USB Vendor Name\" = \"MicroPython\"\n  \"Device Speed\" = 1\n  \"idVendor\" = 11914\n  \"kUSBProductString\" = \"Board in FS mode\"\n  \"USB Serial Number\" = \"e66178c1276d562d\"\n  \"IOGeneralInterest\" = \"IOCommand is not serializable\"\n  \"kUSBAddress\" = 1\n  \"kUSBVendorString\" = \"MicroPython\"\n}\n
"},{"location":"setup/01-desktop/#automatic-power-draw-shutoff","title":"Automatic Power Draw Shutoff","text":"

If your USB port is drawing too much power, then many computers will disable the port. This can happen if you are trying to display too many LEDs or a motor. You will see a message such as \"USB Port Disabled\".

Your computer manufacturer will tell you what the maximum current each USB port allows. This is typically about 1 amp, but it varies based on your computer and what other devices are connected to your computer.

The best way to debug this is to purchase a low-cost USB current monitor and monitor how much current your project is using. If you are using an LED-strip then make sure you test the current with all the pixels fully on (255,255,255).

USB Current Monitor Search on eBay

In general, each NeoPixel at full brightness will draw up to 20 milliamps and the Pico will also draw about 20 milliamps.

For projects that require more than about 500 milliamps, it is strongly recommended that you use an externally powered USB hub.

"},{"location":"setup/01-desktop/#debugging-the-usb-port","title":"Debugging the USB Port","text":""},{"location":"setup/01-desktop/#mac-system-profiler","title":"Mac System Profiler","text":"

On MacOS we can use the system_profiler command:

system_profiler SPUSBDataType\n\nUSB:\n\n    USB 3.1 Bus:\n\n      Host Controller Driver: AppleT8112USBXHCI\n\n    USB 3.1 Bus:\n\n      Host Controller Driver: AppleT8112USBXHCI\n\n        Board in FS mode:\n\n          Product ID: 0x0005\n          Vendor ID: 0x2e8a\n          Version: 1.00\n          Serial Number: e66141040396962a\n          Speed: Up to 12 Mb/s\n          Manufacturer: MicroPython\n          Location ID: 0x00100000 / 1\n          Current Available (mA): 500\n          Current Required (mA): 250\n          Extra Operating Current (mA): 0\n

Note that you can see both the Product ID, Vendor ID Manufacturer (MicroPython) and the mode (File System mode) that the device was connected. The current available and current required are also listed, although these numbers might be somewhat conservative. They are used for the estimation of current only.

"},{"location":"setup/01-desktop/#checking-out-the-clocks-and-watches-code-from-github","title":"Checking out the Clocks and Watches Code from GitHub","text":"

You don't need to copy and paste the code samples one-by-one into your local file system. GitHub allows you to download all the code on the Moving Rainbow site by running the following command:

# create a projects area to checkout your code\nmkdir ~/projects\ncd projects\n# make a copy of the repo on your local hard drive\ngit clone https://github.com/dmccreary/micropython-clocks-and-watches\ncd micropython-clocks-and-watches\n# open the files in your file manager\nopen .\n

You can now go directly to the source for the Raspberry Pi P OLED lab:

cd src/pico-oled\n

Once you have done this step you can make the top Files region of Thonny point to the source area and then click the upper right corner that has the \"Upload to /\" menu. Make sure the bottom area is set to the root, not the /lib area.

The following video shows these steps:

"},{"location":"setup/01-desktop/#understanding-thonny-and-file-systems","title":"Understanding Thonny and File Systems","text":"

To understand how Thonny works, we want to have a clear visual picture of the two parallel file systems that Thonny uses.

  1. Thonny is launched from your desktop or laptop computer. The Files window at the top will always be open to that file system.
  2. Thonny also displays the file system on the microcontroller on the bottom window. It displays this window after you successfully connect through the correct port.

You can run a program in Thonny from either file system. But there is one strict rule. If you don't save your code on your personal GitHub site and verify that the code is there you may not have saved your code.

"},{"location":"setup/02-purchasing-parts/","title":"Strategy for Parts Purchasing Guide","text":"

We have seen many well intended parent come into our workshops telling stories about a $400 robot kit that was used for an hour and then sat in the back of a closet. Before you go out and purchase an expensive STEM kit, we suggest you start small and see if your student will really use the kit.

"},{"location":"setup/02-purchasing-parts/#breadboards","title":"Breadboards","text":"

We strongly suggest purchasing breadboards in bulk at least 10 at a time. We use eBay for purchasing all our breadboards.

Many of our projects use the 1/2 size 400-tie breadboards. This is usually sufficient for simple projects.

"},{"location":"setup/02-purchasing-parts/#microcontrollers","title":"MicroControllers","text":"

Because all or examples run MicroPython, your microcontroller will also need to run MicroPython. Unfortunately, older microcontrollers like the Arduino Uno only come with 2K of RAM. Since we need at least 16K of RAM to run MicroPython, the older Arduino systems will not work.

Therefore we strongly suggest you go with a newer microcontroller like the Raspberry Pi Pico which typically sells for under $4.

"},{"location":"setup/02-purchasing-parts/#purchasing-the-raspberry-pi-pico","title":"Purchasing the Raspberry Pi Pico","text":"

We love MicroCenter because they sell the Raspberry Pi Pico for only $3.99. If there is a MicroCenter near your home, we strongly suggest purchasing the parts there.

"},{"location":"setup/02-purchasing-parts/#raspberry-pi-pico-w","title":"Raspberry Pi Pico W","text":"

If you are going wireless, you will need to pay an extra dollar to get the Raspberry Pi Pico for $5.99

If the Raspberry Pi Picos are out of stock, a backup plan might be an ESP-32 microcontroller. There are two challenges you might face with the ESP-32:

  1. The development boards at MicroCenter, SparkFun and Adafruit are 4x more expensive
  2. There is a huge variety of these boards from many different manufactures. So the instructions you get on each website may not match the device you purchase.
"},{"location":"setup/02-purchasing-parts/#displays","title":"Displays","text":""},{"location":"setup/02-purchasing-parts/#oled-displays","title":"OLED Displays","text":"

Although the small 1\" OLED displays work, they are hard to read from a distance. We like the 2.42\" inch OLED displays since they are bright and have a wide viewing angle.

"},{"location":"setup/02-purchasing-parts/#smartwatch-displays","title":"SmartWatch Displays","text":"

Waveshare RP2040-LCD-1.28

Ebay Listing for $21

"},{"location":"setup/02-purchasing-parts/#led-strips","title":"LED Strips","text":""},{"location":"setup/02-purchasing-parts/#smartwatch-displays_1","title":"Smartwatch Displays","text":""},{"location":"setup/02-purchasing-parts/#real-time-clocks","title":"Real-Time Clocks","text":"

Learning how to use a real-time clock (RTC) is a core part of building digital clocks. So almost all our kits include an RTC. Here is a description of the two main options for RTCs.

"},{"location":"setup/02-purchasing-parts/#the-ds1307","title":"The DS1307","text":"

Although the DS1307 is has been around for a long time, it is still a simple low-cost part that is easy to use. The DS1307 is still perfectly suitable for many basic timekeeping applications and for learning how to use a Most of the development boards come with their own crystal and an I2C interface. Most of our clock kits have now been upgraded to the newer more accurate DS3231 which we can also purchase for under $1.

"},{"location":"setup/02-purchasing-parts/#the-ds3231","title":"The DS3231","text":"

The DS3231 is one of the most commonly used real-time clock (RTC) modules paired with microcontrollers like the Raspberry Pi Pico. It's popular because it:

  1. Has high accuracy (temperature-compensated crystal oscillator) +/- 2 seconds per month
  2. Maintains accuracy over a wide temperature range suitable for indoor and outdoor use
  3. Uses the I2C interface, which is easy to implement
  4. Includes a 3V lithium coin-cell battery backup option which allows it to remember the time and alarm settings even when the power is off
  5. Is relatively inexpensive (under $1 each)
  6. Has extensive library support across different platforms
  7. You can also use it to display the temperature
  8. Includes the ability to store 4K in EEPROM for information such as when alarms and timers should go off

For microcontrollers in particular, the DS3231 tends to be favored because its accuracy doesn't depend on the microcontroller's clock, and it maintains accurate time even when the main microcontroller is reset or loses power.

Here is an example of a DS3231 listing on eBay for under $1:

AliExpress currently has the DS3231 boards listed for $0.73 each.

"},{"location":"setup/03-display-cable-harness/","title":"Display Cable Harness","text":"

Unlike simple sensors that only have a few wires, displays have up to seven wires that need to be connected. This can be tricky when we use breadboards where we can accidentally pull one wire out.

To keep our displays running reliably, we can use a 20 cm ribbon cable and some hot glue to make a connector that is easy to hook up and be much more reliable.

We start by purchasing some 20cm long Male-Female Dupont ribbon connectors from eBay. The price should be about $8 for 120 connectors. Make sure to get the Male-Female version.

We then separate out seven of these wires trying to use the black and red colors in the GND and VCC edge of the group of wires.

You can see a close-up of each of the colors and their connections in the picture below.

At the other end of the cable, we need to make a small change in the order of the cable. Here are the changes:

  1. We separate the red wire from the rest of the group and connect the red to the 3.3V regulated output of the Raspberry Pi Pico. All our displays run on 3.3v now.
  2. We move the back GND wire to be in between the blue and purple CS and DC wires. This allows the row of all the wires to be connected in a single block of wires.

We can then plug this group of wires directly into the breadboard from breadboard rows 3 to 9. This is shown below. Make sure the black GND is on the Pico GND on row 8 of the breadboard.

We designed these connections with the following rules:

  1. The Clock (SCL) and Data (SDA) MUST be connected to rows 4 and 5 respectively because this is where SPI0 CLK and SPI0 TX are located.
  2. The other three signals RES, DC and CS can be on pin so we will use the rows that make the cable connectors direct to rows 6, 7 and 9. Note that GND is on breadboard row 8 which I mark with a black marker.

We have found that once we create these cable assemblies with hot glue to keep the pins in the right order it makes it much easier to connect the displays.

Warning

Note that we still MUST make sure that the black wire in the wiring harness is connected to the GND. It is easy to get the cable reversed so make sure to double-check the cable orientation before you use power on the board.

For younger students, don't be shy about providing a few color-coded hints on the breadboard to guide their assembly process.

"},{"location":"setup/04-led-clocks/","title":"LED Clocks","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. One challenge is can you use simple LEDs to create your own clock? This is where creativity comes in.

Much of the creativity in this course relies on displaying time information in creative ways using both simple LEDs as well as using LED strips also known as NeoPixel strips.

There are tradeoffs for each of these approaches. There is no single answer and each student will have different curiosity.

"},{"location":"setup/04-led-clocks/#basic-custom-led-circuit-approach","title":"Basic Custom LED Circuit Approach","text":"This LED art-clock used individual RGB LEDs. Each RGB LED required four independent soldering connections.

Using individual LEDs requires more wiring and the need to understand how to use current limiting resistors. This approach promotes lower-level understanding of concepts such as current, voltage drop, resistors, current measuring and soldering. Students can place the LEDs in any arrangement on foam-core board, plexiglass or wood.

Remember that a digit requires seven segments with each segment requiring up to 3 LEDs. For a four digit clock that means you will need around 70 LEDs.

"},{"location":"setup/04-led-clocks/#neopixel-approach","title":"NeoPixel Approach","text":"This 12-element NeoPixel ring is perfect for a clock face. Different colors can be used to display the hours, minutes and seconds.

NeoPixels are connivent since you can wire an entire clock display including the seven-segments for four digits, the flashing colon and the AM/PM indicator with a single LED strip or a string of addressable LED lights. The focus will be on programming these strips to display the correct time.

You can get NeoPixels in strips (the most common) but also in strings that can be easily moved into the pattern of an 7-segment display.

The NeoPixel string allows you to easily place the pixels in the shape of a 7-segment digit.

For more examples of using NeoPixels with MicroPython, please refer to the Moving Rainbow website. This site has detailed tutorials appropriate for begging Python students.

"},{"location":"setup/04-led-clocks/#references","title":"References","text":""},{"location":"setup/05-ai-in-the-classroom/","title":"AI in the Classroom","text":""},{"location":"setup/05-ai-in-the-classroom/#sample-prompts","title":"Sample Prompts","text":"

This course provides many sample prompts you can use to give generative AI systems to explain code, generate sample code, generate tests and modify our existing code to add new features.

Teachers can also use these prompts to create lesson plans and learning artifacts such as MicroSims to complement these resources.

"},{"location":"setup/05-ai-in-the-classroom/#leveraging-projects","title":"Leveraging Projects","text":"

We also encourage you to load the kit source code into a project area that your generative AI tools can access. Both Anthropic's Claude.ai and OpenAI ChatGPT. This allows you to do things like reference a sample program and say just like example 07-clock-face.py but with red digits.

"},{"location":"setup/05-button-print-test/","title":"Button Print Test","text":"

In this lab we will be testing if the three buttons have been wired up correctly. When we press any of the three buttons we will see the event printed on the Thonny console.

By printing to the console this lab is not dependant on the display being connected. In the next lab we will also modify the lab to display the button state on the display.

"},{"location":"setup/05-button-print-test/#using-interrupt-request-functions","title":"Using Interrupt Request Functions","text":"

To print what button is being pressed, we will write three small python functions. We then will then have these functions run when the corresponding button is presssed by running the irq function.

"},{"location":"setup/05-button-print-test/#sample-code","title":"Sample Code","text":""},{"location":"setup/06-pico-w-setup/","title":"Pico W Setup","text":"

When installing the Pico \"W\" (Wireless), you will need to download a different version of the MicroPython runtime.

The list of runtime images is on the https://micropython.org/download/RPI_PICO_W page.

In this example, we are using v1.24.1 (2024-11-29) .uf2.

Here is the image from Thonny that shows what version you are using. Note that you MUST select BOTSEL button before Thonny will download the new MicroPython runtime.

"},{"location":"setup/07-sound/","title":"Adding Sound to Your Clock","text":"

Many students want to add alarm features to their clock projects. The Raspberry Pi Pico can generate tones using Pulse Width Modulation (PWM) in MicroPython. Here are the key options for audio output devices:

"},{"location":"setup/07-sound/#piezo-ceramic-disc","title":"Piezo Ceramic Disc","text":"

A thin ceramic disc that converts electrical signals into sound through mechanical vibration.

Pros: - Extremely low cost (\\(0.50-\\)2 each) - Very simple to connect (just 2 wires) - No additional components needed - Durable and reliable - Perfect for basic beeps and alarms - Uses minimal power

Cons: - Limited sound quality - Can only produce simple tones - Relatively quiet - Not suitable for music playback - Can be harsh-sounding at high volumes

Typical Educational Lab Cost: $10-15 for a pack of 10 discs

"},{"location":"setup/07-sound/#piezo-speakers","title":"Piezo Speakers","text":"

Small enclosed speakers with a built-in resonant chamber.

Pros: - Low cost ($2-5 each) - Better sound quality than bare ceramic discs - Built-in enclosure improves volume - Simple 2-wire connection - Good for alarms and basic melodies - More pleasant sound than bare discs

Cons: - Still limited audio quality - Not suitable for music playback - Slightly more expensive than bare discs - Fixed resonant frequency can affect tone quality

Typical Educational Lab Cost: $20-30 for a pack of 10 speakers

"},{"location":"setup/07-sound/#standard-speakers","title":"Standard Speakers","text":"

Traditional cone speakers with magnets and voice coils.

Pros: - Much better sound quality - Can play actual music - Wide frequency response - Available in various sizes - Good volume levels

Cons: - More expensive ($4-15 each) - Requires amplification circuit - More complex wiring - Higher power requirements - Takes up more space - More fragile than piezos

Typical Educational Lab Cost: $20-60 for a set of 5 small speakers

"},{"location":"setup/07-sound/#digital-to-analog-converters-dac","title":"Digital to Analog Converters (DAC)","text":"

Chips that convert digital signals to analog audio.

Pros: - High-quality audio output - Can play music and complex sounds - Professional audio capabilities - Clean signal output - Multiple channels possible

Cons: - Most expensive option ($2-5 per chip) - Requires additional components - Complex wiring and setup - Needs amplification - Requires more advanced programming - May need level shifting circuits

Typical Educational Lab Cost: $20-50 for a complete setup with 5 DAC modules

"},{"location":"setup/07-sound/#microsd-readers","title":"MicroSD Readers","text":"

Card readers for storing and playing audio files.

Pros: - Can store many sound files - Allows for custom sounds - Easy to update sounds - Works well with DACs - Flexible storage options

Cons: - Additional cost ($2-5 per reader) - Requires SD card library - More complex programming - Needs file system management - Additional power requirements - Must handle card formatting

Typical Educational Lab Cost: $30-40 for a set of 5 readers plus cards

Recommendation for School Labs: For a basic classroom set serving 20 students:

Total recommended budget: ~$120 for a well-rounded audio lab setup that allows students to experiment with different sound options while keeping the basic piezo components affordable for everyone.

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

Learning Graph

Binary Clock

"},{"location":"sims/4-digits/","title":"Example of a Four-Digit Digital Clock Face","text":""},{"location":"sims/analog-clock/","title":"Analog Clock","text":"Analog Clock

Run Analog Clock MicroSim

"},{"location":"sims/analog-clock/#prompt","title":"Prompt","text":"
Create a single p5.js sketch file that uses a 400x400 canvas.\nThe file is a simulation of an analog clock face.\nThe clock face has a black background.\nThe clock has white hands for the minute and hour hand.\nThe clock as a red second hand.\nThere are two modes: manual and automtic\nManual mode allows the use to manually set the time using three sliders for hours, minute and seconds.\nAutomatic mode use the local time to update the display.\nAdd labels and values for the sliders.\nMake sure to reenable the controls as we switch back to manual mode.\n
"},{"location":"sims/analog-clock/#code","title":"Code","text":"

This is a wonderful lab to demonstrate some use of the trigonomic functions sine() and cosine(). We ask the question:

Write some Python code that will take in the seconds as a number from 0 to 59 and it will return the x and y positions of the tip of the second hand.

// Draw second hand\n// convert seconds to radians\nsecondHand = map(sc, 0, 60, 0, TWO_PI) - HALF_PI;\n// draw a line from the center of the canvas to the endpoint\nline(0, 0, cos(secondHand) * canvasSize / 2.5, \n           sin(secondHand) * canvasSize / 2.5);\n
"},{"location":"sims/analog-clock/#full-program-source","title":"FUll Program Source","text":"
let canvasSize = 400;\nlet hourHand, minuteHand, secondHand;\nlet hourSlider, minuteSlider, secondSlider;\nlet manualMode = true;\n\nfunction setup() {\n  const canvas = createCanvas(400, 400);\n  // use this line in the web\n  canvas.parent('canvas-container');\n  // use this in editor\n  // createCanvas(canvasSize, canvasSize);\n  background(0);\n\n  // Create sliders for manual mode\n  hourSlider = createSlider(0, 11, 0);\n  hourSlider.position(10, canvasSize + 10);\n\n  minuteSlider = createSlider(0, 59, 0);\n  minuteSlider.position(10, canvasSize + 40);\n\n  secondSlider = createSlider(0, 59, 0);\n  secondSlider.position(10, canvasSize + 70);\n\n  let modeButton = createButton('Switch Mode');\n  modeButton.position(10, canvasSize + 100);\n  modeButton.mousePressed(switchMode);\n}\n\nfunction draw() {\n  background(0);\n  translate(canvasSize / 2, canvasSize / 2);\n\n  let hr, mn, sc;\n  if (manualMode) {\n    hr = hourSlider.value();\n    mn = minuteSlider.value();\n    sc = secondSlider.value();\n  } else {\n    let now = new Date();\n    hr = now.getHours() % 12;\n    mn = now.getMinutes();\n    sc = now.getSeconds();\n  }\n\n  // Draw hour hand\n  stroke(255);\n  strokeWeight(10);\n  hourHand = map(hr, 0, 12, 0, TWO_PI) - HALF_PI;\n  line(0, 0, cos(hourHand) * canvasSize / 4, sin(hourHand) * canvasSize / 4);\n\n  // Draw minute hand\n  strokeWeight(8);\n  minuteHand = map(mn, 0, 60, 0, TWO_PI) - HALF_PI;\n  line(0, 0, cos(minuteHand) * canvasSize / 3, sin(minuteHand) * canvasSize / 3);\n\n  // Draw second hand\n  stroke(255, 0, 0);\n  strokeWeight(4);\n  secondHand = map(sc, 0, 60, 0, TWO_PI) - HALF_PI;\n  line(0, 0, cos(secondHand) * canvasSize / 2.5, sin(secondHand) * canvasSize / 2.5);\n}\n\nfunction switchMode() {\n  manualMode = !manualMode;\n\n  // disable the controls\n  if manualMode {\n    hourSlider.attribute('disabled', !manualMode);\n    minuteSlider.attribute('disabled', !manualMode);\n    secondSlider.attribute('disabled', !manualMode);\n  } else {\n    hourSlider.attribute('enabled', !manualMode);\n    minuteSlider.attribute('enabled', !manualMode);\n    secondSlider.attribute('enabled', !manualMode);\n  }\n\n}\n
"},{"location":"sims/battery-drain/","title":"Battery Drain","text":"Battery Drain

Run the Battery Drain Demo

Edit the Simulation

"},{"location":"sims/battery-drain/#about-the-battery-drain-microsim","title":"About The Battery Drain MicroSim","text":"

This MicroSim teaches students how batteries work in robots. The student can adjust the speed of the motor. But the more power the motor draws, the faster the battery drains. When the motor is off, there is no drain. When the battery is fully drained the motor will no longer turn.

"},{"location":"sims/battery-drain/#sample-prompt","title":"Sample Prompt","text":"
Generate a p5.js file on a 400x400 canvas that demonstrates the\nrate that a battery is discharged when it is powering a motor.\nAdd a slider at the bottom of the canvas that allows the user\nto adjust the speed of the motor.  Place the battery on the\nleft side.  Make the negative sign be at the bottom and \nuse a black filled rect.  Make the battery top be positive\nand use a gold filled rect to draw it.  Draw wires from\nthe battery's positive and negative to a motor in the right side.\nThe motor should drive a circle that spins faster as the slider is changed.\nThe motor should only spin if we have power.\n
"},{"location":"sims/battery-drain/#sample-code","title":"Sample Code","text":"
// battery drain\n// Animate a motor that drains a battery faster the more power is used\nlet canvasWidth = 400;\nlet drawHeight = 400;\nlet canvasHeight = 430;\nlet motorSpeedSlider;\nlet motorSpeed;\nlet batteryLevel = 100;\nlet dischargeRate;\nlet sliderLeftMargin = 100;\n\nfunction setup() {\n  const canvas = createCanvas(canvasWidth, canvasHeight);\n  //\n  textSize(16);\n\n  motorSpeedSlider = createSlider(0, 100, 50);\n  motorSpeedSlider.position(sliderLeftMargin, drawHeight + 10);\n  motorSpeedSlider.style('width', canvasWidth - sliderLeftMargin - 20 + 'px')\n\n  frameRate(60);\n}\n\nfunction draw() {\n  fill(245);\n  rect(0,0,canvasWidth,drawHeight);\n  fill('white');\n  rect(0,drawHeight,canvasWidth,canvasHeight-drawHeight);\n\n  motorSpeed = motorSpeedSlider.value();\n  // Battery representation\n  drawBattery();\n\n  // Wires from battery to motor\n  stroke(0);\n  // top wire\n  line(50, 50, 350, 50);\n  // bottom wire\n  line(50, 350, 350, 350);\n  line(350, 50, 350, 350);\n\n  // Motor and spinning circle\n  drawMotor();\n\n  // Update battery discharge\n  updateBattery();\n  noStroke();\n  fill('black');\n  text(\"Speed:\"+motorSpeed, 10, drawHeight+25);\n}\n\nfunction drawBattery() {\n  // Battery body\n\n  percentGold = .4\n  // Positive terminal\n  fill('gold'); // Gold color\n  rect(20, 50, 50, 300*percentGold);\n\n  // Negative terminal\n  fill('black');\n  rect(20, 350*percentGold, 50, 350*(1-percentGold));\n\n  // Battery level\n  let levelHeight = map(batteryLevel, 0, 100, 0, 300);\n  fill(0, 255, 0);\n  rect(30, 350 - levelHeight, 30, levelHeight);\n}\n\n// draw a spinning motor if we have power\nfunction drawMotor() {\n  fill(150);\n  ellipse(350, 200, 100, 100); // Motor body\n\n  // draw the spinner if we have power\n  if (batteryLevel > 1) {\n   push();\n      translate(350, 200);\n      rotate(frameCount / 50 * motorSpeed);\n      fill(0, 0, 255);\n      ellipse(0, 40, 20, 20); // Spinning circle\n   pop();\n  }\n}\n\n// the battery level goes down with time\nfunction updateBattery() {\n  dischargeRate = motorSpeedSlider.value() / 1000;\n  batteryLevel -= dischargeRate;\n  batteryLevel = max(batteryLevel, 0);\n}\n

Challenges

  1. Add a label that shows the percent of charge remaining
  2. Add a label that predicts how long the battery will last at the current discharge rate
  3. Add another button called \"Recharge\" that will slowly recharge the battery
  4. Add animation to the wires of red dots moving along the wires. Make the dots go faster at a higher power.
  5. Make the battery explode if you charge it too quickly
"},{"location":"sims/battery-drain/#lesson-plans","title":"Lesson Plans","text":""},{"location":"sims/battery-drain/#5th-grade-robotics-class-lesson-plan","title":"5th Grade Robotics Class Lesson Plan","text":""},{"location":"sims/battery-drain/#objective","title":"Objective","text":""},{"location":"sims/battery-drain/#duration","title":"Duration","text":""},{"location":"sims/battery-drain/#materials","title":"Materials","text":""},{"location":"sims/battery-drain/#introduction-10-minutes","title":"Introduction (10 minutes)","text":""},{"location":"sims/battery-drain/#demonstration-of-the-simulation-10-minutes","title":"Demonstration of the Simulation (10 minutes)","text":""},{"location":"sims/battery-drain/#group-activity-experimenting-with-the-simulation-20-minutes","title":"Group Activity: Experimenting with the Simulation (20 minutes)","text":""},{"location":"sims/battery-drain/#discussion-and-reflection-15-minutes","title":"Discussion and Reflection (15 minutes)","text":""},{"location":"sims/battery-drain/#conclusion-5-minutes","title":"Conclusion (5 minutes)","text":""},{"location":"sims/battery-drain/#assessment","title":"Assessment","text":""},{"location":"sims/battery-drain/#follow-up","title":"Follow-Up","text":""},{"location":"sims/binary-clock/","title":"Binary Clock","text":"

Run the Binary Clock Vertical Column MicroSim

Run the Vertical Column Binary Clock MicroSim Edit the Vertical Column Binary Clock MicroSim

"},{"location":"sims/binary-clock/#using-an-iframe","title":"Using an IFrame","text":"
<iframe src=\"./binary-clock-vertical.html\" width=\"420\" height=\"295\" scrolling=\"no\"\n  style=\"overflow: hidden;\"></iframe>\n
\n
"},{"location":"sims/binary-clock/#horizontal-binary-clock-microsim","title":"Horizontal Binary Clock MicroSim","text":"

Edit the Horizontal Microsim

"},{"location":"sims/binary-number/","title":"Binary Number","text":"

A binary number is one where each of the digits can only be a 0 or a 1. The values will double with each position. So the left-most digit is 0 or 1, the second bit is 0 or 2, the third is 0 or 4 and the fourth is 0 or 8. You can generate any number from 0 to 15 by adding the values.

"},{"location":"sims/binary-number/#prompt","title":"Prompt","text":"
Create a simulation of a 4-bit binary number.\nAdd buttons that toggle each of the four bits. \nAdd a text output that shows the decimal equivalent of the binary number.\n
"},{"location":"sims/binary-number/#sample-response","title":"Sample Response","text":"
// Global canvas dimensions\nlet canvasWidth = 330;\nlet canvasHeight = 120;\n\n// Binary bits and decimal value\nlet bits = [0, 0, 0, 0];\nlet decimalValue = 0;\n\nfunction setup() {\n  createCanvas(canvasWidth, canvasHeight);\n  textSize(24);\n  background(245);\n\n  // Create toggle buttons for each bit with correct labeling\n  for (let i = 0; i < 4; i++) {\n    let btn = createButton('Bit ' + i);\n    btn.position(20 + (3 - i) * 80, 50); // Placing buttons with Bit 3 on the left and Bit 0 on the right\n    btn.mousePressed(() => toggleBit(i));\n  }\n}\n\nfunction draw() {\n  // Clear the previous frame\n  clear();\n  background(245);\n\n  // Draw binary bits\n  for (let i = 0; i < bits.length; i++) {\n    text(bits[i], 40 + (3 - i) * 80, 30); // Displaying bits with Bit 3 on the left and Bit 0 on the right\n  }\n\n  // Calculate and draw the decimal value\n  decimalValue = binaryToDecimal(bits);\n  text('Decimal: ' + decimalValue, 20, 110);\n}\n\n// Toggle bit value\nfunction toggleBit(index) {\n  bits[index] = bits[index] === 0 ? 1 : 0;\n}\n\n// Convert binary array to decimal\nfunction binaryToDecimal(binaryArray) {\n  let decimal = 0;\n  for (let i = 0; i < binaryArray.length; i++) {\n    decimal += binaryArray[i] * Math.pow(2, i);\n  }\n  return decimal;\n}\n
"},{"location":"sims/binary-number/#learnings-from-the-response","title":"Learnings from The Response","text":"
  1. How to set up an array of buttons each with different actions
  2. How to use the pow(2, i) to convert a binary to decimal value
  3. How to setup a tobbleBit(index) function using the ? and : syntax
"},{"location":"sims/binary-number/#lesson-plan","title":"Lesson Plan","text":"

Objective:

Students will understand the binary number system and how it is used in computer science. They will learn to convert binary numbers to decimal numbers and vice versa.

Grade Level: 9th Grade

Duration: 1-2 class periods (45-90 minutes)

Materials:

  1. Computers with internet access (or offline environment set up with p5.js).
  2. Projector to demonstrate the simulation.
  3. Link to the simulation.
  4. Worksheets or online notebooks for binary-to-decimal conversion exercises.
  5. Whiteboard and markers.
"},{"location":"sims/binary-number/#lesson-outline","title":"Lesson Outline:","text":"
  1. Introduction to Binary Numbers (15 minutes):

  2. Begin with a discussion on number systems, focusing on the decimal system.

  3. Introduce the binary number system, explaining its base-2 nature.
  4. Discuss the significance of binary numbers in computer science.

  5. Demonstration of the Simulation (10 minutes):

  6. Project the simulation on the screen.

  7. Explain the interface, pointing out the bits, their significance, and the decimal conversion.
  8. Demonstrate toggling the bits and observing the decimal output.

  9. Interactive Session with Simulation (20 minutes):

  10. Allow students to experiment with the simulation on their computers.

  11. Encourage them to predict the decimal output before toggling the bits.
  12. Facilitate a discussion about their observations and insights.

  13. Binary to Decimal Conversion Exercise (15 minutes):

  14. Distribute worksheets with binary numbers.

  15. Instruct students to convert them to decimal numbers using the simulation as a reference.
  16. Review the answers as a class, discussing any common errors or misconceptions.

  17. Group Activity: Real-World Applications (15-30 minutes):

  18. Divide students into small groups.

  19. Assign each group to research and present a short explanation about a real-world application of binary numbers (e.g., digital storage, computer processors).
  20. Facilitate a class discussion on these applications, linking them back to the simulation.

  21. Wrap-up and Reflection (5-10 minutes):

  22. Summarize key takeaways from the lesson.

  23. Encourage students to reflect on what they learned and how they might see binary numbers in their daily lives.
  24. Answer any remaining questions.
"},{"location":"sims/binary-number/#assessment","title":"Assessment","text":""},{"location":"sims/binary-number/#extension-activities","title":"Extension Activities","text":""},{"location":"sims/binary-number/#references","title":"References","text":""},{"location":"sims/learning-graph/","title":"Learning Graph for Clocks and Watches with MicroPython","text":"

View Learning Graph MicroSim

"},{"location":"sims/seven-segment-display/","title":"7-Segment Display","text":"

Demo

"},{"location":"sims/shift-register/","title":"Shift Register MicroSim","text":"

Run Shift Register MicroSim

Logic Analyzer View

"},{"location":"sims/stopwatch/","title":"Stopwatch MicroSim","text":"

Run the Stopwatch MicroSim

Edit the Stopwatch MicroSim

The Stopwatch MicroSim allows you to simulate the actual stopwatch lab here: Stopwatch Lab

"},{"location":"sims/templates/","title":"MicroSim Templates","text":"

Run the MicroSim Template Edit this MicroSim

These templates are used to help guild your generative AI responses for consistency of the user interface.

{ .md-button .md-button--primary }

"},{"location":"sims/themes/","title":"Clock Face Themes","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":"Designing and Building Clocks and Watches with MicroPython and AI","text":"

Welcome to the Clocks and Watches website!

This website is a resource for teachers, mentors and students who want to have fun hands-on ways to learn computational thinking using the popular Python language. This course gives junior high and high school students the tools to design and build their own digital clocks and smart watches using low-cost components.

This website contains detailed instructions for finding low-cost parts (many under $20/student) and customizing your custom clock and watch displays using MicroPython.

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

  1. The 2021 release of the powerful $4 Raspberry Pi Pico microcontroller. It had almost 100x the power of the Arduino Uno and sold for 1/8th the price.
  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 frame buffer 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.
  5. The availability of low-cost yet ultra-precise real-time clock chips like the DS3231.

Our goal is to provide fun ways to teach computational thinking to a wide variety of students from 6th to 12th grades. 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

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

Innovation often occurs at the seams of specialties. This book fits at the intersection of timekeeping, art and teaching computational thinking.

This book can serve as a hands-on guide to introducing students to the fun world of building your own customized clocks and watches with low-cost parts and some MicroPython code to make it all work together.

"},{"location":"about/#why-we-created-this-book","title":"Why We Created This Book","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/#the-evolution-of-learning","title":"The Evolution of Learning","text":"

The above image was my attempt in 2014 to use a breadboard and the original ATmega328P microcontroller to build a clock. This required deep knowledge of the ATmega328P microcontroller and programming in raw low-level C code. Just setting up the integrated development environment could take days. The code needed to be complied and the entire program downloaded into the memory just to run a simple test. You were limited to just 2K or RAM and the clock ran at a slow 16MhZ.

Today the Raspberry Pi has enough RAM to run a full Python interpreter and 264K of RAM just for program memory. It can run up to 150MhZ and has multiple cores that can be working on different tasks such as reading sensors and updating displays.

In short, this power translates into fun projects that are easy to write. Combining these powerful microcontrollers with AI assistants means even novice programmers can quickly achieve great things! Building DIY projects has never been so fun!

"},{"location":"about/#great-way-to-teach-prompt-engineering","title":"Great Way to Teach Prompt Engineering","text":"

This course has hundreds of sample prompts you can use with generative AI tools to create custom code or to generate your own intelligent textbook. You are strongly encouraged to load your config.py file into the projects area of these tool or include the config.py file as part of your prompt.

We hope that you use this course as a proactive way to teach prompt engineering to your students.

Make sure you use the search tool in the page header with the word \"prompt\" to help you get the best prompt for your task.

For example:

Prompt

I have a csv file of birthdays for my family and friends. Please add birthday reminders to the clock display.

"},{"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. Our goal is to create fun projects that teach computational thinking.

  1. Remember: Students begin by identifying essential electronic components---breadboards, microcontrollers, buttons, knobs, real-time clocks, 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). They proceed to use these concepts to customize clock and watch faces using graphic displays.
  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. They can also work with AI to design new clocks.
  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 that blend into the AI-powered home.

"},{"location":"course-description/#computational-thinking","title":"Computational Thinking","text":"

A structured problem-solving approach that uses computer science principles to formulate solutions by breaking down complex tasks into logical, repeatable steps that can be understood by both humans and machines Example: Breaking down the task of making a peanut butter sandwich into discrete steps: \"open jar\", \"grasp knife\", \"scoop peanut butter\".

The main concepts of computational thinking are:

"},{"location":"course-description/#decomposition","title":"Decomposition","text":"

The process of breaking a complex problem into smaller, more manageable parts.

Example: Dividing a clock program into separate functions for displaying time, handling buttons, and managing alarms.

"},{"location":"course-description/#pattern-recognition","title":"Pattern Recognition","text":"

The ability to identify similarities, trends, and regularities in data or problems.

Example: Noticing that both analog and digital clocks need similar time calculation functions despite different display methods.

"},{"location":"course-description/#abstraction","title":"Abstraction","text":"

Focusing on essential details while filtering out irrelevant information to create a generalized solution

Example: Creating a display_time() function that works with any type of display by hiding the specific implementation details.

"},{"location":"course-description/#algorithmic-thinking","title":"Algorithmic Thinking","text":"

Creating a set of ordered steps that will solve a problem or achieve a goal.

Example: Developing a sequence of steps to synchronize a clock with an internet time server.

These concepts work together:

  1. First, decompose the problem
  2. Look for patterns in the smaller pieces
  3. Abstract away unnecessary details
  4. Create step-by-step solutions with algorithms
"},{"location":"faq/","title":"Building Clocks and Watches with MicroPython - Frequently Asked Questions","text":""},{"location":"faq/#general-course-information","title":"General Course Information","text":""},{"location":"faq/#what-prior-knowledge-do-i-need-for-this-course","title":"What prior knowledge do I need for this course?","text":"

No specific electronics or programming experience is required. However, basic computer literacy and comfort with mathematics at a high school level will be helpful. The course starts with fundamentals and builds up gradually.

"},{"location":"faq/#how-long-is-each-class-session","title":"How long is each class session?","text":"

While the course runs for 14 weeks, specific class session lengths are determined by your school's schedule. Contact your instructor for the exact timing of your sessions.

"},{"location":"faq/#do-i-need-to-purchase-my-own-equipment","title":"Do I need to purchase my own equipment?","text":"

Check with your instructor about equipment policies. Generally, core components like the Raspberry Pi Pico W and basic electronics will be provided, but you may want to purchase additional components for your final project.

"},{"location":"faq/#what-software-do-i-need-to-install","title":"What software do I need to install?","text":"

You'll need the Thonny IDE (a Python editor designed for MicroPython), which is free and works on Windows, Mac, and Linux. Your instructor will guide you through the installation process during the first week.

"},{"location":"faq/#technical-questions","title":"Technical Questions","text":""},{"location":"faq/#what-is-micropython-and-how-is-it-different-from-regular-python","title":"What is MicroPython and how is it different from regular Python?","text":"

MicroPython is a streamlined version of Python designed to run on microcontrollers. While it maintains Python's core features and syntax, it's optimized for hardware control and has special libraries for working with electronic components.

"},{"location":"faq/#what-is-a-raspberry-pi-pico-w","title":"What is a Raspberry Pi Pico W?","text":"

The Raspberry Pi Pico W is a small, affordable microcontroller board with built-in Wi-Fi. It's perfect for learning because it's user-friendly, well-documented, and has plenty of input/output pins for connecting different components.

"},{"location":"faq/#will-we-be-doing-any-soldering","title":"Will we be doing any soldering?","text":"

Most projects will use breadboards for non-permanent connections. Some optional advanced projects might involve soldering, but this will be optional and supervised.

"},{"location":"faq/#how-accurate-will-our-clocks-be","title":"How accurate will our clocks be?","text":"

The accuracy depends on the components used. With the basic oscillator, clocks might drift a few seconds per day. When we add a Real-Time Clock (RTC) module, accuracy improves significantly to within a few seconds per month.

"},{"location":"faq/#project-related-questions","title":"Project-Related Questions","text":""},{"location":"faq/#what-kind-of-displays-will-we-use","title":"What kind of displays will we use?","text":"

We'll work with several types: - Simple LEDs for basic digital displays - 128\u00d764 OLED displays for more sophisticated interfaces - Other display options for final projects

"},{"location":"faq/#can-i-create-my-own-custom-clock-design-for-the-final-project","title":"Can I create my own custom clock design for the final project?","text":"

Yes! The final project is designed to be creative and personal. You can combine different components and features we've learned about to create something unique.

"},{"location":"faq/#how-will-we-handle-time-zones-and-daylight-saving-time","title":"How will we handle time zones and daylight saving time?","text":"

We'll learn to sync time using web services (when Wi-Fi is available) and program time zone adjustments. Managing daylight saving time will be covered as part of the advanced features.

"},{"location":"faq/#learning-and-assessment","title":"Learning and Assessment","text":""},{"location":"faq/#how-will-i-be-graded","title":"How will I be graded?","text":"

Assessment typically includes:

"},{"location":"faq/#what-happens-if-my-clock-doesnt-work","title":"What happens if my clock doesn't work?","text":"

Troubleshooting is a crucial part of the learning process. You'll have support from instructors and peers, and we'll learn systematic debugging approaches. Making mistakes and fixing them is how we learn!

"},{"location":"faq/#can-i-work-with-ai-tools-during-the-course","title":"Can I work with AI tools during the course?","text":"

Yes, part of the course includes learning how to effectively use AI tools for design and problem-solving. We'll cover best practices for AI collaboration while ensuring you understand the fundamental concepts.

"},{"location":"faq/#hardware-and-components","title":"Hardware and Components","text":""},{"location":"faq/#what-happens-if-i-accidentally-damage-a-component","title":"What happens if I accidentally damage a component?","text":"

Don't worry! Learning electronics involves some trial and error. Basic components are relatively inexpensive and replaceable. Just inform your instructor if something breaks.

"},{"location":"faq/#how-do-breadboards-work","title":"How do breadboards work?","text":"

A breadboard is a reusable platform for creating temporary electrical connections. We'll spend time early in the course understanding how they work and practicing proper connection techniques.

"},{"location":"faq/#whats-the-difference-between-digital-and-analog-inputsoutputs","title":"What's the difference between digital and analog inputs/outputs?","text":"

Digital signals are either on (1) or off (0), while analog signals can have many values in between. We'll use both types: digital for buttons and simple LEDs, analog for sensors and variable controls.

"},{"location":"faq/#programming-concepts","title":"Programming Concepts","text":""},{"location":"faq/#do-i-need-to-memorize-all-the-micropython-commands","title":"Do I need to memorize all the MicroPython commands?","text":"

No, you don't need to memorize everything. We'll provide reference materials, and part of learning is knowing how to find and use documentation effectively.

"},{"location":"faq/#what-if-i-miss-a-class-and-fall-behind","title":"What if I miss a class and fall behind?","text":"

Each concept builds on previous learning, but we'll provide catch-up resources and extra help sessions. Talk to your instructor as soon as possible if you fall behind.

"},{"location":"faq/#how-will-we-debug-our-code","title":"How will we debug our code?","text":"

We'll learn several debugging techniques:

"},{"location":"faq/#safety-and-best-practices","title":"Safety and Best Practices","text":""},{"location":"faq/#is-there-any-risk-of-electric-shock","title":"Is there any risk of electric shock?","text":"

The Pico W and components we use operate at very low voltages (3.3V or 5V) which are safe to handle. However, we'll still learn and follow proper safety procedures.

"},{"location":"faq/#how-do-i-protect-the-electronic-components","title":"How do I protect the electronic components?","text":"

We'll cover basic handling procedures including:

"},{"location":"faq/#advanced-topics-and-extensions","title":"Advanced Topics and Extensions","text":""},{"location":"faq/#can-we-add-features-like-alarms-or-stopwatch-functions","title":"Can we add features like alarms or stopwatch functions?","text":"

Yes! As you progress through the course, you'll learn to add various features including:

"},{"location":"faq/#will-we-learn-about-power-management","title":"Will we learn about power management?","text":"

Yes, we'll cover power management including:

"},{"location":"faq/#how-long-will-the-cr2032-coin-cell-battery-last","title":"How long will the CR2032 coin cell battery last?","text":"

The typical operational duration in real-time clock circuits ranges from 2 to 10 years, depending on temperature conditions and current draw.

The DS3231 RTC module used in our kits draw 3\u03bcA at room temperature typically operates for 8 years on a single CR2032 battery. The key factors affecting battery life are:

"},{"location":"faq/#if-the-clock-is-plugged-into-the-wall-will-the-backup-cr2032-battery-be-drained","title":"If the clock is plugged into the wall will the backup CR2032 battery be drained?","text":"

No. When external power is connected, the DS3231 RTC board automatically switches to the main power supply and isolates the backup battery circuit, preventing battery drain. The CR2032 is only used when main power is disconnected.

This power switching is handled by internal circuitry using diodes that prevent current from flowing to/from the backup battery during normal operation.

"},{"location":"faq/#future-applications","title":"Future Applications","text":""},{"location":"faq/#can-i-use-these-skills-for-other-projects","title":"Can I use these skills for other projects?","text":"

Absolutely! The skills you'll learn apply to many other electronics and programming projects:

"},{"location":"faq/#how-does-this-course-prepare-me-for-further-study","title":"How does this course prepare me for further study?","text":"

This course builds valuable skills for:

"},{"location":"faq/#tools-and-resources","title":"Tools and Resources","text":""},{"location":"faq/#what-online-resources-can-we-use","title":"What online resources can we use?","text":"

We'll use various resources including:

"},{"location":"faq/#will-we-use-version-control-like-git","title":"Will we use version control like Git?","text":"

While not required, we'll introduce basic version control concepts and how they can help manage your code as projects become more complex. Many of the clock kits will have all the sample code preloaded into the Raspberry Pi Pico file system.

"},{"location":"faq/#collaboration-and-community","title":"Collaboration and Community","text":""},{"location":"faq/#can-we-work-in-teams","title":"Can we work in teams?","text":"

Yes! Many activities will involve pair programming or team collaboration. The final project can be individual or team-based, depending on complexity and scope.

"},{"location":"faq/#how-can-we-share-our-projects-with-others","title":"How can we share our projects with others?","text":"

We'll discuss ways to:

"},{"location":"faq/#getting-help","title":"Getting Help","text":""},{"location":"faq/#what-if-i-get-stuck-on-a-problem","title":"What if I get stuck on a problem?","text":"

Multiple support options are available:

Note that you can load sample programs and yur config.py file into the generative AI projects area and get high quality results that use your local configuration.

"},{"location":"faq/#can-i-get-extra-help-outside-of-class","title":"Can I get extra help outside of class?","text":"

Yes, instructors will have office hours and may offer additional lab time for students who want extra practice or help.

"},{"location":"faq/#course-materials","title":"Course Materials","text":""},{"location":"faq/#what-should-i-bring-to-class","title":"What should I bring to class?","text":"

Typically you'll need:

"},{"location":"faq/#how-should-i-store-and-transport-my-projects","title":"How should I store and transport my projects?","text":"

We'll provide guidance on:

"},{"location":"faq/#final-projects","title":"Final Projects","text":""},{"location":"faq/#what-makes-a-good-final-project","title":"What makes a good final project?","text":"

Strong final projects typically:

"},{"location":"faq/#can-i-continue-working-on-my-project-after-the-course-ends","title":"Can I continue working on my project after the course ends?","text":"

Yes! You'll have the knowledge to continue developing and improving your projects. We'll discuss ways to extend your learning and continue exploring electronics and programming.

"},{"location":"glossary/","title":"Glossary","text":""},{"location":"glossary/#12-hour-vs-24-hour-formats","title":"12-Hour vs 24-Hour Formats","text":"

Methods for displaying time in either 12-hour (AM/PM) or 24-hour (military) format, including conversion between the two.

Example: Converting 13:00 to 1:00 PM and handling the AM/PM indicator.

"},{"location":"glossary/#3d-printing-clock-and-watch-enclosures","title":"3D Printing Clock and Watch Enclosures","text":"

A process of creating custom protective cases for watch components using additive manufacturing technology.

We will not cover these topics in this course. Our focus in this website is using clock and watch projects to teach computational thinking.

"},{"location":"glossary/#abstraction","title":"Abstraction","text":"

A technique for managing complexity by hiding unnecessary details while keeping essential features visible.

Example: Using a display_time() function that accepts hours and minutes without needing to know how individual LED segments are controlled.

Abstraction is one of the four key computational thinking topics we teach in this course.

"},{"location":"glossary/#accelerometer","title":"Accelerometer","text":"

A sensor that measures physical acceleration and orientation, useful for tilt-based display activation.

Example: Detecting when a user lifts their wrist to view the watch face.

"},{"location":"glossary/#alarm","title":"Alarm","text":"

A scheduled alert that triggers audio, visual, or vibration notifications at specific times.

Example: Setting multiple daily alarms using the RTC module.

"},{"location":"glossary/#analog-clock-face","title":"Analog Clock Face","text":"

A traditional clock display using rotating hands to show time on a circular dial.

Example: Drawing hour and minute hands using math.sin() and math.cos() for position calculations.

"},{"location":"glossary/#animation","title":"Animation","text":"

The process of creating movement by rapidly updating display contents.

Example: Smoothly rotating second hands or creating blinking colons between hours and minutes.

"},{"location":"glossary/#asynchronous-programming","title":"Asynchronous Programming","text":"

A coding technique that allows multiple tasks to run concurrently without blocking each other.

Example: Using uasyncio to handle display updates while monitoring button presses.

"},{"location":"glossary/#battery-backup","title":"Battery Backup","text":"

A power system that maintains time and settings when main power is disconnected.

"},{"location":"glossary/#battery-drain-calculation","title":"Battery Drain Calculation","text":"

The process of measuring and estimating power consumption to determine battery life.

Example: Measuring current draw during different display update modes.

"},{"location":"glossary/#battery-power","title":"Battery Power","text":"

Portable electrical energy storage used to operate mobile timepieces.

Example: Calculating power consumption to estimate battery life.

"},{"location":"glossary/#battery-tools","title":"Battery Tools","text":"

Instruments and methods for measuring power consumption and battery health.

Example: Using multimeters to measure current draw in different modes.

"},{"location":"glossary/#blitting","title":"Blitting","text":"

A technique for rapidly copying rectangular portions of memory to update displays efficiently.

Example: Using framebuffer operations to reduce screen flicker.

"},{"location":"glossary/#breadboard","title":"Breadboard","text":"

A reusable plastic board with holes and internal connections for prototyping electronic circuits without soldering.

Example: Connecting LED displays and buttons to the Pico W using jumper wires on a breadboard.

"},{"location":"glossary/#button-debouncing","title":"Button Debouncing","text":"

A technique to prevent false button readings caused by mechanical switch contacts bouncing when pressed or released.

Example: debounce_time = 100 in button configuration prevents multiple accidental triggers.

"},{"location":"glossary/#button-input-handling","title":"Button Input Handling","text":"

Software techniques for detecting and responding to button presses.

Example: Using interrupts and debouncing for reliable input detection.

"},{"location":"glossary/#callback-function","title":"Callback Function","text":"

A function passed as an argument to another function, which will be executed when a specific event occurs.

Example: button_mode_irq function that runs whenever the mode button is pressed.

"},{"location":"glossary/#changing-fonts","title":"Changing Fonts","text":"

The process of modifying text appearance by loading and applying different typefaces.

Example: Using the micropython-ufont library to load compact digital number styles.

"},{"location":"glossary/#chimes","title":"Chimes","text":"

Musical or tone sequences played at specific intervals or times.

Example: Playing Westminster quarter-hour chimes using PWM audio output.

"},{"location":"glossary/#chronograph","title":"Chronograph","text":"

A timepiece with both timekeeping and stopwatch capabilities.

"},{"location":"glossary/#color-animations","title":"Color Animations","text":"

Dynamic changes in display colors to create visual effects or indicate status.

Example: Gradually shifting background colors throughout the day.

"},{"location":"glossary/#color-conversion","title":"Color Conversion","text":"

The process of transforming between different color representation systems.

Example: Converting RGB colors to 16-bit format for TFT displays.

"},{"location":"glossary/#color-conversion-palettes","title":"Color Conversion & Palettes","text":"

Methods for transforming between different color representations and managing limited color sets.

Example: Converting RGB colors to display-specific formats and creating custom color themes.

"},{"location":"glossary/#color-depth","title":"Color Depth","text":"

The number of bits used to represent colors in a display.

Example: Using 16 bits per pixel to show 65,536 different colors.

"},{"location":"glossary/#computational-thinking","title":"Computational Thinking","text":"

A structured problem-solving approach that uses computer science principles to formulate solutions by breaking down complex tasks into logical, repeatable steps that can be understood by both humans and machines Example: Breaking down the task of making a peanut butter sandwich into discrete steps: \"open jar\", \"grasp knife\", \"scoop peanut butter\".

The main concepts of computational thinking are:

"},{"location":"glossary/#decomposition","title":"Decomposition","text":"

The process of breaking a complex problem into smaller, more manageable parts Example: Dividing a clock program into separate functions for displaying time, handling buttons, and managing alarms.

"},{"location":"glossary/#pattern-recognition","title":"Pattern Recognition","text":"

The ability to identify similarities, trends, and regularities in data or problems Example: Noticing that both analog and digital clocks need similar time calculation functions despite different display methods.

"},{"location":"glossary/#abstraction_1","title":"Abstraction","text":"

Focusing on essential details while filtering out irrelevant information to create a generalized solution Example: Creating a display_time() function that works with any type of display by hiding the specific implementation details.

"},{"location":"glossary/#algorithmic-thinking","title":"Algorithmic Thinking","text":"

Creating a set of ordered steps that will solve a problem or achieve a goal Example: Developing a sequence of steps to synchronize a clock with an internet time server.

These concepts work together:

  1. First, decompose the problem
  2. Look for patterns in the smaller pieces
  3. Abstract away unnecessary details
  4. Create step-by-step solutions with algorithms
"},{"location":"glossary/#concurrency","title":"Concurrency","text":"

The ability to handle multiple tasks or events simultaneously in a program.

Example: Updating the display while checking for button presses.

"},{"location":"glossary/#conditionals","title":"Conditionals","text":"

Programming statements that perform different actions based on whether a condition is true or false.

Example: Switching between 12-hour and 24-hour time formats.

"},{"location":"glossary/#connecting-speakers","title":"Connecting Speakers","text":"

Methods for attaching and controlling audio output devices to create sound effects and alarms.

Example: Wiring a speaker to a PWM-capable GPIO pin for generating tones for an alarm clock.

"},{"location":"glossary/#console-output","title":"Console Output","text":"

Text-based information displayed in the development environment for debugging.

Example: print(\"Current time:\", hours, \":\", minutes)

"},{"location":"glossary/#cuckoo-clock","title":"Cuckoo Clock","text":"

A timepiece that marks specific hours with sound and optional mechanical movement.

Example: Playing bird sounds and activating a servo at the top of each hour.

"},{"location":"glossary/#custom-font","title":"Custom Font","text":"

A specialized set of character designs loaded into memory for display purposes.

Example: Loading compact number fonts using the micropython-ufont library.

"},{"location":"glossary/#custom-segment-display","title":"Custom Segment Display","text":"

A specialized arrangement of LED segments for showing numbers or characters.

Example: Creating unique digit patterns using individual LED segments.

"},{"location":"glossary/#digital-to-analog-converter-dac","title":"Digital to Analog Converter (DAC)","text":"

An integrated circuit that transforms digital data into a continuous analog voltage. For example we can generate sound using a microcontroller to play an alarm sounds.

In our labs we use the MicroPython I2S (Inter-IC Sound) protocol for generating high-fidelity audio output.

For example we have a lab that generates a sine wave to create audible tones through a speaker.

Specifically our class uses the PCM5102A DAC Module to generate Sound.

"},{"location":"glossary/#date-and-calendar-display","title":"Date and Calendar Display","text":"

Showing current date information including day of week, month, and year.

Example: Formatting and displaying full date alongside time information.

"},{"location":"glossary/#datetime-objects","title":"datetime Objects","text":"

Programming structures that store date and time information together.

Example: Converting between timestamp integers and datetime format.

"},{"location":"glossary/#daylight-saving-time","title":"Daylight Saving Time","text":"

A seasonal time adjustment typically involving a one-hour shift.

Example: Automatically adjusting clock time based on DST rules.

"},{"location":"glossary/#deep-sleep","title":"Deep Sleep","text":"

A low-power mode that disables most system functions to conserve energy.

Example: Entering sleep mode when the watch face isn't being viewed.

"},{"location":"glossary/#digital-display","title":"Digital Display","text":"

An electronic output device that shows numbers, letters, or symbols using discrete segments or pixels.

Example: Four-digit seven-segment LED display showing hours and minutes.

"},{"location":"glossary/#display-driver","title":"Display Driver","text":"

Software that controls how information is shown on a specific type of display.

Example: Using the ST7735 driver for TFT LCD screens.

"},{"location":"glossary/#display-technology-comparison","title":"Display Technology Comparison","text":"

Analysis of different screen types' advantages and limitations.

Example: Evaluating power consumption versus update speed for various displays.

"},{"location":"glossary/#double-buffering","title":"Double Buffering","text":"

A technique using two memory areas to prepare the next frame while displaying the current one.

Example: Creating smooth animations without visible drawing operations.

"},{"location":"glossary/#drawing-arcs","title":"Drawing Arcs","text":"

Creating curved lines as portions of circles on displays.

Example: Drawing round clock face elements and decorative features.

"},{"location":"glossary/#drawing-circles","title":"Drawing Circles","text":"

Creating perfect circular shapes on displays.

Example: Drawing clock face outlines and hour markers.

"},{"location":"glossary/#drawing-ellipses","title":"Drawing Ellipses","text":"

Creating oval shapes on displays.

Example: Designing stylized clock faces and decorative elements.

"},{"location":"glossary/#drawing-hands","title":"Drawing Hands","text":"

Creating moving indicators for hours, minutes, and seconds.

Example: Using line drawing functions to show analog time.

"},{"location":"glossary/#drawing-libraries","title":"Drawing Libraries","text":"

Software collections that provide functions for creating visual elements.

Example: Using built-in graphics functions to draw clock hands and numbers.

"},{"location":"glossary/#drawing-lines","title":"Drawing Lines","text":"

Creating straight line segments on displays.

Example: Drawing clock hand indicators and markings.

"},{"location":"glossary/#drawing-numbers","title":"Drawing Numbers","text":"

Displaying numerical values on screen.

Example: Showing digital time values and markers.

"},{"location":"glossary/#drawing-pixels","title":"Drawing Pixels","text":"

Setting individual display points for custom graphics.

Example: Creating fine details in watch face designs.

"},{"location":"glossary/#drawing-polygons","title":"Drawing Polygons","text":"

Creating shapes with multiple straight sides.

Example: Making custom hour markers and decorative elements.

"},{"location":"glossary/#drawing-primitives","title":"Drawing Primitives","text":"

Basic shapes and elements used to create more complex visual displays.

Example: Using lines and arcs to create clock hands.

"},{"location":"glossary/#drawing-rectangles","title":"Drawing Rectangles","text":"

Creating four-sided shapes with right angles.

Example: Drawing display borders and menu backgrounds.

"},{"location":"glossary/#drawing-text","title":"Drawing Text","text":"

Displaying characters and strings on screen.

Example: Showing time, date, and menu options.

"},{"location":"glossary/#drawing-tick-marks","title":"Drawing Tick Marks","text":"

Creating small indicators around a clock face.

Example: Marking hours and minutes on an analog display.

"},{"location":"glossary/#ds1307","title":"DS1307","text":""},{"location":"glossary/#ds3231","title":"DS3231","text":"

A high-precision real-time clock (RTC) integrated circuit with temperature compensation for accurate timekeeping.

Example: Using the DS3231 to maintain time accuracy within seconds per month.

"},{"location":"glossary/#e-paper-display","title":"E-Paper Display","text":"

A low-power screen technology that maintains its image without constant power.

Example: Using partial updates to change only modified portions of the display.

"},{"location":"glossary/#epoch","title":"Epoch","text":"

A fixed point in time chosen as a reference for measuring or calculating elapsed time in a computer system or data structure.

Example: The Unix epoch begins at midnight UTC on January 1, 1970, while the NTP epoch begins at midnight UTC on January 1, 1900. The MicroPython epoch is often set to be January 1 2000. On the Raspberry Pi Pico the epoch is January 1st, 2021.

"},{"location":"glossary/#error-handling-and-recovery","title":"Error Handling and Recovery","text":"

Techniques for detecting and recovering from timing errors, power issues, or communication failures.

Example: Implementing watchdog timers and automatic resynchronization after power loss.#### Event Handler A function that responds to specific occurrences like button presses or timer updates.

Example: The button_mode_irq function handles mode button press events.

"},{"location":"glossary/#external-rtc","title":"External RTC","text":"

A separate timekeeping chip that maintains accurate time independent of the main processor.

Example: Using a DS3231 module for precise timekeeping.

"},{"location":"glossary/#external-sensors","title":"External Sensors","text":"

Additional hardware components that measure environmental conditions.

Example: Reading temperature and humidity for weather display.

"},{"location":"glossary/#fast-redraw","title":"Fast Redraw","text":"

Techniques for updating displays quickly to maintain smooth animation.

Example: Using hardware acceleration for screen updates.

"},{"location":"glossary/#filesystem","title":"Filesystem","text":"

A system for storing and organizing files on the microcontroller.

Example: Saving configuration settings and logs to internal storage.

"},{"location":"glossary/#flicker-reduction","title":"Flicker Reduction","text":"

Methods to minimize visible display instability during updates.

Example: Using double buffering to prevent partial frame displays.

"},{"location":"glossary/#framebuffer","title":"Framebuffer","text":"

A region of memory that holds the complete contents of a display.

Example: Modifying pixel data before updating the screen.

"},{"location":"glossary/#functions","title":"Functions","text":"

Reusable blocks of code that perform specific tasks.

Example: Creating a function to convert between 12-hour and 24-hour formats.

"},{"location":"glossary/#generating-waveforms-for-audio","title":"Generating Waveforms for Audio","text":"

Creating electrical signals for producing sounds and tones using digital-to-analog conversion or PWM.

Example: Synthesizing different frequencies for alarm sounds and hourly chimes.

"},{"location":"glossary/#ghosting","title":"Ghosting","text":"

A visual artifact where previous images remain partially visible on certain display types.

Example: Using display clearing techniques on e-paper screens.

"},{"location":"glossary/#gpio-pin","title":"GPIO Pin","text":"

General Purpose Input/Output connection on a microcontroller that can be programmed to send or receive electrical signals.

Example: Using GPIO pin 16 for the mode button: mode_pin = Pin(16, Pin.IN, Pin.PULL_UP)

"},{"location":"glossary/#hardware-documentation","title":"Hardware Documentation","text":"

Technical specifications and usage instructions for electronic components.

Example: Consulting pinout diagrams for display connections.

"},{"location":"glossary/#i2c","title":"I2C","text":"

A medium speed 4-wire communication protocol that allows multiple digital components to exchange data using just two wires plus power and ground.

I2C also has many different connectors that allow you to connect I2C components without the need for soldering.

"},{"location":"glossary/#i2c-sensors","title":"I2C Sensors","text":"

Digital components that communicate using the I2C protocol to provide measurements.

Example: Reading temperature and humidity data for weather display.

"},{"location":"glossary/#i2s","title":"I2S","text":"

The Inter-sound protocol we use to transmit sound data to components such as a Digital to Analog Converter.

Inter-IC Sound bus protocol is a synchronous serial protocol used to connect digital audio devices.

I2S is a communication protocol that allows multiple digital components to exchange data using just two wires plus power and ground.

MicroPython 1.21 has builtin support for the I2S bus.

"},{"location":"glossary/#implementing-clock-themes","title":"Implementing Clock Themes","text":"

Creating customizable visual styles for clock displays, including colors, fonts, and layouts.

Example: Allowing users to switch between day/night themes or seasonal variations.

"},{"location":"glossary/#incremental-drawing","title":"Incremental Drawing","text":"

Updating only the necessary portions of a display to improve efficiency.

Example: Refreshing only the seconds indicator rather than the full screen.

"},{"location":"glossary/#interrupt","title":"Interrupt","text":"

A signal that temporarily pauses normal program execution to handle important events.

Example: Responding immediately to button presses.

"},{"location":"glossary/#json","title":"JSON","text":"

A text-based data format commonly used for exchanging information with web services.

Example: Parsing weather data from online APIs.

"},{"location":"glossary/#led-clock-displays","title":"LED Clock Displays","text":"

A digital timepiece display using light-emitting diodes arranged in segments or matrices to show time information.

Example: Using a four-digit seven-segment LED display to show hours and minutes.

"},{"location":"glossary/#led-matrix","title":"LED Matrix","text":"

An array of light-emitting diodes arranged in rows and columns that can display patterns, numbers, or text.

"},{"location":"glossary/#libraries","title":"Libraries","text":"

Collections of pre-written code that provide useful functions and features.

Example: Using the math module for trigonometric calculations.

"},{"location":"glossary/#lipo-charging","title":"LiPo Charging","text":"

The process of safely recharging Lithium Polymer batteries.

Example: Implementing USB charging circuits with protection features.

"},{"location":"glossary/#lipo-charging-circuit","title":"LiPo Charging Circuit","text":"

Electronic system for safely charging Lithium Polymer batteries while protecting against overcharge and overdischarge.

Example: Implementing USB charging with voltage regulation and protection features.

"},{"location":"glossary/#loading-drivers","title":"Loading Drivers","text":"

Installing and initializing software that controls specific hardware components.

Example: Importing and configuring TFT display drivers.

"},{"location":"glossary/#localtime-function","title":"localtime() Function","text":"

A MicroPython function that returns the current time as a tuple of values.

Example: year, month, day, hour, minute, second = localtime()

"},{"location":"glossary/#logging","title":"Logging","text":"

The practice of recording program events and data for debugging or analysis.

Example: Saving timing discrepancies to investigate accuracy issues.

"},{"location":"glossary/#logging-time-data-to-an-sd-card","title":"Logging Time Data to an SD Card","text":"

Recording timestamped information to external storage for debugging or data collection.

Example: Saving temperature readings with timestamps every hour.

"},{"location":"glossary/#loops","title":"Loops","text":"

Programming structures that repeat code blocks multiple times.

Example: Continuously updating the display every second.

"},{"location":"glossary/#math-module","title":"Math Module","text":"

A collection of mathematical functions for complex calculations.

Example: Using trigonometry to position clock hands.

"},{"location":"glossary/#mathsin-and-mathcos","title":"math.sin() and math.cos()","text":"

Trigonometric functions used for calculating positions on circular displays.

Example: Computing analog clock hand coordinates.

"},{"location":"glossary/#menu-system","title":"Menu System","text":"

An interface allowing users to navigate options and settings.

Example: Creating hierarchical settings menus for watch configuration.

"},{"location":"glossary/#menu-systems-for-watch-settings","title":"Menu Systems for Watch Settings","text":"

Hierarchical interface structures for configuring watch parameters and features.

Example: Creating nested menus for time, alarm, and display settings.

"},{"location":"glossary/#micropython","title":"MicroPython","text":"

A streamlined version of Python programming language designed to run on microcontrollers and embedded systems.

"},{"location":"glossary/#micropython-drawing","title":"MicroPython Drawing","text":"

Built-in functions for creating visual elements on displays.

Example: Using framebuf methods to draw shapes and text.

"},{"location":"glossary/#micropython-network","title":"MicroPython network","text":"

A module providing Wi-Fi and network connectivity functions.

Example: Connecting to wireless networks for time synchronization.

"},{"location":"glossary/#micropython-syntax","title":"MicroPython Syntax","text":"

The specific programming language rules and structure for MicroPython code.

Example: Using Python-style indentation for code blocks.

"},{"location":"glossary/#micropython-ufont-library","title":"micropython-ufont Library","text":"

A specialized library for handling compact font rendering in MicroPython applications.

Example: Loading custom digit fonts optimized for small displays.

"},{"location":"glossary/#neopixel","title":"NeoPixel","text":"

A programmable light-emitting diode that combines red, green, and blue elements in a single package with built-in control circuitry.

NeoPixel's strips only require three wires: GND, 5V and data. It makes creating clock display patterns very easy.

Example: Creating a ring of 12 individually addressable RGB LEDs to mark hour positions on a clock face.

"},{"location":"glossary/#network-time-protocol-ntp","title":"Network Time Protocol (NTP)","text":"

A method for synchronizing time over the internet.

Example: Updating the RTC from online time servers.

"},{"location":"glossary/#oled-display","title":"OLED Display","text":"

A thin, bright display that uses organic light-emitting diodes to show text and graphics without a backlight.

"},{"location":"glossary/#partial-refresh","title":"Partial Refresh","text":"

Updating only changed portions of a display to improve efficiency.

Example: Refreshing only the seconds digits each update.

"},{"location":"glossary/#partial-screen-updates","title":"Partial Screen Updates","text":"

Techniques for refreshing only the changed portions of a display to improve efficiency and reduce flicker.

Example: Updating only the seconds digits while leaving hours and minutes static.

"},{"location":"glossary/#pedometer","title":"Pedometer","text":"

A feature that counts steps using motion sensor data.

Example: Calculating daily step counts from accelerometer readings.

"},{"location":"glossary/#pcm5102a-dac","title":"PCM5102A DAC","text":"

An audio Digital to Analog chip we use in our clock projects.

"},{"location":"glossary/#pin","title":"Pin","text":"

A metal connector on electronic components that carries electrical signals or power.

"},{"location":"glossary/#power-management","title":"Power Management","text":"

Techniques for minimizing and controlling energy consumption.

Example: Using sleep modes and efficient display updates.

"},{"location":"glossary/#power-monitoring","title":"Power Monitoring","text":"

Methods for measuring and tracking power consumption in battery-operated devices.

Example: Logging battery voltage and current draw to optimize device lifetime.

"},{"location":"glossary/#pull-up-resistor","title":"Pull-up Resistor","text":"

An electronic component that ensures a consistent voltage level on input pins when buttons or switches are not pressed.

Example: Using internal pull-up resistors with Pin.PULL_UP for button inputs.

"},{"location":"glossary/#pwm","title":"PWM","text":"

Pulse Width Modulation - a technique for creating varying levels of brightness or speed by rapidly switching a signal on and off.

"},{"location":"glossary/#pwm-audio","title":"PWM Audio","text":"

Using Pulse Width Modulation to generate sounds and tones.

Example: Creating alarm beeps and hourly chimes.

"},{"location":"glossary/#raspberry-pi-pico-w","title":"Raspberry Pi Pico W","text":"

A small, low-cost microcontroller board with built-in wireless capabilities, designed for learning and DIY projects.

"},{"location":"glossary/#real-time-clock-rtc","title":"Real-Time Clock (RTC)","text":"

A specialized chip or circuit that keeps accurate time even when main power is removed.

Example: Using the RTC module to maintain accurate time: rtc = RTC()

"},{"location":"glossary/#real-time-clock-rtc-overview","title":"Real-time Clock (RTC) Overview","text":"

A comprehensive explanation of how real-time clock modules maintain accurate time, including initialization, synchronization, and backup power considerations.

Example: Understanding how the RTC maintains time even when main power is disconnected.

"},{"location":"glossary/#rotary-encoder","title":"Rotary Encoder","text":"

A input device that converts rotational movement into digital signals.

Example: Using encoder rotation to adjust time settings.

"},{"location":"glossary/#screen-tearing-prevention","title":"Screen Tearing Prevention","text":"

Techniques to avoid visual artifacts caused by updating display content while it's being refreshed.

Example: Using vsync or double buffering to ensure clean display updates.

"},{"location":"glossary/#screen-update","title":"Screen Update","text":"

The process of refreshing display contents to show new information.

Example: Efficiently updating only changed portions of the time display.

"},{"location":"glossary/#screen-via-spi","title":"Screen Via SPI","text":"

High-speed serial communication method for updating displays.

Example: Sending framebuffer data to TFT screens efficiently.

"},{"location":"glossary/#servo-motor","title":"Servo Motor","text":"

An actuator that can rotate to precise positions.

Example: Moving physical clock hands on a hybrid display.

"},{"location":"glossary/#setting-system-clock","title":"Setting System Clock","text":"

Configuring the internal timekeeping system with accurate time.

Example: Updating RTC time from an external time source.

"},{"location":"glossary/#setting-time-with-buttons","title":"Setting Time with Buttons","text":"

Interface design and implementation for adjusting clock time using physical buttons.

Example: Using mode, increment, and decrement buttons to set hours and minutes.

"},{"location":"glossary/#setting-up-i2c-communication","title":"Setting Up I\u00b2C Communication","text":"

Configuring and using the Inter-Integrated Circuit (I\u00b2C) protocol for connecting multiple devices.

Example: Initializing I\u00b2C bus for communicating with RTC and sensor modules.

"},{"location":"glossary/#setting-up-wifi-on-the-pico-w","title":"Setting Up Wi\u2011Fi on the Pico W","text":"

Process of configuring wireless network connectivity on the Raspberry Pi Pico W microcontroller.

Example: Connecting to a local network using SSID and password credentials.

"},{"location":"glossary/#seven-segment-display","title":"Seven-Segment Display","text":"

A display made of seven LED segments plus a decimal point that can show numbers and some letters.

Example: Creating patterns for digits using segments 'a' through 'g': SEGMENTS = {'a': 4, 'b': 3, 'c': 2, 'd': 7, 'e': 6, 'f': 5, 'g': 1}

"},{"location":"glossary/#shift-register","title":"Shift Register","text":"

An integrated circuit that converts serial data into parallel outputs, useful for controlling multiple LEDs with fewer pins.

Example: Using SR74HC595 shift register to control display segments.

"},{"location":"glossary/#sleep-mode","title":"Sleep Mode","text":"

A low-power state that reduces energy consumption.

Example: Entering deep sleep between display updates.

"},{"location":"glossary/#sleep-scheduling","title":"Sleep Scheduling","text":"

Managing when the device enters and exits low-power states.

Example: Programming wake-up times for hourly updates.

"},{"location":"glossary/#smartwatch-displays","title":"Smartwatch Displays","text":"

Advanced display modules designed specifically for wearable devices, optimizing size, power consumption, and readability.

Example: Using a small, high-resolution display with automatic brightness adjustment.

"},{"location":"glossary/#sound-encoding","title":"Sound Encoding","text":"

Methods for storing and playing audio data for alarms and notifications.

Example: Converting WAV files to suitable format for playback.

"},{"location":"glossary/#spi","title":"SPI","text":"

Serial Peripheral Interface - a fast communication protocol for connecting digital components using multiple signal lines.

"},{"location":"glossary/#spi-interface","title":"SPI Interface","text":"

A high-speed serial communication protocol for connecting displays and sensors.

Example: Transferring data to TFT displays efficiently.

"},{"location":"glossary/#state-machine","title":"State Machine","text":"

A programming concept where a system can be in one of several defined states, with specific rules for transitioning between them.

Example: Clock modes including \"run\", \"set hour\", \"set minute\", and \"set AM/PM\".

"},{"location":"glossary/#step-counting-pedometer","title":"Step Counting (Pedometer)","text":"

Using motion sensors to detect and count walking steps for fitness tracking.

Example: Processing accelerometer data to identify step patterns and maintain daily count.

"},{"location":"glossary/#stopwatch","title":"Stopwatch","text":"

A timing device that measures elapsed time from a starting point, typically with precision to fractions of a second.

Example: Implementing start, stop, and lap timing functions with millisecond accuracy.

"},{"location":"glossary/#synchronizing-time-from-the-pc","title":"Synchronizing Time from the PC","text":"

The process of setting a microcontroller's clock using the connected computer's time.

Example: Using Thonny IDE to automatically update the Pico's RTC when uploading code.

"},{"location":"glossary/#temperature-and-humidity","title":"Temperature and Humidity","text":"

Integration of environmental sensors to display current conditions alongside time.

Example: Reading DHT22 sensor data to show temperature and humidity with clock display.

"},{"location":"glossary/#temperature-sensor","title":"Temperature Sensor","text":"

A component that measures ambient temperature.

Example: Displaying current temperature alongside time.

"},{"location":"glossary/#tft-displays","title":"TFT Displays","text":"

Thin-film-transistor liquid crystal displays for showing color graphics.

Example: Using ST7735 or ILI9341 displays for watch faces.

"},{"location":"glossary/#thonny-ide","title":"Thonny IDE","text":"

An integrated development environment optimized for MicroPython programming.

Example: Using Thonny to upload code and debug timing issues.

"},{"location":"glossary/#time-module","title":"Time Module","text":"

MicroPython library for handling time-related operations.

Example: Using scheduling functions for regular updates.

"},{"location":"glossary/#time-synchronization","title":"Time Synchronization","text":"

Process of updating device time from an accurate external source.

Example: Getting current time from network time servers.

"},{"location":"glossary/#time-zone","title":"Time Zone","text":"

A region that observes a uniform standard time.

Example: Converting between local time and UTC.

"},{"location":"glossary/#time-based-tasks-with-uasyncio","title":"Time-Based Tasks with uasyncio","text":"

Scheduling and managing time-dependent operations using MicroPython's asynchronous I/O framework.

Example: Updating multiple display elements at different intervals without blocking.

"},{"location":"glossary/#timer","title":"Timer","text":"

A system resource that generates regular time-based events.

Example: Creating precise one-second update intervals.

"},{"location":"glossary/#timer-based-events","title":"Timer-Based Events","text":"

Actions triggered by internal timing mechanisms.

Example: Updating display elements at specific intervals.

"},{"location":"glossary/#ultra-low-power","title":"Ultra-Low Power","text":"

Operating modes and techniques that minimize energy consumption.

Example: Using sleep modes and efficient screen updates.

"},{"location":"glossary/#usb-power","title":"USB Power","text":"

A 5-volt power source available through Universal Serial Bus connections, commonly used for powering small electronic projects.

"},{"location":"glossary/#using-16-bits-to-represent-color","title":"Using 16 Bits to Represent Color","text":"

A color depth specification that allocates 16 bits per pixel, typically with 5 bits for red, 6 for green, and 5 for blue (RGB565 format).

Example: Converting RGB colors to 16-bit format for efficient storage and display.

"},{"location":"glossary/#utc","title":"UTC","text":"

Coordinated Universal Time, the primary time standard for global time coordination.

Example: Converting local time to UTC for synchronization.

"},{"location":"glossary/#wake-on-alarm","title":"Wake on Alarm","text":"

Feature that activates the device from sleep mode at specific times.

Example: Waking the display for scheduled notifications.

"},{"location":"glossary/#watch-face-design","title":"Watch Face Design","text":"

Principles and techniques for creating visually appealing and functional clock displays.

Example: Balancing aesthetics and readability in analog and digital layouts.

"},{"location":"glossary/#watchdog-timer","title":"Watchdog Timer","text":"

A hardware feature that resets the system if the program stops responding.

Example: Ensuring reliability in long-running clock applications.

"},{"location":"glossary/#weather-updates","title":"Weather Updates","text":"

Real-time environmental data obtained through internet services.

Example: Displaying current conditions alongside time.

"},{"location":"glossary/#web-services","title":"Web Services","text":"

Online resources that provide data or functionality through standard internet protocols.

Example: Fetching current time from an internet time server for clock synchronization.

"},{"location":"glossary/#wi-fi-module","title":"Wi-Fi Module","text":"

Hardware that enables wireless network connectivity.

Example: Connecting to the internet for time synchronization.

"},{"location":"glossary/#wifibased-weather-updates","title":"Wi\u2011Fi\u2013Based Weather Updates","text":"

Retrieving and displaying current weather conditions using wireless internet connectivity.

Example: Fetching temperature, humidity, and forecast data from online weather services.

"},{"location":"glossary/#world-clock","title":"World Clock","text":"

A timepiece that displays times for multiple time zones simultaneously.

Example: Showing local time alongside other major cities' times.

"},{"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":""},{"location":"references/#data-sheets","title":"Data Sheets","text":""},{"location":"references/#battery-management","title":"Battery Management","text":"

[HT4928S - T6845C Data sheet][https://components101.com/sites/default/files/component_datasheet/T6845C-Datasheet.pdf] 8 pin battery management Chip

https://www.youtube.com/watch?v=np4NRMKOG6U

"},{"location":"kits/","title":"Introduction to Clock and Watch Project and Kits","text":"

We have grouped information on specific projects together in structures that allow you to find what you are looking for and customize both the hardware and software. We focus on using generative AI tools to customize the code to accelerate your development an enhance your understanding of how each project works.

"},{"location":"kits/#project-structure","title":"Project Structure","text":"

Each kit has the following structure:

"},{"location":"kits/#1-introduction","title":"1. Introduction","text":"

A brief overview of the project including a image of the clock, key features, approximate costs and possible variations.

"},{"location":"kits/#2-purchasing-and-assembly","title":"2. Purchasing and Assembly","text":"

This section shows how to purchase and assemble the clock. Most of the clocks require at a minimum a Raspberry Pi Pico (\\(4), a breadboard (\\)2) and a display. In addition you might add one or more of the following items:

  1. A real-time clock with a coin-cell battery backup to remember the time between power resets.
  2. Buttons to manually set the date and time.
  3. A wireless Pico W to allow your clock to connect to the internet.
  4. A speaker for alarms.
  5. A photodetector for detecting the lighting conditions.
  6. Additional controls such as a knob for adjusting the time.

Since these features can be used in combination, we also provide you with generative AI prompts to customize the code to run these clocks and watches

"},{"location":"kits/#3-code","title":"3. Code","text":"

This kit section provides you with a walkthrough of the core pa project has a brief overview of the project, a separate page for the physical assembly of the clock and a detailed guide to program the clock. The documentation may also have sample generative AI prompts you can use to generate your own code using a tool such as OpenAI's ChatGPT or Anthropic Claude. In general, the more precise your prompt is, the higher the odds that the code generated will be correct.

Each project also has separate folder in the GitHub \"src/kits\" folder. The running clock programs (called the \"mains\") come in several variations. One uses manual buttons (main-buttons.py) to set the time after a power outage. The other version called the \"main-rtc.py\" file name uses a real-time-clock to remember the time between power outages. They require an additional RTC clock that uses a coin-cell battery to hold the current time. If you find a \"main-w.py\" file that uses the secrets.py file to lookup your local wifi login to get the time from the internet. These versions don't need buttons or a RTC to get the correct time. They only need internet access.

"},{"location":"kits/#4-generative-ai-prompts","title":"4. Generative AI Prompts","text":""},{"location":"kits/#led-clocks","title":"LED Clocks","text":""},{"location":"kits/#standard-tm1637-led-clock","title":"Standard TM1637 LED Clock","text":"

This is a great starter clock. The display is low-cost ($1-2), and it only requires four wires to connect it up. It is easy to program LED clock with 4 7-segment displays.

TM1637 LED Clock Kit

"},{"location":"kits/#tinyssd1306-oled-with-an-i2c-interface","title":"TinySSD1306 OLED with an I2C Interface","text":"

This small low-cost OLED display is easy to hook up with just 4 wires. The combination of low-cost and easy hookup makes it an ideal starter project.

We hav purchased these OLEDs on eBay for under $4 each. Some of they have the first 20 rows yellow and the remaining 44 rows blue.

Tiny SSD1306 OLED with I2C Interface

"},{"location":"kits/#larger-242-oled-ssd1306-with-spi-interface","title":"Larger 2.42\" OLED SSD1306 with SPI Interface","text":"

Larger OLED

"},{"location":"kits/#neopixel-clocks","title":"NeoPixel Clocks","text":""},{"location":"kits/#binary-clock","title":"Binary Clock","text":"

Binary Clock

"},{"location":"kits/#seven-segment-clock","title":"Seven Segment Clock","text":""},{"location":"kits/#fibonacci-clock","title":"Fibonacci Clock","text":"

Fibonacci Clock

"},{"location":"kits/#lilygo-rp2040","title":"LilyGo RP2040","text":"

LilyGo RP2040 Kit

"},{"location":"kits/#smartwatch-displays","title":"SmartWatch Displays","text":"

GC9a01 Smartwatch Display

Waveshare LCD Smartwatch Display

"},{"location":"kits/#e-paper-clocks","title":"E-Paper Clocks","text":"

Waveshare e-Paper

"},{"location":"kits/gallery/","title":"Gallery of Clock and Watch Projects","text":""},{"location":"kits/alarm-clock/","title":"Alarm Clocks","text":"

Many changeling features requires attention to detail. Setting requires thoughtfulness of features.

"},{"location":"kits/alarm-clock/#using-buttons","title":"Using Buttons","text":""},{"location":"kits/alarm-clock/#setting-an-alarm","title":"Setting an Alarm","text":""},{"location":"kits/alarm-clock/#modes","title":"Modes","text":"

The Set Button

"},{"location":"kits/alarm-clock/#set-menu","title":"Set Menu","text":""},{"location":"kits/alarm-clock/#set-alarm","title":"Set Alarm","text":""},{"location":"kits/alarm-clock/#stop-alarm","title":"Stop Alarm","text":""},{"location":"kits/alarm-clock/#doze-button","title":"Doze Button","text":""},{"location":"kits/alarm-clock/#multiple-alarms","title":"Multiple Alarms","text":""},{"location":"kits/alarm-clock/#multiple-tones","title":"Multiple Tones","text":""},{"location":"kits/alarm-clock/#custom-wake-music","title":"Custom Wake Music","text":""},{"location":"kits/alarm-clock/display-alarm-icon/","title":"Display Alarm Icon","text":"
from array import array\n\ndef draw_alarm_icon(display, x, y, alarm_hour, alarm_minute, size=24):\n    \"\"\"\n    Draw an alarm bell icon with time display using polygons.\n\n    Args:\n        display: SSD1306 display instance\n        x, y: Top-left position for the icon\n        alarm_hour: Hour of the alarm (1-12)\n        alarm_minute: Minute of the alarm (0-59)\n        size: Base size of the icon (default 24 pixels)\n    \"\"\"\n    # Scale factors\n    scale = size / 24  # Base size is 24 pixels\n\n    # Bell body coordinates (scaled from base design)\n    bell_body = array('B', [\n        int(4 * scale),  int(0 * scale),    # Top left of dome\n        int(20 * scale), int(0 * scale),    # Top right of dome\n        int(24 * scale), int(12 * scale),   # Bottom right curve\n        int(22 * scale), int(18 * scale),   # Right side\n        int(2 * scale),  int(18 * scale),   # Left side\n        int(0 * scale),  int(12 * scale),   # Bottom left curve\n    ])\n\n    # Bell base coordinates\n    bell_base = array('B', [\n        int(2 * scale),  int(18 * scale),   # Top left\n        int(22 * scale), int(18 * scale),   # Top right\n        int(20 * scale), int(20 * scale),   # Bottom right\n        int(4 * scale),  int(20 * scale),   # Bottom left\n    ])\n\n    # Clapper coordinates\n    clapper = array('B', [\n        int(11 * scale), int(20 * scale),   # Top\n        int(13 * scale), int(20 * scale),   # Top right\n        int(14 * scale), int(24 * scale),   # Bottom right\n        int(10 * scale), int(24 * scale),   # Bottom left\n    ])\n\n    # Sound wave lines (using array for consistency)\n    left_wave = array('B', [\n        int(0 * scale),  int(12 * scale),\n        int(-3 * scale), int(12 * scale),\n        int(-4 * scale), int(14 * scale),\n        int(-3 * scale), int(16 * scale),\n    ])\n\n    right_wave = array('B', [\n        int(24 * scale), int(12 * scale),\n        int(27 * scale), int(12 * scale),\n        int(28 * scale), int(14 * scale),\n        int(27 * scale), int(16 * scale),\n    ])\n\n    # Draw the components\n    display.poly(x, y, bell_body, 1, 1)  # Filled bell body\n    display.poly(x, y, bell_base, 1, 1)  # Filled bell base\n    display.poly(x, y, clapper, 1, 1)    # Filled clapper\n\n    # Draw the sound waves\n    if size >= 20:  # Only draw waves if icon is large enough\n        display.poly(x, y, left_wave, 1, 0)   # Left sound wave\n        display.poly(x, y, right_wave, 1, 0)  # Right sound wave\n\n    # Draw the alarm time below the bell\n    time_str = f\"{alarm_hour:2d}:{alarm_minute:02d}\"\n    # Center the time string under the bell\n    text_x = x + (int(24 * scale) - len(time_str) * 6) // 2\n    text_y = y + int(26 * scale)\n    display.text(time_str, text_x, text_y, 1)\n\ndef demo_alarm_icons(display):\n    \"\"\"\n    Demonstrate the alarm icon at different sizes and positions\n    \"\"\"\n    # Clear the display\n    display.fill(0)\n\n    # Draw three different sized bells\n    draw_alarm_icon(display, 0, 0, 7, 30, size=20)    # Small bell\n    draw_alarm_icon(display, 35, 0, 8, 15, size=28)   # Medium bell\n    draw_alarm_icon(display, 80, 0, 6, 45, size=36)   # Large bell\n\n    # Update the display\n    display.show()\n\n# Example usage:\n\"\"\"\noled = ssd1306.SSD1306_SPI(128, 64, spi, DC, RES, CS)\ndemo_alarm_icons(oled)\n\"\"\"\n
"},{"location":"kits/e-paper/","title":"E-Ink Displays","text":"

Prompt

Please suggest a low-cost e-ink display that has strong support for a microPython driver.

ChatGPT

Thought about e-ink display and MicroPython for 5 seconds

If you're looking for a budget-friendly e-ink module that's well-documented and easy to use in MicroPython projects, a great place to start is with the smaller Waveshare e-Paper displays. Specifically, the Waveshare 1.54-inch e-Paper module is often cited for its low cost and good MicroPython support. Here are some details:

"},{"location":"kits/e-paper/#chatgpt-response","title":"ChatGPT Response","text":"

1) Waveshare 1.54-inch e-Paper Module

Why it's a good pick

Where to find drivers

Key specs

"},{"location":"kits/e-paper/#2-waveshare-213-inch-e-paper-slightly-larger-option","title":"2) Waveshare 2.13-inch e-Paper (Slightly Larger Option)","text":"

If you need a bit more display real estate, the 2.13-inch Waveshare e-Paper module is another solid choice. It's still relatively low cost (somewhere around \\(25--\\)30) but gives you higher resolution (around 250 \u00d7 122 or 212 \u00d7 104, depending on the version) for more text or small graphics.

"},{"location":"kits/e-paper/#3-lilygo-t5-series-all-in-one-board","title":"3) LilyGo T5 Series (All-in-One Board)","text":"

Another cost-effective route (if you don't mind an all-in-one solution rather than a separate module) is the LilyGo T5 board series. These boards come with an ESP32 already integrated and a small e-ink display (like 1.54\", 2.13\", 2.7\", 4.7\" versions).

If you already have a microcontroller board and want to keep costs low, a stand-alone Waveshare display will likely be cheaper. However, if you're starting from scratch, the LilyGo T5 might save you some setup time (fewer wiring hassles, etc.).

"},{"location":"kits/e-paper/#summary","title":"Summary","text":"

All of these displays have active communities, plenty of tutorials, and code examples you can adapt for MicroPython, making them a great fit if you're just getting started with e-ink in your microcontroller projects.

o1

"},{"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-displays/","title":"LED Display Clock","text":""},{"location":"kits/led-displays/#simple-tm1637","title":"Simple TM1637","text":"

Simple TM1637

"},{"location":"kits/led-displays/#shift-register-displays","title":"Shift Register Displays","text":"

Shift Register Displays

We can use the TM1637 4-Digit LED Display Module which can be purchased on e-Bay for a few dollars. This means that your clock will cost under $10 total.

Sample Listing on EBay

"},{"location":"kits/led-displays/#hookup-instructions","title":"Hookup Instructions","text":"

We can hook this up to the Raspberry Pi.

|Name|Pin|| |5 Volts|VBUS or VSYS| |GND|Any GND Pin| |CLK|Pins 4| |Data|Pin 5 |

"},{"location":"kits/led-displays/#micropython-driver","title":"MicroPython Driver","text":"

Mike Causer's TM1637 GitHub Driver

"},{"location":"kits/led-displays/#sample-code","title":"Sample Code","text":""},{"location":"kits/led-displays/#python-imports","title":"Python Imports","text":"
import tm1637\nfrom machine import Pin\nfrom utime import sleep\ntm = tm1637.TM1637(clk=Pin(0), dio=Pin(1))\n
"},{"location":"kits/led-displays/#test-program","title":"Test Program","text":"
import tm1637\nfrom machine import Pin\nfrom utime import sleep\ntm = tm1637.TM1637(clk=Pin(0), dio=Pin(1))\n\nwhile True:\n    # all LEDS on \"88:88\"\n    tm.write([127, 255, 127, 127])\n    sleep(1)\n\n    # all LEDS off\n    tm.write([0, 0, 0, 0])\n    sleep(1)\n\n    # show \"0123\"\n    tm.write([63, 6, 91, 79])\n    sleep(1)\n\n    # show \"COOL\"\n    tm.write([0b00111001, 0b00111111, 0b00111111, 0b00111000])\n    sleep(1)\n\n    # show \"HELP\"\n    tm.show('help')\n    sleep(1)\n\n    # display \"dEAd\", \"bEEF\"\n    tm.hex(0xdead)\n    sleep(1)\n\n    tm.hex(0xbeef)\n    sleep(1)\n\n    # show \"12:59\"\n    tm.numbers(12, 59)\n    sleep(1)\n\n    # show \"-123\"\n    tm.number(-123)\n    sleep(1)\n\n    # show temperature '24*C'\n    tm.temperature(24)\n    sleep(1)\n

Next, will will use the localtime() function to build a simple clock.

Simple Clock

"},{"location":"kits/led-displays/#references","title":"References","text":""},{"location":"kits/led-displays/clock-simple/","title":"A Simple Clock with the TM1637 LED Display","text":"

Here is a simple clock program that will display the hours and minutes from the localtime() function. It will also turn the colon LEDs on and off every second using the modulo function to test for the even/odd property of the second.

"},{"location":"kits/led-displays/clock-simple/#the-localtime-function","title":"The Localtime function","text":"
# display hours and minutes on the TM1637 LED display\n# make the colon go on and off every second\nimport tm1637\nfrom machine import Pin\nfrom utime import sleep, localtime\ntm = tm1637.TM1637(clk=Pin(0), dio=Pin(1))\n\nwhile True:\n    now = localtime() # returns 8 inits for date and time\n    hours = now[3]\n    minutes = now[4]\n    seconds = now[5]\n    print(hours, \":\", minutes, ' ', seconds)\n    # flash the colon on and off every second\n    if (seconds % 2): # modulo 2 will be true for odd numbers\n        tm.numbers(hours, minutes, True)\n    else:\n        tm.numbers(hours, minutes, False)\n    sleep(1)\n

That is it! Pretty cool that you can write an entire clock program in that few lines of code. But it is not perfect. Let's find some ways to make the clock work a little better.

Note that the hours is always in 24 hour time. If you want to use 12 hour time you will need to subtract 12 from the hours if the hours is greater than 12.

We can add the following lines of code to make the display better for people that use a 12-hour clock.

if hours > 12:\n    hours = now[3]\n    hours = hours - 12\n

This will work, but it has the disadvantage of displaying the leading zero before the ones digit. Unfortunately, this is the way that the numbers() function was written in the TM1637 module. Let's take a look at how we can clean this up a bit.

"},{"location":"kits/led-displays/clock-simple/#removing-leading-zeros","title":"Removing Leading Zeros","text":"

In order to fix the leading zeros problem in the hours place, we need to write our own version of the numbers() function that changes '0' to be a space (all 7 segments off) if the hours is less than 10.

Here is the original numbers() function taken directly from the driver:

def numbers(self, num1, num2, colon=True):\n    \"\"\"Display two numeric values -9 through 99, with leading zeros\n    and separated by a colon.\"\"\"\n    num1 = max(-9, min(num1, 99))\n    num2 = max(-9, min(num2, 99))\n    segments = self.encode_string('{0:0>2d}{1:0>2d}'.format(num1, num2))\n    if colon:\n        segments[1] |= 0x80 # colon on\n    self.write(segments)\n

You can see that the author used the Python .format function to display the first number using a leading zero. This is fine for our minutes, but not a standard for the hours.

We will need to modify this code to put in a space character in if the hours is less than 10 and to only display the hours number without a leading zero. The format {0:0>2d} will be changed to be: {prefix}{num1:d} where:

prefix = ' ' if num1 < 10 else ''\n
def numbers_nlz(num1, num2, colon=True):\n    \"\"\"Display two numeric values -9 through 99, with a leading space before\n    single-digit first numbers and separated by a colon.\"\"\"\n    num1 = max(-9, min(num1, 99))\n    num2 = max(-9, min(num2, 99))\n    prefix = ' ' if num1 < 10 else ''\n    print(f'\"{prefix}{num1:d}{num2:0>2d}\"')\n    segments = tm.encode_string(f'{prefix}{num1:d}{num2:0>2d}')\n    if colon:\n        segments[1] |= 0x80  # colon on\n    tm.write(segments)\n

Now the display will work as most normal digital clocks.

So as long as the localtime() function is working, this clock should work fine. An as long as your device is connected to your computer via an USB cable it will be fine.

What if you would like your clock to work without being connected to a computer. We have two options:

  1. Used a Raspberry Pi Pico W to get time from a central time service over Wifi
  2. or use a local clock and set the time manually
"},{"location":"kits/led-displays/reference/","title":"Programming Reference for the TM1637 Clock LED Driver","text":""},{"location":"kits/led-displays/reference/#seven-segment-font","title":"Seven Segment Font","text":"

They are called 7-segment displays as there are 7 LEDs for each digit (segment). One byte (7 lower bits) for each segment. The 8th bit (MSB) is for the colon and only on the 2nd segment.

      A\n     ---\n  F |   | B   *\n     -G-      H (on 2nd segment)\n  E |   | C   *\n     ---\n      D\n\n  HGFEDCBA\n0b01101101 = 0x6D = 109 = show \"5\"\n
Display Bin Hex Dec 0 0b00111111 0x3F 63 1 0b00000110 0x06 6 2 0b01011011 0x5B 91 3 0b01001111 0x4F 79 4 0b01100110 0x66 102 5 0b01101101 0x6D 109 6 0b01111101 0x7D 125 7 0b00000111 0x07 7 8 0b01111111 0x7F 127 9 0b01101111 0x6F 111 A 0b01110111 0x77 119 b 0b01111100 0x7C 124 C 0b00111001 0x39 57 d 0b01011110 0x5E 94 E 0b01111001 0x79 121 F 0b01110001 0x71 113 G 0b00111101 0x3D 61 H 0b01110110 0x76 118 I 0b00000110 0x06 6 J 0b00011110 0x1E 30 K 0b01110110 0x76 118 L 0b00111000 0x38 56 M 0b01010101 0x55 85 n 0b01010100 0x54 84 O 0b00111111 0x3F 63 P 0b01110011 0x73 115 q 0b01100111 0x67 103 r 0b01010000 0x50 80 S 0b01101101 0x6D 109 t 0b01111000 0x78 120 U 0b00111110 0x3E 62 v 0b00011100 0x1C 28 W 0b00101010 0x2A 42 X 0b01110110 0x76 118 y 0b01101110 0x6E 110 Z 0b01011011 0x5B 91 blank 0b00000000 0x00 0 - 0b01000000 0x40 64 * 0b01100011 0x63 99"},{"location":"kits/led-displays/reference/#methods","title":"Methods","text":""},{"location":"kits/led-displays/reference/#set-brightneww","title":"Set Brightneww","text":"

Get or set brightness.

brightness(val=None)\n

"},{"location":"kits/led-displays/reference/#write-segments","title":"Write Segments","text":"

Write one or more segments at a given offset.

write(segments, pos=0)\n

"},{"location":"kits/led-displays/reference/#write-two-numbers-and-colon","title":"Write Two Numbers and Colon","text":"

Write Two Numbers and Colon

numbers(num1, num2, colon=True):\n
"},{"location":"kits/led-displays/shift-register-displays/","title":"Shift Register Displays","text":"

The 74hc595 is a chip that takes a clock and data stream in and turns many LEDs on or off. 74hc595 chips can be connected in series to control 7 segments on 4 digits.

I'll create a detailed walkthrough of the clock-driver.py program, breaking it down into digestible sections with accompanying labs. This will help students understand both the code and the underlying concepts.

"},{"location":"kits/led-displays/shift-register-displays/#1-core-components-and-imports","title":"1. Core Components and Imports","text":"
from machine import Pin, RTC \nfrom sr74hc595 import SR74HC595_BITBANG\nfrom utime import sleep, localtime, ticks_ms\n

This section imports the necessary libraries. The program uses: - machine.Pin: Controls individual GPIO pins on the Pico - RTC: Real-Time Clock for keeping time - sr74hc595: Manages the shift register that controls the display - utime: Provides timing functions

"},{"location":"kits/led-displays/shift-register-displays/#lab-1-understanding-gpio-pins","title":"Lab 1: Understanding GPIO Pins","text":"

Have students create a simple LED blink program:

from machine import Pin\nfrom utime import sleep\n\nled = Pin(25, Pin.OUT)  # Built-in LED on Pico\nwhile True:\n    led.value(1)  # Turn on\n    sleep(1)\n    led.value(0)  # Turn off\n    sleep(1)\n
"},{"location":"kits/led-displays/shift-register-displays/#2-rtc-initialization","title":"2. RTC Initialization","text":"
def wait_for_rtc():\n    print(\"Waiting for RTC to be ready...\")\n    rtc = RTC()\n    while True:\n        current_time = rtc.datetime()\n        if current_time[0] != 2021 or current_time[1] != 1 or current_time[2] != 1:\n            print(\"RTC is ready!\")\n            return\n        print(\"RTC not ready yet...\")\n        sleep(1)\n

This function ensures the RTC has been set to a valid time before proceeding.

"},{"location":"kits/led-displays/shift-register-displays/#lab-2-rtc-basics","title":"Lab 2: RTC Basics","text":"

Have students experiment with reading and setting the RTC:

from machine import RTC\nfrom utime import sleep\n\nrtc = RTC()\n# Set the time (year, month, day, weekday, hour, minute, second, subsecond)\nrtc.datetime((2024, 12, 27, 5, 14, 30, 0, 0))\n\nwhile True:\n    current_time = rtc.datetime()\n    print(f\"Current time: {current_time[4]:02d}:{current_time[5]:02d}:{current_time[6]:02d}\")\n    sleep(1)\n
"},{"location":"kits/led-displays/shift-register-displays/#3-seven-segment-display-setup","title":"3. Seven-Segment Display Setup","text":"

class FourDigitClock:\n    ALL_OFF = 0xFF  # All segments off (inverted logic)\n\n    def __init__(self, ser_pin, srclk_pin, rclk_pin):\n        self.ser = Pin(ser_pin, Pin.OUT)\n        self.srclk = Pin(srclk_pin, Pin.OUT) \n        self.rclk = Pin(rclk_pin, Pin.OUT)\n
This class manages the four-digit display. It uses three pins to control the shift register.

"},{"location":"kits/led-displays/shift-register-displays/#lab-3-seven-segment-pattern-display","title":"Lab 3: Seven-Segment Pattern Display","text":"

Have students create a simple program to display a single digit:

from machine import Pin\nfrom utime import sleep\n\n# Create a simple version that lights up segments manually\nsegments = {\n    'a': Pin(2, Pin.OUT),\n    'b': Pin(3, Pin.OUT),\n    'c': Pin(4, Pin.OUT),\n    # ... add more segments\n}\n\ndef display_number(number):\n    # Pattern for number 1\n    if number == 1:\n        segments['b'].value(1)\n        segments['c'].value(1)\n    # Add more numbers...\n

"},{"location":"kits/led-displays/shift-register-displays/#4-digit-patterns","title":"4. Digit Patterns","text":"

self.SEGMENTS = {\n    'a': 4, 'b': 3, 'c': 2, \n    'd': 7, 'e': 6, 'f': 5, 'g': 1\n}\nself.DIGIT_SEGMENTS = {\n    0: 'abcdef', 1: 'bc', 2: 'abged', 3: 'abgcd',\n    4: 'fbcg', 5: 'afgcd', 6: 'afedcg', \n    7: 'abc', 8: 'abcdefg', 9: 'abfgcd'\n}\n
This section defines which segments should be lit for each number.

"},{"location":"kits/led-displays/shift-register-displays/#lab-4-pattern-design","title":"Lab 4: Pattern Design","text":"

Have students draw and design their own custom characters using the seven segments. They can create: - Letters (A, b, C, d, E, F) - Custom symbols - Animated patterns

"},{"location":"kits/led-displays/shift-register-displays/#5-time-display-logic","title":"5. Time Display Logic","text":"
def display_time(self, hour, minute, colon_state):\n    # Convert to 12-hour format\n    if hour > 12:\n        hour -= 12\n    elif hour == 0:  \n        hour = 12\n
"},{"location":"kits/led-displays/shift-register-displays/#lab-5-time-format-conversion","title":"Lab 5: Time Format Conversion","text":"

Have students write a program that converts between 24-hour and 12-hour time formats:

def convert_time(hour24):\n    if hour24 > 12:\n        return hour24 - 12, \"PM\"\n    elif hour24 == 0:\n        return 12, \"AM\"\n    elif hour24 == 12:\n        return 12, \"PM\"\n    else:\n        return hour24, \"AM\"\n
"},{"location":"kits/led-displays/shift-register-displays/#advanced-labs-and-extensions","title":"Advanced Labs and Extensions:","text":""},{"location":"kits/led-displays/shift-register-displays/#1-alarm-clock-lab","title":"1. Alarm Clock Lab","text":"

Modify the clock to add alarm functionality: - Add a button to set alarm time - Add a buzzer for the alarm - Implement snooze functionality

"},{"location":"kits/led-displays/shift-register-displays/#2-temperature-display-lab","title":"2. Temperature Display Lab","text":"

Alternate between showing time and temperature: - Add a temperature sensor - Display temperature for 3 seconds every minute - Add a button to toggle between time and temperature

"},{"location":"kits/led-displays/shift-register-displays/#3-custom-animation-lab","title":"3. Custom Animation Lab","text":"

Create animations for the display: - Make digits spin when changing - Create a \"snake\" animation for the top of each hour - Design transitions between numbers

"},{"location":"kits/led-displays/shift-register-displays/#4-world-clock-lab","title":"4. World Clock Lab","text":"

Modify the clock to show multiple time zones: - Add buttons to cycle through different time zones - Show timezone abbreviation - Store favorite time zones

"},{"location":"kits/led-displays/shift-register-displays/#5-stopwatch-lab","title":"5. Stopwatch Lab","text":"

Add stopwatch functionality: - Use buttons to start/stop/reset - Display tenths of seconds - Store lap times

"},{"location":"kits/led-displays/shift-register-displays/#summary","title":"Summary","text":"

These labs progressively build upon the base code while introducing new concepts and challenges. Each lab reinforces different programming concepts:

This allows students to learn both programming and hardware concepts in a hands-on, engaging way while creating something practical and visible.

"},{"location":"kits/lilygo/","title":"LILYGO T-Display RP2040 Kit","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/neopixel/","title":"NeoPixel LED Strip Clock Kits","text":"

In this section we will present three clocks that use a string of low-cost RGB LEDs commonly called NeoPixels. The name of this standard is technically called the WS2812B addressable LED standards. NeoPixels are frequently used in art-clock projects because they are:

  1. Easy to hook up - just three wires can drive up to 400 RGB LED
  2. Low cost - typically they sell for about $5/meter (60 pixels per meter) on eBay
  3. Available in many variations - they come is strips of various densities, rings, matrices, string and fairy light configurations

We have an entire on-line course on how to used these fun and colorful strips at our Moving Rainbow website. That site includes hundreds of step-by-step lesson plans and sample programs for kids ranging from age 8 to 18.

In this book we have several examples of clocks built with NeoPixels.

  1. Binary Clock This kit displays the time using binary numbers. It is a little hard to read at first, but these kits are extremely popular. Link to the Binary Clock Project. There are also several alternate layouts of how to display a binary clock. There are six different NeoPixel strips, each strip requires about five minutes of soldering, so the assembly takes about an hour. The total parts cost about $11.
  2. Seven Segment Clock The second requires building four seven-segment displays from the LED strip (or LED string) so the assembly time is significantly longer - about three hours if you are good with a soldering iron. Link to the Seven Segment Clock Project
  3. Fibonacci Clock This clock is unusual since it requires a bit of time to be able to read the time. However, it has what many consider the most elegant simple display. Link to the Fibonacci Clock Project.
"},{"location":"kits/neopixel/binary-clock/","title":"Binary Clock","text":"

The binary clock is a popular project where binary numbers are used to display the digits of a clock.

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

We can create a very simple binary clock using a single NeoPixel LED strip with just 12 pixels.

"},{"location":"kits/neopixel/binary-clock/#microsim","title":"MicroSim","text":"

You can see a simulation of the clock here:

Note that you can click the \"Show Text Time\" and \"Show Values\" to get a decoded human-understandable view of how to tell time with the clock.

"},{"location":"kits/neopixel/binary-clock/alternative-layouts/","title":"Alternative Binary Clock Layouts","text":"

The strip will have two rows of six pixels each:

  1. The first row will be the binary hour (0 to 24) (5 green pixels) and one pixel that flashes the second
  2. The second row will show the minutes (0 to 59) (6 blue pixels)
"},{"location":"kits/neopixel/binary-clock/alternative-layouts/#three-row-clock","title":"Three Row Clock","text":""},{"location":"kits/neopixel/binary-clock/alternative-layouts/#sample-code-for-three-rows-with-second-counter","title":"Sample Code for Three Rows with Second Counter","text":"
  1. Row 1 is five pixels with the hours (0-24)
  2. Row 2 is the minutes with the minutes (0-60)
  3. Row 3 is the seconds (0-60)
from machine import Pin\nfrom neopixel import NeoPixel\nfrom utime import sleep, localtime\n\nNEOPIXEL_PIN = 0\nNUMBER_PIXELS = 18\nstrip = NeoPixel(Pin(NEOPIXEL_PIN), NUMBER_PIXELS)\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 is halfed doing a divide by 2\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/neopixel/binary-clock/alternative-layouts/#references","title":"References","text":""},{"location":"kits/neopixel/binary-clock/assembly/","title":"Binary Clock Assembly","text":""},{"location":"kits/neopixel/binary-clock/assembly/#led-strip-layout","title":"LED Strip Layout","text":""},{"location":"kits/neopixel/binary-clock/code/","title":"Binary Clock Code","text":""},{"location":"kits/neopixel/binary-clock/code/#colum-configuration-values","title":"Colum Configuration Values","text":"
# Column configuration (start_index, height, color)\nCOLUMN_CONFIG = {\n    'hours_tens':   (18, 2, HOURS_COLOR),   # Column 1: 2 pixels (0-2)\n    'hours_ones':   (14, 4, HOURS_COLOR),   # Column 2: 4 pixels (0-9)\n    'minutes_tens': (11, 3, MINUTES_COLOR), # Column 3: 3 pixels (0-5)\n    'minutes_ones': (7, 4, MINUTES_COLOR),  # Column 4: 4 pixels (0-9)\n    'seconds_tens': (4, 3, SECONDS_COLOR),  # Column 5: 3 pixels (0-5)\n    'seconds_ones': (0, 4, SECONDS_COLOR),  # Column 6: 4 pixels (0-9)\n}\n
from machine import Pin\nfrom neopixel import NeoPixel\nfrom utime import localtime, sleep\n\n# Configuration\nNEOPIXEL_PIN = 0\nNUM_PIXELS = 20  # Total number of pixels (18 + 2)\n\n# Colors (RGB values) - using light/pastel versions\nHOURS_COLOR = (10, 50, 10)     # Light green\nMINUTES_COLOR = (10, 10, 60)   # Light cyan\nSECONDS_COLOR = (30, 40, 0)    # Light yellow\nOFF_COLOR = (0, 0, 0)          # Off\n\n# Column configuration (start_index, height, color)\nCOLUMN_CONFIG = {\n    'hours_tens':   (18, 2, HOURS_COLOR),   # Column 1: 2 pixels (0-2)\n    'hours_ones':   (14, 4, HOURS_COLOR),   # Column 2: 4 pixels (0-9)\n    'minutes_tens': (11, 3, MINUTES_COLOR), # Column 3: 3 pixels (0-5)\n    'minutes_ones': (7, 4, MINUTES_COLOR),  # Column 4: 4 pixels (0-9)\n    'seconds_tens': (4, 3, SECONDS_COLOR),  # Column 5: 3 pixels (0-5)\n    'seconds_ones': (0, 4, SECONDS_COLOR),  # Column 6: 4 pixels (0-9)\n}\n\n# Initialize NeoPixels\npixels = NeoPixel(Pin(NEOPIXEL_PIN), NUM_PIXELS)\n\ndef int_to_binary_column(number, num_bits):\n    \"\"\"Convert a number to binary and return list of bits.\"\"\"\n    binary = []\n    for i in range(num_bits):\n        binary.append(1 if number & (1 << i) else 0)\n    return binary  # LSB first\n\ndef set_column(start_index, height, color, number):\n    \"\"\"Set the LEDs for a specific column based on the number.\"\"\"\n    binary = int_to_binary_column(number, height)\n\n    # Set each LED in the column\n    for bit_pos in range(height):\n        pixel_index = start_index + bit_pos\n        pixels[pixel_index] = color if binary[bit_pos] else OFF_COLOR\n\ndef update_display(hours, minutes, seconds):\n    \"\"\"Update all columns with current time.\"\"\"\n    # Hours\n    set_column(*COLUMN_CONFIG['hours_tens'], hours // 10)\n    set_column(*COLUMN_CONFIG['hours_ones'], hours % 10)\n\n    # Minutes\n    set_column(*COLUMN_CONFIG['minutes_tens'], minutes // 10)\n    set_column(*COLUMN_CONFIG['minutes_ones'], minutes % 10)\n\n    # Seconds\n    set_column(*COLUMN_CONFIG['seconds_tens'], seconds // 10)\n    set_column(*COLUMN_CONFIG['seconds_ones'], seconds % 10)\n\n    pixels.write()  # Update the NeoPixels\n\ndef main():\n    print(\"Binary Clock Started\")\n    print(\"Columns from right to left:\")\n    print(\"1. Seconds ones (4 bits) - Light Yellow\")\n    print(\"2. Seconds tens (3 bits) - Light Yellow\")\n    print(\"3. Minutes ones (4 bits) - Light Cyan\")\n    print(\"4. Minutes tens (3 bits) - Light Cyan\")\n    print(\"5. Hours ones (4 bits) - Light Green\")\n    print(\"6. Hours tens (2 bits) - Light Green\")\n    print(\"LSB at bottom of each column\")\n\n    while True:\n        # Get current time - replace this line with the RTC call in the final version\n        t = localtime()\n        hours, minutes, seconds = t[3], t[4], t[5]\n\n        # Update the display\n        update_display(hours, minutes, seconds)\n\n        # Print current time for debugging\n        print(f\"{hours:02d}:{minutes:02d}:{seconds:02d}\")\n\n        # Wait before next update\n        sleep(1)\n\nif __name__ == \"__main__\":\n    main()\n
"},{"location":"kits/neopixel/binary-clock/prompt/","title":"Prompt","text":"

Prompt

I would like your help designing a binary clock using NeoPixels with MicroPython The clock will have six columns. Column 1 is for the hours tens digits. Column 2 is for the hours one digits. Column 3 is for the minutes tens digits Column 4 is for the minutes ones digits Column 5 is for the seconds tens digits Column 6 is for the seconds ones digits

Column 6 (seconds ones) starts a index=0 and is 4 pixels high Column 5 (seconds tens) starts index=4 and is 3 pixels high Column 4 (minutes ones) starts at index-7 and is 4 pixels high Column 3 (minutes tens) starts at index=11 and is 3 pixels high Column 2 (hours ones) starts at index=14 and is 4 pixels high Column 1 (hour tens) starts at index=18 and is 2 pixels high

Put the least significant bit at the bottom of the column. Please generate a MicroPython application that shows the time. Use the following preamble.

"},{"location":"kits/neopixel/binary-clock/prompt/#preamble","title":"Preamble","text":"
# Configuration\nNEOPIXEL_PIN = 0\nNUM_PIXELS = 20  # Total number of pixels (18 + 2)\n\n# Column configuration (start_index, height)\nCOLUMN_CONFIG = {\n    'hours_tens':   (18, 2),  # Column 1: 2 pixels (0-2)\n    'hours_ones':   (14, 4),  # Column 2: 4 pixels (0-9)\n    'minutes_tens': (11, 3),  # Column 3: 3 pixels (0-5)\n    'minutes_ones': (7, 4),   # Column 4: 4 pixels (0-9)\n    'seconds_tens': (4, 3),   # Column 5: 3 pixels (0-5)\n    'seconds_ones': (0, 4),   # Column 6: 4 pixels (0-9)\n}\n\n# Initialize NeoPixels\npixels = NeoPixel(Pin(NEOPIXEL_PIN), NUM_PIXELS)\n\n# Colors (RGB values)\nON_COLOR = (0, 20, 20)    # Cyan, dimmed for less power usage\nOFF_COLOR = (0, 0, 0)     # Off\n
"},{"location":"kits/neopixel/binary-clock/prompt/#full-program-generated","title":"Full Program Generated","text":"
from machine import Pin\nfrom neopixel import NeoPixel\nfrom utime import localtime, sleep\n\n# Configuration\nNEOPIXEL_PIN = 0\nNUM_PIXELS = 20  # Total number of pixels (18 + 2)\n\n# Column configuration (start_index, height)\nCOLUMN_CONFIG = {\n    'hours_tens':   (18, 2),  # Column 1: 2 pixels (0-2)\n    'hours_ones':   (14, 4),  # Column 2: 4 pixels (0-9)\n    'minutes_tens': (11, 3),  # Column 3: 3 pixels (0-5)\n    'minutes_ones': (7, 4),   # Column 4: 4 pixels (0-9)\n    'seconds_tens': (4, 3),   # Column 5: 3 pixels (0-5)\n    'seconds_ones': (0, 4),   # Column 6: 4 pixels (0-9)\n}\n\n# Initialize NeoPixels\npixels = NeoPixel(Pin(NEOPIXEL_PIN), NUM_PIXELS)\n\n# Colors (RGB values)\nON_COLOR = (0, 20, 20)    # Cyan, dimmed for less power usage\nOFF_COLOR = (0, 0, 0)     # Off\n\ndef int_to_binary_column(number, num_bits):\n    \"\"\"Convert a number to binary and return list of bits.\"\"\"\n    binary = []\n    for i in range(num_bits):\n        binary.append(1 if number & (1 << i) else 0)\n    return binary  # LSB first\n\ndef set_column(start_index, height, number):\n    \"\"\"Set the LEDs for a specific column based on the number.\"\"\"\n    binary = int_to_binary_column(number, height)\n\n    # Set each LED in the column\n    for bit_pos in range(height):\n        pixel_index = start_index + bit_pos\n        pixels[pixel_index] = ON_COLOR if binary[bit_pos] else OFF_COLOR\n\ndef update_display(hours, minutes, seconds):\n    \"\"\"Update all columns with current time.\"\"\"\n    # Hours\n    set_column(*COLUMN_CONFIG['hours_tens'], hours // 10)\n    set_column(*COLUMN_CONFIG['hours_ones'], hours % 10)\n\n    # Minutes\n    set_column(*COLUMN_CONFIG['minutes_tens'], minutes // 10)\n    set_column(*COLUMN_CONFIG['minutes_ones'], minutes % 10)\n\n    # Seconds\n    set_column(*COLUMN_CONFIG['seconds_tens'], seconds // 10)\n    set_column(*COLUMN_CONFIG['seconds_ones'], seconds % 10)\n\n    pixels.write()  # Update the NeoPixels\n\ndef main():\n    print(\"Binary Clock Started\")\n    print(\"Columns from right to left:\")\n    print(\"1. Seconds ones (4 bits)\")\n    print(\"2. Seconds tens (3 bits)\")\n    print(\"3. Minutes ones (4 bits)\")\n    print(\"4. Minutes tens (3 bits)\")\n    print(\"5. Hours ones (4 bits)\")\n    print(\"6. Hours tens (2 bits)\")\n    print(\"LSB at bottom of each column\")\n\n    while True:\n        # Get current time\n        t = localtime()\n        hours, minutes, seconds = t[3], t[4], t[5]\n\n        # Update the display\n        update_display(hours, minutes, seconds)\n\n        # Print current time for debugging\n        print(f\"{hours:02d}:{minutes:02d}:{seconds:02d}\")\n\n        # Wait before next update\n        sleep(1)\n\nif __name__ == \"__main__\":\n    main()\n
"},{"location":"kits/neopixel/fibonacci-clock/","title":"Fibonacci Clock","text":"

Assembly

Programming Code

Generative AI Prompt

"},{"location":"kits/neopixel/fibonacci-clock/assembly/","title":"Assembly","text":""},{"location":"kits/neopixel/fibonacci-clock/code/","title":"Code","text":"
# Fibonacci time function\ndef fib_time(hours, minutes):\n    vals = [1, 1, 2, 3, 5]\n    state = [0, 0, 0, 0, 0]\n\n    # Calculate Fibonacci representation for hours\n    remaining_hours = hours\n    idx = len(vals) - 1\n    for v in vals[::-1]:\n        if remaining_hours == 0 or idx < 0: break\n        if remaining_hours >= v:\n            state[idx] += 1\n            remaining_hours -= v\n        idx -= 1\n\n    # Calculate Fibonacci representation for minutes (in increments of 5)\n    remaining_minutes = math.floor(minutes / 5)\n    idx = len(vals) - 1\n    for v in vals[::-1]:\n        if remaining_minutes == 0 or idx < 0: break\n        if remaining_minutes >= v:\n            state[idx] += 2\n            remaining_minutes -= v\n        idx -= 1\n\n    return state\n
"},{"location":"kits/neopixel/seven-segment-clock/","title":"Neopixel 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.

"},{"location":"kits/neopixel/seven-segment-clock/#seven-segment-digits","title":"Seven Segment Digits","text":"

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/neopixel/seven-segment-clock/#digit-pixel-map","title":"Digit Pixel Map","text":"

Assume that a digit starts at pixel n and that each segment has three pixels. To turn on the segments, here is the mapping:

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
"},{"location":"kits/neopixel/seven-segment-clock/#clock-code","title":"Clock Code","text":"

Here is some working Python code that displays the time on the clock.

```python from machine import Pin from neopixel import NeoPixel from utime import sleep, localtime

NUMBER_PIXELS = 74 strip = NeoPixel(Pin(0), NUMBER_PIXELS)

"},{"location":"kits/neopixel/seven-segment-clock/#define-segment-patterns-for-digits-0-9","title":"Define segment patterns for digits 0-9","text":""},{"location":"kits/neopixel/seven-segment-clock/#segments-in-order-abcdefg","title":"Segments in order [a,b,c,d,e,f,g]:","text":""},{"location":"kits/neopixel/seven-segment-clock/#a-top-012","title":"a: top (0,1,2)","text":""},{"location":"kits/neopixel/seven-segment-clock/#b-top-right-345","title":"b: top right (3,4,5)","text":""},{"location":"kits/neopixel/seven-segment-clock/#c-bottom-right-678","title":"c: bottom right (6,7,8)","text":""},{"location":"kits/neopixel/seven-segment-clock/#d-bottom-91011","title":"d: bottom (9,10,11)","text":""},{"location":"kits/neopixel/seven-segment-clock/#e-bottom-left-121314","title":"e: bottom left (12,13,14)","text":""},{"location":"kits/neopixel/seven-segment-clock/#f-upper-left-151617","title":"f: upper left (15,16,17)","text":""},{"location":"kits/neopixel/seven-segment-clock/#g-middle-181920","title":"g: middle (18,19,20)","text":"

DIGITS = { # a,b,c,d,e,f,g 0: [1,1,1,1,1,1,0], # all but middle 1: [0,1,1,0,0,0,0], # right side only 2: [1,1,0,1,1,0,1], # all but bottom right and upper left 3: [1,1,1,1,0,0,1], # all but left side 4: [0,1,1,0,0,1,1], # both right, upper left, and middle 5: [1,0,1,1,0,1,1], # all but top right and bottom left 6: [1,0,1,1,1,1,1], # all but top right 7: [1,1,1,0,0,0,0], # top and both right segments 8: [1,1,1,1,1,1,1], # all segments 9: [1,1,1,1,0,1,1] # all but bottom left }

"},{"location":"kits/neopixel/seven-segment-clock/#color-definitions-rgb","title":"Color definitions (RGB)","text":"

ON_COLOR = (10, 10, 10) # Dim white OFF_COLOR = (0, 0, 0) # Off

def set_segment_pixels(start_idx, segment_pattern): \"\"\"Set three pixels for a segment based on pattern\"\"\" for i in range(3): strip[start_idx + i] = ON_COLOR if segment_pattern else OFF_COLOR

def display_digit(digit, start_pixel): \"\"\"Display a single digit starting at the specified pixel\"\"\" if digit not in DIGITS: return

pattern = DIGITS[digit]\n# Map the array positions to the physical segments in correct order\nsegments = [\n    start_pixel + 0,   # a: top (0,1,2)\n    start_pixel + 3,   # b: top right (3,4,5)\n    start_pixel + 6,   # c: bottom right (6,7,8)\n    start_pixel + 9,   # d: bottom (9,10,11)\n    start_pixel + 12,  # e: bottom left (12,13,14)\n    start_pixel + 15,  # f: upper left (15,16,17)\n    start_pixel + 18   # g: middle (18,19,20)\n]\n\n# Set pixels for each segment based on pattern\nfor segment_index, segment_on in enumerate(pattern):\n    set_segment_pixels(segments[segment_index], segment_on)\n

def set_colon(on=True): \"\"\"Set colon pixels (42 and 43)\"\"\" color = ON_COLOR if on else OFF_COLOR strip[42] = color strip[43] = color

def display_time(hour, minute, colon_on): \"\"\"Display time on the LED strip\"\"\" # Convert 24-hour to 12-hour format hour = hour if hour <= 12 else hour - 12 if hour == 0: hour = 12

# Set hours \"1\" digit if needed (pixels 65-73)\nif hour >= 10:\n    for i in range(65, 74):\n        strip[i] = ON_COLOR\nelse:\n    for i in range(65, 74):\n        strip[i] = OFF_COLOR\n\n# Display ones digit of hours (pixels 44-64)\ndisplay_digit(hour % 10, 44)\n\n# Display tens digit of minutes (pixels 21-41)\ndisplay_digit(minute // 10, 21)\n\n# Display ones digit of minutes (pixels 0-20)\ndisplay_digit(minute % 10, 0)\n\n# Set colon (pixels 42-43)\nset_colon(colon_on)\n\nstrip.write()\n

def clear_display(): \"\"\"Turn off all pixels\"\"\" for i in range(NUMBER_PIXELS): strip[i] = OFF_COLOR strip.write()

"},{"location":"kits/neopixel/seven-segment-clock/#main-loop","title":"Main loop","text":"

colon_state = True clear_display()

while True: current_time = localtime() hour = current_time[3] minute = current_time[4] if hour > 12: display_hour = hour - 12

display_time(hour, minute, colon_state)\ncolon_state = not colon_state\nprint(f'{display_hour}:{minute}')\nsleep(1)\n

```

7-Segment Display Demo with P5.js

Moving Rainbow Arduino Sign

"},{"location":"kits/neopixel/seven-segment-clock/03-light-diffusion/","title":"NeoPixel Light Diffusion","text":"

To make a nice clock we need to design an effective diffusion system for NeoPixels that balances visibility and brightness.

For optimal diffusion while maintaining brightness, we recommend a multi-layer approach:

  1. Primary diffusion layer: Use a thin (1-3mm) sheet of white acrylic with a light frost or matte finish, positioned about 5-10mm away from the NeoPixels. This distance allows enough space for the light to blend while keeping individual pixels distinguishable.

  2. Air gap: The space between the NeoPixels and the diffusion layer is crucial. Too close and you'll see harsh spots; too far and you'll lose brightness. Start with 8mm and adjust based on your needs.

  3. Optional secondary diffusion: If you need more smoothing, add a very thin (0.5-1mm) layer of standard white diffusion film about 2-3mm in front of the primary layer.

Some specific materials to consider:

To maximize brightness while maintaining the pixel effect, you can also:

https://www.youtube.com/watch?v=Y2tWdKKYAak

"},{"location":"kits/neopixel/seven-segment-clock/assembly/","title":"Assembling Your Seven Segment NeoPixel Clock","text":""},{"location":"kits/neopixel/seven-segment-clock/assembly/#time-estimate","title":"Time Estimate","text":"

Total = 476 + 8 = 168 + 8 = 174 solder connections

Per connection (average times):

Total: 260 minutes or 4.3 hours

Total: 180

"},{"location":"kits/neopixel/seven-segment-clock/code/","title":"Code for the NeoPixel Seven Segment Clock","text":""},{"location":"kits/neopixel/seven-segment-clock/prompt/","title":"Sample Prompt to Generate NeoPixel Clock Code","text":""},{"location":"kits/neopixel/seven-segment-clock/prompt/#sample-prompt","title":"Sample Prompt","text":"

Prompt

We created a clock display using a 74 pixel WS2811B LED strip. The clock has three full digits and a \"1\" for the tens digit as well as two pixels for the colon. Each digit has seven segments with three pixels per segment. The right-most ones digit minutes digit starts at 0 and goes to 20. The second digit for the tens of minutes goes from 21 to 41. The colons are 42 and 43. The third digit is for the one hours and goes from 44 to 64. The hours ten single 1 goes from 65 to 73. Within a digit, here are the segment pixels Segment a on the top are pixels (0,1,2). Segment b on the upper right are pixels (3,4,5) Segment c on the lower right are pixels (6,7,8) Segment d on the bottom are pixels (9,10,11) Segment e on the lower left are pixels (12, 13, 14) Segment f on the upper left are pixels (15, 16, 17) Segment g in the middle are pixels (18,19 and 20)

Please write a clock program in MicroPython that displays the hours and minutes of localtime() function but the hours range from 1 to 12. Make the colon turn on and off every second.

Here is the preamble

from machine import Pin from neopixel import NeoPixel from utime import sleep

NUMBER_PIXELS = 74 strip = NeoPixel(Pin(0), NUMBER_PIXELS)

"},{"location":"kits/neopixel/seven-segment-clock/prompt/#result","title":"Result","text":"
from machine import Pin\nfrom neopixel import NeoPixel\nfrom utime import sleep, localtime\n\nNUMBER_PIXELS = 74\nstrip = NeoPixel(Pin(0), NUMBER_PIXELS)\n\n# Define segment patterns for digits 0-9\n# Segments in order [a,b,c,d,e,f,g]:\n# a: top (0,1,2)\n# b: top right (3,4,5)\n# c: bottom right (6,7,8)\n# d: bottom (9,10,11)\n# e: bottom left (12,13,14)\n# f: upper left (15,16,17)\n# g: middle (18,19,20)\nDIGITS = {\n    #    a,b,c,d,e,f,g\n    0: [1,1,1,1,1,1,0],  # all but middle\n    1: [0,1,1,0,0,0,0],  # right side only\n    2: [1,1,0,1,1,0,1],  # all but bottom right and upper left\n    3: [1,1,1,1,0,0,1],  # all but left side\n    4: [0,1,1,0,0,1,1],  # both right, upper left, and middle\n    5: [1,0,1,1,0,1,1],  # all but top right and bottom left\n    6: [1,0,1,1,1,1,1],  # all but top right\n    7: [1,1,1,0,0,0,0],  # top and both right segments\n    8: [1,1,1,1,1,1,1],  # all segments\n    9: [1,1,1,1,0,1,1]   # all but bottom left\n}\n\n# Color definitions (RGB)\nON_COLOR = (10, 10, 10)  # Dim white\nOFF_COLOR = (0, 0, 0)    # Off\n\ndef set_segment_pixels(start_idx, segment_pattern):\n    \"\"\"Set three pixels for a segment based on pattern\"\"\"\n    for i in range(3):\n        strip[start_idx + i] = ON_COLOR if segment_pattern else OFF_COLOR\n\ndef display_digit(digit, start_pixel):\n    \"\"\"Display a single digit starting at the specified pixel\"\"\"\n    if digit not in DIGITS:\n        return\n\n    pattern = DIGITS[digit]\n    # Map the array positions to the physical segments in correct order\n    segments = [\n        start_pixel + 0,   # a: top (0,1,2)\n        start_pixel + 3,   # b: top right (3,4,5)\n        start_pixel + 6,   # c: bottom right (6,7,8)\n        start_pixel + 9,   # d: bottom (9,10,11)\n        start_pixel + 12,  # e: bottom left (12,13,14)\n        start_pixel + 15,  # f: upper left (15,16,17)\n        start_pixel + 18   # g: middle (18,19,20)\n    ]\n\n    # Set pixels for each segment based on pattern\n    for segment_index, segment_on in enumerate(pattern):\n        set_segment_pixels(segments[segment_index], segment_on)\n\n    # Set pixels for each segment based on pattern\n    for segment_index, segment_on in enumerate(pattern):\n        set_segment_pixels(segments[segment_index], segment_on)\n\ndef set_colon(on=True):\n    \"\"\"Set colon pixels (42 and 43)\"\"\"\n    color = ON_COLOR if on else OFF_COLOR\n    strip[42] = color\n    strip[43] = color\n\ndef display_time(hour, minute, colon_on):\n    \"\"\"Display time on the LED strip\"\"\"\n    # Convert 24-hour to 12-hour format\n    hour = hour if hour <= 12 else hour - 12\n    if hour == 0:\n        hour = 12\n\n    # Set hours \"1\" digit if needed (pixels 65-73)\n    if hour >= 10:\n        for i in range(65, 74):\n            strip[i] = ON_COLOR\n    else:\n        for i in range(65, 74):\n            strip[i] = OFF_COLOR\n\n    # Display ones digit of hours (pixels 44-64)\n    display_digit(hour % 10, 44)\n\n    # Display tens digit of minutes (pixels 21-41)\n    display_digit(minute // 10, 21)\n\n    # Display ones digit of minutes (pixels 0-20)\n    display_digit(minute % 10, 0)\n\n    # Set colon (pixels 42-43)\n    set_colon(colon_on)\n\n    strip.write()\n\ndef clear_display():\n    \"\"\"Turn off all pixels\"\"\"\n    for i in range(NUMBER_PIXELS):\n        strip[i] = OFF_COLOR\n    strip.write()\n\n# Main loop\ncolon_state = True\nclear_display()\n\nwhile True:\n    current_time = localtime()\n    hour = current_time[3]\n    minute = current_time[4]\n    if hour > 12:\n        display_hour = hour - 12   \n    display_time(hour, minute, colon_state)\n    colon_state = not colon_state\n    print(f'{display_hour}:{minute}')\n    sleep(1)\n
"},{"location":"kits/oled-large/","title":"Large OLED Kit","text":"

The Large OLED Kit is one of our most versatile kits.

It uses a bright monochrome 128x64 SSD1306 OLED with the Raspberry Pi Pico W and a DS3231 real-time clock to keep time accurate to within 2 seconds per month.

The OLEDs are mounted on a piece of acrylic (Plexiglass) with a half-size breadboard.

Each board has three buttons so you can adjust the time and alarm clock settings manually if you are not getting the time from your WiFi network.

"},{"location":"kits/oled-large/#component-list","title":"Component List","text":""},{"location":"kits/oled-large/#kit-assembly","title":"Kit Assembly","text":"

If you have received a clock kit you can proceed directly to the Assembly Instructions.

Once your kit is assembled you can the start to program your clock.

"},{"location":"kits/oled-large/#programming","title":"Programming","text":""},{"location":"kits/oled-large/#step-1-setting-up-your-desktop-and-loading-micropython","title":"Step 1: Setting up your Desktop and Loading MicroPython","text":"

We cover the desktop setup and loading MicroPython in our Desktop Setup

"},{"location":"kits/oled-large/#blink-the-builtin-led","title":"Blink the Builtin LED","text":"

Go to the Blink Lab

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

Your first program is our \"Hello World!\" program for this kit. This is a great way to tell if your display connections are all working. We have one version that prints to the Thonny console and one version that prints to the OLED display.

Go to the Hello World! Program

"},{"location":"kits/oled-large/#display-config","title":"Display Config","text":"

Most of our sample programs read the hardware settings from a configuration file called config.py. This program shows you how to show the configuration parameters on the display.

Go to the Display Config Lab

"},{"location":"kits/oled-large/#display-raw-localtime","title":"Display Raw Localtime","text":"

Date and time information is stored in a set of 6-8 integers. This first program just shows the raw integers on the display from the localtime() function. Not very pretty, but the program is pretty short and simple.

Go to the Display Localtime Raw

"},{"location":"kits/oled-large/#set-the-rtc-from-localtime","title":"Set the RTC from Localtime","text":"

Once you add your RTC to the kit you will need to set the time inside the RTC memory. The best way to do this is to just grab the time from the MicroPython localtime() which is set from your local computer when you connect your Pico using Thonny.

Set the RTC from Localtime

After you run this program your clock will be accurate to within two seconds per month. The backup lithium battery will remember the time even when the clock is not plugged in or there is a power outage. The battery will last about 8 years.

"},{"location":"kits/oled-large/#display-single-large-digit","title":"Display Single Large Digit","text":"

Display Single Digit

"},{"location":"kits/oled-large/00-blink/","title":"Blink The Onboard LED","text":"

Our first program will just blink the on-board LED on the Raspberry Pi Pico. It is a great way to tell if you got Thonny installed correctly and that your computer can connect to the Raspberry Pi Pico. Use the \"copy\" icon in the upper right corner to copy the code below into your pastebuffer. Then open Thonny and paste it into a new file in your Thonny editor (or other Python programming tool). Then press the \"Run\" button. You should see the small green LED on the Pico flashing on and off every second.

# Setup - run once\nfrom machine import Pin # Get the Pin function from the machine module.\nfrom time import sleep # Get the sleep library from the time module.\n\n# This is the built-in green LED on the standard Pico.\n# BUILT_IN_LED_PIN = 25\n# On the Pico \"W\" we use the symbolic \"name\" of \"LED\", not a pin number\nBUILT_IN_LED_PIN = Pin(\"LED\", Pin.OUT)\n\n# The line below indicates we are configuring this as an output (not input)\nled = Pin(BUILT_IN_LED_PIN, Pin.OUT)\n\n# Main loop: Repeat the forever...\nwhile True:\n    led.high() # turn on the LED\n    sleep(0.5) # leave it on for 1/2 second\n    led.low()  # Turn off the LED\n    sleep(0.5) # leave it off for 1/2 second\n

When you run this program, the built-in LED on the Pico will blink every second.

Challenges

  1. Change the delay time to be 0.25 seconds on and 0.25 second off.
  2. How fast can you make the LED blink?
  3. Go to the original MicroPython for Kids website and search for the term \"blink\" in the search form in the upper right. What variations of blink are there in other kits?
  4. If you have any LEDs and 330 ohm resistors, try adding them to the breadboard and getting the external LED to blink. See this lab for sample code.
"},{"location":"kits/oled-large/00-blink/#debugging-tips","title":"Debugging Tips.","text":"

If you are not getting any response when you click the \"Run\" button, try checking that your desktop computer is communicating with the Raspberry Pi Pico through the USB cable.

See the Setup Desktop Debugging Tips page for tips on how to make sure your connection is working.

"},{"location":"kits/oled-large/01-assembly/","title":"Large OLED Assembly","text":"

Here are your assembly instructions for the Large OLED clock kit. This kit comes with a Raspberry Pi W, a nice large 2.24\" OLED display and a DS3132 real-time clock. You can also optionally mount a speaker for an alarm clock. Time can be set using three buttons (mode-set, increment (+) and decrement (-))

"},{"location":"kits/oled-large/01-assembly/#kit-parts-list","title":"Kit Parts List","text":"
  1. Plexiglass base with holes in front
  2. 4 felt pads for the feet
  3. Breadboard - 1/2 size - 30 rows - 400 ties
  4. Raspberry Pi Pico W with header pins installed
  5. OLED display (2.42\" 128X64)
  6. 7 wire display cable (20cm M-F)
  7. Real time clock (DS3132)
  8. CR2032 coin-cell battery
  9. 4-wire real-time clock cable (20cm M-F)
  10. 3 momentary push buttons - long for set, short for +/-
  11. 5 black ground wires for buttons
  12. 3 button wires (yellow, blue and green)
  13. 1 red power jumper (3.3 to bus)
  14. 3 cable ties
  15. USB connector (A to micro)
  16. 5-volt USB power adapter
"},{"location":"kits/oled-large/01-assembly/#assembly-steps","title":"Assembly Steps","text":""},{"location":"kits/oled-large/01-assembly/#prepare-plexiglass-base","title":"Prepare Plexiglass Base","text":"

Peal the protective backing off of both sides of the plexiglass base. You might need to use your fingernails or a knife to peal up the corner.

"},{"location":"kits/oled-large/01-assembly/#attach-felt-pads-to-base","title":"Attach Felt Pads to Base","text":"

Attach the 4 felt pads to the corners of the base by pealing the release liner off each pad and placing each one in each corner. If you are using cable ties on the display, make sure you leave room for the cable ties and positions the felt pads in about 1/4 of an inch from the side edges.

This will be the bottom of your clock.

"},{"location":"kits/oled-large/01-assembly/#attach-breadboard-to-base","title":"Attach Breadboard to Base","text":"

Peal off the release liner for the solderless 1/2 breadboard and place it in the center of the top base with row 1 on the right and row 30 on the left as you face from the front of the clock. Note that the top of the breadboard is row 1 and the bottom is row 30.

"},{"location":"kits/oled-large/01-assembly/#install-pico-w-on-breadboard","title":"Install Pico W on Breadboard","text":"

Put the Raspberry Pi Pico W into the breadboard making sure that USB connector is at the top of the breadboard (row 1) and the pins are aligned like the diagram. The header pins on the Raspberry Pi Pico W will be in columns \"c\" and \"h\".

"},{"location":"kits/oled-large/01-assembly/#33-v-red-bus-jumper","title":"3.3 V Red Bus Jumper","text":"

Use the red jumper wire to connect right row 5 (3.3 OUT) to the red power bus on the right. Both the OLED display and the real-time clock will connect their power to this right power rail.

"},{"location":"kits/oled-large/01-assembly/#black-ground-jumper","title":"Black Ground Jumper","text":"

Attach the black jumper wire from row 8 on the right to the blue bus GND. Note all rows that end in the digit 3 or 8 are GND (3,8,13,18). Your breadboard may have a black mark on the breadboard on these rows. This connection allows the entire left power rail to be GND.

"},{"location":"kits/oled-large/01-assembly/#add-buttons","title":"Add Buttons","text":"

Place the three buttons on the breadboard over the center trough oriented so that the legs of the buttons are on opposite sides of the center trough and closing the switch will close the connection between the rows of the breadboard when pressed.

The internal connections of the buttons is shown below:

"},{"location":"kits/oled-large/01-assembly/#wire-buttons","title":"Wire Buttons","text":"

We are now ready to wire the three buttons up. Their job is to connect the pins on the lower left corner of the Pico W to GND when they are pressed. Each button has GND on the lower-left pin and signal on the upper-left corner of the button. The signal of the top button will go to row 17, the middle button to row 19 and the bottom button will go to row 20.

A logical diagram of the button circuit is shown below:

Here are the pin assignments will use in later labs:

Breadboard Row GPIO Pin Function Row 17 GPIO 13 Set (Mode) Row 19 GPIO 14 Increment Row 20 GPIO 15 Decrement

Note

If you have your software loaded, you can run the 'buttons-test-print.py' to verify that the buttons are all connected correctly. This program will print the name of the button being pressed to the console. This is a great way to tell if you have missed any button connections.

"},{"location":"kits/oled-large/01-assembly/#display-cable-to-oled","title":"Display Cable to OLED","text":"

Locate the 7-wire display cable harness. It has female pins on one end and make pins on the other end. The female pins will go into the OLED display. Note that you must hoop up the black wire to the GND connection and the purple wire to the CS connection. It is very easy to get this connection reversed, so double check the orientation.

"},{"location":"kits/oled-large/01-assembly/#display-cable-to-breadboard","title":"Display Cable to Breadboard","text":"

Attached the display cable to the breadboard in the left side from rows 3 to 9. The orange wire should be in row 4, yellow in 5, green in 6, blue in 7, black in 8 and purple in 9.

Next, connect the red wire to the right side red power rail (3.3 volts)

Double check that the black wire is on the GND pin (row 8). Make sure that the red wire is attached to right red positive power rail.

"},{"location":"kits/oled-large/01-assembly/#install-the-coin-cell-battery-in-the-rtc","title":"Install the Coin Cell Battery in the RTC","text":"

Place the CR2032 coin cell battery in the real-time clock (RTC) making sure the positive side is on the top. The coin cell battery needs to be tucked under the metal connector and pressed down. You may need a small screw driver to pry the batter back up to change it. Most

"},{"location":"kits/oled-large/01-assembly/#rtc-cable-to-rtc","title":"RTC Cable to RTC","text":"

Attach the 4-wire cable to there real time clock making sure that the black GND and the red VCC are connect to the correct pins, SDA is yellow and SCL is orange. The SQW and 32K pins are not used.

"},{"location":"kits/oled-large/01-assembly/#rtc-cable-to-breadboard","title":"RTC Cable to Breadboard","text":"

Attach the other of the 4-wire real-time clock cable to the breadboard. Connect yellow to row 1 on the left (GPIO 0) and orange on row 2 on the left (GPIO 1). Connect the red wire to the red power rail on the right and the black wire to the GND on the right.

"},{"location":"kits/oled-large/01-assembly/#cable-ties","title":"Cable Ties","text":"

(Optional) You can use cable ties or twist ties to connect the display and the RTC to the base through the holes. If you are planning to build your own case you can skip this step.

"},{"location":"kits/oled-large/01-assembly/#final-tests","title":"Final Tests","text":"

After you assemble your clock kit you will be abel to test the connections using MicroPython. Here are some of the tests you will run:

  1. Use the blink to verify that your USB connection is not blocked by your PC/Mac and you have downloaded the correct version of the MicroPython runtime.

  2. Use the i2c scanner to verify that your real-time clock is connected correctly.

  3. Use the hello world program to test if your display is connected correctly.

  4. Use the button test program to test to see if your buttons are connected correctly.

"},{"location":"kits/oled-large/01-assembly/#troubleshooting","title":"Troubleshooting","text":"

You can use generative AI tool like ChatGPT or Anthropic Claude to help you debug the clock. Make sure you give a complete description of your problem using the following prompt template.

Prompt

Please help me diagnose a problem I am having with MicroPython program running on the Raspberry Pi Pico W. I am using a Mac to connect to the Pico with Thonny. I am using Thonny version 4.1.6 and Mac OS 15.0.1. When I run ls /dev/cu* I can see the device listed. When I press the Stop/Restart backend button I see the MicroPython version X on the console.

However, when I run X I get the following error.

"},{"location":"kits/oled-large/01-hello-world/","title":"Hello World","text":""},{"location":"kits/oled-large/01-hello-world/#displaying-hello-world-on-your-console","title":"Displaying \"Hello World!\" on your console","text":"

Here is the classic \"Hello World\" in MicroPython

print(\"Hello World!\")\n

You can type this program into Thonny and run it to see if your USB is connected and the MicroPython runtime is loaded correctly.

"},{"location":"kits/oled-large/01-hello-world/#displaying-hello-word-on-your-display","title":"Displaying Hello Word On your Display","text":"

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

Here is the code:

import machine\nimport ssd1306\n\n# customise these numbers if your hardware config is different\nSCL=machine.Pin(2) # SPI CLock\nSDA=machine.Pin(3) # SPI Data\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 (0=black)\noled.fill(0)\n\n# place a hello message at point (0,0) in white (1=white text)\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/oled-large/02-display-config/","title":"Display Configuration File Data","text":"

This program will display the configuration file (config.py) parameters on your display.

from machine import Pin\nimport ssd1306\nimport config\n\nSCL=Pin(config.SPI_SCL_PIN)\nSDA=Pin(config.SPI_SDA_PIN)\nDC = Pin(config.SPI_DC_PIN)\nRES = Pin(config.SPI_RESET_PIN)\nCS = Pin(config.SPI_CS_PIN)\nSPI_BUS = config.SPI_BUS\nWIDTH = config.DISPLAY_WIDTH\nHEIGHT = config.DISPLAY_HEIGHT\n\nspi=machine.SPI(SPI_BUS, sck=SCL, mosi=SDA, baudrate=1000000)\noled = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)\n\n# place a hello message at point (0,0) in white\noled.text(\"Config:\", 0, 0, 1)\n\n# row 2\noled.text(\"SCL:\" + str(config.SPI_SCL_PIN), 0, 10, 1)\noled.text(\"SDA:\" + str(config.SPI_SDA_PIN), 50, 10, 1)\n\n# row 3\noled.text(\"DC:\" + str(config.SPI_DC_PIN),     0, 20, 1)\noled.text(\"RES:\" + str(config.SPI_RESET_PIN), 50, 20, 1)\n\n# row 4\noled.text(\"CS:\" + str(config.SPI_CS_PIN), 0, 30, 1)\noled.text(\"SPI BUS:\" + str(config.SPI_BUS), 50, 30, 1)\n\n# row 5\noled.text(\"Width:\" + str(config.DISPLAY_WIDTH), 0, 40, 1)\noled.text(\"Height:\" + str(config.DISPLAY_HEIGHT), 0, 50, 1)\n\noled.show()\n
"},{"location":"kits/oled-large/02-i2c-scanner/","title":"I2C Scanner Test","text":"

This program will verify that your RTC clock is connected correctly to your Raspberry Pi Pico.

from machine import I2C, Pin\nimport config\n\nDST_PIN = 0\nCLK_PIN = 1\n\nprint(\"Data DST on pin: \", DST_PIN)\nprint(\"Clock CLK on pin: \", CLK_PIN)\n\ni2c = I2C(0, scl=Pin(CLK_PIN), sda=Pin(DST_PIN), freq=800000)\n\nscan_result = i2c.scan()\n\nprint(scan_result)\n\nprint(\"I2C addresses found:\", [hex(device_address) for device_address in i2c.scan()])\n

This will return a list of the I2C addresses found on your I2C Bus 0. If your DS3231 real-time clock has an address of 0x68 (hex) which is 104 decimal.

You will also see a second I2C address for the EEPROM which is 0x50 (hex) and 80 decimal. These numbers may vary if you purchased different versions of a real-time clock.

For details see the section on Real Time Clocks.

"},{"location":"kits/oled-large/02-set-rtc-from-localtime/","title":"Set RTC form the localtime","text":"

When you plug your Pico into your PC and use the Thonny IDE, the MicroPython runtime will startup and get the time from your desktop or laptop computer's clock and set the MicroPython runtimes localtime() with this time.

Note

You can disable the Thonny localtime if you want to test the RTC functions.

"},{"location":"kits/oled-large/02-set-rtc-from-localtime/#setting-the-rtc-using-the-ds3231","title":"Setting The RTC using the DS3231","text":"

This is a full raw way to set the DS3231 RTC from the MicroPython localtime. You should only need to run this program when you first connect your RTC chip. After you run it the clock should be accurate within 2-seconds per month. In many cases when the clock is at room temperature the accuracy will be higher.

from machine import Pin, I2C\nfrom utime import localtime\n\n# Constants\nDS3231_ADDR = 0x68\nSTATUS_REG = 0x0F  # Status register address\n\n# I2C setup\nsda = Pin(0)\nscl = Pin(1)\ni2c = I2C(0, scl=scl, sda=sda, freq=100000)\n\ndef dec2bcd(dec):\n    \"\"\"Convert decimal to binary coded decimal.\"\"\"\n    return (dec // 10) << 4 | (dec % 10)\n\ndef check_osf():\n    \"\"\"Check the oscillator stop flag.\"\"\"\n    status = i2c.readfrom_mem(DS3231_ADDR, STATUS_REG, 1)[0]\n    return bool(status >> 7)\n\ndef reset_osf():\n    \"\"\"Clear the oscillator stop flag.\"\"\"\n    status = bytearray(1)\n    i2c.readfrom_mem_into(DS3231_ADDR, STATUS_REG, status)\n    i2c.writeto_mem(DS3231_ADDR, STATUS_REG, bytearray([status[0] & 0x7f]))\n\ndef set_ds3231():\n    \"\"\"Set the DS3231 RTC time and ensure oscillator is running.\"\"\"\n    now = localtime()\n    year = now[0] % 100  # Convert to 2-digit year\n    month = now[1]\n    day = now[2]\n    hour = now[3]\n    minute = now[4]\n    second = now[5]\n\n    # First check if oscillator is stopped\n    if check_osf():\n        print(\"Oscillator was stopped. Resetting OSF flag...\")\n        reset_osf()\n\n    data = bytearray([\n        dec2bcd(second),\n        dec2bcd(minute),\n        dec2bcd(hour),\n        dec2bcd(now[6] + 1),  # Convert weekday from 0-6 to 1-7\n        dec2bcd(day),\n        dec2bcd(month),\n        dec2bcd(year)\n    ])\n\n    i2c.writeto_mem(DS3231_ADDR, 0x00, data)\n    print(f\"RTC set to: {month}/{day}/{now[0]} {hour:02d}:{minute:02d}:{second:02d}\")\n\n    # Verify oscillator is running\n    if check_osf():\n        print(\"Warning: Oscillator still shows stopped state!\")\n    else:\n        print(\"Oscillator running normally\")\n\nif __name__ == \"__main__\":\n    set_ds3231()\n
"},{"location":"kits/oled-large/02-set-rtc-from-localtime/#setting-the-rtc-using-the-ds1307","title":"Setting the RTC using the DS1307","text":"
# set the localtime from the DS1307 RTC\nfrom machine import Pin, I2C\nfrom utime import localtime\nfrom ds1307 import DS1307\nimport config\n\nsda = Pin(config.I2C_SDA_PIN)\nscl = Pin(config.I2C_SCL_PIN)\nI2C_BUS = config.I2C_BUS\nRTC_TYPE = config.RTC_TYPE\nRTC_I2C_ADDR = config.RTC_I2C_ADDR\n\n# I2C setup\ni2c = I2C(I2C_BUS, scl=scl, sda=sda, freq=3000000)\nprint(i2c.scan())\nrtc = DS1307(addr=RTC_I2C_ADDR, i2c=i2c)\nprint(\"DS1307 is on I2C address 0x{0:02x}\".format(rtc.addr))\nprint(\"Before setting the time the RTC clock had: \", rtc.datetime)\nprint(\"Localtime: \", localtime())\n\n# Set the local time\nrtc.datetime = localtime()\n\nprint(\"After setting the time from local time the RTC had: \", rtc.datetime)\n\n# Print the date and time in ISO8601 format: 2023-04-18T21:14:22\nprint(\"Today is {:04d}-{:02d}-{:02d}T{:02d}:{:02d}:{:02d} weekday:{}\".format(\n    rtc.year, rtc.month, rtc.day,\n    rtc.hour, rtc.minute, rtc.second, rtc.weekday))\n
"},{"location":"kits/oled-large/03-display-localtime-raw/","title":"Display the Raw Localtime","text":"
from machine import Pin, SPI\nimport ssd1306\nfrom time import localtime, sleep\nimport config\n\n# get the configuration data\nSCL=Pin(config.SPI_SCL_PIN)\nSDA=Pin(config.SPI_SDA_PIN)\nDC = Pin(config.SPI_DC_PIN)\nRES = Pin(config.SPI_RESET_PIN)\nCS = Pin(config.SPI_CS_PIN)\nSPI_BUS = config.SPI_BUS\nWIDTH = config.DISPLAY_WIDTH\nHEIGHT = config.DISPLAY_HEIGHT\n\nspi=machine.SPI(SPI_BUS, sck=SCL, mosi=SDA, baudrate=1000000)\noled = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)\n\nwhile True:\n    # display the raw data from localtime\n    oled.fill(0)\n    oled.text(str(localtime()[0]), 0, 0, 1)\n    oled.text(str(localtime()[1]), 0, 10, 1)\n    oled.text(str(localtime()[2]), 0, 20, 1)\n    oled.text(str(localtime()[3]), 0, 30, 1)\n    oled.text(str(localtime()[4]), 0, 40, 1)\n    oled.text(str(localtime()[5]), 0, 50, 1)\n    oled.show()\n    sleep(1)\n

Challenges

  1. What numbers are fixed and what numbers arc chainging?
  2. What do you think each of the numbers mean?
  3. Why is it hard to read the time using this \"raw\" unformatted way?
"},{"location":"kits/oled-large/04-display-localtime-formatted/","title":"Display Local Time Formatted","text":"

That last program did display the correct local time, but it was hard to read.

In this version we will extract data time values, and then display a formatted version of both the date and the time.

from machine import Pin, SPI\nimport ssd1306\nfrom time import localtime\nimport config\n\nSCL=Pin(config.SPI_SCL_PIN)\nSDA=Pin(config.SPI_SDA_PIN)\nDC = Pin(config.SPI_DC_PIN)\nRES = Pin(config.SPI_RESET_PIN)\nCS = Pin(config.SPI_CS_PIN)\nSPI_BUS = config.SPI_BUS\nWIDTH = config.DISPLAY_WIDTH\nHEIGHT = config.DISPLAY_HEIGHT\n\nspi=machine.SPI(SPI_BUS, sck=SCL, mosi=SDA, baudrate=1000000)\noled = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)\n\nwhile True:\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 in 12 hour time paying special attention with hour == 0 and hour == 12\n    if hour == 0:\n        # Midnight edge case\n        hour = 12\n        am_pm = 'AM'\n    elif hour == 12:\n        # Noon edge case\n        am_pm = 'PM'\n    elif hour > 12:\n        # Afternoon hours\n        hour -= 12\n        am_pm = 'PM'\n    else:\n        # Morning hours\n        am_pm = 'AM'\n\n    oled.fill(0)\n    # display the date on the first line\n    oled.text(str(month) + \"/\" + str(day) + \"/\" + str(year), 0, 0, 1)\n\n    # display the time in hours, minute and seconds on the second line\n    # note that the \":02\" indicates printing in two columns with leading zeros\n    oled.text(f\"{hour}:{minute:02}:{second:02} \" + am_pm, 0, 10, 1)\n\n    oled.show()\n
"},{"location":"kits/oled-large/04-display-localtime-formatted/#time-formatting","title":"Time formatting","text":"

When we print out minutes and seconds we want to make sure that we print them in two digits with a leading zero.

f\"{minute:02}:{second:02}\"\n

The python modulo operator % is used to find the remainder of the hour after dividing by 12. This converts the 24-hour time to 12-hour time.

"},{"location":"kits/oled-large/05-button-test/","title":"Testing Your Buttons","text":"

The diagram above shows three buttons below the Pico that will connect GPIO pins 13, 14 and 15 to GND when they are pressed. When they are not pressed the signals on 13, 14 and 15 must be \"pulled up\" to 5 volts using the internal pull-up resistor within the Pico. We configure this when we setup the code.

"},{"location":"kits/oled-large/05-button-test/#sample-code","title":"Sample Code","text":""},{"location":"kits/oled-large/05-button-test/#single-button-test","title":"Single Button Test","text":"
from utime import sleep\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# Buttons\nBUTTON_1_PIN = 13\n\nbutton_1_pin = Pin(BUTTON_1_PIN, Pin.IN, Pin.PULL_UP)\n\n# This function gets called every time the button is pressed.  The parameter \"pin\" is not used.\ndef button_1_pressed(pin):\n    print('Button 1 pressed')\n\n# now we register the button handler function using the irq setter method for this pin\nbutton_1_pin.irq(trigger=Pin.IRQ_FALLING, handler = button_1_pressed)\n\nwhile True:\n    sleep(1)\n
"},{"location":"kits/oled-large/05-button-test/#three-button-test","title":"Three Button Test","text":"
from utime import sleep\nfrom machine import Pin\n\n# Sample Raspberry Pi Pico MicroPython three button press example\n\n# Button Pin GPIO Configuration\nBUTTON_1_PIN = 13\nBUTTON_2_PIN = 14\nBUTTON_3_PIN = 15\n\n# Create 3 button objects as inputs with pullup resistors\nbutton_1_pin = Pin(BUTTON_1_PIN, Pin.IN, Pin.PULL_UP)\nbutton_2_pin = Pin(BUTTON_2_PIN, Pin.IN, Pin.PULL_UP)\nbutton_3_pin = Pin(BUTTON_3_PIN, Pin.IN, Pin.PULL_UP)\n\n# These functions gets called every time a button is pressed.\ndef button_1_pressed(pin):\n    print('Button 1 pressed')\ndef button_2_pressed(pin):\n    print('Button 2 pressed')\ndef button_3_pressed(pin):\n    print('Button 3 pressed')\n\n# now we register the button handler functions using the irq setter method for each pin\nbutton_1_pin.irq(trigger=Pin.IRQ_FALLING, handler = button_1_pressed)\nbutton_2_pin.irq(trigger=Pin.IRQ_FALLING, handler = button_2_pressed)\nbutton_3_pin.irq(trigger=Pin.IRQ_FALLING, handler = button_3_pressed)\n\n# we don't do anything in the main loop but sleep\nwhile True:\n    sleep(1)\n
"},{"location":"kits/oled-large/05-rtc/","title":"Working with the DS3232 Real Time Clock","text":""},{"location":"kits/oled-large/05-rtc/#printing-the-raw-time-data","title":"Printing The Raw Time Data","text":"
from machine import Pin, I2C\nimport ds3231\n\n# it is more portable to read the pins from the config.py file\ni2c = I2C(0, sda=Pin(0), scl=Pin(1))\nrtc = ds3231.DS3231(i2c)\n\nprint(rtc.datetime())\n

Result:

(2025, 1, 31, 5, 11, 18, 13, 0)\n

Note that this is an 8-value tuple format, not the standard 7 values.

"},{"location":"kits/oled-large/05-rtc/#using-the-hardware-config-file","title":"Using the Hardware Config File","text":"
from machine import Pin, I2C\nimport ds3231\nimport config\n\ni2c = I2C(0, sda=Pin(config.I2C_SDA_PIN), scl=Pin(config.I2C_SCL_PIN))\nrtc = ds3231.DS3231(i2c)\n\nprint(rtc.datetime())\n
"},{"location":"kits/oled-large/05-rtc/#formatting-time-in-iso-format","title":"Formatting Time in ISO format","text":"
from machine import Pin, I2C\nimport ds3231\nimport config\n\ni2c = I2C(0, sda=Pin(config.I2C_SDA_PIN), scl=Pin(config.I2C_SCL_PIN))\nrtc = ds3231.DS3231(i2c)\n\ndef format_time(datetime_tuple):\n    # Unpack the 8-value tuple\n    year, month, day, hour, minute, second, subseconds, weekday = datetime_tuple\n\n    # Convert to 12-hour format\n    am_pm = \"AM\" if hour < 12 else \"PM\"\n    hour_12 = hour if hour <= 12 else hour - 12\n    if hour_12 == 0:\n        hour_12 = 12\n\n    # Create day and month lookup tables\n    days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']\n    months = ['January', 'February', 'March', 'April', 'May', 'June', \n              'July', 'August', 'September', 'October', 'November', 'December']\n\n    # Use 'd' instead of '02d' for hours to remove leading zero\n    return f\"{days[weekday]}, {months[month-1]} {day}, {year} {hour_12}:{minute:02d}:{second:02d} {am_pm}\"\n\ndatetime = rtc.datetime()\nprint(format_time(datetime))\n
"},{"location":"kits/oled-large/05-rtc/#setting-the-ds3232-trc-from-localtime","title":"Setting the DS3232 TRC from Localtime","text":"

Prompt

Please write a detailed step-by-step explanation of the set-ds3231-rtc-from-localtime.py program. Assume your reader is a junior high student that is just learning Python for the first time.

I'll explain how this program works step by step, focusing on what each part does in simple terms.

"},{"location":"kits/oled-large/05-rtc/#1-setting-up-the-rtc-real-time-clock","title":"1. Setting Up the RTC (Real-Time Clock):","text":"

from machine import Pin, I2C\nimport ds3231\nimport config\nfrom time import localtime\n\ni2c = I2C(config.I2C_BUS, sda=Pin(config.I2C_SDA_PIN), scl=Pin(config.I2C_SCL_PIN))\nrtc = ds3231.DS3231(i2c)\n
This part connects to a special clock chip called DS3231 using two wires (I2C communication). Think of it like plugging in a digital clock.

"},{"location":"kits/oled-large/05-rtc/#2-the-format_time-function","title":"2. The format_time Function:","text":"

def format_time(datetime_tuple):\n    year, month, day, weekday, hour, minute, second, subseconds = datetime_tuple\n
This function takes time information and makes it look nice for humans to read. For example, instead of showing \"1\" for January, it shows \"January\".

The function converts 24-hour time to 12-hour time with AM/PM:

am_pm = \"AM\" if hour < 12 else \"PM\"\nhour_12 = hour if hour <= 12 else hour - 12\nif hour_12 == 0:\n    hour_12 = 12\n
So 13:00 becomes 1:00 PM.

"},{"location":"kits/oled-large/05-rtc/#3-the-verify_time-function","title":"3. The verify_time Function:","text":"

def verify_time(local_time):\n    l_year, l_month, l_day, l_hour, l_min, l_sec, l_wday, l_yday = local_time\n
This function is like a safety check. It makes sure the time makes sense: - Years must be between 2000 and 2099 - Months must be between 1 and 12 - Hours must be between 0 and 23 - And so on

"},{"location":"kits/oled-large/05-rtc/#4-setting-the-time","title":"4. Setting the Time:","text":"

local = localtime()\nprint(\"\\nSystem local time:\")\nprint(f\"Year: {local[0]}, Month: {local[1]}, Day: {local[2]}\")\n
The program gets the current time from your computer or microcontroller and shows it to you.

"},{"location":"kits/oled-large/05-rtc/#5-final-step-updating-the-rtc","title":"5. Final Step - Updating the RTC:","text":"
if verify_time(local):\n    rtc_weekday = local[6] + 1\n    new_time = (\n        local[0],     # year\n        local[1],     # month\n        local[2],     # day\n        local[3],     # hour\n        local[4],     # minute\n        local[5],     # second\n        rtc_weekday   # weekday (1-7, Monday=1)\n    )\n    rtc.datetime(new_time)\n

If all the safety checks pass, it updates the RTC chip with the new time. The program then shows you the new time to confirm it worked.

This program is like setting a digital clock, but instead of pressing buttons, it automatically sets the time using your computer's time. The RTC chip will keep tracking time even if you unplug your project, just like how a digital clock keeps time even when unplugged (it uses a small battery).

"},{"location":"kits/oled-large/05-rtc/#full-program-listing","title":"Full Program Listing","text":"
from machine import Pin, I2C\nimport ds3231\nimport config\nfrom time import localtime\n\n# Initialize I2C and RTC\ni2c = I2C(config.I2C_BUS, sda=Pin(config.I2C_SDA_PIN), scl=Pin(config.I2C_SCL_PIN))\nrtc = ds3231.DS3231(i2c)\n\ndef format_time(datetime_tuple):\n    # Unpack the 8-value tuple returned by rtc.datetime()\n    # Format is: year, month, day, weekday, hour, minutes, seconds, subseconds\n    year, month, day, weekday, hour, minute, second, subseconds = datetime_tuple\n\n    # Convert to 12-hour format\n    am_pm = \"AM\" if hour < 12 else \"PM\"\n    hour_12 = hour if hour <= 12 else hour - 12\n    if hour_12 == 0:\n        hour_12 = 12\n\n    # Create day and month lookup tables\n    # weekday in RTC is 1-7 (Monday=1, Sunday=7)\n    days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']\n    months = ['January', 'February', 'March', 'April', 'May', 'June', \n              'July', 'August', 'September', 'October', 'November', 'December']\n\n    # Adjust weekday to 0-based index for days list\n    weekday_index = weekday - 1\n\n    return f\"{days[weekday_index]}, {months[month-1]} {day}, {year} {hour_12}:{minute:02d}:{second:02d} {am_pm}\"\n\ndef verify_time(local_time):\n    \"\"\"\n    Verify that local_time values are within acceptable bounds\n    Returns: True if time appears valid, False otherwise\n    \"\"\"\n    # Unpack tuple\n    l_year, l_month, l_day, l_hour, l_min, l_sec, l_wday, l_yday = local_time\n\n    # Basic bounds checking\n    if not (2000 <= l_year <= 2099):\n        print(\"Error: Year out of range (2000-2099)\")\n        return False\n    if not (1 <= l_month <= 12):\n        print(\"Error: Month out of range (1-12)\")\n        return False\n    if not (1 <= l_day <= 31):\n        print(\"Error: Day out of range (1-31)\")\n        return False\n    if not (0 <= l_hour <= 23):\n        print(\"Error: Hour out of range (0-23)\")\n        return False\n    if not (0 <= l_min <= 59):\n        print(\"Error: Minute out of range (0-59)\")\n        return False\n    if not (0 <= l_sec <= 59):\n        print(\"Error: Second out of range (0-59)\")\n        return False\n\n    return True\n\n# Get current RTC time and print it\nprint(\"\\nCurrent RTC time before setting:\")\ncurrent_rtc_time = rtc.datetime()\nprint(format_time(current_rtc_time))\n\n# Get local time\nlocal = localtime()\nprint(\"\\nSystem local time:\")\nprint(f\"Year: {local[0]}, Month: {local[1]}, Day: {local[2]}\")\nprint(f\"Hour: {local[3]}, Minute: {local[4]}, Second: {local[5]}\")\nprint(f\"Weekday: {local[6]} (0=Monday, 6=Sunday)\")\n\n# Verify the time data\nif verify_time(local):\n    # Convert weekday from localtime (0-6, Monday=0) to RTC format (1-7, Monday=1)\n    rtc_weekday = local[6] + 1\n\n    # Create datetime tuple in correct order for rtc.datetime()\n    # According to the driver docstring:\n    # datetime : tuple, (0-year, 1-month, 2-day, 3-hour, 4-minutes[, 5-seconds[, 6-weekday]])\n    new_time = (\n        local[0],     # year\n        local[1],     # month\n        local[2],     # day\n        local[3],     # hour\n        local[4],     # minute\n        local[5],     # second\n        rtc_weekday   # weekday (1-7, Monday=1)\n    )\n\n    # Set the RTC\n    rtc.datetime(new_time)\n    print(\"\\nRTC time set successfully!\")\n\n    # Read back and print the new time\n    print(\"\\nNew RTC time after setting:\")\n    new_rtc_time = rtc.datetime()\n    print(format_time(new_rtc_time))\nelse:\n    print(\"\\nError: Time verification failed. RTC not set.\")\n
"},{"location":"kits/oled-large/06-display-single-digit/","title":"Display a Single 7-Segment Digit","text":"
# Seven segment display configuration\nsegment_mapping = [\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\ndef draw_digit(digit, x, y, width, height, thickness, color):\n    if digit < 0:\n        return\n    segment_on = segment_mapping[digit]\n\n    # Horizontal segments\n    for i in [0, 3, 6]:\n        if segment_on[i]:\n            if i == 0:  # top\n                y_offset = 0\n            elif i == 3:  # bottom\n                y_offset = height - thickness\n            else:  # middle\n                y_offset = height // 2 - thickness // 2\n            oled.fill_rect(x, y + y_offset, width, thickness, color)\n\n    # Vertical segments\n    for i in [1, 2, 4, 5]:\n        if segment_on[i]:\n            if i == 1 or i == 5:  # upper\n                start_y = y\n                end_y = y + height // 2\n            else:  # lower\n                start_y = y + height // 2\n                end_y = y + height\n            x_offset = 0 if (i == 4 or i == 5) else width - thickness\n            oled.fill_rect(x + x_offset, start_y, thickness, end_y - start_y, color)\n
# Clock Lab 20: Draw Seven Segments\n# this lab uses the fill_rect function to draw the segments\nfrom machine import Pin\nimport utime\nimport ssd1306\nfrom utime import sleep, localtime\nimport config\n\nled = machine.Pin(25, machine.Pin.OUT)\n\nSCL=machine.Pin(config.SPI_SCL_PIN) # SPI CLock\nSDA=machine.Pin(config.SPI_SDA_PIN) # SPI Data\n\nRES = machine.Pin(config.SPI_RESET_PIN) # Reset\nDC = machine.Pin(config.SPI_DC_PIN) # Data/command\nCS = machine.Pin(config.SPI_CS_PIN) # Chip Select\n\nWIDTH = config.DISPLAY_WIDTH\nHEIGHT = config.DISPLAY_HEIGHT\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\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)\nw = 40 # digit width\nh = 64 # digit height\nx = (WIDTH // 2) - w // 2 # center - width\ny = 0 # upper left corner y\nt = 8\n\nwhile True:\n    for i in range(0, 10):\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/oled-large/07-set-rtc-time-with-buttons/","title":"Set the DS3231 RTC using Buttons","text":"

Prompt

Please create a new program that allows the user to set the time in the DS3231 RTC using the three buttons. Use the main-v1.py as your starting point and merge in the code from 08-button-mode-with-debounce.py to change the mode from:

mode 0 - clock running mode 1 - set hour with hour digits on screen flashing every 500ms mode 2 - set minute with minute digits on screen flashing every 500ms mode 3 - set am/pm with am/pm text flashing every 500ms

"},{"location":"kits/oled-large/07-set-rtc-time-with-buttons/#response","title":"Response","text":"

Key changes made:

  1. Integrated button handling with debounce from 08-button-mode-with-debounce.py
  2. Added flashing display elements based on current mode
  3. Modified time adjustment logic to work with RTC
  4. Simplified display code while maintaining core functionality
  5. Added proper button pin configuration from config.py

Test the code by:

  1. Connect buttons to specified GPIO pins
  2. Press mode button to cycle through modes
  3. Use increment/decrement buttons to adjust time
  4. Verify display flashing in each mode

```python

"},{"location":"kits/oled-large/10-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/oled-large/10-draw-seven-segments/","title":"Draw Seven Segments","text":""},{"location":"kits/oled-large/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/oled-large/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/oled-large/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/oled-large/11-draw-thickness/","title":"Draw Seven Segments with Thickness","text":""},{"location":"kits/oled-large/13-three-button-set/","title":"Three Button Set","text":""},{"location":"kits/oled-large/13-three-button-set/#set-time-modes","title":"Set Time Modes","text":"
mode_names = ['run', 'set hour', 'set minute', 'set AM/PM']\n

We will create a new temporary array of date time values called current_time. This is a tuple of the values that the rtc.datetime() function needs to set a new time. We will change it as the user presses the increment and decrement buttons.

"},{"location":"kits/oled-large/13-three-button-set/#update-strategy","title":"Update Strategy","text":"

Remember that I2C communication is very slow. I2C calls should never be allowed in an IRQ context. IRQ job is to set globals and finish without delay. Moving update_rtc() from within an IRQ to the main loop is better than calling it in the IRQ. By setting a pending_update flag in the IRQ and checking it in the main loop, we can manage updates without blocking operations like this:

# main loop\nwhile True:\n    # If we're in run mode and there's a pending update, update the RTC\n    if mode == 0 and pending_update:\n        update_rtc()  # This will update the RTC and clear the flag\n

We ONLY should update the RTC when we are going from mode=3 to mode=0 and we know that the user changed the hour, minute or AM/PM. If these were not changed then we don't update the RTC.

The code to update the RTC is in the update_rtc() which has access to two global variables, the current_time and another Boolean variable called pending_update. When any of the hour, minute or AM/PM is changed, the pending_update is set to True. Calling update_rtc() if pending_update is False does not change the RTC.

Note that the format of the data coming OUT of the RTC is different than the format of the data we use to set the RTC. To set the clock correctly we need to rearrange the tuple to match the expected format:

def update_rtc():\n    \"\"\"Update RTC with current time state\"\"\"\n    global current_time, pending_update\n    if pending_update:\n        # Rearrange the tuple to match the expected format:\n        # From: (year, month, day, weekday, hour, minutes, seconds, subseconds)\n        # To:   (year, month, day, hour, minutes, seconds, weekday)\n        rtc_tuple = (\n            current_time[0],  # year\n            current_time[1],  # month\n            current_time[2],  # day\n            current_time[4],  # hour\n            current_time[5],  # minutes\n            current_time[6],  # seconds\n            current_time[3]   # weekday\n        )\n        rtc.datetime(rtc_tuple)\n        pending_update = False\n
"},{"location":"kits/oled-large/13-three-button-set/#mode-button-pressed-irq","title":"Mode Button Pressed IRQ","text":"
def mode_button_pressed(pin):\n    global mode, last_mode_press, pending_update\n    current_ms = ticks_ms()\n    if ticks_diff(current_ms, last_mode_press) > DEBOUNCE_TIME_MS:\n        last_mode_press = current_ms\n\n        # Increment the mode first\n        mode = (mode + 1) % len(mode_names)\n\n        print(f\"Mode button pressed - New mode: {mode_names[mode]}\")\n

Warning

There should be no calls to functions with I2C calls like update_rtc() which calls rtc.datetime() which is a I2C call.

"},{"location":"kits/oled-large/13-three-button-set/#flash-digits-being-set","title":"Flash Digits Being Set","text":"

flash_state is a Boolean that tells us if the digit is flashing. During normal running flash_state = False

# in the update_screen function\n    # Draw digits with flashing based on mode\n    if mode != 1 or flash_state:\n        if hour_ten > 0:  # Only show tens digit if non-zero\n            draw_digit(hour_ten, left_margin, y_offset, digit_width, digit_height, digit_thickness, 1)\n        draw_digit(hour_one, left_margin + digit_spacing - 2, y_offset, digit_width, digit_height, digit_thickness, 1)\n\n    if mode != 2 or flash_state:\n        draw_digit(minute_ten, left_margin + 2 * digit_spacing, y_offset, digit_width, digit_height, digit_thickness, 1)\n        draw_digit(minute_one, left_margin + 3 * digit_spacing, y_offset, digit_width, digit_height, digit_thickness, 1)\n\n    # Draw AM/PM indicator\n    if mode != 3 or flash_state:\n        oled.text(\"PM\" if is_pm(hour) else \"AM\", 112, 55, 1)\n
"},{"location":"kits/oled-large/13-three-button-set/#full-listing","title":"Full Listing","text":"
from machine import Pin, I2C, SPI\nfrom utime import sleep, ticks_ms, ticks_diff\nfrom ds3231 import DS3231\nimport ssd1306\nimport config\n\n# Display setup\nSCL = Pin(config.SPI_SCL_PIN)\nSDA = Pin(config.SPI_SDA_PIN)\nDC = Pin(config.SPI_DC_PIN)\nRES = Pin(config.SPI_RESET_PIN)\nCS = Pin(config.SPI_CS_PIN)\nspi = SPI(config.SPI_BUS, sck=SCL, mosi=SDA, baudrate=1000000)\noled = ssd1306.SSD1306_SPI(config.DISPLAY_WIDTH, config.DISPLAY_HEIGHT, spi, DC, RES, CS)\n\n# RTC setup\ni2c = I2C(config.I2C_BUS, sda=Pin(config.I2C_SDA_PIN), scl=Pin(config.I2C_SCL_PIN))\nrtc = DS3231(i2c)\n\n# Button setup\nmode_button = Pin(config.BUTTON_MODE_PIN, Pin.IN, Pin.PULL_UP)\nincrement_button = Pin(config.BUTTON_INCREMENT_PIN, Pin.IN, Pin.PULL_UP)\ndecrement_button = Pin(config.BUTTON_DECREMENT_PIN, Pin.IN, Pin.PULL_UP)\n\n# Global variables\nmode = 0  # 0=run, 1=set hour, 2=set minute, 3=set AM/PM\nlast_mode_press = 0\nlast_increment_press = 0\nlast_decrement_press = 0\nDEBOUNCE_TIME_MS = 200\nmode_names = ['run', 'set hour', 'set minute', 'set AM/PM']\nflash_state = False\nlast_flash_toggle = 0\nFLASH_INTERVAL_MS = 500\n\n# Current time state\ncurrent_time = list(rtc.datetime())\npending_update = False\n\n# Seven segment display configuration\nsegment_mapping = [\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\ndef update_rtc():\n    \"\"\"Update RTC with current time state\"\"\"\n    global current_time, pending_update\n    if pending_update:\n        # Rearrange the tuple to match the expected format:\n        # From: (year, month, day, weekday, hour, minutes, seconds, subseconds)\n        # To:   (year, month, day, hour, minutes, seconds, weekday)\n        rtc_tuple = (\n            current_time[0],  # year\n            current_time[1],  # month\n            current_time[2],  # day\n            current_time[4],  # hour\n            current_time[5],  # minutes\n            current_time[6],  # seconds\n            current_time[3]   # weekday\n        )\n        rtc.datetime(rtc_tuple)\n        pending_update = False\n\ndef refresh_time():\n    \"\"\"Read current time from RTC\"\"\"\n    global current_time\n    current_time = list(rtc.datetime())\n\ndef convert_to_12h(hour):\n    \"\"\"Convert 24h hour to 12h hour\"\"\"\n    if hour == 0:\n        return 12  # Midnight is 12 AM\n    elif hour > 12:\n        return hour - 12\n    return hour\n\ndef is_pm(hour):\n    \"\"\"Check if hour is PM in 24h format\"\"\"\n    return hour >= 12\n\ndef toggle_am_pm(hour):\n    \"\"\"Toggle between AM and PM for a given hour\"\"\"\n    if hour < 12:\n        return hour + 12\n    return hour - 12\n\ndef draw_digit(digit, x, y, width, height, thickness, color):\n    if digit < 0:\n        return\n    segment_on = segment_mapping[digit]\n\n    # Horizontal segments\n    for i in [0, 3, 6]:\n        if segment_on[i]:\n            if i == 0:  # top\n                y_offset = 0\n            elif i == 3:  # bottom\n                y_offset = height - thickness\n            else:  # middle\n                y_offset = height // 2 - thickness // 2\n            oled.fill_rect(x, y + y_offset, width, thickness, color)\n\n    # Vertical segments\n    for i in [1, 2, 4, 5]:\n        if segment_on[i]:\n            if i == 1 or i == 5:  # upper\n                start_y = y\n                end_y = y + height // 2\n            else:  # lower\n                start_y = y + height // 2\n                end_y = y + height\n            x_offset = 0 if (i == 4 or i == 5) else width - thickness\n            oled.fill_rect(x + x_offset, start_y, thickness, end_y - start_y, color)\n\n\ndef update_screen():\n    global flash_state, current_time\n    oled.fill(0)\n\n    # Display settings\n    left_margin = -28\n    y_offset = 11\n    digit_width = 33\n    digit_height = 40\n    digit_spacing = 41\n    digit_thickness = 5\n\n    hour = current_time[4]\n    minute = current_time[5]\n    second = current_time[6]\n\n    # Convert to 12-hour format for display\n    display_hour = convert_to_12h(hour)\n\n    # Calculate digits for display\n    hour_ten = display_hour // 10 if display_hour >= 10 else -1\n    hour_one = display_hour % 10\n    minute_ten = minute // 10\n    minute_one = minute % 10\n\n    print(f\"Debug display - Raw Hour:{hour} Display Hour:{display_hour} Min:{minute} \" + \n          f\"Digits: {hour_ten}|{hour_one} : {minute_ten}|{minute_one} {('AM', 'PM')[is_pm(hour)]}\")\n\n    # Draw digits with flashing based on mode\n    if mode != 1 or flash_state:\n        if hour_ten > 0:  # Only show tens digit if non-zero\n            draw_digit(hour_ten, left_margin, y_offset, digit_width, digit_height, digit_thickness, 1)\n        draw_digit(hour_one, left_margin + digit_spacing - 2, y_offset, digit_width, digit_height, digit_thickness, 1)\n\n    if mode != 2 or flash_state:\n        draw_digit(minute_ten, left_margin + 2 * digit_spacing, y_offset, digit_width, digit_height, digit_thickness, 1)\n        draw_digit(minute_one, left_margin + 3 * digit_spacing, y_offset, digit_width, digit_height, digit_thickness, 1)\n\n    # Draw colon (flashing)\n    if second % 2:\n        oled.fill_rect(47, 20, 3, 3, 1)\n        oled.fill_rect(47, 20 + 14, 3, 3, 1)\n\n    # Draw AM/PM indicator\n    if mode != 3 or flash_state:\n        oled.text(\"PM\" if is_pm(hour) else \"AM\", 112, 55, 1)\n\n    # Draw seconds\n    oled.text(f\"{second:02d}\", 0, 54, 1)\n    oled.show()\n\ndef mode_button_pressed(pin):\n    global mode, last_mode_press, pending_update\n    current_ms = ticks_ms()\n    if ticks_diff(current_ms, last_mode_press) > DEBOUNCE_TIME_MS:\n        last_mode_press = current_ms\n\n        # Increment the mode first\n        mode = (mode + 1) % len(mode_names)\n\n        print(f\"Mode button pressed - New mode: {mode_names[mode]}\")\n\ndef increment_button_pressed(pin):\n    global last_increment_press, current_time, pending_update\n    current_ms = ticks_ms()\n    if ticks_diff(current_ms, last_increment_press) > DEBOUNCE_TIME_MS:\n        last_increment_press = current_ms\n        print(\"Increment button pressed\")\n\n        if mode == 1:  # Hour\n            hour = current_time[4]\n            new_hour = (hour + 1) if hour < 23 else 0\n            current_time[4] = new_hour\n            pending_update = True\n            print(f\"Hour adjusted to: {new_hour}\")\n\n        elif mode == 2:  # Minute\n            minute = current_time[5]\n            new_minute = (minute + 1) if minute < 59 else 0\n            current_time[5] = new_minute\n            current_time[6] = 0  # Reset seconds when setting minutes\n            pending_update = True\n            print(f\"Minute adjusted to: {new_minute}\")\n\n        elif mode == 3:  # AM/PM\n            hour = current_time[4]\n            new_hour = toggle_am_pm(hour)\n            current_time[4] = new_hour\n            pending_update = True\n            print(f\"Hour adjusted to: {new_hour} ({('AM', 'PM')[is_pm(new_hour)]})\")\n\ndef decrement_button_pressed(pin):\n    global last_decrement_press, current_time, pending_update\n    current_ms = ticks_ms()\n    if ticks_diff(current_ms, last_decrement_press) > DEBOUNCE_TIME_MS:\n        last_decrement_press = current_ms\n        print(\"Decrement button pressed\")\n\n        if mode == 1:  # Hour\n            hour = current_time[4]\n            new_hour = (hour - 1) if hour > 0 else 23\n            current_time[4] = new_hour\n            pending_update = True\n            print(f\"Hour adjusted to: {new_hour}\")\n\n        elif mode == 2:  # Minute\n            minute = current_time[5]\n            new_minute = (minute - 1) if minute > 0 else 59\n            current_time[5] = new_minute\n            current_time[6] = 0  # Reset seconds when setting minutes\n            pending_update = True\n            print(f\"Minute adjusted to: {new_minute}\")\n\n        elif mode == 3:  # AM/PM\n            hour = current_time[4]\n            new_hour = toggle_am_pm(hour)\n            current_time[4] = new_hour\n            pending_update = True\n            print(f\"Hour adjusted to: {new_hour} ({('AM', 'PM')[is_pm(new_hour)]})\")\n\n# Setup button interrupts\nmode_button.irq(trigger=Pin.IRQ_FALLING, handler=mode_button_pressed)\nincrement_button.irq(trigger=Pin.IRQ_FALLING, handler=increment_button_pressed)\ndecrement_button.irq(trigger=Pin.IRQ_FALLING, handler=decrement_button_pressed)\n\nprint(\"Clock started in mode: run\")\n\n# Initialize time state\nrefresh_time()\n\n# Main loop\n# Main loop\nwhile True:\n    current_ms = ticks_ms()\n\n    # Update flash state every FLASH_INTERVAL_MS\n    if ticks_diff(current_ms, last_flash_toggle) >= FLASH_INTERVAL_MS:\n        flash_state = not flash_state\n        last_flash_toggle = current_ms\n\n    # If we're in run mode and there's a pending update, update the RTC\n    if mode == 0 and pending_update:\n        update_rtc()  # This will update the RTC and clear the flag\n\n    # In run mode, use RTC for timekeeping only\n    if mode == 0 and not pending_update:\n        current_time = list(rtc.datetime())\n\n    # Update display\n    update_screen()\n    sleep(0.5)\n
"},{"location":"kits/oled-large/15-digital-clock/","title":"OLED Digital Clock Display","text":"

In this section we will do a full walk through of a program that uses the real-time clock to get the time and displays the time using the four-digit drawing function we created.

Note that if you don't have your real-time clock connected or working you can still use this program to display the time using the localtime() function.

Change the following line:

From: ```python now = rtc.

"},{"location":"kits/oled-wireless/","title":"OLED Wireless Weather Kit","text":""},{"location":"kits/sh1106/","title":"SH1108 OLED Driver Kit","text":""},{"location":"kits/sh1106/#testing-the-oled-connections","title":"Testing the OLED Connections","text":"
from machine import Pin, I2C\nfrom sh1106 import SH1106_I2C\n\n# OLED DATA Pin on GPIO 0\nOLED_SH11306_SDA_PIN = 0\n# OLED Clock Pin on GPIO 1\nOLED_SH11306_SCL_PIN = 1\n\nsda=Pin(OLED_SH11306_SDA_PIN, Pin.OUT)\nscl=Pin(OLED_SH11306_SCL_PIN, Pin.OUT)\n\n# freq=198000 to 3600000 seem to work.  Use 3000000 as a safe option.\ni2c = I2C(0, scl=scl, sda=sda, freq=100000)\n\n# Initialize display (128x64 pixels)\noled = SH1106_I2C(128, 64, i2c)\noled.rotate(180)\n\n# Clear display\noled.fill(0)\n\n# Write text\noled.text(\"MicroPython\", 0, 0)\noled.text(\"Rocks!\", 20, 20)\n\n# Show the display\noled.show()\n
"},{"location":"kits/sh1106/#displaying-clock-digits","title":"Displaying Clock Digits","text":"

Note that the pins are on the bottom, so for our example we had to do a oled.rotate(180)

from machine import Pin, I2C\nfrom sh1106 import SH1106_I2C\nfrom utime import sleep, localtime\n\n# OLED DATA Pin on GPIO 0\nOLED_SH11306_SDA_PIN = 0\n# OLED Clock Pin on GPIO 1\nOLED_SH11306_SCL_PIN = 1\n\nsda=Pin(OLED_SH11306_SDA_PIN, Pin.OUT)\nscl=Pin(OLED_SH11306_SCL_PIN, Pin.OUT)\n\n# freq=30000 (30K) to 4000000 (4M) seem to work.  Use 3000000 (3M) as a safe option.\ni2c = I2C(0, scl=scl, sda=sda, freq=3000000)\n\n# Initialize display (128x64 pixels)\noled = SH1106_I2C(128, 64, i2c)\noled.rotate(180)\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 upper-left-corner\n# width and height are the dimensions of the digit\n# thinkness is the width of the line segments\n# color is 1 for white and 0 for black\ndef drawDigit(digit, x, y, width, height, thinkness, color):\n  if digit < 0:\n      return\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 - thinkness # bottem element\n      if (i==6):\n          yOffset = height // 2 - thinkness // 2# bottum\n      # oled.line(x - size, y+yOffset-size, x + size, y+yOffset-size, 1);\n      oled.fill_rect(x, y+yOffset, width, thinkness, 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-thinkness\n\n      oled.fill_rect(x+xOffset, startY, thinkness, endY-startY, color)\n\ndef update_screen(year, month, day, hour, minute, am_pm, colon_on):   \n\n    ## Adjust these to fit the display\n    left_margin = -28\n    y_offset = 11\n    digit_width = 33\n    digit_height = 40\n    digit_spacing = 41\n    digit_thickness = 5\n    oled.fill(0)\n    date_str = f\"{month}/{day}/{year}\"\n    oled.text(date_str, 0, 0, 1)\n    if hour > 12:\n        hour = hour - 12\n    if hour > 10:\n        hour_ten = 1\n    else:\n        hour_ten = -1\n    hour_right = hour % 10\n\n    minute_ten = minute // 10\n    minute_right = minute % 10\n\n    # left digit will be 1 or blank\n    drawDigit(hour_ten, left_margin,  y_offset, digit_width, digit_height, digit_thickness, 1)\n    drawDigit(hour_right, left_margin + 1*digit_spacing-2,  y_offset, digit_width, digit_height, digit_thickness, 1)\n    drawDigit(minute_ten, left_margin + 2*digit_spacing,  y_offset, digit_width, digit_height, digit_thickness, 1)\n    drawDigit(minute_right, left_margin + 3*digit_spacing, y_offset, digit_width, digit_height, digit_thickness, 1)\n\n    if colon_on:\n        draw_colon(47,20)\n\n    pm_xoffset = 106\n    pm_yoffset = 55\n\n    oled.text(am_pm, pm_xoffset, pm_yoffset, 1)\n\n    oled.show()\n\ndef draw_colon(x,y):\n    oled.fill_rect(x, y,    3, 3, 1)\n    oled.fill_rect(x, y+14, 3, 3, 1)\n\ncounter = 0\nwhile True:\n    now = localtime()\n    year = now[0]\n    month = now[1]\n    day = now[2]\n    hour = now[3]\n    if hour > 12:\n        am_pm = \"PM\"\n    else:\n        am_pm = \"AM\"\n    minute = now[4]\n\n    update_screen(year, month, day, hour, minute, am_pm, True)\n    sleep(1)\n    update_screen(year, month, day, hour, minute, am_pm, False)\n    sleep(1)\n    counter += 1\n    if counter > 9:\n        counter = 0\n
"},{"location":"kits/shift-register-clock/","title":"74HC595 Shift Register Clock","text":"

The display has five wires. In addition to power and ground we have:

ser_pin=2,    # DIO\nsrclk_pin=1,  # SCLK\nrclk_pin=0    # RCLK\n

The shift register takes in a set of 16 bit commands. The first byte is the segment information with one bit for each of the 7 segments and one bit for the colon. The second bit indicates what digit should be turned on.

Note that for a segment to be on, the bit must be \"0\".

Here are the letters associated with each of the 7 segments:

 aaaa\nf    b\nf    b\n gggg\ne    c\ne    c\n dddd\n
# bit 0 is the right-most significant bit and is used to turn on the colon.\nself.SEGMENTS = {\n    'a': 4,  # bit 4\n    'b': 3,  # bit 3\n    'c': 2,  # bit 2\n    'd': 7,  # bit 7\n    'e': 6,  # bit 6\n    'f': 5,  # bit 5\n    'g': 1   # bit 1\n}\n

\"d\" First byte: 00110000 Second byte: 00010000

This is where \"a\" is missing. a is the fourth bit. First byte: 00010000

This is where all the segments are on except f. \"f\" is the 3rd bit First byte: 00100000 \"A\" is First byte: 10000000

"},{"location":"kits/shift-register-clock/#sample-test","title":"Sample Test","text":"

The following program will cycle through the digits 0 to 9 on the right most digit to the left most digit.

from machine import Pin\nfrom sr74hc595 import SR74HC595_BITBANG\nfrom utime import sleep, localtime\n\nclass FourDigitClock:\n    # Class constants\n    ALL_OFF = 0xFF            # All segments off (inverted logic)\n    SECOND_BYTE = 0b00010000  # Keep bit 4 set in second byte\n\n    def __init__(self, ser_pin, srclk_pin, rclk_pin):\n        # Initialize pins\n        self.ser = Pin(ser_pin, Pin.OUT)      # Serial data\n        self.srclk = Pin(srclk_pin, Pin.OUT)  # Shift register clock\n        self.rclk = Pin(rclk_pin, Pin.OUT)    # Storage register clock\n\n        self.sr = SR74HC595_BITBANG(self.ser, self.srclk, self.rclk)\n\n        # Segment bit positions (0 turns segment on)\n        # a is the top, b in the upper right, c is the lower right\n        # d is the bottom, e is the lower left, f is the upper right\n        # g is the middle segment\n        self.SEGMENTS = {\n            'a': 4,  # bit 4\n            'b': 3,  # bit 3\n            'c': 2,  # bit 2\n            'd': 7,  # bit 7\n            'e': 6,  # bit 6\n            'f': 5,  # bit 5\n            'g': 1   # bit 1\n        }\n\n        # Define segments needed for each digit\n        self.DIGIT_SEGMENTS = {\n            0: 'abcdef',   # 0 needs all but g\n            1: 'bc',       # 1 needs just b and c\n            2: 'abged',    # 2 needs all but c and f\n            3: 'abgcd',    # 3 needs all but e and f\n            4: 'fbcg',     # 4 needs these four\n            5: 'afgcd',    # 5 needs all but e and b\n            6: 'afedcg',   # 6 needs all but b\n            7: 'abc',      # 7 needs just these three\n            8: 'abcdefg',  # 8 needs all segments\n            9: 'abfgcd'    # 9 needs all but e\n        }\n\n        # Pre-calculate patterns for all digits\n        self.DIGIT_PATTERNS = {\n            digit: self.create_pattern(segments) \n            for digit, segments in self.DIGIT_SEGMENTS.items()\n        }\n\n    def create_pattern(self, segments):\n        \"\"\"Create bit pattern from segment letters with inverted logic\"\"\"\n        pattern = self.ALL_OFF\n        for segment in segments:\n            if segment in self.SEGMENTS:\n                pattern &= ~(1 << self.SEGMENTS[segment])\n        return pattern\n\n    def display_pattern(self, first_byte, digit_select_byte):\n        \"\"\"Display a pattern with specified digit selection\"\"\"\n        self.sr.bits(first_byte, 8)\n        self.sr.bits(digit_select_byte, 8)\n        self.sr.latch()\n\n    def test_all_positions(self):\n        \"\"\"Test counting 0-9 on all digit positions\"\"\"\n        print(\"Testing all digit positions...\")\n\n        # Digit selection patterns to try\n        digit_patterns = [\n            (0, 0b00010000, \"Rightmost digit\"),\n            (1, 0b00100000, \"Second digit\"),\n            (2, 0b01000000, \"Third digit\"),\n            (3, 0b10000000, \"Leftmost digit\")\n        ]\n\n        # Test each digit position\n        for position, digit_select, position_name in digit_patterns:\n            print(f\"\\nTesting {position_name}\")\n\n            # Count 0-9 on this position\n            for number in range(10):\n                print(f\"Displaying {number} on {position_name}\")\n\n                # Display the number\n                self.display_pattern(self.DIGIT_PATTERNS[number], digit_select)\n                sleep(.25)\n\n                # Clear display between numbers\n                self.display_pattern(self.ALL_OFF, self.ALL_OFF)\n                sleep(0.05)\n\n            # Wait for user input before moving to next position\n            #input(f\"Press Enter to test next position...\")\n\ndef run_position_test():\n    \"\"\"Run position testing\"\"\"\n    clock = FourDigitClock(\n        ser_pin=2,    # DIO\n        srclk_pin=1,  # SCLK\n        rclk_pin=0    # RCLK\n    )\n\n    print(\"Starting position tests...\")\n    clock.test_all_positions()\n\n# Run the position test\nif __name__ == '__main__':\n    run_position_test()\n
"},{"location":"kits/shift-register-clock/#references","title":"References","text":"

74HC595 Shift Register

"},{"location":"kits/shift-register-clock/clock-main/","title":"Clock Main","text":""},{"location":"kits/shift-register-clock/clock-main/#details","title":"Details","text":"

This program use 12 hour time and also flashes the colon ever 1/2 second. It also is designed to work with a real-time clock that might take a bit of time to startup with the Pico first powers up.

from machine import Pin, RTC \nfrom sr74hc595 import SR74HC595_BITBANG\nfrom utime import sleep, localtime, ticks_ms\n\ndef wait_for_rtc():\n    print(\"Waiting for RTC to be ready...\")\n    rtc = RTC()\n    while True:\n        current_time = rtc.datetime()\n        # On Pico, default RTC starts at (2021, 1, 1, 0, 0, 0, 0, 0)\n        # So we check if it's been set to something else\n        if current_time[0] != 2021 or current_time[1] != 1 or current_time[2] != 1:\n            print(\"RTC is ready!\")\n            return\n        print(\"RTC not ready yet...\")\n        sleep(1)  # Check every second\n\nclass FourDigitClock:\n    ALL_OFF = 0xFF  # All segments off (inverted logic)\n\n    def __init__(self, ser_pin, srclk_pin, rclk_pin):\n        self.ser = Pin(ser_pin, Pin.OUT)\n        self.srclk = Pin(srclk_pin, Pin.OUT) \n        self.rclk = Pin(rclk_pin, Pin.OUT)\n        self.sr = SR74HC595_BITBANG(self.ser, self.srclk, self.rclk)\n\n        self.SEGMENTS = {\n            'a': 4, 'b': 3, 'c': 2, \n            'd': 7, 'e': 6, 'f': 5, 'g': 1\n        }\n        self.DIGIT_SEGMENTS = {\n            0: 'abcdef', 1: 'bc', 2: 'abged', 3: 'abgcd',\n            4: 'fbcg', 5: 'afgcd', 6: 'afedcg', \n            7: 'abc', 8: 'abcdefg', 9: 'abfgcd'\n        }\n        self.DIGIT_PATTERNS = {\n            digit: self.create_pattern(segments)\n            for digit, segments in self.DIGIT_SEGMENTS.items()\n        }\n        self.DIGIT_SELECT = [\n            0b00010000,  # Rightmost digit\n            0b00100000,  # Second digit \n            0b01000000,  # Third digit\n            0b10000000   # Leftmost digit  \n        ]\n\n    def create_pattern(self, segments):\n        pattern = self.ALL_OFF\n        for segment in segments:\n            if segment in self.SEGMENTS:\n                pattern &= ~(1 << self.SEGMENTS[segment]) \n        return pattern\n\n    def display_digit(self, digit, position, dp=False):\n        pattern = self.DIGIT_PATTERNS[digit]\n        if dp:\n            pattern &= ~(1 << 0)  # Turn on decimal point\n        self.sr.bits(pattern, 8)\n        self.sr.bits(self.DIGIT_SELECT[position], 8)\n        self.sr.latch()\n\n    def display_time(self, hour, minute, colon_state):\n        # Convert to 12-hour format\n        if hour > 12:\n            hour -= 12\n        elif hour == 0:  \n            hour = 12\n\n        digits = [hour // 10, hour % 10, minute // 10, minute % 10]\n\n        for position, digit in enumerate(reversed(digits)):\n            self.display_digit(digit, position, dp=(position == 1 and colon_state))\n            sleep(0.002)  # Reduced persistence timing\n\n\ndef run_clock():\n    # wait for a good time to come from localtime()\n    wait_for_rtc()\n\n    clock = FourDigitClock(ser_pin=2, srclk_pin=1, rclk_pin=0)\n    print(\"Starting clock...\")\n\n    last_second = -1\n    colon_state = True\n    last_toggle = ticks_ms()\n\n    while True:\n        current_time = localtime()\n        hour = current_time[3]\n        minute = current_time[4]\n        current_second = current_time[5]\n\n        if ticks_ms() - last_toggle >= 500:\n            colon_state = not colon_state\n            last_toggle = ticks_ms()\n\n        clock.display_time(hour, minute, colon_state)\n\n\nif __name__ == '__main__':\n    run_clock()\n
"},{"location":"kits/shift-register-clock/sample-prompt/","title":"Sample ChatGPT Prompt","text":"

Prompt

Please help me write a micropython program for a 4-digit clock. I have a digital clock display with 4 digits I purchased on eBay. Each of the 4 digits has 7 segments and there is also a colon between the 2nd and 3rd digits. The part says they use 74hc595 shift registers. The names of the connectors are:

  1. GND
  2. VCC
  3. RCLK
  4. SCLK
  5. DIO

I found a micropython driver called: sr74hc595. When I run a test program the various segments do light up, but I don't see a pattern. Can you give me some suggestions on how to write a driver that writes the correct time like 12:34?

Here is the code that is working:

# SPDX-FileCopyrightText: 2021 Mike Causer <https://github.com/mcauser>\n# SPDX-License-Identifier: MIT\n\n\"\"\"\nMicroPython 74HC595 8-Bit Shift Register\nhttps://github.com/mcauser/micropython-74hc595\n\"\"\"\n\nfrom machine import Pin\n# from sr74hc595 import SR74HC595_BITBANG\nfrom sr74hc595 import SR74HC595_BITBANG\nfrom utime import sleep\n\nser = Pin(2, Pin.OUT)\nrclk = Pin(0, Pin.OUT)\nsrclk = Pin(1, Pin.OUT)\n\n# construct without optional pins\nsr = SR74HC595_BITBANG(ser, srclk, rclk)\n\n#sr.clear()  # raises RuntimeError because you haven't provide srclr pin\n#sr.enable()  # raises RuntimeError because you haven't provide oe pin\n\n# reconstruct with all pins\noe = Pin(3, Pin.OUT, value=0)  # low enables output\nsrclr = Pin(3, Pin.OUT, value=1)  # pulsing low clears data\n\nsr = SR74HC595_BITBANG(ser, srclk, rclk, srclr, oe)\n\nwhile True:\n\n    sr.bit(1)  # send high bit, do not latch yet\n    sr.bit(0)  # send low bit, do not latch yet\n    sr.latch()  # latch outputs, outputs=0000_0010\n\n    sr.bit(1, 1)  # send high bit and latch, outputs=0000_0101\n    sr.bit(0, 1)  # send low bit and latch, outputs=0000_1010\n\n    sr.bits(0xFF, 4)  # send 4 lowest bits of 0xff (sends 0x0f), outputs=1010_1111\n    sr.clear(0)  # clear the memory but don't latch yet\n    sr.latch()  # next latch shows the outputs have been reset\n    sr.bits(0b1010_1010, 8)  # write some bits\n    sr.clear()  # clear the memory and latch, outputs have been reset\n\n    sr.enable()  # outputs enabled\n    sr.enable(0)  # outputs disabled\n\n    sleep(1)\n\n    sr.bits(0b1111_1111, 8)  # write some bits\n    sr.clear()  # clear the memory and latch, outputs have been reset\n\n    sr.enable()  # outputs enabled\n    sr.enable(0)  # outputs disabled\n\n    sleep(1)\n\n    sr.bits(0b0000_0011, 8)  # write some bits\n    sr.clear()  # clear the memory and latch, outputs have been reset\n\n    sr.enable()  # outputs enabled\n    sr.enable(0)  # outputs disabled\n    sleep(1)\n
"},{"location":"kits/ssd1306-i2c/","title":"A Tiny SSD1306 OLED Clock on an I2C Interface","text":"

This clock is an ideal starter clock since you can get the displays for just $3-4 for the small 1\" displays. Because they have a simple four-wire I2C connection they are also easy to wire up.

Note that the I2C bus can be slower than the faster SPI bus. However, for a clock that only updates once per second, this is not usually an issue since we don't need the 20 frames per second updates for real-time animation.

"},{"location":"kits/ssd1306-i2c/#testing-the-i2c-bus","title":"Testing the I2C Bus","text":"

We start by putting SDA on pin 0 and clock on pin 1. We can leave the freq parameter out or we can set the clock to a frequency of 3M or 3000000. I have found values from freq=198000 to 3600000 seem to work. Use 3000000 as a safe option.

from machine import I2C, Pin\n\n# OLED DATA Pin\nOLED_SSD1306_SDA_PIN = 0\n# OLED Clock Pin\nOLED_SSD1306_SCL_PIN = 1\n\nsda=Pin(OLED_SSD1306_SDA_PIN, Pin.OUT)\nscl=Pin(OLED_SSD1306_SCL_PIN, Pin.OUT)\n\ni2c = I2C(0, scl=scl, sda=sda, freq=3000000)\nfirst_device = i2c.scan()[0]\n# print(\"dec:\", first_device, \"hex:\", hex(first_device))\n\nif first_device == 60:\n    print(\"PASS: OLED Found on dec: 60 hex: 0x3c\")\nelse:\n    print(\"FAIL: OLED not found on expected address dec: 60 hex: 0x3c\")\n
"},{"location":"kits/ssd1306-i2c/#testing-the-oled-driver-and-connections","title":"Testing the OLED Driver and Connections","text":"
from machine import Pin, I2C\nfrom ssd1306 import SSD1306_I2C\n\n# OLED DATA Pin on GPIO 0\nOLED_SSD1306_SDA_PIN = 0\n# OLED Clock Pin on GPIO 1\nOLED_SSD1306_SCL_PIN = 1\n\nsda=Pin(OLED_SSD1306_SDA_PIN, Pin.OUT)\nscl=Pin(OLED_SSD1306_SCL_PIN, Pin.OUT)\n\n# freq=198000 to 3600000 seem to work.  Use 3000000 as a safe option.\ni2c = I2C(0, scl=scl, sda=sda, freq=3000000)\n\n# Initialize display (128x64 pixels)\noled = SSD1306_I2C(128, 64, i2c)\n\n# Clear display\noled.fill(0)\n\n# Write text\noled.text(\"MicroPython\", 0, 0)\noled.text(\"Rocks!\", 20, 20)\n\n# Show the display\noled.show()\n
"},{"location":"kits/ssd1306-i2c/#display-the-localtime","title":"Display the Localtime","text":"
from machine import Pin, I2C\nfrom ssd1306 import SSD1306_I2C\nfrom utime import localtime\n\n# OLED DATA Pin on GPIO 0\nOLED_SSD1306_SDA_PIN = 0\n# OLED Clock Pin on GPIO 1\nOLED_SSD1306_SCL_PIN = 1\n\nsda=Pin(OLED_SSD1306_SDA_PIN, Pin.OUT)\nscl=Pin(OLED_SSD1306_SCL_PIN, Pin.OUT)\n\n# freq=198000 to 3600000 seem to work.  Use 3000000 as a safe option.\ni2c = I2C(0, scl=scl, sda=sda, freq=3000000)\n\n# Initialize display (128x64 pixels)\noled = SSD1306_I2C(128, 64, i2c)\n\n# Clear display\noled.fill(0)\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), 20, 20, 1)\noled.show()\n
"},{"location":"kits/ssd1306-i2c/adjusting-digit-display/","title":"Adjusting the Clock Digit Layout","text":"

You have the ability to adjust the clock digit layouts by changing the following parameters:

  1. left_margin - were the left-most digit drawing starts
  2. y_offset - how far down the screen to begin to draw the top line of the digits
  3. digit_width - how wide each digit is
  4. digit_height - how high each digit is
  5. digit_spacing - the spacing between the left edges of each digit - not the space between the digits
  6. digit_thickness - how wide each bar of each segment is

In addition you can change the placement of the colon and the AM/PM indicator.

from machine import Pin, I2C\nfrom ssd1306 import SSD1306_I2C\nfrom utime import localtime, sleep\n\n# OLED DATA Pin on GPIO 0\nOLED_SSD1306_SDA_PIN = 0\n# OLED Clock Pin on GPIO 1\nOLED_SSD1306_SCL_PIN = 1\n\nsda=Pin(OLED_SSD1306_SDA_PIN, Pin.OUT)\nscl=Pin(OLED_SSD1306_SCL_PIN, Pin.OUT)\n\n# freq=198000 to 3600000 seem to work.  Use 3000000 as a safe option.\ni2c = I2C(0, scl=scl, sda=sda, freq=3000000)\n\n# Initialize display (128x64 pixels)\noled = SSD1306_I2C(128, 64, i2c)\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 upper-left-corner\n# width and height are the dimensions of the digit\n# thinkness is the width of the line segments\n# color is 1 for white and 0 for black\ndef drawDigit(digit, x, y, width, height, thinkness, 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 - thinkness # bottem element\n      if (i==6):\n          yOffset = height // 2 - thinkness // 2# bottum\n      # oled.line(x - size, y+yOffset-size, x + size, y+yOffset-size, 1);\n      oled.fill_rect(x, y+yOffset, width, thinkness, 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-thinkness\n\n      oled.fill_rect(x+xOffset, startY, thinkness, endY-startY, color)\n\ndef update_screen(digit_val):\n    global counter\n\n    ## Adjust these to fit the display\n    left_margin = -15\n    y_offset = 16\n    digit_width = 30\n    digit_height = 37\n    digit_spacing = 36\n    digit_thickness = 6\n    oled.fill(0)\n    oled.text('Clock Digits Lab', 0, 0, 1)\n\n    # left digit will be 1 or blank\n    drawDigit(1, left_margin,  y_offset, digit_width, digit_height, digit_thinkness, 1)\n    drawDigit(digit_val, left_margin + 1*digit_spacing,  y_offset, digit_width, digit_height, digit_thickness, 1)\n    drawDigit(digit_val, left_margin + 2*digit_spacing,  y_offset, digit_width, digit_height, digit_thickness, 1)\n    drawDigit(digit_val, left_margin + 3*digit_spacing, y_offset, digit_width, digit_height, digit_thickness, 1)\n\n    draw_colon(53,26)\n\n    pm_xoffset = 103\n    pm_yoffset = 55\n    if (counter % 2):\n        # 112 is the max right for the am/pm text\n        oled.text(\"am\", pm_xoffset, pm_yoffset, 1)\n\n    else:\n        oled.text(\"pm\", pm_xoffset, pm_yoffset, 1)\n    oled.text(str(digit_val), 0, 54, 1)\n\n    oled.show()\n\ndef draw_colon(x,y):\n    oled.fill_rect(x, y, 2, 2,1)\n    oled.fill_rect(x, y+8, 2, 2,1)\n\ncounter = 0\nwhile True:\n    update_screen(counter)\n    sleep(1)\n    counter += 1\n    if counter > 9:\n        counter = 0\n
"},{"location":"kits/ssd1306-i2c/integrating-localtime/","title":"Integrating the Local Time","text":"
from machine import Pin, I2C\nfrom ssd1306 import SSD1306_I2C\nfrom utime import localtime, sleep\n\n# OLED DATA Pin on GPIO 0\nOLED_SSD1306_SDA_PIN = 0\n# OLED Clock Pin on GPIO 1\nOLED_SSD1306_SCL_PIN = 1\n\nsda=Pin(OLED_SSD1306_SDA_PIN, Pin.OUT)\nscl=Pin(OLED_SSD1306_SCL_PIN, Pin.OUT)\n\n# freq=198000 to 3600000 seem to work.  Use 3000000 as a safe option.\ni2c = I2C(0, scl=scl, sda=sda, freq=3000000)\n\n# Initialize display (128x64 pixels)\noled = SSD1306_I2C(128, 64, i2c)\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 upper-left-corner\n# width and height are the dimensions of the digit\n# thinkness is the width of the line segments\n# color is 1 for white and 0 for black\ndef drawDigit(digit, x, y, width, height, thinkness, color):\n  if digit < 0:\n      return\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 - thinkness # bottem element\n      if (i==6):\n          yOffset = height // 2 - thinkness // 2# bottum\n      # oled.line(x - size, y+yOffset-size, x + size, y+yOffset-size, 1);\n      oled.fill_rect(x, y+yOffset, width, thinkness, 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-thinkness\n\n      oled.fill_rect(x+xOffset, startY, thinkness, endY-startY, color)\n\ndef update_screen(year, month, day, hour, minute, am_pm, colon_on):   \n\n    ## Adjust these to fit the display\n    left_margin = -15\n    y_offset = 16\n    digit_width = 30\n    digit_height = 37\n    digit_spacing = 36\n    digit_thickness = 5\n    oled.fill(0)\n    date_str = f\"{month}/{day}/{year}\"\n    oled.text(date_str, 0, 0, 1)\n    if hour > 12:\n        hour = hour - 12\n    if hour > 10:\n        hour_ten = 1\n    else:\n        hour_ten = -1\n    hour_right = hour % 10\n\n    minute_ten = minute // 10\n    minute_right = minute % 10\n\n    # left digit will be 1 or blank\n    drawDigit(hour_ten, left_margin,  y_offset, digit_width, digit_height, digit_thickness, 1)\n    drawDigit(hour_right, left_margin + 1*digit_spacing,  y_offset, digit_width, digit_height, digit_thickness, 1)\n    drawDigit(minute_ten, left_margin + 2*digit_spacing,  y_offset, digit_width, digit_height, digit_thickness, 1)\n    drawDigit(minute_right, left_margin + 3*digit_spacing, y_offset, digit_width, digit_height, digit_thickness, 1)\n\n    if colon_on:\n        draw_colon(53,26)\n\n    pm_xoffset = 106\n    pm_yoffset = 55\n\n    oled.text(am_pm, pm_xoffset, pm_yoffset, 1)\n\n    oled.show()\n\ndef draw_colon(x,y):\n    oled.fill_rect(x, y, 2, 2,1)\n    oled.fill_rect(x, y+8, 2, 2,1)\n\ncounter = 0\nwhile True:\n    now = localtime()\n    year = now[0]\n    month = now[1]\n    day = now[2]\n    hour = now[3]\n    if hour > 12:\n        am_pm = \"PM\"\n    else:\n        am_pm = \"AM\"\n    minute = now[4]\n\n    update_screen(year, month, day, hour, minute, am_pm, True)\n    sleep(1)\n    update_screen(year, month, day, hour, minute, am_pm, False)\n    sleep(1)\n    counter += 1\n    if counter > 9:\n        counter = 0\n
"},{"location":"kits/ssd1306-i2c/prompt/","title":"Prompt for the SSD1306 I2C Clock","text":""},{"location":"kits/ssd1306-i2c/setting-time/","title":"Manually Setting Time on Power Up","text":"

This clock has two buttons on a breadboard for setting the time. When the clock is powered up or reset, the clock goes into a \"Power-Up\" mode where all the digits are flashing.

Here are the modes we will need:

  1. Normal Clock Running
  2. Setting the Hour
  3. Setting the Minute
  4. Setting the AM/PM
"},{"location":"kits/st7735/","title":"Digital Clock Using the ST7735 LCD","text":""},{"location":"kits/st7735/#overview","title":"Overview","text":"

The ST7735 is a low-cost color LCD with a 160x120 screen resolution. I found the 1.8 inch version on eBay for about $3.50. Because of the low-cost it makes an ideal starter clock display for clocks kits under $10.

The ST7735 uses a standard SPI 7-wire interface with one additional wire for powering the backlight of the display. It works on 3.3 volts.

"},{"location":"kits/st7735/#benefits-of-the-st7735-clock-kit","title":"Benefits of the ST7735 Clock Kit","text":""},{"location":"kits/st7735/#color-display-capabilities","title":"Color Display Capabilities","text":""},{"location":"kits/st7735/#flexible-display-options","title":"Flexible Display Options","text":""},{"location":"kits/st7735/#hardware-integration","title":"Hardware Integration","text":""},{"location":"kits/st7735/#software-features","title":"Software Features","text":""},{"location":"kits/st7735/#cost-effective","title":"Cost-Effective","text":"

The code demonstrates these benefits through features like:

"},{"location":"kits/st7735/dirty-drawing/","title":"Dirty Drawing","text":"

We need to modify the clock program to minimize flicker by only updating the parts of the display that change. This requires tracking the previous state and only redrawing digits that have changed. Here's how we can improve it:

The key improvements in this version include:

  1. State Tracking: Added global variables to track the previous state of all display elements (digits, date, AM/PM, etc.)

  2. Selective Updates: Only redraws elements that have changed since the last update:

  3. Digits are only redrawn when their values change
  4. The date is only updated when it changes
  5. AM/PM indicator only updates when switching between AM and PM
  6. The colon blinks by only updating its small area

  7. Localized Clearing: Instead of clearing the entire screen:

  8. Each digit area is cleared individually before redrawing
  9. Text areas are cleared in their specific regions before updating

  10. Initialization Handling: Added a screen_initialized flag to ensure the background is only drawn once at startup

  11. Improved Update Rate: Reduced the sleep time to 0.1 seconds for smoother updates while still maintaining efficiency

These changes should significantly reduce the flicker since we're only updating the parts of the screen that actually change. The blue background will now only be drawn once at startup, and subsequent updates will be localized to the specific areas that need to change.

To use this improved version, simply replace the contents of your existing clock-main-with-flicker.py with this new code. The functionality remains the same, but the display should be much more stable with minimal flicker.

this is a great example of how selective updates can make a big difference in display quality. Instead of redrawing the entire screen 10 times per second, we're now only updating the specific segments that change. For example, when displaying \"12:59\", only the \":59\" portion will update frequently. The \"12\" will stay static until it changes to \"1:00\". The colons blink by just toggling those few pixels rather than redrawing everything.

This technique (known as \"dirty rectangle\" updating in graphics programming) is especially important for displays like the ST7735 where each pixel update takes a non-trivial amount of time. By minimizing the number of pixels we need to update, we get both better performance and a cleaner visual result.

from machine import Pin, SPI\nfrom utime import sleep, localtime\nimport ST7735\nimport config\n\n# Initialize SPI and display\nspi = machine.SPI(config.SPI_BUS, \n                  sck=Pin(config.SPI_SCL_PIN),\n                  mosi=Pin(config.SPI_SDA_PIN),\n                  baudrate=8000000)\n\ndisplay = ST7735.ST7735(spi, \n                        rst=config.SPI_RESET_PIN,\n                        ce=config.SPI_CS_PIN,\n                        dc=config.SPI_DC_PIN)\ndisplay.reset()\ndisplay.begin()\ndisplay.set_rotation(config.DISPLAY_ROTATION)\n\n# Define colors using RGB565 format\nBACKGROUND_COLOR = display.rgb_to_565(0, 0, 255)  # Blue background\nDIGIT_COLOR = display.rgb_to_565(255, 255, 255)   # White for digits\nTEXT_COLOR = display.rgb_to_565(255, 255, 0)      # Yellow for text\n\n# Previous state tracking\nprev_date = \"\"\nprev_hour_ten = -1\nprev_hour_right = -1\nprev_minute_ten = -1\nprev_minute_right = -1\nprev_second = -1\nprev_am_pm = \"\"\nscreen_initialized = False\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\ndef day_to_str(day_num):\n    \"\"\"Convert a day number (0-6) to a three-letter day abbreviation.\"\"\"\n    days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']\n    if not 0 <= day_num <= 6:\n        raise ValueError(\"Day number must be between 0 and 6\")\n    return days[day_num]\n\ndef month_to_str(month_num):\n    \"\"\"Convert a month number (1-12) to a three-letter month abbreviation.\"\"\"\n    months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', \n             'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']\n    if not 1 <= month_num <= 12:\n        raise ValueError(\"Month number must be between 1 and 12\")\n    return months[month_num - 1]\n\ndef clear_digit_area(x, y, width, height, color):\n    \"\"\"Clear the area where a digit was previously drawn.\"\"\"\n    display.draw_block(x, y, width, height, color)\n\ndef drawDigit(digit, x, y, width, height, thickness, color):\n    \"\"\"Draw a seven-segment digit on the display.\"\"\"\n    if digit < 0:\n        return\n\n    # Clear the area first\n    clear_digit_area(x, y, width, height, BACKGROUND_COLOR)\n\n    segmentOn = segmentMapping[digit]\n\n    # Draw horizontal segments (top, middle, bottom)\n    for i in [0, 3, 6]:\n        if segmentOn[i]:\n            if i == 0:  # top\n                yOffset = 0\n            elif i == 3:  # bottom\n                yOffset = height - thickness\n            else:  # middle\n                yOffset = height // 2 - thickness // 2\n            display.draw_block(x, y + yOffset, width, thickness, color)\n\n    # Draw vertical segments\n    for i in [1, 2, 4, 5]:\n        if segmentOn[i]:\n            if i == 1 or i == 5:  # upper segments\n                startY = y\n                endY = y + height // 2\n            else:  # lower segments\n                startY = y + height // 2\n                endY = y + height\n            xOffset = 0 if (i == 4 or i == 5) else width - thickness\n            display.draw_block(x + xOffset, startY, thickness, endY - startY, color)\n\ndef draw_colon(x, y, color):\n    \"\"\"Draw the blinking colon between hours and minutes.\"\"\"\n    display.draw_block(x, y, 4, 4, color)\n    display.draw_block(x, y + 14, 4, 4, color)\n\ndef update_screen(year, month, day, hour, minute, second, weekday):\n    \"\"\"Update only the changing parts of the display.\"\"\"\n    global prev_date, prev_hour_ten, prev_hour_right, prev_minute_ten\n    global prev_minute_right, prev_second, prev_am_pm, screen_initialized\n\n    # Initialize screen on first run\n    if not screen_initialized:\n        display.fill_screen(BACKGROUND_COLOR)\n        screen_initialized = True\n\n    # Calculate positions\n    left_margin = -15\n    y_offset = 30\n    digit_width = 32\n    digit_height = 50\n    digit_spacing = 42\n    digit_thickness = 6\n\n    # Convert 24-hour to 12-hour format\n    display_hour = hour if hour <= 12 else hour - 12\n    if display_hour == 0:\n        display_hour = 12\n\n    # Format date string\n    date_str = f\"{day_to_str(weekday)} {month_to_str(month)} {day} {year}\"\n\n    # Update date if changed\n    if date_str != prev_date:\n        display._color = TEXT_COLOR\n        # Clear previous date area\n        display.draw_block(4, 4, 160, 10, BACKGROUND_COLOR)\n        display.p_string(4, 4, date_str)\n        prev_date = date_str\n\n    # Split time into digits\n    hour_ten = display_hour // 10 if display_hour >= 10 else -1\n    hour_right = display_hour % 10\n    minute_ten = minute // 10\n    minute_right = minute % 10\n\n    # Update digits only if they've changed\n    if hour_ten != prev_hour_ten:\n        drawDigit(hour_ten, left_margin, y_offset, digit_width, digit_height, digit_thickness, DIGIT_COLOR)\n        prev_hour_ten = hour_ten\n\n    if hour_right != prev_hour_right:\n        drawDigit(hour_right, left_margin + digit_spacing, y_offset, digit_width, digit_height, digit_thickness, DIGIT_COLOR)\n        prev_hour_right = hour_right\n\n    if minute_ten != prev_minute_ten:\n        drawDigit(minute_ten, left_margin + 2*digit_spacing+10, y_offset, digit_width, digit_height, digit_thickness, DIGIT_COLOR)\n        prev_minute_ten = minute_ten\n\n    if minute_right != prev_minute_right:\n        drawDigit(minute_right, left_margin + 3*digit_spacing+10, y_offset, digit_width, digit_height, digit_thickness, DIGIT_COLOR)\n        prev_minute_right = minute_right\n\n    # Handle colon blinking - clear and redraw based on seconds\n    colon_x = left_margin + digit_spacing + digit_width + 8\n    colon_y = y_offset + 15\n    if second % 2:\n        draw_colon(colon_x, colon_y, DIGIT_COLOR)\n    else:\n        draw_colon(colon_x, colon_y, BACKGROUND_COLOR)\n\n    # Update AM/PM indicator if needed\n    am_pm_str = \"PM\" if hour >= 12 else \"AM\"\n    if am_pm_str != prev_am_pm:\n        display._color = TEXT_COLOR\n        display.draw_block(left_margin + 3*digit_spacing + digit_width, y_offset + 60, 20, 10, BACKGROUND_COLOR)\n        display.p_string(left_margin + 3*digit_spacing + digit_width, y_offset + 60, am_pm_str)\n        prev_am_pm = am_pm_str\n\n    # Update seconds display if changed\n    if second != prev_second:\n        display._color = TEXT_COLOR\n        display.draw_block(4, y_offset + digit_height + 10, 20, 10, BACKGROUND_COLOR)\n        display.p_string(4, y_offset + digit_height + 10, f\"{second:02d}\")\n        prev_second = second\n\n# Main loop\nwhile True:\n    now = localtime()\n    year = now[0]\n    month = now[1]\n    day = now[2]\n    hour = now[3]\n    minute = now[4]\n    second = now[5]\n    weekday = now[6]\n\n    update_screen(year, month, day, hour, minute, second, weekday)\n    sleep(0.1)  # Shorter sleep for more responsive updates\n
"},{"location":"kits/st7735/drawing-four-digits/","title":"Drawing Digits","text":"

Let's now draw four digits on the screen. This will have a lot of flicker which we will clean up in the next version.

from machine import Pin, SPI\nfrom utime import sleep, localtime\nimport ST7735\nimport config\n\n# Initialize SPI and display\nspi = machine.SPI(config.SPI_BUS, \n                  sck=Pin(config.SPI_SCL_PIN),\n                  mosi=Pin(config.SPI_SDA_PIN),\n                  baudrate=8000000)\n\ndisplay = ST7735.ST7735(spi, \n                        rst=config.SPI_RESET_PIN,\n                        ce=config.SPI_CS_PIN,\n                        dc=config.SPI_DC_PIN)\ndisplay.reset()\ndisplay.begin()\ndisplay.set_rotation(config.DISPLAY_ROTATION)\n\n# Define colors using RGB565 format\nBACKGROUND_COLOR = display.rgb_to_565(0, 0, 255)  # Blue background\nDIGIT_COLOR = display.rgb_to_565(255, 255, 255)   # White for digits\nTEXT_COLOR = display.rgb_to_565(255, 255, 0)      # Yellow for text\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\ndef day_to_str(day_num):\n    \"\"\"Convert a day number (0-6) to a three-letter day abbreviation.\"\"\"\n    days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']\n    if not 0 <= day_num <= 6:\n        raise ValueError(\"Day number must be between 0 and 6\")\n    return days[day_num]\n\ndef month_to_str(month_num):\n    \"\"\"Convert a month number (1-12) to a three-letter month abbreviation.\"\"\"\n    months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', \n             'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']\n    if not 1 <= month_num <= 12:\n        raise ValueError(\"Month number must be between 1 and 12\")\n    return months[month_num - 1]\n\ndef drawDigit(digit, x, y, width, height, thickness, color):\n    \"\"\"Draw a seven-segment digit on the display.\"\"\"\n    if digit < 0:\n        return\n    segmentOn = segmentMapping[digit]\n\n    # Draw horizontal segments (top, middle, bottom)\n    for i in [0, 3, 6]:\n        if segmentOn[i]:\n            if i == 0:  # top\n                yOffset = 0\n            elif i == 3:  # bottom\n                yOffset = height - thickness\n            else:  # middle\n                yOffset = height // 2 - thickness // 2\n            display.draw_block(x, y + yOffset, width, thickness, color)\n\n    # Draw vertical segments\n    for i in [1, 2, 4, 5]:\n        if segmentOn[i]:\n            if i == 1 or i == 5:  # upper segments\n                startY = y\n                endY = y + height // 2\n            else:  # lower segments\n                startY = y + height // 2\n                endY = y + height\n            xOffset = 0 if (i == 4 or i == 5) else width - thickness\n            display.draw_block(x + xOffset, startY, thickness, endY - startY, color)\n\ndef draw_colon(x, y, color):\n    \"\"\"Draw the blinking colon between hours and minutes.\"\"\"\n    display.draw_block(x, y, 4, 4, color)\n    display.draw_block(x, y + 14, 4, 4, color)\n\ndef update_screen(year, month, day, hour, minute, second, weekday):\n    \"\"\"Update the entire display with the current time and date.\"\"\"\n    # Clear screen with background color\n    display.fill_screen(BACKGROUND_COLOR)\n\n    # Set text color for the date\n    display._color = TEXT_COLOR\n\n    # Display the date at the top\n    date_str = f\"{day_to_str(weekday)} {month_to_str(month)} {day} {year}\"\n    display.p_string(4, 4, date_str)\n\n    # Convert 24-hour to 12-hour format\n    display_hour = hour if hour <= 12 else hour - 12\n    if display_hour == 0:\n        display_hour = 12\n\n    # Calculate digit positions\n    left_margin = -15\n    y_offset = 30\n    digit_width = 32\n    digit_height = 50\n    digit_spacing = 42\n    digit_thickness = 6\n\n    # Split hours and minutes into digits\n    hour_ten = display_hour // 10 if display_hour >= 10 else -1\n    hour_right = display_hour % 10\n    minute_ten = minute // 10\n    minute_right = minute % 10\n\n    # Draw all digits\n    drawDigit(hour_ten, left_margin, y_offset, digit_width, digit_height, digit_thickness, DIGIT_COLOR)\n    drawDigit(hour_right, left_margin + digit_spacing, y_offset, digit_width, digit_height, digit_thickness, DIGIT_COLOR)\n    drawDigit(minute_ten, left_margin + 2*digit_spacing+10, y_offset, digit_width, digit_height, digit_thickness, DIGIT_COLOR)\n    drawDigit(minute_right, left_margin + 3*digit_spacing+10, y_offset, digit_width, digit_height, digit_thickness, DIGIT_COLOR)\n\n    # Draw the colon if it's an even second (for blinking effect)\n    if second % 2:\n        draw_colon(left_margin + digit_spacing + digit_width + 8, y_offset + 15, DIGIT_COLOR)\n\n    # Display AM/PM indicator\n    display._color = TEXT_COLOR\n    am_pm_str = \"PM\" if hour >= 12 else \"AM\"\n    display.p_string(left_margin + 3*digit_spacing + digit_width, y_offset + 60, am_pm_str)\n\n    # Display seconds\n    display.p_string(4, y_offset + digit_height + 10, f\"{second}\")\n\n# Main loop\nwhile True:\n    now = localtime()\n    year = now[0]\n    month = now[1]\n    day = now[2]\n    hour = now[3]\n    minute = now[4]\n    second = now[5]\n    weekday = now[6]\n\n    update_screen(year, month, day, hour, minute, second, weekday)\n    sleep(1)\n
"},{"location":"kits/st7735/hello-world/","title":"Hello World","text":"
# ST7735 LCD Test\nimport machine\nimport ST7735\n\n# SCL=2 and SDK=3 implied for bus 0\nspi = machine.SPI(0, baudrate=8000000)\nd = ST7735.ST7735(spi, rst=4, ce=6, dc=5)\nd.reset()\nd.begin()\nd.set_rotation(1)\nd._bground = 0xffff\n# white\nd.fill_screen(d._bground)\n# make background all white\nd._color = 0x0000 # black ink\nd.p_string(10,10,'Hello World!')\n
"},{"location":"kits/st7735/hello-world/#config-file","title":"Config File","text":"
# Hardware configuration file for ST7735 clock\n\nSPI_BUS = 0\nSPI_SCL_PIN = 2 # Clock\nSPI_SDA_PIN = 3 # labeled SDI(MOSI) on the back of the display\nSPI_RESET_PIN = 4 # Reset\nSPI_DC_PIN = 5 # Data/command\nSPI_CS_PIN = 6 # Chip Select\n\n# Screen Dimensions\nDISPLAY_WIDTH=160\nDISPLAY_HEIGHT=128\nDISPLAY_ROTATION = 1\n\n# Use builtin pull ups\nMODE_PIN = 14 # one up from lower-left corner\nINCREMENT_PIN = 15 # lower left corner with USB on top\nDECREMENT_PIN = 16 # lower left corner with USB on top\n
"},{"location":"kits/st7735/multiple-fonts/","title":"Multiple Fonts","text":"

Looking at the provided code, I can help explain the font situation and suggest some improvements for working with larger fonts on the ST7735 display.

Currently, the code is using a basic 5x7 pixel font (\"font5x7.fnt\") which is indeed quite small and pixelated. There are several ways to handle larger, better-looking fonts on this display:

"},{"location":"kits/st7735/multiple-fonts/#1-bdfpcf-fonts","title":"1. BDF/PCF Fonts:","text":"

You could convert standard BDF/PCF fonts into a custom format that works with your display. Here's how we could implement this:

To use this improved font system, you would:

  1. Convert a BDF font to our binary format using the convert_bdf_to_binary() function
  2. Load and use the font in your main code like this:
from custom_font_handler import FontHandler\n\n# Initialize your display as before\nspi = machine.SPI(0, baudrate=8000000)\nd = ST7735.ST7735(spi, rst=4, ce=6, dc=5)\nd.reset()\nd.begin()\n\n# Create font handler and load font\nfont_handler = FontHandler(d)\nfont_handler.load_font('your_converted_font.bin')\n\n# Use the font\nfont_handler.text(10, 10, \"Hello World!\", d.rgb_to_565(255, 255, 255))\n
"},{"location":"kits/st7735/multiple-fonts/#2-bitmap-fonts","title":"2. Bitmap Fonts:","text":"

Another approach would be to store pre-rendered characters as bitmap images and display them using your existing bmp() function. This would give you the best quality but would use more storage space.

"},{"location":"kits/st7735/multiple-fonts/#3-vector-fonts","title":"3. Vector Fonts:","text":"

While more complex, you could implement basic vector font rendering. This would give you scalable text but would require more processing power.

For the best balance of quality and performance, I recommend going with the BDF font approach (option 1). You can find many BDF fonts online, including larger sizes that would look better on your display. Some good fonts to start with would be:

To get started, you would:

  1. Download a BDF font
  2. Convert it using the provided converter
  3. Copy the binary font file to your Pico
  4. Use the FontHandler class to render text

Would you like me to provide an example using a specific BDF font, or would you prefer help implementing one of the other approaches?

"},{"location":"kits/st7735/scaling-fonts/","title":"Scaling Fonts","text":"

We can modify the code to use a larger font for the date. Looking at the ST7735.py code, I notice it's using a built-in font5x7 font, but there's no built-in option for larger fonts. However, we can draw larger text by scaling up each character.

from machine import Pin, SPI\nfrom utime import sleep, localtime\nimport ST7735\nimport config\n\n# Initialize SPI and display\nspi = machine.SPI(config.SPI_BUS, \n                  sck=Pin(config.SPI_SCL_PIN),\n                  mosi=Pin(config.SPI_SDA_PIN),\n                  baudrate=8000000)\n\ndisplay = ST7735.ST7735(spi, \n                        rst=config.SPI_RESET_PIN,\n                        ce=config.SPI_CS_PIN,\n                        dc=config.SPI_DC_PIN)\ndisplay.reset()\ndisplay.begin()\ndisplay.set_rotation(config.DISPLAY_ROTATION)\n\n# Define colors using RGB565 format\nBACKGROUND_COLOR = display.rgb_to_565(0, 0, 255)  # Blue background\nDIGIT_COLOR = display.rgb_to_565(255, 255, 255)   # White for digits\nTEXT_COLOR = display.rgb_to_565(255, 255, 0)      # Yellow for text\n\n# Scaled text drawing functions\ndef draw_scaled_char(display, x, y, ch, scale=1):\n    \"\"\"Draw a single character at the specified scale\"\"\"\n    fp = (ord(ch)-0x20) * 5\n    f = open('font5x7.fnt','rb')\n    f.seek(fp)\n    b = f.read(5)\n    char_buf = bytearray(b)\n    char_buf.append(0)\n\n    # Scale up the character\n    char_image = bytearray()\n    for bit in range(8):\n        # Repeat each row scale times\n        for _ in range(scale):\n            for c in range(6):\n                # Repeat each pixel scale times\n                pixel = ((char_buf[c]>>bit) & 1)>0\n                for _ in range(scale):\n                    if pixel:\n                        char_image.append(display._color >> 8)\n                        char_image.append(display._color & 0xff)\n                    else:\n                        char_image.append(display._bground >> 8)\n                        char_image.append(display._bground & 0xff)\n    display.draw_bmp(x, y, 6*scale, 8*scale, char_image)\n    f.close()\n\ndef draw_scaled_string(display, x, y, text, scale=1):\n    \"\"\"Draw a string of text at the specified scale\"\"\"\n    for ch in text:\n        draw_scaled_char(display, x, y, ch, scale)\n        x += 6 * scale\n\n# Previous state tracking\nprev_date = \"\"\nprev_hour_ten = -1\nprev_hour_right = -1\nprev_minute_ten = -1\nprev_minute_right = -1\nprev_second = -1\nprev_am_pm = \"\"\nscreen_initialized = False\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\ndef day_to_str(day_num):\n    \"\"\"Convert a day number (0-6) to a three-letter day abbreviation.\"\"\"\n    days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']\n    if not 0 <= day_num <= 6:\n        raise ValueError(\"Day number must be between 0 and 6\")\n    return days[day_num]\n\ndef month_to_str(month_num):\n    \"\"\"Convert a month number (1-12) to a three-letter month abbreviation.\"\"\"\n    months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', \n             'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']\n    if not 1 <= month_num <= 12:\n        raise ValueError(\"Month number must be between 1 and 12\")\n    return months[month_num - 1]\n\ndef clear_digit_area(x, y, width, height, color):\n    \"\"\"Clear the area where a digit was previously drawn.\"\"\"\n    display.draw_block(x, y, width, height, color)\n\ndef drawDigit(digit, x, y, width, height, thickness, color):\n    \"\"\"Draw a seven-segment digit on the display.\"\"\"\n    if digit < 0:\n        return\n\n    # Clear the area first\n    clear_digit_area(x, y, width, height, BACKGROUND_COLOR)\n\n    segmentOn = segmentMapping[digit]\n\n    # Draw horizontal segments (top, middle, bottom)\n    for i in [0, 3, 6]:\n        if segmentOn[i]:\n            if i == 0:  # top\n                yOffset = 0\n            elif i == 3:  # bottom\n                yOffset = height - thickness\n            else:  # middle\n                yOffset = height // 2 - thickness // 2\n            display.draw_block(x, y + yOffset, width, thickness, color)\n\n    # Draw vertical segments\n    for i in [1, 2, 4, 5]:\n        if segmentOn[i]:\n            if i == 1 or i == 5:  # upper segments\n                startY = y\n                endY = y + height // 2\n            else:  # lower segments\n                startY = y + height // 2\n                endY = y + height\n            xOffset = 0 if (i == 4 or i == 5) else width - thickness\n            display.draw_block(x + xOffset, startY, thickness, endY - startY, color)\n\ndef draw_colon(x, y, color):\n    \"\"\"Draw the blinking colon between hours and minutes.\"\"\"\n    display.draw_block(x, y, 4, 4, color)\n    display.draw_block(x, y + 14, 4, 4, color)\n\ndef update_screen(year, month, day, hour, minute, second, weekday):\n    \"\"\"Update only the changing parts of the display.\"\"\"\n    global prev_date, prev_hour_ten, prev_hour_right, prev_minute_ten\n    global prev_minute_right, prev_second, prev_am_pm, screen_initialized\n\n    # Initialize screen on first run\n    if not screen_initialized:\n        display.fill_screen(BACKGROUND_COLOR)\n        screen_initialized = True\n\n    # Calculate positions\n    left_margin = -15\n    y_offset = 30\n    digit_width = 32\n    digit_height = 50\n    digit_spacing = 42\n    digit_thickness = 6\n\n    # Convert 24-hour to 12-hour format\n    display_hour = hour if hour <= 12 else hour - 12\n    if display_hour == 0:\n        display_hour = 12\n\n    # Format date string - use 2-digit year to save space\n    date_str = f\"{day_to_str(weekday)} {month_to_str(month)} {day} {str(year)[2:]}\"\n\n    # Update date if changed\n    if date_str != prev_date:\n        display._color = TEXT_COLOR\n        # Clear previous date area (increased height for larger font)\n        display.draw_block(4, 4, 160, 20, BACKGROUND_COLOR)\n        draw_scaled_string(display, 4, 4, date_str, scale=2)  # Scale up the date text\n        prev_date = date_str\n\n    # Split time into digits\n    hour_ten = display_hour // 10 if display_hour >= 10 else -1\n    hour_right = display_hour % 10\n    minute_ten = minute // 10\n    minute_right = minute % 10\n\n    # Update digits only if they've changed\n    if hour_ten != prev_hour_ten:\n        drawDigit(hour_ten, left_margin, y_offset, digit_width, digit_height, digit_thickness, DIGIT_COLOR)\n        prev_hour_ten = hour_ten\n\n    if hour_right != prev_hour_right:\n        drawDigit(hour_right, left_margin + digit_spacing, y_offset, digit_width, digit_height, digit_thickness, DIGIT_COLOR)\n        prev_hour_right = hour_right\n\n    if minute_ten != prev_minute_ten:\n        drawDigit(minute_ten, left_margin + 2*digit_spacing+10, y_offset, digit_width, digit_height, digit_thickness, DIGIT_COLOR)\n        prev_minute_ten = minute_ten\n\n    if minute_right != prev_minute_right:\n        drawDigit(minute_right, left_margin + 3*digit_spacing+10, y_offset, digit_width, digit_height, digit_thickness, DIGIT_COLOR)\n        prev_minute_right = minute_right\n\n    # Handle colon blinking - clear and redraw based on seconds\n    colon_x = left_margin + digit_spacing + digit_width + 8\n    colon_y = y_offset + 15\n    if second % 2:\n        draw_colon(colon_x, colon_y, DIGIT_COLOR)\n    else:\n        draw_colon(colon_x, colon_y, BACKGROUND_COLOR)\n\n    # Update AM/PM indicator if needed\n    am_pm_str = \"PM\" if hour >= 12 else \"AM\"\n    if am_pm_str != prev_am_pm:\n        display._color = TEXT_COLOR\n        display.draw_block(left_margin + 3*digit_spacing + digit_width, y_offset + 60, 20, 10, BACKGROUND_COLOR)\n        display.p_string(left_margin + 3*digit_spacing + digit_width, y_offset + 60, am_pm_str)\n        prev_am_pm = am_pm_str\n\n    # Update seconds display if changed\n    if second != prev_second:\n        display._color = TEXT_COLOR\n        display.draw_block(4, y_offset + digit_height + 10, 20, 10, BACKGROUND_COLOR)\n        display.p_string(4, y_offset + digit_height + 10, f\"{second:02d}\")\n        prev_second = second\n\n# Main loop\nwhile True:\n    now = localtime()\n    year = now[0]\n    month = now[1]\n    day = now[2]\n    hour = now[3]\n    minute = now[4]\n    second = now[5]\n    weekday = now[6]\n\n    update_screen(year, month, day, hour, minute, second, weekday)\n    sleep(0.1)  # Shorter sleep for more responsive updates\n

We have made several changes to support larger text:

Now the date should be displayed at double the original size. The scaling works by multiplying each pixel in the original font, so a scale of 2 means each pixel becomes a 2x2 block of pixels, making the text larger while maintaining its proportions.

You can adjust the scale factor by changing the scale=2 parameter in the p_string call. For example:

scale=1 is the original size scale=2 doubles the size scale=3 triples the size

"},{"location":"kits/st7735/set-mode-buttons/","title":"Set the Mode Buttons","text":"

We need a way to monitor the Set/Mode, Increment and Decrement buttons to manually set the time. Here is a program that will monitor the three buttons and change the mode.

from utime import sleep, ticks_ms, ticks_diff\nfrom machine import Pin\n\n# Global Variable Default Settings\nmode = 0\nhour = 6\nminute = 30\npm_indicator = True\nmode_names = ['run', 'set hour', 'set minute', 'set AM/PM']\nmode_count = len(mode_names)\n\n# Button Pin GPIO Configuration\nCHANGE_MODE_PIN = 13\nINCREMENT_PIN = 14\nDECREMENT_PIN = 15\nDEBOUNCE_TIME_MS = 200\n\n# Create 3 button objects as inputs with pullup resistors\nchange_mode_pin = Pin(CHANGE_MODE_PIN, Pin.IN, Pin.PULL_UP)\nincrement_pin = Pin(INCREMENT_PIN, Pin.IN, Pin.PULL_UP)\ndecrement_pin = Pin(DECREMENT_PIN, Pin.IN, Pin.PULL_UP)\n\n# Variables to track last button press times for debouncing\nlast_mode_press = 0\nlast_increment_press = 0\nlast_decrement_press = 0\n\n# These functions gets called every time a button is pressed\ndef mode_button_pressed(pin):\n    global mode, last_mode_press\n    current_time = ticks_ms()\n    if ticks_diff(current_time, last_mode_press) > DEBOUNCE_TIME_MS:\n        mode += 1\n        mode = mode % mode_count\n        last_mode_press = current_time\n\ndef increment_button_pressed(pin):\n    global mode, hour, minute, pm_indicator, last_increment_press\n    current_time = ticks_ms()\n    if ticks_diff(current_time, last_increment_press) > DEBOUNCE_TIME_MS:\n        if mode == 1:\n            hour = (hour % 12) + 1  # Increment hour from 1-12 properly\n        if mode == 2:\n            minute += 1\n            minute = minute % 60  # Fixed: minute should go from 0 to 59\n        if mode == 3:\n            pm_indicator = not pm_indicator\n        last_increment_press = current_time\n\ndef decrement_button_pressed(pin):\n    global mode, hour, minute, pm_indicator, last_decrement_press\n    current_time = ticks_ms()\n    if ticks_diff(current_time, last_decrement_press) > DEBOUNCE_TIME_MS:\n        if mode == 1:\n            hour -= 1\n            if hour <= 0:  # Handle wrapping from 1 to 12\n                hour = 12\n        if mode == 2:\n            minute -= 1\n            if minute < 0:  # Handle wrapping from 0 to 59\n                minute = 59\n        if mode == 3:\n            pm_indicator = not pm_indicator\n        last_decrement_press = current_time\n\n# Register the button handler functions using the irq setter method for each pin\nchange_mode_pin.irq(trigger=Pin.IRQ_FALLING, handler=mode_button_pressed)\nincrement_pin.irq(trigger=Pin.IRQ_FALLING, handler=increment_button_pressed)\ndecrement_pin.irq(trigger=Pin.IRQ_FALLING, handler=decrement_button_pressed)\n\n# Only print on change - these variables store the old values\nlast_mode = mode\nlast_hour = hour\nlast_minute = minute\nlast_pm_indicator = pm_indicator\nprint(\"Default values: mode:\", mode, \"hour:\", hour, \"minute:\", minute, \"pm indicator:\", pm_indicator)\n\nwhile True:\n    if mode != last_mode:\n        print(\"new mode:\", mode, mode_names[mode])\n        last_mode = mode\n    if hour != last_hour:\n        print(\"new hour:\", hour)\n        last_hour = hour\n    if minute != last_minute:\n        print(\"new minute:\", minute)\n        last_minute = minute\n    if pm_indicator != last_pm_indicator:\n        print(\"new pm indicator:\", pm_indicator)\n        last_pm_indicator = pm_indicator\n    # sleep(.01)\n
"},{"location":"kits/st7735/single-digit/","title":"Draw a Single Digit on the ST7735 LCD","text":"
# Draw a single digit on the ST7735 LCD\nimport machine\nimport ST7735\nfrom utime import sleep\n\n# Initialize SPI and display\nspi = machine.SPI(0, baudrate=8000000)\nd = ST7735.ST7735(spi, rst=4, ce=6, dc=5)\nd.reset()\nd.begin()\nd.set_rotation(1)  # Rotate to landscape mode\n\n# Define colors using RGB565 format\nBLUE_BACKGROUND = d.rgb_to_565(0, 0, 255)  # Blue background\nWHITE_DIGIT = d.rgb_to_565(255, 255, 255)  # White for digits\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\ndef drawDigit(digit, x, y, width, height, thickness, color):\n    # Get segments that are on for this digit\n    segmentOn = segmentMapping[digit]\n\n    # Draw 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            elif i == 3:  # bottom\n                yOffset = height - thickness\n            else:  # middle\n                yOffset = height // 2 - thickness // 2\n\n            d.draw_block(x, y + yOffset, width, thickness, color)\n\n    # Draw vertical segments (upper right, lower right, lower left, upper left)\n    for i in [1, 2, 4, 5]:\n        if segmentOn[i]:\n            # Set vertical position\n            if i == 1 or i == 5:  # upper segments\n                startY = y\n                endY = y + height // 2\n            else:  # lower segments\n                startY = y + height // 2\n                endY = y + height\n\n            # Set horizontal position\n            if i == 4 or i == 5:  # left segments\n                xOffset = 0\n            else:  # right segments\n                xOffset = width - thickness\n\n            d.draw_block(x + xOffset, startY, thickness, endY - startY, color)\n\ndef update_screen(digit_val):\n    # Clear screen with blue background\n    d.fill_screen(BLUE_BACKGROUND)\n\n    # Draw the digit in white\n    drawDigit(digit_val, 50, 40, 40, 60, 8, WHITE_DIGIT)\n\n    # Add text label\n    d._color = WHITE_DIGIT  # Set text color to white\n    d.p_string(10, 10, f'Digit: {digit_val}')\n\n# Main loop\ncounter = 0\nwhile True:\n    update_screen(counter)\n    sleep(1)\n    counter = (counter + 1) % 10\n
"},{"location":"kits/stopwatch/","title":"Stopwatch","text":""},{"location":"kits/stopwatch/#overview","title":"Overview","text":"

Here is a two-button stopwatch that uses the OLED display. The first button is called the Start/Stop button. When you first press start it starts to add up the time since it was pressed. Pressing it again will stop collecting time but still remember the total accumulated time while the stopwatch was running.

The second button is the Reset button. When you press Reset the total time accumulated will be reset back to 0.

"},{"location":"kits/stopwatch/#the-microsimulation","title":"The MicroSimulation","text":""},{"location":"kits/stopwatch/#program-structure","title":"Program Structure","text":"

Let me break down this stopwatch program in a way that's easy to understand!

Let's think of this like building a digital stopwatch step by step:

"},{"location":"kits/stopwatch/#step-1-the-parts-hardware-setup","title":"STEP 1 - The Parts (\"Hardware Setup\")","text":""},{"location":"kits/stopwatch/#step-2-setting-up-our-variables-global-variables","title":"STEP 2 - Setting Up Our Variables (\"Global Variables\")","text":""},{"location":"kits/stopwatch/#step-3-the-startstop-button-function","title":"STEP 3 - The Start/Stop Button Function","text":""},{"location":"kits/stopwatch/#step-4-the-reset-button-function","title":"STEP 4 - The Reset Button Function","text":""},{"location":"kits/stopwatch/#step-5-making-time-look-nice-format_time","title":"STEP 5 - Making Time Look Nice (\"format_time\")","text":""},{"location":"kits/stopwatch/#step-6-updating-the-display","title":"STEP 6 - Updating the Display","text":""},{"location":"kits/stopwatch/#step-7-the-main-program-loop","title":"STEP 7 - The Main Program Loop","text":"

Think of it like this: Imagine you're timing a race with a regular stopwatch. You: 1. Press start when the race begins (Start button) 2. Press stop when someone finishes (Stop button) 3. Can pause and restart for multiple racers (Start/Stop button again) 4. Clear it to zero for a new race (Reset button)

This program does all that, but digitally! The main difference is that it's much more precise - it can measure down to milliseconds (thousandths of a second), which would be impossible with a regular handheld stopwatch.

"},{"location":"kits/stopwatch/#sample-code","title":"Sample Code","text":"
from mp_button import Button\nfrom machine import Pin, SPI\nimport ssd1306\nfrom utime import sleep, ticks_ms, ticks_diff\n\n# lower right corner - button closes path to GND\n# HARDWARE PIN CONFIGURATION\n# LEDs\nPICO_ONBOARD_LED_PIN = 25\n# Button pins\nSTOPWATCH_START_STOP_PIN = 14\nSTOPWATCH_RESET_PIN = 15\n# Display pins\nDISPLAY_SCL_PIN = 2\nDISPLAY_SDA_PIN = 3\nDISPLAY_RES_PIN = 4\nDISPLAY_DC_PIN = 5\nDISPLAY_CS_PIN = 6\n\n# configure the pins\nstart_stop_pin = Pin(STOPWATCH_START_STOP_PIN, Pin.IN, Pin.PULL_UP)\nreset_pin = Pin(STOPWATCH_RESET_PIN, Pin.IN, Pin.PULL_UP)\n\nled = machine.Pin(PICO_ONBOARD_LED_PIN, machine.Pin.OUT)\n\nSCL=Pin(DISPLAY_SCL_PIN)\nSDA=Pin(DISPLAY_SDA_PIN)\nspi=SPI(0, sck=SCL, mosi=SDA)\nRES = Pin(DISPLAY_RES_PIN)\nDC = Pin(DISPLAY_DC_PIN)\nCS = machine.Pin(DISPLAY_CS_PIN)\noled = ssd1306.SSD1306_SPI(128, 64, spi, DC, RES, CS)\n\n# Global variables\nSTOPPED = const(0)\nRUNNING = const(1)\nstopwatch_state = STOPPED\nstopwatch_starttime = 0\nstopwatch_elapsed_time = 0  # Renamed from stopwatch_resume_time for clarity\nlast_start_stop_press = 0\nlast_reset_press = 0\nDEBOUNCE_MS = 250\n\ndef start_stop_irq(pin):\n    global last_start_stop_press, stopwatch_state, stopwatch_starttime, stopwatch_elapsed_time\n    current_time = ticks_ms()\n    if ticks_diff(current_time, last_start_stop_press) > DEBOUNCE_MS:\n        last_start_stop_press = current_time\n\n        if stopwatch_state == STOPPED:\n            stopwatch_state = RUNNING\n            stopwatch_starttime = ticks_ms()\n        else:\n            # Calculate the time elapsed since last start\n            stopwatch_elapsed_time += ticks_diff(ticks_ms(), stopwatch_starttime)\n            stopwatch_state = STOPPED\n\ndef reset_irq(pin):\n    global last_reset_press, stopwatch_state, stopwatch_elapsed_time\n    current_time = ticks_ms()\n    if ticks_diff(current_time, last_reset_press) > DEBOUNCE_MS:\n        last_reset_press = current_time\n        stopwatch_state = STOPPED\n        stopwatch_elapsed_time = 0\n\n# Here are the Interupt handlers\nstart_stop_pin.irq(trigger=Pin.IRQ_RISING, handler=start_stop_irq)\nreset_pin.irq(trigger=Pin.IRQ_FALLING, handler=reset_irq)\n\n\ndef format_time(milliseconds):\n    \"\"\"Convert milliseconds to formatted time string (MM:SS.mmm)\"\"\"\n    seconds = milliseconds // 1000\n    ms = milliseconds % 1000\n    minutes = seconds // 60\n    seconds = seconds % 60\n    return f\"{minutes:02d}:{seconds:02d}.{ms:03d}\"\n\ndef update_screen(state, elapsed_time):\n    global stopwatch_starttime\n    oled.fill(0)\n    oled.text(\"stopwatch lab\", 0, 0, 1)\n    state_text = \"RUNNING\" if state == RUNNING else \"STOPPED\"\n    oled.text(state_text, 0, 20, 1)\n\n    if state == RUNNING:\n        current_time = elapsed_time + ticks_diff(ticks_ms(), stopwatch_starttime)\n        oled.text(format_time(current_time), 0, 40, 1)\n    else:\n        oled.text(format_time(elapsed_time), 0, 40, 1)\n    oled.show()\n\n# Main loop\nwhile True:\n    update_screen(stopwatch_state, stopwatch_elapsed_time)\n    sleep(0.1)\n
"},{"location":"kits/tm1637/","title":"TM1637 LED Display","text":"

Setting the Time

The TM1637 LED digital display is an incredibly popular and cost-effective 4-digit 7-segment display module widely used in hobbyist electronics and educational projects. At around 30 cents per unit when purchased in bulk, it offers exceptional value for clock projects, counters, and basic numeric displays.

Key features: - 4 digits with decimal points - Built-in controller chip that handles multiplexing - Simple 2-wire interface (clock and data) - 3.3V or 5V compatible - Brightness control - Colon indicator for clock displays

What makes the TM1637 particularly appealing for MicroPython projects is its straightforward programming interface. Using just two pins (clock and data), you can control all segments and digits through a simple serial protocol. The module handles all the multiplexing internally, making it much easier to use than raw 7-segment displays.

The TM1637 LED Clock is a low-cost way to get started. It is simple to setup and use and only requires the user to make 4 connections.

"},{"location":"kits/tm1637/#parts-list","title":"Parts List","text":"
  1. 1/2 Size 400 tie solderless Breadboard
  2. 4 Male-Female 20cm jumper wires (Dupont Cables)
  3. Raspberry Pi Pico (or Pico W)
  4. TM1637 LED Clock display
  5. Real time clock
"},{"location":"kits/tm1637/#connection-diagram","title":"Connection Diagram","text":""},{"location":"kits/tm1637/#the-micropython-preamble","title":"The MicroPython Preamble","text":"
import tm1637\nfrom machine import Pin, RTC\nfrom utime import sleep, localtime, ticks_ms\n\n# Initialize display\ntm = tm1637.TM1637(clk=Pin(0), dio=Pin(1))\n

Here's some example MicroPython code to test various display features using your preamble:

# Basic number display\ntm.numbers(12, 59)  # Display 12:59\nsleep(1)\n\n# Show temperature with decimal\ntm.temperature(23.4)  # Display 23.4\nsleep(1)\n\n# Control brightness (0-7)\ntm.brightness(2)  # Set to medium brightness\n\n# Blank the display\ntm.show('    ')\nsleep(1)\n\n# Show individual digits\ntm.write([0, 1, 2, 3])  # Display 0123\nsleep(1)\n\n# Toggle colon\ntm.show('1234', True)  # Show 12:34 with colon\nsleep(1)\ntm.show('1234', False)  # Show 1234 without colon\nsleep(1)\n\n# Scroll some numbers\nfor i in range(10):\n    tm.number(i * 111)\n    sleep(0.5)\n\n# Test all segments\ntm.write([0x7f, 0x7f, 0x7f, 0x7f])  # All segments on\nsleep(1)\n

Given its popularity, there are several MicroPython libraries available for the TM1637. The example above uses one of the most common libraries. The display's simplicity and reliability have made it a staple in DIY electronics, particularly for clock projects, temperature displays, and basic counters. You'll find it used in countless Arduino and Raspberry Pi Pico projects, making it one of the most recognizable LED display modules in the maker community.

"},{"location":"kits/tm1637/manual-time-setting/","title":"Manually Setting the Time","text":"

Setting the Time

Next, we will create a complete program that implements a clock with manual time setting using the three buttons. This is a great practical example that teaches state management and user input handling.

import tm1637\nfrom machine import Pin, RTC\nfrom utime import sleep, localtime, ticks_ms\n\n# Initialize display and RTC\ntm = tm1637.TM1637(clk=Pin(0), dio=Pin(1))\nrtc = RTC()\n\n# Initialize buttons with pull-up resistors\nmode_btn = Pin(2, Pin.IN, Pin.PULL_UP)\nnext_btn = Pin(3, Pin.IN, Pin.PULL_UP)\nprev_btn = Pin(4, Pin.IN, Pin.PULL_UP)\n\n# Clock modes\nRUNNING = 0\nSET_HOUR = 1\nSET_MINUTE = 2\nSET_AMPM = 3\n\nclass Clock:\n    def __init__(self):\n        self.mode = RUNNING\n        self.hours = 12\n        self.minutes = 0\n        self.is_pm = False\n        self.colon_on = True\n        self.last_button_time = ticks_ms()\n        self.button_debounce = 200  # milliseconds\n        self.load_time_from_rtc()\n\n    def load_time_from_rtc(self):\n        \"\"\"Get current time from RTC\"\"\"\n        _, _, _, _, hours, minutes, _, _ = rtc.datetime()\n        self.hours = hours % 12\n        if self.hours == 0:\n            self.hours = 12\n        self.is_pm = hours >= 12\n        self.minutes = minutes\n\n    def save_time_to_rtc(self):\n        \"\"\"Save current time to RTC\"\"\"\n        current_time = list(rtc.datetime())\n        hours = self.hours\n        if self.is_pm and hours != 12:\n            hours += 12\n        elif not self.is_pm and hours == 12:\n            hours = 0\n        current_time[4] = hours  # Set hours\n        current_time[5] = self.minutes  # Set minutes\n        rtc.datetime(tuple(current_time))\n\n    def debounce(self):\n        \"\"\"Handle button debouncing\"\"\"\n        current_time = ticks_ms()\n        if current_time - self.last_button_time < self.button_debounce:\n            return False\n        self.last_button_time = current_time\n        return True\n\n    def handle_buttons(self):\n        \"\"\"Process button inputs\"\"\"\n        if not self.debounce():\n            return\n\n        # Mode button cycles through modes\n        if mode_btn.value() == 0:  # Button pressed (active low)\n            self.mode = (self.mode + 1) % 4\n            if self.mode == RUNNING:\n                self.save_time_to_rtc()\n\n        # Next/Previous buttons modify current setting\n        elif next_btn.value() == 0 or prev_btn.value() == 0:\n            increment = -1 if prev_btn.value() == 0 else 1\n\n            if self.mode == SET_HOUR:\n                self.hours = ((self.hours + increment - 1) % 12) + 1\n            elif self.mode == SET_MINUTE:\n                self.minutes = (self.minutes + increment) % 60\n            elif self.mode == SET_AMPM:\n                self.is_pm = not self.is_pm\n\n    def update_display(self):\n        \"\"\"Update the TM1637 display based on current mode and time\"\"\"\n        if self.mode == RUNNING:\n            # Normal time display with blinking colon\n            self.colon_on = not self.colon_on\n        else:\n            # Setting mode - flash the active component\n            flash_on = (ticks_ms() // 500) % 2 == 0\n\n            if self.mode == SET_HOUR:\n                if not flash_on:\n                    tm.show('    ')\n                    return\n            elif self.mode == SET_MINUTE:\n                if not flash_on:\n                    tm.numbers(self.hours, 0)\n                    return\n            elif self.mode == SET_AMPM:\n                if flash_on:\n                    tm.show(' ' + ('P' if self.is_pm else 'A') + ' ')\n                    return\n\n        # Update display\n        tm.numbers(self.hours, self.minutes, colon=self.colon_on)\n\n    def run(self):\n        \"\"\"Main clock loop\"\"\"\n        tm.brightness(2)  # Set initial brightness\n\n        while True:\n            self.handle_buttons()\n\n            if self.mode == RUNNING:\n                self.load_time_from_rtc()\n\n            self.update_display()\n            sleep(0.1)  # Small delay to prevent display flicker\n\n# Create and run the clock\nif __name__ == '__main__':\n    clock = Clock()\n    print(\"Starting clock... Use buttons to set time:\")\n    print(\"Mode: Switch between run/set hour/set minute/set AM,PM\")\n    print(\"Next/Prev: Adjust current setting\")\n    clock.run()\n

Key concepts and features of this implementation:

  1. Button Handling
  2. Uses pull-up resistors (buttons connect to ground when pressed)
  3. Implements debouncing to prevent multiple triggers
  4. Buttons are active-low (0 when pressed, 1 when released)

  5. Mode System

  6. RUNNING: Normal clock operation
  7. SET_HOUR: Adjust hours (1-12)
  8. SET_MINUTE: Adjust minutes (0-59)
  9. SET_AMPM: Toggle between AM and PM

  10. Visual Feedback

  11. Selected component flashes when being set
  12. Colon blinks in running mode
  13. Special AM/PM display during setting

  14. Time Management

  15. Maintains time in 12-hour format internally
  16. Converts to/from 24-hour format for RTC
  17. Handles midnight/noon edge cases

  18. State Management

  19. Uses a class to organize state and behavior
  20. Separates display, button handling, and time management

Common challenges students might encounter:

  1. Button Debouncing
  2. Understanding why debouncing is necessary
  3. Adjusting debounce timing for reliable operation

  4. Time Format Conversion

  5. Converting between 12/24 hour formats
  6. Handling edge cases (12 AM/PM)

  7. Display Updates

  8. Managing display refresh rate
  9. Creating smooth visual feedback

Suggested exercises for students:

  1. Add a temperature display mode
  2. Implement a brightness adjustment feature
  3. Add an alarm setting mode
  4. Save settings to flash memory
  5. Add a battery backup indicator

Would you like me to explain any part in more detail or provide examples of these extensions?

"},{"location":"kits/tm1637/running-clock/","title":"Full Running Clock Using the TM1637","text":"

I'll create a detailed example of a running clock using the TM1637 display. This builds on some of the concepts shown in the clock-driver.py file you shared, but simplified for the TM1637.

import tm1637\nfrom machine import Pin, RTC\nfrom utime import sleep, localtime\n\n# Initialize the display\ntm = tm1637.TM1637(clk=Pin(0), dio=Pin(1))\n\n# Initialize the RTC (Real Time Clock)\nrtc = RTC()\n\ndef set_initial_time():\n    \"\"\"\n    Set an initial time if RTC isn't configured.\n    In a real project, you'd sync this with a time server or user input.\n    \"\"\"\n    # Format: (year, month, day, weekday, hours, minutes, seconds, subseconds)\n    rtc.datetime((2024, 12, 28, 5, 12, 0, 0, 0))\n\ndef format_time(hours, minutes):\n    \"\"\"\n    Convert 24-hour time to 12-hour format\n    Returns hours and whether it's PM\n    \"\"\"\n    is_pm = hours >= 12\n    if hours > 12:\n        hours -= 12\n    elif hours == 0:\n        hours = 12\n    return hours, is_pm\n\ndef run_clock():\n    # Set initial brightness (0-7)\n    tm.brightness(2)\n\n    # Track colon state for blinking\n    colon_on = True\n\n    while True:\n        # Get current time from RTC\n        year, month, day, weekday, hours, minutes, seconds, _ = rtc.datetime()\n\n        # Convert to 12-hour format\n        display_hours, is_pm = format_time(hours, minutes)\n\n        # Toggle colon every second\n        if seconds != prev_seconds:\n            colon_on = not colon_on\n            prev_seconds = seconds\n\n        # Display the time\n        tm.numbers(display_hours, minutes, colon=colon_on)\n\n        # Brief pause to prevent display flicker\n        sleep(0.1)\n\nif __name__ == '__main__':\n    set_initial_time()  # Only needed if RTC isn't set\n    print(\"Starting clock...\")\n    run_clock()\n
"},{"location":"kits/tm1637/running-clock/#core-concepts","title":"Core Concepts","text":"

Let's break down the key concepts students need to understand:

"},{"location":"kits/tm1637/running-clock/#1-real-time-clock-rtc","title":"1. Real-Time Clock (RTC)","text":""},{"location":"kits/tm1637/running-clock/#2-time-formats","title":"2. Time Formats","text":""},{"location":"kits/tm1637/running-clock/#3-display-multiplexing","title":"3. Display Multiplexing","text":""},{"location":"kits/tm1637/running-clock/#4-state-management","title":"4. State Management","text":""},{"location":"kits/tm1637/running-clock/#5-program-structure","title":"5. Program Structure","text":""},{"location":"kits/tm1637/running-clock/#common-challenges","title":"Common challenges","text":"

Here are some challenges students might encounter:

"},{"location":"kits/tm1637/running-clock/#1-time-drift","title":"1. Time Drift","text":""},{"location":"kits/tm1637/running-clock/#2-power-management","title":"2. Power Management","text":""},{"location":"kits/tm1637/running-clock/#3-user-interface","title":"3. User Interface","text":""},{"location":"kits/tm1637/running-clock/#extensions","title":"Extensions","text":"

Here are some additional projects that students could try:

  1. Add a temperature display that alternates with the time
  2. Implement automatic brightness control using a light sensor
  3. Add alarm functionality with a buzzer
  4. Create a menu system for setting the time
  5. Add a battery backup system

For reference, this code builds on the concepts shown in our clock-driver.py file, but simplifies the implementation thanks to the TM1637's built-in controller.

"},{"location":"kits/waveshare-e-paper/","title":"Waveshare E-Paper Display","text":"

Black 1.54 inch e-Paper/e-Ink Display Module 152x152 for Arduino,Raspberry Pi

1.54inch e-Paper Module (C)

Data Sheet

BuyDisplay

"},{"location":"kits/waveshare-e-paper/#description","title":"Description","text":"

ER-EPM0154-1B is e-paper/e-ink display module that is 1.54 inch e-paper display panel attached with a breakout board, 152x152 resolution with white/black color, UC8151C controller,running both 3.3V and 5V power supply,super wide viewing angle,sunlight readable and easily controlled by MCU such as PIC, AVR, ARDUINO,ARM and Raspberry PI. One fantastic aspect of the ePaper display is the fact they don't need any power to keep the image on the screen. Just like a Kindle, you can read whatever is on your screen in daylight without any reflection. It's optional for 3-wire or 4-wire serial interface.

We prepared the library and examples for arduino due,mega 2560,uno,nano and for raspberry pi or raspberry pi zero.

"},{"location":"kits/waveshare-e-paper/#waveshare-micropython-driver","title":"Waveshare MicroPython Driver","text":"

The device I purchased is 152x152.

"},{"location":"kits/waveshare-e-paper/#connections","title":"Connections","text":"Pin Name Description 1 VDD Power Supply (square) 2 VSS Ground (round) 3 SDA Serial Data Pin Input/Output (SPI) 4 SCL Serial Clock Pin Input (SPI) 5 /CS Chip Select Input Pin 6 D/C Data /Command Control Pin 7 /RST Reset Input. Low: Reset. 8 BUSY Busy State Output Pin
from machine import Pin\nimport epd1in54b\n\nmosi = Pin(0)\nclk = Pin(1)\nreset = Pin(2)\ndc = Pin(3)\nbusy = Pin(4)\ncs = Pin(5)\n\nepd = epd1in54b.EPD(reset, dc, busy, cs, clk, mosi)\nepd.init()\n\n# initialize the frame buffer\nfb_size = int(epd.width * epd.height / 8)\nframe_black = bytearray(fb_size)\n\nepd.clear_frame(frame_black, frame_red)\n\n# For simplicity, the arguments are explicit numerical coordinates\nepd.draw_rectangle(frame_black, 10, 60, 50, 110, epd1in54b.COLORED)\nepd.draw_line(frame_black, 10, 60, 50, 110, epd1in54b.COLORED)\nepd.draw_line(frame_black, 50, 60, 10, 110, epd1in54b.COLORED)\nepd.draw_circle(frame_black, 120, 80, 30, epd1in54b.COLORED)\n\nepd.display_frame(frame_black)\n
"},{"location":"kits/waveshare-lcd/","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-lcd/#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-lcd/#lessons","title":"Lessons","text":"
  1. Hello world! Lesson
  2. Color Lab
  3. Drawing Analog Hands
  4. 5x8 Fonts
"},{"location":"kits/waveshare-lcd/#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-lcd/#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-lcd/01-hello-world/","title":"Waveshare Hello World","text":""},{"location":"kits/waveshare-lcd/01-hello-world/#step-1-load-the-library","title":"Step 1: Load the Library","text":""},{"location":"kits/waveshare-lcd/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-lcd/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-lcd/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-lcd/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-lcd/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-lcd/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-lcd/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-lcd/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-lcd/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-lcd/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-lcd/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-lcd/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-lcd/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":"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 high 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-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 using the standard localtime() function. We will learn about how to use the eight integers returned by the localtime() functions.

MicroPython provides a very convenient function called localtime() that will get the time from the host computer in your local timezone 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 usually 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 (not 0).
"},{"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.

However, to test that our code is setting the correct local time we will need to disable this in future labs.

After we do this, the localtime() will return the number of seconds after midnight on January 1st, 2021.

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

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

We will used this when we use a RTC and use the Pico \"W\" to 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/01-timekeeping-architecture/","title":"The Architecture of Timekeeping Devices","text":"

Throughout history people have designed clocks that were not just functional, but were things of beauty to be admired. The example above is the famous Astronomical Clock called The Orloj which was built in Prague around 1400. It remains an important tourist attraction in the city.

Today, creating precise feature rich clocks is within the reach of anyone with $10 and a bit of curiosity. Despite the fact that everyone uses their mobile phones to check the time, the landscape of creative and beautiful timekeeping devices continues to expand. For example the market for beautiful Nixie tube clocks is perhaps larger than it has ever been due to the dedication of craftspeople like those people from Millclock in Ukraine.

I personally think that the Nixie Tube clocks, especially those created by Millclock are some of the most pleasing forms of handcrafted artwork I have seen. The amount of engineering that goes into manufacturing these 70 mm high retro-vacuum tubs is incredible. Each wire in the tubes are made and assembled by hand, which is why a price of almost $2,000 for each clock is not surprising.

It is interesting to note that many of the clocks in this course have features similar to the Millclock clocks such as syncing with time servers to get accurate times. So you don't need a huge budget to get a precise clock these days!

Regardless of your interest in timekeeping be it art, science, astronomy, being a maker, programming or AI, there are projects here to help you build your own highly personalized and customized timepiece using low cost components starting around $10. These devices can be a platform for fun, learning computational thinking and artistic expression.

One of the key things we learn about architecture choices is that there are few solutions that fit everyone's needs. There are always trade offs. You will find simple designs that are low-cost and easy to assemble in an hour. You will also find complex designs that use exotic displays and integrate light and motion sensors to create a personalized experience for your device.

First let take a look at the key architectural components of a timekeeping device.

"},{"location":"lessons/01-timekeeping-architecture/#core-components","title":"Core Components","text":"

Let's start with a high-level view of the core components of an electronic clock. In the diagram above you see the following components:

  1. Sensors - These are things buttons and knobs that help us configure the time and settings of a clock.
  2. Microcontroller - This is the \"brain\" of our clock and where the programs run. In our case we will be using the Raspberry Pi Pico and the Raspberry Pi Pico W (wireless) in our projects.
  3. Communications Bus - this is how we communicated between the microcontroller and the display. In our labs the bus we will use is either a I2C (slower) or the faster SPI bus.
  4. Display - We will be using many types of displays from simple LEDs to complete graphical OLED displays.
  5. Power - Most of our projects are powered by a 5 volt USB cable or batteries.

This diagram skims over a lot of the details like how we add wireless, photosensors to control brightness or adding speakers for sound for our alarm clocks. But all our clocks will share these same basic components or very similar components.

The architecture for our clocks and watches is the same architecture used in a field of computing called Physical Computing where computers read information from the real-world (incoming sensor data) and make changes to the world through actuators (usually things like motors and servos). In the world of timekeeping, most of our output is the display and the speaker. However, there are many fun clock projects that drive motors and servos to change the hands on a clock or change the visibility of a segment in a digit.

Now let's do a little deeper dive into each of these components.

"},{"location":"lessons/01-timekeeping-architecture/#details-of-our-components","title":"Details of our Components","text":""},{"location":"lessons/01-timekeeping-architecture/#sensors-our-inputs","title":"Sensors - Our Inputs","text":""},{"location":"lessons/01-timekeeping-architecture/#buttons","title":"Buttons","text":"

Buttons are the most fundamental input devices in our clock projects. They serve multiple purposes including setting the time, changing modes, and configuring alarms. While they may seem simple, buttons require careful programming consideration to handle issues like switch bounce -- the tendency for mechanical switches to generate multiple signals when pressed once.

In our projects, we typically use momentary push buttons that are normally open, meaning they only complete the circuit when pressed. These buttons are connected to the Pico's GPIO pins configured with internal pull-up resistors. This configuration means the pin reads HIGH normally and LOW when the button is pressed, simplifying our circuit design by eliminating the need for external resistors.

Button debouncing is handled in our code using both hardware (capacitors) and software (timing delays) techniques. The code in our projects typically implements a debounce delay of 50-200 milliseconds, preventing multiple accidental triggers from a single press. This is particularly important when setting time values, where accuracy is crucial.

"},{"location":"lessons/01-timekeeping-architecture/#rotary-encoder-knobs","title":"Rotary Encoder Knobs","text":"

Rotary encoders provide an intuitive way to adjust values like hours and minutes in our clock projects. Unlike potentiometers that have fixed start and end positions, rotary encoders can rotate continuously in either direction. They work by generating two square wave signals (often called A and B) that are offset by 90 degrees, allowing us to determine both the direction and speed of rotation.

The key advantage of rotary encoders in clock projects is their natural mapping to time adjustment -- clockwise rotation increases values while counterclockwise decreases them. Many of our projects use encoders with built-in push buttons, combining rotational input with selection capability in a single component. This allows users to both select what to adjust (by pressing) and make the adjustment (by rotating) with one control.

Programming rotary encoders requires careful attention to interrupt handling, as we need to monitor both signal pins simultaneously to determine direction. Our code typically uses hardware interrupts to detect changes on either pin, then compares the states to determine rotation direction. We also implement acceleration, where faster rotation results in larger value changes, making it efficient to make both small adjustments and large changes.

"},{"location":"lessons/01-timekeeping-architecture/#photosensors","title":"Photosensors","text":"

Photosensors add environmental awareness to our clock projects, enabling automatic brightness adjustment based on ambient light conditions. The most common type we use is the Light Dependent Resistor (LDR), whose resistance varies inversely with light intensity. This simple component allows our displays to be clearly visible in bright daylight while avoiding harsh glare in darkness.

Implementation of photosensors requires understanding analog-to-digital conversion, as these sensors output varying voltage levels rather than digital on/off signals. The Raspberry Pi Pico's ADC capabilities make it straightforward to read these analog values and convert them to usable brightness levels. Our projects typically map the sensor's full range to appropriate brightness values for our specific display type.

Many of our advanced projects combine photosensor data with time-based algorithms to create sophisticated brightness control. For example, we might implement hysteresis to prevent rapid brightness changes in fluctuating light conditions, or combine time-of-day information with light levels to optimize display visibility while conserving power.

"},{"location":"lessons/01-timekeeping-architecture/#the-microcontroller","title":"The MicroController","text":"

The microcontroller serves as the brain of our clock projects, executing the MicroPython code that manages timekeeping, input handling, and display updates. While simple clocks could be built with basic logic circuits, using a microcontroller gives us tremendous flexibility in features and functionality while actually reducing the component count.

Our projects primarily use the RP2040 microcontroller, which offers an excellent balance of processing power, memory, and input/output capabilities. Its dual-core architecture allows us to handle time-critical tasks like display updates on one core while managing user input and other features on the second core. The built-in programmable I/O (PIO) state machines are particularly useful for driving complex displays and handling precise timing requirements.

One of the key advantages of using a microcontroller is the ability to easily add new features through software updates. Want to add a new alarm type or change how the display animates? Simply modify the code and upload it. This flexibility extends to handling different types of time synchronization, from manual setting to NTP time servers when using the wireless-enabled Pico W.

"},{"location":"lessons/01-timekeeping-architecture/#the-raspberry-pi-pico-and-the-rp2040","title":"The Raspberry Pi Pico and the RP2040","text":"

The Raspberry Pi Pico, built around the RP2040 chip, has become a go-to platform for DIY clock projects due to its robust feature set and low cost. The board provides 26 GPIO pins, hardware support for common interfaces like I2C and SPI, and built-in temperature sensing -- all features we leverage in our various clock designs.

What makes the Pico particularly suitable for clock projects is its reliable performance and excellent power management capabilities. The board can run in several power modes, from full-speed operation to low-power sleep modes that help extend battery life in portable projects. The built-in USB programming interface makes development straightforward, while the extensive MicroPython support provides access to a rich ecosystem of libraries and examples.

For more advanced projects, the Pico W variant adds wireless connectivity, enabling features like automatic time synchronization, weather display, and even remote configuration through web interfaces. The board's 2MB of flash storage provides ample space for our MicroPython code and additional features like custom fonts or sound files for alarms.

"},{"location":"lessons/01-timekeeping-architecture/#real-time-clocks-rtc","title":"Real-Time Clocks (RTC)","text":"

Real-Time Clocks are specialized timekeeping devices that maintain accurate time even when the main power is removed. While the Raspberry Pi Pico includes an internal RTC, it resets when power is lost. External RTC modules like the DS3231 provide continuous timekeeping through battery backup, making them essential for reliable clock projects.

The DS3231 is particularly popular in our projects due to its high accuracy and temperature compensation. It communicates with the Pico using the I2C protocol, making it easy to integrate. The module includes a CR2032 battery backup that can maintain accurate time for several years without main power. Beyond basic timekeeping, it provides additional features like programmable alarms and a temperature sensor that helps compensate for timing variations caused by temperature changes.

Working with external RTCs requires understanding both hardware connections and software protocols. Our projects typically use MicroPython's machine.RTC class for the internal RTC, but switch to specific libraries for external RTC modules. This allows us to leverage advanced features like temperature readings and sophisticated alarm settings. The combination of an external RTC with the Pico's internal RTC can provide both accuracy and flexibility, with the external RTC serving as the authoritative time source while the internal RTC handles temporary timing needs.

"},{"location":"lessons/01-timekeeping-architecture/#other-microcontrollers-that-run-micropython","title":"Other Microcontrollers that Run MicroPython","text":"

While our course focuses on the Raspberry Pi Pico, many other microcontrollers support MicroPython and can be used for clock projects. Popular alternatives include the ESP32 family, which offers built-in WiFi and Bluetooth capabilities, and various STM32 boards known for their processing power and extensive peripheral options.

Each platform offers unique advantages. The ESP32, for instance, excels in wireless applications and includes a real-time clock (RTC) with battery backup capabilities. STM32 boards often provide more sophisticated analog interfaces and higher processing speeds, useful for complex display effects or audio generation.

The key advantage of using MicroPython across these platforms is code portability. Many of our basic clock functions can be transferred between different microcontrollers with minimal modification, allowing students to experiment with different hardware while building on their existing knowledge.

"},{"location":"lessons/01-timekeeping-architecture/#our-display","title":"Our Display","text":""},{"location":"lessons/01-timekeeping-architecture/#ledx","title":"LEDx","text":"

LED displays form the foundation of many clock projects, ranging from simple seven-segment displays to complex LED matrices. The most basic projects use individual LEDs for time indication, while more advanced designs employ multiplexed seven-segment displays that can show hours, minutes, and even seconds with minimal microcontroller pins.

Seven-segment displays remain popular due to their excellent visibility and straightforward programming interface. Each digit consists of seven LEDs arranged in a figure-8 pattern, plus an optional decimal point. By controlling which segments are lit, we can display numbers 0-9 and some letters. Multiple digits are typically multiplexed -- rapidly switching between digits faster than the human eye can detect -- to create the illusion of simultaneous display while using fewer GPIO pins.

For more advanced projects, we often use LED matrices that provide greater flexibility in displaying both numbers and custom graphics. These can be controlled through shift registers or specialized LED driver ICs, reducing the number of required microcontroller pins while enabling complex animations and transitions.

"},{"location":"lessons/01-timekeeping-architecture/#graphic-displays-oled","title":"Graphic Displays (OLED)","text":"

OLED displays represent a significant step up in capabilities, offering full graphic control with excellent contrast and wide viewing angles. These displays communicate with the microcontroller via I2C or SPI protocols and can show both text and graphics. The most common resolution in our projects is 128x64 pixels, providing ample space for creative clock faces.

The key advantage of OLED displays is their versatility. They can simulate traditional analog clock faces, display digital time in various fonts and sizes, and show additional information like temperature, humidity, or weather data. The high contrast ratio of OLED technology ensures excellent readability in various lighting conditions, while their low power consumption in displays with dark backgrounds makes them ideal for battery-powered projects.

Programming OLED displays requires understanding basic graphics concepts like framebuffers and drawing primitives. Our projects use libraries that abstract these details while still allowing direct pixel control when needed. We can create smooth animations, implement multiple clock face designs, and even build interactive menus for settings and configuration.

"},{"location":"lessons/01-timekeeping-architecture/#speakers","title":"Speakers","text":"

In clock projects, speakers primarily serve for alarms and hourly chimes, though some advanced projects include more complex audio features. The simplest implementation uses a piezoelectric buzzer directly connected to a GPIO pin, capable of producing basic tones through PWM (Pulse Width Modulation) signals.

For better sound quality, some projects employ small magnetic speakers with audio amplifier circuits. These can reproduce more complex sounds and music, though they require more sophisticated driving circuits and careful power management. The Raspberry Pi Pico's PWM capabilities make it possible to generate various frequencies and even simple melodies without additional hardware.

Advanced projects might include multiple sound effects, customizable alarm tones, or even voice announcements. These typically require additional components like audio DACs (Digital-to-Analog Converters) or dedicated audio modules, along with careful consideration of timing to ensure smooth playback while maintaining accurate timekeeping.

"},{"location":"lessons/01-timekeeping-architecture/#analog-to-digital-converters","title":"Analog to Digital Converters","text":"

Analog to Digital Converters (ADCs) play a crucial role in our clock projects, primarily for reading sensors and implementing volume control for audio features. The Raspberry Pi Pico includes three ADC channels, providing 12-bit resolution for accurate measurements of analog signals.

In clock applications, ADCs are most commonly used with photoresistors for automatic brightness control and potentiometers for user adjustments. The conversion process involves sampling the analog voltage at regular intervals and converting it to digital values our code can process. The Pico's ADC can sample at up to 500,000 samples per second, far exceeding our typical needs for clock projects.

Understanding ADC principles is essential for handling real-world inputs effectively. Our projects implement techniques like oversampling to improve accuracy and filtering to reduce noise in measurements. This knowledge becomes particularly valuable when working with analog sensors for environmental monitoring or audio input processing.

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

Our standard watch kits places two or three 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":"
from 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/05-buttons/#three-button-set","title":"Three Button Set","text":"
from machine import Pin\nimport time\n\n# Pin setup\nmode_pin = Pin(16, Pin.IN, Pin.PULL_UP)\nnext_pin = Pin(17, Pin.IN, Pin.PULL_UP)\nprevious_pin = Pin(18, Pin.IN, Pin.PULL_UP)\n\n# Time state\nhour = 12\nminute = 0\nis_pm = False\nmode = 0\nmode_names = [\"run\", \"set hour\", \"set minute\", \"set AM/PM\"]\nmode_count = len(mode_names)\n\n# Debounce state\nlast_mode_press = 0\nlast_next_press = 0\nlast_prev_press = 0\nDEBOUNCE_MS = 200\n\ndef format_time():\n    return f\"{hour:02d}:{minute:02d} {'PM' if is_pm else 'AM'}\"\n\ndef handle_mode(pin):\n    global mode, last_mode_press\n    current_time = time.ticks_ms()\n    if time.ticks_diff(current_time, last_mode_press) > DEBOUNCE_MS:\n        mode = (mode + 1) % mode_count\n        print(f\"Mode: {mode_names[mode]}\")\n        last_mode_press = current_time\n\ndef handle_next(pin):\n    global hour, minute, is_pm, last_next_press\n    current_time = time.ticks_ms()\n    if time.ticks_diff(current_time, last_next_press) > DEBOUNCE_MS:\n        if mode == 1:  # Set hour\n            hour = (hour % 12) + 1\n        elif mode == 2:  # Set minute\n            minute = (minute + 1) % 60\n        elif mode == 3:  # Toggle AM/PM\n            is_pm = not is_pm\n\n        if mode != 0:\n            print(format_time())\n        last_next_press = current_time\n\ndef handle_previous(pin):\n    global hour, minute, is_pm, last_prev_press\n    current_time = time.ticks_ms()\n    if time.ticks_diff(current_time, last_prev_press) > DEBOUNCE_MS:\n        if mode == 1:  # Set hour\n            hour = ((hour - 2) % 12) + 1\n        elif mode == 2:  # Set minute\n            minute = (minute - 1) % 60\n        elif mode == 3:  # Toggle AM/PM\n            is_pm = not is_pm\n\n        if mode != 0:\n            print(format_time())\n        last_prev_press = current_time\n\n# Set up interrupts\nmode_pin.irq(trigger=Pin.IRQ_FALLING, handler=handle_mode)\nnext_pin.irq(trigger=Pin.IRQ_FALLING, handler=handle_next)\nprevious_pin.irq(trigger=Pin.IRQ_FALLING, handler=handle_previous)\n\n# Main loop\nprint(\"Clock started. Press mode button to change settings.\")\nwhile True:\n    if mode == 0:  # Only update display in run mode\n        print(format_time())\n        time.sleep(1)\n
"},{"location":"lessons/06-setting-time/","title":"Manually Setting Time","text":"

We need a program that will use three buttons to set the time.

"},{"location":"lessons/06-setting-time/#the-mode-button","title":"The Mode Button","text":"

The first button is called the \"mode\" button. It will cycle through four internal \"states\" of our clock. Here are the modes:

  1. Mode 0: Clock Running - this is the normal mode of operation
  2. Mode 1: Setting the Hour
  3. Mode 2: Setting the Minute
  4. Mode 3: Setting AM/PM

Here is a walkthrough of the button-mode-test.py program, breaking it down into clear, digestible concepts for everyone new to MicroPython.

"},{"location":"lessons/06-setting-time/#code-walkthrough","title":"Code Walkthrough","text":""},{"location":"lessons/06-setting-time/#1-basic-setup-and-imports","title":"1. Basic Setup and Imports","text":"
from mp_button import Button\nfrom time import localtime, sleep\nfrom machine import Pin\n

Let's understand what these lines do: - These are import statements that bring in code we need to use - Button is a special module that helps us work with physical buttons - localtime and sleep are time-related functions - Pin lets us work with the physical pins on our Raspberry Pi Pico

"},{"location":"lessons/06-setting-time/#2-setting-up-the-pins","title":"2. Setting Up the Pins","text":"
mode_pin = Pin(16, Pin.IN, Pin.PULL_UP)\nnext_pin = Pin(17, Pin.IN, Pin.PULL_UP)\nprevious_pin = Pin(18, Pin.IN, Pin.PULL_UP)\n

Here's what's happening: - We're setting up three different pins (16, 17, and 18) on the Pico - Each pin is set as an input (Pin.IN) - meaning it receives signals rather than sends them - Pin.PULL_UP means the pin is naturally \"high\" (1) until a button press makes it \"low\" (0) - Think of it like a light switch that's normally on, and pressing the button turns it off

"},{"location":"lessons/06-setting-time/#3-global-variables","title":"3. Global Variables","text":"
counter_pressed = 0\ncounter_released = 0\nmode = 0  # default clock running\nmode_names = [\"run\",\"set hour\",\"set minute\",\"set AM/PM\"]\nmode_count = len(mode_names)\nnow = localtime()\nhours = now[3]\nminutes = now[4]\nam_pm = 0\n

These are our program's variables: - counter_pressed and counter_released keep track of button presses - mode tells us which setting we're currently adjusting (starts at 0) - mode_names is a list of the different modes our clock can be in - mode_count is how many modes we have (4 in this case) - now gets the current time from the Pico - hours and minutes store the current time values - am_pm keeps track of whether it's morning (0) or afternoon (1)

"},{"location":"lessons/06-setting-time/#4-button-handler-functions","title":"4. Button Handler Functions","text":"
def button_mode_irq(button, event):\n    global mode, hours, minutes\n    if event == Button.PRESSED:\n        mode += 1\n        mode = mode % mode_count\n        print('new mode:', mode, mode_names[mode])\n

This is our mode button handler: - global tells Python we want to change variables outside this function - When the button is pressed, we increase the mode by 1 - The % (modulo) operator helps us cycle back to 0 after reaching the last mode - For example: if mode is 3 and we add 1, 4 % 4 = 0, so we go back to the first mode

"},{"location":"lessons/06-setting-time/#5-next-and-previous-button-handlers","title":"5. Next and Previous Button Handlers","text":"
def button_next_irq(button, event):\n    global mode, hours, minutes, am_pm\n    if event == Button.PRESSED:\n        if mode == 1:\n            hours += 1\n        if mode == 2:\n            minutes += 1\n        if mode == 3:\n            am_pm = 1 if am_pm == 0 else 0\n

The next/previous buttons: - They only work when we're in a setting mode (not mode 0) - Mode 1: adjust hours - Mode 2: adjust minutes - Mode 3: toggle between AM and PM - The previous button does the same thing but decreases values

"},{"location":"lessons/06-setting-time/#6-creating-button-objects","title":"6. Creating Button Objects","text":"
button_mode = Button(16, False, button_mode_irq, internal_pullup = True, debounce_time = 100)\nbutton_next = Button(17, False, button_next_irq, internal_pullup = True, debounce_time = 100)\nbutton_previous = Button(18, False, button_previous_irq, internal_pullup = True, debounce_time = 100)\n

Here we create our button objects: - Each button gets a pin number (16, 17, or 18) - We tell it which function to call when pressed (the _irq functions) - debounce_time = 100 prevents multiple triggers from one press - Think of debouncing like waiting a moment to make sure someone really pressed the button once

"},{"location":"lessons/06-setting-time/#7-main-loop","title":"7. Main Loop","text":"
while(True):\n    button_mode.update()\n    button_next.update()\n    button_previous.update()\n

This is our main program loop: - It runs forever (that's what while True means) - Each time through the loop, we check if any buttons were pressed - The update() function handles all the button checking for us

"},{"location":"lessons/06-setting-time/#full-program-listing","title":"Full Program Listing","text":"
from mp_button import Button\nfrom time import localtime, sleep\nfrom machine import Pin\n\nmode_pin = Pin(16, Pin.IN, Pin.PULL_UP)\nnext_pin = Pin(17, Pin.IN, Pin.PULL_UP)\nprevious_pin = Pin(18, Pin.IN, Pin.PULL_UP)\n\n# we create a counter to increment as we press\n# and one to increment as we release\ncounter_pressed = 0\ncounter_released = 0\nmode = 0 # default clock running\nmode_names = [\"run\",\"set hour\",\"set minute\",\"set AM/PM\"]\nmode_count = len(mode_names)\nnow = localtime()\nhours = now[3]\nminutes = now[4]\nam_pm = 0\n\n# the following method (function) will be invoked\n# when the button changes state\n# the Button module expects a callback to handle \n# - pin number\n# - event (Button.PRESSED | Button.RELEASED)\n# the event contains a string 'pressed' or 'released'\n# which can be used in your code to act upon\ndef button_mode_irq(button, event):\n    global mode, hours, minutes\n    if event == Button.PRESSED:\n        mode +=1\n        # cycle back to zero if greater than mode_count\n        mode =  mode % mode_count\n        print('new mode:', mode, mode_names[mode])\n\ndef button_next_irq(button, event):\n    global mode, hours, minutes, am_pm\n    if event == Button.PRESSED:\n        if mode == 1:\n            hours += 1\n        if mode == 2:\n            minutes += 1\n        if mode == 3:\n            if am_pm == 0:\n                am_pm = 1\n            else:\n                am_pm = 0\n        print('next button:', hours, minutes, am_pm)\n\ndef button_previous_irq(button, event):\n    global mode, hours, minutes, am_pm\n    if event == Button.PRESSED:\n        if mode == 1:\n            hours -= 1\n        if mode == 2:\n            minutes -= 1\n        if mode == 3:\n            if am_pm == 0:\n                am_pm = 1\n            else:\n                am_pm = 0\n        print('prev button:', hours, minutes, am_pm)\n\nbutton_mode = Button(16, False, button_mode_irq, internal_pullup = True, debounce_time = 100)\nbutton_next = Button(17, False, button_next_irq, internal_pullup = True, debounce_time = 100)\nbutton_previous = Button(18, False, button_previous_irq, internal_pullup = True, debounce_time = 100)\n\nprint(\"year:\", now[0], \"month:\", now[1], \"day-of-month:\", now[2], \"hours\", now[3], \"minutes:\", now[4])\nwhile(True):\n    button_mode.update()\n    button_next.update()\n    button_previous.update()\n
"},{"location":"lessons/06-setting-time/#the-decrement-time","title":"The Decrement Time","text":"

The following line need some explanation:

hour = ((hour - 2) % 12) + 1\n
Let me break down that line, which handles decrementing hours while staying within the 1-12 range:

Let's walk through it step by step:

  1. First, we subtract 2 from the current hour: (hour - 2)
  2. We subtract 2 (not 1) because we'll add 1 back at the end
  3. This shift is necessary because we want to work with 0-11 for the modulo operation

  4. Then we take modulo 12: % 12

  5. This ensures our number wraps around within 0-11
  6. For example, if hour was 1, then (1-2) = -1, and -1 % 12 = 11

  7. Finally, we add 1: + 1

  8. This shifts our range from 0-11 back to 1-12

Here's an example sequence to show how it works: - Starting at hour = 1: - (1 - 2) = -1 - -1 % 12 = 11 - 11 + 1 = 12 - Starting at hour = 12: - (12 - 2) = 10 - 10 % 12 = 10 - 10 + 1 = 11

This gives us the desired behavior of decrementing through the sequence: 12 \u2192 11 \u2192 10 \u2192 ... \u2192 1 \u2192 12

"},{"location":"lessons/06-setting-time/#updating-the-display","title":"Updating the Display","text":"
from machine import Pin\nfrom utime import localtime, sleep, ticks_ms, ticks_diff\nimport tm1637\n\n# Pin setup\nCLK_PIN = 0\nDST_PIN = 1\nPM_PIN = 25\n\nmode_pin = Pin(16, Pin.IN, Pin.PULL_UP)\nnext_pin = Pin(17, Pin.IN, Pin.PULL_UP)\nprevious_pin = Pin(18, Pin.IN, Pin.PULL_UP)\n# LED value 0 indicates AM, value 1 indicates PM\npm_pin = Pin(PM_PIN, Pin.OUT)\n\n# Time state\nnow = localtime()\nhour = now[3]\nminute = now[4]\nsecond = now[5]\n\ntm = tm1637.TM1637(clk=Pin(CLK_PIN), dio=Pin(DST_PIN))\n\nmode = 0\nmode_names = [\"run\", \"set hour\", \"set minute\", \"set AM/PM\"]\nmode_count = len(mode_names)\n\n# Debounce state\nlast_mode_press = 0\nlast_next_press = 0\nlast_prev_press = 0\nDEBOUNCE_MS = 100\n\ndef format_time():\n    return f\"{hour:d}:{minute:02d}:{second:02d} {'PM' if is_pm else 'AM'}\"\n\ndef set_pm():\n    if hour < 12:\n        is_pm = False\n        pm_pin.value(0)\n    else:\n        is_pm = True\n        pm_pin.value(1)\n\ndef handle_mode(pin):\n    global mode, last_mode_press\n    current_time = ticks_ms()\n    if ticks_diff(current_time, last_mode_press) > DEBOUNCE_MS:\n        mode = (mode + 1) % mode_count\n        print(f\"Mode: {mode_names[mode]}\")\n        last_mode_press = current_time\n\ndef handle_next(pin):\n    global hour, minute, is_pm, last_next_press\n    current_time = ticks_ms()\n    if ticks_diff(current_time, last_next_press) > DEBOUNCE_MS:\n        if mode == 1:  # Set hour\n            hour = (hour % 12) + 1\n        elif mode == 2:  # Set minute\n            minute = (minute + 1) % 60\n        elif mode == 3:  # Toggle AM/PM\n            is_pm = not is_pm\n\n        if mode != 0:\n            print(format_time())\n        last_next_press = current_time\n\ndef handle_previous(pin):\n    global hour, minute, is_pm, last_prev_press\n    current_time = ticks_ms()\n    if ticks_diff(current_time, last_prev_press) > DEBOUNCE_MS:\n        if mode == 1:  # Set hour\n            hour = ((hour - 2) % 12) + 1\n        elif mode == 2:  # Set minute\n            minute = (minute - 1) % 60\n        elif mode == 3:  # Toggle AM/PM\n            is_pm = not is_pm\n\n        if mode != 0:\n            print(format_time())\n        last_prev_press = current_time\n\n# numbers modified to not dispaly the leading zero\ndef numbers_nlz(num1, num2, colon=True):\n    \"\"\"Display two numeric values -9 through 99, with a leading space before\n    single-digit first numbers and separated by a colon.\"\"\"\n    num1 = max(-9, min(num1, 99))\n    num2 = max(-9, min(num2, 99))\n    prefix = ' ' if num1 < 10 else ''\n    # print(f'\"{prefix}{num1:d}{num2:0>2d}\"')\n    segments = tm.encode_string(f'{prefix}{num1:d}{num2:0>2d}')\n    if colon:\n        segments[1] |= 0x80  # colon on\n    tm.write(segments)\n\n# Set up interrupts\nmode_pin.irq(trigger=Pin.IRQ_FALLING, handler=handle_mode)\nnext_pin.irq(trigger=Pin.IRQ_FALLING, handler=handle_next)\nprevious_pin.irq(trigger=Pin.IRQ_FALLING, handler=handle_previous)\n\n# Main loop\nprint(\"Clock started. Press mode button to change settings.\")\n\nwhile True:\n    second = localtime()[5]\n    if mode == 0:  # Only update display in run mode\n        print(format_time())\n        # flash the colon on and off every second\n        if (second % 2): # modulo 2 will be true for odd numbers\n            numbers_nlz(hour, minute, True)\n        else:\n            numbers_nlz(hour, minute, False)\n        set_pm()\n        sleep(1)\n
"},{"location":"lessons/10-i2c-bus/","title":"The I2C Bus Standard","text":"

Most microcontrollers use the I2C bus as a standard way to communicate with peripheral devices such as sensors, real-time clocks and displays. In this section we cover how to connect your I2C devices to the main microcontroller and test that the connections are working.

Note that I2C is a two-way bus. Although in many of our display examples, there is only data moving from the microcontroller to the device. When we study real-time clocks we will see data going back and forth from the microcontroller to peripheral device.

"},{"location":"lessons/10-i2c-bus/#connections","title":"Connections","text":"

The I2C bus has four connections:

  1. GND - Ground
  2. VCC - Power - (usually 3.3 or 5 volts)
  3. SDA - Data
  4. SCL - Clock
"},{"location":"lessons/10-i2c-bus/#ground-gnd","title":"Ground (GND)","text":"

The common reference point for electrical signals that completes the circuit and ensures stable voltage measurements.

Connecting the display's GND pin to the microcontroller's GND pin provides a shared zero-voltage reference.

"},{"location":"lessons/10-i2c-bus/#power-vcc","title":"Power (VCC)","text":"

The voltage supply line that provides electrical power to operate the display, typically accepting either 3.3V or 5V depending on the model.

Connecting a 3.3V supply from the microcontroller to power a small OLED display.

"},{"location":"lessons/10-i2c-bus/#serial-data-sda","title":"Serial Data (SDA)","text":"

A bidirectional line that carries data bits between devices using a specific protocol for addressing and acknowledgment.

In the example code above, we use GPIO pin 0 to transmit display content and receive status information.

"},{"location":"lessons/10-i2c-bus/#serial-clock-scl","title":"Serial Clock (SCL)","text":"

A timing signal line generated by the master device that synchronizes data transfer and defines when the data line should be read or written. In our work, the master device is the microcontroller which sends the clock signal to the display.

In the sample code, we use GPIO pin 1 to coordinate data transfers at rates typically between 100kHz and 400kHz.

Some important points about these connections:

  1. Both SDA and SCL require pull-up resistors (typically 4.7k\u03a9)
  2. All devices share the same GND connection
  3. Multiple I2C devices can share the same SDA and SCL lines
  4. Power must match the display's voltage requirements (check datasheet)
"},{"location":"lessons/10-i2c-bus/#sample-i2c-initialization-code","title":"Sample I2C Initialization Code","text":"
from machine import Pin, I2C\nfrom ssd1306 import SSD1306_I2C\n\ni2c = I2C(0, sda=Pin(0), scl=Pin(1))\ndisplay = SSD1306_I2C(128, 64, i2c)\n\n# drawing code here\ndisplay.fill(0)\ndisplay.text(\"MicroPython\", 0, 0)\ndisplay.text(\"Rocks!\", 20, 20)\ndisplay.show()\n

Let's break down this I2C initialization code:

This code sets up a communication interface to control an OLED display using I2C (Inter-Integrated Circuit) protocol instead of SPI. Here's what each part does:

  1. The imports:
from machine import Pin, I2C            # Gets I2C and Pin control classes\nfrom ssd1306 import SSD1306_I2C         # Gets the OLED display driver\n
  1. The I2C bus initialization:
i2c = I2C(0,                           # Use I2C bus 0 \n          sda=Pin(0),                  # Data line on GPIO pin 0\n          scl=Pin(1))                  # Clock line on GPIO pin 1\n
  1. The display initialization:
display = SSD1306_I2C(128,             # Display width in pixels\n                      64,              # Display height in pixels \n                      i2c)             # I2C bus we created\n

Key differences from the SPI version: - Uses only 2 pins instead of 5 (no CS, DC, or RES needed) - Simpler initialization code - Slightly slower than SPI but adequate for most uses - Can share bus with other I2C devices using different addresses

Worth noting that I2C is often preferred for simple display projects because it requires fewer pins and simpler wiring, even though it's not as fast as SPI.

A common issue to watch for is making sure your I2C connections have appropriate pull-up resistors, though many development boards include these built-in.

"},{"location":"lessons/10-i2c-bus/#testing-your-connections","title":"Testing Your Connections","text":"

The I2C Scanner

"},{"location":"lessons/10-i2c-bus/#common-i2c-addresses-for-clock-and-watches","title":"Common I2C Addresses for Clock and Watches","text":""},{"location":"lessons/10-i2c-bus/#real-time-clocks","title":"Real Time Clocks","text":"

We use two types of Real-Time clocks

"},{"location":"lessons/10-i2c-bus/#real-time-clock-eeprom","title":"Real Time Clock EEPROM","text":""},{"location":"lessons/10-i2c-bus/#displays","title":"Displays","text":"

Our I2C OLED displays are often found at

"},{"location":"lessons/10-i2c-bus/#connector-standards","title":"Connector Standards","text":""},{"location":"lessons/11-spi-bus/","title":"The SPI Bus","text":"

The SPI Bus is the fastest serial bus that is used with most microcontrollers. Although it is fast, most displays need seven wires to connect a display. This is in contrast to the I2C bus that only uses four wires.

from machine import Pin, I2C\nimport ssd1306\n\nSCL=machine.Pin(2) # SPI CLock\nSDA=machine.Pin(3) # SPI Data\n\n# configure the SPI bus\n# for clocks with 20cm connectors 3MBS is a good choice\nspi=machine.SPI(0, sck=SCL, mosi=SDA, baudrate=3_000_000)\n\nRES = machine.Pin(4)\nDC = machine.Pin(5)\nCS = machine.Pin(6)\n\n# configure the display\ndisp = ssd1306.SSD1306_SPI(128, 64, spi, DC, RES, CS)\n

The underscore in 3_000_000 is just Python syntax for making large numbers more readable - it's equivalent to writing 3000000. This is a helpful convention when working with clock speeds and baudrates.

"},{"location":"lessons/11-spi-bus/#spi-bus","title":"SPI Bus","text":"

A serial communication interface that operates in a master-slave configuration using separate clock and data lines to synchronize high-speed data transfer between electronic components.

Example: Configuring an SPI bus with a 3 MHz clock rate to drive an OLED display.

"},{"location":"lessons/11-spi-bus/#oled-display","title":"OLED Display","text":"

A self-illuminating electronic visual output device that uses organic light-emitting diodes arranged in a matrix to show text, numbers, or graphics.

Example: A 128x64 pixel monochrome display showing time and temperature readings.

"},{"location":"lessons/11-spi-bus/#chip-select-cs","title":"Chip Select (CS)","text":"

A control signal that activates or deactivates a specific integrated circuit in a system with multiple devices sharing the same communication bus.

Example: We can change the state of th CS pin to enable communication with the display while keeping other SPI devices inactive. This allows you to use a single bus to communicated with multiple displays.

"},{"location":"lessons/11-spi-bus/#datacommand-dc","title":"Data/Command (DC)","text":"

A control line that indicates whether the transmitted information represents display commands or visual data to be shown.

Example: Using Pin 5 to distinguish between instructions for display configuration and pixel data.

"},{"location":"lessons/11-spi-bus/#reset-res","title":"Reset (RES)","text":"

A control signal that returns an electronic device to its initial state, clearing all registers and settings to default values.

Example: You can use the RESET to delay the startup of a display to make sure that the display has powered up in a coherent way and will start in a consistent state.

"},{"location":"lessons/11-spi-bus/#clock-line-scl","title":"Clock Line (SCL)","text":"

A signal wire that provides timing synchronization pulses to coordinate data transfer between electronic devices.

Example: We use the Clock Line to generate 3 million clock pulses per second for display updates.

"},{"location":"lessons/11-spi-bus/#data-line-sdamosi","title":"Data Line (SDA/MOSI)","text":"

A signal wire that carries information serially from a controlling device to a receiving device.

Example: We use the SDA pin to transmit display content one bit at a time.

"},{"location":"lessons/11-spi-bus/#tuning-the-baudrate","title":"Tuning the Baudrate","text":"

The baudrate parameter determines the transfer between thn microcontroller and the peripheral device.

The baudrate=3_000_000 (3 megabits per second) setting is a reasonable balance for this application. Digital clock displays are often only updated once per second.

For SPI OLED displays with short connections (~20cm or less), a 3 MHz clock rate provides:

"},{"location":"lessons/11-spi-bus/#when-higher-speeds-matter","title":"When Higher Speeds Matter","text":"

Simulating an analog clock face on a color watch display will require a large amount of data being transmitted between the microcontroller and the display. For these situations you will frequently keep the wires between the microcontroller and the display short and then turn up the baudrate on the SPI interface.

"},{"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":"lessons/55-shift-registers/","title":"Shift Registers","text":""},{"location":"lessons/55-shift-registers/#sample-prompt","title":"Sample Prompt","text":"

Prompt

Please create a detailed tutorial about using shift registers that would be understood by a 9th grade high school student. Assume that the student already knows about using the Raspberry Pi Pico to turn on and off LEDs,but does not have enough output pins to drive all 4 digits on a seven-segment display clock. Suggest where figures should be added and what the figures should contain. Discuss the 74HC595 chip, what its inputs and outputs are and what some basic MicroPython code would look like to turn on and off some LEDs controlled by the 74HC595. Return the tutorial in markdown format.

"},{"location":"lessons/55-shift-registers/#sample-response","title":"Sample Response","text":""},{"location":"lessons/55-shift-registers/#using-shift-registers-to-control-more-leds-with-your-pico","title":"Using Shift Registers to Control More LEDs with Your Pico","text":"

Have you ever wanted to build a digital clock but ran out of pins on your Raspberry Pi Pico? A shift register can help! Think of a shift register like a magical helper that turns a few Pico pins into many output pins. Let's learn how to use one!

"},{"location":"lessons/55-shift-registers/#what-is-a-shift-register","title":"What is a Shift Register?","text":"

A shift register is like a row of eight boxes that can each hold either a 1 (on) or 0 (off). The cool part is that you can control all eight outputs using just three pins from your Pico! The 74HC595 is a popular and easy-to-use shift register.

"},{"location":"lessons/55-shift-registers/#the-important-pins-on-the-74hc595","title":"The Important Pins on the 74HC595","text":""},{"location":"lessons/55-shift-registers/#detailed-pin-descriptions","title":"Detailed Pin Descriptions","text":""},{"location":"lessons/55-shift-registers/#srclk","title":"SRCLK","text":"

Shift Register Clock or Input Clock - A digital input signal that controls the shifting of data through a serial-to-parallel register by generating a transition from low to high voltage level.

Example: Pulsing SRCLK from 0V to 3.3V causes each bit to move one position in the shift register.

"},{"location":"lessons/55-shift-registers/#rclk","title":"RCLK","text":"

Register Clock or Latch Pin A digital input signal that transfers the contents of the shift register to the storage register by generating a transition from low to high voltage level.

Example: Pulsing RCLK from 0V to 3.3V updates all output pins simultaneously with new values. The term \"latch pin\" is more descriptive of its function (it \"latches\" or locks in the data), while RCLK is the technical abbreviation used in datasheets. On the 74HC595 chip, this is pin 12, and it's also sometimes labeled as STCP (STorage Clock Pulse).

"},{"location":"lessons/55-shift-registers/#wiring-it-up","title":"Wiring It Up","text":"

[Figure 3: Show a wiring diagram with:

Here's how to connect everything:

  1. Connect VCC to 3.3V on the Pico
  2. Connect GND to ground
  3. Connect Data DS to GPIO 16
  4. Connect Clock (SRCLK) to GPIO 17
  5. Connect Latch (RCLK) to GPIO 18
  6. Connect LEDs with resistors to outputs Q0-Q7
"},{"location":"lessons/55-shift-registers/#the-code","title":"The Code","text":"

Here's some simple MicroPython code to control your LEDs:

from machine import Pin\nfrom utime import sleep\n\n# Set up our Pico pins\ndata_pin = Pin(16, Pin.OUT)\nclock_pin = Pin(17, Pin.OUT)\nlatch_pin = Pin(18, Pin.OUT)\n\ndef shift_byte(byte_val):\n    # Send each bit one at a time\n    for i in range(8):\n        # Get the current bit (0 or 1)\n        bit = (byte_val >> (7-i)) & 1\n\n        # Set the data pin\n        data_pin.value(bit)\n\n        # Pulse the clock\n        clock_pin.value(1)\n        clock_pin.value(0)\n\n    # Pulse the latch to show the new pattern\n    latch_pin.value(1)\n    latch_pin.value(0)\n\n# Example: Light up alternate LEDs\nwhile True:\n    shift_byte(0b10101010)  # Turn on LEDs 7,5,3,1\n    sleep(1)\n    shift_byte(0b01010101)  # Turn on LEDs 6,4,2,0\n    sleep(1)\n
"},{"location":"lessons/55-shift-registers/#how-it-works","title":"How It Works","text":"

[Figure 4: Show an animation or series of diagrams demonstrating how data shifts through the register]

  1. The shift_byte function takes a number between 0 and 255
  2. It converts this number into eight separate bits (1s and 0s)
  3. For each bit:
  4. It puts the bit on the data pin
  5. Pulses the clock to shift it in
  6. After all 8 bits are sent, it pulses the latch to update all LEDs at once
"},{"location":"lessons/55-shift-registers/#using-multiple-shift-registers","title":"Using Multiple Shift Registers","text":"

[Figure 5: Show how to chain two 74HC595s together]

Need even more outputs? You can chain shift registers together! Connect the Q7' (serial out) pin of the first shift register to the DS (data) pin of the second one. They'll share the same clock and latch pins.

"},{"location":"lessons/55-shift-registers/#practice-project-binary-counter","title":"Practice Project: Binary Counter","text":"

Try this: Make a program that counts from 0 to 255, showing each number in binary on your LEDs. Here's a starter:

for number in range(256):\n    shift_byte(number)\n    sleep(0.1)  # Wait a bit between numbers\n

This will help you understand how binary numbers work while practicing with your shift register!

"},{"location":"lessons/55-shift-registers/#common-problems-and-solutions","title":"Common Problems and Solutions","text":"
  1. LEDs not lighting up?
  2. Check your connections
  3. Make sure your LEDs have resistors
  4. Verify your ground connections

  5. Random LED patterns?

  6. Make sure your clock and latch pulses are complete
  7. Check if your data is sending in the right order
"},{"location":"lessons/55-shift-registers/#next-steps","title":"Next Steps","text":"

Once you're comfortable with this, you can: 1. Control a 4-digit display for your clock 2. Make LED light patterns 3. Build a binary game 4. Create a VU meter for music

Remember: A shift register is just a tool to help you control more things with fewer pins. With practice, you'll find lots of cool ways to use it in your projects!

[Note: All figures should be clear, well-labeled, and include captions explaining what they show. Consider using color coding for different types of connections (power, data, ground) in the diagrams.]

"},{"location":"lessons/55-shift-registers/#example-74hc595","title":"Example: 74HC595","text":""},{"location":"lessons/55-shift-registers/#references","title":"References","text":""},{"location":"lessons/60-sound/","title":"Adding Sound to Your Clock","text":""},{"location":"lessons/60-sound/#why-add-sound","title":"Why Add Sound","text":""},{"location":"lessons/60-sound/#feedback-when-setting-time","title":"Feedback When Setting Time","text":"

Using sounds allows you to use low-cost momentary push buttons that don't have a clear \"clink\" when pressed.

"},{"location":"lessons/60-sound/#alarm-clock-sounds","title":"Alarm Clock Sounds","text":""},{"location":"lessons/60-sound/#timers-and-reminders","title":"Timers and Reminders","text":""},{"location":"lessons/60-sound/#adding-a-speaker","title":"Adding a Speaker","text":""},{"location":"lessons/60-sound/#generating-simple-sounds-with-pwm","title":"Generating Simple Sounds with PWM","text":"

Play a Tune with PWM

"},{"location":"lessons/60-sound/#using-a-digital-to-analog-converter","title":"Using a Digital to Analog Converter","text":""},{"location":"lessons/60-sound/#references","title":"References","text":"

Prof. Albrecht Schmidt Wiki

"},{"location":"lessons/60-sound/play-tune/","title":"Play a Tune Using PWM","text":"

Below is an example MicroPython program that uses the provided note frequencies and plays the Mario melody on a PWM pin (GPIO 16 in this example). You can adjust timing and duty cycle to get the sound you want. Simply copy this code onto your MicroPython device (e.g., Raspberry Pi Pico running MicroPython) and run it.

from machine import Pin, PWM\nfrom utime import sleep\n\n# Initialize speaker on GPIO16\nspeaker = PWM(Pin(16))\n\n# Define the tone (note) frequency dictionary\ntones = {\n    \"B0\": 31,\"C1\": 33,\"CS1\": 35,\"D1\": 37,\"DS1\": 39,\"E1\": 41,\"F1\": 44,\"FS1\": 46,\n    \"G1\": 49,\"GS1\": 52,\"A1\": 55,\"AS1\": 58,\"B1\": 62,\"C2\": 65,\n    \"CS2\": 69,\"D2\": 73,\"DS2\": 78,\"E2\": 82,\"F2\": 87,\"FS2\": 93,\"G2\": 98,\n    \"GS2\": 104,\"A2\": 110,\"AS2\": 117,\"B2\": 123,\"C3\": 131,\"CS3\": 139,\n    \"D3\": 147,\"DS3\": 156,\"E3\": 165,\"F3\": 175,\"FS3\": 185,\n    \"G3\": 196,\"GS3\": 208,\"A3\": 220,\"AS3\": 233,\"B3\": 247,\"C4\": 262,\"CS4\": 277,\"D4\": 294,\"DS4\": 311,\n    \"E4\": 330,\"F4\": 349,\"FS4\": 370,\"G4\": 392,\"GS4\": 415,\"A4\": 440,\"AS4\": 466,\"B4\": 494,\"C5\": 523,\"CS5\": 554,\"D5\": 587,\"DS5\": 622,\"E5\": 659,\"F5\": 698,\n    \"FS5\": 740,\"G5\": 784,\"GS5\": 831,\"A5\": 880,\"AS5\": 932,\"B5\": 988,\"C6\": 1047,\"CS6\": 1109,\"D6\": 1175,\"DS6\": 1245,\"E6\": 1319,\"F6\": 1397,\"FS6\": 1480,\"G6\": 1568,\"GS6\": 1661,\n    \"A6\": 1760,\"AS6\": 1865,\"B6\": 1976,\"C7\": 2093,\"CS7\": 2217,\"D7\": 2349,\"DS7\": 2489,\"E7\": 2637,\"F7\": 2794,\"FS7\": 2960,\"G7\": 3136,\"GS7\": 3322,\"A7\": 3520,\n    \"AS7\": 3729,\"B7\": 3951,\"C8\": 4186,\"CS8\": 4435,\"D8\": 4699,\"DS8\": 4978\n}\n\n# Mario melody notes\nmario = [\n    \"E7\", \"E7\", 0, \"E7\", 0, \"C7\", \"E7\", 0, \"G7\", 0, 0, 0, \"G6\", 0, 0, 0,\n    \"C7\", 0, 0, \"G6\", 0, 0, \"E6\", 0, 0, \"A6\", 0, \"B6\", 0, \"AS6\", \"A6\", 0,\n    \"G6\", \"E7\", 0, \"G7\", \"A7\", 0, \"F7\", \"G7\", 0, \"E7\", 0, \"C7\", \"D7\", \"B6\", 0, 0,\n    \"C7\", 0, 0, \"G6\", 0, 0, \"E6\", 0, 0, \"A6\", 0, \"B6\", 0, \"AS6\", \"A6\", 0,\n    \"G6\", \"E7\", 0, \"G7\", \"A7\", 0, \"F7\", \"G7\", 0, \"E7\", 0, \"C7\", \"D7\", \"B6\", 0, 0\n]\n\ndef play_tone(note, duration=0.15):\n    \"\"\"\n    Plays a single tone for the specified duration.\n    If note is 0, it is treated as a rest (no sound).\n    Adjust durations as needed for the best result.\n    \"\"\"\n    if note == 0:\n        # Rest (no sound)\n        speaker.duty_u16(0)\n        sleep(duration)\n    else:\n        # Play the specified note\n        freq = tones[note]\n        speaker.freq(freq)\n        # Set a duty cycle (volume), 0 to 65535\n        speaker.duty_u16(30000)\n        sleep(duration)\n        # Turn the sound off between notes\n        speaker.duty_u16(0)\n        sleep(0.05)\n\n# Play the entire Mario melody\nfor n in mario:\n    play_tone(n)\n\n# Turn speaker off at the end\n# Note that the note still plays on stop/interrupt!\nspeaker.duty_u16(0)\n
"},{"location":"lessons/60-sound/play-tune/#adjust-the-tempo","title":"Adjust the Tempo","text":"
duration=0.15\n
"},{"location":"lessons/60-sound/play-tune/#adding-interrupt-handling","title":"Adding Interrupt Handling","text":"
# Main execution\ntry:\n    print(\"Playing Mario theme...\")\n    play_mario()\n    # Clean up\n    speaker.deinit()\n    print(\"Done!\")\nexcept KeyboardInterrupt:\n    # Clean up if interrupted\n    speaker.deinit()\n    print(\"\\nStopped by user\")\n
from machine import Pin, PWM\nfrom utime import sleep_ms\n\n# Initialize speaker on GPIO 16\nspeaker = PWM(Pin(16))\n\n# Note frequencies in Hz\ntones = {\n    \"B0\": 31, \"C1\": 33, \"CS1\": 35, \"D1\": 37, \"DS1\": 39, \"E1\": 41, \"F1\": 44, \"FS1\": 46,\n    \"G1\": 49, \"GS1\": 52, \"A1\": 55, \"AS1\": 58, \"B1\": 62, \"C2\": 65,\n    \"CS2\": 69, \"D2\": 73, \"DS2\": 78, \"E2\": 82, \"F2\": 87, \"FS2\": 93, \"G2\": 98,\n    \"GS2\": 104, \"A2\": 110, \"AS2\": 117, \"B2\": 123, \"C3\": 131, \"CS3\": 139,\n    \"D3\": 147, \"DS3\": 156, \"E3\": 165, \"F3\": 175, \"FS3\": 185,\n    \"G3\": 196, \"GS3\": 208, \"A3\": 220, \"AS3\": 233, \"B3\": 247, \"C4\": 262, \"CS4\": 277, \"D4\": 294, \"DS4\": 311,\n    \"E4\": 330, \"F4\": 349, \"FS4\": 370, \"G4\": 392, \"GS4\": 415, \"A4\": 440, \"AS4\": 466, \"B4\": 494, \"C5\": 523, \"CS5\": 554, \"D5\": 587, \"DS5\": 622, \"E5\": 659, \"F5\": 698,\n    \"FS5\": 740, \"G5\": 784, \"GS5\": 831, \"A5\": 880, \"AS5\": 932, \"B5\": 988, \"C6\": 1047, \"CS6\": 1109, \"D6\": 1175, \"DS6\": 1245, \"E6\": 1319, \"F6\": 1397, \"FS6\": 1480, \"G6\": 1568, \"GS6\": 1661,\n    \"A6\": 1760, \"AS6\": 1865, \"B6\": 1976, \"C7\": 2093, \"CS7\": 2217, \"D7\": 2349, \"DS7\": 2489, \"E7\": 2637, \"F7\": 2794, \"FS7\": 2960, \"G7\": 3136, \"GS7\": 3322, \"A7\": 3520,\n    \"AS7\": 3729, \"B7\": 3951, \"C8\": 4186, \"CS8\": 4435, \"D8\": 4699, \"DS8\": 4978\n}\n\nmario = [\"E7\", \"E7\", 0, \"E7\", 0, \"C7\", \"E7\", 0, \"G7\", 0, 0, 0, \"G6\", 0, 0, 0, \"C7\", 0, 0, \"G6\",\n         0, 0, \"E6\", 0, 0, \"A6\", 0, \"B6\", 0, \"AS6\", \"A6\", 0, \"G6\", \"E7\", 0, \"G7\", \"A7\", 0, \"F7\", \"G7\",\n         0, \"E7\", 0, \"C7\", \"D7\", \"B6\", 0, 0, \"C7\", 0, 0, \"G6\", 0, 0, \"E6\", 0, 0, \"A6\", 0, \"B6\", 0,\n         \"AS6\", \"A6\", 0, \"G6\", \"E7\", 0, \"G7\", \"A7\", 0, \"F7\", \"G7\", 0, \"E7\", 0, \"C7\", \"D7\", \"B6\", 0, 0]\n\ndef play_tone(frequency, duration=100):\n    \"\"\"Play a tone at the given frequency for the specified duration\"\"\"\n    if frequency > 0:\n        speaker.freq(frequency)  # Set frequency\n        speaker.duty_u16(32768)  # 50% duty cycle (32768 is half of 65535)\n        sleep_ms(duration)\n        speaker.duty_u16(0)     # Turn off tone\n    else:\n        sleep_ms(duration)      # Rest for the specified duration\n\ndef play_mario():\n    \"\"\"Play the Mario theme song\"\"\"\n    # Tempo control\n    tempo = 150  # Adjust this value to control speed (lower = faster)\n\n    # Play each note\n    for note in mario:\n        if note == 0:\n            # Rest\n            play_tone(0, tempo)\n        else:\n            # Play the note\n            play_tone(tones[note], tempo)\n\n        # Brief pause between notes to separate them\n        sleep_ms(50)\n\n# Main execution\ntry:\n    print(\"Playing Mario theme...\")\n    play_mario()\n    # Clean up\n    speaker.deinit()\n    print(\"Done!\")\nexcept KeyboardInterrupt:\n    # Clean up if interrupted\n    speaker.deinit()\n    print(\"\\nStopped by user\")\n
"},{"location":"lessons/70-power/","title":"Powering Your Clocks and Watches","text":""},{"location":"lessons/70-power/#powering-your-raspberry-pi-pico","title":"Powering Your Raspberry Pi Pico","text":"

Understanding how VSYS works and how VSYS is different from VBUS.

Using 3.3 volts for your displays and peripherals

"},{"location":"lessons/70-power/#wall-power-vs-portability","title":"Wall Power vs. Portability","text":""},{"location":"lessons/70-power/#backup-power-when-power-fails","title":"Backup Power When Power Fails","text":""},{"location":"lessons/70-power/#coin-cells-for-real-time-clocks","title":"Coin Cells for Real-time Clocks","text":""},{"location":"lessons/70-power/#usb-power","title":"USB Power","text":""},{"location":"lessons/70-power/#aa-battery-packs","title":"AA Battery Packs","text":""},{"location":"lessons/70-power/#usb-battery-packs","title":"USB Battery Packs","text":""},{"location":"lessons/70-power/#lipo-batteries","title":"LiPo Batteries","text":""},{"location":"lessons/70-power/#lipo-battery","title":"LiPo Battery","text":""},{"location":"lessons/digital-to-analog-converters/","title":"Digital to Analog Converters and the I2S Protocol","text":""},{"location":"lessons/digital-to-analog-converters/#the-pcm5102-dac-board","title":"The PCM5102 DAC Board","text":""},{"location":"lessons/digital-to-analog-converters/#connections","title":"Connections","text":"

Hooking the up PCM5102 board is pretty easy for the general case:

"},{"location":"lessons/digital-to-analog-converters/#pico-connections","title":"Pico Connections","text":"

Use the lower right connections

"},{"location":"lessons/digital-to-analog-converters/#sampling-rates","title":"Sampling Rates","text":"

We can also lower sample rates. The PCM5102A supports the following standard sample rates:

"},{"location":"lessons/digital-to-analog-converters/#references","title":"References","text":"

Todbot Blog: Cheap stereo line out I2S DAC

"},{"location":"lessons/digital-to-analog-converters/code/","title":"Using a DAC to Generate Sounds","text":"
from machine import Pin, I2S\nimport struct\nimport math\nfrom utime import sleep_ms, ticks_ms, ticks_diff\n\n# Pin Definitions\nBCK_PIN = 16      # Connect to BCK on DAC (Bit Clock)\nWS_PIN = 17       # Connect to LCK on DAC (Word Select)\nSD_PIN = 18       # Connect to DIN on DAC (Data Input)\n\n# Audio parameters\nSAMPLE_RATE = 16000\nBUFFER_SIZE = 512  # Larger buffer for smoother playback\n\n# Note frequencies\nNOTES = {\n    'C4': 261.63,\n    'D4': 293.66,\n    'E4': 329.63,\n    'F4': 349.23,\n    'G4': 392.00,\n    'A4': 440.00,\n    'B4': 493.88,\n    'C5': 523.25,\n    'REST': 0\n}\n\n# Simple melody: each tuple contains (note_name, duration_ms)\nMELODY = [\n    ('C4', 300), ('E4', 300), ('G4', 300),  # Ascending arpeggio\n    ('C5', 600),                            # Hold high note\n    ('G4', 200), ('E4', 200), ('C4', 600),  # Descending\n    ('REST', 300),                          # Pause\n    ('G4', 300), ('F4', 300), ('E4', 300),  # Walking down\n    ('D4', 300), ('C4', 600),               # End phrase\n]\n\ndef apply_envelope(value, i, buffer_size, attack_samples=100, release_samples=100):\n    \"\"\"Apply attack and release envelope to reduce clicks and pops\"\"\"\n    if i < attack_samples:\n        return value * (i / attack_samples)\n    elif i > buffer_size - release_samples:\n        return value * ((buffer_size - i) / release_samples)\n    return value\n\ndef make_tone_buffer(frequency):\n    \"\"\"Create a buffer with complete cycles of the sine wave\"\"\"\n    if frequency == 0:  # For REST\n        return bytearray(BUFFER_SIZE * 2)\n\n    # Calculate samples for complete cycles\n    samples_per_cycle = SAMPLE_RATE / frequency\n    num_cycles = max(1, int(BUFFER_SIZE / samples_per_cycle))\n    adjusted_buffer_size = int(num_cycles * samples_per_cycle)\n    if adjusted_buffer_size > BUFFER_SIZE:\n        adjusted_buffer_size = BUFFER_SIZE\n\n    buffer = bytearray(adjusted_buffer_size * 2)\n\n    # Generate a smoother waveform\n    amplitude = 0.15  # Reduced amplitude for cleaner sound\n\n    for i in range(0, adjusted_buffer_size * 2, 2):\n        # Basic sine wave\n        sample_pos = (i // 2) / samples_per_cycle * 2 * math.pi\n        raw_value = math.sin(sample_pos)\n\n        # Apply envelope and amplitude\n        value = int(32767 * amplitude * \n                   apply_envelope(raw_value, i//2, adjusted_buffer_size))\n\n        # Pack into buffer\n        struct.pack_into(\"<h\", buffer, i, value)\n\n    return buffer\n\n# Configure I2S with higher sample precision\naudio_out = I2S(\n    0,\n    sck=Pin(BCK_PIN),\n    ws=Pin(WS_PIN),\n    sd=Pin(SD_PIN),\n    mode=I2S.TX,\n    bits=16,\n    format=I2S.MONO,\n    rate=SAMPLE_RATE,\n    ibuf=2048  # Larger internal buffer\n)\n\nprint(\"Playing melody... Press Ctrl+C to stop\")\n\ntry:\n    while True:\n        for note_name, duration in MELODY:\n            print(f\"\\nPlaying {note_name} for {duration}ms\")\n\n            # Create buffer for this frequency\n            frequency = NOTES[note_name]\n            buffer = make_tone_buffer(frequency)\n\n            # Play the note for specified duration\n            start_time = ticks_ms()\n            while ticks_diff(ticks_ms(), start_time) < duration:\n                audio_out.write(buffer)\n                sleep_ms(10)\n                print(\".\", end=\"\")\n\n            # Longer pause between notes for clearer separation\n            sleep_ms(70)\n\n        # Pause between repetitions\n        sleep_ms(1000)\n\nexcept KeyboardInterrupt:\n    print(\"\\nStopping...\")\nfinally:\n    audio_out.deinit()\n    print(\"Test complete\")\n
"},{"location":"lessons/digital-to-analog-converters/prompt/","title":"Prompt","text":"

Prompt

I need to add sound to my clock. To do this I purchased the following:

Interface I2S PCM5102A DAC Decoder GY-PCM5102 I2S Player Module For Raspberry

It has the following edge connections:

GND - Connected to GND V IN - Connected to 3.3 V LCK - Connected to GPIO 15 D IN - Connected to GPIO 14 BCK - Connected to GPIO 13 SCK - Connected to GPIO 12 LINE OUT - Connected to an 8-ohm speaker

Please help me write a MicroPython program for the Raspberry Pi Pico that can help me test this device.

"},{"location":"lessons/digital-to-analog-converters/prompt/#response","title":"Response","text":"

I'll help you write a MicroPython program to test the I2S DAC module with your Pico. We'll create a simple test that generates a sine wave tone to verify the setup is working correctly.

I2S DAC Test Program

This program provides a basic test of your I2S DAC setup. Here's how it works:

  1. It configures the I2S interface with the pins you've connected:
  2. It creates functions to:
  3. The test sequence plays each note for 500ms.

To use this program:

  1. Save it to your Pico as main.py
  2. When you run it, you should hear a sequence of ascending notes through your speaker
  3. If you don't hear anything:

You can modify the test_sequence() function to play different frequencies or durations. For reference, here are some common note frequencies:

python\nCopy\n\n`NOTE_FREQUENCIES ={'C4':262,'D4':294,'E4':330,'F4':349,'G4':392,'A4':440,'B4':494,'C5':523}`\n

Would you like me to modify the program to include different test patterns or features?

CopyRetry

"},{"location":"lessons/ntp/","title":"Network Time Protocol (NTP)","text":""},{"location":"lessons/ntp/#part-1-understanding-time-synchronization","title":"Part 1: Understanding Time Synchronization","text":""},{"location":"lessons/ntp/#why-do-we-need-ntp","title":"Why Do We Need NTP?","text":"
  1. Computer clocks tend to drift over time
  2. Different devices need to agree on the exact time for:
  3. Coordinating events
  4. Logging activities
  5. Securing network communications
  6. Scheduling tasks
"},{"location":"lessons/ntp/#how-ntp-works","title":"How NTP Works","text":""},{"location":"lessons/ntp/#client-server-model","title":"Client-Server Model","text":""},{"location":"lessons/ntp/#time-server-hierarchy","title":"Time Server Hierarchy","text":""},{"location":"lessons/ntp/#part-2-basic-ntp-communication","title":"Part 2: Basic NTP Communication","text":""},{"location":"lessons/ntp/#the-ntp-request-process","title":"The NTP Request Process","text":"
  1. Your device sends a tiny message to an NTP server
  2. The server adds its timestamp
  3. Your device calculates the time difference, accounting for network delay
  4. Your clock gets adjusted
"},{"location":"lessons/ntp/#understanding-time-formats","title":"Understanding Time Formats","text":"
  1. NTP uses seconds since January 1, 1900
  2. Modern computers use seconds since:
  3. January 1, 1970 (Unix time)
  4. January 1, 2000 (Some embedded systems)
  5. We need to convert between these formats
"},{"location":"lessons/ntp/sample-ntp-function/","title":"NTP Function","text":"

Here is a step-by-step walk though of our NTP function in MicroPython

"},{"location":"lessons/ntp/sample-ntp-function/#function-listing","title":"Function Listing","text":"
# Adapted from official ntptime by Peter Hinch July 2022\n# The main aim is portability:\n# Detects host device's epoch and returns time relative to that.\n# Basic approach to local time: add offset in hours relative to UTC.\n# Timeouts return a time of 0. These happen: caller should check for this.\n# Replace socket timeout with select.poll as per docs:\n# http://docs.micropython.org/en/latest/library/socket.html#socket.socket.settimeout\n\nimport socket\nimport struct\nimport select\nfrom time import gmtime\n\n# (date(2000, 1, 1) - date(1900, 1, 1)).days * 24*60*60\n# (date(1970, 1, 1) - date(1900, 1, 1)).days * 24*60*60\nNTP_DELTA = 3155673600 if gmtime(0)[0] == 2000 else 2208988800\n\n# The NTP host can be configured at runtime by doing: ntptime.host = 'myhost.org'\nhost = \"pool.ntp.org\"\n\ndef time(hrs_offset=0):  # Local time offset in hrs relative to UTC\n    NTP_QUERY = bytearray(48)\n    NTP_QUERY[0] = 0x1B\n    try:\n        addr = socket.getaddrinfo(host, 123)[0][-1]\n    except OSError:\n        return 0\n    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n    poller = select.poll()\n    poller.register(s, select.POLLIN)\n    try:\n        s.sendto(NTP_QUERY, addr)\n        if poller.poll(1000):  # time in milliseconds\n            msg = s.recv(48)\n            val = struct.unpack(\"!I\", msg[40:44])[0]  # Can return 0\n            return max(val - NTP_DELTA + hrs_offset * 3600, 0)\n    except OSError:\n        pass  # LAN error\n    finally:\n        s.close()\n    return 0  # Timeout or LAN error occurred\n

Let's break down the key parts of this code.

# Time difference constants\nNTP_DELTA = 3155673600 if gmtime(0)[0] == 2000 else 2208988800\n
This line determines which epoch your device uses (2000 or 1970) and sets the correct conversion factor.

# Create the NTP request packet\nNTP_QUERY = bytearray(48)\nNTP_QUERY[0] = 0x1B\n
This creates the special message format that NTP servers expect. The 0x1B tells the server this is a time request.

# Connect to NTP server\naddr = socket.getaddrinfo(host, 123)[0][-1]\ns = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n
This code: 1. Looks up the NTP server address 2. Creates a UDP socket for communication 3. Port 123 is the standard NTP port

# Send request and get response\ns.sendto(NTP_QUERY, addr)\nif poller.poll(1000):  # wait up to 1 second\n    msg = s.recv(48)\n    val = struct.unpack(\"!I\", msg[40:44])[0]\n
This section: 1. Sends our time request 2. Waits for up to 1 second for a response 3. Extracts the timestamp from the response

"},{"location":"lessons/ntp/sample-ntp-function/#part-4-hands-on-exercise","title":"Part 4: Hands-on Exercise","text":"

Let's use this code to synchronize a clock:

  1. Save the code as ntp_sync.py
  2. Create a simple clock program:
from machine import RTC\nimport ntp_sync\n\ndef sync_time():\n    # Get time with UTC offset for your timezone\n    # Example: -4 for EDT\n    current_time = ntp_sync.time(-4)  \n    if current_time > 0:\n        # Convert to time tuple and set RTC\n        rtc = RTC()\n        rtc.datetime(gmtime(current_time))\n        print(\"Time synchronized!\")\n    else:\n        print(\"Time sync failed\")\n\n# Run the synchronization\nsync_time()\n
"},{"location":"lessons/ntp/sample-ntp-function/#part-5-understanding-results","title":"Part 5: Understanding Results","text":"

After running the code, you might see: 1. Success: Your device's time is now synchronized 2. Failure: Common causes include: - No internet connection - NTP server timeout - Network restrictions

Remember that in real applications, you should: - Handle errors gracefully - Implement periodic resynchronization - Consider time zones and daylight saving time - Add timeout handling for reliability

"},{"location":"lessons/ntp/sample-ntp-function/#references","title":"References","text":"

[Peter Hinch NTP Function](https://github.com/peterhinch/micropython-samples/blob/master/ntptime/ntptime.py0

"},{"location":"lessons/rtc/","title":"Real Time Clocks","text":"

The DS3231 is a highly accurate real-time clock (RTC) integrated circuit that maintains precise time using an internal temperature-compensated crystal oscillator (TCXO).

Key features:

The device stores time data in BCD format across multiple registers:

0x00: Seconds 0x01: Minutes 0x02: Hours 0x03: Day 0x04: Date 0x05: Month 0x06: Year

Example: Reading hour register 0x02 returns value 0x15 in BCD, representing 15:00 (3:00 PM).

"},{"location":"lessons/rtc/#how-long-will-the-battery-last","title":"How Long Will The Battery Last","text":"

The typical operational duration of the backup coin-cell battery in real-time clock circuits ranges from 2 to 10 years, depending on temperature conditions and current draw.

The DS3231 RTC module used in our kits draw 3\u03bcA at room temperature typically operates for 8 years on a single CR2032 battery. The key factors affecting battery life are:

"},{"location":"lessons/rtc/13-real-time-clocks/","title":"Real Time Clocks","text":"

Note

Minimal real-time clock support has only recently been added to the core MicroPython libraries. Make sure you are using the latest release of MicroPython to use the features built into the MicroPython Runtime.

See The MicroPython RTC Documentation to make sure you have the right release.

Note that the built-in MicroPython library does not setting alarms, turning on and off the oscillator stop flag or accessing the temperature of the RTC (available only on some RTCs like the DS3231). These functions must be done through a library or by providing your own functions.

"},{"location":"lessons/rtc/13-real-time-clocks/#the-ds1307-and-the-ds3231","title":"The DS1307 and the DS3231","text":"

Our lessons use two different RTC chips. Let's do a comparison of these RTC modules and explain their implementation with the MicroPython.

"},{"location":"lessons/rtc/13-real-time-clocks/#ds1307","title":"DS1307","text":"

This is the original battery-backed real-time clock with I2C interface that maintains basic timekeeping functions introduced by Maxim (then Dallas Semiconductor) around 2004-2005. For example, the DS1307 can be used for tracking hours, minutes, seconds with \u00b12 seconds/day accuracy at C 77\u00b0F (25\u00b0). This is appropriate for room temperature household clocks that have a uniform temperature. Large swings in temperature change crystal vibration frequency which can impact clock accuracy.

"},{"location":"lessons/rtc/13-real-time-clocks/#ds3231","title":"DS3231","text":"

This is a newer temperature-compensated real-time clock Released by Maxim Integrated around 2009-2010. With integrated crystal and I2C interface that maintains highly accurate timekeeping under varying conditions.

The DS3231 represented a significant advancement in accuracy and temperature compensation over the earlier DS1307. The improved technology helped make the DS3231 the preferred choice for precision timekeeping applications, despite its higher cost. The DS3231m typically maintains an accuracy of \u00b12 seconds each month (not day) across an incredible range of -40\u00b0F to 185\u00b0F (-40\u00b0C to +85\u00b0C). The DS3231 also has an on-board register you can access to get the current temperature of the device.

Key Technical and Financial Differences:

  1. Accuracy:

  2. DS1307: \u00b12 seconds/day

  3. DS3231: \u00b12 seconds/month due to temperature compensation

  4. Temperature Operation:

  5. DS1307: 32\u00b0F to 158\u00b0F (0\u00b0C to +70\u00b0C)

  6. DS3231: -40\u00b0F to 185\u00b0F (-40\u00b0C to +85\u00b0C) with compensation

  7. Clock Output:

  8. DS1307: Programmable square wave (1Hz, 4kHz, 8kHz, 32kHz)

  9. DS3231: 32kHz output plus programmable square wave

  10. Based on typical retail prices as of 2024:

  11. DS1307: $1-2 USD

  12. DS3231: $3-5 USD
"},{"location":"lessons/rtc/13-real-time-clocks/#implementation-with-micropython","title":"Implementation with MicroPython","text":"

The coding for each of these chips in almost identical since they implement the same I2C interface.

from machine import I2C, Pin\n\n# Both modules use same I2C interface setup\ni2c = I2C(0, sda=Pin(0), scl=Pin(1))\n\n# DS1307 implementation\nDS1307_ADDR = 0x68\ndef ds1307_get_time():\n    return i2c.readfrom_mem(DS1307_ADDR, 0x00, 7)\n\n# DS3231 implementation\nDS3231_ADDR = 0x68\ndef ds3231_get_time():\n    return i2c.readfrom_mem(DS3231_ADDR, 0x00, 7)\n\n# Both modules store time in BCD format\ndef decode_bcd(bcd):\n    return (bcd & 0xF) + ((bcd >> 4) * 10)\n

The code structure remains similar for both modules since they share the I2C interface and address. The DS3231 provides additional registers for temperature data and aging offset, which can be accessed for more precise timekeeping.

For classroom use, the DS3231 is recommended due to its superior accuracy and temperature compensation, though it typically costs more than the DS1307.

"},{"location":"lessons/rtc/13-real-time-clocks/#detecting-the-rtc-type","title":"Detecting the RTC Type","text":"

Both the DS1307 and the DS3231 appear at exactly the same address 0x68. So how can you tell which device you have? The answer is that the DS3231 has additional status, control and temperature registers we can look for. If it has these values we know it is the good stuff!

from machine import I2C, Pin\nimport time\n\n# I2C setup\ni2c = I2C(0, sda=Pin(0), scl=Pin(1))\n\n# Device addresses\nRTC_ADDR = 0x68  # Both DS1307 and DS3231 use 0x68\n\ndef identify_rtc():\n    \"\"\"\n    Identify whether the RTC is a DS1307 or DS3231\n    Returns: String identifying the RTC type\n    \"\"\"\n    try:\n        # Try to read the status register (0x0F) - only exists on DS3231\n        i2c.writeto(RTC_ADDR, b'\\x0F')\n        status = i2c.readfrom(RTC_ADDR, 1)[0]\n\n        # Try to read control register (0x0E) - only exists on DS3231\n        i2c.writeto(RTC_ADDR, b'\\x0E')\n        control = i2c.readfrom(RTC_ADDR, 1)[0]\n\n        # If we got here, it's almost certainly a DS3231\n        # Try reading temperature registers as final confirmation\n        i2c.writeto(RTC_ADDR, b'\\x11')\n        temp_data = i2c.readfrom(RTC_ADDR, 2)\n\n        return \"DS3231 (Temperature-compensated RTC)\"\n\n    except Exception as e:\n        # If we couldn't read those registers, it's probably a DS1307\n        # Let's verify by trying to read the control register (0x07) of DS1307\n        try:\n            i2c.writeto(RTC_ADDR, b'\\x07')\n            control = i2c.readfrom(RTC_ADDR, 1)[0]\n            return \"DS1307 (Basic RTC)\"\n        except:\n            return \"Unknown RTC device\"\n\ndef main():\n    print(\"\\nRTC Model Identifier\")\n    print(\"-\" * 40)\n\n    # First check if any device is present at RTC address\n    devices = i2c.scan()\n    if RTC_ADDR not in devices:\n        print(f\"No RTC found at address 0x{RTC_ADDR:02X}\")\n        return\n\n    # Identify the RTC\n    rtc_type = identify_rtc()\n    print(f\"Found: {rtc_type}\")\n\n    if \"DS3231\" in rtc_type:\n        # Read temperature for DS3231\n        i2c.writeto(RTC_ADDR, b'\\x11')\n        temp_data = i2c.readfrom(RTC_ADDR, 2)\n        temp_msb = temp_data[0]\n        temp_lsb = (temp_data[1] >> 6) * 25  # 0.25\u00b0C precision\n        temp_c = temp_msb + (temp_lsb / 100.0)\n        temp_f = (temp_c * 9/5) + 32\n        print(f\"Temperature: {temp_c:.2f}\u00b0C ({temp_f:.2f}\u00b0F)\")\n\nif __name__ == \"__main__\":\n    main()\n
"},{"location":"lessons/rtc/13-real-time-clocks/#rtcs-and-eeprom","title":"RTCs and EEPROM","text":"

Some RTCs also include a small 4K EEPROM to store information such as what time zone you are in and what the clock skew was for the last period. If your i2c scanner shows something at digital 80 (0x50) when you add your RTC, this is your EEPROM.

The device at 80 (0x50) is almost certainly an AT24C32 EEPROM (Electrically Erasable Programmable Read-Only Memory). It's commonly included on DS3231 RTC modules to provide non-volatile storage. This EEPROM can store about 4KB of data and is often used to store configuration settings.

You can get a dump of its contents by doing the following detection code:

from machine import I2C, Pin\nimport time\n\n# I2C setup\ni2c = I2C(0, sda=Pin(0), scl=Pin(1))\n\n# Device addresses\nDS3231_ADDR = 0x68  # 104 decimal\nEEPROM_ADDR = 0x50  # 80 decimal\n\ndef read_ds3231_temp():\n    \"\"\"Read temperature from DS3231\"\"\"\n    try:\n        # First ensure we're reading fresh temperature data\n        # Write to the control register (0x0E) to force a temperature conversion\n        i2c.writeto(DS3231_ADDR, b'\\x0E\\x20')  # Set CONV bit\n        time.sleep(0.2)  # Wait for conversion\n\n        # Now read temperature registers (0x11 and 0x12)\n        i2c.writeto(DS3231_ADDR, b'\\x11')\n        temp_data = i2c.readfrom(DS3231_ADDR, 2)\n\n        # MSB is the integer part\n        temp_msb = temp_data[0]\n\n        # LSB holds two bits for decimal part\n        temp_lsb = (temp_data[1] >> 6) * 25  # Convert to decimal (0.25\u00b0C precision)\n\n        # Handle negative temperatures (2's complement)\n        if temp_msb & 0x80:\n            temp_msb = -(~(temp_msb - 1) & 0xFF)\n\n        temp_c = temp_msb + (temp_lsb / 100.0)  # Combine integer and decimal parts\n        temp_f = (temp_c * 9/5) + 32\n\n        return temp_c, temp_f\n    except Exception as e:\n        return f\"Error reading temperature: {str(e)}\"\n\ndef read_ds3231_time():\n    \"\"\"Read current time from DS3231\"\"\"\n    try:\n        # Start reading from register 0x00\n        i2c.writeto(DS3231_ADDR, b'\\x00')\n        data = i2c.readfrom(DS3231_ADDR, 7)\n\n        # Convert BCD to decimal\n        def bcd2dec(bcd):\n            return (bcd & 0x0F) + ((bcd >> 4) * 10)\n\n        second = bcd2dec(data[0])\n        minute = bcd2dec(data[1])\n        hour = bcd2dec(data[2])\n        day = bcd2dec(data[4])\n        month = bcd2dec(data[5] & 0x1F)\n        year = bcd2dec(data[6]) + 2000\n\n        return f\"{year:04d}-{month:02d}-{day:02d} {hour:02d}:{minute:02d}:{second:02d}\"\n    except Exception as e:\n        return f\"Error reading time: {str(e)}\"\n\ndef read_eeprom(start_addr=0, length=32):\n    \"\"\"Read data from EEPROM\"\"\"\n    try:\n        # Create buffer for address\n        addr_buf = bytearray(2)\n        addr_buf[0] = (start_addr >> 8) & 0xFF  # High byte\n        addr_buf[1] = start_addr & 0xFF         # Low byte\n\n        # Write address to EEPROM\n        i2c.writeto(EEPROM_ADDR, addr_buf)\n\n        # Read data\n        data = i2c.readfrom(EEPROM_ADDR, length)\n        return data\n    except Exception as e:\n        return f\"Error reading EEPROM: {str(e)}\"\n\ndef main():\n    \"\"\"Main program to read and display device information\"\"\"\n    print(\"\\nI2C Device Information Reader\")\n    print(\"-\" * 40)\n\n    # Scan for devices\n    devices = i2c.scan()\n    print(f\"Found devices at addresses: {[hex(x) for x in devices]}\")\n    print(\"-\" * 40)\n\n    # Read and display DS3231 information\n    # Note that the DS1307 does not have any temperature registers\n    if DS3231_ADDR in devices:\n        print(\"DS3231 RTC Information:\")\n        print(f\"Current Time: {read_ds3231_time()}\")\n        temp_c, temp_f = read_ds3231_temp()\n        print(f\"Temperature: {temp_c:.2f}\u00b0C ({temp_f:.2f}\u00b0F)\")\n    else:\n        print(\"DS3231 not found!\")\n\n    print(\"-\" * 40)\n\n    # Read and display EEPROM contents\n    if EEPROM_ADDR in devices:\n        print(\"AT24C32 EEPROM Contents (first 32 bytes):\")\n        eeprom_data = read_eeprom(0, 32)\n\n        # Display as hex\n        print(\"Hex dump:\")\n        for i in range(0, len(eeprom_data), 16):\n            chunk = eeprom_data[i:i+16]\n            hex_values = ' '.join([f'{x:02X}' for x in chunk])\n            ascii_values = ''.join([chr(x) if 32 <= x <= 126 else '.' for x in chunk])\n            print(f\"{i:04X}: {hex_values:<48} {ascii_values}\")\n\n        # Try to interpret as ASCII\n        print(\"\\nASCII interpretation (if printable):\")\n        ascii_text = ''.join([chr(x) if 32 <= x <= 126 else '.' for x in eeprom_data])\n        print(ascii_text)\n    else:\n        print(\"EEPROM not found!\")\n\nif __name__ == \"__main__\":\n    main()\n
"},{"location":"lessons/rtc/13-real-time-clocks/#results-of-scan-on-rtm-with-eeprom","title":"Results of Scan on RTM with EEPROM","text":"
AT24C32 EEPROM Contents (first 32 bytes):\nHex dump:\n0000: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF  ................\n0010: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF  ................\nASCII interpretation (if printable):\n................................\n
"},{"location":"lessons/rtc/13-real-time-clocks/#dump-eeprom-address","title":"Dump EEPROM Address","text":"

The AT24C32/AT24C64 EEPROM typically is located at 87 (0x57).

Here is a script to dump this memory.

import time\n\n# I2C setup\ni2c = I2C(0, sda=Pin(0), scl=Pin(1))\n\n# Device addresses\nRTC_ADDR = 0x68  # Both DS1307 and DS3231 use 0x68\n# EEPROM address\nEEPROM_ADDR = 0x57\n\ndef dump_eeprom():\n   # Read first 100 bytes in chunks of 8\n   for addr in range(0, 100, 8):\n       data = i2c.readfrom_mem(EEPROM_ADDR, addr, 8)\n       print(f\"0x{addr:02x}:\", \" \".join([f\"{x:02x}\" for x in data]))\n       time.sleep(0.1)  # Small delay between reads\n\ndump_eeprom()\n
"},{"location":"lessons/rtc/13-real-time-clocks/#setting-thr-rtc-from-localtime","title":"Setting thr RTC from localtime","text":"

After you get your RTC clock working it is time to set the correct time in the RTC. You will notice that when the RTC first comes up the date and time will be set to January 1st 2015 at midnight. We now want to update this time with the time from your local laptop or desktop. If you use Thonny, the localtime in the Pico is automatically set to the time on computer when Thonny starts up and connects to the Raspberry Pi Pico.

Now we just need a small program to take the localtime and write it to the RTC's clock. Here is tha line:

i2c.writeto_mem(DS3231_ADDR, 0x00, data)\n

Where data is a binary coded list of binary-coded integers that represent the datetime structure. Here is an example program called set-rtc-from-localtime.py

One other note. The DS3231 has an internal oscillator that can get turned off. After you set the time it is important to make sure that the oscillator is running correctly again. The function reset_osf() will clear the stop flag to make sure that the clock is ticking.

from machine import Pin, I2C\nfrom utime import localtime\n\n# Constants\nDS3231_ADDR = 0x68\nSTATUS_REG = 0x0F  # Status register address\n\n# I2C setup\nsda = Pin(0, Pin.OUT)\nscl = Pin(1, Pin.OUT)\ni2c = I2C(0, scl=scl, sda=sda, freq=3000000)\n\ndef dec2bcd(dec):\n    \"\"\"Convert decimal to binary coded decimal.\"\"\"\n    return (dec // 10) << 4 | (dec % 10)\n\ndef check_osf():\n    \"\"\"Check the oscillator stop flag.\"\"\"\n    status = i2c.readfrom_mem(DS3231_ADDR, STATUS_REG, 1)[0]\n    return bool(status >> 7)\n\ndef reset_osf():\n    \"\"\"Clear the oscillator stop flag.\"\"\"\n    status = bytearray(1)\n    i2c.readfrom_mem_into(DS3231_ADDR, STATUS_REG, status)\n    i2c.writeto_mem(DS3231_ADDR, STATUS_REG, bytearray([status[0] & 0x7f]))\n\ndef set_ds3231():\n    \"\"\"Set the DS3231 RTC time and ensure oscillator is running.\"\"\"\n    now = localtime()\n    year = now[0] % 100  # Convert to 2-digit year\n    month = now[1]\n    day = now[2]\n    hour = now[3]\n    minute = now[4]\n    second = now[5]\n\n    # First check if oscillator is stopped\n    if check_osf():\n        print(\"Oscillator was stopped. Resetting OSF flag...\")\n        reset_osf()\n\n    data = bytearray([\n        dec2bcd(second),\n        dec2bcd(minute),\n        dec2bcd(hour),\n        dec2bcd(now[6] + 1),  # Convert weekday from 0-6 to 1-7\n        dec2bcd(day),\n        dec2bcd(month),\n        dec2bcd(year)\n    ])\n\n    i2c.writeto_mem(DS3231_ADDR, 0x00, data)\n    print(f\"RTC set to: {month}/{day}/{now[0]} {hour:02d}:{minute:02d}:{second:02d}\")\n\n    # Verify oscillator is running\n    if check_osf():\n        print(\"Warning: Oscillator still shows stopped state!\")\n    else:\n        print(\"Oscillator running normally\")\n\nif __name__ == \"__main__\":\n    set_ds3231()\n
"},{"location":"lessons/rtc/13-real-time-clocks/#bcd-format","title":"BCD Format","text":"
def dec2bcd(dec):\n    \"\"\"Convert decimal to binary coded decimal.\"\"\"\n    return (dec // 10) << 4 | (dec % 10)\n

This converts regular numbers to Binary Coded Decimal format that the DS3231 requires. For example, 23 becomes 0x23.

BCD (Binary-Coded Decimal) is used in the DS3231 RTC for three main reasons:

  1. Readability - Each digit maps directly to its binary value (e.g., 23 becomes 0x23), making it easier to read register values when debugging.
  2. Easy conversion - Converting between BCD and human-readable time formats requires minimal processing, important for simple microcontrollers
  3. Historical compatibility - Many early digital clock chips used BCD, and the format persisted in RTC design for consistency

In BCD, each decimal digit (0-9) is stored in 4 bits, with two digits per byte. For example:

Decimal 23 \u2192 BCD 0010 0011 Decimal 59 \u2192 BCD 0101 1001

"},{"location":"lessons/rtc/13-real-time-clocks/#avoid-the-builtin-machinertc-library","title":"Avoid the Builtin Machine.RTC Library","text":"

The machine.RTC() on the Pico uses an internal oscillator that loses time on power loss, unlike the DS3231 which maintains time with its battery backup. The DS3231 is also more accurate (\u00b12ppm vs \u00b110% for the internal RTC).

Unless you need absolute minimal components, stick with the DS3231 for better accuracy and battery backup capability.

The internal RTC example on the MicroPython RP2 site would work functionally, but would need resetting after each power cycle.

"},{"location":"lessons/rtc/13-real-time-clocks/#reading-the-temperature-from-the-ds3231-real-time-clock","title":"Reading the Temperature from the DS3231 Real Time Clock","text":"

The math of reading the temperature from the DS3231 is unfortunately rather complicated. Here are the steps

  1. Strip off the sign bit by doing a Boolean AND with 0x7f
  2. If the MSB is set we need to make the temperature negative
  3. Then we add the fraction from the LSB
  4. Finally, we convert the temperature in C to F
def read_temperature():\n    \"\"\"Read temperature from DS3231 RTC.\"\"\"\n    # Read temperature registers\n    i2c.writeto(DS3231_ADDR, b'\\x11')\n    temp_data = i2c.readfrom(DS3231_ADDR, 2)\n    temp_msb = temp_data[0]\n    temp_lsb = temp_data[1]\n\n    # Get raw temp value (ignoring sign bit)\n    raw_temp = temp_msb & 0x7F  # Strip off sign bit\n\n    # 0xD7 & 0x7F = 0x57 = 87 decimal (original value minus sign bit)\n    # If sign bit was set, make it negative\n    if temp_msb & 0x80:\n        raw_temp = raw_temp ^ 0x7F  # Invert the bits\n        raw_temp = -(raw_temp + 1)  # Two's complement\n\n    # Add fraction from LSB\n    frac = (temp_lsb >> 6) * 0.25\n    temp_c = raw_temp + frac\n\n    # Convert to Fahrenheit\n    temp_f = (temp_c * 9.0 / 5.0) + 32.0\n\n    print(f\"Raw temp (after sign bit removal): {raw_temp}\")\n    print(f\"Temperature: {temp_c}\u00b0C = {temp_f}\u00b0F\")\n\n    return temp_f\n
"},{"location":"lessons/rtc/13-rtc-explained/","title":"RTC Explained","text":""},{"location":"lessons/rtc/13-rtc-explained/#explaining-rtc-to-students","title":"Explaining RTC to Students","text":"

Prompt

You are a friendly and fun science teacher working with a group of high-school students taking a course called \"Programming Clocks and Watches\". Today your lecture is on \"Real time Clocks\" like the old DS1307 and the newer super accurate DS3231. Give the full text of a 15-minute lecture before the hands-on lab and mention where images should be inserted.

Hello everyone, and welcome to today's exciting lecture on Real Time Clocks! In our \"Programming Clocks and Watches\" course, we're going to dive into the fascinating world of the good old DS1307 used in our classrooms and the new super accurate DS3231.

First, let's talk about what a Real Time Clock (RTC) is. An RTC is a computer chip that keeps track of the current time, even when the main power is off. It's like a tiny, dedicated clock inside your electronic devices. RTCs are found in computers, smartphones, appliances, and many other gadgets that need to keep accurate time.

Here is how the DS1307 is typically used in a microcontroller project.

The DS1307 is one of the most well-known RTCs. We have a LOT of them lying around the lab. This little chip was first introduced in 2005 and has been widely used in various electronic projects. The DS1307 keeps track of seconds, minutes, hours, days, months, and years. It communicates with the main processor using a protocol called I2C (Inter-Integrated Circuit).

Now, let's take a look at how the DS1307 works. It has a built-in crystal oscillator that vibrates at a specific frequency, acting as a precise clock source. The chip counts these oscillations to keep track of time. The DS1307 also has a small amount of battery-backed memory, allowing it to maintain the time even when the main power is off.

Speaking of power, the DS1307 uses a small coin cell battery to keep the clock running when the main power is disconnected. This battery can last for several years, ensuring that your device always has the correct time.

Now, let's move on to the newer and more accurate DS3231. Released in 2011, the DS3231 is known for its incredible precision. It has a temperature-compensated crystal oscillator (TCXO), which means it can maintain accurate timekeeping even when the temperature changes. This is important because temperature fluctuations can affect the frequency of regular crystal oscillators, causing the clock to drift over time.

Device Strength Accuracy (PPM) Seconds Drift Per Month Price DS1307 Low Cost \u00b120 \u00b152 $1 DS3231 Accuracy \u00b12 \u00b15 $3

The DS1307 uses a standard crystal oscillator while the DS3231 uses a temperature-compensated crystal oscillator (TCXO), explaining the accuracy difference. How accurate is the DS3231, you might ask? Well, it has a typical accuracy of \u00b12 parts per million (ppm), which translates to a drift of about 1 minute per year! In comparison, the DS1307 has an accuracy of around \u00b120 ppm, which means it can drift by about 10 minutes per year. That's a significant difference!

[Insert image of I2C communication]

Just like the DS1307, the DS3231 communicates with the main processor using the I2C protocol. It has a simple register-based interface that allows you to read and set the time easily.

Now, let's talk about some fun applications of RTCs. One cool project you can build is a digital clock that always displays the correct time, even if it's been unplugged for a while. You can also create a data logger that records temperature, humidity, or other sensor readings along with precise timestamps. The possibilities are endless!

In our hands-on lab today, we'll be working with both the DS1307 and the DS3231. You'll learn how to wire them up, set the time, and read the current time using a microcontroller. We'll also explore some practical examples and discuss how to choose the right RTC for your projects.

So, get ready to have some fun with Real Time Clocks! Remember, whether you're building a simple clock or a complex data logging system, an RTC is an essential component that keeps everything running on time.

Let's head to the lab and start tinkering with these amazing little chips!

"},{"location":"lessons/rtc/13-rtc-explained/#testing-the-ds1307-and-ds3232-real-time-clocks","title":"Testing the DS1307 and DS3232 Real Time Clocks","text":""},{"location":"lessons/rtc/13-rtc-explained/#hardware-configuration","title":"Hardware Configuration","text":""},{"location":"lessons/rtc/13-rtc-explained/#ds1307-connections","title":"DS1307 Connections","text":"
  1. CLK - clock
  2. DST - Data (incoming to Pico)
  3. RST - Reset
  4. VCC - must be 5 volts, not 3.2
  5. GND - ground

We will use the lower right pins on the Pico so that the display can use the pins in the upper-right corner.

These pin assignments are

  1. GP16 - far lower right - CLK
  2. GP17 - second from the bottom - DST
  3. GP18 - fourth pin up - RST

Contents of config.py

CLK_PIN = 16\nDST_PIN = 17\nRST_PIN = 18\n
"},{"location":"lessons/rtc/13-rtc-explained/#sample-ds1307-micropython-test-program-for-the-raspberry-pi-pico","title":"Sample DS1307 MicroPython Test Program for the Raspberry Pi Pico","text":"
from machine import I2C, Pin\nimport config\n\nCLK_PIN = config.CLK_PIN\nDST_PIN = config.DST_PIN\nDS_PIN = config.DS_PIN\n\nprint(\"Clock on pin:\", CLK_PIN)\nprint(\"Data on pin:\", DST_PIN)\nprint(\"Data Select on pin:\", DS_PIN)\n\ni2c = I2C(0, scl=Pin(CLK_PIN), sda=Pin(DST_PIN), freq=100000)\nscan_result = i2c.scan()\nprint(\"I2C addresses found:\", [hex(device_address) for device_address in scan_result])\n\nif 104 in scan_result:\n    print(\"PASS: DS1307 FOUND\")\nelse:\n    print(\"FAIL: DS1307 NOT FOUND\")\n
"},{"location":"lessons/rtc/13-rtc-explained/#sample-test-result","title":"Sample Test Result","text":"
Clock on pin: 17\nData on pin: 16\nData Select on pin: 18\nI2C addresses found: ['0x50', '0x68']\nPASS: DS1307 FOUND\n
"},{"location":"lessons/rtc/13-rtc-explained/#full-test-of-ds1307","title":"Full Test of DS1307","text":"
#!/usr/bin/env python3\n# -*- coding: UTF-8 -*-\n\n\"\"\"I2C DS1307 showcase\"\"\"\n\nfrom ds1307 import DS1307\nfrom machine import I2C, Pin\nfrom utime import gmtime, sleep, time\nimport config\nCLK_PIN = config.CLK_PIN\nDST_PIN = config.DST_PIN\nDS_PIN = config.DS_PIN\n\n# DS1307 on 0x68\nI2C_ADDR = 0x68     # DEC 104, HEX 0x68\n\n# define custom I2C interface, default is 'I2C(0)'\n# check the docs of your device for further details and pin infos\n# this are the pins for the Raspberry Pi Pico adapter board\ni2c = I2C(0, scl=Pin(CLK_PIN), sda=Pin(DST_PIN), freq=800000)\nprint(i2c.scan())\nds1307 = DS1307(addr=I2C_ADDR, i2c=i2c)\n\n# get LCD infos/properties\nprint(\"DS1307 is on I2C address 0x{0:02x}\".format(ds1307.addr))\nprint(\"Weekday start is {}\".format(ds1307.weekday_start))\n\n# get the current RTC time\nprint(\"Current RTC time: {}\".format(ds1307.datetime))\n\n# set the RTC time to the current system time\nnow = gmtime(time())\nds1307.datetime = now\n\n# Print the date and time in ISO8601 format: 2023-04-18T21:14:22\nprint(\"Today is {:04d}-{:02d}-{:02d}T{:02d}:{:02d}:{:02d}\".format(\n    ds1307.year, ds1307.month, ds1307.day,\n    ds1307.hour, ds1307.minute, ds1307.second))\n\n# check whether this year is a leap year\nprint(\"Is this year a leap year? {}\".format(ds1307.is_leap_year(ds1307.year)))\n\n# get the day of the year\nprint(\"Today is day {} of {}\".format(\n    ds1307.day_of_year(year=ds1307.year, month=ds1307.month, day=ds1307.day),\n    ds1307.year))\n\n# halt the oscillator\nprint(\"The oscillator is currently active at {}? {}\".format(\n    ds1307.datetime, ds1307.halt))\nprint(\"Halt the oscillator and wait for 5 seconds ...\")\nds1307.halt = True\nsleep(5)\n\nprint(\"Current RTC time: {}\".format(ds1307.datetime))\n\nprint(\"Enable the oscillator and wait for 5 seconds ...\")\nds1307.halt = False\nsleep(5)\nprint(\"Current RTC time: {}\".format(ds1307.datetime))\n
"},{"location":"lessons/rtc/13-rtc-explained/#test-result","title":"Test Result","text":"
[80, 104]\nDS1307 is on I2C address 0x68\nWeekday start is 0\nCurrent RTC time: (2024, 12, 27, 20, 29, 47, 4, 362)\nToday is 2024-12-27T20:29:51\nIs this year a leap year? True\nToday is day 362 of 2024\nThe oscillator is currently active at (2024, 12, 27, 20, 29, 51, 4, 362)? False\nHalt the oscillator and wait for 5 seconds ...\nCurrent RTC time: (2024, 12, 27, 20, 29, 51, 4, 362)\nEnable the oscillator and wait for 5 seconds ...\nCurrent RTC time: (2024, 12, 27, 20, 29, 56, 4, 362)\n
"},{"location":"lessons/rtc/13-rtc-explained/#localtime-and-gmtimetime","title":"Localtime and gmtime(time())","text":"
from utime import gmtime, time, localtime\nprint(\"time()\", time())\nprint(\"   localtime()\", localtime())\nprint(\"gmtime(time())\", gmtime(time()))\n

Result

time() 1735332067\n   localtime() (2024, 12, 27, 20, 41, 7, 4, 362)\ngmtime(time()) (2024, 12, 27, 20, 41, 7, 4, 362)\n
This code is running on MicroPython on the Raspberry Pi Pico (RP2040) and demonstrates the usage of time-related functions from the utime module.

Let's break it down:

  1. from utime import gmtime, time, localtime: This line imports the gmtime, time, and localtime functions from the utime module, which is MicroPython's equivalent of the time module in standard Python.
  2. print(\"time()\", time()): This line prints the label \"time()\" followed by the result of calling the time() function. The time() function returns the number of seconds elapsed since the Unix epoch (January 1, 1970, 00:00:00 UTC) as an integer value. In your output, time() returned 1735332067, which represents the current timestamp.
  3. print(\" localtime()\", localtime()): This line prints the label \"localtime()\" followed by the result of calling the localtime() function. The localtime() function takes no arguments and returns a tuple representing the current local time. The tuple contains the following elements in order: (year, month, day, hour, minute, second, weekday, yearday). In your output, localtime() returned (2024, 12, 27, 20, 41, 7, 4, 362), indicating the current local time on the Raspberry Pi Pico.
  4. print(\"gmtime(time())\", gmtime(time())): This line prints the label \"gmtime(time())\" followed by the result of calling the gmtime() function with the current timestamp obtained from time(). The gmtime() function takes a timestamp as an argument and returns a tuple representing the corresponding UTC time. The tuple has the same format as the one returned by localtime(). In your output, gmtime(time()) returned (2024, 12, 27, 20, 41, 7, 4, 362), which represents the current UTC time.

What does the number that time() returns represent?

The number returned by time() represents the number of seconds that have elapsed since the Unix epoch (January 1, 1970, 00:00:00 UTC). This value is commonly known as the Unix timestamp or epoch time. It is a widely used standard for representing points in time and is independent of time zones.

In our example, time() returned 1735332067. This timestamp corresponds to the date and time shown in the localtime() and gmtime(time()) outputs, which is December 27, 2024, at 20:41:07 UTC.

The Unix timestamp is a useful representation of time because it allows for easy arithmetic operations on timestamps and can be converted to human-readable formats using functions like localtime() and gmtime(). It is widely used in various programming languages and systems for time-related operations.

It's important to note that the accuracy and synchronization of the time on the Raspberry Pi Pico depend on its internal clock and any time synchronization mechanisms used. Without external time synchronization, the Pico's internal clock may drift over time, resulting in slight inaccuracies compared to the actual current time.

"},{"location":"lessons/rtc/13-rtc-explained/#accuracy-of-the-clock-on-the-raspberry-pi-pico","title":"Accuracy of the Clock on the Raspberry Pi Pico**","text":"

The Raspberry Pi Pico does not have a real-time clock (RTC) built into its hardware. Its clock is derived from the internal oscillator, which may be subject to drift and is not highly accurate for long-term timekeeping. The accuracy depends on the quality of the oscillator and the environmental conditions, such as temperature.

\\[ 1\\% \\text{ of 24 hours} = 0.01 \\times 24 \\times 3600 \\approx 864 \\, \\text{seconds off per day}. \\]

Thus, the clock on the Pico can drift up to \u00b1864 seconds per day without correction.

For improved accuracy, you can add an external RTC module to the Pico.

Accuracy of the Clock on the DS1307

The DS1307 is a low-cost RTC chip widely used in electronics projects. Its accuracy is based on an external 32.768 kHz quartz crystal oscillator, which is sensitive to factors like temperature and crystal quality. \u2022 Drift: The DS1307 typically drifts by \u00b120 ppm (parts per million) at 25\u00b0C. \u2022 Seconds off per day: With a drift of 20 ppm:

\\[ 20 \\, \\text{ppm} \\times 24 \\times 3600 \\, \\text{seconds} = 1.728 \\, \\text{seconds off per day}. \\]

The DS1307 clock may be off by approximately \u00b11.7 seconds per day under ideal conditions.

For more accurate timekeeping, consider using a higher-quality RTC like the DS3231, which has a drift of \u00b12 ppm (about \u00b10.17 seconds per day) due to its temperature compensation feature.

"},{"location":"lessons/rtc/13-rtc-explained/#references","title":"References","text":""},{"location":"lessons/rtc/50-ds1307-breadboard/","title":"Using the DS1307 IC On A Breadboard","text":"

You can use the DS1307 IC directly on a breadboard without a development board. Here's what you'll need:

  1. DS1307 IC (8-pin DIP package)
  2. 32.768 kHz crystal oscillator
  3. CR2032 battery holder and battery (for backup power)
  4. Two 4.7k\u03a9 pull-up resistors (for I2C)
  5. 0.1\u00b5F decoupling capacitor
  6. Standard through-hole breadboard

The basic connections are:

DS1307 Pinout:\nPin 1 (X1) -> Crystal\nPin 2 (X2) -> Crystal\nPin 3 (VBAT) -> Battery positive\nPin 4 (GND) -> Ground\nPin 5 (SDA) -> I2C Data (needs pull-up)\nPin 6 (SCL) -> I2C Clock (needs pull-up)\nPin 7 (SQW) -> Optional square wave output\nPin 8 (VCC) -> 5V power`\n

The biggest advantages of using the raw IC are:

Just remember that the DS1307 requires 5V power (unlike its successor DS3231 which can work with 3.3V), so make sure your microcontroller's I2C lines can handle 5V or use a level shifter if necessary.

"},{"location":"lessons/rtc/testing-the-rtc-type/","title":"Testing the RTC Type","text":""},{"location":"lessons/rtc/testing-the-rtc-type/#two-types","title":"Two Types","text":""},{"location":"lessons/rtc/testing-the-rtc-type/#tests","title":"Tests","text":""},{"location":"lessons/rtc/testing-the-rtc-type/#tests-specific-register-patterns-that-are-unique-to-the-ds3231","title":"Tests specific register patterns that are unique to the DS3231:","text":"
-   Control register bit patterns\n-   Status register bit patterns\n-   Temperature register behavior\n
"},{"location":"lessons/rtc/testing-the-rtc-type/#performs-consistency-checks","title":"Performs consistency checks:","text":"
-   Verifies temperature readings are in valid range (-40\u00b0C to +85\u00b0C)\n-   Checks if temperature readings make sense\n-   Verifies register bit patterns match datasheet specifications\n
"},{"location":"lessons/rtc/testing-the-rtc-type/#better-ds1307-detection","title":"Better DS1307 detection:","text":"
-   Checks the DS1307's control register pattern\n-   Verifies the register behaves as expected\n
from machine import Pin, I2C\nfrom utime import sleep\n\ndef test_ds3231_features(i2c, addr):\n    \"\"\"\n    More thoroughly test if the device is actually a DS3231\n    Returns: (is_ds3231, error_message)\n    \"\"\"\n    try:\n        # Test 1: Check control and status registers with specific masks\n        # DS3231 control register (0x0E) should have bits 6,5,4,2 readable/writable\n        # Other bits should be 0\n        control = i2c.readfrom_mem(addr, 0x0E, 1)[0]\n        if control & 0x83 != 0:  # Bits 7,1,0 should be 0\n            return False, \"Control register pattern doesn't match DS3231\"\n\n        # Test 2: Status register (0x0F) should only have bits 7,3,2,1,0 possibly set\n        status = i2c.readfrom_mem(addr, 0x0F, 1)[0]\n        if status & 0x70 != 0:  # Bits 6,5,4 should be 0\n            return False, \"Status register pattern doesn't match DS3231\"\n\n        # Test 3: Try temperature register consistency check\n        # Read temperature twice with a forced conversion between\n        temp1 = i2c.readfrom_mem(addr, 0x11, 2)\n\n        # Force conversion\n        control |= (1 << 5)  # Set CONV bit\n        i2c.writeto_mem(addr, 0x0E, bytes([control]))\n        sleep(0.01)  # Wait for conversion\n\n        temp2 = i2c.readfrom_mem(addr, 0x11, 2)\n\n        # The readings should be similar and in a reasonable range (-40\u00b0C to +85\u00b0C)\n        msb1 = temp1[0]\n        msb2 = temp2[0]\n\n        # Check if temperatures are in valid range\n        if abs(msb1) > 85 or abs(msb2) > 85:\n            return False, \"Temperature readings out of valid range\"\n\n        return True, \"DS3231 verified\"\n\n    except Exception as e:\n        return False, f\"Error testing DS3231 features: {str(e)}\"\n\ndef identify_rtc():\n    \"\"\"\n    Identify whether the RTC is a DS1307 or DS3231\n    \"\"\"\n    # Initialize I2C\n    i2c = I2C(0, sda=Pin(8), scl=Pin(9), freq=100000)\n\n    RTC_ADDR = 0x68\n\n    # First check if any device is present\n    devices = i2c.scan()\n    if RTC_ADDR not in devices:\n        return \"No RTC found at address 0x68\"\n\n    # Test for DS3231 features\n    is_ds3231, message = test_ds3231_features(i2c, RTC_ADDR)\n\n    if is_ds3231:\n        # Get temperature if it's really a DS3231\n        try:\n            temp_msb = i2c.readfrom_mem(RTC_ADDR, 0x11, 1)[0]\n            temp_lsb = i2c.readfrom_mem(RTC_ADDR, 0x12, 1)[0] >> 6\n\n            if temp_msb & 0x80:\n                temp_msb = -(~temp_msb & 0x7F) - 1\n\n            temp_c = temp_msb + ((temp_lsb & 0x03) * 0.25)\n            temp_f = (temp_c * 9/5) + 32\n\n            return f\"DS3231 found! Current temperature: {temp_c:.2f}\u00b0C ({temp_f:.2f}\u00b0F)\"\n        except Exception as e:\n            return f\"DS3231 found but error reading temperature: {str(e)}\"\n    else:\n        # Test for DS1307\n        try:\n            # DS1307 has a unique control register at 0x07\n            # It should only have bit 4 (OUT) possibly set\n            control = i2c.readfrom_mem(RTC_ADDR, 0x07, 1)[0]\n            if control & 0xEF == 0:  # All bits except bit 4 should be 0\n                return \"DS1307 found!\"\n            else:\n                return \"Found RTC at 0x68 but cannot definitively identify type\"\n        except:\n            return \"Unknown RTC device type\"\n\ndef main():\n    print(\"\\nRTC Identifier\")\n    print(\"-\" * 40)\n\n    result = identify_rtc()\n    print(result)\n\nif __name__ == \"__main__\":\n    main()\n
"},{"location":"lessons/screen-drawing/01-drawing-coordinates/","title":"Drawing Coordinates","text":"

Screen Coordinates Lab on Robot Faces

"},{"location":"lessons/screen-drawing/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/screen-drawing/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/screen-drawing/03-drawing-face/#references","title":"References","text":"

Instructables on Filled Circles and Triangles by TonyGo2

"},{"location":"lessons/screen-drawing/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/screen-drawing/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/screen-drawing/05-drawing-polygons/","title":"Drawing Polygons","text":""},{"location":"lessons/screen-drawing/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/screen-drawing/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/screen-drawing/06-draw-performance/#references","title":"References","text":"
  1. Notes on the very slow refresh rate for ST7735
"},{"location":"lessons/screen-drawing/07-drawing-digits/","title":"Drawing Clock Digits","text":""},{"location":"lessons/screen-drawing/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/screen-drawing/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/screen-drawing/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/screen-drawing/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/screen-drawing/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, 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 # 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 the line width function in the next lesson.

"},{"location":"lessons/screen-drawing/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/screen-drawing/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/screen-drawing/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/screen-drawing/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/screen-drawing/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/screen-drawing/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/screen-drawing/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/screen-drawing/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/screen-drawing/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/screen-drawing/16-drawing-alarm-icon/","title":"Drawing a Scalable Alarm Bell Icon","text":""},{"location":"lessons/screen-drawing/16-drawing-alarm-icon/#sample-prompt","title":"Sample Prompt","text":"

Prompt

Using the MicroPython poly function, please create a Python function that draws an alarm icon on a 22x24 grid. The function should have three parameters: x, y and scale where x and y are the upper left corner coordinates. Just draw the bell body and the clapper.

Test the icon at scales from 12 to 36.

"},{"location":"lessons/screen-drawing/16-drawing-alarm-icon/#sample-result","title":"Sample Result","text":"
from machine import Pin, I2C, SPI\nfrom utime import sleep, localtime\nfrom ds1307 import DS1307\nfrom array import array\n\nimport ssd1306\nimport config\n\nSCL=Pin(config.SPI_SCL_PIN)\nSDA=Pin(config.SPI_SDA_PIN)\nDC = Pin(config.SPI_DC_PIN)\nRES = Pin(config.SPI_RESET_PIN)\nCS = Pin(config.SPI_CS_PIN)\nSPI_BUS = config.SPI_BUS\nWIDTH = config.DISPLAY_WIDTH\nHEIGHT = config.DISPLAY_HEIGHT\n\nspi=SPI(SPI_BUS, sck=SCL, mosi=SDA, baudrate=1000000)\noled = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)\n\ni2c_sda = Pin(config.I2C_SDA_PIN)\ni2c_scl = Pin(config.I2C_SCL_PIN)\nI2C_BUS = config.I2C_BUS\nRTC_TYPE = config.RTC_TYPE\nRTC_I2C_ADDR = config.RTC_I2C_ADDR\n\n# I2C setup\ni2c = I2C(I2C_BUS, scl=i2c_scl, sda=i2c_sda, freq=3000000)\nrtc = DS1307(addr=RTC_I2C_ADDR, i2c=i2c)\n\ndef draw_alarm_icon(display, x, y, size=24):\n    \"\"\"\n    Draw an alarm bell icon with time display using polygons.\n\n    Args:\n        display: SSD1306 display instance\n        x, y: Top-left position for the icon\n        size: Base size of the icon (default 24 pixels)\n    \"\"\"\n    # Scale factors\n    scale = size / 24  # Base size is 24 pixels\n\n    # Bell body coordinates (scaled from base design 22 wide and 20 high)\n    bell_body = array('B', [\n        # start at the top right and go 0,4,10,17 and 20 down\n        int(14 * scale), int(0 * scale),    # Top right of dome top row right\n        int(17 * scale), int(4 * scale),    # Top right of dome 2nd row\n        int(19 * scale), int(10 * scale),   # Bottom right curve\n        int(18 * scale), int(16 * scale),   # Right side indent\n        # bottom row\n        int(22 * scale), int(20 * scale),   # Right bottom corner\n        int(0 * scale),  int(20 * scale),   # Bottom left corner\n        # left side\n        int(4 * scale),  int(16 * scale),   # Left up indent\n        int(3 * scale),  int(10 * scale),   # Left up\n        int(5 * scale),  int(4 * scale),    # Top left of dome 2nd row\n        int(8 * scale),  int(0 * scale),    # Top left of dome top row left\n    ])\n\n    # Clapper coordinates\n    clapper = array('B', [\n        int(10 * scale), int(20 * scale),   # Top left\n        int(12 * scale), int(20 * scale),   # Top right\n        int(14 * scale), int(24 * scale),   # Bottom right\n        int(8 * scale), int(24 * scale),   # Bottom left\n    ])\n\n    # Draw the components\n    display.poly(x, y, bell_body, 1, 1)  # Filled bell body\n    display.poly(x, y, clapper, 1, 1)    # Filled clapper\n\ndef demo_alarm_icons(display):\n    \"\"\"\n    Demonstrate the alarm icon at different sizes and positions\n    \"\"\"\n    # Clear the display\n    display.fill(0)\n\n    # Draw three different sized bells\n    draw_alarm_icon(display, 0,  0, size=10)    # Very small bell 10\n    draw_alarm_icon(display, 20, 0, size=14)    # Very small bell 14 \n    draw_alarm_icon(display, 40, 0, size=16)    # Very small bell 16\n    draw_alarm_icon(display, 60, 0, size=20)    # Small bell 20\n    draw_alarm_icon(display, 80, 0, size=24)    # Standard bell 24\n\n    draw_alarm_icon(display, 0,  24, size=28)   # Medium bell 28\n    draw_alarm_icon(display, 30, 24, size=32)   # Medium bell 28\n    draw_alarm_icon(display, 60, 24, size=36)   # Large bell 36\n\n\n    # Update the display\n    display.show()\n\n\ndemo_alarm_icons(oled)\n
"},{"location":"prompts/","title":"Prompts","text":""},{"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 \"Designing Building Clocks and Watches with AI and MicroPython\".

The course is a hands on-course that uses the Raspberry Pi Pico W, low-cost displays, and real-time clocks 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 graphic 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, jumper wires, a Raspberry Pi Pico W, LEDs, a SSD1307 128x64 OLED graphical display, buttons, a DS3231 real-time clock and a speaker.

Use the 2001 Bloom Taxonomy to guild the course description.

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

Course Title: Designing Building Clocks and Watches with AI and 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 moving from foundational knowledge, and terms 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":"

Prompt

You are an expert at generating concept lists for online training courses. Please generate a list of the 200 core concepts that need to be covered in this course and the order in which they should be taught.

Return numbered list of these core concepts using a single line per concept. For each concept, just return a short concept name and use acronyms or abbreviations when appropriate. They will be used as labels in a graph network. Make sure that the concepts are distinct not duplicated in the list.

Use title case for the concept names.

Start with the most basic foundational concepts and then proceed to the most complex concepts.

"},{"location":"prompts/02-concept-enumeration/#response","title":"Response","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_1","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.

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.

"},{"location":"prompts/03-concept-dependency/#result","title":"Result","text":""},{"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-graphics-asynchronous-concurrency","title":"Category 6: Graphics, 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

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.\"

Clock and Watches

"},{"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":"prompts/05-taxonomy-assignment/#response","title":"Response","text":"

Clock and Watches

"},{"location":"prompts/06-banner-image/","title":"Generate a Banner Image","text":"

Prompt

Please generate a banner image for my new website called \"Designing and Building Clocks and Watches with MicroPython and AI\". Make the banner features high school age students with diverse skin tone having fun working together and building clock and watch projects in a school. Use bright colors but no text. Use images of both digital and analog clocks and smartwatches. Place breadboards, wire, microcontrollers digital watch displays, smartwatch displays and speakers on the table.

"},{"location":"prompts/06-banner-image/#sample-result","title":"Sample Result","text":""},{"location":"prompts/07-faq/","title":"FAQ Prompt","text":"

Prompt

Take a look at the course description and the concepts lists file in this project. Now create a FAQ of the 100 most likely questions that students will ask.

Return the FAQ in Markdown format with the question in level 4 headers and the answers in the body text. Group the FAQs in level 2 headers.

"},{"location":"prompts/07-faq/#result","title":"Result","text":""},{"location":"prompts/07-faq/#building-clocks-and-watches-with-micropython-frequently-asked-questions","title":"Building Clocks and Watches with MicroPython - Frequently Asked Questions","text":""},{"location":"prompts/07-faq/#general-course-information","title":"General Course Information","text":""},{"location":"prompts/07-faq/#what-prior-knowledge-do-i-need-for-this-course","title":"What prior knowledge do I need for this course?","text":"

No specific electronics or programming experience is required. However, basic computer literacy and comfort with mathematics at a high school level will be helpful. The course starts with fundamentals and builds up gradually.

"},{"location":"prompts/07-faq/#how-long-is-each-class-session","title":"How long is each class session?","text":"

While the course runs for 14 weeks, specific class session lengths are determined by your school's schedule. Contact your instructor for the exact timing of your sessions.

"},{"location":"prompts/07-faq/#do-i-need-to-purchase-my-own-equipment","title":"Do I need to purchase my own equipment?","text":"

Check with your instructor about equipment policies. Generally, core components like the Raspberry Pi Pico W and basic electronics will be provided, but you may want to purchase additional components for your final project.

"},{"location":"prompts/07-faq/#what-software-do-i-need-to-install","title":"What software do I need to install?","text":"

You'll need the Thonny IDE (a Python editor designed for MicroPython), which is free and works on Windows, Mac, and Linux. Your instructor will guide you through the installation process during the first week.

"},{"location":"prompts/07-faq/#technical-questions","title":"Technical Questions","text":""},{"location":"prompts/07-faq/#what-is-micropython-and-how-is-it-different-from-regular-python","title":"What is MicroPython and how is it different from regular Python?","text":"

MicroPython is a streamlined version of Python designed to run on microcontrollers. While it maintains Python's core features and syntax, it's optimized for hardware control and has special libraries for working with electronic components.

"},{"location":"prompts/07-faq/#what-is-a-raspberry-pi-pico-w","title":"What is a Raspberry Pi Pico W?","text":"

The Raspberry Pi Pico W is a small, affordable microcontroller board with built-in Wi-Fi. It's perfect for learning because it's user-friendly, well-documented, and has plenty of input/output pins for connecting different components.

"},{"location":"prompts/07-faq/#will-we-be-doing-any-soldering","title":"Will we be doing any soldering?","text":"

Most projects will use breadboards for non-permanent connections. Some optional advanced projects might involve soldering, but this will be optional and supervised.

"},{"location":"prompts/07-faq/#how-accurate-will-our-clocks-be","title":"How accurate will our clocks be?","text":"

The accuracy depends on the components used. With the basic oscillator, clocks might drift a few seconds per day. When we add a Real-Time Clock (RTC) module, accuracy improves significantly to within a few seconds per month.

"},{"location":"prompts/07-faq/#project-related-questions","title":"Project-Related Questions","text":""},{"location":"prompts/07-faq/#what-kind-of-displays-will-we-use","title":"What kind of displays will we use?","text":"

We'll work with several types: - Simple LEDs for basic digital displays - 128\u00d764 OLED displays for more sophisticated interfaces - Other display options for final projects

"},{"location":"prompts/07-faq/#can-i-create-my-own-custom-clock-design-for-the-final-project","title":"Can I create my own custom clock design for the final project?","text":"

Yes! The final project is designed to be creative and personal. You can combine different components and features we've learned about to create something unique.

"},{"location":"prompts/07-faq/#how-will-we-handle-time-zones-and-daylight-saving-time","title":"How will we handle time zones and daylight saving time?","text":"

We'll learn to sync time using web services (when Wi-Fi is available) and program time zone adjustments. Managing daylight saving time will be covered as part of the advanced features.

"},{"location":"prompts/07-faq/#learning-and-assessment","title":"Learning and Assessment","text":""},{"location":"prompts/07-faq/#how-will-i-be-graded","title":"How will I be graded?","text":"

Assessment typically includes: - Participation in class activities and discussions - Completion of hands-on labs and exercises - Quality and functionality of projects - Final project presentation and documentation

"},{"location":"prompts/07-faq/#what-happens-if-my-clock-doesnt-work","title":"What happens if my clock doesn't work?","text":"

Troubleshooting is a crucial part of the learning process. You'll have support from instructors and peers, and we'll learn systematic debugging approaches. Making mistakes and fixing them is how we learn!

"},{"location":"prompts/07-faq/#can-i-work-with-ai-tools-during-the-course","title":"Can I work with AI tools during the course?","text":"

Yes, part of the course includes learning how to effectively use AI tools for design and problem-solving. We'll cover best practices for AI collaboration while ensuring you understand the fundamental concepts.

"},{"location":"prompts/07-faq/#hardware-and-components","title":"Hardware and Components","text":""},{"location":"prompts/07-faq/#what-happens-if-i-accidentally-damage-a-component","title":"What happens if I accidentally damage a component?","text":"

Don't worry! Learning electronics involves some trial and error. Basic components are relatively inexpensive and replaceable. Just inform your instructor if something breaks.

"},{"location":"prompts/07-faq/#how-do-breadboards-work","title":"How do breadboards work?","text":"

A breadboard is a reusable platform for creating temporary electrical connections. We'll spend time early in the course understanding how they work and practicing proper connection techniques.

"},{"location":"prompts/07-faq/#whats-the-difference-between-digital-and-analog-inputsoutputs","title":"What's the difference between digital and analog inputs/outputs?","text":"

Digital signals are either on (1) or off (0), while analog signals can have many values in between. We'll use both types: digital for buttons and simple LEDs, analog for sensors and variable controls.

"},{"location":"prompts/07-faq/#programming-concepts","title":"Programming Concepts","text":""},{"location":"prompts/07-faq/#do-i-need-to-memorize-all-the-micropython-commands","title":"Do I need to memorize all the MicroPython commands?","text":"

No, you don't need to memorize everything. We'll provide reference materials, and part of learning is knowing how to find and use documentation effectively.

"},{"location":"prompts/07-faq/#what-if-i-miss-a-class-and-fall-behind","title":"What if I miss a class and fall behind?","text":"

Each concept builds on previous learning, but we'll provide catch-up resources and extra help sessions. Talk to your instructor as soon as possible if you fall behind.

"},{"location":"prompts/07-faq/#how-will-we-debug-our-code","title":"How will we debug our code?","text":"

We'll learn several debugging techniques: - Using print statements for troubleshooting - Reading error messages - Using the Thonny debugger - Systematic problem-solving approaches

"},{"location":"prompts/07-faq/#safety-and-best-practices","title":"Safety and Best Practices","text":""},{"location":"prompts/07-faq/#is-there-any-risk-of-electric-shock","title":"Is there any risk of electric shock?","text":"

The Pico W and components we use operate at very low voltages (3.3V or 5V) which are safe to handle. However, we'll still learn and follow proper safety procedures.

"},{"location":"prompts/07-faq/#how-do-i-protect-the-electronic-components","title":"How do I protect the electronic components?","text":"

We'll cover basic handling procedures including: - Static electricity precautions - Proper connection techniques - Component storage and handling - Avoiding common mistakes that could damage components

"},{"location":"prompts/07-faq/#advanced-topics-and-extensions","title":"Advanced Topics and Extensions","text":""},{"location":"prompts/07-faq/#can-we-add-features-like-alarms-or-stopwatch-functions","title":"Can we add features like alarms or stopwatch functions?","text":"

Yes! As you progress through the course, you'll learn to add various features including: - Multiple alarms - Stopwatch and timer functions - Custom sounds and alerts - Special display effects

"},{"location":"prompts/07-faq/#will-we-learn-about-power-management","title":"Will we learn about power management?","text":"

Yes, we'll cover power management including: - Battery operation - Sleep modes - Power-efficient programming - Battery life optimization

"},{"location":"prompts/07-faq/#future-applications","title":"Future Applications","text":""},{"location":"prompts/07-faq/#can-i-use-these-skills-for-other-projects","title":"Can I use these skills for other projects?","text":"

Absolutely! The skills you'll learn apply to many other electronics and programming projects: - Other types of electronic devices - Home automation projects - Sensor-based systems - IoT (Internet of Things) devices

"},{"location":"prompts/07-faq/#how-does-this-course-prepare-me-for-further-study","title":"How does this course prepare me for further study?","text":"

This course builds valuable skills for: - Computer science courses - Engineering programs - Electronics projects - General problem-solving - Understanding modern technology

"},{"location":"prompts/07-faq/#tools-and-resources","title":"Tools and Resources","text":""},{"location":"prompts/07-faq/#what-online-resources-can-we-use","title":"What online resources can we use?","text":"

We'll use various resources including: - MicroPython documentation - Raspberry Pi Pico documentation - Component datasheets - Online simulators - Programming reference guides

"},{"location":"prompts/07-faq/#will-we-use-version-control-like-git","title":"Will we use version control like Git?","text":"

While not required, we'll introduce basic version control concepts and how they can help manage your code as projects become more complex.

"},{"location":"prompts/07-faq/#collaboration-and-community","title":"Collaboration and Community","text":""},{"location":"prompts/07-faq/#can-we-work-in-teams","title":"Can we work in teams?","text":"

Yes! Many activities will involve pair programming or team collaboration. The final project can be individual or team-based, depending on complexity and scope.

"},{"location":"prompts/07-faq/#how-can-we-share-our-projects-with-others","title":"How can we share our projects with others?","text":"

We'll discuss ways to: - Document your work - Create project presentations - Share code responsibly - Collaborate with other students - Showcase your projects

"},{"location":"prompts/07-faq/#getting-help","title":"Getting Help","text":""},{"location":"prompts/07-faq/#what-if-i-get-stuck-on-a-problem","title":"What if I get stuck on a problem?","text":"

Multiple support options are available: - In-class help from instructors - Peer support - Office hours - Online resources - Lab assistance sessions

"},{"location":"prompts/07-faq/#can-i-get-extra-help-outside-of-class","title":"Can I get extra help outside of class?","text":"

Yes, instructors will have office hours and may offer additional lab time for students who want extra practice or help.

"},{"location":"prompts/07-faq/#course-materials","title":"Course Materials","text":""},{"location":"prompts/07-faq/#what-should-i-bring-to-class","title":"What should I bring to class?","text":"

Typically you'll need: - Notebook for taking notes - Computer (if not provided) - Any assigned reading materials - Your project components once distributed

"},{"location":"prompts/07-faq/#how-should-i-store-and-transport-my-projects","title":"How should I store and transport my projects?","text":"

We'll provide guidance on: - Component storage - Project organization - Safe transport methods - Protecting delicate components

"},{"location":"prompts/07-faq/#final-projects","title":"Final Projects","text":""},{"location":"prompts/07-faq/#what-makes-a-good-final-project","title":"What makes a good final project?","text":"

Strong final projects typically: - Solve a real problem or fulfill a genuine need - Demonstrate technical skills learned in the course - Show creativity and originality - Are well-documented - Function reliably

"},{"location":"prompts/07-faq/#can-i-continue-working-on-my-project-after-the-course-ends","title":"Can I continue working on my project after the course ends?","text":"

Yes! You'll have the knowledge to continue developing and improving your projects. We'll discuss ways to extend your learning and continue exploring electronics and programming.

"},{"location":"prompts/08-glossary/","title":"Generating a Glossary of Terms","text":""},{"location":"prompts/08-glossary/#prompt","title":"Prompt","text":"

Prompt

Prompt Name: Glossary for Clocks and Watches with MicroPython Book

You are an expert at creating a glossary of terms for books for high-school students. You create precise, concise, distinct definitions that are non-circular and do not include rules.

Your terms are written for the 9th grade reading level.

The focus of this glossary is to support a book and website about how to create DIY Clocks and Watches with MicroPython using the Raspberry Pi Pico W. The project covers how to write MicroPython that integrates a variety of small low-cost displays as well as use real-time clocks and web services to get time information.

For each term, return a term label in a level 4 markdown header. Place a precise, concise, distinct, non-circular definition after the header in a separate paragraph. Do not include the term in the definitions.

Go to the project area and use the file MicroPython_Clock_Concepts.csv for a complete list of concepts that need definitions.

If appropriate create an example of how that term might be\nused in the website.  Use **Example:** with no newline after it.\n\nReturn the entire glossary of terms in a single Markdown file\nfor all concepts in alphabetical order.\n\n## Project Instructions\n\n!!! prompt\n    When I ask you to create a definition for a ter, please create a glossary term\n    entry that complies with the ISO/IEC 11179 metadata registry\n    guidelines for a definition.  These definitions are precise, concise, distinct\n    definitions that are non-circular and do not include rules.  Do not include the\n    term name in the definition itself.  Your terms are written for the 9th grade reading level.\n\n    Return the term in a level 4 markdown header.\n    Return the definition in the body text.\n    If appropriate, give an example using the **Example:** format without a newline.\n
"},{"location":"prompts/09-oled-test/","title":"OLED Test Prompt","text":"

We can use generative AI tools to build test programs. But in order to get working programs, we need to tell the generative AI about what our hardware is and what our configuration is. We can leverage the config.py file to tell the generative AI tools about our local configuration.

"},{"location":"prompts/09-oled-test/#sample-prompt","title":"Sample Prompt","text":"

Prompt

You are an expert at helping high-school kids learn computational thinking using MicroPython. I am working on writing a test program written in MicroPython. I have a 128x64 OLED using ssd1306 display driver chip connected to a Raspberry Pi Pico W running MicroPython version 1.24.1 using an SPI bus. I have the ssd1306.py driver loaded into the /lib directory.

Please write a MicroPython program that prints \"Hello World!\" on the OLED display. Use the config.py program in the project to setup the display.

If your AI does not have a project feature, you can include the config.py in the prompt:

# MicroPython hardware configuration file for Raspberry Pi Pico W\n# Using an SPI OLED Display, a DS3231 RTC with EEPROM, and three buttons\n\nSPI_BUS = 0\nSPI_SCL_PIN = 2 # Clock\nSPI_SDA_PIN = 3 # labeled SDI(MOSI) on the back of the display\nSPI_RESET_PIN = 4 # Reset\nSPI_DC_PIN = 5 # Data/command\nSPI_CS_PIN = 6 # Chip Select\n\n# I2C Bus for the DS3231 RTC\nI2C_BUS = 0\nI2C_DT_PIN = 0 # Data\nI2C_SCK_PIN = 1 # Clock\nRTC_ADDRESS = 0x68\nEEPROM_ADDRESS = 0x57\n\n# OLED Screen Dimensions\nDISPLAY_WIDTH=128\nDISPLAY_HEIGHT=64\n\n# Button Pins where the buttons are between the GPIO and GND\n# Make sure to configure the pull-up resistor in the code\nBUTTON_MODE_PIN = 14\nBUTTON_INCREMENT_PIN = 15\nBUTTON_DECREMENT_PIN = 16\n
"},{"location":"setup/","title":"A Guide to Setting up Your Clocks and Watches Labs","text":"

This section is intended for parents, mentors, teachers and curriculum developers who are responsible for creating a great experience for your students. Within this audience we have typically seen two extremes.

  1. Value-based purchasers with limited budgets who are good at long-term planning. They pre-order parts form China on eBay and Alibaba in bulk and avoid high-cost shipping fees.

  2. Parents in high-disposable income households with limited time and large budgets. They just want to click on a few links and have the parts shipped overnight.

This Setup Guide attempts to provide information for both of these groups. Note that we have used generative AI to help you source the lowest costs parts. We encourage you to ask your generative AI chatbot about sourcing strategies that balance low-cost and fast delivery.

"},{"location":"setup/#minimal-setup","title":"Minimal Setup","text":"

The minimal setup consists of just four parts:

  1. A 1/2 size solderless breadboard - about $2
  2. 20cm jumper wires (male-to-female) - also known as Dupont connectors - about $1
  3. A Raspberry Pi Pico - about $4 or $5 with headers presoldered - you can get the \"W\" if you want to go wireless and use WiFi to sync the time
  4. A OLED display - which range from $5 to $20 depending on the size

If you are patient and clever you can purchase these parts in bulk and keep the kit price under $10 - although the OLED display will be hard to read from more than a few feet away.

Image Caption: An example of a 2.42\" OLED display connected to a 400-tie breadboard holding a Raspberry Pi W with two buttons for changing the display settings.

"},{"location":"setup/#sample-amazon-links","title":"Sample Amazon Links","text":"

Here are the Amazon Links for these parts:

  1. Half Size Solderless Breadboard Search on Amazon
  2. Sample 4-Pack for $6
  3. 20cm male-to-female Dupont Connectors $4 for 40 connectors
  4. Raspberry Pi Pico $8
  5. Amazon Keyword Search for 2.42\" OLED Display 128*64 SPI SSD1309
  6. Amazon Prime 2.42\" OLED Display for $14 in 4 Colors

Note that MicroCenter sells the Pico for $3.99. So you are paying about double on Amazon for some of these parts.

"},{"location":"setup/#sample-e-bay-links","title":"Sample E-Bay Links","text":"
  1. Half Size Solderless Breadboard Search on EBay
  2. 10X 400 Point Solderless Breadboard for $14
  3. 20cm male-to-female dupont connectors
  4. 10/20/30CM MM, MF, FF Dupont Wire Jumper Cable 40PIN Dupont Line Connector for $4
  5. 2.42\" OLED Display 128*64 SPI SSD1309
"},{"location":"setup/#other-components","title":"Other Components","text":""},{"location":"setup/#display-cable-harness","title":"Display Cable Harness","text":"

Display Cable Harness

"},{"location":"setup/#real-time-clocks","title":"Real Time Clocks","text":"

Technically you can get a clock or watch running without a real-time clock (RTC). The problem is that the clock will not be very accurate unless you continually sync the time using WiFi or with your host computer.

However, learning how to use the a RTC is a key learning concept and learning how to use the I2C serial interface is also a good concept to know. So it is optional but strongly encouraged.

"},{"location":"setup/#the-ds1307","title":"The DS1307","text":"

Although this board is old, it is a simple and low-cost part that is easy to use. Most of the development boards come with their own crystal and an I2C interface.

"},{"location":"setup/#the-ds3231","title":"The DS3231","text":"

The DS3231 is one of the most commonly used real-time clock (RTC) modules paired with microcontrollers like the Raspberry Pi Pico. It's popular because it:

  1. Has high accuracy (temperature-compensated crystal oscillator)
  2. Maintains accuracy over a wide temperature range
  3. Has built-in temperature compensation
  4. Uses the I2C interface, which is easy to implement
  5. Includes a battery backup option
  6. Is relatively inexpensive
  7. Has extensive library support across different platforms

The second most common is probably the DS1307, which is an older and simpler version. While less accurate than the DS3231, it's even less expensive and still perfectly suitable for many basic timekeeping applications.

For microcontrollers in particular, the DS3231 tends to be favored because its accuracy doesn't depend on the microcontroller's clock, and it maintains accurate time even when the main microcontroller is reset or loses power.

"},{"location":"setup/01-desktop/","title":"Desktop Setup","text":"

In this lesson, we will review the steps to get your PC or Mac setup to use the free open source Thonny Python Integrated Development Environment (IDE). We will assume you are using either the Raspberry Pi Pico or the Raspberry Pi Pico \"W\" for thee lessons.

"},{"location":"setup/01-desktop/#about-thonny","title":"About Thonny","text":"

Thonny is an ideal tool for writing MicroPython code for students just getting started. It is free, simple to use, and it runs on PCs running Windows, Macs and Linux systems such as the Raspberry Pi.

"},{"location":"setup/01-desktop/#step-1-install-thonny","title":"Step 1: Install Thonny","text":"

The installation of Thonny will vary depending on the type of computer you are using. For both Windows and Mac, you must first download an installer program. The links to these downloads are on the main Thonny page:

For all the versions of Linux, you can open the command shell and type in the correct installation command. For people using the Raspberry Pi operating system, Thonny may already be included in your software.

"},{"location":"setup/01-desktop/#thonny-versions","title":"Thonny Versions","text":"

These screen images were done with Thonny Version 4.0.4. We also tested the steps on Thonny version 4.1.2 and the seem to work fine.

"},{"location":"setup/01-desktop/#step-2-configure-the-micropython-firmware","title":"Step 2: Configure the MicroPython Firmware","text":"

After you first plug in a Raspberry Pi Pico, you will need to install the correct runtime library for the Pico. This just takes a minute and you will only have to do it once for each microcontroller.

Since version 4.0.0 Thonny can detect the microcontroller you are using and download the firmware.

"},{"location":"setup/01-desktop/#manually-downloading-the-firmware","title":"Manually Downloading the Firmware","text":"

There are occasions when the automatic firmware download in Thonny may not work. In this case, you will need to manually download the firmware from the MicroPython website.

Here are the links to the Pico Firmware (called RP2-pico):

Raspberry Pi Pico Firmware

"},{"location":"setup/01-desktop/#step-3-configure-interpreter","title":"Step 3: Configure Interpreter","text":"

Thonny is designed to automatically detect the correct device and configure the right interpreter. You can verify this by using the Thonny Tools -> Options menu and move to the Interperter tab.

Note that the MicroPython (Raspberry Pi Pico) is selected.

"},{"location":"setup/01-desktop/#thonny-firmware-list","title":"Thonny Firmware List","text":"

You can also get a list of all the different versions of firmware by going directly to the MicroPython site. Here is what that list will look like:

After you select a runtime version, Thonny will give you the details about this version.

After Thonny installs the new firmware on the Pico it instructs the Pico to \"restart\" using the new firmware. This makes the USB drive appear to be removed without a disconnect and results in an error. This is normal so don't worry.

"},{"location":"setup/01-desktop/#debugging-tips","title":"Debugging Tips","text":"

When debugging your Thonny installation, it is important to understand the steps that happen when you plug your Raspberry Pi Pico into the computer.

Step 1: Plugging the USB Device The Pico appears as a USB device on your computer when you plug it in. The operating system should send a request to the USB device asking it for its ID.

Step 2: Probing for the USB ID The Pico will respond with a device ID number. This number reflects two items: the company that made the device (Vendor ID) and the device product type (Product ID). If you are using a power-only USB cable this number will never get to the operating system. This means that the data wires are not present in your USB cable or one of the wires is broken or not connecting.

Step 3: Looking Up the USB Driver The operating system will take this USB ID and then look-up the right software and then run that software. In UNIX and Mac systems this will amount to making the USB device appear in the /dev/cu* area. On Windows it will be a COM port.

ls -l /dev/cu.usb*\n\ncrw-rw-rw-  1 root  wheel  0x9000001 Dec 25 08:41 /dev/cu.usbmodem101\n

Note that the exact port name might change. The example above is cu.usbmodem101.

If this device does not immediately appear, then you should double-check your cable and make sure the operating system registers the USB device.

If you know your cable works fine by testing it on other devices then you may have to reboot your computer.

"},{"location":"setup/01-desktop/#setting-usb-permissions-on-macos","title":"Setting USB Permissions on MacOS","text":"

In many classrooms with old Macs, the new security settings for USB connections can get in the way of a good experience for our students. Since these Macs don't leave our classrooms, we have decided to permanently allow USB access for your Raspberry Pi Pico on MacOS Sonoma. Here are the steps we use.

  1. On the Mac, go to System Settings > Privacy & Security
  2. Scroll to Security
  3. Find \"Allow accessories to connect\" and select \"always\"
"},{"location":"setup/01-desktop/#debugging-your-usb-connection","title":"Debugging your USB Connection","text":"

Our students have reported many frustrating experiences getting their Raspberry Pi Pico to connect to their MacOS system. Most of these problems can be quickly solved by checking the security settings on your MacOS.

After you plug in the USB it should be listed in the /dev/cu* area:

ls /dev/cu*\n

A typical response includes this device:

/dev/cu.usbmodem1101\n

If the usbmodem11001 or similar is not listed then THonny will not connect. This is the time to check your cables and connectors. Older USB cables frequently have broken wires.

"},{"location":"setup/01-desktop/#getting-usb-diagnostic-information-on-the-macos","title":"Getting USB Diagnostic Information on the MacOS","text":"

You can also get information about how much current will be provided to the USB using the following shell commnad:

You can get the full information on your device using this command:

system_profiler SPUSBDataType | grep -B 6 -A 5 \"MicroPython\"\n

This command prints the 6 lines before and five lines after the string \"MicroPython\" appears in the output of the system_profiler command.

  Product ID: 0x0005\n  Vendor ID: 0x2e8a\n  Version: 1.00\n  Serial Number: e66178c1276d562d\n  Speed: Up to 12 Mb/s\n  Manufacturer: MicroPython\n  Location ID: 0x01100000 / 1\n  Current Available (mA): 500\n  Current Required (mA): 250\n  Extra Operating Current (mA): 0\n
"},{"location":"setup/01-desktop/#using-the-macos-io-registry-command","title":"Using the MacOS I/O Registry Command","text":"

The MacOS shell command ioreg can also be useful for monitoring USB device status. Here is a command that lists the information of all USB devices. The I/O Registry is a hierarchical database of all the devices and drivers on your Mac. The ioreg shell command is powerful utility to examine system hardware and device information.

ioreg -p IOUSB -w0 -l\n

Here are the parameters: - ioreg:

-   The command-line tool used to display the I/O Registry, a hierarchical database of all the devices and drivers on your Mac. It's a powerful utility to examine system hardware and device information.\n

+-o Board in FS mode@01100000

{\n  \"sessionID\" = 37899561861259\n  \"USBSpeed\" = 1\n  \"idProduct\" = 5\n  \"iManufacturer\" = 1\n  \"bDeviceClass\" = 239\n  \"IOPowerManagement\" = {\"PowerOverrideOn\"=Yes,\"DevicePowerState\"=2,\"CurrentPowerState\"=2,\"CapabilityFlags\"=32768,\"MaxPowerState\"=2,\"DriverPowerState\"=0}\n  \"bcdDevice\" = 256\n  \"bMaxPacketSize0\" = 64\n  \"iProduct\" = 2\n  \"iSerialNumber\" = 3\n  \"bNumConfigurations\" = 1\n  \"UsbDeviceSignature\" = <8a2e0500000165363631373863313237366435363264ef02010202000a0000>\n  \"USB Product Name\" = \"Board in FS mode\"\n  \"locationID\" = 17825792\n  \"bDeviceSubClass\" = 2\n  \"bcdUSB\" = 512\n  \"kUSBSerialNumberString\" = \"e66178c1276d562d\"\n  \"USB Address\" = 1\n  \"IOCFPlugInTypes\" = {\"9dc7b780-9ec0-11d4-a54f-000a27052861\"=\"IOUSBHostFamily.kext/Contents/PlugIns/IOUSBLib.bundle\"}\n  \"kUSBCurrentConfiguration\" = 1\n  \"bDeviceProtocol\" = 1\n  \"USBPortType\" = 0\n  \"IOServiceDEXTEntitlements\" = ((\"com.apple.developer.driverkit.transport.usb\"))\n  \"USB Vendor Name\" = \"MicroPython\"\n  \"Device Speed\" = 1\n  \"idVendor\" = 11914\n  \"kUSBProductString\" = \"Board in FS mode\"\n  \"USB Serial Number\" = \"e66178c1276d562d\"\n  \"IOGeneralInterest\" = \"IOCommand is not serializable\"\n  \"kUSBAddress\" = 1\n  \"kUSBVendorString\" = \"MicroPython\"\n}\n
"},{"location":"setup/01-desktop/#automatic-power-draw-shutoff","title":"Automatic Power Draw Shutoff","text":"

If your USB port is drawing too much power, then many computers will disable the port. This can happen if you are trying to display too many LEDs or a motor. You will see a message such as \"USB Port Disabled\".

Your computer manufacturer will tell you what the maximum current each USB port allows. This is typically about 1 amp, but it varies based on your computer and what other devices are connected to your computer.

The best way to debug this is to purchase a low-cost USB current monitor and monitor how much current your project is using. If you are using an LED-strip then make sure you test the current with all the pixels fully on (255,255,255).

USB Current Monitor Search on eBay

In general, each NeoPixel at full brightness will draw up to 20 milliamps and the Pico will also draw about 20 milliamps.

For projects that require more than about 500 milliamps, it is strongly recommended that you use an externally powered USB hub.

"},{"location":"setup/01-desktop/#debugging-the-usb-port","title":"Debugging the USB Port","text":""},{"location":"setup/01-desktop/#mac-system-profiler","title":"Mac System Profiler","text":"

On MacOS we can use the system_profiler command:

system_profiler SPUSBDataType\n\nUSB:\n\n    USB 3.1 Bus:\n\n      Host Controller Driver: AppleT8112USBXHCI\n\n    USB 3.1 Bus:\n\n      Host Controller Driver: AppleT8112USBXHCI\n\n        Board in FS mode:\n\n          Product ID: 0x0005\n          Vendor ID: 0x2e8a\n          Version: 1.00\n          Serial Number: e66141040396962a\n          Speed: Up to 12 Mb/s\n          Manufacturer: MicroPython\n          Location ID: 0x00100000 / 1\n          Current Available (mA): 500\n          Current Required (mA): 250\n          Extra Operating Current (mA): 0\n

Note that you can see both the Product ID, Vendor ID Manufacturer (MicroPython) and the mode (File System mode) that the device was connected. The current available and current required are also listed, although these numbers might be somewhat conservative. They are used for the estimation of current only.

"},{"location":"setup/01-desktop/#checking-out-the-clocks-and-watches-code-from-github","title":"Checking out the Clocks and Watches Code from GitHub","text":"

You don't need to copy and paste the code samples one-by-one into your local file system. GitHub allows you to download all the code on the Moving Rainbow site by running the following command:

# create a projects area to checkout your code\nmkdir ~/projects\ncd projects\n# make a copy of the repo on your local hard drive\ngit clone https://github.com/dmccreary/micropython-clocks-and-watches\ncd micropython-clocks-and-watches\n# open the files in your file manager\nopen .\n

You can now go directly to the source for the Raspberry Pi P OLED lab:

cd src/pico-oled\n

Once you have done this step you can make the top Files region of Thonny point to the source area and then click the upper right corner that has the \"Upload to /\" menu. Make sure the bottom area is set to the root, not the /lib area.

The following video shows these steps:

"},{"location":"setup/01-desktop/#understanding-thonny-and-file-systems","title":"Understanding Thonny and File Systems","text":"

To understand how Thonny works, we want to have a clear visual picture of the two parallel file systems that Thonny uses.

  1. Thonny is launched from your desktop or laptop computer. The Files window at the top will always be open to that file system.
  2. Thonny also displays the file system on the microcontroller on the bottom window. It displays this window after you successfully connect through the correct port.

You can run a program in Thonny from either file system. But there is one strict rule. If you don't save your code on your personal GitHub site and verify that the code is there you may not have saved your code.

"},{"location":"setup/02-purchasing-parts/","title":"Strategy for Parts Purchasing Guide","text":"

We have seen many well intended parent come into our workshops telling stories about a $400 robot kit that was used for an hour and then sat in the back of a closet. Before you go out and purchase an expensive STEM kit, we suggest you start small and see if your student will really use the kit.

"},{"location":"setup/02-purchasing-parts/#breadboards","title":"Breadboards","text":"

We strongly suggest purchasing breadboards in bulk at least 10 at a time. We use eBay for purchasing all our breadboards.

Many of our projects use the 1/2 size 400-tie breadboards. This is usually sufficient for simple projects.

"},{"location":"setup/02-purchasing-parts/#microcontrollers","title":"MicroControllers","text":"

Because all or examples run MicroPython, your microcontroller will also need to run MicroPython. Unfortunately, older microcontrollers like the Arduino Uno only come with 2K of RAM. Since we need at least 16K of RAM to run MicroPython, the older Arduino systems will not work.

Therefore we strongly suggest you go with a newer microcontroller like the Raspberry Pi Pico which typically sells for under $4.

"},{"location":"setup/02-purchasing-parts/#purchasing-the-raspberry-pi-pico","title":"Purchasing the Raspberry Pi Pico","text":"

We love MicroCenter because they sell the Raspberry Pi Pico for only $3.99. If there is a MicroCenter near your home, we strongly suggest purchasing the parts there.

"},{"location":"setup/02-purchasing-parts/#raspberry-pi-pico-w","title":"Raspberry Pi Pico W","text":"

If you are going wireless, you will need to pay an extra dollar to get the Raspberry Pi Pico for $5.99

If the Raspberry Pi Picos are out of stock, a backup plan might be an ESP-32 microcontroller. There are two challenges you might face with the ESP-32:

  1. The development boards at MicroCenter, SparkFun and Adafruit are 4x more expensive
  2. There is a huge variety of these boards from many different manufactures. So the instructions you get on each website may not match the device you purchase.
"},{"location":"setup/02-purchasing-parts/#displays","title":"Displays","text":""},{"location":"setup/02-purchasing-parts/#oled-displays","title":"OLED Displays","text":"

Although the small 1\" OLED displays work, they are hard to read from a distance. We like the 2.42\" inch OLED displays since they are bright and have a wide viewing angle.

"},{"location":"setup/02-purchasing-parts/#smartwatch-displays","title":"SmartWatch Displays","text":"

Waveshare RP2040-LCD-1.28

Ebay Listing for $21

"},{"location":"setup/02-purchasing-parts/#led-strips","title":"LED Strips","text":""},{"location":"setup/02-purchasing-parts/#smartwatch-displays_1","title":"Smartwatch Displays","text":""},{"location":"setup/02-purchasing-parts/#real-time-clocks","title":"Real-Time Clocks","text":"

Learning how to use a real-time clock (RTC) is a core part of building digital clocks. So almost all our kits include an RTC. Here is a description of the two main options for RTCs.

"},{"location":"setup/02-purchasing-parts/#the-ds1307","title":"The DS1307","text":"

Although the DS1307 is has been around for a long time, it is still a simple low-cost part that is easy to use. The DS1307 is still perfectly suitable for many basic timekeeping applications and for learning how to use a Most of the development boards come with their own crystal and an I2C interface. Most of our clock kits have now been upgraded to the newer more accurate DS3231 which we can also purchase for under $1.

"},{"location":"setup/02-purchasing-parts/#the-ds3231","title":"The DS3231","text":"

The DS3231 is one of the most commonly used real-time clock (RTC) modules paired with microcontrollers like the Raspberry Pi Pico. It's popular because it:

  1. Has high accuracy (temperature-compensated crystal oscillator) +/- 2 seconds per month
  2. Maintains accuracy over a wide temperature range suitable for indoor and outdoor use
  3. Uses the I2C interface, which is easy to implement
  4. Includes a 3V lithium coin-cell battery backup option which allows it to remember the time and alarm settings even when the power is off
  5. Is relatively inexpensive (under $1 each)
  6. Has extensive library support across different platforms
  7. You can also use it to display the temperature
  8. Includes the ability to store 4K in EEPROM for information such as when alarms and timers should go off

For microcontrollers in particular, the DS3231 tends to be favored because its accuracy doesn't depend on the microcontroller's clock, and it maintains accurate time even when the main microcontroller is reset or loses power.

Here is an example of a DS3231 listing on eBay for under $1:

AliExpress currently has the DS3231 boards listed for $0.73 each.

"},{"location":"setup/03-display-cable-harness/","title":"Display Cable Harness","text":"

Unlike simple sensors that only have a few wires, displays have up to seven wires that need to be connected. This can be tricky when we use breadboards where we can accidentally pull one wire out.

To keep our displays running reliably, we can use a 20 cm ribbon cable and some hot glue to make a connector that is easy to hook up and be much more reliable.

We start by purchasing some 20cm long Male-Female Dupont ribbon connectors from eBay. The price should be about $8 for 120 connectors. Make sure to get the Male-Female version.

We then separate out seven of these wires trying to use the black and red colors in the GND and VCC edge of the group of wires.

You can see a close-up of each of the colors and their connections in the picture below.

At the other end of the cable, we need to make a small change in the order of the cable. Here are the changes:

  1. We separate the red wire from the rest of the group and connect the red to the 3.3V regulated output of the Raspberry Pi Pico. All our displays run on 3.3v now.
  2. We move the back GND wire to be in between the blue and purple CS and DC wires. This allows the row of all the wires to be connected in a single block of wires.

We can then plug this group of wires directly into the breadboard from breadboard rows 3 to 9. This is shown below. Make sure the black GND is on the Pico GND on row 8 of the breadboard.

We designed these connections with the following rules:

  1. The Clock (SCL) and Data (SDA) MUST be connected to rows 4 and 5 respectively because this is where SPI0 CLK and SPI0 TX are located.
  2. The other three signals RES, DC and CS can be on pin so we will use the rows that make the cable connectors direct to rows 6, 7 and 9. Note that GND is on breadboard row 8 which I mark with a black marker.

We have found that once we create these cable assemblies with hot glue to keep the pins in the right order it makes it much easier to connect the displays.

Warning

Note that we still MUST make sure that the black wire in the wiring harness is connected to the GND. It is easy to get the cable reversed so make sure to double-check the cable orientation before you use power on the board.

For younger students, don't be shy about providing a few color-coded hints on the breadboard to guide their assembly process.

"},{"location":"setup/04-led-clocks/","title":"LED Clocks","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. One challenge is can you use simple LEDs to create your own clock? This is where creativity comes in.

Much of the creativity in this course relies on displaying time information in creative ways using both simple LEDs as well as using LED strips also known as NeoPixel strips.

There are tradeoffs for each of these approaches. There is no single answer and each student will have different curiosity.

"},{"location":"setup/04-led-clocks/#basic-custom-led-circuit-approach","title":"Basic Custom LED Circuit Approach","text":"This LED art-clock used individual RGB LEDs. Each RGB LED required four independent soldering connections.

Using individual LEDs requires more wiring and the need to understand how to use current limiting resistors. This approach promotes lower-level understanding of concepts such as current, voltage drop, resistors, current measuring and soldering. Students can place the LEDs in any arrangement on foam-core board, plexiglass or wood.

Remember that a digit requires seven segments with each segment requiring up to 3 LEDs. For a four digit clock that means you will need around 70 LEDs.

"},{"location":"setup/04-led-clocks/#neopixel-approach","title":"NeoPixel Approach","text":"This 12-element NeoPixel ring is perfect for a clock face. Different colors can be used to display the hours, minutes and seconds.

NeoPixels are connivent since you can wire an entire clock display including the seven-segments for four digits, the flashing colon and the AM/PM indicator with a single LED strip or a string of addressable LED lights. The focus will be on programming these strips to display the correct time.

You can get NeoPixels in strips (the most common) but also in strings that can be easily moved into the pattern of an 7-segment display.

The NeoPixel string allows you to easily place the pixels in the shape of a 7-segment digit.

For more examples of using NeoPixels with MicroPython, please refer to the Moving Rainbow website. This site has detailed tutorials appropriate for begging Python students.

"},{"location":"setup/04-led-clocks/#references","title":"References","text":""},{"location":"setup/05-ai-in-the-classroom/","title":"AI in the Classroom","text":""},{"location":"setup/05-ai-in-the-classroom/#sample-prompts","title":"Sample Prompts","text":"

This course provides many sample prompts you can use to give generative AI systems to explain code, generate sample code, generate tests and modify our existing code to add new features.

Teachers can also use these prompts to create lesson plans and learning artifacts such as MicroSims to complement these resources.

"},{"location":"setup/05-ai-in-the-classroom/#leveraging-projects","title":"Leveraging Projects","text":"

We also encourage you to load the kit source code into a project area that your generative AI tools can access. Both Anthropic's Claude.ai and OpenAI ChatGPT. This allows you to do things like reference a sample program and say just like example 07-clock-face.py but with red digits.

"},{"location":"setup/05-button-print-test/","title":"Button Print Test","text":"

In this lab we will be testing if the three buttons have been wired up correctly. When we press any of the three buttons we will see the event printed on the Thonny console.

By printing to the console this lab is not dependant on the display being connected. In the next lab we will also modify the lab to display the button state on the display.

"},{"location":"setup/05-button-print-test/#using-interrupt-request-functions","title":"Using Interrupt Request Functions","text":"

To print what button is being pressed, we will write three small python functions. We then will then have these functions run when the corresponding button is pressed by running the irq function.

"},{"location":"setup/05-button-print-test/#sample-code","title":"Sample Code","text":"
from utime import sleep\nfrom machine import Pin\n\n# Sample Raspberry Pi Pico MicroPython three button press example\n\n# Button Pin GPIO Configuration\nBUTTON_1_PIN = 13\nBUTTON_2_PIN = 14\nBUTTON_3_PIN = 15\n\n# Create 3 button objects as inputs with pullup resistors\nbutton_1_pin = Pin(BUTTON_1_PIN, Pin.IN, Pin.PULL_UP)\nbutton_2_pin = Pin(BUTTON_2_PIN, Pin.IN, Pin.PULL_UP)\nbutton_3_pin = Pin(BUTTON_3_PIN, Pin.IN, Pin.PULL_UP)\n\n# These functions gets called every time a button is pressed.\ndef button_1_pressed(pin):\n    print('Button 1 pressed')\ndef button_2_pressed(pin):\n    print('Button 2 pressed')\ndef button_3_pressed(pin):\n    print('Button 3 pressed')\n\n# now we register the button handler functions using the irq setter method for each pin\nbutton_1_pin.irq(trigger=Pin.IRQ_FALLING, handler = button_1_pressed)\nbutton_2_pin.irq(trigger=Pin.IRQ_FALLING, handler = button_2_pressed)\nbutton_3_pin.irq(trigger=Pin.IRQ_FALLING, handler = button_3_pressed)\n\n# we don't do anything in the main loop but sleep\nwhile True:\n    sleep(1)\n
"},{"location":"setup/06-pico-w-setup/","title":"Pico W Setup","text":"

When installing the Pico \"W\" (Wireless), you will need to download a different version of the MicroPython runtime.

The list of runtime images is on the https://micropython.org/download/RPI_PICO_W page.

In this example, we are using v1.24.1 (2024-11-29) .uf2.

Here is the image from Thonny that shows what version you are using. Note that you MUST select BOTSEL button before Thonny will download the new MicroPython runtime.

"},{"location":"setup/07-sound/","title":"Adding Sound to Your Clock","text":"

Many students want to add alarm features to their clock projects. The Raspberry Pi Pico can generate tones using Pulse Width Modulation (PWM) in MicroPython. Here are the key options for audio output devices:

"},{"location":"setup/07-sound/#piezo-ceramic-disc","title":"Piezo Ceramic Disc","text":"

A thin ceramic disc that converts electrical signals into sound through mechanical vibration.

Pros: - Extremely low cost (\\(0.50-\\)2 each) - Very simple to connect (just 2 wires) - No additional components needed - Durable and reliable - Perfect for basic beeps and alarms - Uses minimal power

Cons: - Limited sound quality - Can only produce simple tones - Relatively quiet - Not suitable for music playback - Can be harsh-sounding at high volumes

Typical Educational Lab Cost: $10-15 for a pack of 10 discs

"},{"location":"setup/07-sound/#piezo-speakers","title":"Piezo Speakers","text":"

Small enclosed speakers with a built-in resonant chamber.

Pros: - Low cost ($2-5 each) - Better sound quality than bare ceramic discs - Built-in enclosure improves volume - Simple 2-wire connection - Good for alarms and basic melodies - More pleasant sound than bare discs

Cons: - Still limited audio quality - Not suitable for music playback - Slightly more expensive than bare discs - Fixed resonant frequency can affect tone quality

Typical Educational Lab Cost: $20-30 for a pack of 10 speakers

"},{"location":"setup/07-sound/#standard-speakers","title":"Standard Speakers","text":"

Traditional cone speakers with magnets and voice coils.

Pros: - Much better sound quality - Can play actual music - Wide frequency response - Available in various sizes - Good volume levels

Cons: - More expensive ($4-15 each) - Requires amplification circuit - More complex wiring - Higher power requirements - Takes up more space - More fragile than piezos

Typical Educational Lab Cost: $20-60 for a set of 5 small speakers

"},{"location":"setup/07-sound/#digital-to-analog-converters-dac","title":"Digital to Analog Converters (DAC)","text":"

Chips that convert digital signals to analog audio.

Pros: - High-quality audio output - Can play music and complex sounds - Professional audio capabilities - Clean signal output - Multiple channels possible

Cons: - Most expensive option ($2-5 per chip) - Requires additional components - Complex wiring and setup - Needs amplification - Requires more advanced programming - May need level shifting circuits

Typical Educational Lab Cost: $20-50 for a complete setup with 5 DAC modules

"},{"location":"setup/07-sound/#microsd-readers","title":"MicroSD Readers","text":"

Card readers for storing and playing audio files.

Pros: - Can store many sound files - Allows for custom sounds - Easy to update sounds - Works well with DACs - Flexible storage options

Cons: - Additional cost ($2-5 per reader) - Requires SD card library - More complex programming - Needs file system management - Additional power requirements - Must handle card formatting

Typical Educational Lab Cost: $30-40 for a set of 5 readers plus cards

Recommendation for School Labs: For a basic classroom set serving 20 students:

Total recommended budget: ~$120 for a well-rounded audio lab setup that allows students to experiment with different sound options while keeping the basic piezo components affordable for everyone.

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

Learning Graph

Binary Clock

"},{"location":"sims/4-digits/","title":"Example of a Four-Digit Digital Clock Face","text":""},{"location":"sims/analog-clock/","title":"Analog Clock","text":"Analog Clock

Run Analog Clock MicroSim

"},{"location":"sims/analog-clock/#prompt","title":"Prompt","text":"
Create a single p5.js sketch file that uses a 400x400 canvas.\nThe file is a simulation of an analog clock face.\nThe clock face has a black background.\nThe clock has white hands for the minute and hour hand.\nThe clock as a red second hand.\nThere are two modes: manual and automtic\nManual mode allows the use to manually set the time using three sliders for hours, minute and seconds.\nAutomatic mode use the local time to update the display.\nAdd labels and values for the sliders.\nMake sure to reenable the controls as we switch back to manual mode.\n
"},{"location":"sims/analog-clock/#code","title":"Code","text":"

This is a wonderful lab to demonstrate some use of the trigonomic functions sine() and cosine(). We ask the question:

Write some Python code that will take in the seconds as a number from 0 to 59 and it will return the x and y positions of the tip of the second hand.

// Draw second hand\n// convert seconds to radians\nsecondHand = map(sc, 0, 60, 0, TWO_PI) - HALF_PI;\n// draw a line from the center of the canvas to the endpoint\nline(0, 0, cos(secondHand) * canvasSize / 2.5, \n           sin(secondHand) * canvasSize / 2.5);\n
"},{"location":"sims/analog-clock/#full-program-source","title":"FUll Program Source","text":"
let canvasSize = 400;\nlet hourHand, minuteHand, secondHand;\nlet hourSlider, minuteSlider, secondSlider;\nlet manualMode = true;\n\nfunction setup() {\n  const canvas = createCanvas(400, 400);\n  // use this line in the web\n  canvas.parent('canvas-container');\n  // use this in editor\n  // createCanvas(canvasSize, canvasSize);\n  background(0);\n\n  // Create sliders for manual mode\n  hourSlider = createSlider(0, 11, 0);\n  hourSlider.position(10, canvasSize + 10);\n\n  minuteSlider = createSlider(0, 59, 0);\n  minuteSlider.position(10, canvasSize + 40);\n\n  secondSlider = createSlider(0, 59, 0);\n  secondSlider.position(10, canvasSize + 70);\n\n  let modeButton = createButton('Switch Mode');\n  modeButton.position(10, canvasSize + 100);\n  modeButton.mousePressed(switchMode);\n}\n\nfunction draw() {\n  background(0);\n  translate(canvasSize / 2, canvasSize / 2);\n\n  let hr, mn, sc;\n  if (manualMode) {\n    hr = hourSlider.value();\n    mn = minuteSlider.value();\n    sc = secondSlider.value();\n  } else {\n    let now = new Date();\n    hr = now.getHours() % 12;\n    mn = now.getMinutes();\n    sc = now.getSeconds();\n  }\n\n  // Draw hour hand\n  stroke(255);\n  strokeWeight(10);\n  hourHand = map(hr, 0, 12, 0, TWO_PI) - HALF_PI;\n  line(0, 0, cos(hourHand) * canvasSize / 4, sin(hourHand) * canvasSize / 4);\n\n  // Draw minute hand\n  strokeWeight(8);\n  minuteHand = map(mn, 0, 60, 0, TWO_PI) - HALF_PI;\n  line(0, 0, cos(minuteHand) * canvasSize / 3, sin(minuteHand) * canvasSize / 3);\n\n  // Draw second hand\n  stroke(255, 0, 0);\n  strokeWeight(4);\n  secondHand = map(sc, 0, 60, 0, TWO_PI) - HALF_PI;\n  line(0, 0, cos(secondHand) * canvasSize / 2.5, sin(secondHand) * canvasSize / 2.5);\n}\n\nfunction switchMode() {\n  manualMode = !manualMode;\n\n  // disable the controls\n  if manualMode {\n    hourSlider.attribute('disabled', !manualMode);\n    minuteSlider.attribute('disabled', !manualMode);\n    secondSlider.attribute('disabled', !manualMode);\n  } else {\n    hourSlider.attribute('enabled', !manualMode);\n    minuteSlider.attribute('enabled', !manualMode);\n    secondSlider.attribute('enabled', !manualMode);\n  }\n\n}\n
"},{"location":"sims/battery-drain/","title":"Battery Drain","text":"Battery Drain

Run the Battery Drain Demo

Edit the Simulation

"},{"location":"sims/battery-drain/#about-the-battery-drain-microsim","title":"About The Battery Drain MicroSim","text":"

This MicroSim teaches students how batteries work in robots. The student can adjust the speed of the motor. But the more power the motor draws, the faster the battery drains. When the motor is off, there is no drain. When the battery is fully drained the motor will no longer turn.

"},{"location":"sims/battery-drain/#sample-prompt","title":"Sample Prompt","text":"
Generate a p5.js file on a 400x400 canvas that demonstrates the\nrate that a battery is discharged when it is powering a motor.\nAdd a slider at the bottom of the canvas that allows the user\nto adjust the speed of the motor.  Place the battery on the\nleft side.  Make the negative sign be at the bottom and \nuse a black filled rect.  Make the battery top be positive\nand use a gold filled rect to draw it.  Draw wires from\nthe battery's positive and negative to a motor in the right side.\nThe motor should drive a circle that spins faster as the slider is changed.\nThe motor should only spin if we have power.\n
"},{"location":"sims/battery-drain/#sample-code","title":"Sample Code","text":"
// battery drain\n// Animate a motor that drains a battery faster the more power is used\nlet canvasWidth = 400;\nlet drawHeight = 400;\nlet canvasHeight = 430;\nlet motorSpeedSlider;\nlet motorSpeed;\nlet batteryLevel = 100;\nlet dischargeRate;\nlet sliderLeftMargin = 100;\n\nfunction setup() {\n  const canvas = createCanvas(canvasWidth, canvasHeight);\n  //\n  textSize(16);\n\n  motorSpeedSlider = createSlider(0, 100, 50);\n  motorSpeedSlider.position(sliderLeftMargin, drawHeight + 10);\n  motorSpeedSlider.style('width', canvasWidth - sliderLeftMargin - 20 + 'px')\n\n  frameRate(60);\n}\n\nfunction draw() {\n  fill(245);\n  rect(0,0,canvasWidth,drawHeight);\n  fill('white');\n  rect(0,drawHeight,canvasWidth,canvasHeight-drawHeight);\n\n  motorSpeed = motorSpeedSlider.value();\n  // Battery representation\n  drawBattery();\n\n  // Wires from battery to motor\n  stroke(0);\n  // top wire\n  line(50, 50, 350, 50);\n  // bottom wire\n  line(50, 350, 350, 350);\n  line(350, 50, 350, 350);\n\n  // Motor and spinning circle\n  drawMotor();\n\n  // Update battery discharge\n  updateBattery();\n  noStroke();\n  fill('black');\n  text(\"Speed:\"+motorSpeed, 10, drawHeight+25);\n}\n\nfunction drawBattery() {\n  // Battery body\n\n  percentGold = .4\n  // Positive terminal\n  fill('gold'); // Gold color\n  rect(20, 50, 50, 300*percentGold);\n\n  // Negative terminal\n  fill('black');\n  rect(20, 350*percentGold, 50, 350*(1-percentGold));\n\n  // Battery level\n  let levelHeight = map(batteryLevel, 0, 100, 0, 300);\n  fill(0, 255, 0);\n  rect(30, 350 - levelHeight, 30, levelHeight);\n}\n\n// draw a spinning motor if we have power\nfunction drawMotor() {\n  fill(150);\n  ellipse(350, 200, 100, 100); // Motor body\n\n  // draw the spinner if we have power\n  if (batteryLevel > 1) {\n   push();\n      translate(350, 200);\n      rotate(frameCount / 50 * motorSpeed);\n      fill(0, 0, 255);\n      ellipse(0, 40, 20, 20); // Spinning circle\n   pop();\n  }\n}\n\n// the battery level goes down with time\nfunction updateBattery() {\n  dischargeRate = motorSpeedSlider.value() / 1000;\n  batteryLevel -= dischargeRate;\n  batteryLevel = max(batteryLevel, 0);\n}\n

Challenges

  1. Add a label that shows the percent of charge remaining
  2. Add a label that predicts how long the battery will last at the current discharge rate
  3. Add another button called \"Recharge\" that will slowly recharge the battery
  4. Add animation to the wires of red dots moving along the wires. Make the dots go faster at a higher power.
  5. Make the battery explode if you charge it too quickly
"},{"location":"sims/battery-drain/#lesson-plans","title":"Lesson Plans","text":""},{"location":"sims/battery-drain/#5th-grade-robotics-class-lesson-plan","title":"5th Grade Robotics Class Lesson Plan","text":""},{"location":"sims/battery-drain/#objective","title":"Objective","text":""},{"location":"sims/battery-drain/#duration","title":"Duration","text":""},{"location":"sims/battery-drain/#materials","title":"Materials","text":""},{"location":"sims/battery-drain/#introduction-10-minutes","title":"Introduction (10 minutes)","text":""},{"location":"sims/battery-drain/#demonstration-of-the-simulation-10-minutes","title":"Demonstration of the Simulation (10 minutes)","text":""},{"location":"sims/battery-drain/#group-activity-experimenting-with-the-simulation-20-minutes","title":"Group Activity: Experimenting with the Simulation (20 minutes)","text":""},{"location":"sims/battery-drain/#discussion-and-reflection-15-minutes","title":"Discussion and Reflection (15 minutes)","text":""},{"location":"sims/battery-drain/#conclusion-5-minutes","title":"Conclusion (5 minutes)","text":""},{"location":"sims/battery-drain/#assessment","title":"Assessment","text":""},{"location":"sims/battery-drain/#follow-up","title":"Follow-Up","text":""},{"location":"sims/binary-clock/","title":"Binary Clock","text":"

Run the Binary Clock Vertical Column MicroSim

Run the Vertical Column Binary Clock MicroSim Edit the Vertical Column Binary Clock MicroSim

"},{"location":"sims/binary-clock/#using-an-iframe","title":"Using an IFrame","text":"
<iframe src=\"./binary-clock-vertical.html\" width=\"420\" height=\"295\" scrolling=\"no\"\n  style=\"overflow: hidden;\"></iframe>\n
\n
"},{"location":"sims/binary-clock/#horizontal-binary-clock-microsim","title":"Horizontal Binary Clock MicroSim","text":"

Edit the Horizontal Microsim

"},{"location":"sims/binary-number/","title":"Binary Number","text":"

A binary number is one where each of the digits can only be a 0 or a 1. The values will double with each position. So the left-most digit is 0 or 1, the second bit is 0 or 2, the third is 0 or 4 and the fourth is 0 or 8. You can generate any number from 0 to 15 by adding the values.

"},{"location":"sims/binary-number/#prompt","title":"Prompt","text":"
Create a simulation of a 4-bit binary number.\nAdd buttons that toggle each of the four bits. \nAdd a text output that shows the decimal equivalent of the binary number.\n
"},{"location":"sims/binary-number/#sample-response","title":"Sample Response","text":"
// Global canvas dimensions\nlet canvasWidth = 330;\nlet canvasHeight = 120;\n\n// Binary bits and decimal value\nlet bits = [0, 0, 0, 0];\nlet decimalValue = 0;\n\nfunction setup() {\n  createCanvas(canvasWidth, canvasHeight);\n  textSize(24);\n  background(245);\n\n  // Create toggle buttons for each bit with correct labeling\n  for (let i = 0; i < 4; i++) {\n    let btn = createButton('Bit ' + i);\n    btn.position(20 + (3 - i) * 80, 50); // Placing buttons with Bit 3 on the left and Bit 0 on the right\n    btn.mousePressed(() => toggleBit(i));\n  }\n}\n\nfunction draw() {\n  // Clear the previous frame\n  clear();\n  background(245);\n\n  // Draw binary bits\n  for (let i = 0; i < bits.length; i++) {\n    text(bits[i], 40 + (3 - i) * 80, 30); // Displaying bits with Bit 3 on the left and Bit 0 on the right\n  }\n\n  // Calculate and draw the decimal value\n  decimalValue = binaryToDecimal(bits);\n  text('Decimal: ' + decimalValue, 20, 110);\n}\n\n// Toggle bit value\nfunction toggleBit(index) {\n  bits[index] = bits[index] === 0 ? 1 : 0;\n}\n\n// Convert binary array to decimal\nfunction binaryToDecimal(binaryArray) {\n  let decimal = 0;\n  for (let i = 0; i < binaryArray.length; i++) {\n    decimal += binaryArray[i] * Math.pow(2, i);\n  }\n  return decimal;\n}\n
"},{"location":"sims/binary-number/#learnings-from-the-response","title":"Learnings from The Response","text":"
  1. How to set up an array of buttons each with different actions
  2. How to use the pow(2, i) to convert a binary to decimal value
  3. How to setup a tobbleBit(index) function using the ? and : syntax
"},{"location":"sims/binary-number/#lesson-plan","title":"Lesson Plan","text":"

Objective:

Students will understand the binary number system and how it is used in computer science. They will learn to convert binary numbers to decimal numbers and vice versa.

Grade Level: 9th Grade

Duration: 1-2 class periods (45-90 minutes)

Materials:

  1. Computers with internet access (or offline environment set up with p5.js).
  2. Projector to demonstrate the simulation.
  3. Link to the simulation.
  4. Worksheets or online notebooks for binary-to-decimal conversion exercises.
  5. Whiteboard and markers.
"},{"location":"sims/binary-number/#lesson-outline","title":"Lesson Outline:","text":"
  1. Introduction to Binary Numbers (15 minutes):

  2. Begin with a discussion on number systems, focusing on the decimal system.

  3. Introduce the binary number system, explaining its base-2 nature.
  4. Discuss the significance of binary numbers in computer science.

  5. Demonstration of the Simulation (10 minutes):

  6. Project the simulation on the screen.

  7. Explain the interface, pointing out the bits, their significance, and the decimal conversion.
  8. Demonstrate toggling the bits and observing the decimal output.

  9. Interactive Session with Simulation (20 minutes):

  10. Allow students to experiment with the simulation on their computers.

  11. Encourage them to predict the decimal output before toggling the bits.
  12. Facilitate a discussion about their observations and insights.

  13. Binary to Decimal Conversion Exercise (15 minutes):

  14. Distribute worksheets with binary numbers.

  15. Instruct students to convert them to decimal numbers using the simulation as a reference.
  16. Review the answers as a class, discussing any common errors or misconceptions.

  17. Group Activity: Real-World Applications (15-30 minutes):

  18. Divide students into small groups.

  19. Assign each group to research and present a short explanation about a real-world application of binary numbers (e.g., digital storage, computer processors).
  20. Facilitate a class discussion on these applications, linking them back to the simulation.

  21. Wrap-up and Reflection (5-10 minutes):

  22. Summarize key takeaways from the lesson.

  23. Encourage students to reflect on what they learned and how they might see binary numbers in their daily lives.
  24. Answer any remaining questions.
"},{"location":"sims/binary-number/#assessment","title":"Assessment","text":""},{"location":"sims/binary-number/#extension-activities","title":"Extension Activities","text":""},{"location":"sims/binary-number/#references","title":"References","text":""},{"location":"sims/learning-graph/","title":"Learning Graph for Clocks and Watches with MicroPython","text":"

View Learning Graph MicroSim

"},{"location":"sims/seven-segment-display/","title":"7-Segment Display","text":"

Demo

"},{"location":"sims/shift-register/","title":"Shift Register MicroSim","text":"

Run Shift Register MicroSim

Logic Analyzer View

"},{"location":"sims/stopwatch/","title":"Stopwatch MicroSim","text":"

Run the Stopwatch MicroSim

Edit the Stopwatch MicroSim

The Stopwatch MicroSim allows you to simulate the actual stopwatch lab here: Stopwatch Lab

"},{"location":"sims/templates/","title":"MicroSim Templates","text":"

Run the MicroSim Template Edit this MicroSim

These templates are used to help guild your generative AI responses for consistency of the user interface.

{ .md-button .md-button--primary }

"},{"location":"sims/themes/","title":"Clock Face Themes","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/05-button-print-test/index.html b/setup/05-button-print-test/index.html index 262b21d0..1c780c89 100644 --- a/setup/05-button-print-test/index.html +++ b/setup/05-button-print-test/index.html @@ -1115,8 +1115,70 @@

Button Print Test

lab to display the button state on the display.

Using Interrupt Request Functions

To print what button is being pressed, we will write three small python functions. -We then will then have these functions run when the corresponding button is presssed by running the irq function.

+We then will then have these functions run when the corresponding button is pressed by running the irq function.

Sample Code

+
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
from utime import sleep
+from machine import Pin
+
+# Sample Raspberry Pi Pico MicroPython three button press example
+
+# Button Pin GPIO Configuration
+BUTTON_1_PIN = 13
+BUTTON_2_PIN = 14
+BUTTON_3_PIN = 15
+
+# Create 3 button objects as inputs with pullup resistors
+button_1_pin = Pin(BUTTON_1_PIN, Pin.IN, Pin.PULL_UP)
+button_2_pin = Pin(BUTTON_2_PIN, Pin.IN, Pin.PULL_UP)
+button_3_pin = Pin(BUTTON_3_PIN, Pin.IN, Pin.PULL_UP)
+
+# These functions gets called every time a button is pressed.
+def button_1_pressed(pin):
+    print('Button 1 pressed')
+def button_2_pressed(pin):
+    print('Button 2 pressed')
+def button_3_pressed(pin):
+    print('Button 3 pressed')
+
+# now we register the button handler functions using the irq setter method for each pin
+button_1_pin.irq(trigger=Pin.IRQ_FALLING, handler = button_1_pressed)
+button_2_pin.irq(trigger=Pin.IRQ_FALLING, handler = button_2_pressed)
+button_3_pin.irq(trigger=Pin.IRQ_FALLING, handler = button_3_pressed)
+
+# we don't do anything in the main loop but sleep
+while True:
+    sleep(1)
+
diff --git a/sitemap.xml b/sitemap.xml index ef16ec9d..ed1e3241 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -2,654 +2,654 @@ https://dmccreary.github.io/clocks-and-watches/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/about/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/contact/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/course-description/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/faq/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/glossary/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/references/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/gallery/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/alarm-clock/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/alarm-clock/display-alarm-icon/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/e-paper/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/gc9a01/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/gc9a01/01-hello-world/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/gc9a01/02-display-time/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/gc9a01/03-display-hand/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/gc9a01/04-cycle-fonts/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/gc9a01/05-draw-ticks/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/gc9a01/07-draw-12-digits/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/gc9a01/08-draw-filled-triangle/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/gc9a01/09-watch-face/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/ili9341/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/ili9341/01-rainbow-test/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/ili9341/02-config/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/ili9341/03-multiple-fonts/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/led-displays/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/led-displays/clock-simple/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/led-displays/reference/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/led-displays/shift-register-displays/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/lilygo/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/max7219/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/neopixel/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/neopixel/binary-clock/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/neopixel/binary-clock/alternative-layouts/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/neopixel/binary-clock/assembly/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/neopixel/binary-clock/code/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/neopixel/binary-clock/prompt/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/neopixel/fibonacci-clock/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/neopixel/fibonacci-clock/assembly/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/neopixel/fibonacci-clock/code/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/neopixel/fibonacci-clock/prompt/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/neopixel/seven-segment-clock/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/neopixel/seven-segment-clock/03-light-diffusion/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/neopixel/seven-segment-clock/assembly/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/neopixel/seven-segment-clock/code/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/neopixel/seven-segment-clock/prompt/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/oled-large/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/oled-large/00-blink/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/oled-large/01-assembly/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/oled-large/01-hello-world/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/oled-large/02-display-config/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/oled-large/02-i2c-scanner/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/oled-large/02-set-rtc-from-localtime/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/oled-large/03-display-localtime-raw/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/oled-large/04-display-localtime-formatted/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/oled-large/05-button-test/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/oled-large/05-rtc/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/oled-large/06-display-single-digit/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/oled-large/07-set-rtc-time-with-buttons/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/oled-large/10-clock-hands/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/oled-large/10-draw-seven-segments/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/oled-large/11-draw-thickness/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/oled-large/13-three-button-set/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/oled-large/15-digital-clock/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/oled-wireless/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/sh1106/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/shift-register-clock/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/shift-register-clock/clock-main/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/shift-register-clock/sample-prompt/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/ssd1306-i2c/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/ssd1306-i2c/adjusting-digit-display/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/ssd1306-i2c/assembly/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/ssd1306-i2c/code/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/ssd1306-i2c/integrating-localtime/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/ssd1306-i2c/prompt/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/ssd1306-i2c/setting-time/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/st7735/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/st7735/dirty-drawing/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/st7735/drawing-four-digits/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/st7735/hello-world/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/st7735/multiple-fonts/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/st7735/scaling-fonts/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/st7735/set-mode-buttons/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/st7735/single-digit/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/stopwatch/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/tm1637/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/tm1637/manual-time-setting/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/tm1637/running-clock/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/waveshare-e-paper/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/waveshare-lcd/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/waveshare-lcd/01-hello-world/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/waveshare-lcd/02-color-test/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/waveshare-lcd/02-display-text/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/waveshare-lcd/03-drawing-hands/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/kits/waveshare-lcd/04-5x8-font/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/lessons/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/lessons/00-hardware-config/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/lessons/01-get-local-time/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/lessons/01-timekeeping-architecture/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/lessons/05-buttons/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/lessons/06-setting-time/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/lessons/10-i2c-bus/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/lessons/11-spi-bus/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/lessons/30-ili9341/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/lessons/40-machine-info/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/lessons/55-shift-registers/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/lessons/60-sound/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/lessons/60-sound/play-tune/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/lessons/70-power/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/lessons/digital-to-analog-converters/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/lessons/digital-to-analog-converters/assembly/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/lessons/digital-to-analog-converters/code/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/lessons/digital-to-analog-converters/prompt/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/lessons/ntp/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/lessons/ntp/sample-ntp-function/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/lessons/rtc/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/lessons/rtc/13-real-time-clocks/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/lessons/rtc/13-rtc-explained/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/lessons/rtc/50-ds1307-breadboard/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/lessons/rtc/testing-the-rtc-type/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/lessons/screen-drawing/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/lessons/screen-drawing/01-drawing-coordinates/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/lessons/screen-drawing/02-display-text/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/lessons/screen-drawing/03-drawing-face/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/lessons/screen-drawing/04-larger-fonts/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/lessons/screen-drawing/05-drawing-polygons/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/lessons/screen-drawing/06-draw-performance/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/lessons/screen-drawing/07-drawing-digits/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/lessons/screen-drawing/09-digit-functions/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/lessons/screen-drawing/12-draw-time-functions/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/lessons/screen-drawing/15-scale-testing/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/lessons/screen-drawing/16-drawing-alarm-icon/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/prompts/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/prompts/01-course-description/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/prompts/02-concept-enumeration/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/prompts/03-concept-dependency/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/prompts/04-concept-taxonomy/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/prompts/05-taxonomy-assignment/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/prompts/06-banner-image/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/prompts/07-faq/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/prompts/08-glossary/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/prompts/09-oled-test/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/setup/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/setup/01-desktop/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/setup/02-purchasing-parts/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/setup/03-display-cable-harness/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/setup/04-led-clocks/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/setup/05-ai-in-the-classroom/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/setup/05-button-print-test/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/setup/06-pico-w-setup/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/setup/07-sound/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/sims/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/sims/4-digits/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/sims/analog-clock/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/sims/battery-drain/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/sims/binary-clock/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/sims/binary-number/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/sims/learning-graph/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/sims/seven-segment-display/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/sims/shift-register/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/sims/stopwatch/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/sims/templates/ - 2025-02-01 + 2025-02-02 https://dmccreary.github.io/clocks-and-watches/sims/themes/ - 2025-02-01 + 2025-02-02 \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz index 9cf77579..df0fe4e0 100644 Binary files a/sitemap.xml.gz and b/sitemap.xml.gz differ