The subtitle is appropriate; the author's knowledge about the WEMOS W600-PICO development board and the MicroPython language is at best superficial. The proof is in the fact that all the example scripts found below do one thing only, flash the on-board LED. More telling, in my first post on the W600-PICO, the default MicroPython firmware as delivered from the Lolin store was used and not updated. Unfortunately, version 1.10 was already quite old and proved incompatible with many examples available on the Web. Thankfully, I was informed that a fork of the current version of MicroPython was available for the W60x microcontrollers and it was kindly suggested it would be a good thing to update everything. This new post is an attempt to do this. The older post will remain available just in case someone wants to play with the W600-PICO without updating the firmware, although I can't think of any good reason to do that. Furthermore, I do not think there is anything to be gained by reading the first post; this new post is self-contained.
Table of Contents
- The W600-PICO Development Board
- MicroPython vs Python
- The Default MicroPython Firmware
- Flashing the MicroPython Firmware
- Restoring the MicroPython Firmware
- The Read-eval-print Loop (REPL)
- Quick but Dirty Wi-Fi Connection
- Using the FTP Server
- Using Thonny
- Blinking the LED Again
- Running the
blink
Script - Blinking the LED, Again and Again
- Monitoring a Button
- A Custom
boot.py
- Simple Web Server
- Controling an LED with MQTT
- A Wi-Fi Switch?
- More?
The W600-PICO Development Board
Is it too obvious to say that the W600-PICO dev board by WEMOS is based on the Winner Micro W600 microcontroller? The controller is an 80 Mhz 32-bit ARM M3 with 288 KiB of RAM and 1 MiB of integrated flash memory. Presenting itself as a cheaper alternative to the Espressif ESP8266 or rather the ESP8285 given its integrated flash memory, it has 2.4 GHz Wi-Fi (802.11b/g/n) wireless connectivity. It also has multiple interfaces: GPIO, PWM, SDIO, UART, I²C, SPI, RTC and so on. Here is the pinout of the W600-PICO based on the description in the WEMOS documentation.
As can be seen, this is a relatively simple device, with a PCB antenna, an LED, a 40 MHz crystal, the microcontroller, a 3.3 volts LDO regulator, a USB to serial chip, a microUSB connector, a reset push button plus some passive components (see the schematic for details). The microcontroller is labelled W600-B800. According to the datasheet, this identifies a revision B chip with 1 MiByte of integrated flash memory.
When boards based on the W600 appeared around 2018, they garnered some interest for a year or so which then waned considerably as far as I can make out. Now, contrary to the ESP8266 (or other products from Espressif), there is only a small community coalesced around this chip creating a classic chicken and egg scenario. Hobbyists are less attracted to the device because few hobbyists are using it. Consequently in my first look at the board, I relied mostly on two, rather old, sources for information about the W600-PICO board and another two sources for general information about the W600 microcontroller.
- Get started with MicroPython [W600 series] by WEMOS the maker of the W600-PICO.
- Microcontroller Monday - Wemos W600 Pico by Les Pounder.
- W60X MicroPython User Guide V0.3 by the Beijing Winner Microelectronics Co.
- Winner Micro W600 Series (Wi‑Fi Chips) at CHIP.HAUS
The first two references assume that development will be done with MicroPython which is installed at the factory. The pre-installed version of the language is a Jan 25, 2019, release of a Winner Micro fork of the version 1.10 MicroPython implementation. About 3 months later, wdyichen made a "pull" request to the MicroPython team asking that the Micro Winner W600 be added to the supported ports. From what I can gather, nothing came of that because of the lack of interest in the W600 at the time and the difficulty in implementing TLS in 1 MB of memory. Consequently, the pre-installed version of MicroPython has fallen behind the current 1.19 version of the language. In my first look at the W600-PICO, I used the original firmware which proved somewhat complicated. The good news is that Robert Hammelrath (robert-hh) has continued development of the wdyichen fork with the help of other and actively maintains a MicroPython port to the W60X. Initially I thought it would be necessary to install the toolchain in order to compile the firmware something which I was not interested in doing. However LexxM3 recently informed me that Robert Hammelrath has released and maintains image files of various ports of the current MicroPython version in his Shared-Stuff repository. How to install that version of the firmware will be explained below.
MicroPython vs Python
MicroPython is an implementation of Python 3 designed to run microcontrollers. The project was created by Damien George starting in 2013 and build around an "official" reference hardware platform, the pyboard which contains an ARM Cortex M based microcontroller. New versions of the pyboard are readily available. Over the years, MicroPython has been officially ported to other development boards based on the ARM architecture and others such as Xtensa (ESP8286, ESP32). There are many other "unofficial" ports and forks, including a fork for the W600-PICO by Robert Hammelrath and others (see Awesome MicroPython).
Python is a computer language designed to run on top of an operating system such as Windows or Linux. It is the language used most often used for programs meant to run on the Raspberry Pi OS. Compared to desktops and Raspberry Pi models, most microcontrollers have very limited resources compared to a desktop computer and do not run an operating system. Instead, their firmware controls the hardware directly. Ports, such as the one for the ESP32, designed to run as tasks in FreeRTOS are notable exceptions. No matter which port of MicroPython is used, the firmware provides a minimal kernel or operating system which includes a file system and standard modules. These are precompiled Python scripts included as frozen .mpy
bytecode in the firmware itself.
The MicroPython file system is very important because the microcontroller runs Python scripts saved as text files within that system. Furthermore, it is possible to leverage other libraries or modules by downloading them into the file system. By convention, MicroPython executes two scripts in the root of the file system (more accurately in the /flash
directory), boot.py
and then main.py
in that order. Here is my mental representation of the flash memory of the W600-PICO with the latest version of MicroPython.
For those familiar with Python, the frozen module names that start with a "u" prefix (meant to represent "µ" or "micro") are MicryPython versions of the standard Python module. Just to confuse things, the documentation refers to these modules without the prefix.
Instead of uploading firmware which is a binary file containing compiled C/C++ code, to an Arduino board, the W600-PICO is programmed by uploading a new main.py
Python script and perhaps a new boot.py
script plus any supplementary scripts and libraries to the board's file system. Just how to go about this is investigated in the rest of this post through a few examples. However, it is best to update the on-board MicroPython to the latest available version.
The Default MicroPython Firmware
Before anything else, let's communicate with the development board to find out, at the very least, which version of MicroPython was installed at the factory. Start monitoring the device manager and then plug a USB cable from the Linux system to the W600-PICO. Make sure that it is a full power and data cable because we need a serial link between the device and the computer.
Press the CtrlC key combination to close the monitor. Clearly, a USB device attached to ttyUSB0
was added. That clever way of finding the device assigned to the USB-serial converter on the board was suggested by Les Pounder. The display diagnostic messages command, dmseg
could just as easily be used.
Note that the first command clear the log so that the second command will only show additional messages when the USB cable is connected. The udev
and dmesg
examples were run months apart which explains why the usb
device number is not the same.
Alternatively, serial devices can be found in the usual fashion. The onboard USB to serial converter is a CH340 and these usually show up in the list of connected USB devices and as a ttyUSBx
device. Sometimes, microcontrollers show up as a ttyACMx
device, so let's check both.
I used cu
from the venerable uucp
package as a terminal, others may prefer minicom
, screen
and so on. Once the serial connection is established, I was running in the MicroPyton read-eval-print loop (REPL).
Do not worry about the errors. As far as I can make out, the file system on the W600-PICO contains a number of empty subdirectories and three files: boot.py
and main.py
that are filled with 0xFFs (hence the isn't defined
errors) and an empty easyw600.py
. What is important is that communication was possible and that we have confirmation that the default MicroPython firmware is very old. If you want to continue with this older version of the interpreter, then go back to the first post about this board. My suggestion is to avoid my mistake and to update to a newer version immediately as explained next.
Flashing the MicroPython Firmware
Start by installing w600tool by Volodymyr Shymanskyy (vshymanskyy) which is the firmware flashing tool. It is a Python script with some prerequisites. As usual in such a case I prefer installing the tool in a virtual environment. Below I use my bash script from Python 3 virtual environments, but there are other ways of implementing this same idea.
- Create a virtual environment.
michel@hp:~$ mkvenv w600tool creating virtual environment /home/michel/w600tool updating virtual environment /home/michel/w600tool
- Enable the virtual environment and install prerequisites.
michel@hp:~$ ve w600tool (w600tool) michel@hp:~$ cd w600tool/ (w600tool) michel@hp:~/w600tool$ pip install pyserial PyPrind xmodem Collecting pyserial Using cached pyserial-3.5-py2.py3-none-any.whl (90 kB) Collecting PyPrind Downloading PyPrind-2.11.3-py2.py3-none-any.whl (8.4 kB) Collecting xmodem Downloading xmodem-0.4.6.tar.gz (32 kB) Preparing metadata (setup.py) ... done Building wheels for collected packages: xmodem Building wheel for xmodem (setup.py) ... done Created wheel for xmodem: filename=xmodem-0.4.6-py3-none-any.whl size=34564 sha256=9bf5b6a98f495bba128bc93fd6865e113b12b4aeaa78aff340b3a41692e95768 Stored in directory: /home/michel/.cache/pip/wheels/8a/46/14/833413574281b7009c9180fce7c595a7cc1b538e43fcd8b7e7 Successfully built xmodem Installing collected packages: xmodem, pyserial, PyPrind Successfully installed PyPrind-2.11.3 pyserial-3.5 xmodem-0.4.6
- Download the script from the GitHub repository.
(w600tool) michel@hp:~/w600tool$ wget https://raw.githubusercontent.com/vshymanskyy/w600tool/master/w600tool.py ... 2022-04-21 17:08:44 (6,11 MB/s) - «w600tool.py» enregistré [6441/6441]
- Make the script executable.
(w600tool) michel@hp:~/w600tool$ chmod +x w600tool.py (w600tool) michel@hp:~/w600tool$ ls -l w6* -rwxrwxr-x 1 michel michel 6441 avr 21 17:08 w600tool.py
- Check that the script can reach the W600-PICO.
(w600tool) michel@hp:~/w600tool$ ./w600tool.py -p /dev/ttyUSB0 -b 115200 --get-mac Opening device: /dev/ttyUSB0 MAC: 286DCD2C7E89
- Obtain the latest Micropython firmware for the board from Robert Hammelrath. Currently the repository contains two versions (
wm_w600_lfs.fls
and (wm_w600_lfs_threading.fls
) while theREADME
file talks of four versions. I used the non-threading version, multithreading support remains "highly experimental".
michel@hp:~/w600tool$ wget https://github.com/robert-hh/Shared-Stuff/raw/master/wm_w600_lfs.fls --2022-12-15 15:28:24-- https://github.com/robert-hh/Shared-Stuff/raw/master/wm_w600_lfs.fls Résolution de github.com (github.com)… 140.82.113.4 Connexion à github.com (github.com)|140.82.113.4|:443… connecté. requête HTTP transmise, en attente de la réponse… 302 Found Emplacement : https://raw.githubusercontent.com/robert-hh/Shared-Stuff/master/wm_w600_lfs.fls [suivant] --2022-12-15 15:28:24-- https://raw.githubusercontent.com/robert-hh/Shared-Stuff/master/wm_w600_lfs.fls Résolution de raw.githubusercontent.com (raw.githubusercontent.com)… 185.199.111.133, 185.199.109.133, 185.199.110.133, ... Connexion à raw.githubusercontent.com (raw.githubusercontent.com)|185.199.111.133|:443… connecté. requête HTTP transmise, en attente de la réponse… 200 OK Taille : 608660 (594K) [application/octet-stream] Enregistre : «wm_w600_lfs.fls» wm_w600_lfs.fls 100%[===================>] 594,39K --.-KB/s ds 0,1s 2022-12-15 15:28:24 (4,42 MB/s) - «wm_w600_lfs.fls» enregistré [608660/608660] - Upload the firmware to the board.
(w600tool) michel@hp:~/w600tool$ ./w600tool.py -p /dev/ttyUSB0 -b 115200 -e --upload-baud 115200 -u wm_w600_lfs.fls Opening device: /dev/ttyUSB0 Erasing secboot Erasing image Uploading wm_w600.fls 0% [══════════════════════════════] 100% | ETA: 23:59:59 Total time elapsed: 00:00:55 Reset board to run user code... (w600tool) michel@hp:~/w600tool$It may happen that it will be necessary to reset the board. A prompt will appear in that case.
(w600tool) michel@hp:~/w600tool$ ./w600tool.py -p /dev/ttyUSB0 -b 115200 -e --upload-baud 115200 -u wm_w600_lfs.fls Opening device: /dev/ttyUSB0 Push reset button to enter bootloader... - Check that the newer firmware was uploaded.
Press the reset (RST) button and then open a serial connection with a terminal program.(w600tool) michel@hp:~/w600tool$ cu -l /dev/ttyUSB0 -s 115200 Connected. CCCCCCCCCCCCCCC../../lib/littlefsNo Wifi setting found WinnerMicro W600 MicroPython v1.19.1-784-g913e397b1-dirty on 2022-12-15; WinnerMicro module with W600 Type "help()" for more information.The opening screen may be cleaner in subsequent connections as shown next.
(w600tool) michel@hp:~/w600tool$ cu -l /dev/ttyUSB0 -s 115200 Connected. No Wifi setting found WinnerMicro W600 MicroPython v1.19.1-784-g913e397b1-dirty on 2022-12-15; WinnerMicro module with W600 Type "help()" for more information. >>>
Restoring the MicroPython Firmware
Rather clumsily, I have often put the W600-PICO into a tight loop where it seemed impossible to get control back. At various times the situation seemed hopeless, and I had to erase the flash memory and upload the MicroPython firmware. Luckily, this is simple to do once the flashing tool has been installed as explained above. It is not even necessary to enable the virtual environment to execute the flash tool. All that is needed to execute the W600 tool is to launch the correct Python interpreter found the virtual environment. Here is a little test of what I mean.
Using that fact, I wrote the following short little bash script to avoid having to type that very long command.
That script, named flashW600
, made executable, and stored in the search path, provides a simple means to flash the MicroPython firmware. Of course, it may be necessary to adjust the serial device name and the name of the binary file containing MicroPython. Again, a prompt will be displayed if it necessary to reset the PICO-W600. With that knowledge, it is now safe to embark on an exploration of the board confident that it is possible to recover from any mistake.
The Read-eval-print Loop (REPL)
This will be a very short introduction to the The MicroPython Interactive Interpreter Mode (aka REPL) which has already been encountered. Basically, it is a command-line interface provided by the flashed MicroPython firmware on the W600-PICO. As soon as a serial connection with the board is made, the >>> prompt is available and accepts commands. Interactive REPL sessions will be shown with a turquoise background colour, but they are simply terminal sessions.
As a starting point, let's look at the two suggested examples displayed by the help
command.
Controlling the Built In LED
Let's turn on the built-in LED following the suggestions displayed in the help()
screen. Note that we import only the subset of the machine
module needed for digital input/output.
The help(Pin)
command displays information about the functions and constants of the Pin
object. The next command creates an instance of the Pin
object that will be used to access the built-in LED. The LED is connected to bit 0 of I/O Port A, labelled PA0
on the W600-PICO. That pin must be a digital output in order to turn the LED on or off. The third command turns the LED on by setting the output pin PA_00 low (i.e. to 0 volts).
The LED connections taken from the board's schematic shows why a "reverse" logic must be used to turn the LED on. If PA0 is set to HIGH
then both the anode and cathode of the LED are at the same potential: 3.3 volts. There is no difference, hence no current flows across the diode which will remain off. By setting PA0
to LOW
, a current can flow since the anode is at 3.3 volts and the cathode is at 0 volts.
Instead of explicitly writing a 0
to the output pin, the off
function can be used. So LED.off() could have been used instead of LED.value(0) to turn the LED on. By the same token, the last command, LED.on() is the same as LED.value(1) and it turns the LED off.
Let's put these functions into a for
loop. Here is a MicroPython script that would flash the built in led on for 2 tenths of a second and the off for 8 tenths of a second 20 times.
Trigger warning: the following statement may offend some, but it is just a harmless opinion. Remember, Python uses an archaic syntax where all members of a compound statements must be indented and all begin on the same column; it’s reminiscent of column alignment on Fortran punch cards in the 1970s and earlier. The Auto-indent feature of the REPL makes this somewhat simpler. As soon as a line is terminated with a colon (:), the REPL indents all subsequent lines by four spaces.
Since Pin
was already imported and LED
already defined, there is no need to reenter these commands. To exit the auto-indent mode, press the Backspace
at the start of a blank indented line. Press Enter
again to execute the for
loop. Once the loop is completed, the REPL prompt >>> is displayed again. Instead of using the Backspace
, the Return
key can be pressed twice. So in that case, it will be necessary to press Return
four times after time.sleep(0.8)
to execute the loop.
It is also possible to flash the LED continuously, by creating a never-ending loop. In that case, to get back to the REPL, the board must be reset which can be done by entering a Ctrl+C key combination.
If this is all very new, the reader may want to read the Adafruit Blink LED tutorial.
Connecting to a Wi-Fi Network
One of the valuable characteristics of the W600 is its TCP stack over Wi-Fi. It is quite easy to connect to a Wi-Fi hotspot with the network
module. Let's follow the procedure outlined in the REPL help page.
The ifconfig()
function returns a tuple
containing four elements: (ip, subnet, gateway, dns)
which described the network connection. Note the suggestion to wait a few seconds. If one types quickly and checks if the Wi-Fi connection is established immediately after issuing the connect
command, the result will be false.
If the following list of statements is pasted into the REPL,
then it is guaranteed that the connection will appear to fail. However checking after a few seconds will show that the board is connected to the Wi-Fi network.
Experimentation has shown that the time required to connect to a Wi-Fi network can vary considerably and it is necessary to allow for that possibility. Once connected to my Wi-Fi router, the PICO-W600 is listed among other connected devices but its host name, uart-wifi
, is rather cryptic.
As seen from the output of the ifconfig()
command or from the router list of connected devices, the dynamically assigned IP address of the board was 192.168.1.146. It uses 192.168.1.1 as a gateway (that's the IP address of the Wi-Fi router) and it obtained a DNS server IP address of '192.168.1.1'. ping
will confirm that the board is connected to the network and nmap
will show additional information about the board including the MAC address of the Wi-Fi radio.
Note how nmap
confirms that no TCP port from 0 to 1023 is open.
Starting The FTP Server
Let's go further and start an FTP server on the W600-PICO. With such a server in place, it will be possible to copy files to or from the board, in other words, it will be possible to modify the firmware. Looking at the list of modules, it may not be obvious that there is an FTP server, but it is present in the w600
module which will therefore have to be imported. So after connecting to the Wi-Fi network as explained in the previous subsection, import the w600
module and start the server.
Of course, a different user name and password should be used. It is not a good idea to run a server that has a dynamically assigned IP address. The network.WLAN
object (instantiated as sta
in this example) contains a configuration function that can be used to set a static IP address.
As seen before, the same ifconfig()
command, but without parameters, displays the current IP address, subnet mask, gateway and DNS server IP.
Quick but Dirty Wi-Fi Connection
In his recent MicroPython port for the W600, Robert Hammelrath added a quick and convenient means of connecting to a Wi-Fi network and starting an FTP server. Once the startup code is completed but just before the boot.py
and then main.py
scripts are executed, a frozen module called _boot.py
is executed. It looks for a file named wifi_config.py
which should contain the name of the wireless network and its password. If found, then an attempt is made to connect to the network and to start an FTP server. If that fails then the No Wifi setting found
message is printed to the console. On failing or not, normal execution is resumed.
It may look as there is an egg and chicken conundrum: isn't a running FTP server needed to copy the credentials file to the W600-PICO which is in turn needed to connect to the Wi-Fi network and start the server? The answer is no. There are other ways of copying files using the serial link with the board as will be seen later. One of these can be done within the REPL. First I used a text editor on the desktop to create the following text file.
file = open('wifi_config.py', 'w') file.write('WIFI_SSID = "rosebud"' + '\r\n') file.write('WIFI_PASSWD = "87654321"' + '\r\n') file.close()These four MicroPython commands create a file in write mode, write two lines of text to it and then close it. Of course the Wi-Fi name or SSID and its password will have to be adapted in actual use. Then I connected to the board with a serial link and then copied and pasted the short Python script to the REPL.
The wifi_config.py
file was created alongside the boot.py
and main.py
files in the /flash
directory where _boot.py
will find it. Pressing the reset button on the board for a "hard" reset or pressing the CtrlD combination at the REPL prompt for a "soft" reset, we can see the typical outcome if everything goes well.
In my humble opinion using this method to connect to the Wi-Fi network should be avoided except in the initial testing of new firmware when a serial connection to the board is in place. Close down the Wi-Fi network and start (or reset) the W600-PICO. The board will hang after displaying the connecting...
message. Restart the network and even if the credentials in the wifi_config.py
file are correct it will not connect. I think this scenario is pretty much guaranteed to occur when there's a power blackout because when power returns the microcontroller will boot much faster than a Wi-Fi router. The W600-PICO will be trapped in the connection loop and the only way to get out of it will require physical access to the board to either press the reset button or to turn off power to the board for a short while. There is another reason to avoid using the _boot.py
connection. An FTP server with a dynamic IP address is not very practical although it is not impossible to find the assigned IP address as shown above. This problem is easily fixed in the boot.py
script as will be seen in a later section.
Using the FTP Server
Using these credentials I was able to log into the FTP server with the command line ftp
client that I assume was installed by default on my Linux Mint desktop.
I don't suppose many are familiar with the ftp
utility. I had to consult its help
command to bring back hazy memories of how to start an FTP session (open
) and list files (ls
) as shown above. To illustrate other basic commands, here is an FTP session with a board that contained directories and more files than what was shown above.
Many more would probably prefer to log into the FTP server with Filezilla as described in section 5 of the MicroPython User Guide by Winner Micro. Here is how to "quickconnected" with the server, filing in the Host, Username and Password fields.
Note the warning about the absence of TLS encryption and the use of the default FTP port (i.e. port 21). On connecting Filezilla displayed a popup dialog warning that the password and files would be sent in cleartext. There was also a checkbox to allow insecure connections to this server in the future.
The server can also be defined in the application's site manager.
Note the selection for the method of encryption, again there is none. Once the entry is created, connecting to the FTP server is done by selecting the board in the site manager and clicking on the Connect button.
It is also possible to connect to the FTP server with the Caja file manager (and probably others) so that the file system on the W600 shows up as a remote directory. To do this, go to a location as shown below.
Enter the IP address of the board and specify the ftp://
protocol in the Go To:
box as partly shown next.
Enter the user name and password required by the FTP server on the W600 board.
The Remember forever
item is not checked because I shun setting passwords permanently especially for connections that are mostly temporary, but that is a personal choice, of course. As can be seen below, the root directory of the W600 file system is now available as a networked drive.
When I open a script in the remote file system into an editor (done by clicking with the right mouse button and choosing Open with...
) and modify the script, the changes are uploaded to the W600 after closing the editor. Once used to this, I found this approach to be a fluid work environment. The only thing that I could not do with this method was deleting a directory in the remote (W600) file system. This could be done with FileZilla.
Don't forget, setting up the FTP server is more than a programming environment; it is an over-the-air (OTA) mechanism. No need to add libraries and additional code as done with the ESP chips, although it will be necessary to modify the boot.py
script to always enable the server. More on that later.
Using Thonny
Instead of relying on the FTP server, I use Thonny which is billed as a Python IDE for beginners and it has proven very useful when developing a new W600 project. The version in the Debian repository is rather old. The easiest way to get the current version of the IDE is to set up a virtual environment and then use pip
to install the program.
The last command launches the IDE. Note that we are in the thonny
virtual environment which must be invoked whenever the program is started. A one time screen to set initial settings is shown.
I accepted the defaut settings and pressed the Let's go! screen to continue with a minimal setup. Go to Options
in the Tools
menu and then select the Interpreter
tab. One could also select Configure interpreter...
in the Run
menu.
Thonny can work with forks of MicroPython for specific devices, but the W600 is not among those. So set the kind of interpreter to MicroPython (generic)
. The IDE must also know which serial port to use to communicate with the W600-PICO. Depending on the situation, letting the IDE try to detect the port works well, but I have to specifically set the port when another serial device is plugged is using /dev/ttyUSB0
. With those two settings, the IDE could be started by pressing the OK button.
The main parts of the IDE are a multi-tab editor and a Shell
window underneath which is the REPL console. In addition there is a typical top-level menu and speed bar with buttons to execute common commands. One can go from the editor to the REPL prompt by clicking in the respective window. Edited files can be saved to the desktop computer or to the MicroPython device. Python scripts (.py
extension) can be loaded from the flash memory of the board into the editor. The editor can also work with files from the desktop, but it can handle more file formats.
I would suggest checking the Files
option in the View
menu. That way it will be easier to manage files. If a dark theme is used on the desktop, and if it is difficult to see which items are checked in the View
menu, then select a UI theme
in the Theme & Font
tab of the Tools/Options
menu. Here is how have the IDE set up.
There is a odd quirk when starting the IDE. It reports that No Wifi setting found
even if the wifi_config.py
file is present. And checking in the REPL, it is obvious that the module is connected to the Wi-Fi network.
I verified with a ping
from the desktop that the board was indeed connected. Note that I had to search in the list of connected devices on the Wi-Fi router to find the IP of the board.
I will not go into more details about using Thonny; there are many guides on the Internet. There are other options. Some mention uPyCraft by DFRobot which does look quite similar to Thonny. While not even mentioned in the introduction, it seems that it is available for Linux assuming the prerequisites are in place (see the source code README.md file). Mu, a simple Python editor for beginner programmers appears to be a similar project worth investigating.
Blinking the LED Again
Let's blink the built-in LED with a delay function as already done in the REPL. This time, though, it is done as an obligatory blink.py
script where timing is done with a delay function.
It was cute on my part to rename the sleep_ms
to delay
which is the name of the similar function in Arduino. Beside that, the script is pretty straight forward. We have already seen that the on-board blue LED is connected to the I/O pin labelled PA0 and that the pin must be grounded to turn on the LED. The only other thing worth mentioning is that the blinking does not go on indefinitely so as to continue on after a while.
Pin
constructor required three variables. So the Led
object was instantiated with the following statement in the older script.
In version 1.19, an output pin object can be created without the third parameter. If one wants to include the third parameter, then be aware that Pin.PULL_FLOATING
is no longer defined but it can be replaced with Pin.PULL_NONE
.
Running the blink
Script
The blink
script is ready to go, but how can it be executed? First the file must be uploaded to the W600-PICO file system. We have already seen that there are a number of ways of doing that: using FTP (either the command line ftp
utility or the more ameanable FileZilla
or integration of the W600 file system as a remote drive) or using Thonny (or its equivalent) with its file management functionality. Copy blink.py
alongside boot.py
and main.py
. Do not forget the .py
extension. MicroPython is not a Linux environment, the extension is mandatory.
Remember that MicroPython executes, in order, the boot.py
and main.py
scripts when the microcontroller boots. To run blink.py
automatically when the board is powered up, the by default empty main.py
script could be deleted and blink.py
could be renamed main.py
. Indeed one could have uploaded blink.py
as main.py
to achieve the same goal. Managing files (renaming and deleting them and so on) is easy with FileZilla or with the desktop file manager if the board's file system is mounted as a remote drive. It is not quite straight forward with Thonny or if in the REPL. One thing that can be done to help in the latter two cases is to import upysh.py
. I think the name is an acronym of MicroPython shell.
Now the board has to be restarted to execute the script. A soft reset can be done from the REPL with the CtrlD keyboard shortcut. Alternatively, pressing the RST button on the board will initiate a hard reset.
Of course, the on-board LED should turn on and off in rhythm to what is shown in the terminal.
It makes sense to modify the main.py
script as done above when developing "production type" firmware that will be run permanently and automatically by the microcontroller board. For the purpose of this tutorial, I suggest that another approach be used. Instead of copying blink.py
over main.py
, modify the latter so that its content is a single line importing the blink
module. Now there should be 3 files plus a directory on the board.
The main.py
script should contain the following line importing blink
(without the .py extension).
Restart the board, and the script will be executed. Since there are many modules in this tutorial, it will be easier to modify the main.py
script instead of playing around with renaming files. By the way this is the content of main.py
in the GitHub repository linked to this tutorial.
The demo.py
script imports a number of modules one after the other.
As can be seen there are a lot more blinkies in the set of demonstration scripts. That's because this first demonstration program can do only one thing, and blinking an LED is not very useful.
Blinking the LED, Again and Again
The second blink program avoids the time wasting delay function by using a hardware timer to toggle the state of the LED.
Is it presumptuous to say that this is typical cooperative multitasking à la Arduino; the while True:
at the end is the equivalent of the loop()
function. What happens if some of the other tasks performed in the loop do not play nice? The second while True:
shows that the LED will not blink at a fixed frequency if some tasks take longer to execute than the blink period. A classic way of handling this problem is to use a hardware timer as shown in the next script
No matter the length of time taken by any task performed in the loop, the LED blinks at its specified frequency. At the specified period, the hardware timer, timer1
here, interrupts the flow of the program and performs its callback function and once that is completed, the program resumes where it was interrupted by the timer. Unfortunately, this is not a mechanism that can always be used to achieve concurrency. As will be shown later, it seems that hardware timers have a lower priority than network functions meaning their functioning is suspended during blocking socket operations.
An idea popped in my head at the last minute. Could hardware PWM (pulse width modulation) be used to blink the LED? Turns out the answer is yes.
It is harder to obtain the desired blinking rate with PWM. Just noodling about with the script seems to confirm that a period greater than 200 ms would be difficult to achieve, but this will require delving into the documentation to confirm the actual minimum frequency available. Interestingly, PWM is not suspended during blocking socket operations.
Monitoring a Button
This will be a first example where a third party module is used. In this case, it is MicroPython-Button by Ubi de Feo (udidefeo) which works primarly for temporary contact push buttons. This is a very good library because it takes care of debouncing without using timers and consequently there is no need to define debounce parameters. Normally open or normally closed buttons can be used. For this test, I decided to use a normally open push button with one pole connected to the PA1 button (as labelled on the board) and the other to 3.3 volts (the maximum that should ever be applied to an I/O pin). The I/O pin cannot be left floating which is what would happen when the push button is in its normal open state, so a "pull up" resistor (could be just about any value from 4.7K ohms to 100K or even 1000K) could be added to the circuit as shown at the top of the image to the right. However this is not necessary as the W600 does have internal pull up resistors. If I had wanted the I/O pin to be normally at 0 volts, then the wiring shown at the bottom could have been used, but in that case the external pull down resistor would have been necessary as the W600 does not have internal pull down resistors on its I/O pins (see a MicroPython forum reply by Robert Hammelrath (Roberthh).
The button object constructor has three positional arguments.
- An I/O pin number. This must be the pin constant as defined in
machine.Pin
. The alias for the pin labelledPA1
on the board isPin.PA_01
. Alternatively1
could be used as it is the literal pin number used by MicroPython, which is probably an index into a table of pin definitions. - The I/O pin rest state when the button is not pressed, which is also the state in which the button should be when the object is created. Set to
True
if the I/O pin is connected to Vcc by an internal or external pull up resistor. Set toFalse
if the I/O pin is connected to ground via an external pull down resistor. - The callback function invoked when the button changes state.
There are also two keyword arguments.
-
internal_pullup
: ifTrue
an internal pullup resistor will be activated and the rest state is set toFalse
(i.e.0
) no matter the value of the rest state positional argument. -
internal_pulldown
: ifTrue
an internal pulldown resistor will be activated the rest state is set toTrue
(i.e.1
) no matter the value of the rest state positional argument. Careful, the W600 does not have internal pull down resistors.
If either internal_pulldown
or internal_pullup
is set to False
, it is ignored. If both internal_pullup
and internal_pulldown
are set to True
the latter (internal_pulldown
) takes precedence. If neither internal_pullup
and internal_pulldown
are specified, then no internal pull resistor is activated and the I/0 pin must be pulled to Vcc or ground as appropriate by an external resistor and the positional rest state argument is used.
The callback has the following signature.
The button
parameter will be the MicroPython pin number of the I/O pin connected to the button. In our example, the button
parameter will be set to Pin.PA_01 = 1
. The event
parameter will be either "pressed" or "released" when the push button is first pressed and then when it is released and returns to its normal position.
MicroPython has recently added a package management module called mip
which is similar to Python's pip
. Unfortunately, I have not been able to use it with the W600 seemingly because of the lack of SSL/TLS support. Consequently, the library was installed manually as source code by first downloading to the desktop which can be done in a number of ways such as cloning the repository or downloading a zip archive of the repository. I chose to create a lib
subdirectory in the directory that contains all the scripts for this project on my desktop. The Button.py
MicroPython script could then be downloaded directly into that empty subdirectory.
Copy the Button.py
script into a lib
directory in the W600 file system. Since I downloaded the script into a lib
directory of my desktop, I just copied the desktop lib
directory to the W600 file system and that took care of creating the directory on the board. This can be done in Thonny or with ftp or FileZilla etc.
lib/
directory, consequently Button.py
was saved in the same directory as boot.py
and main.py
. This can also be done in version 1.19.
Here is a short script that uses that library to report when a normally open button is pressed and released and track the number of times it has been released.
The button change callback function reports both button pressed and button released events, and it keeps a count of the number of releases.
Note how the button library is "pumped" with the button_one.update()
method at the start of each iteration of the final loop. This may create a problem similar to the one encountered in blink2.py
. Try running the script setting OTHER_TIME
to 250 or 500 ms. Response to the button presses will feel sluggish. In that case, one solution is to call on Button.update()
) at regular, constant, and short enough intervals with a timer and thus guarantee that the button remains responsive no matter what the other task do. This is done in pollbutton2.py
.
Just like in the blink3.py
script, a hardware timer is used to feed the button "pump" button.update()
. As explained before, this means that button events will not be reported during blocking socket operations.
Anouar way to monitor a digital input is to enable hardware interrupts. Here is a rudimentary example.
It is ironic that a hardware interrupt is used to respond as quickly as possible to the button press or release, but polling is used to handle the event. We are usually told that interrupt service routines (ISR) should be very short and that it may be necessary to avoid calling on other functions that could not be reentrant. As an example of the problem of reentrancy, assume that pinISR
contains the code print('In button ISR')
. Assume that the button is pressed just as print('Current value: {}.somevar))
, somewhere else in the code, is about to print 'v'. Finally, assume that the print function first builds up a complete string of what it must print, "Current value: 32.8943"
, and puts that string in a fixed static buffer. In that case, the content of the buffer will be changed to 'In button ISR'
by the interrupt's print
statement. When the original print statement resumes after the ISR is completed, its buffer will have been mangled and the final result will be something like the following.
That would be a good result. If print
calculated the length of the final string to print instead of testing for a terminal 0x00, things could go very badly indeed.
I have not looked at reentrancy in MicroPython in general and in the W60x port in particular. Note that reentrancy is only one problem linked to interrupts, see the full document cited above.
A Custom boot.py
So far, nothing has required access to the network. But the upcoming scripts will blink the LED remotely. So this is a good time to reconsider the whole question of establishing a connection with the local Wi-Fi network. Like many, I decided that this was the best place to connect to the Wi-Fi network and to start the FTP server in a safe way would be in the boot.py
script that is automatically executed when the board is booted. The default boot.py
script does very little.
Instead of modifying the script itself, I wrote a customboot.py
script which is saved in the root directory beside the boot.py
module or in the lib/
directory. In a real setting I would then import that custom boot in boot.py
.
Again for the purpose of this tutorial, I did not do that and instead customboot
is explicitly imported in demo.py
or in a few other scripts that are not included in the demonstration script. Of course wifi_config.py
should be removed so that the frozen module _boot.py
does not try to connect to the network and possibly hang the microcontroller. Just like _boot.py
, this script reads the network parameters from a file, named secrets.py
. Here is an example, secrets_templates.py
, that should be modified as needed and saved as secrets.py
.
When customboot
cannot connect to the Wi-Fi network, it goes into an endless loop.
A successful connection to the Wi-Fi network is the only way to break out of the loop. Hopefully, the access point will be reachable long enough to transfer corrected files via the FTP server to the board so that in the following restart it will connect to the Wi-Fi network. Here is a dialog showing how I updated the secrets.py
file after the Wi-Fi network password had been changed and customboot
could no longer connect. I waited until the access point came up, connected to w600AP
network with my desktop machine and then started the ftp
utility.
Of course it would be easier to do this with FileZilla or another similar FTP client. Then I just waited until the board reset itself and, if the upload was successful, the connection to the Wi-Fi network was established. Here is customboot.py
in action when everything goes smoothly.
_boot.py
module continues to complain that it cannot find wifi_config.py
. When a connection to the Wi-Fi network is made, an FTP server is also created before the rest of the firmware (main.py
) script is executed. That FTP server allows for easy over-the-air updating of the board's firmware. Because most OTA updates will be ignored unless the board is restarted, it may be a good idea to include a periodic system reset in main.py
or somewhere else in the rest of the firmware.
Here is the source of customboot.py
.
Credit and thanks go to G21-Goose who creates an access point when the Wi-Fi network cannot be reached in the boot.py of his W600_Smart_Plug project.
Simple Web Server
We will create a simple web server that returns one of two pages as shown below. The first is the default page which can be used to control the state of the LED on the board by clicking on the
button. The second page displays a 404 error when an unknown page URL is entered directly in the navigator's address bar and a button to return to the default page.
The Quit button on the first page is a bit unusual. Click on it to close down the web server and terminate the Python script itself.
The code is spread over two modules. The webresponses.py
script contains the function HttpPage(is404, Status)
that builds the two pages which share a common <head>
section with shared CSS styles and a common title. It also returns the response header for each type of page. The Status
parameter is use to display the LED status (ON or OFF) above the button.
The web server itself is just a reworked example found in many sites on the Web such as RANDOM NERD TUTORIALS. Most of these examples are for the ESP8266 or ESP32, but they work without modification with the W600-PICO.
Note that the script begins by testing that the board is connected to the network because otherwise there is no point in trying to start a web server. The first step is to create an IPv4 Internet socket of type stream meaning a TCP connection (see man socket on a Linux system or the socket -- Linux manual page). After that the socket is bound to IP address 0.0.0.0
on port 80. That means the socket will accept incoming requests from any IP address on port 80. The third line s.listen(3)
puts an upper limit on the number of requests that can be queued. If the web server receives a fourth request while it is still handling three previous requests, it will return an ECONREFUSED
indication.
Then the script enters an (almost) endless loop. It starts with a blocking function that waits for an HTTP request to come in. When it does, accept
returns the pair (conn, addr)
where conn
is a socket and addr
is its end point IP and port address. From there it's a matter of reading the incoming request, parsing it and acting in accordance with the request. The following shows the output printed to the serial terminal. It is possible to break out of the loop by clicking on the Quit button which will send a <ip</quit
request to the web server.
Comparing that output with the comments and print statements in the source should make the parsing and response logic evident.
There is another version of the script. It exploits a micropyton optimization wherein the socket returned by the socket.accept()
can be used as a read and write stream. Below we only show the while True:
loop of webserver2.py
. Everything else is the same.
This second version cannot be used "as is" in Python scripts.
Controlling an LED with MQTT
In this example, the LED on the W600-PICO will be turned on and off with MQTT messages. To give the impression that this example could be of actual use, the MQTT messages will be generated by a virtual switch in my Domoticz home automation server.
When the light bulb icon is clicked, the virtual device will toggle its state and then send an MQTT message advertising the new state. The script will be subscribed to those messages from Domoticz and set the LED on the board accordingly. No need to worry, it is not necessary to install Domoticz to try this example as will be shown later. On the other hand, an MQTT broker must be available on the local network. I have Moquitto which is installed on a single-board computer (see 3. Installing the mosquitto MQTT Broker in Home Automation System on a Raspberry Pi and The Mosquitto MQTT Broker in Raspberry Pi OS (Bullseye)).
The MicroPython MQTT client module found in the micropython-lib repository can be used with the W60x microcontrollers with just one slight change.
Make sure to comment out the importing of the hexlify function
from ubinascii
. The frozen module by that name in the W60x port of MicroPython does not contain the function. Thankfully, those statements that use hexlify
are for debug purposes and have all been commented out of the library. To install the library, create in folder lib
a subdirectory named umqtt
and then copy the script simple.py
into the newly created directory. It is also necessary to provide information about the MQTT broker. Edit the file named mqttdata_template.py
and save it as mqttdata.py
in the /lib
or root directory of the board.
Here is the module called mqtttest.py
.
Assuming the W600-PICO is connected to the network, that the MQTT broker is running, and that the MQTT parameters specified in the mqttdata.py
configuration file are correct, then the following will be displayed in the serial terminal as the virtual button is activated.
The full MQTT message sent by Domoticz when turning off the LED is quite long.
As stated, one does not have a running instance of Domoticz to send an MQTT message and the message does not have to be that long. The mosquitto_pub
utility, often installed along with an MQTT broker, can be used as shown next.
The first command turns on the LED, the second turns it off.
A Wi-Fi Switch?
By adding a button as done before to this last MQTT example and by connecting another I/O pin to a relay module one can create a Wi-Fi switch that works with Domoticz. It would probably be relatively easy to modify the script to work with any other home automation system that supports the MQTT protocol.
To test this idea, I didn't actually connect a relay to the pin labelled PB12 but replaced it with an external LED that will be turned on when the pin is set high. I was happy to see that the external LED (i.e., the relay) could be toggled on and off with the push button switch and that both the on-board LED and the virtual switch in Domoticz correctly displayed the state of the relay. And, of course, it worked the other way. Clicking on the bulb icon of the Domoticz virtual switch caused the relay and on board LED to change their state. Here is the code.
And here is the serial output of the script.
There's much more to Theo Arend's Tasmota firmware which is what is running on all the Wi-Fi switches in my home automation system. However, all communication between these IoT devices and the home automation software is done using the two above-mentioned MQTT topics. Consequently, it seems reasonable to conclude that the W600-PICO can be used as the basis of a Wi-Fi switch.
More?
To have something even modestly approaching a Wi-Fi switched running Tasmota, I tried to add the web server to wifiswitch.py
but failed. Hints about the problem have already appeared. Run webserver3.py
to see the problem explicitly. The socket.accept()
function which waits for a connection from a remote client in the web server is blocking. That means that nothing happens until a connection is obtained. As the program shows, Timer
objects and interrupts are suspended when accept()
is waiting for a connection. Hours if not days or weeks can elapse between uses of the web interface rendering the Wi-Fi switch useless.
The obvious solution would be to use a non-blocking socket.accept
. According to the latest MicroPython documentation, a time-out can be set on blocking socket operations. However [not] every MicroPython port supports this method. A more portable and generic solution is to use select.poll
object. Unfortunately, I have not had success with either of these two approaches. Is this because of errors on my part, or is it a feature of the W60x port? Perhaps the threading version of the MicroPython for the W60x should be used. What about the use of the asyncio libraries that is often used for asynchronous modules?
While I should investigate this problem in more detail, it has not prevented me from using the W600-PICO in a role for which it is particularly well suited: as a wireless remote button in the TV room. Actually, three buttons are connected to the board as well as two external LEDS. The buttons activate home automation scenes. Whenever a button is pressed, positive feed back is given by a flashing green LED. The red LED flashes when the garage door is open which is quite useful as there is no way of seeing if that is the case from the house. This device has been running for months and is based on the old 1.10 MicroPython initially installed on the W600-PICO. I intend to add a temperature and humidity sensor and, perhaps, a tiny OLED to display the time. There may be another post about the board.
Up-to-date versions of all the scripts shown in this post are available in a GitHub repository: w600_micropython_1_19_examples.