Digital sound sensor and the Raspberry Pico

There are a couple of different options to do sound detection. One provides a simple digital signal when the sound passes a threshold. The other provides a threshold digital signal as well as an analog amplitude signal.

Digital Sound Detection Option

You can pick these sensors up for about $1 apiece on Amazon.

The sound detection sensor detects whether sound has exceeded a threshold value. Sound is detected via microphone and fed into an LM393 op amp. The sound level set point is adjusted via an on board potentiometer. When the sound level exceeds the set point, an LED on the module is illuminated and the output is set low.

Specifications of sound detection sensor module:

  • Working voltage: DC 3.3-5V
  • Adjustable Sensitivity
  • Dimensions: 32 x 17 mm
  • Signal output indication LED
  • Single channel output in the form of digital switching outputs (0 and 1 high and low)
  • Outputs low level and the signal light when there is sound

Schematic Diagram

The circuit

The program

The Pico MicroPython program to access the signal via any GPIO port. In this case we will use GPIO 16.

from machine import Pin
import utime
di=Pin(16,Pin.IN)
def sound_handler(pin):
    utime.sleep_ms(100)
    if pin.value()==False:
        print("ALARM! Sound detected!")
di.irq(trigger=machine.Pin.IRQ_RISING,handler=sound_handler)

This is a simple program that prints “Alarm! Sound detected” when the interrupt is triggered. You could do the sensing in a main loop as well. You will want to adjust the pot on the sound sensor board to get the desired level of sensitivity. I looked at using the one that also had analog output, but it output an analog audio signal that is more difficult to sense effectively and those boards cost slightly more as well.

Raspberry Pico and GC9A01 Round display in MicroPython

I recently saw some interesting applications of an inexpensive round LCD display. In looking around I didn’t see any good explanations on how to use the display with the Pico and MicroPython, so I thought I’d write one up.

First, I found the hardware I was planning to use on Amazon. The model was the 1.28 Inch TFT LCD Display Module Round RGB 240 * 240 GC9A01 Driver 4 Wire SPI Interface 240×240 PCB. This version had an IC board that fits right into a breadboard with all the current limiting resistors… onboard. This model does not have a direct line to drive the backlight though. It is only $18 for three of them at the time I am writing this.

The pinouts for the board are:

  • VCC – 3.3 to 5VDC
  • GND – Ground
  • SCL – CLOCK
  • SDA – Data In
  • DC – Data/Command
  • CS – Chip Select
  • RST – Reset

I wired my module to the Pico as shown below:

The Fritzing model is not perfect but good enough to show what I was trying to do. VCC is on the left and RST is on the right.

Much of the work to connect via MicroPython was included in a MicroPython driver for GC9A01 available on GitHub. The developer of the library has a fairly extensive set of sample programs that exercise the various features. The developer also has a pre-built set of firmware for the Pico and the PicoW so you just need to install the ‘OS’ on the chip.

Armed with all this information, I dug into the project. The first issue I encountered was that the firmware didn’t load properly (who knows what I had on that Pico previously), so I did a hard reset and loaded in the developer’s firmware. Now Thonny worked correctly accessing the device.

I next had to figure out the correct pins to use on the Pico that had the features I needed. That breadboard illustration above was the result of overcoming those issues. I ended up with a working test program that looked like:

from machine import Pin, SPI
import gc9a01 as gc9a01
# hardware config
SCL_PIN = 14 #chip pin 19
SDA_PIN = 15 #chip pin 20
DC_PIN = 4   #chip pin 6
CS_PIN = 5   #chip pin 7
RST_PIN = 6  #chip pin 9
spi = SPI(1, baudrate=60000000, sck=Pin(SCL_PIN), mosi=Pin(SDA_PIN))
# initialize the display
tft = gc9a01.GC9A01(
    spi,240,240,
    dc=Pin(DC_PIN, Pin.OUT),
    cs=Pin(CS_PIN, Pin.OUT),
    reset=Pin(RST_PIN, Pin.OUT),
    )
tft.fill(gc9a01.BLACK)
print("black")
# x, y, width, height
# red
tft.fill_rect(50,  75, 50, 60, gc9a01.color565(255,0,0))
print("red")
# green
tft.fill_rect(100, 75, 50, 60, gc9a01.color565(0,255,0))
print("green")
# blue
tft.fill_rect(150, 75, 50, 60, gc9a01.color565(0,0,255))
print("blue")

You can see below that the program draws blue, green, and red squares on the display.

Now that I had a handle on getting the screen to display something, I needed to create the tft_config.py file used in all the examples.

It ended up looking like:

"""Generic 240x240 GC9A01

Generic display connected to a Raspberry Pi Pico.

"""
# hardware config
SCL_PIN = 14 #chip pin 19 
SDA_PIN = 15 #chip pin 20 
DC_PIN = 4   #chip pin 6 
CS_PIN = 5   #chip pin 7 
RST_PIN = 6  #chip pin 9 

from machine import Pin, SPI
import gc9a01

TFA = 0
BFA = 0
WIDE = 0
TALL = 1

def config(rotation=0, buffer_size=0, options=0):
    """Configure the display and return an instance of gc9a01.GC9A01."""

    spi = SPI(1, baudrate=60000000, sck=Pin(SCL_PIN), mosi=Pin(SDA_PIN))
    return gc9a01.GC9A01(
        spi,
        240,
        240,
        reset=Pin(RST_PIN, Pin.OUT),
        cs=Pin(CS_PIN, Pin.OUT),
        dc=Pin(DC_PIN, Pin.OUT),
        rotation=rotation,
        options=options,
        buffer_size=buffer_size,
    )
Now, I can pull in one of the examples and as long as the python code and the tft_config.py file are in the same directory the sample code should work. Here is the lines sample program:
"""
lines.py
========
.. figure:: /_static/lines.png
  :align: center

Benchmarks the speed of drawing horizontal and vertical lines on the display.
"""

import random
import time
import gc9a01
import tft_config

tft = tft_config.config(tft_config.TALL)
WIDTH = tft.width()
HEIGHT = tft.height()

def time_function(func, *args):
    """time a function"""
    start = time.ticks_ms()
    func(*args)
    return time.ticks_ms() - start

def horizontal(line_count):
    """draw line_count horizontal lines on random y positions"""
    for _ in range(line_count):
        y = random.randint(0, HEIGHT)
        tft.line(
            0,
            y,
            WIDTH,
            y,
            gc9a01.color565(
                random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)
            ),
        )

def vertical(line_count):
    """draw line_count vertical lines on random x positions"""
    for _ in range(line_count):
        x = random.randint(0, WIDTH)
        tft.line(
            x,
            0,
            x,
            HEIGHT,
            gc9a01.color565(
                random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)
            ),
        )

def diagonal(line_count):
    """draw line_count diagnal lines on random y positions"""
    x = 0
    for _ in range(line_count):
        x += 1
        x %= WIDTH
        tft.line(
            x,
            0,
            WIDTH - x,
            HEIGHT,
            gc9a01.color565(
                random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)
            ),
        )

def main():
    tft.init()
    tft.fill(0)
    print("")
    print("horizonal: ", time_function(horizontal, 1000))
    print("vertical: ", time_function(vertical, 1000))
    print("diagonal: ", time_function(diagonal, 1000))

main()

The output generated was:

horizonal:  289
vertical:  283
diagonal:  2093

Meaning that the MicroPython version of this sample code took 289 ms to draw 1000 horizontal random color lines… Just as a test I took the random generator out and about 25ms of the time was taken generating the random colors for each type of line.

Now that I have this testing behind me, I can move on to something more interest…

For those who remember the invisible monster… or maybe the Robot Spy.

Experimenting with Bing Chat

Since I have been playing with ChatGPT, and Bing Chat came out, I thought I’d try it. I already had a baseline dealing with writing Python for dice rolling, so I thought I’d start with the same kind of query for Bing Chat.

I asked:

Using the Visual Studio Python IDE Python Development Tools to deploy an application for the Android platform?

The answer I got back was:

Hello, this is Bing. I searched the web for your query and found some results that might be helpful.

According to the search results, Visual Studio is a powerful IDE for Python language on Windows, and it provides open-source support for the Python language through the Python Development and Data Science workloads12You can use Visual Studio to edit, debug, and test Python code, and then publish an app3.

However, developing for Android using Python comes with some additional challenges, as Python is not “native” on those platforms, so you have to include Python with your app, and there are no “official” Pythons for either platform4You might want to use some tools or frameworks that can help you create Android apps with Python, such as Briefcase5Kivy5, or BeeWare5.

That was nice, and clear about the Android side of things but still left be wondering about the Visual Studio installation. I had to ask a follow up question:

What would I need to do on Windows Visual Studio to install kivy to develop for the android platform?

And the reply was:

I searched the web for your query and found some results that might be helpful.

According to the search results, to install Kivy on Windows Visual Studio, you need a compiler. On Windows, the Visual Studio Build Tools are required, and they are available for free1You can either download and install the complete Visual Studio IDE, which contains the build tools1, or download and install only the build tools1Then, you can use pip to install Kivy2.

To develop for Android using Kivy, you need to create a Python file and a Kivy file for your app. You can use Kivy’s widgets and layouts to design your interface, and use Python’s logic to implement your functionality3You can also use Kivy’s APIs to access Android features, such as vibration, sensors, texting, etc4To test your app, you can use the Kivy Launcher app, which allows you to run your Kivy app without a compilation step4To compile and publish your app, you need to use the Buildozer tool, which can create a fully signed AAB (Android App Bundle) for your app4.

That is different answer than the one from Chat GPT. It was more succinct but not quite as detailed.

So I thought I’d give it the first question I gave ChatGPT related to GUI development that really shocked me:

write a graphic user interface in python that rolls two dice using Kivy

The answer was a bit underwhelming:

I searched the web for your query and found some results that might be helpful.

According to the search results, to create a graphic user interface in python using Kivy, you need to import the Kivy library and create an App class that inherits from the App base class1The App class has a build() method that returns a widget that will be displayed on the screen1You can use various widgets and layouts from the Kivy library to design your interface, such as Label, Button, BoxLayout, GridLayout, etc2You can also use the Canvas instruction to draw shapes and lines on the screen3.

To create a graphic user interface in python that rolls two dice using Kivy, you need to import the random module and create a function that generates two random numbers between 1 and 6, and returns them as a tuple4You also need to create a Label widget that displays the result of the dice roll, and a Button widget that triggers the dice roll function when clicked5You can use a BoxLayout or a GridLayout to arrange the widgets on the screen2.

Here is an example of a Python file and a Kivy file for a graphic user interface that rolls two dice using Kivy:

# dice.py

import kivy

kivy.require('2.1.0')

from kivy.app import App

from kivy.uix.label import Label

from kivy.uix.button import Button

from kivy.uix.boxlayout import BoxLayout

from kivy.properties import StringProperty

import random

class DiceApp(App):

    # A property to store the result of the dice roll

    result = StringProperty('Roll the dice!')

    def build(self):

        # Create a box layout to hold the widgets

        layout = BoxLayout(orientation='vertical')

        # Create a label widget to display the result

        self.label = Label(text=self.result, font_size=50)

        # Create a button widget to trigger the dice roll

        self.button = Button(text='Roll', font_size=50, on_press=self.roll_dice)

        # Add the widgets to the layout

        layout.add_widget(self.label)

        layout.add_widget(self.button)

        # Return the layout as the root widget

        return layout

    def roll_dice(self, instance):

        # Generate two random numbers between 1 and 6

        dice1 = random.randint(1, 6)

        dice2 = random.randint(1, 6)

        # Update the result property with the dice values

        self.result = f'{dice1} + {dice2} = {dice1 + dice2}'

# Run the app

if __name__ == '__main__':

    DiceApp().run()

# dice.kv

<DiceApp>:

    # Define the appearance of the widgets

    label:

        color: 0, 0, 0, 1 # Black text

        size_hint: 1, 0.8 # Take 80% of the vertical space

    button:

        background_color: 0, 0, 1, 1 # Blue background

        color: 1, 1, 1, 1 # White text

        size_hint: 1, 0.2 # Take 20% of the vertical space

It did give me some code and it gave me references that were totally missing from ChatGPT but I was not as impressed.

I will need to try and go back and forth between the two and see what happens.

It did make me think about writing a paper with Bing Chat (to get the references) and then having ChatGPT make it better. So, I asked my question that started me down the path of looking at ChatGPT:

Write 500 words about the origin of computer aided design.

Even though it said it couldn’t do it, it produced a surprisingly good response:

I searched the web for your query and found some results that might be helpful.

According to the search results, computer aided design (CAD) is the use of computers to facilitate or assist design, especially in engineering and architecture1The term CAD emerged in the 1950s and is commonly credited to Douglas Ross, a computer scientist who was working at MIT when the acronym was coined23The origin of CAD can be traced back to the Second World War, when there was a huge wave of development in computing1.

One of the earliest examples of CAD was the Program for Numerical Tooling Operations (PRONTO), the very first Computer Numerical Control (CNC) programming system, developed by Patrick Hanratty in 19571PRONTO allowed the creation of numerical control programs for machining parts using a computer1Another pioneer of CAD was Ivan Sutherland, who developed Sketchpad in 1963, the first interactive computer graphics program that allowed users to draw and manipulate geometric shapes on a screen using a light pen4.

The development of CAD was influenced by the advancement of computer hardware and software, such as the invention of the transistor, the integrated circuit, the mouse, the graphical user interface, and the object-oriented programming25The first commercial CAD systems were introduced in the late 1960s and early 1970s, such as ADAM (Automated Drafting And Machining), DAC-1 (Design Augmented by Computer), and CADAM (Computer Augmented Drafting And Manufacturing)2These systems were mainly used for 2D drafting and design, and were expensive and limited to large corporations and institutions2.

The 1980s and 1990s saw the emergence of 3D modeling and rendering, parametric and feature-based modeling, solid and surface modeling, and computer-aided engineering (CAE) and manufacturing (CAM) integration24Some of the notable CAD systems of this era were AutoCAD, CATIA, Pro/ENGINEER, SolidWorks, and Unigraphics2These systems were more affordable and accessible, and enabled more complex and realistic design and simulation2.

The 21st century has witnessed the development of cloud-based, web-based, and mobile CAD systems, as well as the application of CAD in various fields, such as animation, gaming, biomedical, and fashion24Some of the current trends and challenges in CAD are artificial intelligence, virtual and augmented reality, additive manufacturing, and collaborative and distributed design24CAD has become an indispensable tool for designers and engineers, and has revolutionized the design process and the quality of the products24.

With references that were totally missing from the ChatGPT response.

So I went to Chat GPT and asked it to improve the essay. ChatGPT produced the following:

Computer-aided design (CAD) is a technology that has revolutionized the way that engineers and designers create and refine products. Simply put, CAD is the use of computers to facilitate or assist in the design process, particularly in the fields of engineering and architecture. From its humble beginnings in the 1950s to its current use in a wide range of industries, CAD has come a long way in terms of both capabilities and accessibility.

The term “CAD” is commonly credited to Douglas Ross, a computer scientist who was working at MIT when the acronym was coined in the 1950s. However, the origins of CAD can be traced back even further, to the Second World War, when there was a huge wave of development in computing. One of the earliest examples of CAD was the Program for Numerical Tooling Operations (PRONTO), which was the very first Computer Numerical Control (CNC) programming system. PRONTO allowed the creation of numerical control programs for machining parts using a computer. Another pioneer of CAD was Ivan Sutherland, who developed Sketchpad in 1963. This was the first interactive computer graphics program that allowed users to draw and manipulate geometric shapes on a screen using a light pen.

The development of CAD was influenced by the advancement of computer hardware and software, such as the invention of the transistor, the integrated circuit, the mouse, the graphical user interface, and the object-oriented programming. The first commercial CAD systems were introduced in the late 1960s and early 1970s, such as ADAM (Automated Drafting And Machining), DAC-1 (Design Augmented by Computer), and CADAM (Computer Augmented Drafting And Manufacturing). These systems were mainly used for 2D drafting and design, and were expensive and limited to large corporations and institutions.

The 1980s and 1990s saw the emergence of 3D modeling and rendering, parametric and feature-based modeling, solid and surface modeling, and computer-aided engineering (CAE) and manufacturing (CAM) integration. Some of the notable CAD systems of this era were AutoCAD, CATIA, Pro/ENGINEER, SolidWorks, and Unigraphics. These systems were more affordable and accessible, and enabled more complex and realistic design and simulation.

The 21st century has witnessed the development of cloud-based, web-based, and mobile CAD systems, as well as the application of CAD in various fields, such as animation, gaming, biomedical, and fashion. Some of the current trends and challenges in CAD are artificial intelligence, virtual and augmented reality, additive manufacturing, and collaborative and distributed design. CAD has become an indispensable tool for designers and engineers and has revolutionized the design process and the quality of the products.

A definite style improvement though now all the references disappeared.

A mixed bag of results from my perspective.

Continuing to understand ChatGPT and its programming capabilities.

I saw a post yesterday in the media stating that ChatGPT is not particularly innovative. This statement was made by one of Meta‘s AI chief scientists. He said, “It’s nothing revolutionary, although that’s the way it’s perceived in the public. It’s just that, you know, it’s well put together, it’s nicely done.”

Guess what, a usable tool is innovative. Look at the Lisa (predecessor to the Macintosh). Did it have anything all that innovative compared to what Xerox was doing already? Not really, but it was well integrated and thought out. By the time it became the Macintosh, it was also accessible and easy to use.

The Meta AI chief scientist may have access to “what’s available at other labs.”, but I don’t, so I am continuing my path to discovery with what I have. Taking up where I left off in the previous post about ChatGPT’s ability to aid software development:

I got my Python environment setup to use the Python PyQt5 library. Now I have the following screen displayed and working:

Not too bad for someone who is barely functional in Python…

Now I need to determine how to move the software program over to Android, so I asked ChatGPT:

Using the Visual Studio Python IDE Python Development Tools to deploy an application for the Android platform?

The answer I got back was:

Deploying an application for the Android platform using the Visual Studio Python IDE and Python Development Tools (PTVS) is not a straightforward process and requires some setup and additional tools. Here's an overview of the steps you can take:

Install the Android SDK on your computer:

Download the Android SDK from the official website (https://developer.android.com/sdk)

Install the SDK and set the ANDROID_HOME environment variable to the path of the SDK

Install the Java Development Kit (JDK) on your computer:

Download the JDK from the official website (https://www.oracle.com/java/technologies/javase-downloads.html)

Install the JDK and set the JAVA_HOME environment variable to the path of the JDK

Install Python for Android (python-for-android) on your computer:

Run the command pip install python-for-android

Create a new project in Visual Studio using the Python Development Tools:

Open Visual Studio and create a new project

Select the "Python Application" template

Write your Python code for the application

Use python-for-android to create an APK file:

Open a command prompt or terminal window

Navigate to the directory where your Python code is located

Run the command python-for-android apk --private . --package org.test.myapp --name "My App" --version 0.1 --bootstrap=sdl2

This will create an APK file in the bin directory of the project

Install the APK file on your Android device:

Connect your Android device to your computer

Copy the APK file to your device

Open the file manager on your device and navigate to the location where you copied the APK file

Tap on the APK file to start the installation process

Note that this process may require some tweaking and adjustments to your script and settings to make it run well on mobile devices, also it's not as simple as using Kivy, which is a more complete framework for building mobile applications.

Kivy is an Open Source Python application development Framework to build and distribute Python cross-platform GUI apps. These instructions are going to have to wait to test out another day.

A Python dice implementation by ChatGPT

My first thought was what are the GUI options for Python, so I asked:

How to generate a gui interface from python code?

What resulted from this request was:

There are several libraries that can be used to generate a GUI interface from Python code, some of the most popular ones include:

Tkinter: This is a built-in Python library that provides a simple way to create graphical user interfaces. It is a powerful tool for creating desktop applications.

PyQt: This is a set of Python bindings for the Qt application framework and runs on all platforms supported by Qt including Windows, OS X, Linux, iOS, and Android.

wxPython: This is a set of Python bindings for the wxWidgets toolkit, which allows you to create graphical user interfaces that are compatible with Windows, OS X, and Linux.

PyGTK: PyGTK is a set of Python bindings for the GTK toolkit. It is the foundation for the GNOME desktop environment and is compatible with Windows, OS X, and Linux.

PySide: PySide is a set of Python bindings for the Qt toolkit and runs on all platforms supported by Qt including Windows, OS X, Linux, iOS, and Android.

Each of these libraries has its own strengths and weaknesses, so you should choose the one that best suits your needs.

That was a pretty useful overview of the options, so I thought I’d just let it take a crack at it and asked:

write a graphic user interface in python that rolls two dice

What I got back definitely worked:

import tkinter as tk
import random

def roll_dice():
    dice1 = random.randint(1, 6)
    dice2 = random.randint(1, 6)
    result_label.config(text=f"Dice 1: {dice1}\nDice 2: {dice2}")

root = tk.Tk()
root.title("Dice Roller")
result_label = tk.Label(root, text="Welcome to Dice Roller", font=("Helvetica", 20))
result_label.pack()
roll_button = tk.Button(root, text="Roll Dice", command=roll_dice)
roll_button.pack()
root.mainloop()

This GUI has a button labeled "Roll Dice" and a label that displays the results of the dice roll (e.g. "Dice 1: 4\nDice 2: 5"). When the button is clicked, the roll_dice() function is called, which generates random numbers between 1 and 6 for each dice and updates the label with the results.

This is just a simple example, you can customize it as you like and add more functionality, for example you can add a history of rolls or a picture of the dices, etc.

No comments, but when I ran it, the GUI generated showed:

And when I clicked Roll Dice it displayed a window:

And new random numbers were displayed every time I clicked Roll Dice. Not too bad with the level of direction I gave it. So, I asked it to improve upon the design, by stating:

add to the GUI the ability to have pictures of the dice

It already knew about the previous program so I was hoping it would just build upon that result. This was what was generated.

import tkinter as tk
from PIL import Image, ImageTk
import random

def roll_dice():
    dice1 = random.randint(1, 6)
    dice2 = random.randint(1, 6)
    dice1_image = ImageTk.PhotoImage(Image.open(f"dice{dice1}.png"))
    dice2_image = ImageTk.PhotoImage(Image.open(f"dice{dice2}.png"))
    dice1_label.config(image=dice1_image)
    dice2_label.config(image=dice2_image)
    dice1_label.image = dice1_image
    dice2_label.image = dice2_image
root = tk.Tk()
root.title("Dice Roller")
result_label = tk.Label(root, text="Welcome to Dice Roller", font=("Helvetica", 20))
result_label.pack()
dice1_label = tk.Label(root)
dice1_label.pack(side=tk.LEFT)
dice2_label = tk.Label(root)
dice2_label.pack(side=tk.RIGHT)
roll_button = tk.Button(root, text="Roll Dice", command=roll_dice)
roll_button.pack()
root.mainloop()

Here, I'm using the PIL library to open and display images of dice. I'm assuming that the images are in the same folder as the script, and are named dice1.png, dice2.png, etc.

In the roll_dice() function, I'm generating random numbers between 1 and 6 for each dice, and then creating PhotoImage objects for each dice using the ImageTk.PhotoImage method. I'm then configuring the dice1_label and dice2_label to display the corresponding images and updating the labels' images.

You can use your own images of the dices, just make sure that the images are in the same folder as the script and the name of the images match the code, for example if you have a dice with the number 3, the image should be named dice3.png

Keep in mind that the images must be in a format that the PIL library can open, like PNG, JPG, etc.

I now had to find some dice images for it to display, which I just pulled off the internet into the folder with the file names stated. I dropped those into the folder, so now the display looked like:

This is better, so now I realize it is not compatible with Android, one platform where I would like this to run. I asked it:

write a graphic user interface in python that rolls two dice PyQt

and the following was generated:

import sys
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import QApplication, QLabel, QPushButton, QVBoxLayout, QWidget
from random import randint

class DiceRoller(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Dice Roller")
        self.initUI()
        
    def initUI(self):
        self.dice1_label = QLabel()
        self.dice1_label.setAlignment(Qt.AlignCenter)
        self.dice2_label = QLabel()
        self.dice2_label.setAlignment(Qt.AlignCenter)

        roll_button = QPushButton("Roll Dice")
        roll_button.clicked.connect(self.roll_dice)

        layout = QVBoxLayout()
        layout.addWidget(self.dice1_label)
        layout.addWidget(self.dice2_label)
        layout.addWidget(roll_button)
        
        self.setLayout(layout)

    def roll_dice(self):
        dice1 = randint(1, 6)
        dice2 = randint(1, 6)
        self.dice1_label.setPixmap(QPixmap(f"dice{dice1}.png"))
        self.dice2_label.setPixmap(QPixmap(f"dice{dice2}.png"))

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = DiceRoller()
    window.show()
    sys.exit(app.exec_())

This GUI has a button labeled "Roll Dice" and two labels that displays the results of the dice roll (dice images). When the button is clicked, the roll_dice() function is called, which generates random numbers between 1 and 6 for each dice, loads the images using the QPixmap class and updates the labels with the images.
As in the previous example, I'm assuming that the images are in the same folder as the script, and are named dice1.png, dice2.png, etc. and you can use your own images, just make sure the name of the images match the code, for example if you have a dice with the number 3, the image should be named dice3.png.
Keep in mind that the images must be in a format that PyQt can open, like PNG, JPG, etc.

If this code is error free (and it should be), that definitely took some of the grunt work out of learning GUI programming with Python. I’ll have to get back to this tomorrow though.

Pico W analog clock complete

Way back in July and August I posted about purchasing a Pico W and using it to create an analog clock. I didn’t get back to the effort for a while but finally pulled it together. Here is what it looks like:

The box is about 15 inches long and the clock time pointer moves horizontally to indicate the time over about a 9-inch path.

The real purpose of this effort was to bring together many of the elements in a microcontroller class I’ve been giving at the woodshop, showing the students something that works. This class is based on the Pico documentation but incorporates several other components, as well as American version of the examples.

When you turn the clock on, the pointer is moved until the end of day limit switch is hit. The clock then rewinds until the start of day limit switch is hit. The Pico W then gets the time off the Internet and then moves the pointer to the current time of day. After that, each minute the pointer is advanced the correct amount, until it hits the end of day limit switch. I originally tried to do everything without limit switches, but there is enough friction and slippage that I couldn’t get it exactly right every time. Limit switches used concepts from the class and allowed the program to reset the time each midnight (or so) — the best approach to keep the clock relatively accurate.

If you look inside the box:

You’ll see the:

  • Pico W
  • Stepper motor
  • Parts normally used on a 3D printer
  • Two limit switches
  • Batteries

For those who look closely, you’ll notice that the rod supporting the pointer movement is disconnected. I didn’t realize that when I took the picture – that’s fixed now.

The entire box is made out of Alder wood. I used my laser to inscribe the lid. I think the saying is unique and interesting, since it can be interpreted in multiple ways. All the nonmetal mounting components were 3D printed to minimize the size of the structures.

The current draw for the stepper motor was a bit more than I expected. The power supply contains 3 AA batteries (to get about 5 V) and that does not last long, but I was doing quite a few power-on resets when I got started. A power-on moves the pointer the entire length of the rod, twice.

I will probably not do too much more with it. For demonstration purposes it is good enough. All in, there is about $7 worth of wood and $25 worth of parts, as well as uncounted hours of effort figuring the whole design, debug, assembly process out.

Pico W getting the time from an NTP server

Rather than continuing to use the kludge that I showed in the last example, I asked a question in Reddit if someone has an example of an NTP connection from the Pico W. I got a pointer to a git site back the same day.

I am still not sure about the constant NTP_DELTA, but the rest of the code makes sense and is much more reliable. Here is how I incorporated it into my test:

import network
import socket
import time
import struct
import secrets
import machine
import timeoffset

from machine import Pin

NTP_DELTA = 2208988800
host = "time-c.nist.gov"


def set_time():
    NTP_QUERY = bytearray(48)
    NTP_QUERY[0] = 0x1B
    addr = socket.getaddrinfo(host, 123)[0][-1]
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        s.settimeout(1)
        res = s.sendto(NTP_QUERY, addr)
        msg = s.recv(48)
    finally:
        s.close()
    NTPSeconds= struct.unpack("!I", msg[40:44])[0]
    #print("val: ",NTPSeconds)
    t = NTPSeconds- NTP_DELTA    #NTP_DELTA?
    datetimetuple = time.gmtime(t)
    machine.RTC().datetime((datetimetuple[0], datetimetuple[1], datetimetuple[2], datetimetuple[6] + 1, datetimetuple[3], datetimetuple[4], datetimetuple[5], 0))
    return t

def get_hours(datetimetuple):
    hours = datetimetuple[3]
    return hours

def get_minutes(datetimetuple):
    minutes = datetimetuple[4]
    return minutes

def get_my_time(epochtimeseconds):
     epochtimeseconds = epochtimeseconds + timeoffset.TIMEOFFSETSECONDS
     return epochtimeseconds
#devices
led = Pin("LED", Pin.OUT)
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(secrets.SSID, secrets.PASSWORD)

max_wait = 10
while max_wait > 0:
    if wlan.status() < 0 or wlan.status() >= 3:
        break
    max_wait -= 1
    print('waiting for connection...')
    time.sleep(1)

if wlan.status() != 3:
    raise RuntimeError('network connection failed')
else:
    print('connected')
    status = wlan.ifconfig()
    print( 'ip = ' + status[0] )

led.on()
epochTime = set_time()
print("set time epoch: ",epochTime)
print("time tuple: ",time.gmtime())
localEpoch = get_my_time(epochTime)
print("local epochtimeseconds: ",localEpoch)
print("my time tuple: ",time.gmtime(localEpoch))
print("hours: ", get_hours(time.gmtime(localEpoch)))
print("minutes: ",get_minutes(time.gmtime(localEpoch)) )
led.off()

Similar to the previous example, I have my security information in a secrets.py file and my local offset in a timeoffset.py file.

I still need to see if I can get it to be more robust though, since every once in a while, I get a timeout error.

Another light and sound project for the G gage train

I have been working on numerous projects to make the outdoor train at Sun City more interesting. The latest is to add siren sounds and flashing lights to the fire station. After putting all the effort into putting together a training session using the Pico, covering sound and lights

I thought I’d make a small 3D printed box to hold it all together. There are 3 major components:

  1. A buck converter — to take the 12 volts around the layout and step it down to the 5V needed for the project.
  2. The Pico
  3. A sound module — this holds the MP3 files and talks to the Pico via a serial interface.

The finished product (with the top off and connected to my test harness) looks like:

Sound and light module

In the various jacks I’ve plugged in the speaker, switch, and two LEDs. Those would be removed and replaced with the actual connections.

The code for the project is:

from machine import Pin,UART
import utime
#constant
UART_TX = 0
UART_RX = 1
ONBOARD_LED_GPx = 25
LEFT_RED_GPx = 14
RIGHT_RED_GPx = 15
BUTTON_GPx = 13
## command to play through all the sounds
PLAY_NEXT_ARRAY = bytearray(8)
PLAY_NEXT_ARRAY[0] = 0x7E
PLAY_NEXT_ARRAY[1] = 0xFF
PLAY_NEXT_ARRAY[2] = 0x06
PLAY_NEXT_ARRAY[3] = 0x01
PLAY_NEXT_ARRAY[4] = 0x00
PLAY_NEXT_ARRAY[5] = 0x00
PLAY_NEXT_ARRAY[6] = 0x00
PLAY_NEXT_ARRAY[7] = 0xEF
## command to get status
STATUS_ARRAY = bytearray(8)
STATUS_ARRAY[0] = 0x7E
STATUS_ARRAY[1] = 0xFF
STATUS_ARRAY[2] = 0x06
STATUS_ARRAY[3] = 0x42
STATUS_ARRAY[4] = 0x00
STATUS_ARRAY[5] = 0x00
STATUS_ARRAY[6] = 0x00 
STATUS_ARRAY[7] = 0xEF
## command to reset the device
STOP_ARRAY = bytearray(8)
STOP_ARRAY[0] = 0x7E
STOP_ARRAY[1] = 0xFF
STOP_ARRAY[2] = 0x06
STOP_ARRAY[3] = 0x16
STOP_ARRAY[4] = 0x00
STOP_ARRAY[5] = 0x00
STOP_ARRAY[6] = 0x00 
STOP_ARRAY[7] = 0xEF
## command to set max volume
VOLUME_ARRAY = bytearray(8)
VOLUME_ARRAY[0] = 0x7E
VOLUME_ARRAY[1] = 0xFF
VOLUME_ARRAY[2] = 0x06
VOLUME_ARRAY[3] = 0x06
VOLUME_ARRAY[4] = 0x00
VOLUME_ARRAY[5] = 0x00
VOLUME_ARRAY[6] = 0x1E # 30 is the max?
VOLUME_ARRAY[7] = 0xEF
#device definition
uart = UART(0, baudrate=9600, tx=Pin(UART_TX), rx=Pin(UART_RX))
led_onboard = machine.Pin(ONBOARD_LED_GPx, machine.Pin.OUT)
left_LED = machine.Pin(LEFT_RED_GPx, machine.Pin.OUT)
right_LED = machine.Pin(RIGHT_RED_GPx, machine.Pin.OUT)
button = machine.Pin(BUTTON_GPx, machine.Pin.IN, machine.Pin.PULL_DOWN)
#main program
uart.write(VOLUME_ARRAY)
utime.sleep(0.1) # give it some time to read the data
led_onboard.value(1) # to show the program is running
utime.sleep(0.3)
led_onboard.value(0) # flash to show the program is running
utime.sleep(0.3)
led_onboard.value(1) # to show the program is running
while True:
    if button.value()==1:
        uart.write(PLAY_NEXT_ARRAY)
        utime.sleep(0.1)
        #initialize the lights
        left_LED.value(1)
        right_LED.value(0)
        playing = True;
        while playing:
            left_LED.toggle()
            right_LED.toggle()
            uart.write(STATUS_ARRAY) # ask for the status
            utime.sleep(0.3)
            statusBytes = uart.read()
            utime.sleep(0.2) # give the device time to respond
            if len(statusBytes)>5:
                if statusBytes[6] == 1:
                    playing = True
                else:
                    playing = False
                    uart.write(STOP_ARRAY) # work around for issue with playing
                    utime.sleep(0.1)
        left_LED.value(0)
        right_LED.value(0)
        led_onboard.value(0) # flash to show the program is running
        utime.sleep(0.3)
        led_onboard.value(1) # to show the program is running
        utime.sleep(0.2)

The sound module replies with an invalid status the first time the loop is triggered, so I had to add a bit of code to work around that. This code will play through any number of MP3s that are placed on a microSD card, one sound file for each button press. The lights will flash for the duration the sound is playing.

Overall, a fun project for the layout. The total cost is less than $20.

Added a lesson about relays with the Pico

I have continued work on my Pico class and model railroad automation. An effort like this would definitely be incomplete without triggering some relays. Here is a slightly modified version of the lession.

What if your device needs 12vdc or 120vac or even just 3.3vdc but with more current than the 16 milliamps (mA) a Pico GPIO pin can provide? You need add a device or logic to isolate the power circuit from the PICO. There are two ways to do this … with a relay, or a transistor. This post explains how to use a low-cost relay (about $1) to switch just about anything on or off.

Relays are, in effect, electronic switches that can switch higher voltages and currents than the triggering device can handle. In nearly all cases, the working voltage being switched by the device is supplied externally. The PICO will simply turn the relay on and off.

Note that the voltages used in this class will not hurt you, but when you start working with the kind of voltages and currents that are possible to switch with the relay, you need to be very careful.

Many relays operate using a coil to switch mechanical contacts. There can be many relays mounted in a single relay system. Many devices are available commercially with 1 to 16 relays.

Two SPDT relay module

A coil triggered relay has an operating voltage and current requirement. The Pico has a maximum current load for all pins at 50 mA, so keep that in mind when you design your circuit.

The relay used here employs an Optocoupler (a FL-3FF) inside. These draw significantly lower current (as little as 5 mA) than the ones using coils, so our Pico can easily switch them.

A board mount relay example

The two positions of relays are either considered Normally Open (NO) and Normally Closed (NC).

The relay logical diagram

The normal position is when the circuit is not energized. This is when the GPIO pin controlling the relay is low (and drawing no current). The pins on our relay are:

  • NO = Normally Open connection
  • COM = Line being switched
  • NC = Normally Closed connection
  • IN = The volage triggering the switch
  • DC- = DC circuit ground
  • DC+ = 3V3 (3.3 volts from the Pico) in our case

For our circuit example, the wiring looks like:

Relay circuit

We wire NO to the yellow LED and the NC to the red LED. The COM line that is being switched goes to VBUS (5V). The controlling signal is connected to GP15 of the Pico and the DC- goes to ground and the DC+ can go to either VBUS or 3V3.

With the following code, the relay should switch on the yellow LED for on 5 seconds and then the red LED on for 1 second, in a loop.

#imports
import machine
import utime
#constant2
RELAY_GPx = 15
#variables
relay = machine.Pin(RELAY_GPx, machine.Pin.OUT)
#main loop
while True:
    relay.value(1)
    utime.sleep(5)
    relay.value(0)
    utime.sleep(1)

Hopefully, that provides enough details to get you started.

A little trouble with Thonny on the Raspberry Pi

The next thing I thought I would try with the Pico is have an autorun program that starts as soon as you plug the device in. It is a pretty straightforward process — you just name the program main.py.

Sure enough, after saving the file, I plug in the device and the program starts running. In my case, it started the servo moving as defined. I then encountered a real problem with Thonny on a Raspberry Pi there was no way to make the program stop. I tried all kinds of things to interrupt the program flow and none of them worked.

Finally, I hooked the Pico up to a Windows machine running Thonny and it was able to stop the program fine. Actually, it works under Windows a bit better than fine, so I think I’ll be doing most of my development under Windows now. I also noticed when Thonny started on my Raspberry Pi, my Bluetooth keyboard stopped working — how frustrating.