From 81e3b23d742d7d129a56de2fc183efb71b7d9298 Mon Sep 17 00:00:00 2001
From: Dan Mccreary This website and GitHub repository are for sharing resources to teach MicroPython to students in 5th to 12th grades (10-18 years old). The course assumes that either a mentor, teacher or students have access to at least one microcontroller such as the $4 Raspberry Pi Pico or the $10 ESP32. Students should also have access to some low-cost sensors (buttons, potentiometers, ultrasonic distance sensor) and displays such as LEDs or OLED displays. If you are looking for a specific topic, please remember to use the search function in the upper right of the website. The website is best displayed on a wide screen to see the navigation bar on the left although the website also works on the small screens of mobile phones and tablets. You can use the navigation area on the left side panel to navigate to different parts of the website. Here is a high-level overview of the main sections of the site. This part is a high-level overview of what MicroPython is and why is has become the most popular way to do physical computing, program microcontrollers and build robots. We also discuss the different types of microcontrollers available, their price and features and how to purchase them independently or in kits. This part will help you get started programming MicroPython on your microcontroller and learn how to hook up parts on a solderless breadboard. We discuss the need for a desktop Integrated Development Environment (IDE) and how to get started writing simple programs These ten lessons are the foundations for learning MicroPython. They include learning how to blink one or more LEDs, monitor for button presses, fade LEDs in and out using PWM signals, read analog values from potentiometers, read light sensors, turn motors and servos and display rainbow patterns on a NeoPixel strip. Many other labs are variations of these 10 labs. Introduction to Basic MicroPython Examples This section will give you more examples of how to use different types of sensors such as heat sensors, current sensors, rotary encoders, accelerometers, gesture sensors, and magnetic field sensors. Reading Sensors with MicroPython This is our student's favorite part of this site! Once you can make a motor go forward and reverse, you are close to being able to make a robot move. We walk you through the basics of using a simple transistor to control a motor, to using simple motor controllers like the L293D chips. Introduction to Motors and Robots with MicroPython Note that we have many other advanced labs that use our $11 Cytron Maker Pi RP2040 Kits. These incredible boards have everything integrated to build robots with lights and sounds. This section shows you how to use many different types of displays, from simple 7-segment digital displays to complex OLED graphic displays. On the old 2K Arduino controllers these graphics labs used to be hard, but now we have 264K of RAM on the Raspberry Pi RP2040 microcontrollers. Now these labs are easy! Having powerful microcontrollers allows us to generate complex sounds, play tones and even playback recoded sound effects. Introduction to Sound and Music with MicroPython We have now covered all the things you need to build hundreds of projects. This section contains deeper dives into other topics such as how to use the MicroPython Remote ```pmremote`````` tools to automate the loading of software onto your microcontroller. Advanced Topics This section contains detailed steps to use the popular educational kits that are now integrating MicroPython and the RP2040 microcontroller. There are many kits and these lessons contain full working programs to build complex projects like a collision avoidance robot with OLED displays. MicroPython Kits Lastly, we have a large glossary of terms, contact information and references to other websites that might be useful in your projects. Many of our more advanced projects have been moved into separate websites. Here are a few of these sites: Glossary of MicroPython Terms This is an annotated list of other on-line resources to help you learn MicroPython and use microcontrollers. Micropython References - links to other useful sites. If you have suggestions for additional references projects, please let us know! Contact This section includes some advanced topics that might be useful for helping you create and debug MicroPython projects. Topics include: Wokwi ESP32 Simulator for the Raspberry Pi Pico This section includes some advanced topics that might be useful for helping you create and debug MicroPython projects. Topics include: An Interrupt Handler (also called an ISR for Interrupt Service Request) is a special Python function that is called when specific events occur such as a button being pressed. ISRs are the preferred way to detect external events, as opposed to polling methods that are inconsistent and inefficient. However, they are a bit tricky to setup and debug. So a good design should be as simple as possible and avoid using complex features unless you really know you need them. So why are ISRs so important? Let's illustrate this is a story. Imagine you have 10 friends each with a button at their home. In the polling method you would need to drive to each of their houses and ask them \"Is the button get pressed\"? You would have to do this frequently in case the button was pressed and released too quickly. This is a slow and painful process and takes a lot of CPU cycles. An interrupt handler on the other hand has each friend tell you directly if their button has been pressed. The messages are quick and efficient. They don't use a lot of extra CPU power and the results get handled quickly. However, there are specific rules about what we can and can't do within an ISR function. They need to be quick and efficient. We can't wonder off and do crazy things like printing debugging lines within a good ISR. Our job is typically update a global value and finish ASAP. A good ISR should be as efficient as possible. This is our first ISR example. It has several parts: Now if you run this program, you will see that it prints to the Terminal each time the button is pressed and it also tells us how many times the button has been pressed. example output: But if you are careful, you will note something slightly unexpected might happen. I the example above, I actually only pressed the button about 10 times. But the button value is 21! What could be going on here? Could there be a bug in the code? The answer is that buttons are not perfect on/off switches. They are essentially noisy on/off devices that may go through a transition of off/on/off/on each time we press the button. As a switch goes from open to closed, it moves from a stable state, through an unstable transition state and then it finally arrives at a new stable state. This is illustrated in the drawing below. We can reduce this \"noise\" with a small capacitor next to the button. The capacitor will quickly absorb the energy of the button transition and it will \"smooth\" out the spikes. This will give us a more consistent readout of the number of button presses and avoid accidental \"double presses\" that were not intended. However, we can also get a clean signal by using software. The key is when we first detect that a transition may be happening we \"stop listening\" for a short period of time until we are confident that the unstable transition state is over. This is typically around 20 milliseconds, but there may be a few stray signals left. Since we may not have to detect changes more frequently than 5 presses per second, we can go to sleep in our ISR for up to 200 milliseconds. This will give us a nice stable reading from the button. These are general rules but for our breadboard mounted momentary switches, the values are appropriate. Now let's show you the code that does the hard work of debouncing a signal from a button or switch. In this example, our ISR is called The net effect is that the presses variable will ONLY be incremented once, and not multiple times during the transition. Here is the full code: Although there are benefits to the simplicity of the code above, some microcontrollers developers suggest that you simply deactivate the IRQ during the debounce sleep. This makes sense since there is two small calculation of the time differences (a subtraction and a compare operation) that do not need to be performed. The key lines we add are a deactivate of the IRQ, a sleep for 200 milliseconds and a re-enable of the IRQ after the sleep. Both approaches have worked for me and I will let you decide the tradeoffs. The Raspberry Pi Pico has an internal temperature sensor that can be access using We often need to calculate how much time has elapsed since an event occurred. To to this we can use the There are one million microseconds in a single second. The The following example times the sleep function and measures the difference in the number of clock ticks in microseconds between the two events. results: You will note that the difference between the start and end time should be one million microseconds. However, the run-time libraries on the pico have some variability, so you will see the actual time vary by a few microseconds. Most of the time you can use milliseconds to compare time intervals. results: These results are almost always 1000 with an occasional 1001 value. Conda is a powerful tool for building consistent and stable Python environments. These environments include all the Python libraries that you need to be a productive MicroPython developer. Using Conda allows you to keep each of your Python projects cleanly separated. This may not be important on your 2nd or 3rd Python project, but as you do more Python projects you will benefit from isolated environments that each have their own versions of each Python libraries that will not conflict with each other. One other important fact to remember is that if you use a conda virtual environment you should never need to use sudo (root) to install Python libraries. This means your installation scripts are more secure and less likely to introduce security problems and accidentally remove libraries that other projects depend on. To get started, it is best to go directly to the Conda web site and follow the installation instructions for you specific version of your operating system. There are many variations of installation not just for Windows, Mac and Linux, but each version my had different steps required. Once you can open a terminal and type in which returns: Our fist job is to create a desktop environment that allows us to run Python programs that support the MicroPython development process. After this your prompt should now show that you are in the micropython environment. This process may take about five minutes, since all the most current libraries must be downloaded onto your desktop. Once this process finsihes you must remember to deactivate your current conda environment (if you have one and then activate your new micropython environment. MicroPython Pico Forum MicroPython PyCopy In the Thonny tool, open the Terminal. At the REPL prompt type: which returns returns followed by help(modules) returns See MicroPython Real Time Clock The Pico has two I2C hardware controllers. Each controller can talk to multiple IIC devices as long as all the devices communicating on each controller have distinct addresses. Results for both a OLED display on I2C 0 and a time-of-flight sensor on I2C 1 MicroPython.org docs on framebuf returns:
"},{"location":"#section-7-sound-and-music","title":"Section 7: Sound and Music","text":"
"},{"location":"#glossary-of-terms","title":"Glossary of Terms","text":"
"},{"location":"status/#time-of-flight-distance-sensor-display-box","title":"Time of Flight Distance Sensor Display Box","text":"
"},{"location":"status/#pot-display-box","title":"Pot Display Box","text":"
"},{"location":"status/#compass-lab-box","title":"Compass Lab Box","text":"
"},{"location":"advanced-labs/","title":"Advanced Labs","text":"
"},{"location":"advanced-labs/#references","title":"References","text":"
"},{"location":"advanced-labs/02-interrupt-handlers/","title":"Interrupt Handlers in MicroPython","text":""},{"location":"advanced-labs/02-interrupt-handlers/#what-is-an-interrupt-handler","title":"What is an Interrupt Handler?","text":"
# Use an interrupt function count the number of times a button has been pressed\nfrom machine import Pin\nimport micropython\nimport time\n\n# global value\nbutton_pressed_count = 0\n\n# Interrupt Service Routine for Button Pressed Events - with no debounce\ndef button1_pressed(change):\n global button_pressed_count\n button_pressed_count += 1\n\n# we define button1 as being connected to GP14 and to use the internal Pico PULL_DOWN resistor\nbutton1 = Pin(14, Pin.IN, Pin.PULL_DOWN)\n\n# here is how we associate the falling value on the input pin with the callback function\nbutton1.irq(handler=button1_pressed, trigger=Pin.IRQ_FALLING)\n\nbutton_pressed_count_old = 0\nwhile True:\n if button_pressed_count_old != button_pressed_count:\n print('Button 1 value:', button_pressed_count)\n button_pressed_count_old = button_pressed_count\n
Button 1 value: 1\nButton 1 value: 2\nButton 1 value: 3\nButton 1 value: 4\nButton 1 value: 5\nButton 1 value: 6\nButton 1 value: 8\nButton 1 value: 9\nButton 1 value: 10\nButton 1 value: 11\nButton 1 value: 12\nButton 1 value: 13\nButton 1 value: 14\nButton 1 value: 15\nButton 1 value: 16\nButton 1 value: 17\nButton 1 value: 18\nButton 1 value: 19\nButton 1 value: 20\nButton 1 value: 21\n
button_pressed_handler(pin)
. As soon as it is called, it checks the number of milliseconds since it was last called. If the time difference is under 200 milliseconds we are good to go and we update the button_presses global variable. If we are under the 200 millisecond window, we might be in that transition state and we don't do anything.new_time = utime.ticks_ms()\n# if it has been more that 1/5 of a second since the last event, we have a new event\n if (new_time - last_time) > 200: \n button_presses +=1\n last_time = new_time\n
"},{"location":"advanced-labs/02-interrupt-handlers/#isr-with-deactivation","title":"ISR with Deactivation","text":"import utime\nfrom machine import Pin\n\n# Sample Raspberry Pi Pico MicroPython button press example with a debounce delay value of 200ms in the interrupt handler\n\nbutton_presses = 0 # the count of times the button has been pressed\nlast_time = 0 # the last time we pressed the button\n\nbuiltin_led = machine.Pin(25, Pin.OUT)\n# the lower left corner of the Pico has a wire that goes through the buttons upper left and the lower right goes to the 3.3 rail\nbutton_pin = machine.Pin(14, machine.Pin.IN, machine.Pin.PULL_DOWN)\n\n# this function gets called every time the button is pressed\ndef button_pressed_handler(pin):\n global button_presses, last_time\n new_time = utime.ticks_ms()\n # if it has been more that 1/5 of a second since the last event, we have a new event\n if (new_time - last_time) > 200: \n button_presses +=1\n last_time = new_time\n\n# now we register the handler function when the button is pressed\nbutton_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\n\n# This is for only printing when a new button press count value happens\nold_presses = 0\nwhile True:\n # only print on change in the button_presses value\n if button_presses != old_presses:\n print(button_presses)\n builtin_led.toggle()\n old_presses = button_presses\n
"},{"location":"advanced-labs/02-interrupt-handlers/#debounce-without-disabling-the-irq","title":"Debounce Without Disabling the IRQ","text":""},{"location":"advanced-labs/02-interrupt-handlers/#references","title":"References","text":"import machine, utime\n\n# the lower right coner has a wire that goes throuh\ncount_input = machine.Pin(14, machine.Pin.IN, machine.Pin.PULL_DOWN)\npresses = 0\n\ndef count_handler(pin):\n global presses\n # disable the IRQ during our debounce check\n count_input.irq(handler=None)\n presses +=1\n # debounce time - we ignore any activity diring this period \n utime.sleep_ms(200)\n # re-enable the IRQ\n count_input.irq(trigger=machine.Pin.IRQ_FALLING, handler = count_handler)\n\ncount_input.irq(trigger=machine.Pin.IRQ_FALLING, handler = count_handler)\n\nold_presses = 0\nwhile True:\n # only print on change\n if presses != old_presses:\n if presses > old_presses + 1:\n print('double counting in irq. Fixing...')\n presses = old_presses + 1\n print(presses)\n old_presses = presses\n
"},{"location":"advanced-labs/03-internal-temperature/","title":"Using the Builtin Temperature Sensor","text":"machine.ADC(4)
. This might be useful to see if your RP2040 CPY is running \"hot\" and might benefit from a cooling fan.
"},{"location":"advanced-labs/03-internal-temperature/#logging-the-temperature","title":"Logging the Temperature","text":"import machine\nimport utime\nsensor_temp = machine.ADC(4)\nwhile True:\n reading = sensor_temp.read_u16() * conversion_factor\n temperature = 27 - (reading - 0.706)/0.001721\n print(temperature)\n print('\\n')\n
"},{"location":"advanced-labs/03-timing-functions/","title":"Timing Functions","text":"import machine\nimport utime\nsensor_temp = machine.ADC(machine.ADC.CORE_TEMP)\nconversion_factor = 3.3 / (65535)\nfile = open(\"temps.txt\", \"w\")\nwhile True:\n reading = sensor_temp.read_u16() * conversion_factor\n temperature = 27 - (reading - 0.706)/0.001721\n file.write(str(temperature))\n file.flush()\n utime.sleep(10)\n
ticks
functions in the MicroPython utime library.utime
library allows us to count the number of microseconds that have elapsed since the processor was powered up.import machine, utime\n\nstart_time = utime.ticks_us()\n# sleep for 1 second\nutime.sleep(1)\nend_time = utime.ticks_us()\n\nwhile True:\n print(\"Start Time:\", start_time)\n print(\"End Time:\", end_time)\n print(\"Delta Time:\", end_time - start_time)\n print(\"\")\n
Start Time: 403122147\nEnd Time: 404122241\nDelta Time: 1000094\n\nStart Time: 403122147\nEnd Time: 404122241\nDelta Time: 1000096\n
import machine, utime\n\nstart_time = utime.ticks_ms()\nutime.sleep(1)\nend_time = utime.ticks_ms()\n\nwhile True:\n print(\"Start Time:\", start_time)\n print(\"End Time:\", end_time)\n print(\"Delta Time:\", end_time - start_time)\n print(\"\")\n
Start Time: 855845\nEnd Time: 856845\nDelta Time: 1000\n\nStart Time: 858031\nEnd Time: 859032\nDelta Time: 1001\n
conda --version
you have successfully installed Conda. For this document we have used conda:conda --version\n
"},{"location":"advanced-labs/04-conda-env/#creating-your-conda-environment","title":"Creating Your Conda Environment","text":"conda 4.10.1\n
conda create -n micropython python=3\n
conda activate micropython\n
help()
help()\n
Welcome to MicroPython!\n\nFor online help please visit https://micropython.org/help/.\n\nFor access to the hardware use the 'machine' module. RP2 specific commands\nare in the 'rp2' module.\n\nQuick overview of some objects:\n machine.Pin(pin) -- get a pin, eg machine.Pin(0)\n machine.Pin(pin, m, [p]) -- get a pin and configure it for IO mode m, pull mode p\n methods: init(..), value([v]), high(), low(), irq(handler)\n machine.ADC(pin) -- make an analog object from a pin\n methods: read_u16()\n machine.PWM(pin) -- make a PWM object from a pin\n methods: deinit(), freq([f]), duty_u16([d]), duty_ns([d])\n machine.I2C(id) -- create an I2C object (id=0,1)\n methods: readfrom(addr, buf, stop=True), writeto(addr, buf, stop=True)\n readfrom_mem(addr, memaddr, arg), writeto_mem(addr, memaddr, arg)\n machine.SPI(id, baudrate=1000000) -- create an SPI object (id=0,1)\n methods: read(nbytes, write=0x00), write(buf), write_readinto(wr_buf, rd_buf)\n machine.Timer(freq, callback) -- create a software timer object\n eg: machine.Timer(freq=1, callback=lambda t:print(t))\n\nPins are numbered 0-29, and 26-29 have ADC capabilities\nPin IO modes are: Pin.IN, Pin.OUT, Pin.ALT\nPin pull modes are: Pin.PULL_UP, Pin.PULL_DOWN\n\nUseful control commands:\n CTRL-C -- interrupt a running program\n CTRL-D -- on a blank line, do a soft reset of the board\n CTRL-E -- on a blank line, enter paste mode\n\nFor further help on a specific object, type help(obj)\nFor a list of available modules, type help('modules')\n
"},{"location":"advanced-labs/05-os-functions/#os-functions","title":"OS Functions","text":"help('modules')\n__main__ gc uasyncio/funcs uos\n_boot machine uasyncio/lock urandom\n_onewire math uasyncio/stream ure\n_rp2 micropython ubinascii uselect\n_thread onewire ucollections ustruct\n_uasyncio rp2 uctypes usys\nbuiltins uarray uerrno utime\ncmath uasyncio/__init__ uhashlib uzlib\nds18x20 uasyncio/core uio\nframebuf uasyncio/event ujson\n
import os\nprint(dir(os))\n
"},{"location":"advanced-labs/05-os-functions/#real-time-clock","title":"Real Time Clock","text":"['__class__', '__name__', 'remove', 'VfsFat', 'VfsLfs2', 'chdir', 'getcwd', 'ilistdir', 'listdir', 'mkdir', 'mount', 'rename', 'rmdir', 'stat', 'statvfs', 'umount', 'uname', 'urandom']\n
from machine import RTC\n\nrtc = RTC()\nrtc.datetime((2017, 8, 23, 2, 12, 48, 0, 0)) # set a specific date and time\nrtc.datetime() # get date and time\n
"},{"location":"advanced-labs/06-i2c/#i2c-scanner-for-i2c-0","title":"I2C Scanner for I2C 0","text":"
"},{"location":"advanced-labs/06-i2c/#i2c-scanner-for-both-i2c-0-and-1","title":"I2C Scanner for Both I2C 0 and 1","text":"import machine\n\nI2C_SDA_PIN = 0\nI2C_SCL_PIN = 1\ni2c=machine.I2C(0,sda=machine.Pin(I2C_SDA_PIN), scl=machine.Pin(I2C_SCL_PIN), freq=400000)\n\nprint('Scanning I2C bus.')\ndevices = i2c.scan() # this returns a list of devices\n\ndevice_count = len(devices)\n\nif device_count == 0:\n print('No i2c device found.')\nelse:\n print(device_count, 'devices found.')\n\nfor device in devices:\n print('Decimal address:', device, \", Hex address: \", hex(device))\n
import machine\n\nI2C0_SDA_PIN = 0\nI2C0_SCL_PIN = 1\nI2C1_SDA_PIN = 2\nI2C1_SCL_PIN = 3\ni2c0=machine.I2C(0,sda=machine.Pin(I2C0_SDA_PIN), scl=machine.Pin(I2C0_SCL_PIN), freq=400000)\ni2c1=machine.I2C(1,sda=machine.Pin(I2C1_SDA_PIN), scl=machine.Pin(I2C1_SCL_PIN), freq=400000)\n\nprint('Scanning I2C bus 0.')\ndevices = i2c0.scan() # this returns a list of devices\n\ndevice_count = len(devices)\n\nif device_count == 0:\n print('No i2c device found on bus 0.')\nelse:\n print(device_count, 'devices found.')\n\nfor device in devices:\n print('Decimal address:', device, \", Hex address: \", hex(device))\n\nprint('Scanning I2C bus 1.')\ndevices = i2c1.scan() # this returns a list of devices\n\ndevice_count = len(devices)\n\nif device_count == 0:\n print('No i2c device found on bus 1.')\nelse:\n print(device_count, 'devices found.')\n\nfor device in devices:\n print('Decimal address:', device, \", Hex address: \", hex(device))\n
"},{"location":"advanced-labs/06-i2c/#references","title":"References","text":"Scanning I2C bus 0.\n1 devices found.\nDecimal address: 60 , Hex address: 0x3c\nScanning I2C bus 1.\n1 devices found.\nDecimal address: 41 , Hex address: 0x29\n
"},{"location":"advanced-labs/07-framebuffer/","title":"Framebuffers in MicroPython","text":""},{"location":"advanced-labs/07-framebuffer/#references","title":"References","text":"
"},{"location":"advanced-labs/08-minicom/#installation-on-linux-raspberry-pi-os","title":"Installation on Linux (Raspberry Pi OS)","text":"brew install minicom\n
"},{"location":"advanced-labs/08-minicom/#verification-of-installation","title":"Verification of Installation","text":"sudo apt install minicom\n
which minicom\n
/usr/local/bin/minicom\n
minicom --version\n
minicom version 2.8 (compiled Jan 4 2021)\n
minicom --help\n
Usage: minicom [OPTION]... [configuration]\nA terminal program for Linux and other unix-like systems.\n\n -b, --baudrate : set baudrate (ignore the value from config)\n -D, --device : set device name (ignore the value from config)\n -s, --setup : enter setup mode\n -o, --noinit : do not initialize modem & lockfiles at startup\n -m, --metakey : use meta or alt key for commands\n -M, --metakey8 : use 8bit meta key for commands\n -l, --ansi : literal; assume screen uses non IBM-PC character set\n -L, --iso : don't assume screen uses ISO8859\n -w, --wrap : Linewrap on\n -H, --displayhex : display output in hex\n -z, --statline : try to use terminal's status line\n -7, --7bit : force 7bit mode\n -8, --8bit : force 8bit mode\n -c, --color=on/off : ANSI style color usage on or off\n -a, --attrib=on/off : use reverse or highlight attributes on or off\n -t, --term=TERM : override TERM environment variable\n -S, --script=SCRIPT : run SCRIPT at startup\n -d, --dial=ENTRY : dial ENTRY from the dialing directory\n -p, --ptty=TTYP : connect to pseudo terminal\n -C, --capturefile=FILE : start capturing to FILE\n --capturefile-buffer-mode=MODE : set buffering mode of capture file\n -F, --statlinefmt : format of status line\n -R, --remotecharset : character set of communication partner\n -v, --version : output version information and exit\n -h, --help : show help\n configuration : configuration file to use\n\nThese options can also be specified in the MINICOM environment variable.\nThis variable is currently unset.\nThe configuration directory for the access file and the configurations\nis compiled to /usr/local/Cellar/minicom/2.8/etc.\n\nReport bugs to <minicom-devel@lists.alioth.debian.org>.\n
Secure Digital (SD) is a non-volatile memory card format for use in portable devices such as cameras, MP3 players and portable devices.
On Microcontrollers SD cards are usually access through an SPI interface although there are also devices that use I2C interfaces.
"},{"location":"advanced-labs/09-micro-sd-card-reader/#maker-pi-pico-connections","title":"Maker Pi Pico Connections","text":"GPIO Pin SD Mode SPI Mode GP10 CLK SCK GP11 CMD SDI GP12 DAT0 SD0 GP13 DAT1 X GP14 DAT2 X GP15 CD/DAT3 CSn"},{"location":"advanced-labs/09-micro-sd-card-reader/#maker-pi-pico-example-code","title":"Maker Pi Pico Example Code","text":""},{"location":"advanced-labs/09-micro-sd-card-reader/#pin-definitions","title":"Pin Definitions","text":"# SD Mode Definitions\nSDCARD_CLK = 10\nSDCARD_CMD = 11\nSDCARD_DAT0 = 12\nSDCARD_DAT1 = 13\nSDCARD_DAT2 = 14\nSDCARD_CD_DAT3 = 15\n\n# SPI Mode Definitions\nSDCARD_SCK = 10\nSDCARD_SDI = 11\nSDCARD_SD0 = 12\nSDCARD_X1 = 13\nSDCARD_X2 = 14\nSDCARD_CS = 15\n
"},{"location":"advanced-labs/09-micro-sd-card-reader/#sample-code-for-spi-mode","title":"Sample Code for SPI Mode","text":"import machine, os, sdcard\n\n# Assign chip select (CS) pin (and start it high)\ncs = machine.Pin(15, machine.Pin.OUT)\n# Intialize SPI peripheral (start with 1 MHz)\nspi = machine.SPI(1,\n baudrate=1000000,\n polarity=0,\n phase=0,\n bits=8,\n firstbit=machine.SPI.MSB,\n sck=machine.Pin(10),\n mosi=machine.Pin(11),\n miso=machine.Pin(12))\n# Initialize SD card\nsd = sdcard.SDCard(spi, cs)\n\n# OR this simpler initialization code should works on Maker Pi Pico too...\n#sd = sdcard.SDCard(machine.SPI(1), machine.Pin(15))\n\nos.mount(sd, '/sd')\n# check the content\nos.listdir('/sd')\n\n# try some standard file operations\nfile = open('/sd/test.txt', 'w')\nfile.write('Testing SD card on Maker Pi Pico')\nfile.close()\nfile = open('/sd/test.txt', 'r')\ndata = file.read()\nprint(data)\nfile.close()\n
Results:
Testing SD card on Maker Pi Pico\n
"},{"location":"advanced-labs/09-micro-sd-card-reader/#references","title":"References","text":"MicroPython was created in by Australian programmer Damian George in May of 2014. Although Adafruit originally supported MicroPython, in July 2017 Adafruit created a fork of MicroPython and called it CircuitPython. We can only speculate why this decisive action was taken, but the result is it divided the community into two incompatible branches and it doubled the amount of work needed to be done to introduce a new device to the Python community. Unfortunately, the consequence is that many programs written in CircuitPython are difficult to port to MicroPython.
Today, according to Google Trends, MicroPython is still four more popular than CircuitPython when we look at worldwide web search comparisons. However, in the US, the popularity is more equal. Although the claim was that CircuitPython was done for \"simplicity\" there is little evidence that CircuitPython programs are smaller or easier to maintain than MicroPython.
The one thing is clear, CircuitPython has lots of drivers for unusual hardware devices. If you find a driver you need in MicroPython you will need to convert it to MicroPython. This is usually done manually on a line-by-line basis.
"},{"location":"advanced-labs/10-converting-circuitpython-to-micropython/#setting-up-a-circuitpython-virtual-environment","title":"Setting up a CircuitPython Virtual Environment","text":"Because MicroPython and CircuitPython are incompatible, it is important that you don't intermix your Python libraries.
Here is how we setup a virtual environment for CircuitPython using Conda.
conda create -n circuitpython python=3\nconda activate circuitpython\n
"},{"location":"advanced-labs/10-converting-circuitpython-to-micropython/#installing-the-ssd1306-circuitpython-library","title":"Installing the SSD1306 CircuitPython Library","text":"Per Directions Here
pip3 install adafruit-circuitpython-ssd1306\n
pip3 install adafruit-circuitpython-displayio-ssd1306\n
Note
ERROR: Could not find a version that satisfies the requirement adafruit-circuitpython-displayio-ssd1306 ERROR: No matching distribution found for adafruit-circuitpython-displayio-ssd1306
Successfully installed Adafruit-Blinka-6.3.2 Adafruit-PlatformDetect-3.2.0 Adafruit-PureIO-1.1.8 adafruit-circuitpython-busdevice-5.0.6 adafruit-circuitpython-framebuf-1.4.6 adafruit-circuitpython-ssd1306-2.11.1 pyftdi-0.52.9 pyserial-3.5 pyusb-1.1.1\n
# Basic example of clearing and drawing pixels on a SSD1306 OLED display.\n# This example and library is meant to work with Adafruit CircuitPython API.\n# Author: Tony DiCola\n# License: Public Domain\n\n# Import all board pins.\nfrom board import SCL, SDA\nimport busio\n\n# Import the SSD1306 module.\nimport adafruit_ssd1306\n\n\n# Create the I2C interface.\ni2c = busio.I2C(SCL, SDA)\n\n# Create the SSD1306 OLED class.\n# The first two parameters are the pixel width and pixel height. Change these\n# to the right size for your display!\ndisplay = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c)\n# Alternatively you can change the I2C address of the device with an addr parameter:\n#display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, addr=0x31)\n\n# Clear the display. Always call show after changing pixels to make the display\n# update visible!\ndisplay.fill(0)\n\ndisplay.show()\n\n# Set a pixel in the origin 0,0 position.\ndisplay.pixel(0, 0, 1)\n# Set a pixel in the middle 64, 16 position.\ndisplay.pixel(64, 16, 1)\n# Set a pixel in the opposite 127, 31 position.\ndisplay.pixel(127, 31, 1)\ndisplay.show()\n
"},{"location":"advanced-labs/10-converting-circuitpython-to-micropython/#using-chatgpt-to-convert-circuitpython-to-micropython","title":"Using ChatGPT to convert CircuitPython to MicroPython","text":"ChatGPT does a good job of automatically converting CircuitPython to MicroPython. In your prompt you just add the following instruction:
Convert the following CircuitPython code to MicroPython:\n
An example of this working with the basic blink example is shown below:
"},{"location":"advanced-labs/10-converting-circuitpython-to-micropython/#trend-analysis","title":"Trend analysis","text":"As of March 2021, MicroPython is about two to four times more popular than CircuitPython.
Google Worldwide Search Trends
"},{"location":"advanced-labs/11-mpremote/","title":"MicroPython Remote","text":"MicroPython now has a standard format for all remote access. The program is called mpremote. There is ample documentation on the site, and there is a higher chance it will include the latest features.
"},{"location":"advanced-labs/11-mpremote/#why-use-micropython-remote","title":"Why Use MicroPython Remote","text":"There are three main reasons to use mpremote:
A partial list of the most frequently used commands are:
$EDITOR
program and then copy the file back to the Pico.Note that you can only be connected to one remote device at a time to use many commands.
"},{"location":"advanced-labs/11-mpremote/#installing","title":"Installing","text":"The first time:
pip install --user mpremote\n
pip install --upgrade --user mpremote\n
"},{"location":"advanced-labs/11-mpremote/#install-log","title":"Install Log","text":"I use conda and you can see that it found the mpremote package version 1.20.0
Requirement already satisfied: mpremote in /Users/dan/opt/miniconda3/envs/mkdocs/lib/python3.6/site-packages (1.20.0)\nRequirement already satisfied: pyserial>=3.3 in /Users/dan/opt/miniconda3/envs/mkdocs/lib/python3.6/site-packages (from mpremote) (3.5)\nRequirement already satisfied: importlib-metadata>=1.4 in /Users/dan/opt/miniconda3/envs/mkdocs/lib/python3.6/site-packages (from mpremote) (4.8.3)\nRequirement already satisfied: zipp>=0.5 in /Users/dan/opt/miniconda3/envs/mkdocs/lib/python3.6/site-packages (from importlib-metadata>=1.4->mpremote) (3.4.0)\nRequirement already satisfied: typing-extensions>=3.6.4 in /Users/dan/opt/miniconda3/envs/mkdocs/lib/python3.6/site-packages (from importlib-metadata>=1.4->mpremote) (3.7.4.3)\n
"},{"location":"advanced-labs/11-mpremote/#testing-the-version","title":"Testing the Version","text":"The version of mpremote is not yet working, but eventually, it will be used like this:
$ mpremote --version\n
mpremote 0.0.0-unknown\n
"},{"location":"advanced-labs/11-mpremote/#getting-help","title":"Getting Help","text":"$ mpremote --help\n
"},{"location":"advanced-labs/11-mpremote/#help-results","title":"Help Results","text":"mpremote -- MicroPython remote control\nSee https://docs.micropython.org/en/latest/reference/mpremote.html\n\nList of commands:\n connect connect to given device\n disconnect disconnect current device\n edit edit files on the device\n eval evaluate and print the string\n exec execute the string\n fs execute filesystem commands on the device\n help print help and exit\n mip install packages from micropython-lib or third-party sources\n mount mount local directory on device\n repl connect to given device\n resume resume a previous mpremote session (will not auto soft-reset)\n run run the given local script\n soft-reset perform a soft-reset of the device\n umount unmount the local directory\n version print version and exit\n\nList of shortcuts:\n --help \n --version \n a0 connect to serial port \"/dev/ttyACM0\"\n a1 connect to serial port \"/dev/ttyACM1\"\n a2 connect to serial port \"/dev/ttyACM2\"\n a3 connect to serial port \"/dev/ttyACM3\"\n bootloader make the device enter its bootloader\n c0 connect to serial port \"COM0\"\n c1 connect to serial port \"COM1\"\n c2 connect to serial port \"COM2\"\n c3 connect to serial port \"COM3\"\n cat \n cp \n devs list available serial ports\n df \n ls \n mkdir \n reset reset the device after delay\n rm \n rmdir \n setrtc \n touch \n u0 connect to serial port \"/dev/ttyUSB0\"\n u1 connect to serial port \"/dev/ttyUSB1\"\n u2 connect to serial port \"/dev/ttyUSB2\"\n u3 connect to serial port \"/dev/ttyUSB3\"```\n
"},{"location":"advanced-labs/11-mpremote/#examples-of-file-system-commands","title":"Examples of File System Commands","text":"Here is the syntax of the copy file command:
mpremote fs cp main.py :main.py\n
This copies the local file \"main.py\" to your pico. The colon \":\" is the root of the pico.
"},{"location":"advanced-labs/11-mpremote/#setting-up-a-unix-alias","title":"Setting Up a UNIX Alias","text":"If you get tired of typing \"mpremote fs cp\" you can create an command-line alias called \"pcp\" for Pico Copy:
alias pcp='mpremote fs cp'\n
The copy command the becomes simply:
pcp main.py :main.py\n
If you place this line in your .bashrc or similar shell startup it saves you a lot of typing.
File systems examples include:
cat <file..>
to show the contents of a file or files on the devicels
to list the current directoryls <dirs...>
to list the given directoriescp [-r] <src...> <dest>
to copy filesrm <src...>
to remove files on the devicemkdir <dirs...>
to create directories on the devicermdir <dirs...>
to remove directories on the devicetouch <file..>
to create the files (if they don\u2019t already exist)For large classrooms that teach MicroPython using kits, we recommend that you arrange all the mkdir
and copy (cp
) file shell commands in a single UNIX shell script for consistency.
Here are the steps:
mkdir
If you follow these steps, then when the students connect to the Pico using Thonny they will see all the labs in the right order from simple to the most complex.
"},{"location":"advanced-labs/11-os/","title":"Sample MicroPython OS functions","text":"MicroPython provides a small list of os functions that allow you to manipulate files on the local filesystem of a MicroController.
These functions for filesystem access, and mounting, terminal redirection and duplication, and the uname and urandom functions.
These commands include:
import os\nuname = os.uname()\nprint(uname)\n
Returns:
(\n sysname='rp2',\n nodename='rp2',\n release='1.19.1',\n version='v1.19.1-88-g74e33e714 on 2022-06-30 (GNU 11.2.0 MinSizeRel)',\n machine='Raspberry Pi Pico W with RP2040'\n)\n
Thonny does not easily allow you to delete files. To do this you will need to use the \"os\" functions.
import os\nos.listdir()\nos.remove('myfile')\nos.listdir()\n
To find out all the os functions use:
import os\nprint(dir(os))\n``\n\nReturns\n
['class', 'name', 'remove', 'VfsFat', 'VfsLfs2', 'chdir', 'getcwd', 'ilistdir', 'listdir', 'mkdir', 'mount', 'rename', 'rmdir', 'stat', 'statvfs', 'umount', 'uname', 'urandom']`"},{"location":"advanced-labs/11-os/#percent-storage-full-and-free-ram","title":"Percent Storage Full and Free RAM","text":"import gc\nimport os\n\ndef df():\n s = os.statvfs('//')\n return ('{0} MB'.format((s[0]*s[3])/1048576))\n\ndef free(full=False):\n F = gc.mem_free()\n A = gc.mem_alloc()\n T = F+A\n P = '{0:.2f}%'.format(F/T*100)\n if not full: return P\n else : return ('Total:{0} Free:{1} ({2})'.format(T,F,P))\n\ndef free-gc(full=False):\n gc.collect()\n F = gc.mem_free()\n A = gc.mem_alloc()\n T = F+A\n P = '{0:.2f}%'.format(F/T*100)\n if not full: return P\n else : return ('Total:{0} Free:{1} ({2})'.format(T,F,P))\n\nprint(df())\n\nprint(free())\n\nprint(free-gc())\n
"},{"location":"advanced-labs/11-os/#machine-clock-frequency","title":"Machine Clock frequency","text":"print('Machine Clock Frequency:', '{:,} MHz'.format(machine.freq()/1000000))\n
returns:
Machine Clock Frequency: 125 MHz\n
"},{"location":"advanced-labs/11-os/#references","title":"References","text":"https://www.youtube.com/watch?v=jnSX8ZMmHZ4
"},{"location":"advanced-labs/11-rshell/","title":"Using rshell on the Raspberry Pi Pico","text":"Note
MicroPython now has standardized file transfers with the new \"MicroPython Remote\" or mpremote commands. See the mpremote docs for details. We will be updating all our documentation to use this standard. This page is mostly for older systems.
Using an IDE such as Thonny you can copy many files at a time to the Raspberry Pi Pico by using the GUI.
However, this process becomes error prone if you want to copy a large number of files. To do this we will use the \"remote shell\" program from the command line. In our classes we often want to copy a dozen or more files to a new robot for student to try out.
Rshell was written by David Hyland. The source code and installations are documented on Dave's GitHub repository here: https://github.com/dhylands/rshell. Dave added support for the Raspberry Pi Pico in release 0.0.30 in March of 2021.
Rshell's primary use is to to get filesystem information on the pico (ls), and to copy files to and from MicroPython's filesystem. It can also be used as a terminal to run interactive REPL commands.
"},{"location":"advanced-labs/11-rshell/#conda-setup","title":"Conda Setup","text":"If you are new to Python and you don't have any previous virtual environments set up you can skip this step. Experienced Python developers have many different environments that they want to keep separated due to library incompatibility issues. Here is how to create a new Python Conda environment that keeps your rshell libraries separated.
conda create -n pico python=3\nconda deactivate\nconda activate pico\n
Your prompt should now indicate you are in the pico environment.
"},{"location":"advanced-labs/11-rshell/#install-rshell-in-your-pico-environment","title":"Install rshell in your pico environment","text":"We will now use the standard pip installer tool to install the rshell command.
python -m pip install rshell\n
You can check that rshell has been correctly installed in your command PATH by running the UNIX which
command.
which rshell\n
"},{"location":"advanced-labs/11-rshell/#running-shell","title":"Running shell","text":"Rshell communicates with the Pico through the USB port. When you plug in the Pico you should see a new file created in the UNIX /dev directory. It typically begins with the letters /dev/cu.modem
. One way to test this is to unlpug the pico and run the following command:
ls /dev/cu.usbmodem*\n
With the pico unplugged, there should be no files that match the ls wildcard pattern. However, after you plug in the pico the following should be returned: /dev/cu.usbmodem14101\n
This is the port you will use to connect to the pico. We will use the -p
for port option to startup rshell.
rshell -p /dev/cu.usbmodem14101\n
Using buffer-size of 128\nConnecting to /dev/cu.usbmodem14101 (buffer-size 128)...\nTrying to connect to REPL connected\nRetrieving sysname ... rp2\nTesting if ubinascii.unhexlify exists ... Y\nRetrieving root directories ... /LCD_SPI.py/ /lcd-spi-test.py/ /lcd-test.py/ /lcd.py/\nSetting time ... Oct 28, 2021 20:30:56\nEvaluating board_name ... pyboard\nRetrieving time epoch ... Jan 01, 1970\nWelcome to rshell. Use Control-D (or the exit command) to exit rshell.\n
"},{"location":"advanced-labs/11-rshell/#boards","title":"Boards","text":"The boards command will list the boards rshell is connected to:
boards pyboard @ /dev/cu.usbmodem14101 connected Epoch: 1970 Dirs: /pyboard/hello.py /pyboard/main.py
"},{"location":"advanced-labs/11-rshell/#listing-files","title":"Listing Files","text":"We can see that the board is called \"pyboard\" and you can use that as a path to list the files on the board.
ls /pyboard\nhello.py main.py\n
"},{"location":"advanced-labs/11-rshell/#giving-your-board-a-name","title":"Giving Your Board a Name","text":"rshell will look for a program called board.py when it connects to the board. If this file contains a board name it will use that as the board name the next time it connects to the board. You can use the \"echo\" command to generate the file. In the example below, we will call our board \"pico\"
echo 'name=\"pico\"' > /pyboard/board.py\n
After you use the CONTROL-C and reconnect you will see the following:
pico @ /dev/cu.usbmodem14101 connected Epoch: 1970 Dirs: /pico/hello.py /pico/main.py\n
Remember you must disconnect from rshell and reconnect before the boards.py function is used.
For the remainder of this lesson we will assume you have renamed your board \"pico\".
You can then type cd /pico
followed by a ls
to see the files on your pico.
You can enter the REPL loop using the repl
command and use the same commands that you used in the Thonny shell.
repl\nprint('Hello World!')\n
returns
Hello World!\n
"},{"location":"advanced-labs/11-rshell/#getting-help-on-rshell-commands","title":"Getting Help on rshell commands","text":"You can type the help
command to see all the rshell commands:
help\n\nDocumented commands (type help <topic>):\n========================================\nargs cat connect date edit filesize help mkdir rm shell\nboards cd cp echo exit filetype ls repl rsync\n\nUse Control-D (or the exit command) to exit rshell.\n
"},{"location":"advanced-labs/11-rshell/#running-backup","title":"Running Backup","text":"If you want to copy all the python files from the pico to a backup directory you can use the following command:
cd /pico\ncp *.py /Users/dan/backup\n
You will need to create the /Users/dan/backup directory before you do this. You can also use the tilde ~
character to stand in for your home directory like this:
cp *.py /Users/dan/backup\n
Copying '/pico/hello.py' to '/Users/dan/backup/hello.py' ... Copying '/pico/main.py' to '/Users/dan/backup/main.py' ...
"},{"location":"advanced-labs/11-rshell/#installing-files","title":"Installing files","text":"If you have a directory called ~/build
that contains many files you want to install on the pico file system you can use the following command:
cp ~/build/* /pico\n
If you have done a clone to the CoderDojoTC micropython repository and put it in your home directory under ~/micropython then following command will copy the python files from the Maker Pi RP2040 kit to your pico:
mkdir /pico/lib\ncp ~/micropython/src/drivers/*.py /pico/lib\ncp ~/micropython/src/kits/maker-pi-rp2040/*.py /pico\ncp ~Documents/ws/micropython/src/kits/maker-pi-rp2040-robots/*.py /pico/lib\n
Note that the drivers will be placed in the /lob directory.
"},{"location":"advanced-labs/11-rshell/#direct-command-execution","title":"Direct Command Execution","text":"You do not need to use an interactive session to run a command with rshell. You can just add the command you would like to run to the end of the rshell command like this:
rshell -p /dev/cu.usbmodem14101 ls /pico\n
returns:
hello.py main.py\n
"},{"location":"advanced-labs/12-string-formatting/","title":"MicroPython String Formatting","text":"The normal Python has advanced string formatting functions using \"%\" and .format methods documented at the PyFormat website.
Although most of these functions work, there are some exceptions when using date formats.
The following % formats do not work under MicroPython:
The following example prints a floating point number with two decimals of precision in a field of six characters with leading zeros.
print('{:06.2f}'.format(3.141592653589793))\n
returns:
003.14\n
"},{"location":"advanced-labs/13-timers/","title":"Timers in MicroPython","text":"When clocks are used to trigger future events, they are called timers. Timers are used to efficiently use CPU resources. In microcontrollers, the work of keeping track of timers is frequently delegated to hardware outside of the main CPU loop. This makes your microcontroller more efficient, more reliable, and makes your code easier to read.
Timers work independently of the main CPU's clock. As a result, they are called asynchronous objects. Using timers can be both efficient and reliable, but they can be complex to debug. Errors within Timers and interrupts can be difficult to get information on.
"},{"location":"advanced-labs/13-timers/#types-of-timers","title":"Types of Timers","text":"Timers are used to schedule events in the future. There are two types:
Both of these timer objects need a callback function to be specified when they are initialized. These callback functions are also called interrupt service routines (ISRs). This is the function that will be executed when the timer gets triggered. So we must define an ISR function before timers are properly initialized.
Periodic timers are usually initialized with a period
parameter. This is the amount of time in milliseconds between each event. They are useful for doing like checking if new data is available on a network connection or checking if a sensor is still working.
One-shot timers are also initialized with a period
parameter often called a \"timeout\" period. This is the amount of time in milliseconds before the timer will fire. One-shot timers are used if you want to do something in the future but don't want to deal with it now. You can think of this as a reminder service.
Here is how both Periodic and one-shot timers are setup:
myOneShotTimer = Timer()\n# call once in 10 seconds from now\nmyOneShotTimer.init(mode=Timer.ONE_SHOT, callback=myCallbackFunction, period=10000)\n\n# call every two seconds until the timer is deactivated\nmyPeriodicTimer = Timer()\nmyPeriodicTimer.init(mode=Timer.PERIODIC, callback=myCallbackFunction, period=2000) \n
"},{"location":"advanced-labs/13-timers/#using-frequency-on-periodic-timers","title":"Using Frequency on Periodic Timers","text":"A Periodic timer can be configured to use either a period or a frequency as a parameter. The frequency is the number of times the timer will fire per second. The period is the amount of time in milliseconds between each event. The frequency is used to calculate the period. The following are equivalent:
# 50ms between events\nmyTimer.init(period=50, mode=Timer.PERIODIC, callback=move_pixel) \n# 20 events per second\nmyTimer.init(freq=20, mode=Timer.PERIODIC, callback=move_pixel) \n
You can always convert between the two by taking the inverse and multiplying by 1000.
frequency = 1 / period * 1000\nperiod = 1 / frequency * 1000\n
"},{"location":"advanced-labs/13-timers/#hardware-vs-virtual-software-timers","title":"Hardware vs. Virtual Software Timers","text":"Older microcontrollers have fixed hardware associated with each timer. These are call \"hardware timers\". When you initialize a hardware timer, you specify what set of hardware to use using a fixed ID (0,1,2,3 etc.). On the Raspberry Pi Pico all timers are \"virtual\" and are implemented in software. All virtual timers have an ID of -1 and the timer ID does not need to be specified.
"},{"location":"advanced-labs/13-timers/#sample-timer-program","title":"Sample Timer Program","text":"In the following program we will create timer that will toggle the built-in LED on the Raspberry Pi Pico every second. We will create a new function called toggle_led()
that will toggle the builtin LED on and off each time it is called. There are three key lines in this program.
Here is a sample code to toggle the builtin LED on the Raspberry Pi Pico and off.
from machine import Pin, Timer\nfrom utime import sleep\n\n# create an LED object using the onboard LED\nmyLED = machine.Pin(\"LED\", machine.Pin.OUT)\n\n# create an uninitialized timer object\nmyTimer = Timer()\n\n# create a function to be called when the timer goes off\n# this function just toggles the onboard LED\ndef toggle_led(timer):\n myLED.toggle()\n\n# initialize the timer object to tick every second (1,000 milliseconds)\nmyTimer.init(period=1000, mode=Timer.PERIODIC, callback=toggle_led)\n\nwhile True:\n sleep(10)\n print('just sleeping here')\n
"},{"location":"advanced-labs/13-timers/#sample-code-to-animate-an-led-strip-with-a-timer","title":"Sample Code to Animate an LED Strip With a Timer","text":"Many times you want to animate an LED strip but not complicate up your main event loop with this code. For example, you might want to light up the entire strip with a pattern of motion on the LED strip. You can do this by creating a new function that will conditionally turn one pixel to a color and increment a global variable that keeps track of which pixel to turn on. This function will be called every time the timer goes off.
from machine import Pin, Timer\nfrom utime import sleep\nfrom neopixel import NeoPixel\n\nNEOPIXEL_PIN = 0\nNUMBER_PIXELS = 8\nstrip = NeoPixel(machine.Pin(NEOPIXEL_PIN), NUMBER_PIXELS)\n\n# create an uninitialized timer object\nmyTimer = Timer()\n\ncounter = 0\n# a callback function. Use this with a timer that triggers 20 times a second\ndef move_pixel(myTimer):\n global counter\n for i in range(0, NUMBER_PIXELS):\n if i == counter:\n strip[i] = (10,0,0)\n else:\n strip[i] = (0,0,10)\n strip.write()\n counter += 1\n if counter > NUMBER_PIXELS:\n counter = 0\n\n# initialize the timer object to tick 20 times per second (50 milliseconds)\nmyTimer.init(period=50, mode=Timer.PERIODIC, callback=move_pixel)\n\nwhile True:\n print('Just sleeping here. The timer is doing all the work flashing the LED...', counter)\n sleep(5) # sleep for five seconds\n
For example, if a robot has a set of \"modes\" like driving forward, turning left, turning right, backing up etc. You can use a timer to flash the LED strip in different colors with different patterns to indicate the robot is in different modes.
"},{"location":"advanced-labs/13-timers/#removing-a-timer","title":"Removing a Timer","text":"The Timer.deinit()
can be used to remove a timer from the system. This is useful if you want to stop a timer that is currently running. If you have many timers that are each using memory, it is a good practice to remove them when you are done with them.
In MicroPython on the RP2040, there are no limits placed on the number of timers other than you must have enough memory available. Because there are no specific hardware limits, these are often referred to as \"virtual\" timers. The number of virtual timers is limited by the amount of memory available to the microcontroller.
Other implementations of MicroPython on different hardware have stricter limits placed on the number of timers. The ESP32 MicroPython port currently only has four hardware timers and a numeric ID of 0 to 3 must be used when you setup these timers. For the pyboard has a limit of 14 timers. Be aware of these limits if you are interested in creating portable MicroPython code.
"},{"location":"advanced-labs/13-timers/#drawbacks-of-timers-and-isrs","title":"Drawbacks of Timers and ISRs","text":"Unfortunately, different hardware implementations of MicroPython have different ways to setup and use timers. Some hardware requires timers to each have an ID tied to a specific resource. Because of this, be cautious about using timers in your code if you require portability between hardware.
As we mentioned, timers need callback functions are a type of function called interrupt service requests (ISRs). In multi-core systems like the RP2040 these interrupts can only be called from the core that executed the timer. This means that if you are using a timer to trigger an interrupt, you must make sure that the interrupt is only called from the core that is executing the timer.
There are special limitations on what can and can't be done within interrupts in most systems. For example, you are not allowed to allocate dynamic memory within an interrupt. Your interrupt handler should be a short function that performs the minimum work to change external variables. This is because the interrupt handler is called in a separate thread and can't allocate memory.
In general, doing complex logic within timers and interrupts is not a good idea. If you are interested in doing complex logic, you should use a different method that is easy to debug. This will make your code easier to understand and maintain.
"},{"location":"advanced-labs/13-timers/#exercises","title":"Exercises","text":"machine.reset()
if it gets stuck? Hint: See Watchdog TimersIn the recent past, memory management has been a challenge for embedded systems. Before 2020 microcontrollers like the Arduino usually only came with a few kilobytes of RAM, and careful attention needed to be paid to each byte using clever memory management trickery. The memory management system in recent years has become much easier. Even a low-cost $4 Raspberry Pi Pico now comes with 264K RAM! This is an almost 100-fold increase over the older Arduino Uno which only had 2K of RAM. That being said, there are always developers that push their hardware to the limit, and knowing the basics of memory management is important to understand how embedded systems work.
MicroPython is very clever about how memory is allocated. It is very stingy about memory use and will only allocate memory for what is needed. This is very different from the standard Python that allocates memory for everything that is needed up-front assuming that most laptops, desktops and servers have gigabytes of RAM and virtual memory support. This is an important difference, and it is important to understand if you want to push the limits of your microcontroller.
This lab will cover the basics of memory management, and how to use the memory management system in MicroPython. We will cover the following topics:
The memory management system is responsible for allocating and freeing memory. The memory management system must allocate memory for data variables, and for freeing this memory when a variable is no longer needed. The system is also responsible for allocating memory for the parameters used by functions, and for freeing memory when a function is no longer needed. You can read more about memory management on the MicroPython Memory Management Docs website.
The heap is the area of memory that is used to store general data. In MicroPython the heap is located in the lower memory and it grows upwards in memory address space. The exception is memory used as parameters to functions, which is stored in the stack.
The stack is the area of memory that is used to store all the parameters to function calls. In MicroPython the stack is located in the upper memory and it grows downwards in memory address space. If you are calling highly recursive functions, the stack will get a lot of use.
The garbage collector is a process to reuse memory that is no longer in use. The garbage collector is automatically run when the heap is full, but you can also run it manually to reclaim unused memory to get finer-grain control over memory usage and to avoid memory fragmentation.
In MicroPython most of these operations are done automatically for you. You don't really need to worry about how memory works unless you are reaching the limits of what your microcontroller can do.
One other key concept is continuous memory. If you are allocating a large amount of memory in an array or a long string, we need to allocate all this memory in one large chunk. As programs run for a long time memory becomes fragmented (many small free sections) and can't be used for storing large arrays.
You can read more about how MicroPython is clever about memory usage by reading the MicroPython Optimizations Docs website.
"},{"location":"advanced-labs/14-memory-management/#functions-to-show-free-and-used-memory","title":"Functions to show free and used memory","text":"We will be using the \"gc\" module to show the amount of free and used memory. \"gc\" orignally stood for \"garbage collection\", but the model has been generalized to perform other memory management tasks.
Here are the key functions to show the amount of free and used memory and current memory usage on a Raspberry Pi Pico with an RP2040 microcontroller: gc.mem_free()
and gc.mem_alloc()
:
import gc\nimport micropython\n\nprint('Memory Free:', \"{:,}\".format(gc.mem_free()), 'bytes')\nprint('Memory Allocated:', \"{:,}\".format(gc.mem_alloc()), 'bytes')\n
results for RP2040:
Memory Free: 187,232 bytes\nMemory Allocated: 4,864 bytes\n
You can see that although the RP2040 chip has a specification of 264K of RAM, it only has 187,232 bytes of RAM available for program use. The other RAM us used to store the MicroPython interpreter software. You can also see that the heap is currently using 4,864 bytes of RAM. This is typical of the additional overhead that MicroPython requires to run a program.
"},{"location":"advanced-labs/14-memory-management/#viewing-memory-layout","title":"Viewing Memory Layout","text":"You can use the micropython.mem_info(1)
function to view the memory layout of the MicroPython interpreter. This function returns a list of tuples, each tuple containing the address, size and type of each memory block. The address on the left of each row is the memory address of the start of the block within the heap. In MicroPython, memory blocks are each typically 16 bytes.
```python\nimport micropython\nprint(micropython.mem_info(1))\n
results:
stack: 532 out of 7936\nGC: total: 192064, used: 4896, free: 187168\n No. of 1-blocks: 52, 2-blocks: 13, max blk sz: 64, max free sz: 11650\nGC memory layout; from 200084a0:\n00000: h=MhhhBDhhBTTBDhTBDBBBh===DBDh====B=BBBBBBTB=BTB=BBBTB=TBTB=Bh==\n00400: =BB=h===========TB=h=h===================h=====h==h=============\n00800: ====h===========================================================\n00c00: ====h===========================================================\n01000: ====h=hBhh=ShhS..h=.........Sh=.................................\n01400: ....Shh=======h========h====h=====..............................\n (181 lines all free)\n2ec00: ....................................\nNone\n
This shows that the garbage collector is responsible for managing a total of 192,064 bytes of memory. The other numbers give you an indication of how fragmented you heap is.
Each of the letters represents the type of memory at that position on the heap:
| Letter | Description | ||| |.|Free memory| |h|head of a block of memory| |=|tail of a block of memory| |T|Tuple| |L|List| |D|Dictionary| |S|String| |A|Array or Byte Array| |F|Float| |B|Function BC| |M|Module|
If the heap is 192,064 bytes and each block is 16 bytes then there should be 12,004 blocks on the heap. If each row in the report displays 62 characters then there are 12,004/62=193 rows in the report. To keep the report short, the function will only show the rows that are not free. The report indicates that there are 181 lines all free blocks, so it will only show the non-free lines which in the example above is about six non-free rows in lower heap memory.
"},{"location":"advanced-labs/14-memory-management/#functions-to-manually-allocate-and-free-memory","title":"Functions to manually allocate and free memory","text":"You can manually run the garbage collector using the gc.collect()
functions. This function is used to force garbage collection exactly when you want to, not when the heap is full. This may occur at a time that is inconvenient for the program when it must be sending data at a specific speed.
Note
Still under development.
import gc\nimport micropython\n\ndef show_memory():\n print('Memory Free:', \"{:,}\".format(gc.mem_free()), 'bytes')\n print('Memory Allocated:', \"{:,}\".format(gc.mem_alloc()), 'bytes')\n\nshow_memory()\n# allocate a block of memory\nprint('Allocating a block of memory')\nmyBigBlockOfMemory = matrix[100][100]\nshow_memory()\nprint('Freeing the block of memory')\n# free the block of memory\ngc.mem_free(ptr)\nshow_memory()\n
"},{"location":"advanced-labs/14-memory-management/#image-size","title":"Image Size","text":"You can determine how much space we have available on the Pico flash after programming an ELF or UF2 file. For example, if we have an ELF file that's 1 MB and we were to program via openocd, then where should we offset my user data in flash? (i.e. XIP_BASE + OFFSET)
With an elf or uf2 file, using picotool info -a
on it will show the start and end of the binary (the start is usually 0x10000000 unless you did something to change it).
Inside your code, you can use the symbols defined by the linker script __flash_binary_start (defined here) and __flash_binary_end (defined here) like this:
extern char __flash_binary_start; // defined in linker script\nextern char __flash_binary_end; // defined in linker script\nuintptr_t start = (uintptr_t) &__flash_binary_start;\nuintptr_t end = (uintptr_t) &__flash_binary_end;\nprintf(\"Binary starts at %08x and ends at %08x, size is %08x\\n\", start, end, end-start);\n
"},{"location":"advanced-labs/14-memory-management/#references","title":"References","text":"You need to set GP25 to output and set it high and also set GP29 to input with no pull resistors before reading. And don't forget that the input from VSYS to ADC is divided by 3, so you have to multiply your result to get real value. When I do that I get around 4.7 V when powered from USB, so it definitely works.
https://forums.raspberrypi.com/viewtopic.php?t=301152
import machine\n\n# Vsys = machine.ADC(3)\nVsys = machine.ADC(29)\nconversion_factor = (3.3 / (65535)) * 3\n\nreading = Vsys.read_u16() * conversion_factor\n\nprint(reading)\n
"},{"location":"advanced-labs/17-file-system/","title":"MicroPython File Systems","text":"Unlike older Arduino systems, MicroPython has full support for a \"virtual file system\" (VFS) that we use to store and retrieve programs and data. The way we access the file systems in MicroPython is similar to the way we access files in standard Python.
"},{"location":"advanced-labs/17-file-system/#blocks-and-fragments","title":"Blocks and Fragments","text":"First, we need to define two key terms: blocks and fragments.
The block size refers to the size of the fundamental unit of storage on the file system. All files and directories occupy an integral number of blocks, with the size of each file and directory being a multiple of the block size. The block size is typically a power of 2, and can vary depending on the file system and the size of the storage medium.
The fragment size, on the other hand, refers to the smallest unit of space that can be allocated for a file or directory. Files and directories may occupy a number of fragments that is not necessarily an integer multiple of the fragment size. Fragmentation occurs when the file system is unable to allocate a contiguous block of storage for a file or directory, resulting in the file or directory being spread out over multiple fragments. The fragment size is typically smaller than the block size, and may also vary depending on the file system and the size of the storage medium.
"},{"location":"advanced-labs/17-file-system/#file-systems-statistics","title":"File Systems Statistics","text":"In MicroPython, the os.statvfs('/') function provides information about the root file system. Among the information it provides is the block size and fragment size of the file system.
In the os.statvfs('/')
function, the block size and fragment size are reported as the first and second elements of the tuple returned by the function, respectively. Specifically, stats[0] contains the block size, and stats[1] contains the fragment size. These values can be used to calculate various file system statistics, such as the total size of the file system, the total number of blocks and fragments, and the amount of free space available.
You can also mount file systems on other flash drives. You can get the stats of these file systems by using the new mount point with the os.statvfs()
function.
\"\"\"\nPrint the statistics from the virtual file system (vfs)\n\nhttps://docs.micropython.org/en/latest/library/os.html?highlight=os#os.statvfs\n\n0 f_bsize \u2013 file system block size\n1 f_frsize \u2013 fragment size\n2 f_blocks \u2013 size of fs in f_frsize units\n3 f_bfree \u2013 number of free blocks\n4 f_bavail \u2013 number of free blocks for unprivileged users\n5 f_files \u2013 number of inodes\n6 f_ffree \u2013 number of free inodes\n7 f_favail \u2013 number of free inodes for unprivileged users\n8 f_flag \u2013 mount flags\n9 f_namemax \u2013 maximum filename length\n\"\"\"\n\nimport os\n\nstats = os.statvfs(\"/\")\nprint(stats)\n\nblock_size = stats[0]\nfragment_size = stats[1]\ntotal_blocks = stats[2]\n\nfree_blocks = stats[3]\navailable_blocks = stats[4]\n\nmount_flags = stats[8]\nmax_filename_length = stats[9]\n\n# byte calculations\ntotal_bytes = total_blocks * fragment_size\nfree_bytes = free_blocks * fragment_size\navailable_bytes = available_blocks * fragment_size\n\nprint(\"File system block size: {:,} bytes\".format(block_size))\nprint(\"Fragement size: {:,} bytes\".format(fragment_size))\nprint(\"Size of entire file system in fragement blocks: {:,}\".format(total_blocks))\nprint(\"Size of entire file system in bytes: {:,}\".format(total_bytes))\n\nprint(\"Total free blocks for system and users: {:,}\".format(free_blocks))\nprint(\"Number of free blocks for unprivileged users: {:,} bytes\".format(available_blocks))\n\nprint(\"Free size for system and users: {:,} bytes\".format(free_bytes))\nprint(\"Free size for users: {:,} bytes\".format(available_bytes))\nprint(\"Mount flags: {:,}\".format(mount_flags))\nprint(\"Max filename length: {:,}\".format(max_filename_length))\n
"},{"location":"advanced-labs/20-tinyml/","title":"TinyML","text":"TinyML is designed to help you do machine learning and embedded systems. Once you have trained a deep learning model, you can use TinyML perform data analysis (inference) on a microcontroller or a cell phone.
"},{"location":"advanced-labs/20-tinyml/#references","title":"References","text":"import re\n\ndef extract_gpio_pin(input_string):\n # Use a regular expression to find the GPIO number\n match = re.search(r\"GPIO(\\d+)\", input_string)\n if match:\n # Convert the extracted number to an integer to remove leading zeros\n return int(match.group(1))\n else:\n # Return None if no match is found (or raise an exception if that's preferable)\n return None\n\n# Test the function with examples\nprint(extract_gpio_pin(\"Pin(GPIO15, mode=IN, pull=PULL_UP)\")) # Output: 15\nprint(extract_gpio_pin(\"Pin(GPIO7, mode=IN, pull=PULL_UP)\")) # Output: 7\nprint(extract_gpio_pin(\"Pin(GPIO03, mode=IN, pull=PULL_UP)\")) # Output: 3\n
https://chat.openai.com/share/6e6d8123-ed4d-4dc6-a915-030fe2245dfe
"},{"location":"advanced-labs/30-spectrum-analyzer/","title":"Spectrum Analyzer","text":""},{"location":"advanced-labs/30-spectrum-analyzer/#chatgpt-prompt","title":"ChatGPT Prompt","text":"I would like to create an audio spectrum analyzer using a Raspberry Pi Pico and a 128x64 OLED display using MicroPython. How would you suggest I go about this project. Break it down step-by-step.\n
"},{"location":"advanced-labs/30-spectrum-analyzer/#chatgpt-gpt-4-response","title":"ChatGPT GPT-4 Response","text":"Creating an audio spectrum analyzer with a Raspberry Pi Pico and a 128x64 OLED display using MicroPython is a fascinating project that combines hardware interfacing with digital signal processing. Below is a step-by-step guide to help you get started on this project.
"},{"location":"advanced-labs/30-spectrum-analyzer/#step-1-gather-the-components","title":"Step 1: Gather the Components","text":"This project involves a mix of hardware setup, digital signal processing, and real-time data visualization. Take it step by step, and don't hesitate to seek out community forums and resources if you encounter challenges.
"},{"location":"advanced-labs/30-spectrum-analyzer/01-test-incoming-sound/","title":"Test Incoming Sound","text":""},{"location":"advanced-labs/30-spectrum-analyzer/01-test-incoming-sound/#prompt","title":"Prompt","text":"OK, I now have a Raspberry Pi Pico and the OLED display hooked up. I also have a microphone hooked up to ADC0 on port GPIO26. How can I test that the signal is coming into the ADC? Can you write a MicroPython program that displays the audio signal on the OLED?\n\nI am using a ssd1306 and I use the following MicroPython:\n
from machine import Pin\nimport ssd1306\n\nWIDTH = 128\nHEIGHT = 64\n\nclock=Pin(2) #SCL\ndata=Pin(3) #SDA\nRES = machine.Pin(4)\nDC = machine.Pin(5)\nCS = machine.Pin(6)\n\nspi=machine.SPI(0, sck=clock, mosi=data)\ndisplay = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)\n# clear the display\ndisplay.fill(0)\n# draw a diagonal line\ndisplay.line(0, 0, WIDTH, HEIGHT, 1)\n
"},{"location":"advanced-labs/30-spectrum-analyzer/01-test-incoming-sound/#gpt-4-reponse","title":"GPT-4 Reponse","text":"To test that the signal is coming into the ADC (Analog-to-Digital Converter) on the Raspberry Pi Pico via GPIO26, and to display the audio signal on your SSD1306 OLED, you can use the following MicroPython script. This script will read the analog values from the microphone connected to GPIO26 and plot these values in real-time on the OLED display, creating a simple waveform visualization.
Note: Before running this script, ensure you have properly initialized and connected your microphone to GPIO26 (ADC0), and the OLED display is correctly wired for SPI communication as per your provided initialization.
import machine\nimport utime\nfrom machine import Pin, ADC, SPI\nimport ssd1306\n\n# OLED display width and height\nWIDTH = 128\nHEIGHT = 64\n\n# SPI pins for OLED\nclock = Pin(2) # SCL\ndata = Pin(3) # SDA\nRES = Pin(4)\nDC = Pin(5)\nCS = Pin(6)\n\n# Initialize SPI and OLED Display\nspi = SPI(0, sck=clock, mosi=data)\ndisplay = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)\n\n# Initialize ADC for GPIO26 (ADC0)\nadc = ADC(Pin(26))\n\ndef plot_signal():\n display.fill(0) # Clear the display\n old_x = 0\n old_y = HEIGHT // 2\n\n # For simplicity, we're plotting every other pixel\n for x in range(0, WIDTH, 2):\n # Read from ADC (values will be from 0 to 4095)\n val = adc.read_u16()\n # Scale the ADC value to fit the OLED height\n y = int((val / 65535) * HEIGHT)\n # Invert y to plot correctly on the OLED\n y = HEIGHT - y\n # Draw a line from the last point to the new point\n display.line(old_x, old_y, x, y, 1)\n old_x, old_y = x, y\n\n display.show() # Update the display with the new data\n\nwhile True:\n plot_signal()\n utime.sleep(0.1) # Small delay to reduce flickering\n
This script continuously reads the analog signal from the microphone and plots a simplified version of the waveform across the OLED display. Here's a breakdown of the plotting part:
display.fill(0)
.display.show()
to visualize the waveform.This example provides a basic way to visualize audio signals. The resolution and quality of the visualization can be further enhanced or adjusted according to your requirements.
"},{"location":"advanced-labs/30-spectrum-analyzer/02-gain-pot/","title":"Adding A Gain Potentiometer.","text":""},{"location":"advanced-labs/30-spectrum-analyzer/02-gain-pot/#prompt","title":"Prompt","text":"The signal is very low on the display. I have added a Potentiometer on ADC1 which is on GPIO PIN 27. Can you please regenerate the entire program with sound on ADC0 and gain on ADC1. sound = ADC(Pin(26)) gain = ADC(Pin(27))
"},{"location":"advanced-labs/30-spectrum-analyzer/02-gain-pot/#chatgpt-","title":"ChatGPT-","text":"Certainly! To adjust the signal's amplitude on the display based on the gain from a potentiometer connected to ADC1 (GPIO PIN 27), you can modify the program as follows. This updated script will use the value from the potentiometer to scale the audio signal from the microphone, making the waveform larger or smaller on the OLED display according to the potentiometer's position.
import machine\nimport utime\nfrom machine import Pin, ADC, SPI\nimport ssd1306\n\n# OLED display width and height\nWIDTH = 128\nHEIGHT = 64\n\n# SPI pins for OLED\nclock = Pin(2) # SCL\ndata = Pin(3) # SDA\nRES = Pin(4)\nDC = Pin(5)\nCS = Pin(6)\n\n# Initialize SPI and OLED Display\nspi = SPI(0, sck=clock, mosi=data)\ndisplay = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)\n\n# Initialize ADC for sound input (GPIO26) and gain control (GPIO27)\nsound = ADC(Pin(26))\ngain = ADC(Pin(27))\n\ndef plot_signal_with_gain():\n display.fill(0) # Clear the display\n old_x = 0\n old_y = HEIGHT // 2\n\n # Read gain control (potentiometer) value\n gain_value = gain.read_u16() + 1 # Adding 1 to avoid division by zero\n\n for x in range(0, WIDTH, 2):\n # Read from ADC (sound input)\n val = sound.read_u16()\n\n # Adjust the sound value based on the gain\n # Note: This scaling might need adjustment depending on your specific potentiometer and desired sensitivity\n adjusted_val = min(((val * gain_value) >> 16), 65535) # Ensure the adjusted value does not exceed ADC's max value\n\n # Scale the adjusted value to fit the OLED height\n y = int((adjusted_val / 65535) * HEIGHT)\n # Invert y to plot correctly on the OLED\n y = HEIGHT - y\n # Draw a line from the last point to the new point\n display.line(old_x, old_y, x, y, 1)\n old_x, old_y = x, y\n\n display.show() # Update the display with the new data\n\nwhile True:\n plot_signal_with_gain()\n utime.sleep(0.1) # Small delay to reduce flickering\n
In this script, the gain control (potentiometer) reading is used to adjust the amplitude of the sound signal before plotting it. The gain_value
is read from ADC1 and is used to scale the audio signal from the microphone connected to ADC0. The line adjusted_val = min(((val * gain_value) >> 16), 65535)
scales the microphone input based on the potentiometer's position, and the bit shift >> 16
is used to keep the value within the 16-bit range of the ADC's output.
This method should help make the displayed signal more responsive to the potentiometer's adjustments, allowing you to visually enhance or reduce the waveform amplitude on the OLED display.
"},{"location":"advanced-labs/30-spectrum-analyzer/03-display-scroll/","title":"Display Scroll","text":""},{"location":"advanced-labs/30-spectrum-analyzer/03-display-scroll/#prompt","title":"Prompt","text":"The display is a bit slow. Can you rewrite the plot\\_signal\\_with\\_gain function so that the scroll() function is used to move the display to the left by one pixel?\n
"},{"location":"advanced-labs/30-spectrum-analyzer/03-display-scroll/#chatgpt-gpt-4-response","title":"ChatGPT GPT-4 Response","text":"To make the display update faster and to create a scrolling effect that moves the waveform to the left by one pixel, you can modify the plot_signal_with_gain
function to use the scroll()
method of the SSD1306 library. This will shift the entire display content to the left by one pixel, and then you can plot the new data point at the right edge of the display. Here's how you can implement this:
def plot_signal_with_gain():\n # Scroll the display content to the left by one pixel\n display.scroll(-1, 0)\n\n # Read gain control (potentiometer) value\n gain_value = gain.read_u16() + 1 # Adding 1 to avoid division by zero\n\n # Read from ADC (sound input)\n val = sound.read_u16()\n\n # Adjust the sound value based on the gain\n adjusted_val = min(((val * gain_value) >> 16), 65535) # Ensure the adjusted value does not exceed ADC's max value\n\n # Scale the adjusted value to fit the OLED height\n y = int((adjusted_val / 65535) * HEIGHT)\n # Invert y to plot correctly on the OLED\n y = HEIGHT - y\n\n # Since we've scrolled, we only need to update the last column (x = WIDTH - 1)\n # Clear the last column first (optional, depending on the desired visual effect)\n for clear_y in range(HEIGHT):\n display.pixel(WIDTH - 1, clear_y, 0)\n\n # Draw the new data point at the right edge of the display\n display.pixel(WIDTH - 1, y, 1)\n\n # Update the display with the new data\n display.show()\n
In this updated function, the display is scrolled left by one pixel at the beginning of each update cycle using display.scroll(-1, 0)
. This creates room to draw the new data point at the far right edge (WIDTH - 1
) of the display. The loop that clears the last column before drawing the new point is optional and can be adjusted or removed depending on the visual effect you're aiming for.
Remember, while scroll()
improves the visual fluidity by creating a continuous scrolling effect, the update rate can still be limited by the display's physical refresh rate and the speed at which your MicroPython script can run. Experiment with the utime.sleep(0.1)
delay at the end of the main loop to find a balance between smoothness and speed that works for your specific application.
This lesson assumes that you have at least skimmed the Getting Started sections and have selected one of the MicroPython IDEs to write your code.
The lesson will introduce you to the basic concepts of using MicroPython using the $4 Raspberry Pi Pico or a similar microcontroller.
The first two examples just use the Raspberry Pi Pico and don't require a breadboard or wiring. All the subsequent examples will require you to place components on a solderless breadboard.
"},{"location":"basics/03-blink/","title":"Blink in MicroPython","text":""},{"location":"basics/03-blink/#overview","title":"Overview","text":"In this lab, we will use MicroPython to make the green on-board LED on the Raspberry Pi Pico blink on and off every half second. The only things you need to run this program are
The pico has a single built in green LED wired to logical pin 25. We call this GPIO 25. GPIO means General Purpose Input and Output pin. Here is a sample program that you can use. Don't worry about understanding each line yet. We will cover the various parts of the file in later sections.
# Setup - run once\nfrom machine import Pin # Get the Pin function from the machine module.\nfrom time import sleep # Get the sleep library from the time module.\n\n# This is the built-in green LED on the Pico.\nBUILT_IN_LED_PIN = 25\n# change this to the following named pin on the \"W\"\n# BUILT_IN_LED_PIN = Pin(\"LED\", Pin.OUT)\n\n# The line below indicates we are configuring this as an output (not input)\nled = machine.Pin(BUILT_IN_LED_PIN, machine.Pin.OUT)\n\n# Main loop: Repeat the forever...\nwhile True:\n led.high() # turn on the LED\n sleep(0.5) # leave it on for 1/2 second\n led.low() # Turn off the LED\n sleep(0.5) # leave it off for 1/2 second\n
This program has two main parts. The first part is often called the preamble or the setup code. This code gets executed only once when the program is run. In this example, the setup loads the right libraries and initializes global variables.
The second part is the main event loop. This program continues to run until the device is powered down or the program is reset.
The from machine import Pin
statement is required to define the characteristics of our physical machine. In this case, we are loading the Pin python library.
The from time import sleep
library is required for the python sleep function. Note that some programs use utime
for MicroPython time. These both work the same. They are really just synonyms or alias of each other. Although the use of utime
is technically a little bit more precise - the reader knows that the code is using the actual MicroPython time library, the use of the time
alias makes our code on character smaller and can make our code more portable to other systems.
Note that the text after the hash or pound characters are comments. Comments are ignored by the Python interpreter but it is a good practice to put in comments in your code to help others understand your program.
"},{"location":"basics/03-blink/#changing-the-blink-speed","title":"Changing the Blink Speed","text":"Next, lets create a Python global variable for the delay that the LED is on and off.
from machine import Pin\nfrom time import sleep\nBUILT_IN_LED_PIN = 25\n# this is the builtin LED on the Pico\nled = Pin(BUILT_IN_LED_PIN, machine.Pin.OUT)\n\n# global variables\ndelay = .25\n\n# repeat forever\nwhile True:\n led.high() # turn on the LED\n sleep(delay) # leave it on for 1/2 second\n led.low() # Turn off the LED\n sleep(delay) # leave it off for 1/2 second\n
This program will blink the built-in LED on and off every 1/4 of a second. By changing the delay variable you can make the LED blink faster and slower.
Challenge
What is the fastest you can make the LED blink and still see it changing? What does this tell you about the human eye?
"},{"location":"basics/03-blink/#using-toggle","title":"Using Toggle","text":"Instead of using the on()
and off()
methods, we can also just use the toggle()
function.
from machine import Pin\nfrom time import sleep\nled_onboard = machine.Pin(25, machine.Pin.OUT)\nwhile True:\n led_onboard.toggle()\n sleep(.25)\n
The toggle()
function looks at the state of the output pin. If it is high, it sets the value low. If it is low, it sets it high. Whatever the value currently is, it will set it to the opposite value.
If you save the file as main.py, this program will run when the microcontroller starts up without the BOOTSEL being pressed.
"},{"location":"basics/03-blink/#blinking-an-external-led","title":"Blinking an External LED","text":"Although the builtin LED is convenient, you can use the almost the code to blink any external LED that is connected through a 330 ohm resister in series to the LED.
We will assume that an LED is connected to pin GIO16 and is connected via a 330 ohm resistor to ground.
Here is the code that will blink an LED that is connected to PIN GIO16, which is in the upper right corner of the Pico.
from machine import Pin\nfrom time import sleep\n# this is the lower right corner pin on the Pico with USB on the bottom\nEXTERNAL_LED_PIN = 16\nled = machine.Pin(EXTERNAL_LED_PIN, machine.Pin.OUT)\n\n# repeat forever\nwhile True:\n led.high() # turn on the LED\n time.sleep(0.5) # leave it on for 1/2 second\n led.low() # Turn off the LED\n sleep(0.5) # leave it off for 1/2 second\n
Here is that same program using the toggle()
function:
from machine import Pin\nfrom time import sleep\nEXTERNAL_LED_PIN = 16\nexternal_led = machine.Pin(EXTERNAL_LED_PIN, machine.Pin.OUT)\n\nwhile True:\n external_led.toggle()\n sleep(5)\n
"},{"location":"basics/03-blink/#solution-to-led-row-lab","title":"Solution to LED Row Lab","text":"from machine import Pin\nfrom time import sleep\nEXTERNAL_LED_PIN1 = 16\nEXTERNAL_LED_PIN2 = 17\nEXTERNAL_LED_PIN3 = 18\nEXTERNAL_LED_PIN4 = 19\nled1 = machine.Pin(EXTERNAL_LED_PIN1, machine.Pin.OUT)\nled2 = machine.Pin(EXTERNAL_LED_PIN2, machine.Pin.OUT)\nled3 = machine.Pin(EXTERNAL_LED_PIN3, machine.Pin.OUT)\nled4 = machine.Pin(EXTERNAL_LED_PIN4, machine.Pin.OUT)\n\nwhile True:\n # blink the first LED\n led1.high()\n sleep(.5)\n led1.low()\n\n # blink the 2nd LED\n led2.high()\n sleep(.5)\n led2.low()\n\n\n # blink the 3rd LED\n led3.high()\n sleep(.5)\n led3.low()\n\n\n # blink the 4th LED\n led4.high()\n sleep(.5)\n led4.low()\n
"},{"location":"basics/03-blink/#extra-credit-lab","title":"Extra credit lab","text":"Can you rewrite the program above using an array of pin values like this:
LED_PINS = [16,17,28,19]\n
"},{"location":"basics/03-blink/#more-to-explore","title":"More to Explore","text":"In this lesson we will hook a single momentary push button up to our Raspberry Pi Nano. We will use it to toggle the built-in LED. We will start out with simply polling the button 10 times a second to check it's state. Then we will show how to use an interrupt handler function to monitor events from the button.
In the example above, we are connecting the button on the left to the lower-left corner pin of the Raspberry Pi Pico. This is GPIO Pin 15 and is in row number 20 of our breadboard.
"},{"location":"basics/03-button/#momentary-switch-buttons","title":"Momentary Switch Buttons","text":"We use \"B3F\" tactile switch buttons that can be mounted directly on our breadboards. When the button is pressed, it connects a wire that joins two pins on one side to the two pins on the other side. The buttons can be mounted directly over the trough in the center of the breadboard. They typically cost under $2 for 10 buttons or about 20 cents per button.
Here are the internal connections within the switch.
This is the connection diagram that shows how the button is connected to the GPIO connector in the lower-left corner of the Raspberry Pi Pico. This corresponds to GP15 or Pin #15 in our code.
"},{"location":"basics/03-button/#sample-button-polling-code","title":"Sample Button Polling Code","text":"Here is our fist example that uses a simple \"watching\" loop to check if the button value has change 10 times per second. In this case, the built-in LED is connected to pin 25.
from machine import Pin\nimport time\n\n# GPIO is the internal built-in LED\nled = Pin(25, Pin.OUT)\n# input on the lower left of the Pico using a built-in pull-down resistor to keep the value from floating\nbutton = Pin(15, Pin.IN, Pin.PULL_DOWN) \n\nwhile True:\n if button.value(): # if the value changes\n led.toggle()\n time.sleep(0.1) # wait 1/10th of a second\n
"},{"location":"basics/03-button/#interrupt-handler-version","title":"Interrupt Handler Version","text":"Although the polling version is simple, it does take a lot of the CPU resources. The button.value() is checked 10 times a second, even though the button might only be pressed once a day!
A more efficient version uses a strategy called an interrupt handler. This is a function that is \"registered\" by micropython to handel external events such as a button press.
# Use an interrupt function count the number of times a button has been pressed\nfrom machine import Pin\nimport micropython\nimport time\n\n# global value\nbutton_pressed_count = 0\n\n# Interrupt Service Routine for Button Pressed Events - with no debounce\ndef button1_pressed(change):\n global button_pressed_count\n button_pressed_count += 1\n\nbutton1 = Pin(14, Pin.IN, Pin.PULL_DOWN)\nbutton1.irq(handler=button1_pressed, trigger=Pin.IRQ_FALLING)\n\nbutton_pressed_count_old = 0\nwhile True:\n if button_pressed_count_old != button_pressed_count:\n print('Button 1 value:', button_pressed_count)\n button_pressed_count_old = button_pressed_count\n
"},{"location":"basics/03-button/#interrupt-handler-with-a-debounce-feature","title":"Interrupt Handler with a Debounce Feature","text":"One of the problems with most switches is that they don't turn on and off perfectly each time. As the connection is getting close to closing some electrons jump the gap and the switch appears to turn on for a few microseconds. So to a computer, this looks like someone quickly pressing a button rapidly until it is firmly closed or completely open. This intermediate stage between completely open and closed is called the \"bounce\" stage of a switch opening and closing.
To remove this problem and get a clean signal, we can use either a hardware solution (wiring a capacitor to remove the high frequency noise) or we can be clever and solve the problem with a few extra lines of code.
The secret is to setup a timer when the switch is first closed or opened. We then ignore all the crazy stuff that happens for about 1/5th of a second (200 milliseconds). By then we usually have a solid indication that the button is changing state and we can return the new value.
Here is a example of this \"Debounce\" code in MicroPython:
import utime\nfrom machine import Pin\n\n# Sample Raspberry Pi Pico MicroPython button press example with a debounce delay value of 200ms in the interrupt handler\n\nbutton_presses = 0 # the count of times the button has been pressed\nlast_time = 0 # the last time we pressed the button\n\nbuiltin_led = machine.Pin(25, Pin.OUT)\n# The lower left corner of the Pico has a wire that goes through the buttons upper left and the lower right goes to the 3.3 rail\nbutton_pin = machine.Pin(14, machine.Pin.IN, machine.Pin.PULL_DOWN)\n\n# This function gets called every time the button is pressed. The parameter \"pin\" is not used.\ndef button_pressed_handler(pin):\n global button_presses, last_time\n new_time = utime.ticks_ms()\n # if it has been more that 1/5 of a second since the last event, we have a new event\n if (new_time - last_time) > 200: \n button_presses +=1\n last_time = new_time\n\n# now we register the handler function when the button is pressed\nbutton_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\n\n# This is for only printing when a new button press count value happens\nold_presses = 0\nwhile True:\n # only print on change in the button_presses value\n if button_presses != old_presses:\n print(button_presses)\n builtin_led.toggle()\n old_presses = button_presses\n
"},{"location":"basics/03-button/#two-button-example","title":"Two Button Example","text":"This example uses two buttons. One adds one to a counter, and the other decrements the counter. In the IRQ we look for the string '''14''' in the incoming pin event string. If the string is present, then we increment the button_presses
variable. If it is not, then we decrement the counter.
This example is useful whenever you have two buttons that control the mode
of a microcontroller.
Note that you can tune the delay period. If you have clean buttons and you want to allow for fast double-click events you can lower the time. 200 milliseconds is good for low-cost noisy buttons that may have many spikes in the open and closing transitions.
import utime\nfrom machine import Pin\n\n# Sample two button Raspberry Pi Pico MicroPython example\n# with a debounce delay value of 200ms in the interrupt handler\n# https://www.coderdojotc.org/micropython/basics/03-button/\n\n# these are the pins in the lower-left corner (USB on top)\nBUTTON_PIN_A = 14\nBUTTON_PIN_B = 15\n\nbutton_presses = 0 # the count of times the button has been pressed. A is +1, B is -1\nlast_time = 0 # the last time we pressed the button\n\n# we toggle the builtin LED to get visual feedback\nbuiltin_led = machine.Pin(25, Pin.OUT)\n\n# The lower left corner of the Pico has a wire that goes through the buttons upper left and the lower right goes to the 3.3 rail\nbutton_a = machine.Pin(BUTTON_PIN_A, machine.Pin.IN, machine.Pin.PULL_DOWN)\nbutton_b = machine.Pin(BUTTON_PIN_B, machine.Pin.IN, machine.Pin.PULL_DOWN)\n\n# this is the interrupt callback handler\n# get in and out quickly\ndef button_callback(pin):\n global button_presses, last_time\n new_time = utime.ticks_ms()\n # if it has been more that 1/5 of a second since the last event, we have a new event\n if (new_time - last_time) > 200:\n # print(pin)\n if '14' in str(pin):\n button_presses +=1\n else:\n button_presses -= 1\n last_time = new_time\n\n# now we register the handler functions when either of the buttons is pressed\nbutton_a.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_callback)\nbutton_b.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_callback)\n\n# This is for only printing when a new button press count value happens\nold_presses = 0\n\nprint(button_presses)\nwhile True:\n # only print on change in the button_presses value\n if button_presses != old_presses:\n print(button_presses)\n builtin_led.toggle()\n old_presses = button_presses\n
"},{"location":"basics/03-button/#references","title":"References","text":"In this lab we will use a 10K ohm potentiometer to demonstrate how a turn of a knob can result in getting a continuous variable from a user into our code. We will show how we can use a potentiometer to change the blinking speed of on LED.
"},{"location":"basics/03-potentiometer/#about-analog-to-digital-converters","title":"About Analog to Digital Converters","text":"Digital microcontrollers are inherently noisy. They have clocks that pull power from the power supply and cause voltage fluctuations when we compare a signal to these power lines. This makes it difficult to get
ADC_VREF is the ADC power supply (and reference) voltage, and is generated on Pico by filtering the 3.3V supply. This pin can be used with an external reference if better ADC performance is required. AGND is the ground reference for GPIO26-29, there is a separate analog ground plane running under these signals and terminating at this pin.
"},{"location":"basics/03-potentiometer/#circuit-diagram","title":"Circuit Diagram","text":"Note: to get an accurate noise-free reading from the potentiometer you must use the ADC_VREF and the AGND pins. These are special pins designed to reduce the noise on the power areas of the pico.
"},{"location":"basics/03-potentiometer/#sample-code-to-print-potentiometer-values","title":"Sample Code To Print Potentiometer Values","text":"from machine import ADC\nfrom utime import sleep\npot = ADC(26)\nwhile True:\n print(pot.read_u16())\n sleep(.2)\n
graph LR\np[Pico]-->|ADC_VREF 36 row=6| pos(Positive)\np[Pico]-->|AGND 33 row=8| neg(Negative)\np[Pico]-->|GP26 pin=26 ADC0 31 row=10| tap(Center Tap)\n pos(Positive) --- pot(Potentiometer)\n neg(Negative) --- pot(Potentiometer)\n tap(Center Tap) --- pot(Potentiometer)\n
Connect the positive to pin 35 ADC_REF (row 6 on the breadboard) and the negative to pin 33 AGND (row 8 on the breadboard). The Pico has special noise reduction circuits to avoid power supply jitter on these reference pins.
"},{"location":"basics/03-potentiometer/#changing-blink-speed-with-a-potentiometer","title":"Changing Blink Speed with a Potentiometer","text":"from machine import ADC, Pin\nfrom utime import sleep\n\n# this is the built-in LED on the Pico\nled = Pin(25, Pin.OUT)\n\n# ADC0 is GPIO 26. Connect to row 10 the right side\npot = ADC(26)\n\nMAX_DELAY = .5 # seconds\n\n# global variables\ndelay = 0\n\n# repeat forever\nwhile True:\n pot_value = pot.read_u16() # read the value from the pot\n delay = pot_value/65025 * MAX_DELAY\n print(\"delay:\", delay)\n if delay > 0:\n print(\"frequency (toggles per second):\", 1/delay)\n led.high() # turn on the LED\n sleep(delay) # leave it on for 1/2 second\n led.low() # Turn off the LED\n sleep(delay) # leave it off for 1/2 second\n
The following video shows this script in action.
"},{"location":"basics/03-potentiometer/#changing-the-brightness-of-the-building-led","title":"Changing the Brightness of the Building LED","text":"We can change the brightness of the builtin LED by using the POT value to change the PWM duty cycle.
Here is a sample progam that does this:
from machine import ADC, Pin, PWM\nfrom utime import sleep\n\n# Pins Used\nBUILT_IN_LED_PIN = 25\nPOT_PIN = 26\n\npot = ADC(POT_PIN)\n\nbuiltin_pwm = PWM(Pin(BUILT_IN_LED_PIN))\nbuiltin_pwm.freq(1000) # 1K Hz\n\nPOLL_DELAY = .1 # poll the pot after this delay in seconds\n\n# repeat forever\nwhile True:\n pot_value = pot.read_u16() # read the value from the pot\n print(\"pot value:\", pot_value)\n builtin_pwm.duty_u16(pot_value)\n sleep(POLL_DELAY)\n
"},{"location":"basics/04-fade-in-and-out/","title":"Fade an LED in and Out","text":"In the prior Blink lab, we turned an LED on an off at different speeds. But what if we want to slowly turn on our LED on and off? In this lab we will show you how to dim your LED to any brightness level you want.
"},{"location":"basics/04-fade-in-and-out/#welcome-to-pulse-width-modulation","title":"Welcome to Pulse Width Modulation","text":"Although digital computers are good at quickly turning signals on and off, they don't really allow us to easily set an output to a given voltage level without complex circuits. But there is an easier way to adjust the brightness of an LED! We can quickly turn the signal to the LED on and off. We can do this so quickly that you can't even see it flicker. Controlling the amount of time a signal is on is all about controlling the width of the ON pulse. That is why this is called Pulse Width Modulation or PWM for short.
With a PWM design there are two things we need to tell the microcontroller:
The rate of change of the pulse is call the frequency. You can set the frequency to be 1,000 changes per second, which is much faster than the human eye can detect. This is done using the following line:
pwm.freq(1000)\n
Note that we can slow the frequency way down and the dimming effect will still work. As an experiment you can change the PWM frequency to around 20 and you will see a distinct flicker as the LED turns on.
Here is the sample program that will slowly dim the builtin LED that is on pin 25:
from machine import Pin, PWM\nfrom time import sleep\n\npwm = PWM(Pin(25))\n\npwm.freq(1000)\n\nwhile True:\n for duty in range(65025):\n pwm.duty_u16(duty)\n sleep(0.0001)\n for duty in range(65025, 0, -1):\n pwm.duty_u16(duty)\n sleep(0.0001)\n
Note that the duty cycle starts at 0 (always off) and moves slowly up to 65,025 (always on). It then does the reverse and slowly dims the LED and then repeats. There is only a 1/10,000 of a delay between these changes so the LED will completely turn on in about six seconds before it starts to dim again.
"},{"location":"basics/04-fade-in-and-out/#pwm-functions","title":"PWM Functions","text":"Here is a list of the PWM functions.
from machine import Pin, PWM\n\npwm0 = PWM(Pin(0)) # create PWM object from a pin\npwm0.freq() # get current frequency\npwm0.freq(1000) # set frequency (1000 cycles per minute)\npwm0.duty_u16() # get current duty cycle, range 0-65535\npwm0.duty_u16(200) # set duty cycle, range 0-65535\npwm0.deinit() # turn off PWM on the pin\n
Make sure you deinit()
to de-initialize the PWM controller after you are done. You may have to trap the stop to do this. For example if a PWM is driving motors, your Stop must send deinit() to each motor controller. See the Interrupt Handlers for details.
The Pico has 26 general purpose input and output pins. However, each pin's power is designed to digitally communicate with other devices and has a limited current capacity of around 17 milliamps according to the Raspberry Pi Pico Datasheet Table 5. 17 milliamps is fine for lighting up an LED. However, motors require much more power. 17 milliamps is not enough current to drive even small motors. Even our small DC hobby motors we use with our robots require around 200 milliamps.
But don't worry! We have two ways around this problem.
Set the frequency to 50Hz (one cycle per 20ms) and the duty value to between 51 (51/1023 * 20ms = 1ms) and 102 (102/1023 * 20ms = 2ms)
"},{"location":"basics/04-motor/#sample-coder","title":"Sample Coder","text":"import machine\n\n# set the 7th from the bottom on right as our motor pin\nmotor_pin = machine.Pin(21, machine.Pin.OUT)\n# allocate a PWM object for controlling the motor speed\nmotor_pwm = machine.PWM(motor_pin)\nmotor_pwm.freq(50) # 50 hertz\nmotor_pwm.duty(51)\n
"},{"location":"basics/04-motor/#references","title":"References","text":"ADC_VREF is the ADC power supply (and reference) voltage, and is generated on Pico by filtering the 3.3V supply. This pin can be used with an external reference if better ADC performance is required. AGND is the ground reference for GPIO26-29, there is a separate analog ground plane running under these signals and terminating at this pin.
graph LR\np[Pico]-->|ADC_VREF 36 row=6| pos(Positive)\np[Pico]-->|AGND 33 row=8| neg(Negative)\np[Pico]-->|GP26 pin=26 ADC0 31 row=10| tap(Center Tap)\n pos(Positive) --- pot(Potentiometer)\n neg(Negative) --- pot(Potentiometer)\n tap(Center Tap) --- pot(Potentiometer)\n
Connect the positive to pin 35 ADC_REF (row 6 on the breadboard) and the negative to pin 33 AGND (row 8 on the breadboard). The Pico has special noise reduction circuits to avoid power supply jitter on these reference pins.
"},{"location":"basics/04-read-pot/#sampling-data","title":"Sampling data","text":"Sometimes the data coming from your Potentiometer is noisy. You can sample the value multiple times and then average the values.
Here is a sample program. Just pass in the pin and a count and it will return the average values. This version waits 5 milliseconds between samples.
def sample_pot(pin, count):\n total = 0\n for i in range(count):\n total += int(pin.read_u16())\n utime.sleep_ms(5)\n return int(total / count)\n
pot_pin_1 = machine.ADC(26)\n# return a value after sampling 10 times\nsample_pot(pot_pin_1, 10)\n
"},{"location":"basics/04-servo/","title":"Micropython Servo Lab","text":"TBD
import machine\nimport pyb\n\n# The pyboard has four simple servo connections\nservo = pyb.Servo(1)\n\nservo.angle(90, 5000)\n
"},{"location":"basics/05-neopixel/","title":"NeoPixels","text":"NeoPixels are Red-Green-Blue LEDs that are designed to makes them easy to control with three wires: GND, +5V and a single serial data line. They are very popular with our students because they are powerful, easy to program and full of bling.
Note
As of March of 2022 there is now built-in support for NeoPixels in the MicroPython 1.18 runtime for the Raspberry Pi RP2040 microcontroller. Although you can still use custom libraries, this tutorial assumes you are using version 1.18 or later.
Controlling NeoPixels is challenging since the timing of data being sent must be very precise. Python alone is not fast enough to send bits out of a serial port. So a small function that uses assembly code is used. This code can be called directly from a neopixel driver file so that the user's don't need to see this code.
MicroPython Example Code on ESP8266
"},{"location":"basics/05-neopixel/#different-types-of-neopixels","title":"Different Types of NeoPixels","text":"There are many different types of NeoPixels. They come in many forms such as strips, rings and matrices.
The most common type of NeoPixels are strips. The strips come in a variety of densities and waterproofing. The most common and easiest to use are the 60 pixels-per-meter type.
"},{"location":"basics/05-neopixel/#circuit-connections","title":"Circuit connections","text":"LED Strip Pico Name Pico Pin Description GND GND 3 Ground 5v VBUS 40 Voltage from the USB bus. Top right with USB on top Data GP22 22 Row 12 on the right sideNote that you can also power most of the LED strips using the 3.3 volts available on Grove connectors. The only difference is the brightness might not be quite as high, but for most applications this will not be a problem.
"},{"location":"basics/05-neopixel/#setup-parameters","title":"Setup Parameters","text":"Our Python code will have four parts:
Here are the import statements we use:
from machine import Pin\nfrom utime import sleep\nfrom neopixel import NeoPixel\n
"},{"location":"basics/05-neopixel/#static-initialization-parameters","title":"Static Initialization Parameters","text":"There are only two values. The number of pixels in the LED strip or LED ring and the pin number the data pin is connected to.
NUMBER_PIXELS = 8\nLED_PIN = 22\n
"},{"location":"basics/05-neopixel/#initialize-the-strip-object","title":"Initialize the Strip Object","text":"To setup the NeoPixel object we just pass it the two parameters like this:
strip = NeoPixel(Pin(LED_PIN), NUMBER_PIXELS)\n
Here is the full initialization code:
from machine import Pin\nfrom utime import sleep\nfrom neopixel import NeoPixel\n\nNUMBER_PIXELS = 8\nLED_PIN = 22\nstrip = NeoPixel(Pin(LED_PIN), NUMBER_PIXELS)\n
"},{"location":"basics/05-neopixel/#sample-programs","title":"Sample Programs","text":"Now we are ready to write our first small test program!
"},{"location":"basics/05-neopixel/#move-red-pixel-across-strip","title":"Move Red Pixel Across Strip","text":"from machine import Pin\nfrom time import sleep\nfrom neopixel import NeoPixel\n\nNUMBER_PIXELS = 60\nLED_PIN = 0\n\nstrip = NeoPixel(Pin(LED_PIN), NUMBER_PIXELS)\n\nwhile True:\n for i in range(0, NUMBER_PIXELS):\n strip[i] = (255,0,0) # red=255, green and blue are 0\n strip.write() # send the data from RAM down the wire\n sleep(.1) # keep on 1/10 of a second\n strip[i] = (0,0,0) # change the RAM back but don't resend the data\n
"},{"location":"basics/05-neopixel/#fade-in-and-out","title":"Fade in and Out","text":"Make the first pixel fade the red color in and out. We do this by slowly turning up the color level of the red on strip[0].
Off: strip[0] = (0,0,0)\nRed Very Dim: strip[0] = (1,0,0)\nDim Red: strip[0] = (10,0,0)\nRed Half Brightness: strip[0] = (128,0,0)\nRed Full Brightness: strip[0] = (255,0,0)\n
We start a 0 and go up to 255. Then we go back from 255 back down to zero. We delay about 5 milliseconds between each of the 255 brightness levels.
from machine import Pin\nfrom time import sleep\nfrom neopixel import NeoPixel\n\n\nNUMBER_PIXELS = 60\nLED_PIN = 0\n\nstrip = NeoPixel(Pin(LED_PIN), NUMBER_PIXELS)\n\ndelay = .005\n\nwhile True:\n for i in range(0, 255):\n strip[0] = (i,0,0) # red=255, green and blue are 0\n strip.write() # send the data from RAM down the wire\n sleep(delay)\n for i in range(255, 0, -1):\n strip[0] = (i,0,0)\n strip.write()\n sleep(delay)\n
"},{"location":"basics/05-neopixel/#heartbeat-lab","title":"Heartbeat Lab","text":"What if you were building a robot and you wanted to flash the LED to look like a human heartbeat? Instead of slowing fading in and out, you would want the brightness to follow the electrical signals coming from the heart. This is called an electro cardiogram (EKG) and it look like this:
Notice that the signal is low for about one second and then it spikes up to maximum brightness and then comes back down. When we are moving the brightness up and down, we don't have to pause between each of the 256 brightness values. The eye can't usually see the intermediate brightness values if the brightness is changing quickly. To make our code efficient we can skip over 9 out of 10 of the brightness gradations between 0 and 255. We call this the skip_interval
in our code below.
The following code emulates this heart beat pattern:
from machine import Pin\nfrom time import sleep\nfrom neopixel import NeoPixel\n\n# Most people have a heart rate of around 60-70 beats per minute\n# If we add a once second delay between \"beats\" you can make and LED\n# look like a beating heart.\n\nNUMBER_PIXELS = 1\nLED_PIN = 0\n\nstrip = NeoPixel(Pin(LED_PIN), NUMBER_PIXELS)\n\nramp_delay = .001\nbeat_delay = 1\nskip_interval = 10\n\nwhile True:\n # ramp brightness up using the ramp_delay\n for i in range(0, 255, skip_interval):\n strip[0] = (i,0,0)\n strip.write()\n sleep(ramp_delay)\n # ramp brightness down using the same delay\n for i in range(255, 0, -skip_interval):\n strip[0] = (i,0,0)\n strip.write()\n sleep(ramp_delay)\n strip[0] = (0,0,0)\n strip.write()\n sleep(beat_delay)\n
"},{"location":"basics/05-neopixel/#move-red-green-and-blue","title":"Move Red, Green and Blue","text":"The following program will just take the block of code in the for loop above and duplicate it three times, one for red, one for blue and one for green.
from machine import Pin\nfrom neopixel import NeoPixel\nfrom time import sleep\n\nNUMBER_PIXELS = 8\nLED_PIN = 0\n\nstrip = NeoPixel(Pin(LED_PIN), NUMBER_PIXELS)\n\n# we use the same brightness for each color\nbrightness = 25\ndelay = .1\n# here we define variables for each color\nred = (brightness, 0, 0)\ngreen = (0, brightness, 0)\nblue = (0, 0, brightness)\nwhile True:\n # draw red up the strip\n for i in range(0, NUMBER_PIXELS):\n strip[i] = red\n strip.write()\n sleep(delay)\n strip[i] = (0,0,0)\n # draw blue up the strip\n for i in range(0, NUMBER_PIXELS):\n strip[i] = green\n strip.write()\n sleep(delay)\n strip[i] = (0,0,0)\n # draw green up the strip\n for i in range(0, NUMBER_PIXELS):\n strip[i] = blue\n strip.write()\n sleep(delay)\n strip[i] = (0,0,0)\n
"},{"location":"basics/05-neopixel/#rainbow-cycle","title":"Rainbow Cycle","text":"The program cycles each pixel through all the colors in a rainbow. It uses two functions:
from machine import Pin\nfrom neopixel import NeoPixel\nfrom utime import sleep\n\nNEOPIXEL_PIN = 22\nNUMBER_PIXELS = 8\nstrip = NeoPixel(Pin(NEOPIXEL_PIN), NUMBER_PIXELS)\n\ndef wheel(pos):\n # Input a value 0 to 255 to get a color value.\n # The colors are a transition r - g - b - back to r.\n if pos < 0 or pos > 255:\n return (0, 0, 0)\n if pos < 85:\n return (255 - pos * 3, pos * 3, 0)\n if pos < 170:\n pos -= 85\n return (0, 255 - pos * 3, pos * 3)\n pos -= 170\n return (pos * 3, 0, 255 - pos * 3)\n\ndef rainbow_cycle(wait):\n global NUMBER_PIXELS, strip\n for j in range(255):\n for i in range(NUMBER_PIXELS):\n rc_index = (i * 256 // NUMBER_PIXELS) + j\n # print(rc_index)\n strip[i] = wheel(rc_index & 255)\n strip.write()\n sleep(wait)\n\ncounter = 0\noffset = 0\nwhile True:\n print('Running cycle', counter)\n rainbow_cycle(.05)\n counter += 1\n
"},{"location":"basics/05-neopixel/#references","title":"References","text":"One June 30th, 2022 the Raspberry Pi Foundation announced the availability of the Raspberry Pi Pico W. This $6 microprocessor now supports WiFi and with a software upgrade it may soon support Bluetooth.
The Pico W supports 2.4 Ghz 802.11n wireless networking. For MicroPython, we can use a MicroPython library built around the lwip TCP/IP stack. This stack is accessible using the MicroPython network functions.
The WiFi chip used is the Infineon CYW43439 chip. This chip also uses an ARM architecture and has extensive support for Bluetooth wireless communication.
You can read more about the capabilities of the WiFi/Bluetooth chip by reading the Infineon CYW43439 Datasheet. I found it interesting that the CYW43439 chip has 512KB of SRAM - almost double what the RP2040 chip contains!
"},{"location":"basics/06-wireless/#compatibility-with-prior-code","title":"Compatibility with Prior Code","text":"The Pico W code is very similar to prior versions of the Pico with a few small exceptions. One of these is the fact that we must now use a symbolic label called an alias such as Pin(\"LED\")
instead of Pin(25)
to access the LED pin, not a hardwired PIN number. This allows us to keep our code more portable as the underlying hardware changes.
from machine import Pin, Timer\n\n# was Pin(25)\nled = Pin(\"LED\", Pin.OUT)\ntim = Timer()\ndef tick(timer):\n global led\n led.toggle()\n\ntim.init(freq=2.5, mode=Timer.PERIODIC, callback=tick)\n
See the new Sample Blink code on the Raspberry Pi Examples site.
"},{"location":"basics/06-wireless/#getting-the-new-pico-w-image","title":"Getting the New Pico W Image","text":"I had to download a brand new image for the Pico W runtime from the Raspberry Pi Foundation Software Site
After I downloaded the new image and ran a Stop/Reset on Thonny I got the following prompt:
MicroPython v1.19.1-88-g74e33e714 on 2022-06-30; Raspberry Pi Pico W with RP2040\nType \"help()\" for more information.\n>>> \n
Note that the \"Pico W\" is listed in the prompt. If you do not see the \"W\" then the network code will not work.
"},{"location":"basics/06-wireless/#beginner-wifi-programs","title":"Beginner WiFi Programs","text":"We will store the name of our local WiFi network we wish to connect to and the password for that name in a file called secrets.py. This is called you WiFi \"access point\" and the variable name to store the name is called the `SSID
. We will need to make sure we never save this file into a public GitHub repo by adding this file to our .gitignore file.
By convention, we put both our SSID and password in a python file called \"secrets.py\". This file should never be checked into a public source code repository. We can add secrets.py
to the .gitignore file to make sure the secrets.py is never checked into GitHub and exposing your passwords to everyone.
SSID = \"MY_WIFI_NETWORK_NAME\"\nPASSWORD = \"MY_WIFI_PASSWORD\"\n
By importing the secrets.py file you can then reference your network name like this:
print('Connecting to WiFi Network Name:', secrets.SSID)\n
"},{"location":"basics/06-wireless/#testing-your-wifi-access-point-connection","title":"Testing Your WiFi Access Point Connection","text":"Here is a very simple script to test see if your network name and password are correct. This script may work, but as we will see, it is both slow and potentially unreliable.
import network\nimport secrets\nfrom utime import sleep\n\nprint('Connecting to WiFi Network Name:', secrets.SSID)\nwlan = network.WLAN(network.STA_IF)\nwlan.active(True) # power up the WiFi chip\nprint('Waiting for wifi chip to power up...')\nsleep(3) # wait three seconds for the chip to power up and initialize\nwlan.connect(secrets.SSID, secrets.PASSWORD)\nprint('Waiting for access point to log us in.')\nsleep(2)\nif wlan.isconnected():\n print('Success! We have connected to your access point!')\n print('Try to ping the device at', wlan.ifconfig()[0])\nelse:\n print('Failure! We have not connected to your access point! Check your secrets.py file for errors.')\n
Returns:
Connecting to WiFi Network Name: MY_WIFI_NETWORK_NAME\nWaiting for wifi chip to power up...\nWaiting for access point to log us in...\nSuccess! We have connected to your access point!\nTry to ping the device at 10.0.0.70\n
If the result is a Failure
you should check the name of the network and the password and that you are getting a strong WiFi signal where you are testing.
Note that we are using the sleep()
function to insert delays into our code. However, the results may actually be faster or slower than our sleep times. Our next step is to add logic that will test to see if the networking device is ready and if our local access point allows us to login correctly.
Sometimes we want to keep checking if our access point is connected before we begin using our connection. To do this we can create a while loop and continue in the loop while we are not connected.
import network\nimport secrets\nfrom utime import sleep, ticks_ms, ticks_diff\n\nprint('Connecting to WiFi Network Name:', secrets.SSID)\nwlan = network.WLAN(network.STA_IF)\nwlan.active(True)\n\nstart = ticks_ms() # start a millisecond counter\n\nif not wlan.isconnected():\n wlan.connect(secrets.SSID, secrets.PASSWORD)\n print(\"Waiting for connection...\")\n counter = 0\n while not wlan.isconnected():\n sleep(1)\n print(counter, '.', sep='', end='', )\n counter += 1\n\ndelta = ticks_diff(ticks_ms(), start)\nprint(\"Connect Time:\", delta, 'milliseconds')\nprint('IP Address:', wlan.ifconfig()[0])\n
This code also supports a timer that will display the number of seconds for the access point to become valid in the console. The first time after you power on, this may take several seconds. After you are connected the connection will be cached and the time will be 0 milliseconds.
First run upon power on might take several seconds:
>>> %Run -c $EDITOR_CONTENT\nConnecting to WiFi Network Name: MY_NETWORK_NAME\nWaiting for connection...\n0.1.2.3.Connect Time: 4640\nIP Address: 10.0.0.70\n
The second and consecutive runs will use a cached connection.
>>> %Run -c $EDITOR_CONTENT\nConnecting to WiFi Network Name: MY_NETWORK_NAME\nConnect Time: 0 milliseconds\nIP Address: 10.0.0.70\n>>>\n
"},{"location":"basics/06-wireless/#error-handling","title":"Error Handling","text":"lan = network.WLAN(network.STA_IF)\nwlan.active(True)\nwlan.connect(ssid, password)\n\n# Wait for connect or fail\nmax_wait = 10\nwhile max_wait > 0:\n if wlan.status() < 0 or wlan.status() >= 3:\n break\n max_wait -= 1\n print('waiting for connection...')\n time.sleep(1)\n\n# Handle connection error\nif wlan.status() != 3:\n raise RuntimeError('network connection failed')\nelse:\n print('connected')\n status = wlan.ifconfig()\n print( 'ip = ' + status[0] )\n
The full TCP/IP stack is running on your Pico W. You should be able to ping the pico using the IP address returned by the status[0] of the wlan.ifconfig() function above.
"},{"location":"basics/06-wireless/#testing-http-get","title":"Testing HTTP GET","text":"The following example was taken from Tom's Hardware
import network\nimport secrets\nfrom utime import sleep, ticks_ms, ticks_diff\nimport urequests\n\nwlan = network.WLAN(network.STA_IF)\nwlan.active(True)\nwlan.connect(secrets.SSID, secrets.PASSWORD)\n\nstart = ticks_ms() # start a millisecond counter\n\nastronauts = urequests.get(\"http://api.open-notify.org/astros.json\").json()\n\ndelta = ticks_diff(ticks_ms(), start)\n\nnumber = astronauts['number']\nprint('There are', number, 'astronauts in space.')\nfor i in range(number):\n print(i+1, astronauts['people'][i]['name'])\n\nprint(\"HTTP GET Time in milliseconds:\", delta)\n
Returns:
There are 10 astronauts in space.\n1 Oleg Artemyev\n2 Denis Matveev\n3 Sergey Korsakov\n4 Kjell Lindgren\n5 Bob Hines\n6 Samantha Cristoforetti\n7 Jessica Watkins\n8 Cai Xuzhe\n9 Chen Dong\n10 Liu Yang\nHTTP GET Time in milliseconds: 786\n
"},{"location":"basics/06-wireless/#listing-the-functions-in-your-network-library","title":"Listing the Functions in Your Network Library","text":"The network library provided by the Raspberry Pi Foundation for the Pico W is new an may change as new functions are added. To get the list of functions in your network library you can use the Python help(network) at the prompt or use the dir()
function.
You can also get a list of the network functions by typing help(network)
at the Python REPL prompt.
help(network)\nobject <module 'network'> is of type module\n __name__ -- network\n route -- <function>\n WLAN -- <class 'CYW43'>\n STAT_IDLE -- 0\n STAT_CONNECTING -- 1\n STAT_WRONG_PASSWORD -- -3\n STAT_NO_AP_FOUND -- -2\n STAT_CONNECT_FAIL -- -1\n STAT_GOT_IP -- 3\n STA_IF -- 0\n AP_IF -- 1\n
"},{"location":"basics/06-wireless/#network-dir-function","title":"Network dir() Function","text":"import network\nfunction_list = dir(network)\n\nfor function in function_list:\n print(function)\n
Returns:
__class__\n__name__\nAP_IF\nSTAT_CONNECTING\nSTAT_CONNECT_FAIL\nSTAT_GOT_IP\nSTAT_IDLE\nSTAT_NO_AP_FOUND\nSTAT_WRONG_PASSWORD\nSTA_IF\nWLAN\nroute\n
"},{"location":"basics/06-wireless/#urequest","title":"Urequest","text":"It is easy to communicate with non-SSL protected HTTP protocols sites using the WLAN `urequest
function. It supports the standard GET, POST, PUT and DELETE functions.
help(urequests)\nobject <module 'urequests' from 'urequests.py'> is of type module\n head -- <function head at 0x2000b740>\n post -- <function post at 0x2000ba80>\n delete -- <function delete at 0x2000bbb0>\n get -- <function get at 0x2000b750>\n __file__ -- urequests.py\n Response -- <class 'Response'>\n patch -- <function patch at 0x2000baf0>\n put -- <function put at 0x2000ba90>\n usocket -- <module 'lwip'>\n __name__ -- urequests\n request -- <function request at 0x2000bb80>\n
"},{"location":"basics/06-wireless/#getting-the-macethernet-access","title":"Getting the MAC/Ethernet Access","text":"You can get the device MAC/Ethernet address and test the roundtrip time between the RP2040 and the WiFi chip using the MAC address function.
import network\nfrom utime import sleep, ticks_us, ticks_diff\n\nprint('Getting MAC/Ethernet Address for this device.')\n\nstart = ticks_us() # start a millisecond counter\nwlan = network.WLAN(network.STA_IF)\nwlan.active(True) # this line powers up the chip - it takes about 2.5 seconds\n\n# This returns a byte array of hex numbers\nmac_addess = wlan.config('mac')\nprint('Time in microseconds:', ticks_diff(ticks_us(), start))\n# each MAC address is 6 bytes or 48 bits\nprint(\"Hex byte array:\", mac_addess, 'length:', len(mac_addess))\n\n# This should be in hex per the Notational Conventions\n# https://en.wikipedia.org/wiki/MAC_address#Notational_conventions\n# b'(\\xcd\\xc1\\x015X'\n# 28:cd:c1:1:35:58\n# format in MAC Notational Convention\nfor digit in range(0,5):\n print(str(hex(mac_addess[digit]))[2:4], ':', sep='', end = '')\nprint(str(hex(mac_addess[5]))[2:4] )\n
First Time After Power On Results:
Getting MAC/Ethernet Address for this device.\nTime in microseconds: 2584424\nHex byte array: b'(\\xcd\\xc1\\x015X' length: 6\n28:cd:c1:1:35:58\n
Note that it takes about 2.5 seconds just to power on the chip before we get the MAC address.
Subsequent Times
Getting MAC/Ethernet Address for this device.\nTime in microseconds: 211\nHex byte array: b'(\\xcd\\xc1\\x015X' length: 6\n28:cd:c1:1:35:58\n
Note
We must add the wlan.active(True)
line to this code. If we don't do this, the wifi device will not be powered up and we can't get the MAC address. The function will return all zeros.
The MAC address is six bytes or \"octets\". The first three octets are assigned to the organization that created the device. The second three octets are assigned by the organization that created the device. See the Wikipedia Page on MAC Address for more information. If you run this on your Pico W the first octets should be similar.
Here are the two MAC addresses for two different Pico W devices:
28:cd:c1:1:35:54\n28:cd:c1:1:35:58\n
Because they were purchased together, their MAC address are very similar.
I ran this program on my Pico W and I got times of between 214 and 222 microseconds. This shows you that it takes about 100 microseconds to send a request from the RP2040 to the CYW43439 WiFi chip and about 100 milliseconds to return the results. This time lag represents some of the key performance limitations in using the Pico W for high-performance networking.
"},{"location":"basics/06-wireless/#advanced-wifi-programs","title":"Advanced WiFi Programs","text":"Once we have mastered the basics of connecting to a local access point and returning our IP address, we are no ready to build some sample Internet of Things applications.
"},{"location":"basics/06-wireless/#using-the-pico-w-as-a-web-server","title":"Using the Pico W as a Web Server","text":"This program turns your Pico W into a small web server. The web page has two links on it. One link will turn the on-board LED on and the other link will turn the LED off.
Screen image of Pico W Web Server:
# Code taken from https://www.cnx-software.com/2022/07/03/getting-started-with-wifi-on-raspberry-pi-pico-w-board/\nimport network\nimport socket\nimport time\nimport secrets\n\nfrom machine import Pin\n\n# Select the onboard LED\nled = machine.Pin(\"LED\", machine.Pin.OUT)\n\nwlan = network.WLAN(network.STA_IF)\nwlan.active(True)\nwlan.connect(secrets.SSID, secrets.PASSWORD)\nstateis = \"LED is OFF\"\n\nhtml = \"\"\"<!DOCTYPE html>\n<html>\n <head>\n <title>Web Server On Pico W </title>\n </head>\n <body>\n <h1>Pico Wireless Web Server</h1>\n <p>%s</p>\n <a href=\"/light/on\">Turn On</a>\n <a href=\"/light/off\">Turn Off</a>\n </body>\n</html>\n\"\"\"\n\n# Wait for connect or fail\nmax_wait = 10\nwhile max_wait > 0:\n if wlan.status() < 0 or wlan.status() >= 3:\n break\n max_wait -= 1\n print('waiting for connection...')\n time.sleep(1)\n\n# Handle connection error\nif wlan.status() != 3:\n raise RuntimeError('network connection failed')\nelse:\n print('We are connected to WiFI access point:', secrets.SSID)\n status = wlan.ifconfig()\n print( 'The IP address of the pico W is:', status[0] )\n\n# Open socket\naddr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]\nprint('addr:', addr)\ns = socket.socket()\n#if not addr:\ns.bind(addr)\ns.listen(1)\n\nprint('listening on', addr)\n\n# Listen for connections\nwhile True:\n try:\n cl, addr = s.accept()\n print('client connected from', addr)\n request = cl.recv(1024)\n print(request)\n request = str(request)\n led_on = request.find('/light/on')\n led_off = request.find('/light/off')\n print( 'led on = ' + str(led_on))\n print( 'led off = ' + str(led_off))\n\n if led_on == 6:\n print(\"led on\")\n led.value(1)\n stateis = \"LED is ON\"\n\n if led_off == 6:\n print(\"led off\")\n led.value(0)\n stateis = \"LED is OFF\"\n # generate the we page with the stateis as a parameter\n response = html % stateis\n cl.send('HTTP/1.0 200 OK\\r\\nContent-type: text/html\\r\\n\\r\\n')\n cl.send(response)\n cl.close()\n\n except OSError as e:\n cl.close()\n print('connection closed')\n
"},{"location":"basics/06-wireless/#shttp-support","title":"SHTTP Support","text":"Warning
This code is not working. I believe we need to get a SSL certificate for SSL to work. To do this I think we need to use a command line tool to generate a certificate for the device and store it in RAM.
import network\nimport secrets\nimport time\nimport urequests\nwlan = network.WLAN(network.STA_IF)\nwlan.active(True)\nwlan.connect(secrets.SSID, secrets.PASSWORD)\nprint(wlan.isconnected())\nmy_ip = urequests.get(\"https://api.myip.com/\").json()\nprint(im_pi)\n
"},{"location":"basics/06-wireless/#sending-notifications","title":"Sending Notifications","text":"We can connect to a remote server to send text and e-mail notifications if specific events occur on our devices. To do this you must have credentials on some system that response to messages such as IFTTT or an MQTT server.
TBD
"},{"location":"concept-cards/01-intro/","title":"MicroPython Concept Cards","text":"This is a list of concept cards that are related to MicroPython. Our goal is to print these concept cards on 1/2 sheet laminated paper and have them available at the center of the tables in our CoderDojo Classrooms.
Please let me know if you are willing to volunteer.
Each concept card will have the content stored in a MarkDown file. A Python script will convert the MarkDown into HTML with a CSS file that paginates the output.
Concept Cards
"},{"location":"concept-cards/01-intro/#beginning-concepts","title":"Beginning concepts","text":""},{"location":"concept-cards/01-intro/#physical-computing","title":"Physical Computing","text":""},{"location":"concept-cards/01-intro/#sensors","title":"Sensors","text":""},{"location":"concept-cards/01-intro/#switches-and-buttons","title":"Switches and Buttons","text":""},{"location":"concept-cards/01-intro/#sensing-light","title":"Sensing Light","text":""},{"location":"concept-cards/01-intro/#sensing-distance","title":"[Sensing Distance]","text":""},{"location":"concept-cards/01-intro/#ping-distance-sensors","title":"Ping Distance Sensors","text":""},{"location":"concept-cards/01-intro/#time-of-flight-distance-sensors","title":"Time of Flight Distance Sensors","text":""},{"location":"concept-cards/01-intro/#motors","title":"Motors","text":""},{"location":"concept-cards/01-intro/#dc-motors","title":"DC Motors","text":""},{"location":"concept-cards/01-intro/#servos","title":"Servos","text":""},{"location":"concept-cards/01-intro/#stepper-motors","title":"Stepper Motors","text":""},{"location":"concept-cards/01-intro/#batteries-and-power","title":"Batteries and Power","text":""},{"location":"concept-cards/01-intro/#aa-batteries","title":"AA Batteries","text":""},{"location":"concept-cards/01-intro/#lipo-batteries","title":"LiPo Batteries","text":""},{"location":"concept-cards/01-intro/#intermediate-concepts","title":"Intermediate Concepts","text":""},{"location":"concept-cards/01-intro/#interrupts","title":"Interrupts","text":""},{"location":"concept-cards/01-intro/#advanced-concepts","title":"Advanced Concepts","text":""},{"location":"concept-cards/01-intro/#multicore-programming","title":"MultiCore Programming","text":""},{"location":"concept-cards/01-intro/#references","title":"References","text":"https://www.sitepoint.com/css-printer-friendly-pages/
https://www.jotform.com/blog/css-perfect-print-stylesheet-98272/
"},{"location":"debugging/28-debugging-python/","title":"How to Debug Micropython","text":""},{"location":"debugging/28-debugging-python/#listing-the-modules","title":"Listing the Modules","text":"help('modules')\n
Result:
__main__ gc uasyncio/funcs uos\n_boot machine uasyncio/lock urandom\n_onewire math uasyncio/stream ure\n_rp2 micropython ubinascii uselect\n_thread onewire ucollections ustruct\n_uasyncio rp2 uctypes usys\nbuiltins uasyncio/__init__ uhashlib utime\nds18x20 uasyncio/core uio uzlib\nframebuf uasyncio/event ujson\nPlus any modules on the filesystem\n
"},{"location":"debugging/28-debugging-python/#micropython-issues","title":"Micropython issues","text":"https://github.com/micropython/micropython/issues
"},{"location":"debugging/29-debugging-spi/","title":"Debugging SPI","text":"In this lab we use a logic analyzer to debug the SPI protocol being used to drive a sample OLED device. We will be using the the 8 port Saleae Logic Analyser. The retail cost is about $399.00 although there are lower cost logic analyzer available.
"},{"location":"debugging/29-debugging-spi/#the-ssd1306-spi-oled-timing-diagram","title":"The SSD1306 SPI OLED Timing Diagram","text":"The OLED display is a read-only interface. It does not send any data back to the microcontroller, so there is no MOSI connection. The data is transmitted on the SDK line when the SCL line goes high. The CS line must be low for the OLED to be active.
For details, see section 8.1.3 MCU Serial Interface on page 21 of the SSD1305 132 x 64 Dot Matrix OLED/PLED Segment/Common Driver with Controller.
"},{"location":"debugging/29-debugging-spi/#oled-spi-settings","title":"OLED SPI settings","text":"Our OLED device has seven wires. In addition to power and ground there a five data connections we will be observing on our logic analyzer.
Saleae Logic Analyser SPI Logic Analyser Settings
"},{"location":"debugging/29-debugging-spi/#check-a-working-device","title":"Check a Working Device","text":"The first thing we want to see is what the signals to a working SPI OLED should be. There are plenty of working drivers for the Arduino, so I hooked one up to the Logic analizer to see what they were.
"},{"location":"debugging/29-debugging-spi/#viewing-data-clock-and-res","title":"Viewing Data Clock and RES","text":"All five signals
DC and CS signals have a larger period.
Our Clock (SCL) has 8 positive pulses with a width of .4167 microseconds. This means that the positve/negative combined width has a period of 2 * .4167 = .8333 microseconds. This can be converted into a frequency of 1.2 megahertz.
"},{"location":"debugging/29-debugging-spi/#references","title":"References","text":"Video on how to use the Saleae Logic Analyzer
https://www.youtube.com/watch?v=Ak9R4yxQPhs
"},{"location":"debugging/29a-debugging-i2c/","title":"Debugging I2C Bus","text":""},{"location":"displays/graph/01-intro/","title":"Introduction to OLED Displays","text":"Four colors of 2.44\" OLED displays from DIY More. We can purchase them on EBay for around $18 each.
We use small OLED displays in many of our labs because:
The first step is to find out what type of display graphics chip is used in your OLED.
In these lessons we will assume you have a 128X64 or similar OLED display. Many of these displays can be purchased for around $4 on eBay. Many of these displays use the popular SSD1306 chip to drive the displays. There are also to communication options:
Updating a 128X64 display using I2C takes around 37ms. When using the SPI interface, updating the display can be reduced to around 2.79ms.
These labs will assume these parameters, but you can modify the labs to use different sizes and display driver chips by only modifying a few lines of code.
"},{"location":"displays/graph/01-intro/#i2c-scanner","title":"I2C Scanner","text":"Because your microcontroller might have multiple displays on it, their must be some way to address the devices using an address. Most of the devices come with a default address of decimal value 60 (hex value X3C). To test this the i3c module has a i2c scan function.
import machine\nsda=machine.Pin(0) # row one on our standard Pico breadboard\nscl=machine.Pin(1) # row two on our standard Pico breadboard\ni2c=machine.I2C(0, sda=sda, scl=scl, freq=400000)\nprint(\"Device found at decimal\", i2c.scan())\n
If you don't see a return value of \"60\" or similar, then you need to check your wiring and make sure that you have an I2C (not an SPI) device.
"},{"location":"displays/graph/01-intro/#references","title":"References","text":"At the beginning of of your Python programs there is usually a few lines of setup instruction to tell the system which libraries to use, what pins to assign and what devices to initialize.
We will first look at the simple I2C setup. Then we will look at the SPI setup.
"},{"location":"displays/graph/02-oled-setup/#i2c-scanner","title":"I2C Scanner","text":"Because your microcontroller might have multiple I2C devices and displays on it, there must be some way to address the devices using an address. Most of the devices come with a default address of decimal value 60 (hex value X3C). To test this the i3c module has a i2c scan function.
import machine\nsda=machine.Pin(0)\nscl=machine.Pin(1)\ni2c=machine.I2C(0, sda=sda, scl=scl, freq=400000)\nprint(\"Device found at decimal\", i2c.scan())\n
returns: [60]
returns: [60]
"},{"location":"displays/graph/02-oled-setup/#ssd1306-examples","title":"SSD1306 Examples","text":""},{"location":"displays/graph/02-oled-setup/#ssd1306-i2c-setup","title":"SSD1306 I2C Setup","text":"from ssd1306 import SSD1306_I2C\noled = SSD1306_I2C(128, 64, i2c)\noled.text('Hello World!', 0, 0, 1)\noled.show()\n
"},{"location":"displays/graph/02-oled-setup/#ssd1306-spi-setup","title":"SSD1306 SPI Setup","text":"Back connections:
Front labels on OLED with SPI:
Here is the connection diagram:
Here is the code:
import machine import ssd1306\nspi_sck=machine.Pin(2)\nspi_tx=machine.Pin(3)\nspi=machine.SPI(0, baudrate=100000, sck=spi_sck, mosi=spi_tx)\nCS = machine.Pin(1)\nDC = machine.Pin(4)\nRES = machine.Pin(5)\noled = ssd1306.SSD1306_SPI(128, 64, spi, DC, RES, CS)\noled.text('Hello World!', 0, 0, 1)\noled.show()\n
"},{"location":"displays/graph/02-oled-setup/#ssh1106-i2c-setup","title":"SSH1106 I2C Setup","text":"from machine import Pin, I2C\nimport sh1106\n\nsda=machine.Pin(0)\nscl=machine.Pin(1)\ni2c = I2C(0, scl=scl, sda=sda, freq=400000)\noled = SH1106_I2C(128, 64, i2c)\noled.text('Hello World!', 0, 0, 1)\noled.show()\n
"},{"location":"displays/graph/02-oled-setup/#references","title":"References","text":"Micropython SSD1306 Driver on GitHub
"},{"location":"displays/graph/03-basic-drawing/","title":"Basic Drawing","text":""},{"location":"displays/graph/03-basic-drawing/#basic-draw-functions","title":"Basic Draw Functions","text":"For our beginning labs we will just do some basic drawing. We will start out with just four functions:
Let's assume that we have a four wire OLED that uses the popular SSD1306 chip with 128X64 pixels. We call our oled \"oled\" using the following line:
from ssd1306 import SSD1306_I2C\noled = SSD1306_I2C(128, 64, i2c)\n
Function Description Parameters oled.fill(0) Fill the display with white or black 0=black and 1=white oled.text(\"Hello\", Draw text String, x (horizontal from left edge) and y (vertical from the top)Example: Draw \"Hello World\" 40 over and 10 down. oled.text(\"Hello World!\", 40, 10) show Show the display Send the current frame buffer to the display. You must do this after you make and changes to the Framebuffer. The full program would look like this:
from ssd1306 import SSD1306_I2C\noled = SSD1306_I2C(128, 64, i2c)\noled.fill(0)\noled.text(\"Hello World!\", 0, 0)\noled.show()\n
This would display the following:
"},{"location":"displays/graph/03-basic-drawing/#full-list-of-drawing-functions","title":"Full list of Drawing Functions","text":"Every drawing library might have slightly different functions. But we can quickly see the functions that we want by using the dir() function on the SSD1306_I2C class.
from ssd1306 import SSD1306_I2C\nprint(dir(SSD1306_I2C))\n
This returns the following list: ['__class__', '__init__', '__module__', '__name__', '__qualname__',\n'__bases__', '__dict__', 'blit', 'fill', 'fill_rect', 'hline',\n'invert', 'line', 'pixel', 'rect', 'scroll', 'text', 'vline',\n'init_display', 'write_cmd', 'show', 'poweroff', 'poweron',\n'contrast', 'write_data']\n
Technically, these are called methods of the SSD1306_I2C class. The ones that begin and end with double underscores are class methods for creating new object instances. The rest of the items on the list are the drawing functions. The following are relevant for the SSD1306_I2C display.
The display has (0,0) in the upper left corner. X is horizontal (width) and Y is vertical (height). The state or color is 0=off (black) and 1=on (white).
Function Description Example blit(fbuf, x, y, color) Bit Level Transfer blit(fbuf, 1, 1) fill(state) Fill Fill with black (0) or white(1) fill_rect Fill a rectangle hline(x, x, length, state) Draw a horizontal line Draw a horizontal line at the top of the display: oled.hline(0, 0, 127, 1) invert() invert the display Filp the orientation of the display line(x1,y1,x2,y2) draw a line at any angle Horizontal oled.line(0,0, 127, 63, 1) pixel(x,y, color) Draw a single point on the screen rect(x, y, width, height) Draw an empty rectangle scroll(x,y) Scroll the display text(x,y,color) Write text at a point vline(x,y,length, color) Draw a Vertical Line oled.vline(width - 1, 0, height - 1, 1) # right edge init_display() Initialize the display write_cmd Write a command to the display show() Update the display from the frame buffer poweroff() poweron() contrast() write_data()"},{"location":"displays/graph/03-basic-drawing/#pixel-drawing-example","title":"Pixel Drawing Example","text":"ICON = [\n [ 0, 0, 0, 0, 0, 0, 0, 0, 0],\n [ 0, 1, 1, 0, 0, 0, 1, 1, 0],\n [ 1, 1, 1, 1, 0, 1, 1, 1, 1],\n [ 1, 1, 1, 1, 1, 1, 1, 1, 1],\n [ 1, 1, 1, 1, 1, 1, 1, 1, 1],\n [ 0, 1, 1, 1, 1, 1, 1, 1, 0],\n [ 0, 0, 1, 1, 1, 1, 1, 0, 0],\n [ 0, 0, 0, 1, 1, 1, 0, 0, 0],\n [ 0, 0, 0, 0, 1, 0, 0, 0, 0],\n]\n\ndisplay.fill(0) # Clear the display\nfor y, row in enumerate(ICON):\n for x, c in enumerate(row):\n display.pixel(x, y, c) \n\ndisplay.show()\n
"},{"location":"displays/graph/03-basic-drawing/#drawing-tutorial-and-primitives","title":"Drawing Tutorial and Primitives","text":"Taken from the MicroPython site: Using a SSD1306 OLED display - although the path name imply the ESP8266, these functions also run on the Raspberry Pi Pico.
display.poweroff() # power off the display, pixels persist in memory\ndisplay.poweron() # power on the display, pixels redrawn\ndisplay.contrast(0) # dim\ndisplay.contrast(255) # bright\ndisplay.invert(1) # display inverted\ndisplay.invert(0) # display normal\ndisplay.rotate(True) # rotate 180 degrees\ndisplay.rotate(False) # rotate 0 degrees\ndisplay.show() # write the contents of the FrameBuffer to display memory\n
display.fill(0) # fill entire screen with colour=0\ndisplay.pixel(0, 10) # get pixel at x=0, y=10\ndisplay.pixel(0, 10, 1) # set pixel at x=0, y=10 to colour=1\ndisplay.hline(0, 8, 4, 1) # draw horizontal line x=0, y=8, width=4, colour=1\ndisplay.vline(0, 8, 4, 1) # draw vertical line x=0, y=8, height=4, colour=1\ndisplay.line(0, 0, 127, 63, 1) # draw a line from 0,0 to 127,63\ndisplay.rect(10, 10, 107, 43, 1) # draw a rectangle outline 10,10 to 107,43, colour=1\ndisplay.fill_rect(10, 10, 107, 43, 1) # draw a solid rectangle 10,10 to 107,43, colour=1\ndisplay.text('Hello World', 0, 0, 1) # draw some text at x=0, y=0, colour=1\ndisplay.scroll(20, 0) # scroll 20 pixels to the right\n
"},{"location":"displays/graph/03-basic-drawing/#working-with-the-framebuf","title":"Working with the framebuf","text":"A frame buffer is a region of RAM that holds an exact image of what is on the display. The data can be copied from the framebuffer memory with the blit()
(BLock Transfer) operation that copies a rectangular area of one framebuffer to another framebuffer.
Here is an example of the blit() function:
oled.blit(my_frame_buf, 10, 10, 0)\n
# draw another FrameBuffer on top of the current one at the given coordinates\nimport framebuf\nfbuf = framebuf.FrameBuffer(bytearray(8 * 8 * 1), 8, 8, framebuf.MONO_VLSB)\nfbuf.line(0, 0, 7, 7, 1)\ndisplay.blit(fbuf, 10, 10, 0) # draw on top at x=10, y=10, key=0\ndisplay.show()\n
"},{"location":"displays/graph/03-basic-drawing/#references","title":"References","text":"A Framebuffer is the core data structure we use when drawing bitmaps.
"},{"location":"displays/graph/03-bitmaps/#block-image-transfers-blit","title":"Block Image Transfers (blit)","text":"The basic function we use to draw a rectangular region of the screen is called the blit()
function:
display.blit(frame_buffer, x, y)\n
This function moves all the data within any frame buffer to the given (x,y) position of the display. The function will check the dimensions of the frame buffer to know how much data to move to the display. You just need to tell the function where to start drawing.
"},{"location":"displays/graph/03-bitmaps/#blit-functions-are-efficient","title":"Blit Functions Are Efficient","text":"Blit operations can be much more efficient then the display.show()
function when you are just updating a small region of the screen. This is because the display.show()
function transfers the entire screen image each time it is called. Using blit
functions can be written to only update the area of the screen that changes.
For example, if you are writing a video game that has a ball moving across the screen, you only need to update the pixels around the ball, not the entire screen image. The exact performance difference between show()
and blit()
operations will depend on the size of the screen, the size of the blit update and the speed of the transfer of data from the framebuffer to the display device.
The key disadvantage of using blit()
functions is that you must consider what other artifacts there are on the screen that you might overwrite. Keeping track of the differences requires more computation by the microcontroller. The more powerful your microcontroller is relative to the communication speed, the more difference computations you can do.
Not all display drivers will let you write directly from the microcontroller resident image directly to a region of the display. Sometimes you must follow your blit() operations with a show() to transfer the entire framebuffer to the display.
"},{"location":"displays/graph/03-bitmaps/#working-with-bytearrays","title":"Working with ByteArrays","text":"MicroPython blit operations use a data representation format for images called a ByteArray. These are sequences of the bytes that will be sent in a blit operation. They are coded using the following notation:
my_bytearray = (b\"\\xFF\\xFF\\xFF\\xBF\\xDF\\xEF\\xF7\\xFF\\xFB\\xFF\\xFD\")\n
Note that the letter b
begins the parameter to show the Python interpreter that the all the characters between the double quotes are byte array values. The characters \\x
indicate that there are hexadecimals useds to encode the bit values.
Sometimes you want to update an entire region of the screen with a block of pixels that are all on or off. You can do this with the following steps
# create a 10x10 matrix of on pixels\n# allocate an array of 20 bytes = \non_buffer = bytearray(20)\n# put all 1's in that buffer\non_buffer[:] = b'\\xff' * 20\n# create a frame buffer using monochrome \non_square = framebuf.FrameBuffer(on_buffer, 10, 10, framebuf.MONO_HLSB)\n\noled.blit(logo, i, 0)\n
"},{"location":"displays/graph/03-bitmaps/#image-encoding-options","title":"Image Encoding Options","text":"There are several alternate methods to encode the bits of an image into a byte array. The bits can be coded left to right or top to bottom. You can also put the bits in most-significant bit first or least-significant bit first. All these options and controlled when you interface with a framebuffer.
"},{"location":"displays/graph/03-bitmaps/#vertical-least-significant-bit-layout","title":"Vertical Least Significant Bit Layout","text":"framebuf.MONO_VLSB Monochrome (1-bit) color format This defines a mapping where the bits in a byte are vertically mapped with bit 0 being nearest the top of the screen. Consequently each byte occupies 8 vertical pixels. Subsequent bytes appear at successive horizontal locations until the rightmost edge is reached. Further bytes are rendered at locations starting at the leftmost edge, 8 pixels lower.
"},{"location":"displays/graph/03-bitmaps/#horizontal","title":"Horizontal","text":"MONO_HLSB. Monochrome (1-bit) color format This defines a mapping where the bits in a byte are horizontally mapped. Each byte occupies 8 horizontal pixels with bit 7 being the leftmost. Subsequent bytes appear at successive horizontal locations until the rightmost edge is reached.
"},{"location":"displays/graph/03-bitmaps/#references","title":"References","text":"MicroPython Bitmap Tool Video - this video created by Lucky Resistor is a good overview of the image formats used by MicroPython.
"},{"location":"displays/graph/04-extended-drawing-functions/","title":"Extending Drawing Functions","text":"Although there are several drawing functions available in most of the standard graphics libraries, most of them lack some basic shapes such as a circle. To draw circles on your display, you will need to add new Python functions. Here are some examples of these custom drawing functions.
"},{"location":"displays/graph/04-extended-drawing-functions/#circle","title":"Circle","text":"Here is a function to draw a circle at a given (x,y) point with a radius of r and fill indicator.
Here are the parameters for circle functions
from math import sqrt\n\ndef draw_circle(cx, cy, r, color):\n diameter = r*2\n upper_left_x = cx - r\n upper_left_y = cy - r \n # scan through all pixels and only turn on pixels within r of the center\n for i in range(upper_left_x, upper_left_x + diameter):\n for j in range(upper_left_y, upper_left_y + diameter):\n # distance of the current point (i, j) from the center (cx, cy)\n d = sqrt( (i - cx) ** 2 + (j - cy) ** 2 )\n if d < r:\n oled.pixel(i, j, color)\n
"},{"location":"displays/graph/04-extended-drawing-functions/#testing-circle-drawing","title":"Testing Circle Drawing","text":"from machine import Pin\nfrom utime import sleep\nfrom math import sqrt\nimport ssd1306\n\nWIDTH = 128\nHEIGHT = 64\nclock=Pin(2) # SCL\ndata=Pin(3) # SDA\nRES = machine.Pin(4)\nDC = machine.Pin(5)\nCS = machine.Pin(6)\n\nspi=machine.SPI(0, sck=clock, mosi=data)\noled = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)\n\ndef circle(cx, cy, r, color):\n diameter = r*2\n upper_left_x = cx - r\n upper_left_y = cy - r \n # scan through all pixels and only turn on pixels within r of the center\n for i in range(upper_left_x, upper_left_x + diameter):\n for j in range(upper_left_y, upper_left_y + diameter):\n # distance of the current point (i, j) from the center (cx, cy)\n d = sqrt( (i - cx) ** 2 + (j - cy) ** 2 )\n if d < r:\n oled.pixel(i, j, color)\n\nHALF_WIDTH = int(WIDTH/2)\nHALF_HEIGHT = int(HEIGHT/2)\nwhile True:\n for rad in range(1,HALF_HEIGHT+2):\n draw_circle(HALF_WIDTH, HALF_HEIGHT, rad, 1)\n oled.show()\n sleep(.1)\n sleep(3)\n oled.fill(1)\n for rad in range(1,HALF_HEIGHT+2):\n circle(HALF_WIDTH, HALF_HEIGHT, rad, 0)\n oled.show()\n sleep(.1)\n oled.fill(0)\n sleep(3)\n
"},{"location":"displays/graph/04-extended-drawing-functions/#drawing-a-face","title":"Drawing a Face","text":"If we assume we have a 64x128 display we can call two circle functions to draw eyes
display.fill(0) # Clear the display. display.circle(32, 32, 10, 1) # draw the left eye
"},{"location":"displays/graph/05-timing-draw-speed/","title":"Timing Drawing Speed","text":"If you are writing a video game and want fast drawing times for objects on the screen, there are several different algorithms you can try. You can use the MicroPython time_us
function to record the time before and after you call a drawing function and return the difference to get an idea of the time saved in different versions of your drawing functions.
from utime import ticks_us\n\nstart = ticks_us()\nmy_function()\nend = ticks_us()\nprint('Execution time in microseconds:', end - start)\n
MicroPython also supports the ticks_cpu()
function which could return a smaller granularity for precise time measurements. However, on the Raspberry Pi implementation, the results are exactly the same as the ticks_us()
function.
In the following code, we compare two circle drawing algorithms.
sine()
and cosine()
functions to calculate the x and y distance from the center of the circle to that point.For small circles, it is very inefficient to calculate all 360 points. Scanning all the points in a 5X5 grid only takes 25 calculations. However, the larger the circle becomes, the more points there are to calculate in the row scanner method. A 20X20 circle will need to run the distance calculation 400 times.
from utime import sleep, ticks_cpu, ticks_us\nimport math\nimport ssd1306\n\n# this is the built-in LED on the Pico\nled = Pin('LED', Pin.OUT)\n\nWIDTH = 128\nHEIGHT = 64\nclock=Pin(2)\ndata=Pin(3)\nRES = machine.Pin(4)\nDC = machine.Pin(5)\nCS = machine.Pin(6)\n\nspi=machine.SPI(0, sck=clock, mosi=data)\noled = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)\n\n# \ndef fast_circle(x, y, r, color):\n # draw points around a circle skiping every 4th one\n for theta in range(0, 360, 2):\n # we can save 5% of the time by only doing this once\n radians = math.radians(theta)\n x_pos = int(x + r * math.cos(radians))\n y_pos = int(y + r * math.sin(radians))\n # check if we are within range\n #if 0 <= x_pos < 128 and 0 <= y_pos < 64:\n # we can cut another 5% by not doing these checks\n oled.pixel(x_pos, y_pos, color)\n\ndef circle(x, y, r, color):\n diameter1 = (r - 0.5) ** 2\n diameter2 = (r + 0.5) ** 2\n x_min = max(0, int(x - r))\n x_max = min(128, int(x + r + 1))\n y_min = max(0, int(y - r))\n y_max = min(64, int(y + r + 1))\n\n for y_pos in range(y_min, y_max):\n for x_pos in range(x_min, x_max):\n if ((x_pos - x) ** 2 + (y_pos - y) ** 2 >= diameter1) and ((x_pos - x) ** 2 + (y_pos - y) ** 2 <= diameter2):\n oled.pixel(x_pos, y_pos, color)\n\n\nstart = ticks_us()\ncircle(32, 32, 10, 1)\nend = ticks_us()\nprint('Standard scanner circle draw time in cpu ticks', end - start)\noled.show()\nsleep(1)\nstart = ticks_us()\nfast_circle(96, 32, 10, 1)\nend = ticks_us()\nprint('Fast draw time in cpu ticks', end - start)\noled.show()\n
Challenge
`for theta in range(0, 360, 2):
. Can you dynamically change the number of points skipped as the circle becomes smaller?In this lesson, we will draw a box around the edge of the display using the commands that draw horizontal and vertical lines: hline
and vline
. Then we will draw a ball that bounces off these edges.
import machine\nimport utime\nfrom ssd1306 import SSD1306_I2C\n\nsda=machine.Pin(0)\nscl=machine.Pin(1)\ni2c=machine.I2C(0,sda=sda, scl=scl)\n# Screen size\nwidth=128\nheight=64\noled = SSD1306_I2C(width, height, i2c)\n\noled.hline(0, 0, width - 1, 1) # top edge\noled.hline(0, height - 1, width - 1, 1) # bottom edge\noled.vline(0, 0, height - 1, 1) # left edge\noled.vline(width - 1, 0, height - 1, 1) # right edge\noled.show()\n
"},{"location":"displays/graph/10-oled-bounce/#make-a-ball-bounce-around-inside-the-wall","title":"Make a Ball Bounce Around Inside the Wall","text":"import machine\nimport utime\nfrom ssd1306 import SSD1306_I2C\n\nsda=machine.Pin(0)\nscl=machine.Pin(1)\ni2c=machine.I2C(0,sda=sda, scl=scl)\n# Screen size\nwidth=128\nheight=64\noled = SSD1306_I2C(width, height, i2c)\n\noled.fill(0) # clear to black\n\n# note that OLEDs have problems with screen burn it - don't leave this on too long!\ndef border(width, height):\n oled.hline(0, 0, width - 1, 1) # top edge\n oled.hline(0, height - 1, width - 1, 1) # bottom edge\n oled.vline(0, 0, height - 1, 1) # left edge\n oled.vline(width - 1, 0, height - 1, 1) # right edge\n\n# ok, not really a circle - just a square for now\ndef draw_ball(x,y, size, state):\n if size == 1:\n oled.pixel(x, y, state) # draw a single pixel\n else:\n for i in range(0,size): # draw a box of pixels of the right size\n for j in range(0,size):\n oled.pixel(x + i, y + j, state)\n # TODO: for size above 4 round the corners\n\nborder(width, height)\n\nball_size = 2\n# start in the middle of the screen\ncurrent_x = int(width / 2)\ncurrent_y = int(height / 2)\n# start going down to the right\ndirection_x = 1\ndirection_y = -1\n# delay_time = .0001\n\n# Bounce forever\nwhile True:\n draw_ball(current_x,current_y, ball_size,1)\n oled.show()\n # utime.sleep(delay_time)\n draw_ball(current_x,current_y,ball_size,0)\n # reverse at the edges\n # left edge test\n if current_x < 2:\n direction_x = 1\n # right edge test\n if current_x > width - ball_size -2:\n direction_x = -1\n # top edge test\n if current_y < 2:\n direction_y = 1\n # bottom edge test\n if current_y > height - ball_size - 2:\n direction_y = -1\n # update the ball\n current_x = current_x + direction_x\n current_y = current_y + direction_y\n
"},{"location":"displays/graph/11-lcd-waveshare/","title":"Waveshare LCD","text":""},{"location":"displays/graph/11-lcd-waveshare/#specification","title":"Specification","text":"Waveshare spec Waveshare wiki Demo Code ST7735S Datasheet
"},{"location":"displays/graph/11-oled-ping/","title":"OLED PING","text":""},{"location":"displays/graph/11-oled-ping/#circuit","title":"Circuit","text":""},{"location":"displays/graph/11-oled-ping/#coder","title":"Coder","text":"from machine import Pin, I2C, Timer\nfrom ssd1306 import SSD1306_I2C\nimport utime\n\n\n# global toggle button variable\nmeasure_on = False\n\n# debounce for button\ndef debounce(pin):\n timer.init(mode=Timer.ONE_SHOT, period=200, callback=on_pressed)\n\n# if button pressed, toggle measure_on\ndef on_pressed(timer):\n global measure_on\n measure_on = not measure_on\n\n# Init button\nbutton = Pin(16, Pin.IN, Pin.PULL_DOWN)\ntimer = Timer()\nbutton.irq(debounce, Pin.IRQ_RISING)\n\n# Init Display\ni2c = I2C(0,sda=Pin(0),scl=Pin(1),freq=40000)\noled = SSD1306_I2C(128,64,i2c)\n\n# Init HC-SR04 pins\ntrigger = Pin(14, Pin.OUT)\necho = Pin(13, Pin.IN)\n\n\ndef ultra():\n trigger.low()\n utime.sleep_us(2)\n trigger.high()\n utime.sleep_us(5)\n trigger.low()\n while echo.value() == 0:\n signaloff = utime.ticks_us()\n while echo.value() == 1:\n signalon = utime.ticks_us()\n timepassed = signalon - signaloff\n distance = (timepassed * 0.0343) / 2\n return distance\n\ntry:\n while True:\n oled.fill(0)\n if measure_on:\n result = ultra()\n oled.text(\"Distance:\",0,0)\n oled.text(str(result) + \" cm\",0,10)\n oled.show()\n utime.sleep(1) \nexcept KeyboardInterrupt:\n pass\n
"},{"location":"displays/graph/11-oled-sh1106-i2c/","title":"OLED SSD1306 I2C Examples","text":"We use small OLED displays in many of our labs because:
The first step is to find out what type of display graphics chip is used in your OLED.
"},{"location":"displays/graph/11-oled-sh1106-i2c/#sh1106-example","title":"SH1106 Example","text":"from machine import Pin, I2C\nimport sh1106\n\nsda=machine.Pin(0)\nscl=machine.Pin(1)\ni2c = I2C(0, scl=scl, sda=sda, freq=400000)\n\ndisplay = sh1106.SH1106_I2C(128, 64, i2c, Pin(4), 0x3c)\ndisplay.sleep(False)\n\ndisplay.fill(0)\ndisplay.text('CoderDojo', 0, 0, 1)\ndisplay.show()\n\nprint('done')\n
"},{"location":"displays/graph/11-oled-sh1106-i2c/#counter-example","title":"Counter Example","text":"In this example we will updated the display 50 times with a 1/10th of a second pause between each refresh. A counter will cycle from 1 to 50.
import machine\nimport utime\nfrom ssd1306 import SSD1306_I2C\n\nsda=machine.Pin(0)\nscl=machine.Pin(1)\ni2c=machine.I2C(0,sda=sda, scl=scl, freq=400000)\noled = SSD1306_I2C(128, 64, i2c)\n\nfor i in range(1, 51): # count 1 to 50\n oled.fill(0) # clear to black\n oled.text('CoderDojo Rocks!', 0, 0, 1) # at x=0, y=0, white on black\n oled.text(str(i), 40, 20, 1) # move 30 pixels horizontal and 20 down from the top\n oled.show() # update display\n utime.sleep(0.1) #wait 1/10th of a second\n\nprint('done')\n
"},{"location":"displays/graph/11-oled-sh1106-i2c/#animated-box","title":"Animated Box","text":"This draws a title and four lines around a drawing area. It then draws boxes that move to the right.
from machine import Pin, I2C\nimport sh1106\nimport utime\n\nsda=machine.Pin(0)\nscl=machine.Pin(1)\ni2c = I2C(0, scl=scl, sda=sda, freq=400000)\n\n## note that we can only draw from 0 to 62\ndisplay = sh1106.SH1106_I2C(128, 64, i2c, Pin(4), 0x3c)\ndisplay.sleep(False)\n\ndisplay.fill(0) # clear to black\ndisplay.text('CoderDojo Rocks', 0, 0, 1) # at x=0, y=0, white on black\n# line under title\ndisplay.hline(0, 9, 127, 1)\n# bottom of display\ndisplay.hline(0, 30, 127, 1)\n# left edge\ndisplay.vline(0, 10, 32, 1)\n# right edge\ndisplay.vline(127, 10, 32, 1)\n\nfor i in range(0, 118):\n # box x0, y0, width, height, on\n display.fill_rect(i,10, 10, 10, 1)\n # draw black behind number\n display.fill_rect(10, 21, 30, 8, 0)\n display.text(str(i), 10, 21, 1)\n display.show() # update display\n # utime.sleep(0.001)\n\nprint('done')\n
"},{"location":"displays/graph/11-oled-sh1106-i2c/#bounce-on-the-sh1106-display-using-i2c","title":"Bounce on the SH1106 Display using I2C","text":"This example is a ball that bounces around the inside of a border rectangle. Is similar to other bounce examples with the exception that you can't draw on the last row of pixels.
import machine\nimport utime\n# from ssd1306 import SSD1306_I2C\nimport sh1106\n\nsda=machine.Pin(0)\nscl=machine.Pin(1)\ni2c=machine.I2C(0,sda=sda, scl=scl)\n# Screen size\nwidth=128\nheight=64 # we could make this be 63 but the init method should use the full value\n# oled = SSD1306_I2C(width, height, i2c)\noled = sh1106.SH1106_I2C(width, height, i2c, machine.Pin(4), 0x3c)\n\noled.fill(0) # clear to black\n\n# note that OLEDs have problems with screen burn it - don't leave this on too long!\ndef border(width, height):\n oled.hline(0, 0, width - 1, 1) # top edge\n oled.hline(0, height - 2, width - 1, 1) # bottom edge\n oled.vline(0, 0, height - 1, 1) # left edge\n oled.vline(width - 1, 0, height - 1, 1) # right edge\n\n# ok, not really a circle - just a square for now\ndef draw_ball(x,y, size, state):\n if size == 1:\n oled.pixel(x, y, state) # draw a single pixel\n else:\n for i in range(0,size): # draw a box of pixels of the right size\n for j in range(0,size):\n oled.pixel(x + i, y + j, state)\n # TODO: for size above 4 round the corners\n\nborder(width, height)\n\nball_size = 5\ncurrent_x = int(width / 2)\ncurrent_y = int(height / 2)\ndirection_x = 1\ndirection_y = -1\n# delay_time = .0001\n\n# oled.line(0, height-2, width-1, height-2, 1)\n\n# Bounce forever\nwhile True:\n draw_ball(current_x,current_y, ball_size,1)\n oled.show()\n # utime.sleep(delay_time)\n draw_ball(current_x,current_y,ball_size,0)\n # reverse at the edges\n # left edge test\n if current_x < 2:\n direction_x = 1\n # right edge test\n if current_x > width - ball_size -2:\n direction_x = -1\n # top edge test\n if current_y < 2:\n direction_y = 1\n # bottom edge test\n if current_y > height - ball_size - 3:\n direction_y = -1\n # update the ball\n current_x = current_x + direction_x\n current_y = current_y + direction_y\n\nprint('done')\n
"},{"location":"displays/graph/11-oled-sh1106-i2c/#sh1106-references","title":"SH1106 References","text":"You can now use the Thonny \"Tools -> Manage Packages...\" menu to add the Python driver for the SSD1306 device. You will need to do this for every new device you use.
If the Manage Packages menu is disabled, then you will need to go into the shell and add it with the pip command.
"},{"location":"displays/graph/11-oled-ssd1306-i2c/#i2c-hello-world","title":"I2C Hello World","text":"import machine\nfrom ssd1306 import SSD1306_I2C\n\nsda=machine.Pin(0)\nscl=machine.Pin(1)\ni2c=machine.I2C(0,sda=sda, scl=scl, freq=400000)\noled = SSD1306_I2C(128, 64, i2c)\noled.fill(0)\noled.text(\"Hello World!\", 0, 0)\noled.show()\nprint('Done')\n
After this program runs you should see the text on your OLED display.
"},{"location":"displays/graph/11-oled-ssd1306-i2c/#sh1106-example","title":"SH1106 Example","text":"from machine import Pin, I2C\nimport sh1106\n\nsda=machine.Pin(0)\nscl=machine.Pin(1)\ni2c = I2C(0, scl=scl, sda=sda, freq=400000)\n\ndisplay = sh1106.SH1106_I2C(128, 64, i2c, Pin(4), 0x3c)\ndisplay.sleep(False)\n\ndisplay.fill(0)\ndisplay.text('CoderDojo', 0, 0, 1)\ndisplay.show()\n\nprint('done')\n
"},{"location":"displays/graph/11-oled-ssd1306-i2c/#counter-example","title":"Counter Example","text":"In this example we will updated the display 50 times with a 1/10th of a second pause between each refresh. A counter will cycle from 1 to 50.
import machine\nimport utime\nfrom ssd1306 import SSD1306_I2C\n\nsda=machine.Pin(0)\nscl=machine.Pin(1)\ni2c=machine.I2C(0,sda=sda, scl=scl, freq=400000)\noled = SSD1306_I2C(128, 64, i2c)\n\nfor i in range(1, 51): # count 1 to 50\n oled.fill(0) # clear to black\n oled.text('CoderDojo Rocks!', 0, 0, 1) # at x=0, y=0, white on black\n oled.text(str(i), 40, 20, 1) # move 30 pixels horizontal and 20 down from the top\n oled.show() # update display\n utime.sleep(0.1) #wait 1/10th of a second\n\nprint('done')\n
"},{"location":"displays/graph/11-oled-ssd1306-i2c/#animated-box","title":"Animated Box","text":"This draws a title and four lines around a drawing area. It then draws boxes that move to the right.
from machine import Pin, I2C\nimport sh1106\nimport utime\n\nsda=machine.Pin(0)\nscl=machine.Pin(1)\ni2c = I2C(0, scl=scl, sda=sda, freq=400000)\n\ndisplay = sh1106.SH1106_I2C(128, 64, i2c, Pin(4), 0x3c)\ndisplay.sleep(False)\n\ndisplay.fill(0) # clear to black\ndisplay.text('CoderDojo Rocks', 0, 0, 1) # at x=0, y=0, white on black\n# line under title\ndisplay.hline(0, 9, 127, 1)\n# bottom of display\ndisplay.hline(0, 30, 127, 1)\n# left edge\ndisplay.vline(0, 10, 32, 1)\n# right edge\ndisplay.vline(127, 10, 32, 1)\n\nfor i in range(0, 118):\n # box x0, y0, width, height, on\n display.fill_rect(i,10, 10, 10, 1)\n # draw black behind number\n display.fill_rect(10, 21, 30, 8, 0)\n display.text(str(i), 10, 21, 1)\n display.show() # update display\n # utime.sleep(0.001)\n\nprint('done')\n
"},{"location":"displays/graph/11-oled-ssd1306-i2c/#install-ssd1306-module","title":"Install SSD1306 Module","text":""},{"location":"displays/graph/11-oled-ssd1306-i2c/#ssd1306-module","title":"ssd1306 module","text":"SSD1306 Library - click the RAW button and then right click to do a \"Save As\"
"},{"location":"displays/graph/11-oled-ssd1306-i2c/#ssd1306-vs-sh1106","title":"SSD1306 vs. SH1106","text":"There is only one small difference between SSD1306 and SH1106: The SH1106 controller has an internal RAM of 132x64 pixel. The SSD1306 only has 128x64 pixel.
"},{"location":"displays/graph/11-oled-ssd1306-i2c/#the-spi-interface","title":"The SPI interface","text":"The four wire I2C interface is great for kids that don't want to hook up more than four wires. But there are times when we want a higher performance screen with faster refresh times. This is when the SPI interface comes in handy.
"},{"location":"displays/graph/11-oled-ssd1306-i2c/#spi-baudrate","title":"SPI Baudrate","text":"https://raspberrypi.github.io/pico-sdk-doxygen/group__hardware__spi.html#ga37f4c04ce4165ac8c129226336a0b66c
The seven wires on the back of the SPI OLED screens are the following as read from the top to bottom looking at the back of the display:
# Sample code sections\n 28 # ------------ SPI ------------------\n 29 # Pin Map SPI\n 30 # - 3v - xxxxxx - Vcc\n 31 # - G - xxxxxx - Gnd\n 32 # - D7 - GPIO 13 - Din / MOSI fixed\n 33 # - D5 - GPIO 14 - Clk / Sck fixed\n 34 # - D8 - GPIO 4 - CS (optional, if the only connected device)\n 35 # - D2 - GPIO 5 - D/C\n 36 # - D1 - GPIO 2 - Res\n
SCK is the clock - hook this to the oled SCL MOSI is the line taking data from your Pico to the peripheral device. Hook this to SDA
From the SDK: https://datasheets.raspberrypi.org/pico/raspberry-pi-pico-python-sdk.pdf Section 3.7
This contradicts p122 in GET STARTED WITH MICROPYTHON ON RASPBERRY PI PICO
spi_sck=machine.Pin(2)\nspi_tx=machine.Pin(3)\nspi_rx=machine.Pin(4)\n
"},{"location":"displays/graph/11-oled-ssd1306-i2c/#spi-terms","title":"SPI Terms","text":"Master Out Slave In (MOSI)
We send the data to the SPI RX (Receive) port on the Pico. These are pin 1 (GP0) or pin 6 (GP4)
"},{"location":"displays/graph/11-oled-ssd1306-i2c/#sample-nonworking-spi-code","title":"Sample Nonworking SPI Code","text":"From the documentation:
From
spi is an SPI object, which has to be created beforehand and tells the ports for SCLJ and MOSI. MISO is not used.
dc is the GPIO Pin object for the Data/Command selection. It will be initialized by the driver.
res is the GPIO Pin object for the reset connection. It will be initialized by the driver. If it is not needed, it can be set to None or omitted. In this case the default value of None applies.
cs is the GPIO Pin object for the CS connection. It will be initialized by the driver. If it is not needed, it can be set to None or omitted. In this case the default value of None applies.
import machine\nimport machine\nimport utime\nimport ssd1306\nled = machine.Pin(25, machine.Pin.OUT)\n\n# From: https://github.com/robert-hh/SH1106\n# display = sh1106.SH1106_SPI(width, height, spi, dc, res, cs)\n#MOSI=machine.Pin(7)\n#SCK=machine.Pin(6)\n#spi = machine.SPI(0, baudrate=400000, sck=SCK, mosi=MOSI)\nspi_sck=machine.Pin(6)\nspi_tx=machine.Pin(7)\n# spi_rx=machine.Pin(4)\nspi=machine.SPI(0,baudrate=100000,sck=spi_sck, mosi=spi_tx)\n\nCS = machine.Pin(8)\nDC = machine.Pin(9)\nRES = machine.Pin(10)\n\noled = ssd1306.SSD1306_SPI(128, 64, spi, DC, RES, CS)\n\n# flash all pixels on\noled.fill(1)\noled.show()\nutime.sleep(0.5)\n\noled.fill(0)\noled.text('CoderDojo Rocks!', 0, 0, 1)\noled.show()\n\n# flash the LED to show end\nled.high()\nutime.sleep(0.5)\nled.low()\n\nprint('Done')\n
"},{"location":"displays/graph/11-oled-ssd1306-i2c/#references","title":"References","text":"MicroPython Tutorial on the SSD1306
robert-hh's SH1106 Driver
M Fitzp OLED Display i2c Article
Adafruit Stats
DIY More OLED Product Description
You can now use the Thonny \"Tools -> Manage Packages...\" menu to add the Python driver for the SSD1306 device. You will need to do this for every new device you use.
If the Manage Packages menu is disabled, then you will need to go into the shell and add it with the pip command.
"},{"location":"displays/graph/11-oled-ssd1306-spi/#install-ssd1306-module","title":"Install SSD1306 Module","text":""},{"location":"displays/graph/11-oled-ssd1306-spi/#ssd1306-module","title":"ssd1306 module","text":"SSD1306 Library - click the RAW button and then right click to do a \"Save As\"
SSD1306 Library Searchable
"},{"location":"displays/graph/11-oled-ssd1306-spi/#the-spi-interface","title":"The SPI interface","text":"The four wire I2C interface is great for kids that don't want to hook up more than four wires. But there are times when we want a higher performance screen with faster refresh times. This is when the SPI interface comes in handy.
"},{"location":"displays/graph/11-oled-ssd1306-spi/#displaying-spi-defaults","title":"Displaying SPI Defaults","text":"from machine import Pin\nfrom ssd1306 import SSD1306_SPI\n# default is data (MOSI) on GP7 and clock (sck) on GP6\nspi=machine.SPI(0)\nprint(spi)\nSPI(0, baudrate=992063, polarity=0, phase=0, bits=8, sck=6, mosi=7, miso=4)\n### SPI Baudrate\nhttps://raspberrypi.github.io/pico-sdk-doxygen/group__hardware__spi.html#ga37f4c04ce4165ac8c129226336a0b66c\n\nThe seven wires on the back of the SPI OLED screens are the following as read from the top to bottom looking at the back of the display:\n\n![](../../img/oled-back-connections.png)\n\n1. CS - Chip Select - pin 4\n2. DC - Data/Command - pin 5\n3. RES - Reset - pin 6\n4. SDA - Data - SPIO TX GP7 pin 10\n5. SCL - Clock - Connect to SPIO SCK GP6 pin 9\n6. VCC - Connect to the 3.3V Out pin 36\n7. GND - pin 38 or 3 any other GND pin\n\n### Pico Pins\n
"},{"location":"displays/graph/11-oled-ssd1306-spi/#sample-code-sections","title":"Sample code sections","text":"28 # ------------ SPI ------------------ 29 # Pin Map SPI 30 # - 3v - xxxxxx - Vcc 31 # - G - xxxxxx - Gnd 32 # - D7 - GPIO 13 - Din / MOSI fixed 33 # - D5 - GPIO 14 - Clk / Sck fixed 34 # - D8 - GPIO 4 - CS (optional, if the only connected device) 35 # - D2 - GPIO 5 - D/C 36 # - D1 - GPIO 2 - Res
* SCK is the clock - hook this to the oled SCL\n* MOSI is the line taking data from your Pico to the peripheral device. Hook this to SDA\n\nFrom the SDK:\nhttps://datasheets.raspberrypi.org/pico/raspberry-pi-pico-python-sdk.pdf\nSection 3.7\n\n1. SPI0_SCK - pin 6\n2. SPI0_MOSI - pin 7\n3. SPI0_MISO - pin 8\n\nThis contradicts p122 in GET STARTED WITH MICROPYTHON ON RASPBERRY PI PICO\n
spi_sck=machine.Pin(2) spi_tx=machine.Pin(3) spi_rx=machine.Pin(4) We send the data to the SPI RX (Receive) port on the Pico. These are pin 1 (GP0) or pin 6 (GP4)\n\n## Sample Nonworking SPI Code\n\nFrom the documentation:\n\n!!! From Raspberry Pi Pico Documentation\n **spi** is an SPI object, which has to be created beforehand and tells the ports for SCLJ and MOSI. MISO is not used.\n\n **dc** is the GPIO Pin object for the Data/Command selection. It will be initialized by the driver.\n\n **res** is the GPIO Pin object for the reset connection. It will be initialized by the driver. If it is not needed, it can be set to None or omitted. In this case the default value of None applies.\n\n **cs** is the GPIO Pin object for the CS connection. It will be initialized by the driver. If it is not needed, it can be set to None or omitted. In this case the default value of None applies.\n\n```py\nimport machine\nimport utime\nimport ssd1306\nled = machine.Pin(25, machine.Pin.OUT)\n\nspi_sck=machine.Pin(6)\nspi_tx=machine.Pin(7)\n# spi_rx=machine.Pin(4)\nspi=machine.SPI(0,baudrate=100000,sck=spi_sck, mosi=spi_tx)\n\nCS = machine.Pin(8)\nDC = machine.Pin(9)\nRES = machine.Pin(10)\n\noled = ssd1306.SSD1306_SPI(128, 64, spi, DC, RES, CS)\n\n# flash all pixels on\noled.fill(1)\noled.show()\nutime.sleep(0.5)\n\noled.fill(0)\noled.text('CoderDojo Rocks!', 0, 0, 1)\noled.show()\n\n# flash the LED to show end\nled.high()\nutime.sleep(0.5)\nled.low()\n\nprint('Done')\n
"},{"location":"displays/graph/11-oled-ssd1306-spi/#references","title":"References","text":"robert-hh's SH1106 Driver
MicroPython SSD1306 Class
https://www.mfitzp.com/article/oled-displays-i2c-micropython/
https://github.com/adafruit/Adafruit_CircuitPython_SSD1306/blob/master/examples/ssd1306_stats.py
https://github.com/robert-hh/SH1106/blob/master/sh1106.py
DIY More OLED Product Description
"},{"location":"displays/graph/11-oled-ssd1306-spi/#ssd1306","title":"SSD1306","text":"https://www.solomon-systech.com/en/product/advanced-display/oled-display-driver-ic/ssd1306/
"},{"location":"displays/graph/11-oled-ssd1306-spi/#ssd1307","title":"SSD1307","text":"https://www.solomon-systech.com/en/product/advanced-display/oled-display-driver-ic/ssd1307/
"},{"location":"displays/graph/11-pong/","title":"Pong","text":"Using a low-cost OLED device you can write a pong game. If you use a small 128X64 OLED the price can be around $12.
"},{"location":"displays/graph/11-pong/#part-list","title":"Part list","text":"Part Name Price Link Description Raspberry Pi Pico $4 Microcenter With 264K RAM it has plenty of room for storing the framebuffer 1/2 Size Solderless Breadboard $2 link 400 tie breadboard 128X64 OLED $5 eBay You can also get larger 2.42\" displays for around $20 2 10K Potentiometers $1.5 each eBay You can purchase these in QTY 10 for less. Use the part number B10K to narrow your search. Clear Plastic Box $4 The Container Store Shallow Narrow Stackable Rectangle Clear with Lids 8-1/4\" x 3-1/2\" x 1-7/8\" h. The link is to the white lids.Raspberry Pi Pico for $4.
OLED with I2C Interface. Note the pins are VCC, GND, SCL (clock), SDA (data).
1/2 size 400 connector solderless breadboard
10K potentiometer with pre-soldered connectors. You will need two of these. You can use a male-to-male header to connect it to the breadboard.
"},{"location":"displays/graph/11-pong/#connections","title":"Connections","text":"To run this program, you will need a MicroPython display driver. Our display in this example is the popular SSD1306 driver chip. Your OLED might have a slightly different driver type.
Here is the line that must be customized for your display:
from ssd1306 import SSD1306_I2C\n
"},{"location":"displays/graph/11-pong/#testing-the-oled","title":"Testing the OLED","text":"This test will verify that your OLED connections are correct.
from machine import Pin, I2C\nfrom ssd1306 import SSD1306_I2C\nWIDTH = 128\nHEIGHT = 64\nsda=machine.Pin(0)\nscl=machine.Pin(1)\ni2c=machine.I2C(0,sda=sda, scl=scl)\noled = SSD1306_I2C(WIDTH, HEIGHT, i2c)\noled.fill(0)\noled.text(\"CoderDojo Rocks\",0,0)\noled.show()\n
"},{"location":"displays/graph/11-pong/#drawing-the-border","title":"Drawing the Border","text":"def border(WIDTH, HEIGHT):\n oled.rect(0, 0, WIDTH, HEIGHT, 1)\n
"},{"location":"displays/graph/11-pong/#full-program","title":"Full Program","text":"# Pong game on Raspberry Pi Pico with a OLED and two Potentimeters\nfrom machine import Pin, PWM, SPI\nimport ssd1306\nfrom utime import sleep\nimport random # random direction for new ball\n\nspi_sck=machine.Pin(2)\nspi_tx=machine.Pin(3)\nspi=machine.SPI(0,baudrate=100000,sck=spi_sck, mosi=spi_tx)\nCS = machine.Pin(1)\nDC = machine.Pin(4)\nRES = machine.Pin(5)\noled = ssd1306.SSD1306_SPI(128, 64, spi, DC, RES, CS)\n# connect the center tops of the potentiometers to ADC0 and ADC1\npot_pin_1 = machine.ADC(26)\npot_pin_2 = machine.ADC(26) # make them the same for testing\n\n# lower right corner with USB connector on top\nSPEAKER_PIN = 16\n# create a Pulse Width Modulation Object on this pin\nspeaker = PWM(Pin(SPEAKER_PIN))\n\n# globals variables\n# static variables are constants are uppercase variable names\nWIDTH = 128\nHALF_WIDTH = int(WIDTH / 2)\nHEIGHT = 64\nHALF_HEIGHT = HEIGHT\nBALL_SIZE = 3 # 2X2 pixels\nPAD_WIDTH = 2\nPAD_HEIGHT = 8\nHALF_PAD_WIDTH = int(PAD_WIDTH / 2)\nHALF_PAD_HEIGHT = int(PAD_HEIGHT / 2)\nPOT_MIN = 3000\nPOT_MAX = 65534\nMAX_ADC_VALUE = 65534 # Maximum value from the Analog to Digital Converter is 2^16 - 1\n# dynamic global variables use lowercase\npaddle1_vel = 0\npaddle2_vel = 0\nl_score = 0\nr_score = 0\n# continiuous update of the paddle and ball\n# play_startup_sound()\n# start with the ball in the center\nball_x = int(WIDTH / 2)\nball_y = int(HEIGHT / 2)\n# set the initial directinon to down to the right\nball_x_dir = 1\nball_y_dir = 1\n\ndef play_startup_sound():\n speaker.duty_u16(1000)\n speaker.freq(600)\n sleep(.25)\n speaker.freq(800)\n sleep(.25)\n speaker.freq(1200)\n sleep(.25)\n speaker.duty_u16(0)\n\ndef play_bounce_sound():\n speaker.duty_u16(1000)\n speaker.freq(900)\n sleep(.25)\n speaker.duty_u16(0)\n\ndef play_score_sound():\n speaker.duty_u16(1000)\n speaker.freq(600)\n sleep(.25)\n speaker.freq(800)\n sleep(.25)\n speaker.duty_u16(0)\n\n# note that OLEDs have problems with screen burn it - don't leave this on too long!\ndef border(WIDTH, HEIGHT):\n oled.rect(0, 0, WIDTH, HEIGHT, 1)\n\n\n# Takes an input number vale and a range between high-and-low and returns it scaled to the new range\n# This is similar to the Arduino map() function\ndef valmap(value, istart, istop, ostart, ostop):\n return int(ostart + (ostop - ostart) * ((value - istart) / (istop - istart)))\n\n# draw a vertical bar\ndef draw_paddle(paddle_no, paddle_center):\n if paddle_no == 1:\n x = 0\n else:\n x = WIDTH - 2\n y = paddle_center - HALF_PAD_HEIGHT\n oled.fill_rect(x, y, PAD_WIDTH, PAD_HEIGHT, 1) # fill with 1s\n\ndef draw_ball():\n oled.fill_rect(ball_x, ball_y, BALL_SIZE, BALL_SIZE, 1) # square balls for now\n\n# The main event loop\nwhile True:\n oled.fill(0) # clear screen\n oled.vline(int(WIDTH / 2), 0, HEIGHT, 1)\n # border(WIDTH, HEIGHT)\n # read both the pot values\n pot_val_1 = pot_pin_1.read_u16()\n pot_val_2 = pot_pin_1.read_u16()\n # print(pot_val_1)\n\n # scale the values from the max value of the input is a 2^16 or 65536 to 0 to HEIGHT - PAD_HEIGHT\n # ideally, it should range from 5 to 58\n pot_val_1 = valmap(pot_val_1, POT_MIN, POT_MAX, HALF_PAD_HEIGHT, HEIGHT - HALF_PAD_HEIGHT - 2)\n pot_val_2 = valmap(pot_val_2, POT_MIN, POT_MAX, HALF_PAD_HEIGHT, HEIGHT - HALF_PAD_HEIGHT - 2)\n\n # print(pot_val, pot_scaled)\n draw_paddle(1, pot_val_1 + HALF_PAD_HEIGHT)\n draw_paddle(2, pot_val_2 + HALF_PAD_HEIGHT)\n draw_ball()\n\n #update ball position with the current directions\n ball_x = ball_x + ball_x_dir\n ball_y = ball_y + ball_y_dir\n\n # update the ball direction if we are at the top or bottom edge\n if ball_y < 0:\n ball_y_dir = 1\n #play_bounce_sound()\n if ball_y > HEIGHT - 3:\n ball_y_dir = -1\n #play_bounce_sound()\n\n # if it hits the paddle bounce else score\n if ball_x < 1:\n top_paddle = pot_val_1 - HALF_PAD_HEIGHT\n bottom_paddle = pot_val_1 + HALF_PAD_HEIGHT\n if ball_y > top_paddle and ball_y < bottom_paddle:\n # we have a hit\n ball_x_dir = 1\n ball_x = 2\n play_bounce_sound()\n print('paddle hit on left edge', pot_val_1, top_paddle, bottom_paddle)\n else:\n # we have a score for the right player\n play_score_sound()\n r_score += 1\n ball_x = int(WIDTH / 2)\n ball_y = int(HEIGHT / 2)\n ball_x_dir = random.randint(-1, 2)\n if ball_x_dir == 0:\n ball_x_dir = 1\n ball_y_dir = random.randint(-1, 2)\n print('score on left edge', pot_val_1, top_paddle, bottom_paddle)\n sleep(.25)\n\n if ball_x > WIDTH - 3:\n ball_x = WIDTH - 4\n top_paddle = pot_val_2 - HALF_PAD_HEIGHT\n bottom_paddle = pot_val_2 + HALF_PAD_HEIGHT\n if ball_y > top_paddle and ball_y < bottom_paddle:\n ball_x_dir = -1\n print('bounce on right paddle', pot_val_1, top_paddle, bottom_paddle)\n else:\n l_score += 1\n play_score_sound()\n ball_x = int(WIDTH / 2)\n ball_y = int(HEIGHT / 2)\n ball_x_dir = random.randint(-1, 2)\n if ball_x_dir == 0:\n ball_x_dir = 1\n ball_y_dir = random.randint(-1, 2)\n play_bounce_sound()\n print('score on right edge', pot_val_1, top_paddle, bottom_paddle)\n sleep(.25)\n\n oled.text(str(l_score), HALF_WIDTH - 20, 5, 1)\n\n oled.text(str(r_score), HALF_WIDTH + 5, 5, 1)\n\n oled.show()\n
YouTube Video
"},{"location":"displays/graph/12-e-paper-display/","title":"Raspberry Pi E-Paper Displays with","text":""},{"location":"displays/graph/12-e-paper-display/#specifications","title":"Specifications","text":"In this lesson, we will use a potentiometer to change the value of an OLED display. We will use a small SSD1306 OLED with an I2C interface.
A potentiometer has three wires. The two outside wires connect to GND and the 3.3 volt output. The center wire, called the \"tap\" wire will connect to the pin that converts an continuous analog voltage value into a digital number.
Wikipedia Page on Potentiometer
"},{"location":"displays/graph/12-oled-pot/#circuit-diagram","title":"Circuit Diagram","text":""},{"location":"displays/graph/12-oled-pot/#sample-code","title":"Sample Code","text":""},{"location":"displays/graph/12-oled-pot/#testing-the-pot","title":"Testing the POT","text":"Our first task is to find what pin to use for our first Analog to Digital concerter. GP26 is the same as ADC0. This is pin number 31 on the Pico.
import machine\nimport utime\npot = machine.ADC(26)\nwhile True:\n print(pot.read_u16())\n utime.sleep(.2)\n
"},{"location":"displays/graph/12-oled-pot/#sample-16-bit-output","title":"Sample 16 bit output","text":"A 16-bit integer can store 216 (or 65,536) distinct values. In an unsigned representation, these values are the integers between 0 and 65,535. So we are expecting numbers from 0 to 65,535.
Sample results as we move the potentiometer from the minimum to the maximum values.
65535\n52844\n31047\n7745\n256\n352\n19140\n41114\n62239\n65535\n57277\n33384\n10114\n352\n288\n19940\n28086\n
"},{"location":"displays/graph/12-oled-pot/#testing-the-oled","title":"Testing the OLED","text":""},{"location":"displays/graph/12-oled-pot/#getting-the-defaults","title":"Getting the defaults","text":"from machine import Pin, I2C\n# i2c=machine.I2C(0)\ni2c=machine.I2C(0)\nprint(\"Device found at decimal\", i2c.scan())\nprint(i2c)\n
Results: This tells you the default pins and frequency that the I2C bus is running at.
Device found at decimal [60]\nI2C(0, freq=399361, scl=9, sda=8)\n
Device found at decimal [60]\nI2C(0, freq=399361, scl=1, sda=0)\n
This tells us that the default pins are GP9 (row 12) for clock and GP8 (row 11) for data.
from machine import Pin, I2C\nfrom ssd1306 import SSD1306_I2C\nWIDTH = 128\nHEIGHT = 64\ni2c = I2C(0) # Init I2C using I2C0 defaults SCL on GP9 (12) and SDA on GP8 (11) \noled = SSD1306_I2C(WIDTH, HEIGHT, i2c)\noled.fill(0)\noled.text(\"CoderDojo Rocks\",0,0)\noled.show()\n
"},{"location":"displays/graph/12-oled-pot/#continuous-text-display-on-oled","title":"Continuous Text Display on OLED","text":"from machine import Pin, I2C\nfrom ssd1306 import SSD1306_I2C\nWIDTH = 128\nHEIGHT = 32\ni2c = I2C(0) # Init I2C using I2C0 defaults SCL on GP9 (12) and SDA on GP8 (11) \noled = SSD1306_I2C(WIDTH, HEIGHT, i2c)\n\nPOT_PIN = machine.ADC(26)\n\nwhile True:\n oled.fill(0)\n oled.text(POT_PIN.read_u16())\n oled.show()\n utime.sleep(.2)\n
"},{"location":"displays/graph/12-oled-pot/#bar-chart-and-text-display-of-pot-value","title":"Bar Chart and Text Display of Pot Value","text":"import machine\nimport utime\nimport sh1106\n\nsda=machine.Pin(0)\nscl=machine.Pin(1)\npot_pin = machine.ADC(26)\n\ni2c=machine.I2C(0,sda=sda, scl=scl)\n# Screen size\nwidth=128\nheight=64\nhalf_height = int(height / 2)\n# oled = SSD1306_I2C(width, height, i2c)\noled = sh1106.SH1106_I2C(width, height, i2c, machine.Pin(4), 0x3c)\n\noled.fill(0) # clear to black\n\n# note that OLEDs have problems with screen burn it - don't leave this on too long!\ndef border(width, height):\n oled.hline(0, 0, width - 1, 1) # top edge\n oled.hline(0, height - 2, width - 1, 1) # bottom edge\n oled.vline(0, 0, height - 1, 1) # left edge\n oled.vline(width - 1, 0, height - 1, 1) # right edge\n\n# Takes an input number vale and a range between high-and-low and returns it scaled to the new range\n# This is similar to the Arduino map() function\ndef valmap(value, istart, istop, ostart, ostop):\n return int(ostart + (ostop - ostart) * ((value - istart) / (istop - istart)))\n\n# draw a horizontal bar\ndef draw_hbar(inval, height, state):\n oled.fill(0) # clear screen\n border(width, height) # draw a border\n oled.fill_rect(0, 1, inval, height, 1) # fill with 1\n utime.sleep(.1) # wait a bit\n\n# continuous update\nwhile True:\n pot_val = int(pot_pin.read_u16())\n # the max value of the input is a 2^16 or 65536\n pot_scaled = valmap(pot_val, 0, 65536, 0, 127)\n print(pot_val, pot_scaled)\n draw_hbar(pot_scaled, half_height, 1)\n\n oled.text('raw:', 0, half_height + 5, 1)\n oled.text(str(pot_val), 30, half_height + 5, 1)\n\n oled.text('scaled:', 0, half_height + 15, 1)\n oled.text(str(pot_scaled), 60, half_height + 15, 1)\n oled.show() \n
"},{"location":"displays/graph/12-oled-pot/#gif-of-oled","title":"Gif of OLED","text":"Gif of small .96\" OLED
Gif of larger 2.42\" OLED
"},{"location":"displays/graph/12-oled-ssd1306-spi-v1/","title":"OLED SSD1306 SPI V1","text":""},{"location":"displays/graph/12-oled-ssd1306-spi-v1/#oled-spi-demo","title":"OLED SPI Demo","text":"This code was provide by Jim Tannenbaum (aka Jet)
"},{"location":"displays/graph/12-oled-ssd1306-spi-v1/#image","title":"Image","text":""},{"location":"displays/graph/12-oled-ssd1306-spi-v1/#code","title":"Code","text":"import machine import ssd1306\nspi_sck=machine.Pin(2)\nspi_tx=machine.Pin(3)\nspi=machine.SPI(0,baudrate=100000,sck=spi_sck, mosi=spi_tx)\nCS = machine.Pin(1)\nDC = machine.Pin(4)\nRES = machine.Pin(5)\noled = ssd1306.SSD1306_SPI(128, 64, spi, DC, RES, CS)\n# flash all pixels on oled.fill(0)\noled.show()\noled.text('Hello Jet', 0, 0, 1)\noled.show()\n
"},{"location":"displays/graph/13-pixel-draw/","title":"Sample Pixel-Based Drawing Program","text":"Code example provided by Jim Tannenbaum.
This program will act like an Etch-A-Sketch(TM) program. It will use potentiometers with the center tap on GPIO pins GP26 and GP27 and draw as you move the potentiometers to control the X and Y dimensions.
from machine import Pin, SPI, ADC\nimport ssd1306\nimport time\n\n# Takes an input number value and a range between high-and-low and returns it scaled to the new range\n# This is similar to the Arduino map() function\ndef scaled(value, istart, istop, ostart, ostop):\n return int(ostart + (ostop - ostart) * ((int(value) - istart) / (istop - istart)))\n\n# Define the pins for SPI Clock and Transmit\nspi_sck = Pin(2)\nspi_tx = Pin(3)\nspi = SPI(0, baudrate=100000, sck=spi_sck, mosi=spi_tx)\n\n# Define the pins for Chip Select, DC (Command), and Reset\nCS = Pin(1)\nDC = Pin(4)\nRES = Pin(5)\n\noled = ssd1306.SSD1306_SPI(128, 64, spi, DC, RES, CS)\n\n# Turn all pixels off\noled.fill(0)\noled.show()\n\n# Provide info to user\noled.text('Etch-A-Sketch', 0, 0, 1)\noled.text('Hit the reset', 0, 20, 1)\noled.text('button to clear', 0, 30, 1)\noled.text('the screen', 0, 40, 1)\noled.show()\n\n# Define the pin for the reset button\nresetButton = Pin(14, Pin.IN, Pin.PULL_DOWN)\n\n# Wait unti the user hits the button to clear the screen and start drawing\nwhile resetButton.value() != 1:\n time.sleep(.25)\n\noled.fill(0)\noled.show()\n\n# Define the Horizontal and Vertical inputs from the Rheostats\nvert = ADC(26)\nhoriz = ADC(27)\n\n# Calculate where to start the line\nx = newX = scaled(vert.read_u16(), 0, 65536, 0, 128)\ny = newY = scaled(horiz.read_u16(), 0, 65536, 0, 64)\n\n# Loop forever\n# Draw the line, look for a reset to clear the screen, and get the new end points for the line\nwhile True:\n oled.line(x, y, newX, newY, 1)\n x = newX\n y = newY\n if resetButton.value():\n oled.fill(0)\n oled.show()\n time.sleep(.2)\n newX = scaled(vert.read_u16(), 0, 65536, 0, 128)\n newY = scaled(horiz.read_u16(), 0, 65536, 0, 64)\n
"},{"location":"displays/graph/14-lcd-st7789V/","title":"MicroPython ST7789V LCD Display","text":"The Sitronix ST7789 is a driver chip for small color IPS LCD displays that supports SPI interfaces. This example uses a 2-inch color LDC display manufactured by Waveshare with a retail price of approximately $13 or $14.75 on Amazon Prime.
"},{"location":"displays/graph/14-lcd-st7789V/#specifications","title":"Specifications","text":"Note: The ST7789 uses a SPI interfaces but not a true standard SPI protocol. The device only uses MOSI (DIN) to send data from master to slave for LCD display. Only four wires are needed to connect from the Pico to the device.
"},{"location":"displays/graph/14-lcd-st7789V/#device-interface","title":"Device Interface","text":""},{"location":"displays/graph/14-lcd-st7789V/#interface","title":"Interface","text":"Although the device has eight wires, your Pico only needs a few of them to be controlled by the GPIO ports.
"},{"location":"displays/graph/14-lcd-st7789V/#uploading-the-st7789v-python-firmware","title":"Uploading the ST7789V Python Firmware","text":"The firmware contains pre-compiled objects for various devices with the st7789 C driver and frozen python font files.
The library for the driver is delivered as a single firmware.uf2 file available here:
https://github.com/russhughes/st7789_mpy/tree/master/firmware/RP2
To load this file you will need to hold down the BOTSEL button on the Pico and drag this file into the RP2 folder that is mounted.
"},{"location":"displays/graph/14-lcd-st7789V/#micropython-initialization","title":"MicroPython Initialization","text":"I used the following SPI Device ID 1 pinout on the lower left corner of the Pico:
Pin GP Number Label on LCD 14 (GP10) BL 15 (GP11) RST 16 (GP12) DC 17 (GP13) CS 18 (GND) GND 19 (GP14) CLK 20 (GP15) DIN"},{"location":"displays/graph/14-lcd-st7789V/#sample-device-initialize","title":"Sample Device Initialize","text":"from machine import Pin, SPI\nimport st7789\n\nBACKLIGHT_PIN = 10\nRESET_PIN = 11\nDC_PIN = 12\nCS_PIN = 13\nCLK_PIN = 14\nDIN_PIN = 15 # lower left corner\n\nimport vga1_bold_16x32 as font\n\nspi = SPI(1, baudrate=31250000, sck=Pin(CLK_PIN), mosi=Pin(DIN_PIN))\ntft = st7789.ST7789(spi, 240, 320,\n reset=Pin(RESET_PIN, Pin.OUT),\n cs=Pin(CS_PIN, Pin.OUT),\n dc=Pin(DC_PIN, Pin.OUT),\n backlight=Pin(BACKLIGHT_PIN, Pin.OUT),\n rotation=3)\ntft.init()\n# draw white letters on a back background at 10 over and 20 down\ntft.text(font, \"Hello World!\", 10, 20, st7789.color565(255,255,255), st7789.color565(0,0,0))\n
"},{"location":"displays/graph/14-lcd-st7789V/#sample-hello-world-in-four-colors","title":"Sample Hello World In Four Colors","text":"from machine import Pin, SPI\nimport st7789\n\nBACKLIGHT_PIN = 10\nRESET_PIN = 11\nDC_PIN = 12\nCS_PIN = 13\nCLK_PIN = 14\nDIN_PIN = 15 # lower left corner\n\nimport vga1_bold_16x32 as font\n\nspi = SPI(1, baudrate=31250000, sck=Pin(CLK_PIN), mosi=Pin(DIN_PIN))\ntft = st7789.ST7789(spi, 240, 320,\n reset=Pin(RESET_PIN, Pin.OUT),\n cs=Pin(CS_PIN, Pin.OUT),\n dc=Pin(DC_PIN, Pin.OUT),\n backlight=Pin(BACKLIGHT_PIN, Pin.OUT),\n rotation=3)\ntft.init()\n\ntft.text(font, \"Hello World!\",10, 0, st7789.color565(255,255,255), st7789.color565(0,0,0))\ntft.text(font, \"Hello World!\",10, 50, st7789.color565(255,0,0), st7789.color565(0,0,0))\ntft.text(font, \"Hello World!\",10, 100, st7789.color565(0,255,0), st7789.color565(0,0,0))\ntft.text(font, \"Hello World!\",10, 150, st7789.color565(0,0,255), st7789.color565(0,0,0))\n
"},{"location":"displays/graph/14-lcd-st7789V/#driver-implementation-notes","title":"Driver Implementation Notes","text":"The ST7789V supports RGB444, RGB565 and RGB666 three formats. The Waveshare LCD uses RGB565. For most LCD controllers, the communication method of the controller can be configured, they are usually using 8080 parallel interface, 3-line SPI, 4-line SPI, and other communication methods. This LCD uses a 4-line SPI interface for reducing GPIO and fast speed.LCD
You can see what data needs to be changing from the timing diagram below:
This combination of two bits provides 4 modes of SPI data transmission. The commonly used is SPI0 mode, i.e. GPHL=0 and CPOL=0.
According to the figure above, data transmitting begins at the first falling edge, 8bit data are transmitted at one clock cycle. It is SPI0. MSB.
"},{"location":"displays/graph/14-lcd-st7789V/#references","title":"References","text":"This program uses the MicroPython urandom
library to generate random X and Y positions on the display. It then uses an array of binary values to draw a heart icon at that location.
from machine import Pin, PWM, SPI\nimport urandom\nimport ssd1306\nfrom utime import sleep\nimport random # random direction for new ball\n\nWIDTH = 128\nHEIGHT = 64\nCS = machine.Pin(1)\nspi_sck=machine.Pin(2)\nspi_tx=machine.Pin(3)\nDC = machine.Pin(4)\nRES = machine.Pin(5)\nspi=machine.SPI(0,baudrate=100000,sck=spi_sck, mosi=spi_tx)\n\noled = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)\n\nHEART = [\n [ 0, 0, 0, 0, 0, 0, 0, 0, 0],\n [ 0, 1, 1, 0, 0, 0, 1, 1, 0],\n [ 1, 1, 1, 1, 0, 1, 1, 1, 1],\n [ 1, 1, 1, 1, 1, 1, 1, 1, 1],\n [ 1, 1, 1, 1, 1, 1, 1, 1, 1],\n [ 0, 1, 1, 1, 1, 1, 1, 1, 0],\n [ 0, 0, 1, 1, 1, 1, 1, 0, 0],\n [ 0, 0, 0, 1, 1, 1, 0, 0, 0],\n [ 0, 0, 0, 0, 1, 0, 0, 0, 0],\n]\n\ndef draw_heart(xofs, yofs):\n for y, row in enumerate(HEART):\n for x, c in enumerate(row):\n oled.pixel(x + xofs, y + yofs, c)\n\ndef random_heart():\n xofs = urandom.getrandbits(7)\n yofs = urandom.getrandbits(6)\n print(xofs, yofs)\n draw_heart(xofs, yofs)\n\n\noled.fill(0)\nfor n in range(10):\n random_heart()\n\noled.show()\n
"},{"location":"displays/graph/15-cu1609c-led/","title":"CU1609C LED Display","text":"Note: This is a work-in-progress. We have not found a MicroPython driver for this display.
The UC1609 is a graphic LED driver chip with an SPI interface. Because it is low cost ($4) and 2 inches across it is ideal for low-cost robot displays.
192X62 LCD display for $4 USB device
"},{"location":"displays/graph/15-cu1609c-led/#connections","title":"Connections","text":"Pin Name Description 1 K Backlight Cathode Connect to GND rail 2 A Backlight Anode Connect via 200 ohm to 3.2v rail to limit backlight current to 3 milliamps. The current for backlight is limited to 20 milliamps. 3 GND Ground Connect to GND rail 4 VDD Power Supply connect to +3.3v rail 5 SCK Serial clock input. Connect to SPI CLK 6 SDA Serial data input. Connect to SPI Data SCL 7 RST Connect to 3.3v rail. 8 CD It determines whether the access is related to data or command. Connect to GPIO 9 CS Chip select input. Connect to GND when LCD is use."},{"location":"displays/graph/15-cu1609c-led/#connection-notes","title":"Connection Notes","text":"When RST=L, all control registers are re-initialized by their default sates. Since UC1609c has built-in Power-on Reset, the RST pin is not required for proper chip operation. An RC filter has been included on-chip. There is no need for external RC noise filter. When RST is not used, connect the pin to High.
CS determines whether the access is related to data or command. When CS=\u201cH\u201d : Display data. When CS=\u201cL\u201d : Command.
"},{"location":"displays/graph/15-cu1609c-led/#hello-world","title":"Hello World","text":"from machine import Pin, SPI\n\nSPI_CLK = 2 # SPI clock\nSPI_SDA = 3\nCD = 5 # command or data\n# RST is tied to 3.3v\n# CS is tied to GND\n\n# SPI(0, baudrate=992063, polarity=0, phase=0, bits=8, sck=2, mosi=3, miso=4)\nspi = SPI(0, baudrate=31250000, sck=Pin(SPI_CLK), mosi=Pin(SPI_SDA))\nprint(spi)\n
"},{"location":"displays/graph/15-cu1609c-led/#similar-drivers","title":"Similar Drivers","text":"There are two similar drivers. One is for the LCD160CR
LCD160CR Display Driver
The other is the Arduino C version by Gavin Lyones that has been around for a long time.
Gavin Lyons GitHub Repo supporting the UC1609
Our goal is to port Gavin's C code to use the same function as the LCD160CR driver.
"},{"location":"displays/graph/15-cu1609c-led/#references","title":"References","text":"In this lesson, we will show how you can display interesting repeating patterns on your OLED screen. Our program will write a pattern into the framebuffer using a simple math equation. The oled.show() will then update the pattern on the display.
This lesson was suggested by Parker Erickson.
"},{"location":"displays/graph/15-oled-patterns/#math-functions","title":"Math Functions","text":"We will use a few unusual functions to create repeating patterns:
The modulo function is written %
. It returns the integer remainder after a division. So 7 % 3
is 1 and 7 % 4
is 3. The Power function of X to the Y power is written in python as pow(x,y)
. For example pow(7, 2) is seven squared = 49.
The bitwise and is written as x & y
for i in range(8):\n 13 & i\n
Function Returns 13 & 0 0 13 & 1 1 13 & 2 0 13 & 3 1 13 & 4 4 13 & 5 5 13 & 6 4 13 & 7 5 13 & 8 8 13 & 9 9 13 & 10 8 13 & 11 9 13 & 12 12"},{"location":"displays/graph/15-oled-patterns/#some-sample-equations","title":"Some Sample Equations","text":"Note there are other patterns that use the power pow(x,y)
or Exponentiation **
function but I can't get these to work with Micropython.
This program evaluates the function x % (y+1)
for each of the pixels on the screen. If the function returns a non-zero the pixel will be off. If the pixel is zero, the pixel will be on.
draw-patterns-ssd1306-spi.py
import machine\nimport ssd1306\n\nWIDTH = 128\nHEIGHT = 64\nspi_sck=machine.Pin(2)\nspi_tx=machine.Pin(3)\nspi=machine.SPI(0,baudrate=100000,sck=spi_sck, mosi=spi_tx)\nCS = machine.Pin(1)\nDC = machine.Pin(4)\nRES = machine.Pin(5)\noled = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)\n\noled.fill(0) # clear display\nfor x in range(WIDTH):\n for y in range(HEIGHT):\n if x % (y+1):\n oled.pixel(x,y,0)\n else:\n oled.pixel(x,y,1)\noled.show()\n
"},{"location":"displays/graph/15-oled-patterns/#adding-a-list-of-patterns","title":"Adding a List of Patterns","text":""},{"location":"displays/graph/15-oled-patterns/#the-eval-function","title":"The Eval Function","text":"The eval() function takes any string and passes it to the python interpreter for evaluation within the current context of variables that are in scope. We can use eval to pass an expression that should be evaluated to any function.
list = [\"x+y\", \"x-y\", \"x*y\", \"x % (y+1)\"]\n\nfor i in range(0, 4):\n print(list[i], ': ', sep='', end='')\n for x in range(5):\n for y in range(5):\n print(eval(list[i]), '', end='')\n print('')\n
Output:
x+y: 0 1 2 3 4 1 2 3 4 5 2 3 4 5 6 3 4 5 6 7 4 5 6 7 8 \nx-y: 0 -1 -2 -3 -4 1 0 -1 -2 -3 2 1 0 -1 -2 3 2 1 0 -1 4 3 2 1 0 \nx*y: 0 0 0 0 0 0 1 2 3 4 0 2 4 6 8 0 3 6 9 12 0 4 8 12 16 \nx % (y+1): 0 0 0 0 0 0 1 1 1 1 0 0 2 2 2 0 1 0 3 3 0 0 1 0 4 \n
"},{"location":"displays/graph/15-oled-patterns/#the-command-design-pattern","title":"The Command Design Pattern","text":"The command pattern holds a list of commands in an array. Each command is executed in the sequence it appears in the list of commands.
In the following program we have the equations in a list. The program steps through each item in the list and displays that equation on the OLED display.
import machine\nimport ssd1306\nfrom utime import sleep, time\n\nWIDTH = 128\nHEIGHT = 64\nspi_sck=machine.Pin(2)\nspi_tx=machine.Pin(3)\nspi=machine.SPI(0,baudrate=100000,sck=spi_sck, mosi=spi_tx)\nCS = machine.Pin(1)\nDC = machine.Pin(4)\nRES = machine.Pin(5)\noled = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)\n\nequations = ['(x * y) & 24', '(x * y) & 47', '(x * y) & 64', 'x & y', 'x % y', '(x % y) % 4', '40 % (x % y+1)']\n\nfor eqn in range(0, len(equations)):\n start = time()\n\n oled.fill(0) # clear display\n oled.text('calculating', 0, 0, 1)\n oled.text(equations[eqn], 0, 10, 1)\n oled.show()\n for x in range(WIDTH):\n for y in range(1, HEIGHT):\n if eval(equations[eqn]):\n oled.pixel(x,y,0)\n else:\n oled.pixel(x,y,1)\n oled.show()\n sleep(5)\n\n end = time()\n duration = str(end - start)\n print(equations[eqn])\n print(duration, ' seconds')\n\noled.text('done', 0, 0, 1)\noled.show()\nprint('done')\n
"},{"location":"displays/graph/15-oled-patterns/#sample-screen-images","title":"Sample Screen Images","text":""},{"location":"displays/graph/15-oled-patterns/#x-modulo-y","title":"X Modulo Y","text":"x % y
Sierpinsky Triangles Bitwise and of x and y
"},{"location":"displays/graph/15-oled-patterns/#x-y-24","title":"(x * y) & 24","text":""},{"location":"displays/graph/15-oled-patterns/#x-y-64","title":"(x * y) & 64","text":""},{"location":"displays/graph/15-oled-patterns/#40-x-y1","title":"40 % x % (y+1)","text":""},{"location":"displays/graph/15-oled-patterns/#reference","title":"Reference","text":"Martin Kleppe Post on Twitter
"},{"location":"displays/graph/16-tft-ili9341/","title":"LI9341 TDF Display","text":"This is a 3.2\" $10 240X320 color display that is easy to set up on the Raspberry Pi Pico using the SPI interface. The hardware supports a touch screen and an SD card, but we could not locate drivers for these components.
Sample $10 part on Amazon or ebay
The TFT uses a 16-bit representation of the color of each pixel:
This requires us to include the color565
library for doing color operations. So for example, to get the color yellow, you would need to do the following:
yellow = color565(255, 255, 0)\n
"},{"location":"displays/graph/16-tft-ili9341/#sample-spi-hello-world-example","title":"Sample SPI Hello World Example","text":"# print out \"Hello World!\" using the rotation=3 using 32-bit high font\n# the default is white text on a black background\nfrom ili934x import ILI9341\nfrom machine import Pin, SPI\nimport tt32\n\n# Use these PIN definitions. SCK must be on 2 and data (SDL) on 3\nSCK_PIN = 2\nMISO_PIN = 3 # labeled SDI(MOSI) on the back of the display\nDC_PIN = 4\nRESET_PIN = 5\nCS_PIN = 6\n\n# mosi=Pin(23)\n# miso=Pin(MISO_PIN)\nspi = SPI(0, baudrate=20000000, mosi=Pin(MISO_PIN),sck=Pin(SCK_PIN))\ndisplay = ILI9341(spi, cs=Pin(CS_PIN), dc=Pin(DC_PIN), rst=Pin(RESET_PIN), w=320, h=240, r=3)\ndisplay.erase()\ndisplay.set_font(tt32)\ndisplay.set_pos(0,0)\ndisplay.print('Hello World!')\n
"},{"location":"displays/graph/16-tft-ili9341/#draw-random-rectangles","title":"Draw Random Rectangles","text":"from ili934x import ILI9341, color565\nfrom machine import Pin, SPI\nfrom utime import sleep\nfrom random import randint\n\nWIDTH = 320\nHALF_WIDTH = int(WIDTH/2)\nHEIGHT = 240\nHALF_HEIGHT = int(HEIGHT/2)\nROTATION = 3 # landscape with 0,0 in upper left and pins on left\n\nSCK_PIN = 2\nMISO_PIN = 3\nDC_PIN = 4\nRST_PIN = 5\nCS_PIN = 6\n\n# mosi=Pin(23)\n# miso=Pin(MISO_PIN)\nspi = SPI(0, baudrate=20000000, mosi=Pin(MISO_PIN), sck=Pin(SCK_PIN))\ndisplay = ILI9341(spi, cs=Pin(CS_PIN), dc=Pin(DC_PIN), rst=Pin(RST_PIN), w=WIDTH, h=HEIGHT, r=ROTATION)\ndisplay.erase()\n\n# color defintions converted to 565 represnetations\nblack = color565(0, 0, 0)\nwhite = color565(255, 255, 255)\nred = color565(255, 0, 0)\ngreen = color565(0, 255, 0)\nblue = color565(0, 0, 255)\nyellow = color565(255, 255, 0)\ncyan = color565(0, 255, 255)\nmagenta = color565(255, 0, 255)\ngray = color565(128, 128, 128)\nlight_gray = color565(192, 192, 192)\ndark_gray = color565(64, 64, 64)\nbrown = color565(165, 42, 42)\norange = color565(255, 60, 0)\n# 150 for the green and blue wash out the colors\npink = color565(255, 130, 130)\npurple = color565(128, 0, 128)\nlavender = color565(150, 150, 200)\nbeige = color565(200, 200, 150)\n# by definition, maroon is 50% of the red on, but 128 is way too bright\nmaroon = color565(105, 0, 0)\nolive = color565(128, 128, 0)\nturquoise = color565(64, 224, 208)\ndark_green = color565(0,100,0)\ncolor_list = [white, red, green, blue, yellow, cyan, magenta,\n gray, light_gray, dark_gray, brown, orange, pink, purple, lavender,\n beige, maroon, olive, turquoise, dark_green, black]\ncolor_num = len(color_list)\n\n# Draw forever\nwhile True:\n # rect_fill(x, y, width, height, color)\n x = randint(0, HALF_WIDTH)\n y = randint(0, HALF_HEIGHT)\n width = randint(0, HALF_WIDTH)\n height = randint(0, HALF_HEIGHT)\n color = color_list[randint(0, color_num-1)]\n print('fill_rectangle(', x, y, width, height, color)\n display.fill_rectangle(x, y, width, height, color)\n
"},{"location":"displays/graph/16-tft-ili9341/#draw-color-lists","title":"Draw Color Lists","text":"One of the best ways to study the color values is to display a rectangle and list the color name and values under the rectangle.
Here is a sample program that will do this.
from ili934x import ILI9341, color565\nfrom machine import Pin, SPI\nfrom utime import sleep\nfrom random import randint\nimport tt32\n\nWIDTH = 320\nHALF_WIDTH = int(WIDTH/2)\nHEIGHT = 240\nHALF_HEIGHT = int(HEIGHT/2)\nROTATION = 3 # landscape with 0,0 in upper left and pins on left\nSCK_PIN = 2\nMISO_PIN = 3\nDC_PIN = 4\nRST_PIN = 5\nCS_PIN = 6\n\n# mosi=Pin(23)\n# miso=Pin(MISO_PIN)\nspi = SPI(0, baudrate=20000000, mosi=Pin(MISO_PIN),sck=Pin(SCK_PIN))\ndisplay = ILI9341(spi, cs=Pin(CS_PIN), dc=Pin(DC_PIN), rst=Pin(RST_PIN), w=WIDTH, h=HEIGHT, r=ROTATION)\ndisplay.set_font(tt32)\ndisplay.erase()\n\n# color defintions convered to 565 represnetations\nblack = color565(0, 0, 0)\nwhite = color565(255, 255, 255)\nred = color565(255, 0, 0)\ngreen = color565(0, 255, 0)\nblue = color565(0, 0, 255)\nyellow = color565(255, 255, 0)\ncyan = color565(0, 255, 255)\nmagenta = color565(255, 0, 255)\ngray = color565(128, 128, 128)\nlight_gray = color565(192, 192, 192)\ndark_gray = color565(64, 64, 64)\nbrown = color565(165, 42, 42)\norange = color565(255, 60, 0)\n# 150 for the green and blue wash out the colors\npink = color565(255, 130, 130)\npurple = color565(128, 0, 128)\nlavender = color565(150, 150, 200)\nbeige = color565(200, 200, 150)\n# by definition, maroon is 50% of the red on, but 128 is way too bright\nmaroon = color565(105, 0, 0)\nolive = color565(128, 128, 0)\nturquoise = color565(64, 224, 208)\ndark_green = color565(0,100,0)\ncolor_list = [white, red, green, blue, yellow, cyan, magenta,\n gray, light_gray, dark_gray, brown, orange, pink, purple, lavender,\n beige, maroon, olive, turquoise, dark_green, black]\ncolor_names = ['white (255,255,255)', 'red (255,0,0)', 'green (0,255,0)', 'blue (0,0,255)', 'yellow (255,255,0)',\n 'cyan (0,255,255)', 'magenta (255,0,255)',\n 'gray (128,128,128)', 'light gray (192,192,192)', 'dark gray (64,64,64)',\n 'brown (165,42,42)', 'orange (255,60,0)', 'pink (255,130,130)', 'purple (128,0,128)',\n 'lavender (150,150,200)',\n 'beige (200,200,150)', 'maroon (105,0,0)', 'olive (128,128,0)', 'turquoise (64,224,208)',\n 'dark green (0,100,0)', 'black (0,0,0)']\ncolor_num = len(color_list)\n\ndisplay.fill_rectangle(0, 0, WIDTH, HEIGHT, black)\nwhile True:\n for i in range(0, color_num):\n display.fill_rectangle(0, 0, WIDTH, HEIGHT-33, color_list[i])\n # black behind the white text\n display.fill_rectangle(0, HEIGHT-32, WIDTH, 32, black)\n\n display.set_pos(0,HEIGHT-32)\n display.print(color_names[i])\n print(color_names[i])\n sleep(1)\n
"},{"location":"displays/graph/16-tft-ili9341/#screen-update-speed","title":"Screen Update Speed","text":"One large disadvantage of this display is the very slow refresh rate. Transmitting the entire screen of 240X320 with two bytes per pixel takes a long time over SPI. This makes this setup difficult to use for animation.
"},{"location":"displays/graph/16-tft-ili9341/#ball-bounce-animation","title":"Ball Bounce Animation","text":"Here is a very slow \"ball bounce\" animation that is slow and has a lot of flicker.
from ili934x import ILI9341, color565\nfrom machine import Pin, SPI\nfrom utime import sleep\n\nWIDTH = 320\nHEIGHT = 240\nROTATION = 3 # landscape with 0,0 in upper left and pins on left\nSCK_PIN = 2\nMISO_PIN = 3\nDC_PIN = 4\nRST_PIN = 5\nCS_PIN = 6\n\n# mosi=Pin(23)\n# miso=Pin(MISO_PIN)\nspi = SPI(0, baudrate=20000000, mosi=Pin(MISO_PIN),sck=Pin(SCK_PIN))\ndisplay = ILI9341(spi, cs=Pin(CS_PIN), dc=Pin(DC_PIN), rst=Pin(RST_PIN), w=WIDTH, h=HEIGHT, r=ROTATION)\ndisplay.erase()\n\n# color defintions convered to 565 represnetations\nblack = color565(0, 0, 0)\nwhite = color565(255, 255, 255)\nred = color565(255, 0, 0)\ngreen = color565(0, 255, 0)\nblue = color565(0, 0, 255)\n\n# ok, not really a circle - just a square for now\ndef draw_ball(x,y, size, color):\n if size == 1:\n display.pixel(x, y, color) # draw a single pixel\n else:\n display.fill_rectangle(x, y, size, size, color)\n\nball_size = 20\n# start in the middle of the screen\ncurrent_x = int(WIDTH / 2)\ncurrent_y = int(HEIGHT / 2)\n# start going down to the right\ndirection_x = 1\ndirection_y = -1\n# delay_time = .0001\n\n# Bounce forever\nwhile True:\n # draw the square ball in white\n draw_ball(current_x,current_y, ball_size, white)\n sleep(.1)\n # the erase the old ball takes too long and causes a flicker\n draw_ball(current_x,current_y,ball_size, black)\n if current_x < 0:\n direction_x = 1\n # right edge test\n if current_x > WIDTH - ball_size -2:\n direction_x = -1\n # top edge test\n if current_y < 0:\n direction_y = 1\n # bottom edge test\n if current_y > HEIGHT - ball_size - 2:\n direction_y = -1\n # update the ball\n current_x = current_x + direction_x\n current_y = current_y + direction_y\n
"},{"location":"displays/graph/16-tft-ili9341/#references","title":"References","text":"Jeffmer's GitHub library which includes four fonts - the sizes are 8, 14, 24 and 32 pixels.
Amazon HiLetgo ILI9341 2.8\" SPI TFT LCD Display Touch Panel 240X320 with PCB 5V/3.3V STM32
ebay Listing
The SSD1351 is a $40 color OLED measures 1.5\" diagonal and contains 128x128 RGB pixels. It supports a SPI interface.
"},{"location":"displays/graph/17-ssd1352/#drawing-shapes","title":"Drawing Shapes","text":"This demo shows how to draw shapes on the display. It starts out with simple lines and rectangles then progresses to more complex shapes such as circles and ellipses.
\"\"\"SSD1351 demo (shapes).\"\"\"\nfrom time import sleep\nfrom ssd1351 import Display, color565\nfrom machine import Pin, SPI\n\n\ndef test():\n \"\"\"Test code.\"\"\"\n # Baud rate of 14500000 seems about the max\n spi = SPI(2, baudrate=14500000, sck=Pin(18), mosi=Pin(23))\n print('spi started')\n display = Display(spi, dc=Pin(17), cs=Pin(5), rst=Pin(16))\n print('display started')\n\n display.clear(color565(64, 0, 255))\n sleep(1)\n\n display.clear()\n\n display.draw_hline(10, 127, 63, color565(255, 0, 255))\n sleep(1)\n\n display.draw_vline(10, 0, 127, color565(0, 255, 255))\n sleep(1)\n\n display.fill_hrect(23, 50, 30, 75, color565(255, 255, 255))\n sleep(1)\n\n display.draw_hline(0, 0, 127, color565(255, 0, 0))\n sleep(1)\n\n display.draw_line(127, 0, 64, 127, color565(255, 255, 0))\n sleep(2)\n\n display.clear()\n\n coords = [[0, 63], [78, 80], [122, 92], [50, 50], [78, 15], [0, 63]]\n display.draw_lines(coords, color565(0, 255, 255))\n sleep(1)\n\n display.clear()\n display.fill_polygon(7, 63, 63, 50, color565(0, 255, 0))\n sleep(1)\n\n display.fill_rectangle(0, 0, 15, 127, color565(255, 0, 0))\n sleep(1)\n\n display.clear()\n\n display.fill_rectangle(0, 0, 63, 63, color565(128, 128, 255))\n sleep(1)\n\n display.draw_rectangle(0, 64, 63, 63, color565(255, 0, 255))\n sleep(1)\n\n display.fill_rectangle(64, 0, 63, 63, color565(128, 0, 255))\n sleep(1)\n\n display.draw_polygon(3, 96, 96, 30, color565(0, 64, 255),\n rotate=15)\n sleep(3)\n\n display.clear()\n\n display.fill_circle(32, 32, 30, color565(0, 255, 0))\n sleep(1)\n\n display.draw_circle(32, 96, 30, color565(0, 0, 255))\n sleep(1)\n\n display.fill_ellipse(96, 32, 30, 16, color565(255, 0, 0))\n sleep(1)\n\n display.draw_ellipse(96, 96, 16, 30, color565(255, 255, 0))\n\n sleep(5)\n display.cleanup()\n\n\ntest()\n
"},{"location":"displays/graph/17-ssd1352/#ssd1351-micropython-driver","title":"SSD1351 MicroPython Driver","text":"The driver is here RDagger GitHub
SSD1351 MicroPython Driver on RDagger GitHub
"},{"location":"displays/graph/17-ssd1352/#ssd1351-datasheet","title":"SSD1351 Datasheet","text":"Datasheet on NewHaven Displays
"},{"location":"displays/graph/18-neopixel-matrix/","title":"NeoPixel Matrix Display","text":""},{"location":"displays/graph/18-neopixel-matrix/#introduction","title":"Introduction","text":"This lesson uses MicroPython to control display that uses a 8X32 matrix of WS2812 RGB LEDs to display information. The entire display is controlled by three wires, a ground, +5V, and a serial data signal. We will use the MicroPython builtin NeoPixel library to control the display. You can use many of the programs in the NeoPixel Basics lesson to control the display. The key difference is that we will need to convert matrix coordinates to NeoPixel index numbers.
"},{"location":"displays/graph/18-neopixel-matrix/#purchasing-hardware","title":"Purchasing Hardware","text":"You can purchase a matrix of 8X32 WS2812 RGB LED on eBay for about $12 on eBay or about $100 on Adafruit. They are also available in 16X16 versions and the devices can be chained together to make larger displays. On our version tested here, we have a total of 8*32 = 256 pixels.
"},{"location":"displays/graph/18-neopixel-matrix/#basic-software-setup","title":"Basic Software Setup","text":"We must first create a function that will draw a pixel at a given x and y position. This is complicated by the fact that the matrix is not a regular grid, but rather a grid that is connected in a zig-zag serpentine pattern illustrated below.
Note that the math for doing even and odd columns is different. The even columns are drawn from the top down and the odd columns are drawn from the bottom to the top which is the order the pixels are wired together in the matrix.
To use the functions that draw pixels, we must first create a function that will convert the x and y coordinates to a NeoPixel index. This is done by the following function. We will then pass this function into the library that will draw characters on the screen.
from machine import Pin\nfrom neopixel import NeoPixel\n\nNEOPIXEL_PIN = 0\nROWS = 8\nCOLS = 32\nNUMBER_PIXELS = ROWS * COLS\n# Allocate memory for the NeoPixel matrix\nmatrix = NeoPixel(Pin(NEOPIXEL_PIN), NUMBER_PIXELS)\n\ndef write_pixel(x, y, value):\n if y >= 0 and y < ROWS and x >=0 and x < COLS:\n # odd count rows 1, 3, 5 the wire goes from bottup\n if x % 2: \n strip[(x+1)*ROWS - y - 1] = value \n else: # even count rows, 0, 2, 4 the wire goes from the top down up\n strip[x*ROWS + y] = value\n
"},{"location":"displays/graph/18-neopixel-matrix/#testing-your-write-pixel-function","title":"Testing Your Write Pixel Function","text":"We can then test the function by calling it at four corners with different colors.
# draw four colors at each corner of the matrix\nwrite_pixel(0, 0, (255, 0, 0)) # draw a red pixel at the top left corner\nwrite_pixel(7, 0, (0, 255, 0)) # draw a green pixel at the lower left corner\nwrite_pixel(0, 7, (0, 0, 255)) # draw a blue pixel at the top right corner\nwrite_pixel(7, 7, (255, 255, 255)) # draw a white pixel at the lower right corner\n
"},{"location":"displays/graph/18-neopixel-matrix/#bounce-a-ball","title":"Bounce a Ball","text":"To test the write_pixel() function, lets write a function that will draw a ball at a given x and y position. We will move the ball around the screen and reverse the direction when the ball hits the edge of the screen.
# Bounce a ball around a NeoPixel Matrix\nfrom neopixel import NeoPixel\nfrom utime import sleep\n\nNEOPIXEL_PIN = 0\nROWS = 8\nCOLS = 32\nNUMBER_PIXELS = ROWS * COLS\nstrip = NeoPixel(machine.Pin(NEOPIXEL_PIN), NUMBER_PIXELS)\n\n# matrix = [[0 for _ in range(cols)] for _ in range(rows)]\ndef clear():\n for i in range(0, NUMBER_PIXELS):\n strip[i] = (0,0,0)\n strip.write()\n\ndef write_pixel(x, y, value):\n if y >= 0 and y < ROWS and x >=0 and x < COLS:\n # odd count rows 1, 3, 5 the wire goes from bottup\n if x % 2: \n strip[(x+1)*ROWS - y - 1] = value \n else: # even count rows, 0, 2, 4 the wire goes from the top down up\n strip[x*ROWS + y] = value\n\ndef show():\n strip.write()\n\nbrightness=1\nx=0\ny=0\ndx = 1\ndy = 1\ncounter = 0\nwhile True:\n if x <= 0:\n dx = 1\n if y <= 0:\n dy = 1\n if x >= COLS-1:\n dx = -1\n if y >= ROWS-1:\n dy = -1\n print(x,y)\n if counter < 100:\n write_pixel(x, y, (brightness,0,0)) # blue\n elif counter < 200:\n write_pixel(x, y, (0,brightness,0)) # blue\n elif counter < 300:\n write_pixel(x, y, (0,0,brightness)) # blue\n show()\n x += dx\n y += dy\n counter += 1\n if counter > 300:\n counter = 0\n if not counter % 150:\n x += 1\n sleep(.1)\n
"},{"location":"displays/graph/18-neopixel-matrix/#bitmap-library","title":"Bitmap LIbrary","text":"# MicroPython basic bitmap font renderer.\n# Author: Tony DiCola\n# License: MIT License (https://opensource.org/licenses/MIT)\ntry:\n import ustruct\nexcept ImportError:\n import struct as ustruct\n\n\nclass BitmapFont:\n\n def __init__(self, width, height, pixel, font_name='font5x8.bin'):\n # Specify the drawing area width and height, and the pixel function to\n # call when drawing pixels (should take an x and y param at least).\n # Optionally specify font_name to override the font file to use (default\n # is font5x8.bin). The font format is a binary file with the following\n # format:\n # - 1 unsigned byte: font character width in pixels\n # - 1 unsigned byte: font character height in pixels\n # - x bytes: font data, in ASCII order covering all 255 characters.\n # Each character should have a byte for each pixel column of\n # data (i.e. a 5x8 font has 5 bytes per character).\n self._width = width\n self._height = height\n self._pixel = pixel\n self._font_name = font_name\n\n def init(self):\n # Open the font file and grab the character width and height values.\n # Note that only fonts up to 8 pixels tall are currently supported.\n self._font = open(self._font_name, 'rb')\n self._font_width, self._font_height = ustruct.unpack('BB', self._font.read(2))\n\n def deinit(self):\n # Close the font file as cleanup.\n self._font.close()\n\n def __enter__(self):\n self.init()\n return self\n\n def __exit__(self, exception_type, exception_value, traceback):\n self.deinit()\n\n def draw_char(self, ch, x, y, *args, **kwargs):\n # Don't draw the character if it will be clipped off the visible area.\n if x < -self._font_width or x >= self._width or \\\n y < -self._font_height or y >= self._height:\n return\n # Go through each column of the character.\n for char_x in range(self._font_width):\n # Grab the byte for the current column of font data.\n self._font.seek(2 + (ord(ch) * self._font_width) + char_x)\n line = ustruct.unpack('B', self._font.read(1))[0]\n # Go through each row in the column byte.\n for char_y in range(self._font_height):\n # Draw a pixel for each bit that's flipped on.\n if (line >> char_y) & 0x1:\n self._pixel(x + char_x, y + char_y, *args, **kwargs)\n\n def text(self, text, x, y, *args, **kwargs):\n # Draw the specified text at the specified location.\n for i in range(len(text)):\n self.draw_char(text[i], x + (i * (self._font_width + 1)), y,\n *args, **kwargs)\n\n def width(self, text):\n # Return the pixel width of the specified text message.\n return len(text) * (self._font_width + 1)\n
"},{"location":"displays/graph/18-neopixel-matrix/#full-code","title":"Full Code","text":"# LED Matrix message scroller demo.\n\nimport bitmapfont\nimport machine\nimport utime\nfrom neopixel import NeoPixel\n\nNEOPIXEL_PIN = 0\nROWS = 8\nCOLS = 32\nNUMBER_PIXELS = ROWS * COLS\nmatrix = NeoPixel(machine.Pin(NEOPIXEL_PIN), NUMBER_PIXELS)\n\ndef fill(val):\n for i in range(0, NUMBER_PIXELS):\n matrix[i] = val\n\n# Configuration:\nDISPLAY_WIDTH = 32 # Display width in pixels.\nDISPLAY_HEIGHT = 8 # Display height in pixels.\nSPEED = 20.0 # Scroll speed in pixels per second.\n\ndef show():\n matrix.write()\n\ndef write_pixel_value(x, y, value):\n if y >= 0 and y < ROWS and x >=0 and x < COLS:\n # odd count rows 1, 3, 5 the wire goes from bottup\n if x % 2: \n matrix[(x+1)*ROWS - y - 1] = value \n else: # even count rows, 0, 2, 4 the wire goes from the top down up\n matrix[x*ROWS + y] = value\n\ndef write_pixel(x, y):\n write_pixel_value(x, y, (1,1,2))\n\ndef scroll_text(message):\n\n with bitmapfont.BitmapFont(DISPLAY_WIDTH, DISPLAY_HEIGHT, write_pixel) as bf:\n # Global state:\n pos = DISPLAY_WIDTH # X position of the message start.\n message_width = bf.width(message) # Message width in pixels.\n last = utime.ticks_ms() # Last frame millisecond tick time.\n speed_ms = SPEED / 1000.0 # Scroll speed in pixels/ms.\n # Main loop:\n while True:\n # Compute the time delta in milliseconds since the last frame.\n current = utime.ticks_ms()\n delta_ms = utime.ticks_diff(current, last)\n last = current\n # Compute position using speed and time delta.\n pos -= speed_ms*delta_ms\n if pos < -message_width:\n pos = DISPLAY_WIDTH\n # Clear the matrix and draw the text at the current position.\n fill((0,0,0))\n bf.text(message, int(pos), 0)\n # Update the matrix LEDs.\n show()\n # Sleep a bit to give USB mass storage some processing time (quirk\n # of SAMD21 firmware right now).\n utime.sleep_ms(20)\n\nwrite_pixel(0,0)\nshow()\n#scroll_text('Dan Loves Ann!')\nscroll_text('MicroPython Rocks')\n
"},{"location":"displays/graph/18-neopixel-matrix/#references","title":"References","text":""},{"location":"displays/graph/19-wiring-harness/","title":"Display Wiring Harness","text":"Unlike simple sensors that only have a few wires, displays have up to seven wires that need to be connected. This can be tricky when we use breadboards where we can accidentally pull one wire out.
To keep our displays running reliably, we can use a 20 cm ribbon cable and some hot glue to make a connector that is easy to hook up. It will be very reliable.
We start by purchasing some 20 cm long Male-Female Dupont ribbon connectors from eBay. The price should be about $8 for 120 connectors. Make sure to get the Male-Female version.
We then will separate 7 of these wires making sure to put the black and red colors in the GND and VCC edge of the group of wires.
You can see a close-up of each of the colors and their connections in the picture below.
At the other end of the cable, we need to make a small change in the order of the cable. Here are the changes:
We can then plug this group of wires directly into the breadboard from breadboard rows 3 to 9. This is shown below.
We designed these connections with the following rules:
We have found that once we create these cable assemblies with hot glue to keep the pins in the right order it makes it much easier to connect the displays.
Warning
Note that we still MUST make sure that the black wire in the wiring harness is connected to the GND. It is easy to get the cable reversed so make sure to double-check the cable orientation before you use it.
"},{"location":"displays/graph/19-wiring-harness/#cable-wiring-diagram","title":"Cable Wiring Diagram","text":"Here is the detailed wiring diagram showing the wires as they route from the back of the OLED display to the pins on the breadboard:
"},{"location":"displays/graph/19-wiring-harness/#sample-python-code","title":"Sample Python Code","text":"from machine import Pin\n\n# Customize these GPIO numbers for your layout\n# Note these are not breadboard row numbers\n# The breadboard row numbers are 4,5,6,7 and 9 with GND on row 8\nSCL_PIN = 2\nSDA_PIN = 3\nRES_PIN = 4\nDC_PIN = 5\nCS_PIN = 6\n\n# create the Pin objects\nscl=Pin(SCL_PIN)\nsda=Pin(SDA_PIN)\nres=Pin(RES_PIN)\ndc=Pin(DC_PIN)\ncs = Pin(CS_PIN)\n\nspi=machine.SPI(0, sck=scl, mosi=sda)\noled = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, dc, res, cs)\n
"},{"location":"displays/graph/19-wiring-harness/#building-a-harness-for-the-cytron-maker-pi-rp2040-board","title":"Building A Harness for the Cytron Maker Pi RP2040 Board","text":"We can also make a display harness for the Cytron Maker Pi RP2040 Board. To do this we will need to use three grove connectors. We use all four wires of the first Grove connector, two of the data signals on the second and just a single wire on the third Grove connector. This connector is shown below.
The MicroPython code for this harness is the following:
from machine import Pin\n\n# Customize these GPIO pin numbers for your layout\nSCL_PIN = 2\nSDA_PIN = 3\nRES_PIN = 4\nDC_PIN = 5\nCS_PIN = 16\n\n# create the Pin objects\nscl=Pin(SCL_PIN)\nsda=Pin(SDA_PIN)\nres=Pin(RES_PIN)\ndc=Pin(DC_PIN)\ncs = Pin(CS_PIN)\n\nspi=machine.SPI(0, sck=scl, mosi=sda)\noled = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, dc, res, cs)\n
Note
This code is exactly the same as the Pico version above with the exception of the CS_PIN which was on GPIO 6 but we now moved it to GPIO 16.
"},{"location":"displays/graph/20-seven-segments/","title":"Drawing a Seven Segment Display","text":"https://en.wikipedia.org/wiki/Seven-segment_display
Wikipedia Seven Segment Display
"},{"location":"displays/graph/40-oled-references/","title":"OLED References","text":"In this section cover small non-graphical displays such as LED bar displays, LCD character displays and other displays that don't require a drawing library and support for framebuffers. There is a separate section for graphical displays.
"},{"location":"displays/non-graph/02-led-button/","title":"LED Buttons Lab","text":"LED Button
"},{"location":"displays/non-graph/03-10-bar-leds/","title":"Ten Bar LED Display","text":""},{"location":"displays/non-graph/03-10-bar-leds/#goals-for-the-lesson","title":"Goals for the Lesson","text":"These LED displays can be purchased on eBay for around 40 cents each in quantity 10. These displays are ideal for showing a reading such as a battery charge or a signal strength.
Our goal is to learn how to use python lists to turn on and off a row of 10 LEDs.
"},{"location":"displays/non-graph/03-10-bar-leds/#circuit","title":"Circuit","text":"The LEDs come in a dual-in-line package with each of the LEDs connected by the pins aligned across the package.
In the circuit below, I connected the positive (anode) of each LED to a GPIO pin and the negative (cathode) through a 330-ohm resistor to the GND rail of the solderless breadboard.
Note! You MUST use a current limiting resistor or you will burn out the LED.
One end of each of the bars will go to one of the power rails and the other to a GIPO pin. I used the pis on the lower part of the Raspberry Pi Pico for this demo.
"},{"location":"displays/non-graph/03-10-bar-leds/#programming","title":"Programming","text":"We will create a list that has each of the GPIO pins for output.
pin_ids = [12,13,14,15,20,19,18,17,16]\n
For each pin on this list, we will create a new list that contains the pin object that we can turn on or off.
from machine import Pin\nfrom utime import sleep\n\npin_ids = [12,13,14,15,20,19,18,17,16]\npins = []\npin_ids\nfor i in pin_ids:\n pins.append(machine.Pin(pin_ids[i], machine.Pin.OUT))\n
We will use this same preamble code in all our examples.
"},{"location":"displays/non-graph/03-10-bar-leds/#code-to-blink-all-10-leds","title":"Code to Blink all 10 LEDs","text":"from machine import Pin\nfrom utime import sleep\n\npin_ids = [12,13,14,15,20,19,18,17,16]\npins = []\npin_ids\nfor i in pin_ids:\n pins.append(machine.Pin(pin_ids[i], machine.Pin.OUT))\n\ndelay = .5\nwhile True:\n # turn all the pins on\n for pin in pins:\n pins.on()\n sleep(delay) # wait\n # turn all the pins off\n for pin in pins:\n pins[i].off()\n sleep(delay)\n
"},{"location":"displays/non-graph/03-10-bar-leds/#sample-running-lights-example","title":"Sample Running Lights Example","text":"The \"running lights\" pattern gives the impression that there is a red object that is moving up and down a row. We do this by successively turning on adjacently LEDs and then turning them off. This give the illusion of motion.
from machine import Pin\nfrom utime import sleep\n\npin_ids = [12,13,14,15,20,19,18,17,16]\npins = []\n\nfor i in range(0, 9):\n pins.append(machine.Pin(pin_ids[i], machine.Pin.OUT))\n\ndelay = .1\nwhile True:\n for i in range(0, 9):\n pins[i].on()\n sleep(delay)\n pins[i].off()\n for i in range(8, 1, -1):\n pins[i].on()\n sleep(delay)\n pins[i].off()\n
"},{"location":"displays/non-graph/03-10-bar-leds/#swipe","title":"swipe","text":"The swipe pattern turns each LED on but keeps it on until the direction is reversed.
"},{"location":"displays/non-graph/03-10-bar-leds/#adding-a-binary-counter-patterns","title":"Adding a Binary Counter Patterns","text":"We can also create another patten that will demonstrate binary counting. In this pattern, the least significant bit flickers on and off. For each cycle the adjacent pixel toggles once. The happens for each adjacent pixel. The most significant bit will only change every 1024 cycles of the least significant bit.
"},{"location":"displays/non-graph/04-8x8-led-matrix/","title":"MAX7219 8x8 LED Matrix","text":"This is a low-cost ($3) and easy-to-program device that is perfect for small projects that don't need a full graphical display. You will be surprised at how creative our students are with just an 8x8 display!
eBay Search for \"MAX7219 8x8 matrix\"
The device comes with five connectors:
We can communicate with the device using the standard SPI interface. There is also an 8x8 driver supplied by Mike Causer Here is an excerpt of how we configured the driver to use a single display:
from machine import SPI, Pin\nimport max7219\nfrom utime import sleep\nCLOCK_PIN = 2\nDATA_PIN = 3\nCS_PIN = 4\nspi0=SPI(0,baudrate=10000000, polarity=1, phase=0, sck=Pin(CLOCK_PIN), mosi=Pin(DATA_PIN))\ncs = Pin(CS_PIN, Pin.OUT)\nmatrix = max7219.Matrix8x8(spi0, cs , 1)\n# display text a x=0, y=0 and state = 1 (on)\nmatrix.text('1234', 0, 0, 1)\nmatrix.show()\n
You can change the last parameter from \"1\" to \"4\" if you have 4 displays wired together:
import max7219\nfrom machine import Pin, SPI\nspi = SPI(1)\nmatrix = max7219.Matrix8x8(spi0, cs , 4)\ndisplay.text('1234',0,0,1)\ndisplay.show()\n
The displays can also be \"cascaded\"
"},{"location":"displays/non-graph/04-8x8-led-matrix/#basic-program","title":"Basic Program","text":"from machine import SPI, Pin\nimport max7219\nfrom utime import sleep\nCLOCK_PIN = 2\nDATA_PIN = 3\nCS_PIN = 4\nspi0=SPI(0,baudrate=10000000, polarity=1, phase=0, sck=Pin(CLOCK_PIN), mosi=Pin(DATA_PIN))\n\ncs = Pin(CS_PIN, Pin.OUT)\n\nmatrix = max7219.Matrix8x8(spi0, cs , 1)\n\nmatrix.text('A', 0, 0, 1)\nmatrix.show()\nsleep(delay_time)\n
"},{"location":"displays/non-graph/04-8x8-led-matrix/#full-demo","title":"Full Demo","text":"from machine import SPI, Pin\nimport max7219\nfrom utime import sleep\nspi0=SPI(0,baudrate=10000000, polarity=1, phase=0, sck=Pin(2), mosi=Pin(3))\n\ncs = Pin(4, Pin.OUT)\n\nmatrix = max7219.Matrix8x8(spi0, cs , 1)\n\ndelay_time = 1\nwhile True:\n # Draw a single character\n\n matrix.text('A', 0, 0, 1)\n matrix.show()\n sleep(delay_time)\n\n # Draw an X in a box\n matrix.fill(0)\n matrix.line(0, 0, 7, 7, 1)\n matrix.show()\n sleep(delay_time)\n\n matrix.line(7, 0, 0, 7, 1)\n matrix.show()\n sleep(delay_time)\n\n matrix.rect(0, 0, 8, 8, 1)\n matrix.show()\n sleep(delay_time)\n matrix.fill(0)\n\n # Smile Face\n matrix.pixel(1, 1, 1)\n matrix.pixel(6, 1, 1)\n matrix.pixel(0, 4, 1)\n matrix.pixel(7, 4, 1)\n matrix.pixel(1, 5, 1)\n matrix.pixel(6, 5, 1)\n matrix.pixel(2, 6, 1)\n matrix.pixel(5, 6, 1)\n matrix.pixel(3, 7, 1)\n matrix.pixel(4, 7, 1)\n matrix.show()\n sleep(delay_time)\n matrix.fill(0)\n matrix.show()\n sleep(delay_time)\n
"},{"location":"displays/non-graph/05-4-digit/","title":"Four Digit LED Display","text":"In this lesson, we will use a 4-digit LED display to create a clock that displays the time of day. These clocks will use Mike Causer's tm1637 library to communicate with the four-digit display. Some of these displays also have a \"colon\" between the hour and minute digits that flashes every second.
You can purchase 4-digit LED displays on eBay for about $2 each.
"},{"location":"displays/non-graph/05-4-digit/#connections","title":"Connections","text":"These displays have four pins:
In our examples, we will connect the power to our 3.3 regulated output of the Pico. We will connect Data to GP0 and Clock to GP1.
The following example
from machine import Pin\nfrom time import sleep\nimport tm1637\n\n# data and clock pins\nDIO_PIN = 0\nCLK_PIN = 1\n\ntm = tm1637.TM1637(clk=Pin(CLK_PIN), dio=Pin(DIO_PIN))\n\n# display \"1234\"\ntm.write([1, 2, 3, 4])\n
The tm.write()
function takes a sequence of numbers and will shifts them in from right to left.
We can create a simple clock by using the localtime()
function when the programs first starts up and then we just update the time after the sleep() functions run for a second. This also can updates the colon between the hours and minutes.
localtime()
returns an array of numbers for date, hour, minute and second. In our example here, we only need the hour and minutes.
# a simple clock that only grabs the time from the server on startup\nimport tm1637\nfrom machine import Pin\nfrom utime import sleep, localtime\n\ntm = tm1637.TM1637(clk=Pin(1), dio=Pin(0))\n\nnow = localtime()\nhour = now[3]\n# use AM/PM 12 hour time\nif hour > 12:\n hour = hour - 12\nminute = now[4]\nsec = now[5]\nprint(hour, ':', minute, ' ', sec, sep='')\n\n# update from the first time\nwhile True:\n # turn the colon on\n tm.numbers(hour,minute,colon=True)\n sleep(0.5)\n # turn the colon off\n tm.numbers(hour,minute,colon=False)\n sleep(0.5)\n sec = sec + 1\n if sec == 60:\n minute = minute + 1\n sec = 0\n if minute == 60:\n hour = hour + 1\n minute = 0\n if hour == 24:\n hour = 0\n
A more accurate version will access the new time from the server every minute.
"},{"location":"displays/non-graph/05-4-digit/#accurate-clock","title":"Accurate Clock","text":"# a more accurate clock that only grabs the time from the server once per minute\nimport tm1637\nfrom machine import Pin\nfrom utime import sleep, localtime\n\nhour = 0\nminute = 0\nsec = 0\n\ndef update_time():\n global hour, minute, second\n now = localtime()\n hour = now[3]\n # use AM/PM\n if hour > 12:\n hour = hour - 12\n minute = now[4]\n sec = now[5]\n\ntm = tm1637.TM1637(clk=Pin(1), dio=Pin(0))\n\nupdate_time()\n# loop every second\nwhile True:\n tm.numbers(hour,minute,colon=True)\n sleep(0.5)\n tm.numbers(hour,minute,colon=False)\n sleep(0.5)\n sec = sec + 1\n if sec == 60:\n # get the new time from the host\n update_time()\n print(hour, ':', minute, ' ', sec, sep='')\n minute = minute + 1\n sec = 0\n if minute == 60:\n hour = hour + 1\n minute = 0\n if hour == 24:\n hour = 0\n
"},{"location":"displays/non-graph/05-4-digit/#references","title":"References","text":"This lesson is for using the LCM1602 I2C LCD interface. It is a popular It has four wires:
The photo above shows the use of a 3.3 to 5v voltage converter. This allows us to use the full 5v to the LCD backlight so we get bright contrast. You can connect the VCC to the 3V3(out) pin but the display will be harder to read.
"},{"location":"displays/non-graph/10-character-lcd-display/#i2c-address-scanner-test","title":"I2C Address Scanner Test","text":"Our first task is to make sure that the 1602 chip's I2C circuits are working. We use the following I2C scanner code to do this.
import machine\nI2C_SDA_PIN = 0\nI2C_SCL_PIN = 1\ni2c=machine.I2C(0,sda=machine.Pin(I2C_SDA_PIN), scl=machine.Pin(I2C_SCL_PIN), freq=400000)\n\nprint('Scanning I2C bus.')\ndevices = i2c.scan() # this returns a list of devices\ndevice_count = len(devices)\nif device_count == 0:\n print('No i2c device found.')\nelse:\n print(device_count, 'devices found.')\nfor device in devices:\n print('Decimal address:', device, \", Hex address: \", hex(device))\n
"},{"location":"displays/non-graph/10-character-lcd-display/#scanner-result","title":"Scanner Result","text":"Scanning I2C bus.\n1 devices found.\nDecimal address: 39 , Hex address: 0x27\n
"},{"location":"displays/non-graph/10-character-lcd-display/#testing-the-lcd","title":"Testing the LCD","text":"from machine import I2C\nfrom lcd_api import LcdApi\nfrom pico_i2c_lcd import I2cLcd\n\nI2C_ADDR = 0x27\nI2C_NUM_ROWS = 2\nI2C_NUM_COLS = 16\n\ni2c = I2C(0, sda=machine.Pin(0), scl=machine.Pin(1), freq=400000)\nlcd = I2cLcd(i2c, I2C_ADDR, I2C_NUM_ROWS, I2C_NUM_COLS) \nlcd.putstr(\"CoderDojo Rocks!\")\n
"},{"location":"displays/non-graph/10-character-lcd-display/#putting-the-device-through-display-option-tests","title":"Putting the Device Through Display Option Tests","text":"Now that we know how to display text on the device, we can learn how other functions work:
import utime\n\nimport machine\nfrom machine import I2C\nfrom lcd_api import LcdApi\nfrom pico_i2c_lcd import I2cLcd\n\nI2C_ADDR = 0x27\nI2C_NUM_ROWS = 2\nI2C_NUM_COLS = 16\n\ndef test_main():\n #Test function for verifying basic functionality\n print(\"Running test_main\")\n i2c = I2C(0, sda=machine.Pin(0), scl=machine.Pin(1), freq=400000)\n lcd = I2cLcd(i2c, I2C_ADDR, I2C_NUM_ROWS, I2C_NUM_COLS) \n lcd.putstr(\"CoderDojo Rocks!\")\n utime.sleep(10)\n lcd.clear()\n count = 0\n while True:\n lcd.clear()\n time = utime.localtime()\n lcd.putstr(\"{year:>04d}/{month:>02d}/{day:>02d} {HH:>02d}:{MM:>02d}:{SS:>02d}\".format(\n year=time[0], month=time[1], day=time[2],\n HH=time[3], MM=time[4], SS=time[5]))\n if count % 10 == 0:\n print(\"Turning cursor on\")\n lcd.show_cursor()\n if count % 10 == 1:\n print(\"Turning cursor off\")\n lcd.hide_cursor()\n if count % 10 == 2:\n print(\"Turning blink cursor on\")\n lcd.blink_cursor_on()\n if count % 10 == 3:\n print(\"Turning blink cursor off\")\n lcd.blink_cursor_off() \n if count % 10 == 4:\n print(\"Turning backlight off\")\n lcd.backlight_off()\n if count % 10 == 5:\n print(\"Turning backlight on\")\n lcd.backlight_on()\n if count % 10 == 6:\n print(\"Turning display off\")\n lcd.display_off()\n if count % 10 == 7:\n print(\"Turning display on\")\n lcd.display_on()\n if count % 10 == 8:\n print(\"Filling display\")\n lcd.clear()\n string = \"\"\n for x in range(32, 32+I2C_NUM_ROWS*I2C_NUM_COLS):\n string += chr(x)\n lcd.putstr(string)\n count += 1\n utime.sleep(2)\n\n#if __name__ == \"__main__\":\ntest_main()\n
"},{"location":"displays/non-graph/10-character-lcd-display/#references","title":"References","text":"MFitzp article on OLED displays
Adafruit SSD1306 Driver
Adafruit LCD Guide
"},{"location":"displays/non-graph/seven-segment/","title":"Sample Seven Segment Display Lab","text":""},{"location":"displays/non-graph/seven-segment/#4-digit-seven-segment-display","title":"4 Digit Seven Segment Display","text":"Make sure to put a current limiting resistor in series with each LED. A 330 ohm resistor is a generally safe value for 5 volt circuits and you can use a 220 ohm resistor for 3.3 volt circuits.
This code was provided by Jaison Miller from his GitHub Repo.
```py from machine import Pin, PWM, Timer import utime
"},{"location":"displays/non-graph/seven-segment/#constants-where-the-pins-are-currently-plugged-into-etc","title":"Constants - where the pins are currently plugged into, etc.","text":"number_bitmaps = { 0: 0b00111111, 1: 0b00000110, 2: 0b01011011, 3: 0b01001111, 4: 0b01100110, 5: 0b01101101, 6: 0b01111101, 7: 0b00000111, 8: 0b01111111, 9: 0b01100111 } segment_masks = { 'a': 0b00000001, 'b': 0b00000010, 'c': 0b00000100, 'd': 0b00001000, 'e': 0b00010000, 'f': 0b00100000, 'g': 0b01000000 } pin_segments = { 'a': 10, 'b': 11, 'c': 12, 'd': 17, 'e': 16, 'f': 13, 'g': 14} pin_others = { 'decimal': 22, 'colon': 6, 'dash': 8 } pin_digits = { 1: 18, 2: 19, 3: 20, 4: 21 } pin_control_others = { 'colon': 27, 'dash': 7 }
"},{"location":"displays/non-graph/seven-segment/#initial-setup-of-the-pins-alternatives-include-using-pwm-to-set-the-brightness","title":"initial setup of the pins, alternatives include using PWM to set the brightness","text":""},{"location":"displays/non-graph/seven-segment/#if-not-using-pwm-then-make-sure-to-use-appropriate-resistors-to-avoid-blowing-the-leds-in-the-display-like-i-have","title":"if not using PWM then make sure to use appropriate resistors to avoid blowing the LEDs in the display (like I have)","text":"segment_maps = {}
for segment, pin in pin_segments.items(): segment_maps[segment] = Pin(pin, Pin.OUT)
other_pin_maps = {}
for feature, pin in pin_others.items(): other_pin_maps[feature] = Pin(pin, Pin.OUT)
digit_maps = {}
for digit, pin in pin_digits.items(): digit_maps[digit] = Pin(pin, Pin.OUT)
other_maps = {}
for feature, pin in pin_control_others.items(): other_maps[feature] = Pin(pin, Pin.OUT)
def render_digit_display(show_digit=1, number=8, decimal=False):
# turn everything off\nfor segment, mask in segment_masks.items():\n segment_maps[segment].value(1)\n\nother_pin_maps['decimal'].value(1)\n\n# turn on the digit required to be displayed\nfor digit, digit_pin in digit_maps.items():\n if show_digit == digit:\n digit_pin.value(1)\n # print(\"\\n\\nDigit: {} - Pin: {} - Number: {}\\n\".format(digit, pin, number))\n else:\n digit_pin.value(0)\n\nutime.sleep(0.001)\n\ndisplay_number_bitmap = number_bitmaps[number]\n\n# check every\nfor segment, mask in segment_masks.items():\n # print(\"segment: {}\\nmask: {}\".format(segment, mask))\n\n if display_number_bitmap & mask == mask:\n # print(\"segment OFF: {}\".format(segment))\n segment_maps[segment].value(0)\n else:\n segment_maps[segment].value(1)\n\n# show decimal\nif decimal:\n other_pin_maps['decimal'].value(0)\nelse:\n other_pin_maps['decimal'].value(1)\n\nutime.sleep(0.001)\n
def render_feature_display(show_colon=False, show_dash=False): if show_colon: other_pin_maps['colon'].value(0) other_maps['colon'].value(1) else: other_pin_maps['colon'].value(0) other_maps['colon'].value(0)
if show_dash:\n other_pin_maps['dash'].value(0)\n other_maps['dash'].value(1)\nelse:\n other_pin_maps['dash'].value(0)\n other_maps['dash'].value(0)\n
while True:
lt_year, lt_month, lt_mday, lt_hour, lt_minute, lt_second, lt_weekday, lt_yearday = utime.localtime()\n\n# testing out all the features of the display\ndigit_1_decimal = (lt_second % 4 == 0)\ndigit_2_decimal = (lt_second % 4 == 1)\ndigit_3_decimal = (lt_second % 4 == 2)\ndigit_4_decimal = (lt_second % 4 == 3)\n\nrender_digit_display(1, lt_minute // 10, digit_1_decimal)\nrender_digit_display(2, lt_minute % 10, digit_2_decimal)\nrender_digit_display(3, lt_second // 10, digit_3_decimal)\nrender_digit_display(4, lt_second % 10, digit_4_decimal)\n\nif (lt_second % 2 == 0):\n render_feature_display(True, False)\nelse:\n render_feature_display(False, True)\n\n ```\n
"},{"location":"getting-started/01-intro/","title":"Welcome to the CoderDojo course on MicroPython","text":"This course is about MicroPython, a variation of the popular Python programming language that is used to program microcontrollers.
"},{"location":"getting-started/01-intro/#intended-audience","title":"Intended Audience","text":"User should be familiar with the the following Python concepts:
imports
Our intent is to allow teachers and mentors around the world to integrate MicroPython into their courses without any fees. We want you to be able to use this content freely with a few conditions: please give us attribution and please don't resell our content for profit.
Note that we use the same Creative Commons licensing as the Raspberry Pi Foundation and the CoderDojo Foundation:
Creative Commons Attribution NonCommercial ShareAlike
This means you are free to reuse and remix this content for non-commercial educational purposes as long as you keep the attribution and preserve the license agreement.
"},{"location":"getting-started/01b-libraries/","title":"What is MicroPython?","text":"MicroPython is an implementation of the Python 3 programming language that includes a small subset of the Python standard library and is optimized to run on microcontrollers. (From micropython.org)
MicroPython was originally created by the Australian programmer and physicist Damien George. It is written in C.
MicroPython is now an OpenSource project and the source code is available in GitHub.
"},{"location":"getting-started/01b-libraries/#micropython-libraries","title":"Micropython Libraries","text":"When you start up your IDE, it may have a list of python modules built in. You can list the current modules you have installed by running the help('modules')
command.
help('modules')\n
"},{"location":"getting-started/01b-libraries/#micropython-builtin-functions","title":"MicroPython Builtin Functions","text":"MicroPython is designed to run quickly in a small memory system. So it has trimmed down many of the standard Python libraries to fit the needs of microcontrollers. Most of these libraries start with the letter \"u\" so that you are aware they are designed to run on microcontrollers.
cmath \u2013 mathematical functions for complex numbers\ngc \u2013 control the garbage collector\nmath \u2013 mathematical functions\nuarray \u2013 arrays of numeric data\nuasyncio \u2014 asynchronous I/O scheduler\nubinascii \u2013 binary/ASCII conversions\nucollections \u2013 collection and container types\nuerrno \u2013 system error codes\nuhashlib \u2013 hashing algorithms\nuheapq \u2013 heap queue algorithm\nuio \u2013 input/output streams\nujson \u2013 JSON encoding and decoding\nuos \u2013 basic \u201coperating system\u201d services\nure \u2013 simple regular expressions\nuselect \u2013 wait for events on a set of streams\nusocket \u2013 socket module\nussl \u2013 SSL/TLS module\nustruct \u2013 pack and unpack primitive data types\nusys \u2013 system specific functions\nutime \u2013 time related functions\nuzlib \u2013 zlib decompression\n_thread \u2013 multithreading support\n
"},{"location":"getting-started/01b-libraries/#micropython-specific-libraries","title":"MicroPython Specific Libraries","text":"btree \u2013 simple BTree database\nframebuf \u2014 frame buffer manipulation\nmachine \u2014 functions related to the hardware\nmicropython \u2013 access and control MicroPython internals\nnetwork \u2014 network configuration\nubluetooth \u2014 low-level Bluetooth\nucryptolib \u2013 cryptographic ciphers\nuctypes \u2013 access binary data in a structured way\n
"},{"location":"getting-started/01b-libraries/#adding-a-module","title":"Adding a module","text":"When you are using python and you attempt to use a module that python can't find you will get an error. You must then use the python pip
installer tool to add the new library.
Filter Only MicroPython Libraries
"},{"location":"getting-started/01b-libraries/#full-list-of-modules","title":"Full List of modules","text":"ESP-test audioop filecmp random\n__future__ base64 fileinput re\n_abc bcrypt fnmatch readline\n_ast bdb formatter reedsolo\n_asyncio binascii fractions reprlib\n_bisect binhex ftplib resource\n_blake2 bisect functools rlcompleter\n_bootlocale bitstring gc runpy\n_bz2 blink-builtin-led genericpath sched\n_cffi_backend brain_argparse getopt secrets\n_codecs brain_attrs getpass select\n_codecs_cn brain_builtin_inference gettext selectors\n_codecs_hk brain_collections glob send2trash\n_codecs_iso2022 brain_crypt grp serial\n_codecs_jp brain_curses gzip setuptools\n_codecs_kr brain_dataclasses hashlib sh1106\n_codecs_tw brain_dateutil heapq sh1106-test\n_collections brain_fstrings hmac shelve\n_collections_abc brain_functools html shlex\n_compat_pickle brain_gi http shutil\n_compression brain_hashlib i2c-display signal\n_contextvars brain_http i2c-scanner site\n_crypt brain_io i2c_lcd six\n_csv brain_mechanize i2clcd smtpd\n_ctypes brain_multiprocessing imaplib smtplib\n_ctypes_test brain_namedtuple_enum imghdr sndhdr\n_curses brain_nose imp socket\n_curses_panel brain_numpy_core_fromnumeric importlib socketserver\n_datetime brain_numpy_core_function_base inspect spi-debug\n_dbm brain_numpy_core_multiarray io sqlite3\n_decimal brain_numpy_core_numeric ipaddress sre_compile\n_dummy_thread brain_numpy_core_numerictypes isort sre_constants\n_elementtree brain_numpy_core_umath itertools sre_parse\n_functools brain_numpy_ndarray jedi ssl\n_hashlib brain_numpy_random_mtrand json stat\n_heapq brain_numpy_utils keyword statistics\n_imp brain_pkg_resources lazy_object_proxy string\n_io brain_pytest led-strip stringprep\n_json brain_qt lib2to3 struct\n_locale brain_random linecache subprocess\n_lsprof brain_re list-modules sunau\n_lzma brain_six locale symbol\n_markupbase brain_ssl logging symtable\n_md5 brain_subprocess lzma sys\n_multibytecodec brain_threading macpath sysconfig\n_multiprocessing brain_typing mailbox syslog\n_opcode brain_uuid mailcap tabnanny\n_operator builtins marshal tarfile\n_osx_support bz2 math telnetlib\n_pickle cProfile mccabe tempfile\n_posixsubprocess calendar mimetypes termios\n_py_abc certifi mmap test\n_pydecimal cffi modulefinder textwrap\n_pyio cgi multiprocessing this\n_queue cgitb mypy thonny\n_random chunk mypy_extensions threading\n_scproxy clonevirtualenv mypyc time\n_sha1 cmath nacl timeit\n_sha256 cmd netrc tkinter\n_sha3 code nis token\n_sha512 codecs nntplib tokenize\n_signal codeop ntpath toml\n_sitebuiltins collections nturl2path trace\n_socket colorsys numbers traceback\n_sqlite3 compileall opcode tracemalloc\n_sre concurrent operator tty\n_ssl configparser optparse turtle\n_stat contextlib os turtledemo\n_string contextvars paramiko typed_ast\n_strptime copy parser types\n_struct copyreg parso typing\n_symtable crypt pathlib typing_extensions\n_sysconfigdata_m_darwin_darwin cryptography pdb unicodedata\n_testbuffer csv pickle unittest\n_testcapi ctypes pickletools urllib\n_testimportmultiple curses pip uu\n_testmultiphase dataclasses pipenv uuid\n_thread datetime pipes venv\n_threading_local dbm pkg_resources virtualenv\n_tkinter decimal pkgutil virtualenv_support\n_tracemalloc difflib platform warnings\n_uuid dir-example plistlib wave\n_warnings dis poplib weakref\n_weakref distutils posix webbrowser\n_weakrefset doctest posixpath websockets\n_xxtestfuzz docutils pprint wheel\nabc dummy_threading profile wrapt\naifc easy_install pstats wsgiref\nantigravity ecdsa pty xdrlib\nargparse email ptyprocess xml\narray encodings pwd xmlrpc\narray-test ensurepip py_compile xxlimited\nast enum pyclbr xxsubtype\nastroid errno pycparser zipapp\nasttokens espefuse pydoc zipfile\nasynchat espressif pydoc_data zipimport\nasyncio espsecure pyexpat zlib\nasyncore esptool pylint \nat faulthandler queue \natexit fcntl quopri \n
"},{"location":"getting-started/02-boards/","title":"Micropython Boards","text":"Technically, any computer that has at least 16K of RAM can run MicroPython as long as someone has ported the MicroPython runtime to use that instruction set.
"},{"location":"getting-started/02-boards/#raspberry-pi-pico","title":"Raspberry Pi Pico","text":"Most of these lessons use a low-cost ($4 retail list price) Raspberry Pi Pico(../glossary.md#pico). The microcontroller was designed by the Raspberry Pi Foundation specifically to provide a low-cost way for student to learn how to program MicroPython. The Raspberry Pi Foundation has also worked with the Thonny developers to create a simple clean kid-friendly interface that is ideal for beginning students.
"},{"location":"getting-started/02-boards/#esp32","title":"ESP32","text":"The ESP32 is similar to the Raspberry Pi Pico but ESP32 also has both WiFi and bluetooth.
"},{"location":"getting-started/02-boards/#cables","title":"Cables","text":"You will need a USB cable to program your microcontroller. These cables are frequently sold at high margin rates at retail stores. If you plan ahead, you can usually find these cables on eBay for about 50% less. Classroom purchases make this a good option.
"},{"location":"getting-started/02-boards/#getting-machine-statistics","title":"Getting Machine Statistics","text":"import machine\nhelp(machine)\n
"},{"location":"getting-started/02-breadboards/","title":"Breadboards","text":"We use standard solderless mini breadboards in our labs. The breadboards have holes that are spaced 1/10th of an inch apart which is a standard for most electronics in the US.
Our breadboards are usually 1/2 size with 400-ties. They have a central trough and power rails on the left and right edges.
"},{"location":"getting-started/02-breadboards/#breadboard-regions-and-connections","title":"Breadboard Regions and Connections","text":"Learning how a breadboard works is critical for building your projects. In the figure above you will see that there are two types of regions of the breadboard
a, b, c, d and e
are all electrically connected. Within any row, columns f, h, i, j, and k
are also electrically connected. However, there is a gap between columns e and f called the center gap or component slot that parts are usually placed over. Components like buttons and chips usually have their pins straddle the component slot.For most of our labs we place the Pico so that pin 1 of the Pico is in row 1 of the breadboard as in the image below.
This means that the GND connections to the Pico are always in rows 3, 8, 13 and 18 on both sides of the breadboard. One of the ground pins is usually hooked up to the vertical blue power rails on the sides of the breadboard.
"},{"location":"getting-started/02-breadboards/#pico-placement-annotations","title":"Pico Placement Annotations","text":"3V3_EN connects to the on-board SMPS enable pin, and is pulled high (to VSYS) via a 100K resistor. To disable the 3.3V (which also de-powers the RP2040), short this pin low. In effect by making the 3V3_EN LOW you are turning off the Pico.
"},{"location":"getting-started/02-breadboards/#breadboard-connections","title":"Breadboard Connections","text":""},{"location":"getting-started/02-esp32/","title":"ESP32 TTGO","text":"The ESP32 is a low-cost (under $10) microcontroller with both built-in WiFi and Bluetooth. This lab demonstrates using a version of the ESP32 that includes an integrated display.
"},{"location":"getting-started/02-esp32/#step-1-install-the-usb-to-uart-bridge-vcp-drivers","title":"Step 1: Install the USB to UART Bridge VCP Drivers","text":"Follow the directions here:
https://www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers
Test this by running the ``ls -l /dev/cu*``` and verify you see:
/dev/cu.SLAB_USBtoUART
If you don't see this try to reboot.
Mac: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/establish-serial-connection.html https://github.com/loboris/MicroPython_ESP32_psRAM_LoBo/raw/master/MicroPython_BUILD/firmware/MicroPython_LoBo_esp32_all.zip
"},{"location":"getting-started/02-esp32/#step-2-create-a-python-conda-environment-for-esp32","title":"Step 2: Create a Python Conda Environment for ESP32","text":"This is so we don't mess up other Python projects on your system.
conda create -n esp32 python=3\nconda activate esp32\n
"},{"location":"getting-started/02-esp32/#step-3-install-the-esptool","title":"Step #3: Install the esptool","text":"$ pip3 install esptool\nCollecting esptool\n Downloading esptool-3.0.tar.gz (149 kB)\n |\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 149 kB 2.9 MB/s \n ...\nInstalling collected packages: pycparser, six, cffi, reedsolo, pyserial, ecdsa, cryptography, bitstring, esptool\nSuccessfully installed bitstring-3.1.7 cffi-1.14.5 cryptography-3.4.6 ecdsa-0.16.1 esptool-3.0 pycparser-2.20 pyserial-3.5 reedsolo-1.5.4 six-1.15.0\n
"},{"location":"getting-started/02-esp32/#step-4-erase-the-old-firmware","title":"Step 4: Erase the Old Firmware","text":"esptool.py --port /dev/cu.SLAB_USBtoUART erase_flash\n
"},{"location":"getting-started/02-esp32/#step-5-download-the-new-firmware","title":"Step 5: Download the New Firmware","text":"Get the ESP32_All prebuilt binary:
https://github.com/loboris/MicroPython_ESP32_psRAM_LoBo/wiki/firmwares
"},{"location":"getting-started/02-esp32/#step-6-reflash-the-new-esp32-firmware","title":"Step 6: Reflash the new ESP32 Firmware","text":"cd esp32_all/\n../flash.sh -p /dev/cu.SLAB_USBtoUART\n
this will run...
$ esptool.py --port /dev/cu.SLAB_USBtoUART erase_flash\nesptool.py v3.0\nSerial port /dev/cu.SLAB_USBtoUART\nConnecting........_\nDetecting chip type... ESP32\nChip is ESP32-D0WDQ6 (revision 1)\nFeatures: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None\nCrystal is 40MHz\nMAC: 24:62:ab:ca:62:84\nUploading stub...\nRunning stub...\nStub running...\nErasing flash (this may take a while)...\nChip erase completed successfully in 2.5s\nHard resetting via RTS pin...\n
"},{"location":"getting-started/02-esp32/#configure-thonny","title":"Configure Thonny","text":"You must configure Thonny to use the ESP32.
"},{"location":"getting-started/02-esp32/#set-the-serial-port","title":"Set the Serial Port","text":"First, you must tell Thonny how to find the right port.
"},{"location":"getting-started/02-esp32/#set-the-interpreter","title":"Set the Interpreter","text":"Next, yo must tell Thonny to use the ESP32 interpreter.
"},{"location":"getting-started/02-esp32/#run-a-test","title":"Run a test","text":"import machine, display, time, math, network, utime\n\n\ntft = display.TFT()\ntft.init(tft.ST7789,bgr=False,rot=tft.LANDSCAPE, miso=17,backl_pin=4,backl_on=1, mosi=19, clk=18, cs=5, dc=16)\n\ntft.setwin(40,52,320,240)\n\nfor i in range(0,241):\n color=0xFFFFFF-tft.hsb2rgb(i/241*360, 1, 1)\n tft.line(i,0,i,135,color) \n\ntft.set_fg(0x000000) \ntft.ellipse(120,67,120,67) \ntft.line(0,0,240,135) \n\ntext=\"CoderDojo Rocks!\" \ntft.text(120-int(tft.textWidth(text)/2),67-int(tft.fontSize()[1]/2),text,0xFFFFFF)\n
You should see the following on the ESP32 display:
"},{"location":"getting-started/02-esp32/#references","title":"References","text":"https://www.instructables.com/TTGO-color-Display-With-Micropython-TTGO-T-display/
"},{"location":"getting-started/02-pi-pico/","title":"Getting Started with the Raspberry Pi RP2040 Microcontroller","text":"The Raspberry Pi RP2040 is a custom silicon microcontroller built by the Raspberry Pi Foundation. The RP2040 is used in the Raspberry Pi Pico with a retail list prices of $4. With 264K SRAM, it has around 100 times the RAM of an Arduino Uno (2K). It is ideal for projects that need more RAM such as projects that require drawing to an OLED display.
\"H\" is with headers.
"},{"location":"getting-started/02-pi-pico/#specs","title":"Specs","text":"The Raspberry Pi Pico uses a USB-micro connector. You can purchase USB Micro-B to USB-A or USB-C (Mac) cables on e-bay for under $2 or for $5 at Microcenter. - image from ebay
"},{"location":"getting-started/02-pi-pico/#pico-pinout","title":"Pico Pinout","text":"The pinout diagram for the Raspberry Pi Pico is shown below.
It features: * 26 \u00d7 multi-function GPIO pins * 2 \u00d7 SPI, 2 \u00d7 I2C, 2 \u00d7 UART, 3 \u00d7 12-bit ADC, 16 \u00d7 controllable PWM
Raspberry Pi Pico\u2019s 40 pins with pin 1 in the upper right corner with the USB connector at the top. The pin numbers are incremented as you go counterclockwise around the board. You go down the left side and then continue up on the right side until you get to pin 40 in the upper right corner.
When you program the Pico, you use the machine.Pin()
but you always use the GP* number, never the pin number on the board pin numbers.
The diagram above shows the top view where pins 1, 2 and 40 are printed next to the pins.
Next to each pin is the primary label of what the pin does. Pins 3, 8, 13, 18, 23, 28, 33 and 38 with the black background are all GND pins.
Pins are numbered 0-29, and 26-29 have ADC capabilities Pin IO modes are: Pin.IN, Pin.OUT, Pin.ALT Pin pull modes are: Pin.PULL_UP, Pin.PULL_DOWN
Label Name Description V3 3.3 volts power A source of 3.3 V power, the same voltage your Pico runs at internally, generated from the VSYS input. This power supply can be switched on and off using the 3V3_EN pin above it, which also switches your Pico off. VSYS ~2-5 volts power A pin directly connected to your Pico\u2019s internal power supply, which cannot be switched off without also switching Pico off. VBUS 5 volts power A source of 5 V power taken from your Pico\u2019s micro USB port, and used to power hardware which needs more than 3.3 V. GND 0 volts ground A ground connection, used to complete a circuit connected to a power source. Several of these pins are dotted around your Pico to make wiring easier. GPxx General-purpose input/output pin number \u2018xx The GPIO pins available for your program, labelled \u2018GP0\u2019 through to \u2018GP28\u2019. GPxx_ADCx General-purpose input/output pin number \u2018xx\u2019, with analogue input number \u2018x\u2019 A GPIO pin which ends in \u2018ADC\u2019 and a number can be used as an analogue input as well as a digital input or output \u2013 but not both at the same time. ADC_VREF Analogue-to-digital converter (ADC) voltage reference A special input pin which sets a reference voltage for any analogue inputs. AGND Analogue-to-digital converter (ADC) 0 volts ground A special ground connection for use with the ADC_VREF pin. RUN Enables or disables your Pico The RUN header is used to start and stop your Pico from another microcontroller."},{"location":"getting-started/02-pi-pico/#steps-to-get-micropython-running-on-the-mac","title":"Steps To Get Micropython Running on the Mac","text":"Thonny is a free lightweight Python development tool.
Downloading 465408 bytes from https://github.com/raspberrypi/micropython/releases/download/pico-20210120/pico_micropython_20210121.uf2\nWriting to /Volumes/RPI-RP2/firmware\n100%\nWaiting for the port...\nFound 2e8a:0005 at /dev/cu.usbmodem0000000000001\n\nDone!\n
"},{"location":"getting-started/02-pi-pico/#getting-the-bootloader-running-from-the-thonny-python-shell","title":"Getting The Bootloader Running from the Thonny Python Shell","text":"Although you can hold down the BOOTSEL button as you are plugging in the Pico, there is a much easier way. Just type the following into the Thonny shell:
machine.bootloader()\n
This will make the Pico go into the Bootloader Mode and mount the file system. You can then copy the bootloader file using the drag-and-drop from your file system or use a UNIX copy command. Once the copy is finished the Pico will automaticaly restart using the new uf2 file.
"},{"location":"getting-started/02-pi-pico/#using-the-onboard-led","title":"Using the Onboard LED","text":"from machine import Pin\nimport utime\nled_onboard = machine.Pin(25, machine.Pin.OUT)\nled_onboard.value(1)\n
from machine import Pin\nimport utime\n\n# right uppermost pin with USB on the left\nled = Pin(16, Pin.OUT)\nled.low()\nwhile True:\n led.toggle()\n utime.sleep(1)\n
Press the Play Button"},{"location":"getting-started/02-pi-pico/#adding-gnd-markers","title":"Adding GND Markers","text":"One of the key disadvantages
"},{"location":"getting-started/02-pi-pico/#references","title":"References","text":""},{"location":"getting-started/02-pi-pico/#getting-started-guide","title":"Getting Started Guide","text":"Raspberry Pi Getting Started
"},{"location":"getting-started/02-pi-pico/#micropython-rp2040-reference","title":"MicroPython RP2040 Reference","text":"MicroPython RP2040 Quick Reference - this web page has details on how MicroPython was ported to the RP2040 Microcontroller.
"},{"location":"getting-started/02-pi-pico/#book-pdf","title":"Book PDF","text":"Raspberry Pi Book PDF Download from HackSpace Commons Attribution-NonCommercial-ShareAlike 3.0 Unported (CC BY-NC-SA 3.0)
"},{"location":"getting-started/02c-thonny/","title":"Thonny Python IDE","text":"A lightweight Python integrated development environment (IDE) that is ideal for beginners writing simple Python programs for first-time users. It has been modified to work well with the Raspberry Pi Pico. It supports different ways of stepping through the code, step-by-step expression evaluation, detailed visualization of the call stack and a mode for explaining the concepts of references and heap.
We strongly suggest that classes begin with Thonny for the first several weeks. As students want to do more complex functions such as build automatic deployment scripts other IDEs are more appropriate.
Thonny 3.3.3 (2021-01-21) was the first version to support the Raspberry Pi Pico. There have also been several enhancements since that release. For a release history, see the Thonny Release History. We suggest checking this link monthly for updates.
"},{"location":"getting-started/02c-thonny/#installing-thonny","title":"Installing Thonny","text":"The best way to install Thonny is to go to the Thonny website an look for the \"Download\" area for your operating system. That link is here:
https://thonny.org/
Make sure you upgrade to the latest version of Thonny if you already have a Thonny installed on your computer.
You can find more tips on getting started with Thonny on the Raspberry Pi website:
https://projects.raspberrypi.org/en/projects/getting-started-with-the-pico/2
Thonny runs on Mac, Windows and Linux.
"},{"location":"getting-started/02c-thonny/#upgrading-thonny","title":"Upgrading Thonny","text":"Although you can always upgrade Thonny by removing it and reinstalling a new version, on Mac and Linux systems there is an easier method.
Run the following shell command:
sudo yum upgrade thonny\n
or
sudo apt-get upgrade thonny\n
"},{"location":"getting-started/02c-thonny/#running-help","title":"Running help()","text":"You can enter the help() function in the main script area and then press the Play button. This will tell you
MicroPython v1.14 on 2021-02-02; Raspberry Pi Pico with RP2040\nType \"help()\" for more information.\n>>> %Run -c $EDITOR_CONTENT\nWelcome to MicroPython!\n\nFor online help please visit https://micropython.org/help/.\n\nFor access to the hardware use the 'machine' module. RP2 specific commands\nare in the 'rp2' module.\n\nQuick overview of some objects:\n machine.Pin(pin) -- get a pin, eg machine.Pin(0)\n machine.Pin(pin, m, [p]) -- get a pin and configure it for IO mode m, pull mode p\n methods: init(..), value([v]), high(), low(), irq(handler)\n machine.ADC(pin) -- make an analog object from a pin\n methods: read_u16()\n machine.PWM(pin) -- make a PWM object from a pin\n methods: deinit(), freq([f]), duty_u16([d]), duty_ns([d])\n machine.I2C(id) -- create an I2C object (id=0,1)\n methods: readfrom(addr, buf, stop=True), writeto(addr, buf, stop=True)\n readfrom_mem(addr, memaddr, arg), writeto_mem(addr, memaddr, arg)\n machine.SPI(id, baudrate=1000000) -- create an SPI object (id=0,1)\n methods: read(nbytes, write=0x00), write(buf), write_readinto(wr_buf, rd_buf)\n machine.Timer(freq, callback) -- create a software timer object\n eg: machine.Timer(freq=1, callback=lambda t:print(t))\n\nPins are numbered 0-29, and 26-29 have ADC capabilities\nPin IO modes are: Pin.IN, Pin.OUT, Pin.ALT\nPin pull modes are: Pin.PULL_UP, Pin.PULL_DOWN\n\nUseful control commands:\n CTRL-C -- interrupt a running program\n CTRL-D -- on a blank line, do a soft reset of the board\n CTRL-E -- on a blank line, enter paste mode\n\nFor further help on a specific object, type help(obj)\nFor a list of available modules, type help('modules')\n>>>\n
"},{"location":"getting-started/02c-thonny/#save-options","title":"Save Options","text":"You can save a python file in Thonny to either the Pico or to your local computer's file system.
first stop execution of any program you are running.
"},{"location":"getting-started/02c-thonny/#downloading-the-firmware","title":"Downloading the Firmware","text":"After you start up Thonny there will be a button in the lower right corner.
After you click on it you will see the following:
Downloading 465408 bytes from https://github.com/raspberrypi/micropython/releases/download/pico-20210120/pico_micropython_20210121.uf2\nWriting to /Volumes/RPI-RP2/firmware\n100%\nWaiting for the port...\nFound 2e8a:0005 at /dev/cu.usbmodem0000000000001\n\nDone!\n
"},{"location":"getting-started/02c-thonny/#version","title":"Version","text":"After you press play the following will appear in the console.
MicroPython v1.13-290-g556ae7914 on 2021-01-21; Raspberry Pi Pico with RP2040\nType \"help()\" for more information.\n>>> %Run -c $EDITOR_CONTENT\n
"},{"location":"getting-started/02c-thonny/#plotting-values-on-thonny","title":"Plotting Values on Thonny","text":"If you are reading sensor values and want to see a nice plot of the values, you can use Thonny's Plot function to view the values. Simply add numeric print values to your main loop and they will be displayed in the plot window. This is very useful for any analog to digital conversions and can be used as a simple tool to view anomalies in incoming data. For example if you accidentally hook up a potentiometer's positive rail to 3.3OUT instead of the 3.3REF you will see noise in the incoming data caused by spikes on the power rails.
"},{"location":"getting-started/02c-thonny/#background-on-thonny","title":"Background on Thonny","text":"MicroPython was originally developed by Damien George and first released in 2014. However, MicroPython did not have a development environment that was easy for students to use. Thonny was developed to provide an easy to use tool just for MicroPython development. Thonny was created at the University of Tartu Institute of Computer Science in Estonia for this purpose. They continue to support Thonny.
Several feature for Thonny were sponsored by the Raspberry Pi Foundation and we continue to see a close relationship between the Raspberry Pi Foundation and the Thonny development team.
Although the Thonny IDE is a great way for kids to start programming in Python on the Raspberry Pi Pico, it has limited advanced features and no large library of extensions.
For intermediate to advanced Python developers, the Visual Studio Code IDE is a good options when it is used with an appropriate extension such as the Pico Go extension by Chris Wood.
Note that Visual Studio Code is sometimes just called VS Code.
Code extensions provide code auto-completion and allows you to communicate with your Raspberry Pi Pico board using the built-in REPL console. You can a single file on your board, sync your entire project or directly type and execute commands. Because the files are stored on your local computer, it makes it easier to use version control software to allow you to work in teams with remote developers.
"},{"location":"getting-started/02d-vscode/#installing-visual-studio-code","title":"Installing Visual Studio Code","text":"Visual Studio Code runs on Windows, Mac and Linux systems such as the operating systems that run on the Raspberry Pi 3 or 4. You typically need around 2GB of RAM to run VS-Code.
See the VS Code Requirements.
If you have a Raspberry Pi the installation instructions are here.
"},{"location":"getting-started/02d-vscode/#adding-the-pico-go-extension","title":"Adding the Pico Go Extension","text":"After you have installed VS-Code you must download the Pico Go Extension:
Pico Go Quick Start
"},{"location":"getting-started/02d-vscode/#references","title":"References","text":"One of the key values of CoderDojo clubs around the world is to not charge any fees for attending these clubs. Parts need to be purchase by limited funds raised through donations. Club organizers need to be frugal about getting low-cost parts for the participants.
Our experience is that is difficult to get the right parts at low cost using firms that promise quick delivery times of a few days. The lowest price parts often must be purchased weeks in advance from places like China and Taiwan. As a result, clubs that use funds carefully must plan weeks if not months in advance of classes and events. So we strongly suggest bringing an robot part procurement team together two months before you begin to offer robot courses where students can each have their own robots.
"},{"location":"getting-started/03-suggested-parts/#purchasing-the-raspberry-pi-kits","title":"Purchasing The Raspberry Pi Kits","text":"As of June 2021, the Raspberry Pi Pico did not come integrated into low-cost development kits that include robot kit parts. You frequently must to purchase the independent parts yourself.
Here are some of the parts we recommend.
"},{"location":"getting-started/03-suggested-parts/#the-raspberry-pi-pico","title":"The Raspberry Pi Pico","text":"In the US, our best source of these has been Microcenter stores. They sell them for $3.99 and they often have them on sale for $1.99.
Microcenter Pico Part Listing
Microcenter has around 25 Locations in the US.
"},{"location":"getting-started/03-suggested-parts/#usb-cable","title":"USB Cable","text":"For a Mac with USB-C connectors, you will need to get a USB micro to C cable:
We need male breakaway headers with standard 1/10th inch spacing. The Raspberry Pi Pico will need 20 pins on each side. If you get 40-pin versions they can easily be broken in half.
EBay: Ebay 40-Pin Male Header 0.1\" 2.54mm Breadboard PCB Strip Connectors 5pc - these ship from China in a few weeks and you can get five of them for $1 (including shipping).
Amazon: Amazon MCIGICM 10pcs Male Header Pin, 40 Pin Header Strip (2.54 mm) for Arduino Connector
We like the 1/2 size boards that have 400 connection points. We like to mount them with the USB connector at the top with the numbers running down the left side to align with the pin numbers.
EBay: Solderless Breadboard 400 Point Tie Prototype Test Bread Board PCB For Arduino
Amazon: Amazon 4 Pieces for $6
Use 22 gauge wire. Get a large spool of black and red and smaller spools of other colors. We use
"},{"location":"getting-started/03-suggested-parts/#breakable-40-pin-headers","title":"Breakable 40-Pin Headers","text":"If you purchase the Raspberry Pi Pico kit that does not have the header pins soldered in, you can use the following to add your own header pins in so the Pico's can be used with the header pins.
40 pin Breakable Pin Header 2.54mm Single Row Male Header Connector Kit on eBay. I purchased 12 of these for $5 or $4.24 per pack of 12 when I purchase 4 packs.
"},{"location":"getting-started/03-suggested-parts/#male-to-male-breadboard-jumper-wires","title":"Male-to-Male Breadboard Jumper Wires","text":"You can use 22 gauge wire to build your projects, however kids that don't have fine motor skills sometimes get frustrated with raw wires. You can purchase a Jumper Wire Kit with 65 jumpers on EBay for under $2 that has nice plastic ends that are easier to use.
Ebay: 65pcs Jumper Wire cable kit for Solderless Breadboard New - 65 of various lengths for $1.50
Ebay: 65X 65PCS Jumper Wire Cable Kit For Solderless Breadboard
Amazon: ZYAMY 195pcs Breadboard Jumper Wire 12cm 16cm 20cm 24cm Solderless Flexible Breadboard Connecting Line Cables Male to Male Breadboard Jumpers (65pcs/Set x 3) - three packs of 65 for about $8
A B3F Momentary Press Button with a blue cap.
Note the schematic in the lower right corner that shows the internal connections of the button.
We like the B3F-4055 12 x12x7.3mm Tactile Switch Momentary Press Buttons that have small dimples on the bottom that fit into the breadboard center trough. They can be purchased for under 10 cents per unit on eBay. You can by them with our without colored caps. The links below have example listings on eBay.
Although not all of these items are really \"sensors\" (some are displays), these kits provide high value at a low price-per item cost. Note that some of these kits contain tilt switches that contain Mercery. I remove these from the kits.
We use linear 10K potentiometers in many labs and kits. The pre-wired options are very handy but you will need some male-to-male jumpers.
We like these small momentary press buttons because they are easy to mount directly on the breadboard. They provide ideal \"Mode Programming\" buttons to put the microcontroller into a programming mode.
"},{"location":"getting-started/03-suggested-parts/#switches","title":"Switches","text":"These are ideal for turning your project boxes on and off.
"},{"location":"getting-started/03-suggested-parts/#small-096-oled-displays","title":"Small 0.96\" OLED displays","text":"We love these low-cost 128X64 OLED displays. They are bright and draw very little power. There are two different connectors: 4-wire I2C and 7-wire SPI. I would suggest the simpler I2C for most starter projects.
For our robot projects our students like to view the values from a distance. For them we use these $17 OLED displays that are about twice the size.
2.42\" OLED Display wired with SPI
"},{"location":"getting-started/03-suggested-parts/#non-rechargeable-aa-and-aaa-battery-packs","title":"Non-rechargeable AA and AAA Battery Packs","text":""},{"location":"getting-started/03-suggested-parts/#rechargeable-battery-packs","title":"Rechargeable Battery Packs","text":"If you are work on project that need long-lasting portable power such as LED strip costumes, there are a wide variety of long-lasting rechargeable battery packs available from prices around $9 to $15. My favorites are ones that have percentage of power remaining displayed.
"},{"location":"getting-started/03-suggested-parts/#ultrasonic-distance-sensors","title":"Ultrasonic Distance Sensors","text":"These inexpensive \"ping\" sensors are used in many robot projects.
We like two motor three wheel robots in our classrooms. They need a H-Bridge circuit for controlling the motor direction. The popular L293D chip takes four PWM signals and will use these to drive two 3-12v DC motors. The L293D chip can be mounted directly on your breadboard. However, we like the low-cost Mini motor controller boards that are only $2 that also have handy screw headers for easily attaching and removing the motor and power wires.
Here are the specs: Here
600mA output per channel (per motor)
See section 4.4 in the Raspberry Pi Pico Datasheet on the Powerchain - note that the input VSYS, the main system input voltage, is only designed to go up to up to 5.5 volts. VSYS feeds the RVT6150 buck-boost SMPS.
Mini Motor Drive Shield Expansion Board L293D Module For Arduino UNO MEGA2560 R3 $3
Note that the L293D Mini Motor Drive shield also has a voltage regulator that delivers a constant 5 volt signal to the robot microcontroller.
"},{"location":"getting-started/04-power/","title":"Powering Your MicroPython Projects","text":"If you are just using MicroPython to learn how to code you can use the USB connector from your Mac or PC to power your project. However, if you are creating a device that can't be connected to the USB cable such as a robot, then you will need to hook up portable power supply like a battery pack to power your device.
The good news is that most microcontrollers like the Raspberry Pi Pico or ESP32 have many options and they are easy to use. Just be careful about applying too much power since unlike the old 5V Arduino boards, these devices only use 3.3v power supplies. Connecting the Pico to a 4 AA batteries (4 X 1.5 volts = 6 volt total) can damage the Pico microcontroller.
"},{"location":"getting-started/04-power/#power-connectors","title":"Power Connectors","text":"The Raspberry Pi Pico has three important power connectors you should learn about.
There is large and growing market for rechargeable cell-phone power packs that are ideal for applications such as robotics and powering a remote microcontroller for a long time. They can be purchased in many power storage levels from 2500 milliamp hours up to over 1 million milliamp hours.
"},{"location":"getting-started/04-power/#preventing-usb-power-pack-autoshutdown","title":"Preventing USB Power Pack Autoshutdown","text":"The one issue to be aware of with battery packs is that they automatically power down if they don't sense a minimum current being drawn such as about 10 milliamps. In many applications the Pico draws less than that amount. One fix is to simply add LED power indicator that draws 10 milliamps. This will
"},{"location":"getting-started/04-power/#battery-power","title":"Battery Power","text":"3 AA alkaline batteries wired in series provide plenty of power for small Pico-based MicroPython projects. Each battery is 1.5 volts which give a total of 4.5 volts which is well within the maximum power use by the VSYS input on the Pico.
As an alternative, you can also use 4 rechargeable NiCad batteries that have a nominal rating of 1.2 volts each. This is a total of 4.8 volts, which is still under the 5.5 volt limit.
Warning
Do not connect 4 AA batteries directly to VSYS. 6 volts is too high for the Pico's power system and could damage it. Use a voltage regulator such as is found on motor driver boards. Another alternative is to use a DC-to-DC voltage regulator such as a Buck Converter.
"},{"location":"getting-started/04-power/#monitoring-usb-power","title":"Monitoring USB Power","text":"On the Pico, GP24 can be used to indicate if power is being drawn from the USB cable. You can also use this information to change the behavior such as drop into low-power mode when disconnected from a USB source.
See here
Here is some sample MicroPython code that displays this value:
import machine\nimport utime\n\nled_onboard = machine.Pin(25, machine.Pin.OUT)\nUSBpower = machine.Pin(24, machine.Pin.IN) \n\nwhile True:\n led_onboard.value(1)\n utime.sleep(0.5)\n led_onboard.value(0)\n utime.sleep(0.5)\n if USBpower() != 1:\n utime.sleep(1)\n
This program prints out the value of the USB Power indicator.
import machine\nimport utime\n\nled_onboard = machine.Pin(25, machine.Pin.OUT)\nUSBpower = machine.Pin(24, machine.Pin.IN) \n\nif USBpower() = 1:\n print('drawing power from the USB')\nelse\n print('drawing power from VSYS - a battery or external power source')\n
Power consumption when running this code is approximately 0.1W (19mA at 4.99V, so 4 x AA batteries (@ 2,000mAh each) would keep the Pico running for well over 4 days.
"},{"location":"getting-started/04-power/#running-both-usb-and-external-battery-power-on-the-raspberry-pi-pico","title":"Running both USB and External Battery Power on the Raspberry Pi Pico","text":"The battery should provide a voltage greater than 1.8v and less than 5.5v. Importantly if both a battery and a micro USB cable are connected at the same time a Schottky diode should be placed between the battery positive and VSYS [see section 4.4 & 4.5 of the Raspberry Pi Pico Datasheet. As long as the battery voltage is less than that coming in from the USB cable, power will be drawn from the USB supply and not the battery and, when you unplug the Pico from its USB supply, the Pico will keep on running, using power from the battery (and visa versa when you plug it back in).
"},{"location":"getting-started/04-power/#monitoring-batter-power-level-on-the-raspberry-pi-pico","title":"Monitoring Batter Power Level on the Raspberry Pi Pico","text":"You can use one of the three analog to digital converters to allow the Pico to monitor the power remaining in an external battery. For example if you have 3 AA batteries you can connect two 100K ohm resistors in series and connect the top and bottom to the power and ground. Then connect the midpoint to one of the three ADC inputs. This will give you a way to monitor the power remaining in an external battery. A fully charge battery pack voltage such at 4.5 volts will generate a voltage of 1/2 the maximum level. As the voltage drops to 1.8 volts it should display a value of 0%. An OLED can provide an ideal way to display the power level remaining.
"},{"location":"getting-started/06-yd-2040/","title":"The YD-RP2040","text":"The VCC-GND YD-RP2040 is a microcontroller that sells for $5-$10 on e-Bay. It is rumored to come with either 4MB or 16MB flash, but the units I have received only have 2MB.
"},{"location":"getting-started/06-yd-2040/#additions","title":"Additions","text":"The board has a W25Q32 (32M-bit) Serial Flash chip.
From the W25Q32 Datasheet:
It provides a storage solution for systems with limited space, pins and power. The 25Q series offers flexibility and performance well beyond ordinary Serial Flash devices. They are ideal for code shadowing to RAM, executing code directly from Dual/Quad SPI (XIP) and storing voice, text and data. The devices operate on a single 2.7V to 3.6V power supply with current consumption as low as 5mA active and 1\u00b5A for power-down. All devices are offered in space-saving packages.
"},{"location":"getting-started/06-yd-2040/#demo-program","title":"Demo Program","text":"This program shows the blue LED flashing and the NeoPixel cycling through colors. You can press the USR button to change the cycle speed.
"},{"location":"getting-started/06-yd-2040/#references","title":"References","text":"In the past, the memory available in an standard Arduino Uno (2K bytes) was too small to add high quality displays. With the arrival of the ESP32 and the Raspberry Pi Pico this has all changed. These microcontrollers have around 100 times that RAM - typically around 200K bytes. So we are integrating low-cost OLED displays into many of our CoderDojo projects!
"},{"location":"getting-started/10-displays/#display-types","title":"Display Types","text":"There are four main types of display technology that use for small microcontrollers.
240X240 TFT Display
Full Color LCD TFT Display SPI HD 65K Module ST7735
"},{"location":"getting-started/10-displays/#concepts","title":"Concepts","text":"Before you begin to use these displays, there are a few things to understand to use them effectively. Based on your project needs, you can use this knowledge to find the right solution for you.
"},{"location":"getting-started/10-displays/#framebuffers","title":"Framebuffers","text":"A framebuffer is a copy of the display information that is resident within the RAM of the microcontroller. It must be as large as the display. For a 128X64 monochrome display this would be 128 * 64 = 8192 bits or 1,024 bytes (1K). A full color 240X240 TFT which uses 8 bits for red, green and blue would require 3 X 8 X 240 X 240 = 1,382,400 bits or 172K bytes.
Not all all displays need framebuffers. Some displays can take a series of vector drawing commands such as \"draw line\" and \"draw text\". These displays can be useful if you don't have a large amount of RAM.
"},{"location":"getting-started/10-displays/#display-chip-types","title":"Display Chip Types","text":"There are two common versions:
You can usually look on the back of the display device and see what type of check controls your OLED display.
"},{"location":"getting-started/10-displays/#communication-protocols","title":"Communication Protocols","text":"In addition to the multiple types of displays and types of chips driving the displays, there are also two options on how you want to communicate between your microcontroller and the display.
For our beginning labs we will just do some basic drawing. We will start out with just four functions:
Let's assume that we have a four wire OLED that uses the popular SSD1306 chip with 128X64 pixels. We call our oled \"oled\" using the following line:
from ssd1306 import SSD1306_I2C\noled = SSD1306_I2C(128, 64, i2c)\n
Function Description Parameters oled.fill(0) Fill the display with white or black 0=black and 1=white oled.text(\"Hello\", Draw text String, x (horizontal from left edge) and y (vertical from the top)Example: Draw \"Hello World\" 40 over and 10 down. oled.text(\"Hello World!\", 40, 10) show Show the display Send the current frame buffer to the display. You must do this after you make and changes to the Framebuffer. The full program would look like this:
from ssd1306 import SSD1306_I2C\noled = SSD1306_I2C(128, 64, i2c)\noled.fill(0)\noled.text(\"Hello World!\", 0, 0)\noled.show()\n
This would display the following:
"},{"location":"getting-started/10-displays/#full-list-of-drawing-functions","title":"Full list of Drawing Functions","text":"Every drawing library might have slightly different functions. But we can quickly see the functions that we want by using the dir() function on the SSD1306_I2C class.
from ssd1306 import SSD1306_I2C\nprint(dir(SSD1306_I2C))\n
This returns the following list: ['__class__', '__init__', '__module__', '__name__', '__qualname__',\n'__bases__', '__dict__', 'blit', 'fill', 'fill_rect', 'hline',\n'invert', 'line', 'pixel', 'rect', 'scroll', 'text', 'vline',\n'init_display', 'write_cmd', 'show', 'poweroff', 'poweron',\n'contrast', 'write_data']\n
Technically, these are called methods of the SSD1306_I2C class. The ones that begin and end with double underscores are class methods for creating new object instances. The rest of the items on the list are the drawing functions. The following are relevant for the SSD1306_I2C display.
The display has (0,0) in the upper left corner. X is horizontal (width) and Y is vertical (height). The state is 0=off (black) and 1=on (white).
Function Description Example blit fill(state) Fill Fill with black (0) or white(1) fill_rect Fill a rectangle hline(x1, x2, y, state) Draw a horizontal line Draw a horizontal line at the top of the display: oled.hline(0, 0, 127, 1) invert invert the display line(x1,y1,x2,y2) draw a line at any angle Horizontal oled.line(0,0, 127, 63, 1) pixel Draw a single point on the screen rect Draw an empty rectangle scroll Scroll the display text Write text at a point vline Draw a Vertical Line oled.vline(width - 1, 0, height - 1, 1) # right edge init_display Initialize the display write_cmd Write a command to the display show Update the display from the frame buffer poweroff poweron contrast write_data"},{"location":"getting-started/10-displays/#interfaces","title":"Interfaces","text":""},{"location":"getting-started/10-displays/#i2c","title":"I2C","text":"Pros: Simple four wire interface
Pin Purpose Description"},{"location":"getting-started/10-displays/#spi","title":"SPI","text":"Example: 128X64 pixel monochrome displays
"},{"location":"getting-started/10-displays/#types-of-displays","title":"Types of Displays","text":""},{"location":"getting-started/10-displays/#summary-table","title":"Summary Table","text":"Display Type Cost Links Notes"},{"location":"getting-started/10-displays/#lcd","title":"LCD","text":""},{"location":"getting-started/10-displays/#oled","title":"OLED","text":""},{"location":"getting-started/10-displays/#tft-displays","title":"TFT Displays","text":""},{"location":"getting-started/10-displays/#references","title":"References","text":"ST7735 Micropython Driver by Anthony Norman
"},{"location":"intro/01-about/","title":"About MicroPython for Kids","text":"The this site provides a rich collection of resources to teach computational thinking to students from 10 to 16 years old using fun Python programs that control the physical world around us.
The sub-field of computer science that reads sensors and controls lights and motors is called Physical Computing. We cover physical computing in our next section.
"},{"location":"intro/01-about/#content-licenses","title":"Content Licenses","text":"All the content on this website is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike. This means if your preserve the attribution and license you can use the content for free in your classrooms and modify and extend the curriculum to meet your needs. However, you can not charge your students additional feeds for the content or resell the content.
"},{"location":"intro/01-about/#contributing-to-this-website","title":"Contributing to This Website","text":"We invite all students, teachers and mentors to help us build a better website. You can read our publishing process on the CoderDojo Twin Cities Content Authoring Guide.
Please make sure you using original content and avoid using any images that you have not created yourself. It is always a good idea to have a friend check your spelling, typos and links.
There are several ways to contribute to get your content on this website.
"},{"location":"intro/01-about/#git-pull-requests","title":"Git Pull Requests","text":"If you know how, you can student submit a Git Pull Request. This tells our team that you have content to contribute. Don't be scared about learning how to do this. There are lots of examples online and please reach out if you are having trouble. This is our preferred approach, but we realize that the first time you do this there are several things you need to know.
"},{"location":"intro/01-about/#adding-a-new-issues","title":"Adding a New Issues","text":"You can just open a new [Issue](https://github.com/CoderDojoTC/micropython/issues and put your content in Markdown. You will also need to tell us where you store any images and videos. It might take us some time get this content into a new release.
"},{"location":"intro/01-about/#let-us-setup-a-edit-page","title":"Let Us Setup a Edit Page","text":"If learning how to do pull requests is too daunting for you, don't worry! You are not alone. If you want us to setup a web page you can edit using the simple \"Edit\" button on GitHub we can do that for you. Just tell us where you want the page located and give us your GitHub ID and we will set this up. You will need to let us know when your content is ready to be merged in to our releases.
"},{"location":"intro/01-about/#manual-methods","title":"Manual Methods","text":"What if you have an urgent class coming up and don't have time to learn Markdown? If this happens, you can send us your raw content in MS-Word, PowerPoint or a Google Doc. Since we are an all-volunteer organization, we will need time to find a volunteer to convert your content into Markdown. All urgent requests should go to:
info@codesavvy.org
"},{"location":"intro/02-physical-computing/","title":"What is Physical Computing?","text":"Physical Computing is the process of using computers to read data from sensors about the world around us and then taking actions on this incoming data stream. These actions are typically doing things like blinking and LED, moving a motor or updating a display.
"},{"location":"intro/02-physical-computing/#physical-computing-in-teaching-computational-thinking","title":"Physical Computing in Teaching Computational Thinking","text":"Physical computing plays an important role in teaching the core concepts in Computational Thinking. Often times students quickly lose interest when only abstract concepts are used. Physical computing allows direct hands-on experiences that keeps students engaged and gives them immediate feedback. Although our labs start slowly with simply blinking LEDs, they quick move to controlling motors and building robots.
The material in this website is designed to leverage everything we have learned to make our lessons be fun, engaging and allow students to come up with their own ideas for project-based learning.
"},{"location":"intro/02-physical-computing/#why-physical-computing-has-become-so-popular","title":"Why Physical Computing Has Become So Popular","text":"In the past, the chips we used to teach physical computing (called microcontrollers) were slow, had huge memory limitations, and were expensive. They were also hard to program and since we could not use the Python language that is popular in most classrooms today.
This all changed in January of 2021 when the Raspberry Pi Foundation released a $4 microcontroller called the Pico that has 200 times the memory of the most popular microcontroller (The $25 Arduino Uno). Now teachers could purchase an entire classroom full of microcontrollers that were powerful enough to even do machine learning.
One way to measure the cost effectiveness of this system is to compare the cost per kilobyte of RAM. The Arduino Uno was about $12.50 per kilobyte. The new Raspberry Pi Pico, with 264K RAM cost only 2 cents per kilobyte!
There was also one other HUGE advantage of these microcontrollers. They ran Python! Python is the most popular language for students today. There are millions of on-line websites that show kids how to learn Python like our own CoderDojo Beginning Python classes. Teachers now had the big three factors:
But there was a small problem. Although there were millions of sample programs for the old under-powered Arduino microcontrollers, we needed high-quality lessons for our students to learn to use the new microcontrollers. And these lessons need to be flexible so teachers around the world could build new courses out of this content without having to purchase expensive textbooks. That is why this website is so important. We hope you see how excited we are to bring you this new content!
"},{"location":"intro/02-physical-computing/#examples-of-sensors","title":"Examples of Sensors","text":"Here are some sample sensors that we use:
Here are some of the Actuators we use:
In the real world, sensor data can be complex. We often need to look for complex patterns in an incoming data stream. For example, how can we detect complex motion such as gestures from a simple distance measurement? Other questions that we have to consider include:
Left to right: Raspberry Pi Pico, Cytron Maker Pi Nano, Cytron Maker Pi 2040, Cytron Maker Pi Pico, ESP-32, ESP TTGO
This lesson is an overview of microcontrollers and their role in teaching physical computing.
A microcontroller is a small low-cost computer used to control physical devices such as LED, servos and motors. Microcontroller boards typically cost around $4 to $6 and are an ideal way to learn about computer science because you can use them to build fun projects. For example you can control a row of LEDs, move a robot and sense the world around us with a variety of light, sound and motion sensors.
"},{"location":"intro/03-microcontrollers/#types-of-microcontroller-boards-used-in-these-labs","title":"Types of Microcontroller Boards Used in These Labs","text":"We use a variety of microcontroller boards that are based on either the Raspberry Pi RP2040 chip or the ESP32. The boards we use are all low-cost (under $10) but have a variety of on-board devices such as buttons, LEDs, speakers and motor controllers. Which board you use depends on how many of these components you need in your projects. The RP2040 has 264K RAM and the ESP32 has 520K RAM.
Some of the boards are mounted on breadboards and others use Grove connectors to connect sensors and actuators.
"},{"location":"intro/03-microcontrollers/#raspberry-pi-pico","title":"Raspberry Pi Pico","text":"This microcontroller is low cost (retail list price is $4) and can be mounted directly on a breadboard. The Pico has just a single LED and no buttons.
In the past, microcontrollers were difficult for younger students to program. They were also too expensive for every student to purchase and take home. A typical Arduino kit could easily cost over $20 and required you to learn C to program it.
Today, microcontrollers such as the Raspberry Pi Pico and the ESP32 cost as little as four dollars. And these devices are designed to be programmed in Python, the most popular programming language for students.
"},{"location":"intro/03-microcontrollers/#what-is-physical-computing","title":"What is Physical Computing?","text":"Physical Computing is a field of study that can sense and respond to the world around us. Unlike programming a cell phone or a laptop computer, our focus is reading sensor values and quickly responding to changes. Physical Computing is widely used to teach principals of computer science because students can create their own projects and express creativity such a controlling the patterns of lights or creating complex sounds.
"},{"location":"intro/03-microcontrollers/#how-microcontrollers-are-used","title":"How Microcontrollers are Used","text":"Microcontrollers do three things:
Here is a general diagram to think about when you are designing microcontroller systems:
Here is a specific example instance of what inputs and outputs might do.
"},{"location":"intro/03-microcontrollers/#programming-a-microcontroller","title":"Programming a Microcontroller","text":"In this class, we will use the MicroPython to program our Microcontroller.
Our programs will usually have the following structure:
The following is an example code in Micropython:
# Import Section\nimport machine\nimport time\n\n# Setup Section\nled = machine.Pin(16, machine.Pin.OUT)\n\n# Main Loop\nwhile True:\n led.high()\n time.sleep(0.5)\n led.low()\n time.sleep(0.5)\n
Almost all our programs will start with the import machine
line. This tells the system that we need to gather all the libraries that understand our physical machine.
If you couldn't understand the example program - don't worry! We will be going through it in detail as we work on our labs.
"},{"location":"intro/03-microcontrollers/#references","title":"References","text":"This section review several kits for learning MicroPython. Most of them use the RP2040 chip, but there are some that also use the ESP32 when wireless communication is needed.
"},{"location":"kits/01-intro/#solderless-connectors","title":"Solderless Connectors","text":"There are several types of solderless connectors used in these kits. They connect sensors and motors to these kits without the need for soldering. They are ideal for student labs that don't want the fire-hazards associated with soldering or where solderless breadboards and hot-glue is not flexible enough.
These are usually 3 and 4-wire connectors that support analog and digital input and output as well as I2C bus and UART communications. They are typically designed to carry about 1 amp of current.
The following list is not design to be an exhaustive list of all MicroPython development kits available on the market. We focus on value-based kits that will help our students have fun learning computational thinking.
You can also use the MicroPython \"tag\" to search all the kits on the SparkFun site:
https://www.sparkfun.com/categories/tags/micropython
"},{"location":"kits/01-intro/#references","title":"References","text":"Here are kits that we have seen but have not yet evaluated:
"},{"location":"kits/01-intro/#waveshare-picogo-robot","title":"Waveshare PicoGo Robot","text":"PicoGo Mobile Robot is a $43 robot based on Raspberry Pi Pico.
Vilros Getting Started With MicroPython on Raspberry Pi Pico Kit
This kit includes:
The only problem with the parts is the lack of connectors for the potentiometers don't work well directly on the breadboard. You will need to solder wires to use them on the breadboard.
"},{"location":"kits/microbit/","title":"MicroBit","text":"Although the BBC MicroBit is a good educational product for learning MicroPython, we don't use it in our MicroPython courses for the following reason:
The Larson Scanner is a light pattern special effect named after Glen A. Larson. Larson used this pattern to give his Battlestar Galactica Cylon and KITT robot eyes a sense of sentience. See Knight Rider for the backstory.
This project uses a 144 pixel/meter LED strip and a Raspberry Pi Pico to produce this effect.
I used a craft pumpkin from Michaels. I cut a slit in it and used hot-glue to hold the LED strip in place.
"},{"location":"kits/larson-scanner/01-intro/#parts-list","title":"Parts List","text":"This is a screen image from e-bay showing a 1/2 meter of LED strip for $8.
"},{"location":"kits/larson-scanner/01-intro/#sample-code","title":"Sample Code","text":"This code shows a five-pixel wide \"eye\" moving back-an-forth over a 27 pixel strip. There is a central bright red LED surrounded by dimmer red LEDs that move back-and-forth. We are using the NeoPixel library supplied by Bla\u017e Rolih.
The example below has a delay of 1/10th of a second between drawing events. You can make the delay smaller to speed up the speed of the eye movement.
from utime import sleep\n# We are using https://github.com/blaz-r/pi_pico_neopixel\nfrom neopixel import Neopixel\n\nNUMBER_PIXELS = 27\nSTATE_MACHINE = 0\nLED_PIN = 0\n\nstrip = Neopixel(NUMBER_PIXELS, STATE_MACHINE, LED_PIN, \"GRB\")\n\n# Color RGB values\nred = (255, 0, 0)\nred_med = (32, 0, 0)\nred_light = (8, 0, 0)\noff = (0,0,0)\n\ndelay = .1\nwhile True:\n for i in range(2, NUMBER_PIXELS-2):\n strip.set_pixel(i-2, red_light)\n strip.set_pixel(i-1, red_med)\n strip.set_pixel(i, red)\n strip.set_pixel(i+1, red_med)\n strip.set_pixel(i+2, red_light)\n if i > 0: strip.set_pixel(i-3, off)\n strip.show()\n sleep(delay)\n for i in range(NUMBER_PIXELS-4, 1, -1):\n if i < NUMBER_PIXELS-2: strip.set_pixel(i+3, off)\n strip.set_pixel(i-2, red_light)\n strip.set_pixel(i-1, red_med)\n strip.set_pixel(i, red)\n strip.set_pixel(i+1, red_med)\n strip.set_pixel(i+2, red_light)\n strip.show()\n sleep(delay)\n
"},{"location":"kits/larson-scanner/01-intro/#adding-some-color","title":"Adding Some Color","text":"The pattern above is faithful to the original Cylon robot pattern, but to be honest, it is a little boring. We can spruce it up a bit by adding some color and the comet-tail pattern.
This program cycles through a \"moving rainbow\" pattern and then the comet pattern for 10 colors.
from utime import sleep\n# We are using https://github.com/blaz-r/pi_pico_neopixel\nfrom neopixel import Neopixel\n\nNUMBER_PIXELS = 25\nSTATE_MACHINE = 0\nLED_PIN = 0\n\n# The Neopixels on the Maker Pi RP2040 are the GRB variety, not RGB\nstrip = Neopixel(NUMBER_PIXELS, STATE_MACHINE, LED_PIN, \"GRB\")\n\n# Color RGB values\nred = (255, 0, 0)\noff = (0,0,0)\norange = (255, 60, 0) # Gamma corrected from G=128 to be less like yellow\nyellow = (255, 150, 0)\ngreen = (0, 255, 0)\nblue = (0, 0, 255)\ncyan = (255, 0, 255)\nindigo = (75, 0, 130) # purple?\nviolet = (138, 43, 226) # mostly pink\ncolor_names = ('red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet')\nnum_colors = len(color_names)\ncolors = (red, orange, yellow, green, blue, indigo, violet)\n\n# set to be 1 to 100 for percent brightness\nstrip.brightness(100)\n\ndef draw_eye_7(r, g, b):\n for i in range(6, NUMBER_PIXELS): \n strip.set_pixel(i, (r, g, b))\n # step back from the current to 6 back halfing the intensity each time\n for j in range(0,7):\n strip.set_pixel(i-j, (int(r/pow(2,j)), int(g/pow(2,j)), int(b/pow(2,j))))\n if i > 6: strip.set_pixel(i-7, (0,0,0))\n strip.show()\n sleep(delay)\n strip.set_pixel(i, off)\n for i in range(NUMBER_PIXELS-6, 0, -1):\n strip.set_pixel(i, (r, g, b)) \n for j in range(7,0):\n strip.set_pixel(i+j, (int(r/pow(2,j)), int(g/pow(2,j)), int(b/pow(2,j))))\n if i < NUMBER_PIXELS-7: strip.set_pixel(i+7, (0,0,0))\n strip.show()\n sleep(delay)\n\ndef draw_rainbow():\n for i in range(0, NUMBER_PIXELS-7):\n strip.set_pixel(i, violet)\n strip.set_pixel(i+1, indigo)\n strip.set_pixel(i+2, blue)\n strip.set_pixel(i+3, green)\n strip.set_pixel(i+4, yellow)\n strip.set_pixel(i+5,orange)\n strip.set_pixel(i+6, red)\n if i > 6: strip.set_pixel(i-7, (0,0,0))\n strip.show()\n sleep(delay)\n strip.set_pixel(i, off)\n for i in range(NUMBER_PIXELS-7, 1, -1):\n strip.set_pixel(i, red)\n strip.set_pixel(i+1, orange)\n strip.set_pixel(i+2, yellow)\n strip.set_pixel(i+3, green)\n strip.set_pixel(i+4, blue)\n strip.set_pixel(i+5, indigo)\n strip.set_pixel(i+6, violet)\n if i < NUMBER_PIXELS-7: strip.set_pixel(i+7, (0,0,0))\n strip.show()\n sleep(delay)\n\n# delay = .031\n\ndelay = .06\ncolor_index = 0\nwhile True:\n draw_rainbow()\n draw_eye_7(255,0,0) #red\n draw_eye_7(255,60,0) #orange\n draw_eye_7(255,255,0) # yellow\n draw_eye_7(0,255,0) # green\n draw_eye_7(0,0,255) # b;ie\n draw_eye_7(0,255,255) # cyan\n draw_eye_7(75,30,130) # indigo\n draw_eye_7(255,0,255) # violet\n draw_eye_7(255,255,255) # white\n
"},{"location":"kits/larson-scanner/01-intro/#adding-the-cylon-scanner-sounds","title":"Adding the Cylon Scanner Sounds","text":"You can also add the Cylon eye scanner sound by addint a .wav file to the pico and using the playWave library. This is covered in the Sound and Music Play Audio File lesson of this microsite.
"},{"location":"kits/larson-scanner/01-intro/#more-to-explore","title":"More to Explore","text":"The Cytron Nano RP2040 is a low-cost ($9), high-functionality board.
"},{"location":"kits/maker-nano-rp2040/01-intro/#features","title":"features","text":"from machine import Pin # get the Pin function from the machine module\nfrom time import sleep # get the sleep library from the time module\n# this is the built-in green LED on the Pico\nled = machine.Pin(0, machine.Pin.OUT)\n\n# repeat forever\nwhile True:\n led.high() # turn on the LED\n sleep(0.5) # leave it on for 1/2 second\n led.low() # Turn off the LED\n sleep(0.5) # leave it off for 1/2 second\n
"},{"location":"kits/maker-pi-pico/","title":"Cytron Maker Pi Pico","text":"The Cytron Maker Pi Pico is a $9.99 breakout board for the Raspberry Pi Pico with many features.
Cytron Maker Pi Pico Github Repo
"},{"location":"kits/maker-pi-pico/02-running-lights/","title":"Running lights","text":"This program turns on all 24 blue LEDs on the board, one at a time. It then turns them all off.
TODO - record a GIF or video.
import machine\nimport utime\n\n# RUNNING LIGHT\n\nfor i in range(29): # from 0 to 28 \n if i != 23 and i != 24: # pin 23 and 24 are not GPIO pins\n machine.Pin(i,machine.Pin.OUT) # set the pins to output\n\nwhile True:\n for i in range(29): \n if i != 23 and i != 24: \n machine.Pin(i).value(0) # turn off the LED\n utime.sleep(0.1) # sleep for 100ms\n machine.Pin(i).value(1) # turn on the LED\n\n for i in range(28,-1,-1): # from 28 to 0\n if i != 23 and i != 24:\n machine.Pin(i).value(1) # turn on the LED\n utime.sleep(0.1)\n machine.Pin(i).value(0) # turn off the LED\n
"},{"location":"kits/maker-pi-pico/02-running-lights/#references","title":"References","text":"This program was taken from tje Cytron GitHub site here.
"},{"location":"kits/maker-pi-pico/09-micro-sd-card-reader/","title":"Micro SD Card Reader","text":"Secure Digital (SD) is a non-volatile memory card format for use in portable devices such as cameras, MP3 players and portable devices.
On Microcontrollers SD cards are usually access through an SPI interface although there are also devices that use I2C interfaces.
"},{"location":"kits/maker-pi-pico/09-micro-sd-card-reader/#maker-pi-pico-connections","title":"Maker Pi Pico Connections","text":"GPIO Pin SD Mode SPI Mode GP10 CLK SCK GP11 CMD SDI GP12 DAT0 SD0 GP13 DAT1 X GP14 DAT2 X GP15 CD/DAT3 CSn"},{"location":"kits/maker-pi-pico/09-micro-sd-card-reader/#maker-pi-pico-example-code","title":"Maker Pi Pico Example Code","text":""},{"location":"kits/maker-pi-pico/09-micro-sd-card-reader/#pin-definitions","title":"Pin Definitions","text":"# SD Mode Definitions\nSDCARD_CLK = 10\nSDCARD_CMD = 11\nSDCARD_DAT0 = 12\nSDCARD_DAT1 = 13\nSDCARD_DAT2 = 14\nSDCARD_CD_DAT3 = 15\n\n# SPI Mode Definitions\nSDCARD_SCK = 10\nSDCARD_SDI = 11\nSDCARD_SD0 = 12\nSDCARD_X1 = 13\nSDCARD_X2 = 14\nSDCARD_CSn = 15\n
"},{"location":"kits/maker-pi-pico/09-micro-sd-card-reader/#sample-code-for-spi-mode","title":"Sample Code for SPI Mode","text":"import machine, os, sdcard\n\n# Assign chip select (CS) pin (and start it high)\ncs = machine.Pin(15, machine.Pin.OUT)\n# Intialize SPI peripheral (start with 1 MHz)\nspi = machine.SPI(1,\n baudrate=1000000,\n polarity=0,\n phase=0,\n bits=8,\n firstbit=machine.SPI.MSB,\n sck=machine.Pin(10),\n mosi=machine.Pin(11),\n miso=machine.Pin(12))\n# Initialize SD card\nsd = sdcard.SDCard(spi, cs)\n\n# OR this simpler initialization code should works on Maker Pi Pico too...\n#sd = sdcard.SDCard(machine.SPI(1), machine.Pin(15))\n\nos.mount(sd, '/sd')\n# check the content\nos.listdir('/sd')\n\n# try some standard file operations\nfile = open('/sd/test.txt', 'w')\nfile.write('Testing SD card on Maker Pi Pico')\nfile.close()\nfile = open('/sd/test.txt', 'r')\ndata = file.read()\nprint(data)\nfile.close()\n
Results:
Testing SD card on Maker Pi Pico\n
"},{"location":"kits/maker-pi-pico/09-micro-sd-card-reader/#references","title":"References","text":"The Maker Pi PR2040 kit from Cytron Technologies is a $9.90 US kit that is designed to simplify learning robotics using the RP2040 chip. It became available in April of 2021, but demand has been very high and it is out-of-stock on many retailers sites. We can understand this. The Maker Pi PR2040 is the most powerful robotics board we have ever seen for under $10!
The photo above is our base robot kit. It includes the Maker Pi RP2040 board mounted on a standards Smart Car chassis. The image above shows the Time-of-flight distance sensor mounted in the front below the chassis. Note that the batteries are mounted on the bottom of the robot.
Below is the top view of the Cytron Maker Pi RP2040 robotics board.
"},{"location":"kits/maker-pi-rp2040/#features","title":"Features","text":"All our robots are built around a RP2040 and a motor driver with a few low-cost sensors and displays. With the exception of the OLED display, this board packs in a huge number of features for a low cost of $9.90.
These labs don't need anything except the Maker Pi RP2040 board.
These labs require additional parts such as DC hobby motors and servos
There are literally hundreds of sensors that have Grove connectors on them. In addition, we can purchase Grove connectors for as low as 30 cents each. Any other sensors with male pins can be easily connected with these 38 cent connectors with female Dupont jumper connectors. Classrooms with a large collection of these sensors can allow students to try new sensors and outputs without needing a breadboard or soldering. We will focus initially on two sensors we will use for our collision avoidance robot. We prefer the Time-of-Flight sensor because it uses a standard I2C interface and thus could share the I2C bus with other devices.
When this kit is combined with a standard 2 Wheel Drive Smart Car Chassis and a distance sensor it becomes a great low-cost way of getting started with Python and robots.
"},{"location":"kits/maker-pi-rp2040/#clearly-labeled-pin-numbers","title":"Clearly Labeled Pin Numbers","text":"One of the biggest disadvantages of the Raspberry Pi Pico is the fact that pin labels are NOT visible when it is mounted on a breadboard. We have to take the Pico out of the breadboard to read the pin numbers on the bottom of the board. A much better design would be to follow the best practices and put the labels on the top of the board where they are visible. This is clearly done on the Maker Pi RP2040 board!
Note the pin labels, GND, 3.3V, GP0 and GP1 are clearly printed on the top of the board.
Note the circuit in the upper right corner displays how you can use the analog input port to read the battery level of the robot.
"},{"location":"kits/maker-pi-rp2040/#removing-the-default-circuitpython","title":"Removing the Default CircuitPython","text":"Cytron Technologies has a wonderful YouTube videos on how to program the Maker Pi RP2040 using MicroPython. Unfortunately, this board does NOT come with our standard MicroPython loaded! :-O It uses the non-standard Adafruit CircuitPython that is incompatible with most MicroPython programs being used today. This is a sad state of affairs that confuses our students and makes it difficult to share code and libraries for MicroPython. According to Google trends, over the last 12 months for worldwide searches, MicroPython has almost five time the interest of CircuitPython. Preloading the board with CircuitPython sends a very confusing message to the marketplace.
"},{"location":"kits/maker-pi-rp2040/#flash-nuke","title":"Flash Nuke","text":"I want to make sure that my RP2040 was starting out with a clean image. I downloaded the flash_nuke.uf2 file to remove the default CircuitPython runtime and all the related files.
Note that the board must be fully powered down after this load for it to work. I had 4 AA batteries connected to the VIN screw headers, so it was not resetting correctly and the reset was not working until I disconnected the batteries.
The latests MicroPython runtimes are here
"},{"location":"kits/maker-pi-rp2040/#easy-motor-testing-buttons","title":"Easy Motor Testing Buttons","text":"One of the things I love about this board is how incredibly easy it is for students to test their motors. The board provides four very convenient motor test buttons right on the board. By pressing each one you can make both motors go forward and backwards. This is a great way for students to learn about how we can generate PWM signals to simulate these four buttons. Whoever design this board clearly had their students in mind!
"},{"location":"kits/maker-pi-rp2040/#references","title":"References","text":"The retail list price is $9.99. Thie Cytron Maker Pi RP2040 kit includes 3 Grove connectors, screwdriver and feet) Cytron
Here are some online retailers that seel this kit:
There is also a YouTube Video that demonstrates the features of the board.
"},{"location":"kits/maker-pi-rp2040/01-getting-started/#install-the-micropython-runtime-library","title":"Install the MicroPython Runtime Library","text":"The Maker Pi RP2040 comes with an incompatible CircuitPython run-time. Our first step is to re-initialize the board with the Raspberry Pi flash_nuke.uf2 runtime. We can then load the latest MicroPython libraries. When we wrote these labs we were using MicroPython version 1.7 that was released in September of 2021.
To install MicroPython you mush hold down the BOTSEL button on the main board while you turn on the board using the on-board power switch. This will make the board look like a USB drive. You can then just drag the flash-nuke file onto the drive and the board will be initialized. Make sure to power the board off and back on.
You can now repeat this process with the Raspberry Pi MicroPython Runtime. Just remember to press and hold down the BOTSEL before you turn on the board and reboot after the image has been copied to the microcontroller.
If you have never used MicroPython, the Raspberry Pi Foundation has a nice Getting Started Guide that can be helpful.
"},{"location":"kits/maker-pi-rp2040/01-getting-started/#get-familiar-with-your-ide-thonny-and-the-basic-programs","title":"Get Familiar with your IDE (Thonny) and the Basic Programs","text":"There are many Integrated Development Environments (IDEs) that work with the Raspberry Pi RP2040 chip. The one you chose just should support MicroPython and be able to upload and run new programs. Once you turn on the board you should be able to configure Thonny to use the Raspberry Pi MicroPython interface. When you press the Stop/Reset button you should see the MicroPython REPL prompt.
"},{"location":"kits/maker-pi-rp2040/01-getting-started/#_1","title":"Getting Started","text":""},{"location":"kits/maker-pi-rp2040/01-getting-started/#test-the-motor-connections","title":"Test the Motor Connections","text":"Use the Motor Connection Lab
"},{"location":"kits/maker-pi-rp2040/01-getting-started/#getting-help","title":"Getting Help","text":"MicroPython on the RP2040 is the most powerful low-cost system on the market today. With 264K of RAM, it will take a LOT of work to run out of memory. But with all things new, there is a lock of good documentation, drivers and sample code. To help you along, we suggest the following resources:
Once you have the MicroPython runtime installed and your IDE setup, this board is easy to program!
Let's take a look at the classic \"Blink\" program that turns a single LED on and off every second.
"},{"location":"kits/maker-pi-rp2040/02-blue-led-lab/#blink-first-blue-led","title":"Blink First Blue LED","text":"The Maker Pi RP2040 has a row of 13 small blue LEDs that monitor the digital input/output of 13 of the GPIO signals. If you set any of the output pins to be HIGH, the LED will be on. If you set the pin to be LOW, the blue LED will be off. These LEDs make it easy for you to view the state of your GPIO pins and can help debugging your programs.
Just remember that if you are using the pins for communication, you can't use the LEDs for other purposes.
Here is a small program that will blink the first blue LED:
import machine\nimport time\n# setup the first LED as an output signal\nfirst_led = machine.Pin(0, machine.Pin.OUT)\n\nwhile True:\n first_led.toggle()\n time.sleep(1)\n
Note that the first four lines are the \"setup\" of the program. These lines will only run once when the program starts. The code indented after the while True:
line will continue to run until the device is reset or powered down.
Here is a demo using the 13 nice blue LEDs used to show the status of the pins.
import machine\nimport time\n\n# The Maker Pi RP2040 has 13 fantastic blue GPIO status LEDs\nblue_led_pins = [0,1,2,3,4,5,6,7,16,17,26,27,28]\nnumber_leds = len(blue_led_pins)\nled_ports = []\ndelay = .05\n\n# create a list of the ports\nfor i in range(number_leds):\n led_ports.append(machine.Pin(blue_led_pins[i], machine.Pin.OUT))\n\n# loop forever\nwhile True:\n # blue up\n for i in range(0, number_leds):\n led_ports[i].high()\n time.sleep(delay)\n led_ports[i].low()\n # blue down\n for i in range(number_leds - 1, 0, -1):\n led_ports[i].high()\n time.sleep(delay)\n led_ports[i].low()\n
This demo uses a list of all the 13 digital I/O ports. For each port it sets the port to be a digital output. In the main loop it then goes up and down the strip of LEDs, turning each one on for 1/20th of a second (.05 seconds).
"},{"location":"kits/maker-pi-rp2040/03-neopixel-lab/","title":"NeoPixel Lab","text":""},{"location":"kits/maker-pi-rp2040/03-neopixel-lab/#neopixel-demo-lab","title":"NeoPixel Demo Lab","text":"The Maker Pi RP2040 comes with two built-in NeoPixels. Each NeoPixel has a red, green and blue LED inside it. Each of these LEDs can be set to any one of 256 values from 0 (off) to 255 (brightest value).
"},{"location":"kits/maker-pi-rp2040/03-neopixel-lab/#neopixel-setup","title":"NeoPixel Setup","text":"from neopixel import Neopixel\n\nNUMBER_PIXELS = 2\nSTATE_MACHINE = 0\nLED_PIN = 18\n\n# The Neopixels on the Maker Pi RP2040 are the GRB variety, not RGB\nstrip = Neopixel(NUMBER_PIXELS, STATE_MACHINE, LED_PIN, \"GRB\")\n
"},{"location":"kits/maker-pi-rp2040/03-neopixel-lab/#neopixel-blink-lab","title":"NeoPixel Blink Lab","text":"In this lab, we will turn the first NeoPixel element on red for 1/2 second and then turn it off for 1/2 second. We repeat this until the program is terminated.
"},{"location":"kits/maker-pi-rp2040/03-neopixel-lab/#setting-up-the-neopixel-library","title":"Setting up the NeoPixel Library","text":"We will be calling a NeoPixel driver in the /lib directory. We initiaze our NeoPixel strip by calling the init method all Neopixel() and pass it three parameters:
from utime import sleep\n# We are using https://github.com/blaz-r/pi_pico_neopixel\nfrom neopixel import Neopixel\n\nNUMBER_PIXELS = 2\nSTATE_MACHINE = 0\nLED_PIN = 18\n\n# The Neopixels on the Maker Pi RP2040 are the GRB variety, not RGB\nstrip = Neopixel(NUMBER_PIXELS, STATE_MACHINE, LED_PIN, \"GRB\")\n\nwhile True:\n # turn on first pixel red for 1/2 second\n strip.set_pixel(0, (255, 0, 0))\n strip.show()\n sleep(.5) \n strip.set_pixel(0, (0, 0, 0)) # turn all colors off\n strip.show()\n sleep(.5)\n
import time\n# We are using https://github.com/blaz-r/pi_pico_neopixel\nfrom neopixel import Neopixel\n\nNUMBER_PIXELS = 2\nSTATE_MACHINE = 0\nLED_PIN = 18\n\n# The Neopixels on the Maker Pi RP2040 are the GRB variety, not RGB\nstrip = Neopixel(NUMBER_PIXELS, STATE_MACHINE, LED_PIN, \"GRB\")\n\n# Color RGB values\nred = (255, 0, 0)\norange = (255, 60, 0) # Gamma corrected from G=128 to be less like yellow\nyellow = (255, 150, 0)\ngreen = (0, 255, 0)\nblue = (0, 0, 255)\nindigo = (75, 0, 130) # purple?\nviolet = (138, 43, 226) # mostly pink\ncolor_names = ('red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet')\nnum_colors = len(color_names)\ncolors = (red, orange, yellow, green, blue, indigo, violet)\n\n# set to be 1 to 100 for percent brightness\nstrip.brightness(100)\n\ncolor_index = 0\nwhile True:\n for color in colors:\n for i in range(NUMBER_PIXELS):\n print(i, color_names[color_index])\n strip.set_pixel(i, color)\n strip.show()\n time.sleep(1)\n color_index += 1\n if color_index >= num_colors: color_index = 0\n
"},{"location":"kits/maker-pi-rp2040/04-button-lab/","title":"Two Button Press","text":"We learned how to write code to monitor a button press in the Button Lab.
Recall we talked about how to remove the \"debounce noise\" when a button is pressed by adding a timer to make sure we had a clean transition (debouncing the switch):
We did this by waiting for the transition to settle down to its new state.
import utime\nfrom machine import Pin\n\n# Sample Raspberry Pi Pico MicroPython button press example with a debounce delay value of 200ms in the interrupt handler\n\nbutton_presses = 0 # the count of times the button has been pressed\nlast_time = 0 # the last time we pressed the button\n\nbuiltin_led = machine.Pin(25, Pin.OUT)\n# The lower left corner of the Pico has a wire that goes through the buttons upper left and the lower right goes to the 3.3 rail\nfaster_pin = machine.Pin(20, machine.Pin.IN, machine.Pin.PULL_DOWN)\nslower_pin = machine.Pin(21, machine.Pin.IN, machine.Pin.PULL_DOWN)\n\n# This function gets called every time the button is pressed. The parameter \"pin\" is not used.\ndef button_pressed_handler(pin):\n global button_presses, last_time\n new_time = utime.ticks_ms()\n # if it has been more that 1/5 of a second since the last event, we have a new event\n if (new_time - last_time) > 200:\n # this should be pin.id but it does not work\n if '20' in str(pin):\n button_presses +=1\n else:\n button_presses -=1\n last_time = new_time\n\n\n# now we register the handler function when the button is pressed\nfaster_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\nslower_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\n\n# This is for only printing when a new button press count value happens\nold_presses = 0\n\nwhile True:\n # only print on change in the button_presses value\n if button_presses != old_presses:\n print(button_presses)\n builtin_led.toggle()\n old_presses = button_presses\n
"},{"location":"kits/maker-pi-rp2040/04-button-lab/#making-the-buttons-change-the-neopixel-color","title":"Making the Buttons Change the NeoPixel Color","text":"In this lab, we will combine the button press lab with our NeoPixel lab to allow you to change the NeoPixel colors if a button on the board is pressed. Each button will control the color of one of the pixels.
# press buttons to change the color of the NeoPixels\nimport utime\nfrom machine import Pin\nfrom neopixel import Neopixel\n\nNUMBER_PIXELS = 2\nSTATE_MACHINE = 0\nLED_PIN = 18\nBUTTON_A_PIN = 20\nBUTTON_B_PIN = 21\n# Sample Raspberry Pi Pico MicroPython button press example with a debounce delay value of 200ms in the interrupt handler\n# The Neopixels on the Maker Pi RP2040 are the GRB variety, not RGB\nstrip = Neopixel(NUMBER_PIXELS, STATE_MACHINE, LED_PIN, \"GRB\")\n\n# Color RGB values\nred = (255, 0, 0)\norange = (125, 60, 0) # Gamma corrected from G=128 to be less like yellow\nyellow = (255, 150, 0)\ngreen = (0, 255, 0)\nblue = (0, 0, 255)\ncyan = (0, 255, 255)\nindigo = (75, 0, 130) # purple?\nviolet = (138, 43, 226) # mostly pink\nwhite = (255, 255, 255)\ncolor_names = ('red', 'orange', 'yellow', 'green', 'blue', 'cyan', 'indigo', 'violet', 'white')\nnum_colors = len(color_names)\ncolors = (red, orange, yellow, green, blue, cyan, indigo, violet, white)\n\n# color index into colors list\nneopixel_a = 0\nneopixel_b = 0\n# set to be 1 to 100 for percent brightness\nstrip.brightness(100)\n\nbutton_presses = 0 # the count of times the button has been pressed\nlast_time = 0 # the last time we pressed the button\n\n# The lower left corner of the Pico has a wire that goes through the buttons upper left and the lower right goes to the 3.3 rail\nbutton_a = machine.Pin(BUTTON_A_PIN, machine.Pin.IN, machine.Pin.PULL_DOWN)\nbutton_b = machine.Pin(BUTTON_B_PIN, machine.Pin.IN, machine.Pin.PULL_DOWN)\n\n# This function gets called every time the button is pressed. The parameter \"pin\" is not used.\ndef button_pressed_handler(pin):\n global button_presses, last_time, num_colors, neopixel_a, neopixel_b\n new_time = utime.ticks_ms()\n # if it has been more that 1/5 of a second since the last event, we have a new event\n if (new_time - last_time) > 200:\n # this should be pin.id but it does not work\n button_presses += 1\n if '20' in str(pin):\n neopixel_a +=1\n if neopixel_a > num_colors - 1:\n neopixel_a = 0 \n else:\n neopixel_b +=1\n if neopixel_b > num_colors - 1:\n neopixel_b = 0 \n last_time = new_time\n\n# now we register the handler function when the button is pressed\nbutton_a.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\nbutton_b.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\n\n# This is for only printing when a new button press count value happens\nold_presses = 0\nprint('Running NeoPixel Button Lab')\nstrip.set_pixel(0, (4,5,5))\nstrip.set_pixel(1, (4,5,5))\nstrip.show()\n\ndef main():\n global button_presses, old_presses, colors, neopixel_a, neopixel_b\n while True:\n # only print on change in the button_presses value\n if button_presses != old_presses:\n print(button_presses)\n print('NeoPixel A:', color_names[neopixel_a], 'index:', neopixel_a)\n print('NeoPixel B:', color_names[neopixel_b], 'index:', neopixel_b)\n strip.set_pixel(0, colors[neopixel_a])\n strip.set_pixel(1, colors[neopixel_b])\n strip.show()\n old_presses = button_presses\n\ntry:\n main()\nexcept KeyboardInterrupt:\n print('Got ctrl-c')\nfinally:\n # Cleanup code\n print('Turning off NeoPixels')\n strip.set_pixel(0, (0,0,0))\n strip.set_pixel(1, (0,0,0))\n strip.show()\n
"},{"location":"kits/maker-pi-rp2040/05-sound-lab/","title":"Play Mario on MicroPython","text":"This program will play the theme music from the Mario video game.
from machine import Pin, PWM\nfrom utime import sleep\nbuzzer = PWM(Pin(22))\n\ntones = {\n\"B0\": 31,\"C1\": 33,\"CS1\": 35,\"D1\": 37,\"DS1\": 39,\"E1\": 41,\"F1\": 44,\"FS1\": 46,\n\"G1\": 49,\"GS1\": 52,\"A1\": 55,\"AS1\": 58,\"B1\": 62,\"C2\": 65,\n\"CS2\": 69,\"D2\": 73,\"DS2\": 78,\"E2\": 82,\"F2\": 87,\"FS2\": 93,\"G2\": 98,\n\"GS2\": 104,\"A2\": 110,\"AS2\": 117,\"B2\": 123,\"C3\": 131,\"CS3\": 139,\n\"D3\": 147,\"DS3\": 156,\"E3\": 165,\"F3\": 175,\"FS3\": 185,\n\"G3\": 196,\"GS3\": 208,\"A3\": 220,\"AS3\": 233,\"B3\": 247,\"C4\": 262,\"CS4\": 277,\"D4\": 294,\"DS4\": 311,\n\"E4\": 330,\"F4\": 349,\"FS4\": 370,\"G4\": 392,\"GS4\": 415,\"A4\": 440,\"AS4\": 466,\"B4\": 494,\"C5\": 523,\"CS5\": 554,\"D5\": 587,\"DS5\": 622,\"E5\": 659,\"F5\": 698,\n\"FS5\": 740,\"G5\": 784,\"GS5\": 831,\"A5\": 880,\"AS5\": 932,\"B5\": 988,\"C6\": 1047,\"CS6\": 1109,\"D6\": 1175,\"DS6\": 1245,\"E6\": 1319,\"F6\": 1397,\"FS6\": 1480,\"G6\": 1568,\"GS6\": 1661,\n\"A6\": 1760,\"AS6\": 1865,\"B6\": 1976,\"C7\": 2093,\"CS7\": 2217,\"D7\": 2349,\"DS7\": 2489,\"E7\": 2637,\"F7\": 2794,\"FS7\": 2960,\"G7\": 3136,\"GS7\": 3322,\"A7\": 3520,\n\"AS7\": 3729,\"B7\": 3951,\"C8\": 4186,\"CS8\": 4435,\"D8\": 4699,\"DS8\": 4978\n}\n\nsong = [\"E5\",\"G5\",\"A5\",\"P\",\"E5\",\"G5\",\"B5\",\"A5\",\"P\",\"E5\",\"G5\",\"A5\",\"P\",\"G5\",\"E5\"]\nmario = [\"E7\", \"E7\", 0, \"E7\", 0, \"C7\", \"E7\", 0, \"G7\", 0, 0, 0, \"G6\", 0, 0, 0, \"C7\", 0, 0, \"G6\",\n 0, 0, \"E6\", 0, 0, \"A6\", 0, \"B6\", 0, \"AS6\", \"A6\", 0, \"G6\", \"E7\", 0, \"G7\", \"A7\", 0, \"F7\", \"G7\",\n 0, \"E7\", 0,\"C7\", \"D7\", \"B6\", 0, 0, \"C7\", 0, 0, \"G6\", 0, 0, \"E6\", 0, 0, \"A6\", 0, \"B6\", 0,\n \"AS6\", \"A6\", 0, \"G6\", \"E7\", 0, \"G7\", \"A7\", 0, \"F7\", \"G7\", 0, \"E7\", 0,\"C7\", \"D7\", \"B6\", 0, 0]\n\ndef playtone(frequency):\n buzzer.duty_u16(1000)\n buzzer.freq(frequency)\n\ndef bequiet():\n buzzer.duty_u16(0)\n\ndef playsong(mysong):\n for i in range(len(mysong)):\n if (mysong[i] == \"P\" or mysong[i] == 0 ):\n bequiet()\n else:\n playtone(tones[mysong[i]])\n sleep(0.3)\n bequiet()\nplaysong(mario)\n
"},{"location":"kits/maker-pi-rp2040/06-up-down-lab/","title":"Up Down Mode Lab","text":"In this lab, we will combine the two buttons with the blue LEDs, the NeoPixels and the buzzer labs. The We will make the LED, NeoPixels and sound all change for each button press. You will be able to up and down the color spectrum and the sound frequency.
We will start with the material from our button lab. We will create two functions that will be triggered by the two buttons. One will increment a counter (add one) and the other will decrement the counter (subtract 1). By pressing one of the two buttons you will cycle through the modes of the program.
The diagram has eight different modes. The default mode is usually mode=0. When you press the left button the mode will increase by one. The NeoPixels will change from red to orange. Pressing the left button will increase the mode by one going to the orange mode. Pressing the right button will subtract one from the mode going from mode 1 (orange) back to model 0 (red).
Within our Interrupt Request Handler (IRQ) function we will also have to add two lines to deal with the wrap around logic like this:
# wrap around to first mode\n if mode >= mode_count: mode = 0\n if mode < 0: mode = mode_count - 1\n
"},{"location":"kits/maker-pi-rp2040/06-up-down-lab/#full-program","title":"Full Program","text":"# Mode Up/Down Lab\n# Change a mode using the buttons on the Maker Pi RP2040 board\n# Changes the NeoPixel color and the blue GPIO status LEDs\nimport time\nfrom machine import Pin, PWM\n# We are using a MicroPython NeoPixel library from here: https://github.com/blaz-r/pi_pico_neopixel\nfrom neopixel import Neopixel\n\nBUZZER_PORT = 22\nbuzzer = PWM(Pin(BUZZER_PORT))\n\nNUMBER_PIXELS = 2\nSTATE_MACHINE = 0\nNEOPIXEL_PIN = 18\n\n# The Neopixels on the Maker Pi RP2040 are the GRB variety, not RGB\nstrip = Neopixel(NUMBER_PIXELS, STATE_MACHINE, NEOPIXEL_PIN, \"GRB\")\n\n# have up to 13 that we can use\nblue_led_pins = [0,1,2,3,4,5,6,7,16,17,26,27,28]\nnumber_leds = len(blue_led_pins)\nled_ports = []\n# create a list of the port pin object instances\nfor i in range(number_leds):\n led_ports.append(machine.Pin(blue_led_pins[i], machine.Pin.OUT))\n\n# Color RGB values as tuples - needs some Gamma corrections\nred = (255, 0, 0)\norange = (255, 60, 0) # Gamma corrected from G=128 to be less like yellow\nyellow = (255, 150, 0)\ngreen = (0, 255, 0)\nblue = (0, 0, 255)\nindigo = (75, 0, 130) # purple?\nviolet = (138, 43, 226) # mostly pink\ncyan = (0, 255, 255)\nlightgreen = (100, 255, 100)\nwhite = (128, 128, 128) # not too bright\ncolor_names = ('red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet', 'cyan', 'lightgreen', 'white')\nnum_colors = len(color_names)\ncolors = (red, orange, yellow, green, blue, indigo, violet, cyan, lightgreen, white)\n\n# set to be 1 to 100 for percent brightness\nstrip.brightness(100)\n\n# Sample Raspberry Pi Pico MicroPython button press example with a debounce delay value of 200ms in the interrupt handler\n\nmode = 0 # the default mode on powerup and reset\nmode_count = len(color_names)\nlast_time = 0 # the last time we pressed the button\n\nbuiltin_led = machine.Pin(25, Pin.OUT)\n# Give our pins some logical names\nnext_mode_pin = machine.Pin(20, machine.Pin.IN, machine.Pin.PULL_DOWN)\nprevious_mode_pin = machine.Pin(21, machine.Pin.IN, machine.Pin.PULL_DOWN)\n\n# This function gets called every time the button is pressed. The parameter \"pin\" is not used.\ndef button_pressed_handler(pin):\n global mode, last_time\n new_time = time.ticks_ms()\n # if it has been more that 1/5 of a second since the last event, we have a new event\n if (new_time - last_time) > 200:\n # this should be pin.id but it does not work\n if '20' in str(pin):\n mode +=1\n else:\n mode -=1\n # wrap around to first mode\n if mode >= mode_count: mode = 0\n if mode < 0: mode = mode_count - 1\n last_time = new_time\n\ndef set_blue_led_mode(mode):\n global num_colors\n for i in range(0, num_colors):\n if i == mode:\n led_ports[i].high()\n else:\n led_ports[i].low()\n\n# Register the handler function when either button is pressed\nnext_mode_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\nprevious_mode_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\n\n# Note the non-linear increases in frequency - note that some are louder\ntone_freq = [100, 150, 210, 280, 350, 450, 580, 750, 850, 950, 1000]\ndef playtone(frequency):\n buzzer.duty_u16(1000)\n buzzer.freq(frequency)\n\ndef bequiet():\n buzzer.duty_u16(0)\n\n# This is for only printing when a new button press count value happens\nold_mode = -1\n\nprint('found ', mode_count, ' modes.')\nwhile True:\n # only print on change in the button_presses value\n if mode != old_mode:\n print('new mode:', mode, color_names[mode], tone_freq[mode])\n # get the color mode\n color = colors[mode]\n strip.set_pixel(0, color)\n strip.set_pixel(1, color)\n strip.show()\n set_blue_led_mode(mode)\n playtone(tone_freq[mode])\n time.sleep(.2)\n bequiet()\n old_mode = mode\n
"},{"location":"kits/maker-pi-rp2040-robot/","title":"Cytron Maker Pi RP2040 Collision Avoidance Robot","text":"We have been working on designing a run robot that can be used to teach computational thinking since 2014. We have gone through many generations, and now we think we have a fantastic design that is powerful, flexible, extendible and inexpensive. We love this robot because:
This version uses the time-of-flight sensor.
The robot can be built ia a few hours using a screwdriver and soldering four wires onto the motors.
"},{"location":"kits/maker-pi-rp2040-robot/#sample-parts-list","title":"Sample Parts List","text":"This kit is a $25 robot kit that we will use in our CoderDojo robotics classes. This kit includes:
You will need to provide 4 AA batteries and a Micro USB connector that works with your PC or Mac.
"},{"location":"kits/maker-pi-rp2040-robot/02-assembly/#assemble-the-smartcar-chassis","title":"Assemble the SmartCar Chassis","text":"In this version of the kit, the wires are pre-soldered onto the motors.
Here is the robot kit in all the packaging:
Your first task is to remove the protective backing from the acrylic body.
Here are all the parts removed from the packaging:
We mount the motors with the wires on the inside and the heads of the bolts on the outside. This photo shows cable ties I have added so that the wires don't get pulled out by our students. These cable ties are optional.
Next, we position the battery pack on the BOTTOM so that we have more room on the top for our circuit board, sensors and add-on displays.
I used a 1/8th inch drill bit to put holes where the battery pack should be mounted.
Next, I put the flat-head screws in the battery pack. We want to make sure the top of the screw is all the way in so that it does not get in the way of the battery.
Next, we mount the rubber feet on the bottom of the Maker Pi RP2040 circuit board so that we have some space between the PC board and the main chassis. I use the space next to the four corners to mount the feet. Note that we must put the drag wheel on before we put the PC board on top of the chassis.
Next, we put the four screws and spacers in the four holes at the bottom rear of the robot directly behind the battery pack.
We then add the four screws to mount the drag wheel.
Now is a good time to check the spacing of the battery pack and the read drag wheel. The rear drag wheel must be able to spin freely in a full circle without bumping into the battery. If it bumps you might need to remount the battery pack before you proceed to the next step.
This figure has the switch that comes with the battery pack. For our work, we will not need this switch since the Maker Pi RP2040 circuit board has an no-board power switch. Most of our students put the switch in if they ever need to change circuit boards that don't have a built-in power switch. If you do this, you can solder the switch between the red power of the battery and the positive terminal of VIN.
Next, line up the printed circuit board with the USB connector facing the rear. Note where the holes are in the board and drill two 1/8\" holes to mount the board.
This photo shows the holes drilled with the screws in them.
This is the side-view from the rear of the screws holding on the circuit board.
Next use two 6 mm M3 screws to mount the ultrasonic distance sensor on top front of the robot. Some of our students like to mount the ultrasonic sensor under the chassis and point the sensor up a little so the sensor does not reflect off the floor. You can use a heat gun to soften the plastic mount to change the angle.
Next I added a drop of hot-glue under the front screws that mount the pc board. I did this because the battery pack and motor mounts get in the way of adding a nut under the board.
Next, I used a small rubber coated twist tie to keep the wires under the robot away from the wheels and battery. We don't want them to drag on the floor.
Next, we connect the motors up to the screw headers on the printed circuit board. There is a screwdriver that comes with the Cytron Maker Pi RP2040 that is handy for tightening the screws. Don't worry about getting the connections all correct. They can be adjusted in your software.
Press the wheels on the motors.
Lastly, we connect the battery to the VIN jumper, making sure to connect the red wire to the \"+\" terminal and the black wire to the \"-\" terminal.
Connect the Maker Pi RP2040 board to the top with the USB connector facing the rear.
Here is a short video of the assembly of a SmartCar Chassis. Note that this video puts the battery on the top, where we put it on the bottom.
There are many videos online how to assemble to motors to the chassis. The trick is orienting the motors correctly and making sure the bolts don't get in the way of the wheels.
"},{"location":"kits/maker-pi-rp2040-robot/06-up-down-motor-lab/","title":"Up Down Motor Speed Lab","text":"In this lab, we will make the motor speed change as the mode changes.
# Motor Setup\n# motors just barely turn at this power level\nMIN_POWER_LEVEL = 10000\nMAX_POWER_LEVEL = 65025\nPOWER_STEP = int((MAX_POWER_LEVEL - MIN_POWER_LEVEL) / 10)\n# lower right pins with USB on top\nRIGHT_FORWARD_PIN = 8\nRIGHT_REVERSE_PIN = 9\nLEFT_FORWARD_PIN = 11\nLEFT_REVERSE_PIN = 10\n\nright_forward = PWM(Pin(RIGHT_FORWARD_PIN))\nright_reverse = PWM(Pin(RIGHT_REVERSE_PIN))\nleft_forward = PWM(Pin(LEFT_FORWARD_PIN))\nleft_reverse = PWM(Pin(LEFT_REVERSE_PIN))\n\ndef drive_speed(power_level):\n right_forward.duty_u16(power_level)\n left_forward.duty_u16(power_level)\n
In the main we have:
power_level = MIN_POWER_LEVEL + mode * POWER_STEP\n# turn off the motor if we are at mode 0\nif mode == 0: power_level = 0\ndrive_speed(power_level)\n
"},{"location":"kits/maker-pi-rp2040-robot/06-up-down-motor-lab/#full-program","title":"Full Program","text":"# Mode Up/Down Lab\n# Change a mode using the buttons on the Maker Pi RP2040 board\n# Changes the NeoPixel color and the blue GPIO status LEDs\nimport time\nfrom machine import Pin, PWM\n# We are using a MicroPython NeoPixel library from here: https://github.com/blaz-r/pi_pico_neopixel\nfrom neopixel import Neopixel\n\nBUZZER_PORT = 22\nbuzzer = PWM(Pin(BUZZER_PORT))\n\nNUMBER_PIXELS = 2\nSTATE_MACHINE = 0\nNEOPIXEL_PIN = 18\n\n# The Neopixels on the Maker Pi RP2040 are the GRB variety, not RGB\nstrip = Neopixel(NUMBER_PIXELS, STATE_MACHINE, NEOPIXEL_PIN, \"GRB\")\n\n# have up to 13 that we can use\nblue_led_pins = [0,1,2,3,4,5,6,7,16,17,26,27,28]\nnumber_leds = len(blue_led_pins)\nled_ports = []\n# create a list of the port pin object instances\nfor i in range(number_leds):\n led_ports.append(machine.Pin(blue_led_pins[i], machine.Pin.OUT))\n\n# Color RGB values as tuples - needs some Gamma corrections\nred = (255, 0, 0)\norange = (255, 60, 0) # Gamma corrected from G=128 to be less like yellow\nyellow = (255, 150, 0)\ngreen = (0, 255, 0)\nblue = (0, 0, 255)\nindigo = (75, 0, 130) # purple?\nviolet = (138, 43, 226) # mostly pink\ncyan = (0, 255, 255)\nlightgreen = (100, 255, 100)\nwhite = (128, 128, 128) # not too bright\ncolor_names = ('red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet', 'cyan', 'lightgreen', 'white')\nnum_colors = len(color_names)\ncolors = (red, orange, yellow, green, blue, indigo, violet, cyan, lightgreen, white)\n\n# set to be 1 to 100 for percent brightness\nstrip.brightness(100)\n\n# Sample Raspberry Pi Pico MicroPython button press example with a debounce delay value of 200ms in the interrupt handler\n\n# Motor Setup\n# motors just barely turn at this power level\nMIN_POWER_LEVEL = 10000\nMAX_POWER_LEVEL = 65025\nPOWER_STEP = int((MAX_POWER_LEVEL - MIN_POWER_LEVEL) / 10)\n# lower right pins with USB on top\nRIGHT_FORWARD_PIN = 8\nRIGHT_REVERSE_PIN = 9\nLEFT_FORWARD_PIN = 11\nLEFT_REVERSE_PIN = 10\n\nright_forward = PWM(Pin(RIGHT_FORWARD_PIN))\nright_reverse = PWM(Pin(RIGHT_REVERSE_PIN))\nleft_forward = PWM(Pin(LEFT_FORWARD_PIN))\nleft_reverse = PWM(Pin(LEFT_REVERSE_PIN))\n\ndef drive_speed(power_level):\n right_forward.duty_u16(power_level)\n left_forward.duty_u16(power_level)\n\nmode = 0 # the default mode on powerup and reset\nmode_count = len(color_names)\nlast_time = 0 # the last time we pressed the button\n\nbuiltin_led = machine.Pin(25, Pin.OUT)\n# Give our pins some logical names\nnext_mode_pin = machine.Pin(20, machine.Pin.IN, machine.Pin.PULL_DOWN)\nprevious_mode_pin = machine.Pin(21, machine.Pin.IN, machine.Pin.PULL_DOWN)\n\n# This function gets called every time the button is pressed. The parameter \"pin\" is not used.\ndef button_pressed_handler(pin):\n global mode, last_time, power_level\n new_time = time.ticks_ms()\n # if it has been more that 1/5 of a second since the last event, we have a new event\n if (new_time - last_time) > 200:\n # this should be pin.id but it does not work\n if '20' in str(pin):\n mode +=1\n # power_level += POWER_STEP\n else:\n mode -=1\n # power_level -= POWER_STEP\n # wrap around to first mode\n if mode >= mode_count: mode = 0\n if mode < 0: mode = mode_count - 1\n last_time = new_time\n\ndef set_blue_led_mode(mode):\n global num_colors\n for i in range(0, num_colors):\n if i == mode:\n led_ports[i].high()\n else:\n led_ports[i].low()\n\n# Register the handler function when either button is pressed\nnext_mode_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\nprevious_mode_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\n\n# non-linear increase is frequency - note that some are lowder\ntone_freq = [100, 150, 210, 280, 350, 450, 580, 750, 850, 950, 1000]\ndef playtone(frequency):\n buzzer.duty_u16(1000)\n buzzer.freq(frequency)\n\ndef bequiet():\n buzzer.duty_u16(0)\n\n# This is for only printing when a new button press count value happens\nold_mode = -1\n\npower_level = MIN_POWER_LEVEL\nprint('found ', mode_count, ' modes.')\nwhile True:\n # only print on change in the button_presses value\n if mode != old_mode:\n print('new mode:', mode, color_names[mode], tone_freq[mode], power_level)\n # get the color mode\n color = colors[mode]\n strip.set_pixel(0, color)\n strip.set_pixel(1, color)\n strip.show()\n set_blue_led_mode(mode)\n playtone(tone_freq[mode])\n time.sleep(.2)\n bequiet()\n power_level = MIN_POWER_LEVEL + mode * POWER_STEP\n # turn off the motor if we are at mode 0\n if mode == 0: power_level = 0\n drive_speed(power_level)\n old_mode = mode\n
"},{"location":"kits/maker-pi-rp2040-robot/07-motor-connection-lab/","title":"Motor Drive Connection Test","text":""},{"location":"kits/maker-pi-rp2040-robot/07-motor-connection-lab/#built-in-motor-driver","title":"Built-In Motor Driver","text":"The Maker Pi RP2040 board contains a MX1508 dual channel H-bridge chip and easy-to-connect screw headers for power and motor connections. This is fantastic for teaching robotics since students can driver two motors without ever having to use a soldering iron.
!!! Note that the is designed to work with small DC-hobby motors and there is no documentation on exactly what motor driver chip is used or its precise current and power limitations.
The documentation only indicates that the maximum current is 1A continuous power and 1.5A for up to 5 seconds. The input voltage is only rated at 6 volts, which find for our standard 4 AA battery packs.
If this motor driver chip is similar to the ubiquitous L293x motor controllers, and the current should be 1A per motor.
I suspect that if you glued a small heat sink like a 16 pin DIP fin to the unknown motor driver IC on the main board you could drive slightly larger motors.
Close-up of the motor driver chip. I can't quite make out the numbers on the chip, but the logo is not \"TI\".
"},{"location":"kits/maker-pi-rp2040-robot/07-motor-connection-lab/#testing-the-connections","title":"Testing The Connections","text":"In our standard robot, the M1 is the right wheel as you are looking from the top-back of the robot. The M2 wheel is the left wheel. I connect the red to the right of the two connectors and it is also the right terminal of the motors as you are looking from the rear.
Look at the buttons near the motor connectors. Press the M1A button and verify that the right wheel is moving forward. Press the M1B and the motor should turn in reverse. Similarly the M2B button should turn the left wheel forward and the M2A should turn the left wheel in reverse. If you don't wire these connections the same way I did it is not a worry. It is easy to change the code.
"},{"location":"kits/maker-pi-rp2040-robot/07-motor-connection-lab/#motor-pin-definitions","title":"Motor Pin Definitions","text":"Now that we know what buttons control what motors and directions they turn, we are ready to define the pins that are associated with each robot movement. We have four pin assignments: both forward and reverse for both the right and left motors.
RIGHT_FORWARD_PIN = 8\nRIGHT_REVERSE_PIN = 9\nLEFT_FORWARD_PIN = 11\nLEFT_REVERSE_PIN = 10\n
"},{"location":"kits/maker-pi-rp2040-robot/07-motor-connection-lab/#testing-your-pin-definitions","title":"Testing Your Pin Definitions","text":"The following program is called our motor connection test. It will turn each motor direction for three seconds and it will print out the motor and direction in the console.
from machine import Pin, PWM\nimport time\n\nPOWER_LEVEL = 65025\n# lower right pins with USB on top\nRIGHT_FORWARD_PIN = 8\nRIGHT_REVERSE_PIN = 9\nLEFT_FORWARD_PIN = 11\nLEFT_REVERSE_PIN = 10\n\nright_forward = PWM(Pin(RIGHT_FORWARD_PIN))\nright_reverse = PWM(Pin(RIGHT_REVERSE_PIN))\nleft_forward = PWM(Pin(LEFT_FORWARD_PIN))\nleft_reverse = PWM(Pin(LEFT_REVERSE_PIN))\n\ndef spin_wheel(pwm):\n pwm.duty_u16(POWER_LEVEL)\n time.sleep(3)\n pwm.duty_u16(0)\n time.sleep(2)\n\nwhile True:\n print('right forward')\n spin_wheel(right_forward)\n\n print('right reverse')\n spin_wheel(right_reverse)\n\n print('left foward')\n spin_wheel(left_forward)\n\n print('left_reverse')\n spin_wheel(left_reverse)\n
"},{"location":"kits/maker-pi-rp2040-robot/07b-drive-square-lab/","title":"Drive Square Lab","text":""},{"location":"kits/maker-pi-rp2040-robot/07b-drive-square-lab/#prerequsites","title":"Prerequsites","text":"This lab assumes you have your Maker Pi RP2040 mounted on a SmartCar chassis with two motors and a battery hooked up.
In this lab we will program our robot to drive in a square pattern. We will start out doing a \"bench test\" that will require you to put the robot up on a block so you can see the wheels turn, but it will not drive off your desktop. You can also observe the red LED lights on the many board to see which motor direction is on.
The main loop will look like this:
while True:\n forward()\n sleep(FWD_TIME)\n\n stop()\n sleep(STOP_TIME)\n\n turn_right()\n sleep(TURN_TIME)\n\n stop()\n sleep(STOP_TIME)\n
We will need to adjust the TURN_TIME parameter to have the robot turn 90 degrees. A good value for most robots is about 1/2 second or sleep(.5).
Since we will be calling the sleep function many times we will use the following import format to keep our code tidy:
from utime import sleep\n
This says that whenever we want to pause our system we just use the sleep(time)
function we mean to use the sleep function in the micropython time library. This keeps our code small and portable."},{"location":"kits/maker-pi-rp2040-robot/07b-drive-square-lab/#adding-a-keyboard-interrupt-handler-control-c","title":"Adding a Keyboard Interrupt Handler (Control-C)","text":"It is also a problem that when we stop a program running that the PWM circuits keep generating signals, which means the robot keeps moving even after we press the STOP/RESET button. To clean this up we will allow you to run a special cleanup handler that will add a function to set all the motors to off using the stop()
function.
try:\n main()\nexcept KeyboardInterrupt:\n print('Got ctrl-c')\nfinally:\n # Optional cleanup code\n print('Cleaning up')\n print('Powering down all motors now.')\n stop()\n
"},{"location":"kits/maker-pi-rp2040-robot/07b-drive-square-lab/#full-program","title":"Full Program","text":"You are now ready to test the full program. Save the following to the main.py file, disconnect the USB connector and turn on the power on the main board. Your robot should not we driving in a square!
from machine import Pin, PWM\nfrom utime import sleep\n\nPOWER_LEVEL = 65025\n# lower right pins with USB on top\nRIGHT_FORWARD_PIN = 8\nRIGHT_REVERSE_PIN = 9\nLEFT_FORWARD_PIN = 11\nLEFT_REVERSE_PIN = 10\n\nright_forward = PWM(Pin(RIGHT_FORWARD_PIN))\nright_reverse = PWM(Pin(RIGHT_REVERSE_PIN))\nleft_forward = PWM(Pin(LEFT_FORWARD_PIN))\nleft_reverse = PWM(Pin(LEFT_REVERSE_PIN))\n\nFWD_TIME = 2\nTURN_TIME = .5 # adjust this to get the turn to be 90 degrees\nSTOP_TIME = 2\n\nright_forward = PWM(Pin(RIGHT_FORWARD_PIN))\nright_reverse = PWM(Pin(RIGHT_REVERSE_PIN))\nleft_forward = PWM(Pin(LEFT_FORWARD_PIN))\nleft_reverse = PWM(Pin(LEFT_REVERSE_PIN))\n\n\ndef turn_motor_on(pwm):\n pwm.duty_u16(POWER_LEVEL)\n\ndef turn_motor_off(pwm):\n pwm.duty_u16(0)\n\ndef forward():\n turn_motor_on(right_forward)\n turn_motor_on(left_forward)\n turn_motor_off(right_reverse)\n turn_motor_off(left_reverse)\n\ndef reverse():\n turn_motor_on(right_reverse)\n turn_motor_on(left_reverse)\n turn_motor_off(right_forward)\n turn_motor_off(left_forward)\n\ndef turn_right():\n turn_motor_on(right_forward)\n turn_motor_on(left_reverse)\n turn_motor_off(right_reverse)\n turn_motor_off(left_forward)\n\ndef turn_left():\n turn_motor_on(right_reverse)\n turn_motor_on(left_forward)\n turn_motor_off(right_forward)\n turn_motor_off(left_reverse)\n\ndef stop():\n turn_motor_off(right_forward)\n turn_motor_off(right_reverse)\n turn_motor_off(left_forward)\n turn_motor_off(left_reverse)\n\nprint('Running Drive Square Lab')\nprint('Use Control-C to Stop All Motors')\n\ndef main():\n while True:\n print('forward')\n forward()\n sleep(FWD_TIME)\n\n print('stop')\n stop()\n sleep(STOP_TIME)\n\n print('turning right')\n turn_right()\n sleep(TURN_TIME)\n\n print('stop')\n stop()\n sleep(STOP_TIME)\n\ntry:\n main()\nexcept KeyboardInterrupt:\n print('Got ctrl-c')\nfinally:\n # Optional cleanup code\n print('Cleaning up')\n print('Powering down all motors now.')\n stop()\n
"},{"location":"kits/maker-pi-rp2040-robot/08-servo-lab/","title":"Maker Pi RP2040 Servo Lab","text":"Servo motors are ideal for controlling the angle of an item such as a steering angle or the direction of a sensor. The servos used in these labs are inexpensive SG90 micro-servos that draw very little power and are ideal for a teaching lab. They can be purchased for about $3 each US on eBay. To control a 180 degree servo, you just tell it what angle you would like it to move to. The range of values is typically -90 to 90 degrees with 0 being the nominal resting position for many applications such as the steering wheel angle of a car.
The Maker Pi RP2040 has four servo ports in the upper left corner of the board (with the USB on the bottom) that use ports GP12, GP13, GP14 and GP15. You can connect any small micro servo directly to these ports. Just make sure to get the polarity correct. The colors for servos may vary somewhat, but the two most common standards are:
The general rule is that the lighter colors of orange and white will be the signal and the brown and black will be ground.
"},{"location":"kits/maker-pi-rp2040-robot/08-servo-lab/#servo-control","title":"Servo Control","text":"We will use the PWM functions in our MicroPython library to send a PWM signal to each of the servos. Servos are not controlled by the duty cycle directly. They are controlled by the width of the pulses. But we can control the approximate with of the pulses by holding the frequency constant and changing the duty cycle.
We will use a 40 hertz signal to send a PWM signal to each of the servos like this.
SERVO_FREQ_HZ = 40\n# SERVO_PERIOD_MS = 1000 / SERVO_FREQ_HZ is a 25 millisecond pulse width\nmy_pwm.freq(SERVO_FREQ_HZ)\n
"},{"location":"kits/maker-pi-rp2040-robot/08-servo-lab/#calibration-of-the-servo","title":"Calibration of the Servo","text":"There are small manufacturing variations in servos. This means to get the full sweep of a 180% servo you have to adjust the duty cycle.
By some experimentation I got the following results
SERVO_MIN_DUTY = 1725 # -90 degrees\nSERVO_MAX_DUTY = 6378 # 90 degrees\n
We can use a linear mapping function to convert the angle (from -90 to 90):
# This will take in integers of range in (min and max) return a integer in the output range (min and max)\n# Used to convert one range of values into another using a linear function like the Arduino map() function\ndef convert(x, in_min, in_max, out_min, out_max):\n return (x - in_min) * (out_max - out_min) // (in_max - in_min) + out_min\n\nangle = 0\nduty = convert(angle, -90, 90, SERVO_MIN_DUTY, SERVO_MAX_DUTY)\nprint('For angle: ', angle, ' the duty is: ', duty)\npwm.duty_u16(duty)\n
"},{"location":"kits/maker-pi-rp2040-robot/08-servo-lab/#checking-your-servo-calibration-with-buttons","title":"Checking your Servo Calibration with Buttons","text":"We can also use the buttons on the Maker Pi RP2040 to verify that the extreme angles are correct. One button will increase the angle and one will decrease the angle.
# Maker Pi RP2040 program to check the limits of a 180 degree servo such as a SG90 micro servo\nfrom machine import Pin, PWM\nimport time\n\nBUTTON_1_PIN = 20 # increment the angle\nBUTTON_2_PIN = 21 # decrement the angle\n\nSERVO_1_PIN = 12\nSERVO_2_PIN = 13 # MAX=5749@40\nSERVO_3_PIN = 14\nSERVO_4_PIN = 15\n# this is ususlly standard across most servos\nSERVO_FREQ_HZ = 40\n\npwm = PWM(Pin(SERVO_2_PIN))\n\n# the two button on the Maker Pi RP2040\nincrement_angle_button_pin = machine.Pin(BUTTON_1_PIN, machine.Pin.IN, machine.Pin.PULL_DOWN)\ndecrement_angle_button_pin = machine.Pin(BUTTON_2_PIN, machine.Pin.IN, machine.Pin.PULL_DOWN)\n\n\n# return int( ( (0.0015*SERVO_FREQ_HZ) + ((angle/90) * (0.0005*SERVO_FREQ_HZ)) ) * 65535 )\n# This will take in integers of range in (min and max) return a integer in the output range (min and max)\n# Used to convert one range of values into another using a linear function like the Arduino map() function\ndef convert(x, in_min, in_max, out_min, out_max):\n return (x - in_min) * (out_max - out_min) // (in_max - in_min) + out_min\n\n# globals\nangle = -90\nlast_time = 0 # the last time we pressed the button\n\n# if the pin is 20 then increment, else decement\ndef button_pressed_handler(pin):\n global angle, last_time\n new_time = time.ticks_ms()\n # if it has been more that 1/5 of a second since the last event, we have a new event\n if (new_time - last_time) > 200:\n # this should be pin.id but it does not work\n if '20' in str(pin):\n angle +=1\n else:\n angle -=1\n last_time = new_time\n # now we register the handler function when the button is pressed\nincrement_angle_button_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\ndecrement_angle_button_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\n\npwm.freq(SERVO_FREQ_HZ)\nold_angle = -1\n\nwhile True:\n # only print on change in the button_presses value\n if angle != old_angle:\n duty = ServoDuty(angle)\n print('new angle:', angle, 'duty: ', duty)\n pwm.duty_u16(duty)\n old_angle = angle\n
"},{"location":"kits/maker-pi-rp2040-robot/08-servo-lab/#sample-sweep-code","title":"Sample Sweep Code","text":"from machine import Pin, PWM\nimport time\n\nBUTTON_1_PIN = 20\nBUTTON_2_PIN = 21\n\nSERVO_1_PIN = 12\nSERVO_2_PIN = 13\nSERVO_3_PIN = 14\nSERVO_4_PIN = 15\nSERVO_FREQ_HZ = 50\nSERVO_MIN_DUTY = 1725\nSERVO_MAX_DUTY = 6378\n# this is ususlly standard across most servos\nSERVO_FREQ_HZ = 40\n\npwm = PWM(Pin(SERVO_2_PIN))\n\n# the two button on the Maker Pi RP2040\nclock_button_pin = machine.Pin(BUTTON_1_PIN, machine.Pin.IN, machine.Pin.PULL_DOWN)\ncounter_clock_button_pin = machine.Pin(BUTTON_2_PIN, machine.Pin.IN, machine.Pin.PULL_DOWN)\n\n# globals\nangle = 90\nlast_time = 0 # the last time we pressed the button\n\ndef button_pressed_handler(pin):\n global angle, last_time\n new_time = time.ticks_ms()\n # if it has been more that 1/5 of a second since the last event, we have a new event\n if (new_time - last_time) > 200:\n # this should be pin.id but it does not work\n if '20' in str(pin):\n angle +=1\n else:\n angle -=1\n # wrap around to first mode\n if mode >= mode_count: mode = 0\n if mode < 0: mode = mode_count - 1\n last_time = new_time\n\n# now we register the handler function when the button is pressed\nclock_button_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\ncounter_clock_button_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler) \n# return int( ( (0.0015*SERVO_FREQ_HZ) + ((angle/90) * (0.0005*SERVO_FREQ_HZ)) ) * 65535 )\n\n# Thisw will take in integers of range in (min and max) return a integer in the output range (min and max)\n# Used to convert one range of values into another using a linear function like the Arduino map() function\ndef convert(x, in_min, in_max, out_min, out_max):\n return (x - in_min) * (out_max - out_min) // (in_max - in_min) + out_min\n\n# -90 should generate 1725\n# 90 should generate 7973\n\nold_angle = -1\n\npwm.freq(50)\nwhile True:\n for angle in range(-90, 90):\n duty = convert(angle, -90, 90, SERVO_MIN_DUTY, SERVO_MAX_DUTY)\n print('angle:', angle, 'duty: ', duty)\n pwm.duty_u16(duty)\n old_angle = angle\n time.sleep(.01)\n for angle in range(90, -90, -1):\n duty = convert(angle, -90, 90, SERVO_MIN_DUTY, SERVO_MAX_DUTY)\n print('angle:', angle, 'duty: ', duty)\n pwm.duty_u16(duty)\n old_angle = angle\n time.sleep(.01)\n
"},{"location":"kits/maker-pi-rp2040-robot/08-servo-lab/#shutting-down-all-servos","title":"Shutting Down All Servos","text":"from machine import Pin, PWM\nimport time\n\nSERVO_1_PIN = 12\nSERVO_2_PIN = 13\nSERVO_3_PIN = 14\nSERVO_4_PIN = 15\n\nprint('shutting down all servos!')\nfor i in range(12, 16):\n print('Servo', i, 'shutting down')\n pwm1 = PWM(Pin(SERVO_1_PIN))\n pwm1.duty_u16(0)\n
"},{"location":"kits/maker-pi-rp2040-robot/08-servo-lab/#adding-cleanup-code","title":"Adding Cleanup Code","text":"PWM signals continue to be generated even after you do a STOP/RESET on your microcontroller. This could drain batteries and wear out your servo motors. To stop the servos from getting PWM signals you can add an interrupt to your code to catch these signals and set the PWM duty cycle back to zero. This
"},{"location":"kits/maker-pi-rp2040-robot/08-servo-lab/#references","title":"References","text":"MicroPython Reference Page - this page is not very helpful. The implication is that servo controls are standardized across MicroPython system. This does not appear to be the case.
"},{"location":"kits/maker-pi-rp2040-robot/09-i2c-scanner-test/","title":"I2C Scanner Test","text":"How do we know that our connection to the distance sensor is wired correctly? The quick way to test this is to run a program called the I2C scanner. It will return a list of all the devices it finds on the I2C bus.
We first run the I2C scanner program to verify that the sensor is connected correct and is responding to the I2C bus scan.
import machine\n# Pins on the Grove Connector 1 on the Maker Pi RP2040 are GP0 and GP1\nsda=machine.Pin(0)\nscl=machine.Pin(1)\ni2c=machine.I2C(0, sda=sda, scl=scl, freq=400000)\nprint(\"I2C device ID list:\", i2c.scan())\n
This should return a list of the devices it finds. If you just have the Time-of-Flight sensor it will look like this:
[41]\n``\n\n```py\ndevice_id = i2c.scan()[0]\n
"},{"location":"kits/maker-pi-rp2040-robot/09-i2c-scanner-test/#testing-for-the-time-of-flight-sensor","title":"Testing for the Time-of-Flight Sensor","text":"import machine\nsda=machine.Pin(0)\nscl=machine.Pin(1)\ni2c=machine.I2C(0, sda=sda, scl=scl, freq=400000)\n\n# i2c.scan() returns a list of devices that have been found\n# i2c.scan()[0] is the first device found\ndevice_id = i2c.scan()[0]\nprint(\"Device found at decimal\", device_id)\n\nif device_id == 41:\n print(\"TEST PASS\")\nelse:\n print(\"No device found at decimal 41\")\n print(\"TEST FAIL\")\n
"},{"location":"kits/maker-pi-rp2040-robot/10-time-of-flight-lab/","title":"Time of Flight Distance Sensor Lab","text":"In this lab we create a program that will show the distance measured by the Time-of-Flight sensor by printing the distance on the console and also displaying the distance on 11 blue LEDs.
First, make sure you have your driver for the Time-of-Flight sensor installed.
You can copy the code from here and save it in the file VL53L0X.py. Note the zero between the \"L\" and \"X\" in the file name, not the letter \"O\".
We use a non-linear distance scale as we get closer to an object. We store the numbers of each LED and the distance it should change in a lists:
blue_led_pins = [2, 3, 4, 5, 6, 7, 16, 17, 26, 27, 28]\ndist_scale = [2, 6, 10, 20, 30, 40, 50, 60, 80, 110, 150]\n
"},{"location":"kits/maker-pi-rp2040-robot/10-time-of-flight-lab/#calibration","title":"Calibration","text":"There are three numbers you can change when you calibrate the sensor:
ZERO_DIST = 60 # The value of the sensor when an object is 0 CM away\nMAX_DIST = 1200 # max raw distance we are able to read\nSCALE_DIST = .3 # multiplier for raw to calibrated distance in CM\n
"},{"location":"kits/maker-pi-rp2040-robot/10-time-of-flight-lab/#full-program","title":"Full Program","text":"# Demo for Maker Pi RP2040 board using the VL32L0X time of flight distance sensor\n# Note the driver I used came from here: https://github.com/CoderDojoTC/micropython/blob/main/src/drivers/VL53L0X.py\n# Perhaps derived from here: https://github.com/uceeatz/VL53L0X/blob/master/VL53L0X.py\n\n# This demo makes the blue LEDs show the distance and prints the distance on the console\nimport machine\nimport time\nimport VL53L0X\n\nsda=machine.Pin(0) # row one on our standard Pico breadboard\nscl=machine.Pin(1) # row two on our standard Pico breadboard\ni2c=machine.I2C(0, sda=sda, scl=scl, freq=400000)\n# print(\"Device found at decimal\", i2c.scan())\n\n# The Maker Pi RP2040 has 13 fantastic blue GPIO status LEDs which we can use 11\n# The distance scale is non linear\n# GP0 and GP1 will always be on since they are the I2C Data and Clock\nblue_led_pins = [2, 3, 4, 5, 6, 7, 16, 17, 26, 27, 28]\ndist_scale = [2, 6, 10, 20, 30, 40, 50, 60, 80, 110, 150]\nnumber_leds = len(blue_led_pins)\nled_ports = []\ndelay = .05\n\n# initial calibration parameters\nZERO_DIST = 60\nMAX_DIST = 1200 # max raw distance we are able to read\nSCALE_DIST = .3 # multiplier for raw to calibrated distance\n\n# create a list of the ports\nfor i in range(number_leds):\n led_ports.append(machine.Pin(blue_led_pins[i], machine.Pin.OUT))\n\n# Create a VL53L0X object\ntof = VL53L0X.VL53L0X(i2c)\n\n\n# get the normalized time-of-flight distance\ndef get_distance():\n global zero_dist, scale_factor\n tof_distance = tof.read()\n if tof_distance > MAX_DIST:\n return tof_distance\n # if our current time-of-flight distance is lower than our zero distance then reset the zero distance\n if tof_distance < ZERO_DIST:\n zero_dist = tof_distance\n return int((tof_distance - ZERO_DIST) * SCALE_DIST)\n\n# use the dist_scale to turn on LEDs\ndef led_show_dist(in_distance):\n global number_leds\n for led_index in range(0, number_leds):\n if in_distance > dist_scale[led_index]:\n led_ports[led_index].high()\n else:\n led_ports[led_index].low()\n\nprint('Using', number_leds, ' blue leds to show distance.')\n\n# blue up\nfor i in range(0, number_leds):\n led_ports[i].high()\n time.sleep(delay)\n led_ports[i].low()\n# blue down\nfor i in range(number_leds - 1, 0, -1):\n led_ports[i].high()\n time.sleep(delay)\n led_ports[i].low()\n\n# start our time-of-flight sensor\ntof.start()\n# autocalibrate the minimum distance\nmin_distance = 1000\n\n\n# loop forever\nwhile True:\n raw_distance = get_distance()\n # recalibrate if we have a new min distance\n if raw_distance < min_distance:\n min_distance = raw_distance\n calibrated_distance = raw_distance - min_distance\n print(raw_distance, calibrated_distance)\n led_show_dist(calibrated_distance)\n time.sleep(0.05)\n\n# clean up\ntof.stop()\n
"},{"location":"kits/maker-pi-rp2040-robot/10-time-of-flight-lab/#references","title":"References","text":"Kevin McAleer's GitHub Repo on the Vl53lx0 Kevin McAleer's 662 line driver - I am not sure we need all 662 lines of code. Kevin McAleer's Time of Flight Test
"},{"location":"kits/maker-pi-rp2040-robot/11-ping-lab/","title":"Ultrasonic Ping Sensor Lab","text":"The Grove sensors on our Maker Pi RP2040 only supply 3.3 volts. So the standard very popular low cost HC-SR04 will not work, since it requires 5 volts of power. We have two options. One is to get a separate 5V power source, but the other is to purchase the new HC-SR04P (for Pico?) sensor that will work with our 3.3 volt power on our Grove connector.
Using the Grove 4 connection wire the HC-SP04P sensor with the trigger on GPIO-16 (White cable) and the echo on GPIO-17 (Yellow cable), VCC (Red cable), and GND (Black cable)
All wired up
# Sample code to test HC-SR04 Ultrasonice Ping Sensor\n# Connect GND to any GND pin on the Pico\n# Connnect VCC to VBUS or 5 Volt power\n\nfrom machine import Pin, Timer\nimport utime\n\nTRIGGER_PIN = 16 # With USB on the top, this pin is the bottom left corner\nECHO_PIN = 17 # One up from bottom left corner\n\n# Init HC-SR04 pins\ntrigger = Pin(TRIGGER_PIN, Pin.OUT) # send trigger out to sensor\necho = Pin(ECHO_PIN, Pin.IN) # get the delay interval back\n\ndef ping():\n trigger.low()\n utime.sleep_us(2) # Wait 2 microseconds low\n trigger.high()\n utime.sleep_us(5) # Stay high for 5 miroseconds\n trigger.low()\n while echo.value() == 0:\n signaloff = utime.ticks_us()\n while echo.value() == 1:\n signalon = utime.ticks_us()\n timepassed = signalon - signaloff\n distance = (timepassed * 0.0343) / 2\n return distance\n\nwhile True:\n print(\"Distance:\", ping(), \"cm\")\n utime.sleep(.25)\n
More advanced version with sound
# Sample code to test HC-SR04 Ultrasonice Ping Sensor\n# Connect GND to any GND pin on the Pico\n# Connnect VCC to VBUS or 5 Volt power\n\nfrom machine import Pin, Timer, PWM\nimport utime\n\nTRIGGER_PIN = 16 # With USB on the top, this pin is the bottom left corner\nECHO_PIN = 17 # One up from bottom left corner\n\n# Init HC-SR04 pins\ntrigger = Pin(TRIGGER_PIN, Pin.OUT) # send trigger out to sensor\necho = Pin(ECHO_PIN, Pin.IN) # get the delay interval back\n\nBUZZER_PORT = 22\nbuzzer = PWM(Pin(BUZZER_PORT))\n\n# Note the non-linear increases in frequency - note that some are louder\ntone_freq = [100, 150, 210, 280, 350, 450, 580, 750, 850, 950, 1000]\ndef playtone(frequency):\n buzzer.duty_u16(1000)\n buzzer.freq(frequency)\n\ndef bequiet():\n buzzer.duty_u16(0)\n\ndef ping():\n trigger.low()\n utime.sleep_us(2) # Wait 2 microseconds low\n trigger.high()\n utime.sleep_us(5) # Stay high for 5 miroseconds\n trigger.low()\n while echo.value() == 0:\n signaloff = utime.ticks_us()\n while echo.value() == 1:\n signalon = utime.ticks_us()\n timepassed = signalon - signaloff\n distance = (timepassed * 0.0343) / 2\n return distance\n\nwhile True:\n dist=round(ping())\n print(\"Distance:\", dist, \"cm\")\n if dist < 20:\n print(\"Panic\")\n playtone(350)\n # Beep faster the closer you get\n utime.sleep(.05/(20/dist))\n bequiet()\n utime.sleep(.1)\n
"},{"location":"kits/maker-pi-rp2040-robot/11-ping-lab/#link-to-sample-ping-lab","title":"Link to Sample Ping Lab","text":"This code is very similar to the previous ping lab but with the different GPIO lines used.
Link to Standard Ping Lab
"},{"location":"kits/maker-pi-rp2040-robot/12-time-of-flight-sound-lab/","title":"Time of Flight Distance Sensor Test","text":"# Demo for Maker Pi RP2040 board\n\nfrom machine import Pin,PWM\nimport time\nimport VL53L0X\nbuzzer=PWM(Pin(22))\n\nsda=machine.Pin(0) # row one on our standard Pico breadboard\nscl=machine.Pin(1) # row two on our standard Pico breadboard\ni2c=machine.I2C(0, sda=sda, scl=scl, freq=400000)\n# print(\"Device found at decimal\", i2c.scan())\n\n# The Maker Pi RP2040 has 13 fantastic blue GPIO status LEDs\nblue_led_pins = [2, 3, 4, 5, 6, 7, 16, 17, 26, 27, 28]\n# dist_scale = [2, 4, 6, 8, 10, 13, 16, 20, 25, 35, 50, 75, 100]\ndist_scale = [2, 4, 6, 8, 10, 15, 20, 25, 50, 100, 150, 200, 300]\n\nnumber_leds = len(blue_led_pins)\nled_ports = []\ndelay = .05\n\n# calibration parameters\nzero_dist = 65 # distance measure when an object is about 1/2 cm away\nmax_dist = 350 # max distance we are able to read\nscale_factor = .2\n\n# create a list of the ports\nfor i in range(number_leds):\n led_ports.append(machine.Pin(blue_led_pins[i], machine.Pin.OUT))\n\n# Create a VL53L0X object\ntof = VL53L0X.VL53L0X(i2c)\n\n# blue up\nfor i in range(0, number_leds):\n led_ports[i].high()\n time.sleep(delay)\n led_ports[i].low()\n# blue down\nfor i in range(number_leds - 1, 0, -1):\n led_ports[i].high()\n time.sleep(delay)\n led_ports[i].low()\n\n# get the normalized time-of-flight distance\ndef get_distance():\n global zero_dist, scale_factor\n tof_distance = tof.read()\n if tof_distance > max_dist:\n return tof_distance\n # if our current time-of-flight distance is lower than our zero distance then reset the zero distance\n if tof_distance < zero_dist:\n zero_dist = tof_distance\n return int((tof_distance - zero_dist) * scale_factor)\n\ndef led_show_dist(in_distance):\n global number_leds\n for led_index in range(0, number_leds):\n if in_distance > dist_scale[led_index]:\n led_ports[led_index].high()\n else:\n led_ports[led_index].low()\n\ndef playtone(frequency):\n buzzer.duty_u16(1000)\n buzzer.freq(frequency)\n\ndef bequiet():\n buzzer.duty_u16(0)\n\ndef play_no_signal():\n playtone(100)\n time.sleep(0.1)\n bequiet()\n\ndef play_turn():\n playtone(500)\n time.sleep(0.1)\n bequiet()\n\n# start our time-of-flight sensor\ntof.start()\nvalid_distance = 1\n\n# loop forever\ndef main():\n while True:\n global valid_distance\n distance = get_distance()\n if distance > 1000:\n # only print if we used to have a valid distance\n if valid_distance == 1:\n print('no signal')\n\n valid_distance = 0\n else:\n print(distance)\n if distance < 30:\n play_turn()\n valid_distance = 1\n led_show_dist(distance)\n time.sleep(0.05)\n\n# clean up\n\n\n# This allows us to stop the sound by doing a Stop or Control-C which is a keyboard intrrup\ntry:\n main()\nexcept KeyboardInterrupt:\n print('Got ctrl-c')\nfinally:\n # Optional cleanup code\n print('turning off sound')\n buzzer.duty_u16(0)\n tof.stop()\n
"},{"location":"kits/maker-pi-rp2040-robot/20-collision-avoidance-robot/","title":"Maker Pi RP2040 Collision Avoidance Robot","text":"This robot works very similar to our standard CoderDojo Collision Avoidance Robot but all the pins are now configured to use the connections on the Maker Pi RP2040 board.
The board is mounted on a SmartCar Chassis and Grove Connector 0 is used to connect to a Time-of-Flight distance sensor that is using the I2C bus.
"},{"location":"kits/maker-pi-rp2040-robot/20-collision-avoidance-robot/#random-turn-direction","title":"Random Turn Direction","text":"if dist < TURN_DIST:\n play_reverse()\n reverse()\n sleep(REVERSE_TIME)\n # half right and half left turns\n if urandom.random() < .5:\n turn_right()\n play_turn_right()\n else:\n turn_left()\n play_turn_left()\n sleep(TURN_TIME)\n forward()\n
# Demo for Maker Pi RP2040 board\n\nfrom machine import Pin,PWM\nfrom time import sleep, sleep_ms\nimport urandom\nimport VL53L0X\n\n# Piezo Buzzer is on GP22\nbuzzer=PWM(Pin(22))\n\n# this is the max power level\nPOWER_LEVEL = 65025\n\n# Motor Pins are A: 8,9 and B: 10,11\nRIGHT_FORWARD_PIN = 8\nRIGHT_REVERSE_PIN = 9\nLEFT_FORWARD_PIN = 11\nLEFT_REVERSE_PIN = 10\n\n# our PWM objects\nright_forward = PWM(Pin(RIGHT_FORWARD_PIN))\nright_reverse = PWM(Pin(RIGHT_REVERSE_PIN))\nleft_forward = PWM(Pin(LEFT_FORWARD_PIN))\nleft_reverse = PWM(Pin(LEFT_REVERSE_PIN))\n\n\ndef turn_motor_on(pwm):\n pwm.duty_u16(65025)\n\ndef turn_motor_off(pwm):\n pwm.duty_u16(0)\n\ndef forward():\n turn_motor_on(right_forward)\n turn_motor_on(left_forward)\n turn_motor_off(right_reverse)\n turn_motor_off(left_reverse)\n\ndef reverse():\n turn_motor_on(right_reverse)\n turn_motor_on(left_reverse)\n turn_motor_off(right_forward)\n turn_motor_off(left_forward)\n\ndef turn_right():\n turn_motor_on(right_forward)\n turn_motor_on(left_reverse)\n turn_motor_off(right_reverse)\n turn_motor_off(left_forward)\n\ndef turn_left():\n turn_motor_on(right_reverse)\n turn_motor_on(left_forward)\n turn_motor_off(right_forward)\n turn_motor_off(left_reverse)\n\ndef stop():\n turn_motor_off(right_forward)\n turn_motor_off(right_reverse)\n turn_motor_off(left_forward)\n turn_motor_off(left_reverse)\n\n# Time of flight sensor is on the I2C bus on Grove connector 0\nsda=machine.Pin(0) # row one on our standard Pico breadboard\nscl=machine.Pin(1) # row two on our standard Pico breadboard\ni2c=machine.I2C(0, sda=sda, scl=scl, freq=400000)\n# print(\"Device found at decimal\", i2c.scan())\n\n# The Maker Pi RP2040 has 13 fantastic blue GPIO status LEDs\nblue_led_pins = [2, 3, 4, 5, 6, 7, 16, 17, 26, 27, 28]\n# dist_scale = [2, 4, 6, 8, 10, 13, 16, 20, 25, 35, 50, 75, 100]\ndist_scale = [2, 4, 6, 8, 10, 15, 20, 25, 50, 100, 150, 200, 300]\n\nnumber_leds = len(blue_led_pins)\nled_ports = []\ndelay = .05\n\n# calibration parameters\nzero_dist = 65 # distance measure when an object is about 1/2 cm away\nmax_dist = 350 # max distance we are able to read\nscale_factor = .2\n\n# create a list of the ports\nfor i in range(number_leds):\n led_ports.append(machine.Pin(blue_led_pins[i], machine.Pin.OUT))\n\n# Create a VL53L0X object\ntof = VL53L0X.VL53L0X(i2c)\n\n# blue up\nfor i in range(0, number_leds):\n led_ports[i].high()\n time.sleep(delay)\n led_ports[i].low()\n# blue down\nfor i in range(number_leds - 1, 0, -1):\n led_ports[i].high()\n time.sleep(delay)\n led_ports[i].low()\n\n# get the normalized time-of-flight distance\ndef get_distance():\n global zero_dist, scale_factor\n tof_distance = tof.read()\n if tof_distance > max_dist:\n return tof_distance\n # if our current time-of-flight distance is lower than our zero distance then reset the zero distance\n if tof_distance < zero_dist:\n zero_dist = tof_distance\n return int((tof_distance - zero_dist) * scale_factor)\n\ndef led_show_dist(in_distance):\n global number_leds\n for led_index in range(0, number_leds):\n if in_distance > dist_scale[led_index]:\n led_ports[led_index].high()\n else:\n led_ports[led_index].low()\n\ndef playtone(frequency):\n buzzer.duty_u16(1000)\n buzzer.freq(frequency)\n\ndef bequiet():\n buzzer.duty_u16(0)\n\ndef play_no_signal():\n playtone(100)\n time.sleep(0.1)\n bequiet()\n\ndef play_turn():\n playtone(500)\n sleep(0.1)\n bequiet()\n\n# start our time-of-flight sensor\ntof.start()\nvalid_distance = 1\n\n# loop forever\ndef main():\n global valid_distance\n while True: \n distance = get_distance()\n if distance > 1000:\n # only print if we used to have a valid distance\n if valid_distance == 1:\n print('no signal') \n valid_distance = 0\n else:\n print(distance)\n if distance < 30:\n play_turn()\n # back up for 1/2 second\n reverse()\n sleep(0.5)\n turn_right()\n sleep(0.75)\n forward()\n else:\n print('forward')\n forward()\n valid_distance = 1\n led_show_dist(distance)\n sleep(0.05)\n\n# clean up\n\n\n# This allows us to stop the sound by doing a Stop or Control-C which is a keyboard intrrup\ntry:\n main()\nexcept KeyboardInterrupt:\n print('Got ctrl-c')\nfinally:\n # Optional cleanup code\n print('turning off sound')\n buzzer.duty_u16(0)\n print('powering down all motors')\n stop()\n print('stopping time of flight sensor')\n tof.stop()\n
"},{"location":"kits/maker-pi-rp2040-robot/21-collision-avoidance-ping/","title":"Maker Pi RP2040 Collision Avoidance Robot With Ping Sensor","text":"This robot works very similar to our standard CoderDojo Collision Avoidance Robot but all the pins are now configured to use the connections on the Maker Pi RP2040 board.
The board is mounted on a SmartCar Chassis and Grove Connector 4 is used to connect the ultrasonic ping sensor. Connect the Trigger on white and Echo on yellow. The black should be connected to GND and the Red is connected to the VCC which on the
The robot has an initial mode of 0, which will run the blue LEDs and change colors on the Neopixels. By pressing the on-board button you will start the collision avoidance program.
"},{"location":"kits/maker-pi-rp2040-robot/21-collision-avoidance-ping/#robot-parameters","title":"Robot Parameters","text":"There are four different robot parameters you can adjust. They change the speed and distance before the robot backs up. You can also adjust the time the robots goes into reverse and the time it turns.
POWER_LEVEL = 35000 # max is \nTURN_DISTANCE = 20 # distance in cm we decide to turn - try 20\nREVERSE_TIME = .4 # how long we backup\nTURN_TIME = .4 # how long we turn\n
"},{"location":"kits/maker-pi-rp2040-robot/21-collision-avoidance-ping/#full-source-code","title":"Full Source Code","text":"# Demo for Maker Pi RP2040 board using Ping sensor\nfrom machine import Pin, PWM, Timer\nimport utime\nimport urandom\nfrom neopixel import Neopixel\n\n# Adjust these parameters to tune the collision avoidance behavior\n\nPOWER_LEVEL = 35000\nTURN_DISTANCE = 20 # distance we decide to turn - try 20\nREVERSE_TIME = .4 # how long we backup\nTURN_TIME = .4 # how long we turn\n\n# startup mode is 0 - motors off and LEDs flashing\n# mode 1 is slow\n# mode 2 is medium\n# mode 3 is fast\nmode = 0\n\n# Use the Grove 4 Connector and put trigger on white and echo on yellow\nTRIGGER_PIN = 16 # With USB on the top, this pin is the bottom left corner\nECHO_PIN = 17 # One up from bottom left corner\n\n# Init HC-SR04P pins\ntrigger = Pin(TRIGGER_PIN, Pin.OUT) # send trigger out to sensor\necho = Pin(ECHO_PIN, Pin.IN) # get the delay interval back\n\nfaster_pin = machine.Pin(20, machine.Pin.IN, machine.Pin.PULL_DOWN)\nslower_pin = machine.Pin(21, machine.Pin.IN, machine.Pin.PULL_DOWN)\n\nlast_time = 0 # the last time we pressed the button\n\n# This function gets called every time the button is pressed. The parameter \"pin\" is not used.\ndef button_pressed_handler(pin):\n global mode, last_time\n new_time = utime.ticks_ms()\n # if it has been more that 1/5 of a second since the last event, we have a new event\n if (new_time - last_time) > 200:\n # this should be pin.id but it does not work\n if '20' in str(pin):\n mode +=1\n else:\n mode -=1\n # deal with ends\n if mode > 4: mode = 2\n if mode < 0: mode = 0\n last_time = new_time\n\n# now we register the handler function when the button is pressed\nfaster_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\nslower_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\n\n# Piezo Buzzer is on GP22\nbuzzer=PWM(Pin(22))\n\nMAX_POWER_LEVEL = 65025\n\nMAX_DISTANCE = 100 # ignore anything above this\n\n# Motor Pins are A: 8,9 and B: 10,11\nRIGHT_FORWARD_PIN = 11\nRIGHT_REVERSE_PIN = 10\nLEFT_FORWARD_PIN = 9\nLEFT_REVERSE_PIN = 8\n\n# our PWM objects\nright_forward = PWM(Pin(RIGHT_FORWARD_PIN))\nright_reverse = PWM(Pin(RIGHT_REVERSE_PIN))\nleft_forward = PWM(Pin(LEFT_FORWARD_PIN))\nleft_reverse = PWM(Pin(LEFT_REVERSE_PIN))\n\n# returns distance in cm\ndef ping():\n print('in ping')\n trigger.low()\n utime.sleep_us(2) # Wait 2 microseconds low\n trigger.high()\n utime.sleep_us(5) # Stay high for 5 miroseconds\n trigger.low()\n while echo.value() == 0:\n signaloff = utime.ticks_us()\n print('echo is 1')\n while echo.value() == 1:\n signalon = utime.ticks_us()\n timepassed = signalon - signaloff\n distance = (timepassed * 0.0343) / 2\n print(distance)\n return int(distance)\n\ndef turn_motor_on(pwm):\n pwm.duty_u16(65025)\n\ndef turn_motor_off(pwm):\n pwm.duty_u16(0)\n\ndef forward():\n turn_motor_on(right_forward)\n turn_motor_on(left_forward)\n turn_motor_off(right_reverse)\n turn_motor_off(left_reverse)\n\ndef reverse():\n turn_motor_on(right_reverse)\n turn_motor_on(left_reverse)\n turn_motor_off(right_forward)\n turn_motor_off(left_forward)\n\ndef turn_right():\n turn_motor_on(right_forward)\n turn_motor_on(left_reverse)\n turn_motor_off(right_reverse)\n turn_motor_off(left_forward)\n\ndef turn_left():\n turn_motor_on(right_reverse)\n turn_motor_on(left_forward)\n turn_motor_off(right_forward)\n turn_motor_off(left_reverse)\n\ndef stop():\n turn_motor_off(right_forward)\n turn_motor_off(right_reverse)\n turn_motor_off(left_forward)\n turn_motor_off(left_reverse)\n\n# The Maker Pi RP2040 has 13 fantastic blue GPIO status LEDs\n# remove 16 and 17 since the are used for the ping sensor\nblue_led_pins = [0, 1, 2, 3, 4, 5, 6, 7, 26, 27, 28]\n# dist_scale = [2, 4, 6, 8, 10, 13, 16, 20, 25, 35, 50, 75, 100]\ndist_scale = [2, 4, 6, 8, 10, 15, 20, 25, 50, 100, 150, 200, 300]\n\nNUMBER_PIXELS = 2\nSTATE_MACHINE = 0\nNEOPIXEL_PIN = 18\n\n# The Neopixels on the Maker Pi RP2040 are the GRB variety, not RGB\nstrip = Neopixel(NUMBER_PIXELS, STATE_MACHINE, NEOPIXEL_PIN, \"GRB\")\nstrip.brightness(100)\n\nnumber_leds = len(blue_led_pins)\nled_ports = []\nred = (255, 0, 0)\norange = (255, 60, 0) # Gamma corrected from G=128 to be less like yellow\nyellow = (255, 150, 0)\ngreen = (0, 255, 0)\nblue = (0, 0, 255)\nindigo = (75, 0, 130) # purple?\nviolet = (138, 43, 226) # mostly pink\ncyan = (0, 255, 255)\nlightgreen = (100, 255, 100)\nwhite = (128, 128, 128) # not too bright\npink = (255, 128, 128)\ncolor_names = ('red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet', 'cyan', 'lightgreen', 'white')\nnum_colors = len(color_names)\ncolors = (red, orange, yellow, green, blue, indigo, violet, cyan, lightgreen, white, pink)\n\n# create a list of the ports\nfor i in range(number_leds):\n led_ports.append(machine.Pin(blue_led_pins[i], machine.Pin.OUT))\n\nLED_DELAY = .08\ndef run_lights():\n for i in range(0, number_leds):\n led_ports[i].high()\n strip.set_pixel(0, colors[i])\n strip.set_pixel(1, colors[i])\n strip.show()\n utime.sleep(LED_DELAY)\n led_ports[i].low()\n # blue down\n for i in range(number_leds - 1, 0, -1):\n led_ports[i].high()\n strip.set_pixel(0, colors[i])\n strip.set_pixel(1, colors[i])\n strip.show()\n utime.sleep(LED_DELAY)\n led_ports[i].low()\n\ndef led_show_dist(in_distance):\n global number_leds\n for led_index in range(0, number_leds):\n if in_distance > dist_scale[led_index]:\n led_ports[led_index].high()\n else:\n led_ports[led_index].low()\n\ndef play_no_signal():\n playnote(100, 0.1)\n sound_off()\n\ndef play_turn():\n playnote(500, .1)\n sound_off()\n\ndef setfreq(frequency):\n buzzer.freq(frequency)\n\ndef playnote(frequency, time):\n buzzer.duty_u16(1000)\n setfreq(frequency)\n utime.sleep(time)\n\ndef sound_off():\n buzzer.duty_u16(0)\n\ndef rest(time):\n buzzer.duty_u16(0)\n utime.sleep(time)\n\ndef play_startup():\n playnote(600, .2)\n rest(.05)\n playnote(600, .2)\n rest(.05)\n playnote(600, .2)\n rest(.1)\n playnote(800, .4)\n sound_off()\n\nvalid_distance = 1\n# loop forever\ndef main():\n global valid_distance\n print(\"running main()\")\n\n play_startup()\n\n while True:\n if mode == 0:\n stop()\n run_lights()\n else:\n distance = ping()\n print('Distance:', distance)\n if distance > MAX_DISTANCE:\n # only print if we used to have a valid distance\n if valid_distance == 1:\n print('no signal') \n valid_distance = 0\n else:\n print(distance)\n if distance < TURN_DISTANCE:\n play_turn()\n # back up for a bit\n reverse()\n utime.sleep(REVERSE_TIME)\n # half right and half left turns\n if urandom.random() < .5:\n turn_right()\n else:\n turn_left()\n utime.sleep(TURN_TIME)\n forward()\n else:\n print('forward')\n forward()\n valid_distance = 1\n led_show_dist(distance)\n utime.sleep(0.05)\n\n# clean up\n\n# This allows us to stop the sound and motors when we do a Stop or Control-C which is a keyboard interrupt\ntry:\n main()\nexcept KeyboardInterrupt:\n print('Got ctrl-c')\nfinally:\n # Optional cleanup code\n print('turning off sound')\n buzzer.duty_u16(0)\n print('shutting motors down')\n stop()\n
"},{"location":"kits/maker-pi-rp2040-robot/21-collision-avoidance-ping/#experiments","title":"Experiments","text":"This robot was inspired by my friend, Michael York.
Microswitches can be purchased for under $1. They can be mounted on the front of our robot. When the robot hits a wall in front of it the switch will open (or close) and the robot controller can make the robot go in reverse or turn.
In the example below, we attached a stiff wire to the lever of the microswitch.
In the example below, we connected three microswitches to the front of our robot.
If the left switch is activated, the robot should turn to the right. If the right switch is activated, the robot should go to the left.
This image shows how we used two of the Grove connectors to read in the values of the switches.
"},{"location":"kits/maker-pi-rp2040-robot/23-microswitch-bot/#testing-switches","title":"Testing Switches","text":"The following code can be used to test your switches. A line on the console prints out which of the three switches are activated using the pin value()
function.
from machine import Pin\nfrom time import sleep\n\n# GPIO is the internal built-in LED\nled0 = Pin(0, Pin.OUT)\nled1 = Pin(1, Pin.OUT)\nled2 = Pin(2, Pin.OUT)\n\n# input on the lower left of the Pico using a built-in pull-down resistor to keep the value from floating\nmiddle_switch = Pin(7, Pin.IN, Pin.PULL_DOWN) \nright_switch = Pin(28, Pin.IN, Pin.PULL_DOWN)\nleft_switch = Pin(27, Pin.IN, Pin.PULL_DOWN)\n\nwhile True:\n if middle_switch.value(): # if the value changes\n led0.on()\n print('middle')\n else: led0.off()\n\n if right_switch.value(): # if the value changes\n led1.on()\n print('right')\n else: led1.off()\n\n if left_switch.value(): # if the value changes\n led2.on()\n print('left')\n else: led2.off()\n sleep(.1)\n
"},{"location":"kits/maker-pi-rp2040-robot/23-microswitch-bot/#sample-collision-avoidance-robot-code","title":"Sample Collision Avoidance Robot Code","text":"from machine import Pin, PWM\nfrom time import sleep\n\n# GPIO is the internal built-in LED\nled0 = Pin(0, Pin.OUT)\nled1 = Pin(1, Pin.OUT)\nled2 = Pin(2, Pin.OUT)\n\n# input on the lower left of the Pico using a built-in pull-down resistor to keep the value from floating\nmiddle_switch = Pin(7, Pin.IN, Pin.PULL_DOWN) \nright_switch = Pin(28, Pin.IN, Pin.PULL_DOWN)\nleft_switch = Pin(27, Pin.IN, Pin.PULL_DOWN)\n\n# Go slow to avoid bending wires\nPOWER_LEVEL = 25000 # max is 65000\n\n# These values depend on motor wiring\nRIGHT_FORWARD_PIN = 10\nRIGHT_REVERSE_PIN = 11\nLEFT_FORWARD_PIN = 9\nLEFT_REVERSE_PIN = 8\n\nright_forward = PWM(Pin(RIGHT_FORWARD_PIN))\nright_reverse = PWM(Pin(RIGHT_REVERSE_PIN))\nleft_forward = PWM(Pin(LEFT_FORWARD_PIN))\nleft_reverse = PWM(Pin(LEFT_REVERSE_PIN))\n\ndef turn_motor_on(pwm):\n pwm.duty_u16(POWER_LEVEL)\n\ndef turn_motor_off(pwm):\n pwm.duty_u16(0)\n\ndef forward():\n turn_motor_on(right_forward)\n turn_motor_on(left_forward)\n turn_motor_off(right_reverse)\n turn_motor_off(left_reverse)\n\ndef reverse():\n turn_motor_on(right_reverse)\n turn_motor_on(left_reverse)\n turn_motor_off(right_forward)\n turn_motor_off(left_forward)\n\ndef turn_right():\n turn_motor_on(right_forward)\n turn_motor_on(left_reverse)\n turn_motor_off(right_reverse)\n turn_motor_off(left_forward)\n\ndef turn_left():\n turn_motor_on(right_reverse)\n turn_motor_on(left_forward)\n turn_motor_off(right_forward)\n turn_motor_off(left_reverse)\n\ndef stop():\n turn_motor_off(right_forward)\n turn_motor_off(right_reverse)\n turn_motor_off(left_forward)\n turn_motor_off(left_reverse)\n\ndef main():\n while True:\n if middle_switch.value(): # if the value changes\n print('middle')\n led0.on()\n reverse()\n sleep(1)\n turn_right()\n sleep(.75)\n forward()\n else:\n led0.off()\n forward()\n\n if right_switch.value(): # if the value changes\n print('right')\n led1.on()\n reverse()\n sleep(.75)\n turn_left()\n sleep(.5)\n forward()\n else:\n led1.off()\n forward()\n\n if left_switch.value(): # if the value changes\n led2.on()\n print('left')\n reverse()\n sleep(.75)\n turn_right()\n sleep(.5)\n forward()\n else:\n led2.off()\n forward()\n\nprint('middle', middle_switch.value())\nprint('left', left_switch.value())\nprint('right', right_switch.value())\n\ntry:\n main()\nexcept KeyboardInterrupt:\n print('Got ctrl-c')\nfinally:\n # Optional cleanup code\n print('turning off sound')\n # sound_off()\n print('turning off motors')\n stop()\n
"},{"location":"kits/maker-pi-rp2040-robot/24-ping-servo-meter/","title":"Cytron Maker Pi RP2040 Ping Servo Meter Bot","text":"This robot works very similar to our standard CoderDojo Collision Avoidance Robot. However it adds a 180 degree servo to show the distance to the object in front of it. It also uses a OLED display to present instructions and display the distance to the object.
This program was contributed by Joe Glenn for the Minneapolis Bakken Museum Droid December event in December 2021.
"},{"location":"kits/maker-pi-rp2040-robot/24-ping-servo-meter/#test-servo","title":"Test Servo","text":"This program tests the servo by sweeping the angle from 0 to 180 and back.
# servo sweep test\n# Brown: GND\n# Orange/Red : VCC\n# Yellow: Signal\n#\n# Time for high level (Radio Shack Micro-servo @ 5V)\n# 0.5 ms : 0 degree\n# 1.0 ms : 45 degree\n# 1.5 ms : 90 degree\n# 2.0 ms : 135 degree\n# 2.5 ms : 180 degree\n\nfrom machine import Pin, PWM\nfrom time import sleep\n\nSERVO_PIN = 15\nservoPin = PWM(Pin(SERVO_PIN))\nservoPin.freq(50)\n\ndef servo(degrees):\n if degrees > 180: degrees=180\n if degrees < 0: degrees=0\n maxDuty=8000 # duty*100\n minDuty=2000 # duty*100\n #maxDuty=2000 # test\n #minDuty=8000 # test\n newDuty=minDuty+(maxDuty-minDuty)*(degrees/180)\n servoPin.duty_u16(int(newDuty))\n\nwhile True:\n\n for degree in range(0,180,1):\n servo(degree)\n sleep(0.01)\n print(\"increasing -- \"+str(degree))\n\n for degree in range(180, 0, -1):\n servo(degree)\n sleep(0.01)\n print(\"decreasing -- \"+str(degree))\n
"},{"location":"kits/maker-pi-rp2040-robot/24-ping-servo-meter/#main-python-code","title":"Main Python Code","text":"# Demo for Maker Pi RP2040 board using Ping sensor, servo and i2c display\n\nfrom machine import Pin, PWM, Timer\nimport utime\nimport urandom\nfrom ssd1306 import SSD1306_I2C\nfrom neopixel import Neopixel\nfrom machine import Pin, I2C\nfrom ssd1306 import SSD1306_I2C\nimport framebuf\nimport math\nimport utime\n#\n# Ping Sensor\n# Use the Grove 4 Connector and put trigger on white and echo on yellow\n#\nPING_TRIGGER_PIN = 7 # GP7\nPING_ECHO_PIN = 28 # GP28\n\n#\n# i2c OLED 128x32\n#\nOLED_SDA_PIN = 26 # GP26\nOLED_SCL_PIN = 27 # GP27\n\n#\n# Servo\n# GND: Brown\n# VCC: Orange/Red\n# GP15 Yellow: Signal\n#\n# Time for high level (Radio Shack Micro-servo @ 5V)\n# 0.5 ms : 0 degree\n# 1.0 ms : 45 degree\n# 1.5 ms : 90 degree\n# 2.0 ms : 135 degree\n# 2.5 ms : 180 degree\nSERVO_PIN = 15\n\n# IQR Pins\nFASTER_PIN = 20\nSLOWER_PIN = 21\n\n# built-in Buzzer\nBUZZER_PIN = 22\n\n# Adjust these parameters to tune the collision avoidance behavior\n\nPOWER_LEL = 35000\nTURN_DISTANCE = 20 # distance we decide to turn - try 20\nREVERSE_TIME = .4 # how long we backup\nTURN_TIME = .4 # how long we turn\n\n# startup mode is 0 - motors off and LEDs flashing\n# mode 1 is slow\n# mode 2 is medium\n# mode 3 is fast\nmode = 0\n\n# Init HC-SR04P pins\ntrigger = Pin(PING_TRIGGER_PIN, Pin.OUT) # send trigger out to sensor\necho = Pin(PING_ECHO_PIN, Pin.IN) # get the delay interval back\n\nfaster_pin = machine.Pin(FASTER_PIN, machine.Pin.IN, machine.Pin.PULL_DOWN)\nslower_pin = machine.Pin(SLOWER_PIN, machine.Pin.IN, machine.Pin.PULL_DOWN)\n\nlast_time = 0 # the last time we pressed the button\n\n#\n# DISPLAY STUFF\n#\n# Display Image & text on I2C driven ssd1306 OLED display \n\n\nWIDTH = 128 # oled display width\nHEIGHT = 32 # oled display height\n\n# Explicit Method\nsda=machine.Pin(OLED_SDA_PIN)\nscl=machine.Pin(OLED_SCL_PIN)\ni2c=machine.I2C(1,sda=sda, scl=scl, freq=40000) # 400k is too fast and has issues\nprint( 'i2c={:02X}'.format( i2c.scan()[0] ) )\n#print(help(i2c))\n#print(help(i2c.init))\n#print(help(i2c.scan))\n#print(help(i2c.start))\n#print(help(i2c.stop))\n#print(help(i2c.readinto))\n#print(help(i2c.write))\n#print(help(i2c.readfrom))\n#print(help(i2c.readfrom_into))\n#print(help(i2c.writeto))\n#print(help(i2c.writevto))\n#print(help(i2c.readfrom_mem))\n#print(help(i2c.readfrom_mem_into))\n#print(help(i2c.writeto_mem))\n#exit\n\noled = SSD1306_I2C(128, 32, i2c)\n\n# Raspberry Pi logo as 32x32 bytearray\nbuffer = bytearray(b\"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00|?\\x00\\x01\\x86@\\x80\\x01\\x01\\x80\\x80\\x01\\x11\\x88\\x80\\x01\\x05\\xa0\\x80\\x00\\x83\\xc1\\x00\\x00C\\xe3\\x00\\x00~\\xfc\\x00\\x00L'\\x00\\x00\\x9c\\x11\\x00\\x00\\xbf\\xfd\\x00\\x00\\xe1\\x87\\x00\\x01\\xc1\\x83\\x80\\x02A\\x82@\\x02A\\x82@\\x02\\xc1\\xc2@\\x02\\xf6>\\xc0\\x01\\xfc=\\x80\\x01\\x18\\x18\\x80\\x01\\x88\\x10\\x80\\x00\\x8c!\\x00\\x00\\x87\\xf1\\x00\\x00\\x7f\\xf6\\x00\\x008\\x1c\\x00\\x00\\x0c \\x00\\x00\\x03\\xc0\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\")\n\n# Load the raspberry pi logo into the framebuffer (the image is 32x32)\nfb = framebuf.FrameBuffer(buffer, 32, 32, framebuf.MONO_HLSB)\n\ndef blk():\n oled.fill(0)\n oled.show()\n\ndef horiz(l,t,r,c): # left, right , top\n n = r-l+1 # Horizontal line\n for i in range(n):\n oled.pixel(l + i, t, c)\n\ndef vert(l,t,b,c): # left, top, bottom\n n = b-t+1 # Vertical line\n for i in range(n):\n oled.pixel(l, t+i,c)\n\ndef box(l,t,r,b,c): # left, top, right, bottom\n horiz(l,t,r,c) # Hollow rectangle\n horiz(l,b,r,c)\n vert(l,t,b,c)\n vert(r,t,b,c)\n\ndef ring2(cx,cy,r,c): # Centre (x,y), radius, colour\n for angle in range(0, 90, 2): # 0 to 90 degrees in 2s\n y3=int(r*math.sin(math.radians(angle)))\n x3=int(r*math.cos(math.radians(angle)))\n oled.pixel(cx-x3,cy+y3,c) # 4 quadrants\n oled.pixel(cx-x3,cy-y3,c)\n oled.pixel(cx+x3,cy+y3,c)\n oled.pixel(cx+x3,cy-y3,c)\n\n#print(help(oled.text()))\n#print(help())\n#help('modules')\n#help(oled)\n#help(oled.text)\n#help(framebuf.FrameBuffer)\n#help(framebuf.FrameBuffer.help())\n\n# Clear the oled display in case it has junk on it.\noled.fill(0) # Black\n\n# Blit the image from the framebuffer to the oled display\noled.blit(fb, 96, 0)\n\n# Basic stuff\noled.text(\"Raspberry Pi\",5,5)\noled.text(\"RP2040\",5,15)\noled.text(\"press GP21\",5,25)\noled.pixel(10,60,1)\n\n#ring2(50,43,20,1) # Empty circle \n# Finally update the oled display so the image & text is displayed\noled.show()\nutime.sleep(1)\n\n\n#\n# Back to the motor control stuff. (sorry... i'm soppy today)\n# \n# This function gets called every time the button is pressed. The parameter \"pin\" is not used.\ndef button_pressed_handler(pin):\n global mode, last_time\n new_time = utime.ticks_ms()\n # if it has been more that 1/5 of a second since the last event, we have a new event\n if (new_time - last_time) > 200:\n # this should be pin.id but it does not work\n if '21' in str(pin):\n mode +=1\n else:\n mode -=1\n # deal with ends\n if mode > 4: mode = 2\n if mode < 0: mode = 0\n last_time = new_time\n\n# now we register the handler function when the button is pressed\nfaster_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\nslower_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\n\n# Piezo Buzzer is on GP22\nbuzzer=PWM(Pin(BUZZER_PIN))\n\nMAX_POWER_LEVEL = 65025\n\nMAX_DISTANCE = 100 # ignore anything above this\n\n# Motor Pins are A: 8,9 and B: 10,11\nRIGHT_FORWARD_PIN = 11 # this must be wired backword?\nRIGHT_REVERSE_PIN = 10 \nLEFT_FORWARD_PIN = 9\nLEFT_REVERSE_PIN = 8\n\n# our PWM objects\nright_forward = PWM(Pin(RIGHT_FORWARD_PIN))\nright_reverse = PWM(Pin(RIGHT_REVERSE_PIN))\nleft_forward = PWM(Pin(LEFT_FORWARD_PIN))\nleft_reverse = PWM(Pin(LEFT_REVERSE_PIN))\n\n# returns distance in cm\ndef ping():\n #print('in ping')\n trigger.low()\n utime.sleep_us(2) # Wait 2 microseconds low\n trigger.high()\n utime.sleep_us(5) # Stay high for 5 miroseconds\n trigger.low()\n while echo.value() == 0:\n signaloff = utime.ticks_us()\n #print('echo is 1')\n while echo.value() == 1:\n signalon = utime.ticks_us()\n timepassed = signalon - signaloff\n distance = (timepassed * 0.0343) / 2\n print(distance)\n\n return int(distance)\n\ndef turn_motor_on(pwm):\n #pwm.duty_u16(65025)\n pwm.duty_u16(16000)\n\ndef turn_motor_off(pwm):\n pwm.duty_u16(0)\n\ndef forward():\n turn_motor_on(right_forward)\n turn_motor_on(left_forward)\n turn_motor_off(right_reverse)\n turn_motor_off(left_reverse)\n\ndef reverse():\n turn_motor_on(right_reverse)\n turn_motor_on(left_reverse)\n turn_motor_off(right_forward)\n turn_motor_off(left_forward)\n\ndef turn_right():\n turn_motor_on(right_forward)\n turn_motor_on(left_reverse)\n turn_motor_off(right_reverse)\n turn_motor_off(left_forward)\n\ndef turn_left():\n turn_motor_on(right_reverse)\n turn_motor_on(left_forward)\n turn_motor_off(right_forward)\n turn_motor_off(left_reverse)\n\ndef stop():\n turn_motor_off(right_forward)\n turn_motor_off(right_reverse)\n turn_motor_off(left_forward)\n turn_motor_off(left_reverse)\n\n# The Maker Pi RP2040 has 13 fantastic blue GPIO status LEDs\nblue_led_pins = [0, 1, 2, 3, 4, 5, 6, 7, 26, 27, 28]\nblue_led_pins.remove(PING_TRIGGER_PIN)\nblue_led_pins.remove(PING_ECHO_PIN)\nblue_led_pins.remove(OLED_SDA_PIN)\nblue_led_pins.remove(OLED_SCL_PIN)\n\n\n# dist_scale = [2, 4, 6, 8, 10, 13, 16, 20, 25, 35, 50, 75, 100]\ndist_scale = [2, 4, 6, 8, 10, 15, 20, 25, 50, 100, 150, 200, 300]\n\nNUMBER_PIXELS = 2\nSTATE_MACHINE = 0\nNEOPIXEL_PIN = 18\n\n# The Neopixels on the Maker Pi RP2040 are the GRB variety, not RGB\nstrip = Neopixel(NUMBER_PIXELS, STATE_MACHINE, NEOPIXEL_PIN, \"GRB\")\nstrip.brightness(100)\n\nnumber_leds = len(blue_led_pins)\nled_ports = []\nred = (255, 0, 0)\norange = (255, 60, 0) # Gamma corrected from G=128 to be less like yellow\nyellow = (255, 150, 0)\ngreen = (0, 255, 0)\nblue = (0, 0, 255)\nindigo = (75, 0, 130) # purple?\nviolet = (138, 43, 226) # mostly pink\ncyan = (0, 255, 255)\nlightgreen = (100, 255, 100)\nwhite = (128, 128, 128) # not too bright\npink = (255, 128, 128)\ncolor_names = ('red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet', 'cyan', 'lightgreen', 'white')\nnum_colors = len(color_names)\ncolors = (red, orange, yellow, green, blue, indigo, violet, cyan, lightgreen, white, pink)\n\n# create a list of the ports\nfor i in range(number_leds):\n led_ports.append(machine.Pin(blue_led_pins[i], machine.Pin.OUT))\n\nLED_DELAY = .08\ndef run_lights():\n for i in range(0, number_leds):\n led_ports[i].high()\n strip.set_pixel(0, colors[i])\n strip.set_pixel(1, colors[i])\n strip.show()\n utime.sleep(LED_DELAY)\n led_ports[i].low()\n # blue down\n for i in range(number_leds - 1, 0, -1):\n led_ports[i].high()\n strip.set_pixel(0, colors[i])\n strip.set_pixel(1, colors[i])\n strip.show()\n utime.sleep(LED_DELAY)\n led_ports[i].low()\n\ndef led_show_dist(in_distance):\n global number_leds\n for led_index in range(0, number_leds):\n if in_distance > dist_scale[led_index]:\n led_ports[led_index].high()\n else:\n led_ports[led_index].low()\n\ndef play_no_signal():\n playnote(100, 0.1)\n sound_off()\n\ndef play_turn():\n playnote(500, .1)\n sound_off()\n\ndef setfreq(frequency):\n buzzer.freq(frequency)\n\ndef playnote(frequency, time):\n buzzer.duty_u16(1000)\n setfreq(frequency)\n utime.sleep(time)\n\ndef sound_off():\n buzzer.duty_u16(0)\n\ndef rest(time):\n buzzer.duty_u16(0)\n utime.sleep(time)\n\ndef play_startup():\n playnote(600, .2)\n rest(.05)\n playnote(600, .2)\n rest(.05)\n playnote(600, .2)\n rest(.1)\n playnote(800, .4)\n sound_off()\n\ndef servo(degrees):\n if degrees > 180: degrees=180\n if degrees < 0: degrees=0\n maxDuty=8000 # duty*100\n minDuty=2000 # duty*100\n #maxDuty=2000 # test\n #minDuty=8000 # test\n newDuty=minDuty+(maxDuty-minDuty)*(degrees/180)\n servoPin.duty_u16(int(newDuty))\n\nservoPin = PWM(Pin(SERVO_PIN))\nservoPin.freq(50)\n\nvalid_distance = 1\n\ndef main():\n global valid_distance\n print(\"running main()\")\n\n play_startup()\n oled_count=0 # repeat every\n oled_count_max=0 # times through loop\n servo_count=0 \n servo_count_max=0 # when to update\n servo_degrees = 0\n servo(servo_degrees) # start in that pos\n\n # loop forever\n while True:\n if mode == 0:\n stop()\n run_lights()\n else:\n distance = ping()\n print('Distance:', distance)\n oled_count += 1\n if oled_count > oled_count_max:\n oled.fill(0) # Black\n oled.text(\"Distance:\",5,5)\n oled.text(\"{:f}\".format(distance),5,15)\n oled.show()\n oled_count = 0\n\n servo_count += 1\n if servo_count > servo_count_max:\n if distance > MAX_DISTANCE:\n servo_degrees = 0\n else:\n servo_degrees = 180-distance/MAX_DISTANCE*180\n servo(servo_degrees)\n servo_count = 0\n\n if distance > MAX_DISTANCE:\n # only print if we used to have a valid distance\n if valid_distance == 1:\n print('no signal') \n valid_distance = 0\n else:\n print(distance)\n #note=distance*10\n #playnote(note, .025)\n\n if distance < TURN_DISTANCE:\n play_turn()\n # back up for a bit\n reverse()\n utime.sleep(REVERSE_TIME)\n # half right and half left turns\n if urandom.random() < .5:\n turn_right()\n else:\n turn_left()\n utime.sleep(TURN_TIME)\n forward()\n else:\n print('forward')\n forward()\n valid_distance = 1\n led_show_dist(distance)\n utime.sleep(0.05)\n\n# clean up\n\n# This allows us to stop the sound and motors when we do a Stop or Control-C which is a keyboard interrupt\ntry:\n main()\nexcept KeyboardInterrupt:\n print('Got ctrl-c')\nexcept Exception as e: print(e)\nfinally:\n # Optional cleanup code\n print('turning off sound')\n buzzer.duty_u16(0)\n print('shutting motors down')\n stop()\n
"},{"location":"kits/maker-pi-rp2040-robot/25-line-follower/","title":"Line Follower Robot","text":"Line following robot projects is a favorite project for our students. They teach the students the concept of feedback to keep a robot following a line on a track or on the floor. They are not easy to get working and require careful calibration of the sensor sensitivity and carefully adjusting the right and left motor power to keep the robot on track. Once the student gets the robot working it is a time for celebration!
The total cost of the kit is about $20.
"},{"location":"kits/maker-pi-rp2040-robot/25-line-follower/#parts-list","title":"Parts List","text":"Part Name Description Price Link Cytron Board Main board with RP2040 and motor driver. Kits come with Grove connectors and a screwdriver. $12 SmartCar Chassis SmartCar chassis with wheels and 4 AA battery pack IR Sensors (2) Low cost IR light sensors $1"},{"location":"kits/maker-pi-rp2040-robot/25-line-follower/#ir-sensors","title":"IR Sensors","text":"We can purchase IR sensors on eBay for about $1 each in quantity 10. The sensors send a digital 0/1 signal to the microcontroller depending on if they are over the line. Our job is to write some MicroPython code to keep the robot following the line. Each IR sensor has a small trim potentiometer that we must adjust for the correct sensitivity for each room.
Each IR sensor has a small trim potentiometer that we must adjust for the correct sensitivity for each room. Some of our rooms have a white floor with a black line and some of our rooms have a dark floor with a white line. You may have to adjust both the trim potentiometer and the logic of the code for different situations.
Use the Grove connectors to hook up the IR sensors. I used the red (VCC), black (GDN) and white wires (Data) and I just cut off the yellow wires to keep them out of the way. I then connected the Grove connectors to ports 2 and 3 of the Cytron board.
I connected the motors to the MOTOR 1 and MOTOR 2 headers with a screwdriver and I hooked the battery wires up to the power header of the board.
from machine import Pin\nfrom utime import sleep\n\nRIGHT_SENSOR_PIN = 2\nLEFT_SENSOR_PIN = 4\n\nright_sensor = Pin(RIGHT_SENSOR_PIN)\nleft_sensor = Pin(LEFT_SENSOR_PIN)\n\nwhile True:\n r = right_sensor.value()\n l = left_sensor.value()\n print(\"r\", r, \"l=\", l)\n if r == 0:\n print(\"right over white\")\n if l == 0:\n print(\"left over white\")\n sleep(.2)\n
"},{"location":"kits/maker-pi-rp2040-robot/25-line-follower/#full-source-code","title":"Full Source Code","text":"from machine import Pin, PWM\nfrom time import sleep\n\n# sensor setup\nRIGHT_SENSOR_PIN = 2\nLEFT_SENSOR_PIN = 4\n\nright_sensor = Pin(RIGHT_SENSOR_PIN)\nleft_sensor = Pin(LEFT_SENSOR_PIN)\n\n# speaker pin on the Cytron Maker Pi RP2040\nSPEAKER_PIN = 22\n# create a Pulse Width Modulation Object on this pin\nspeaker = PWM(Pin(SPEAKER_PIN))\n# set the duty cycle\nspeaker.duty_u16(1000)\n\n# Motor setup\nRIGHT_FORWARD_PIN = 11\nRIGHT_REVERSE_PIN =10\nLEFT_FORWARD_PIN = 8\nLEFT_REVERSE_PIN = 9\n\nright_forward = PWM(Pin(RIGHT_FORWARD_PIN))\nright_reverse = PWM(Pin(RIGHT_REVERSE_PIN))\nleft_forward = PWM(Pin(LEFT_FORWARD_PIN))\nleft_reverse = PWM(Pin(LEFT_REVERSE_PIN))\n\nMAX_POWER_LEVEL = 65025\nQUARTER_POWER = 65025 >> 2\nSLOW_DRIVE_POWER = 16000\nBOOST_LEVEL = 15000\n\n# while True:\ndef spin_wheel(pwm):\n pwm.duty_u16(SLOW_DRIVE_POWER)\n sleep(2)\n pwm.duty_u16(0)\n sleep(1)\n\ndef forward():\n right_forward.duty_u16(SLOW_DRIVE_POWER)\n right_reverse.duty_u16(0)\n left_forward.duty_u16(SLOW_DRIVE_POWER)\n left_reverse.duty_u16(0)\n speaker.duty_u16(0)\n\ndef right():\n right_forward.duty_u16(SLOW_DRIVE_POWER - BOOST_LEVEL)\n right_reverse.duty_u16(0)\n left_forward.duty_u16(SLOW_DRIVE_POWER + BOOST_LEVEL)\n left_reverse.duty_u16(0)\n speaker.duty_u16(1000)\n speaker.freq(800)\n\ndef left():\n right_forward.duty_u16(SLOW_DRIVE_POWER + BOOST_LEVEL)\n right_reverse.duty_u16(0)\n left_forward.duty_u16(SLOW_DRIVE_POWER - BOOST_LEVEL)\n left_reverse.duty_u16(0)\n speaker.duty_u16(1000)\n speaker.freq(1000)\n\ndef stop():\n right_forward.duty_u16(0)\n right_reverse.duty_u16(0)\n left_forward.duty_u16(0)\n left_reverse.duty_u16(0)\n speaker.duty_u16(0)\n\ndef main():\n while True:\n r = right_sensor.value()\n l = left_sensor.value()\n if r == 0 and l == 1:\n print(\"right over white - turning left\")\n right()\n if l == 0:\n print(\"left over white\")\n left()\n else:\n forward()\n\n# end of main()\n\ntry:\n main()\nexcept KeyboardInterrupt:\n print('Got ctrl-c')\nfinally:\n # Cleanup code\n print('Cleaning up')\n print('Powering down all motors now.')\n stop()\n
"},{"location":"kits/neopixel/","title":"NeoPixel Kit","text":"This is a low-cost (under $10) kit that we use in many of our classes. It contains the following parts:
These lessons will assume you have these parts and can connect up to the Pico to your laptop using a USB cable. If you have a Mac with a USB-C connector you may need to provide a USB-C to USB-A adaptor.
"},{"location":"kits/neopixel/#sample-labs","title":"Sample Labs","text":"We start with the sample code in the Basics/NeoPixel area.
We then show how you can load full programs. Here is the source code:
NeoPixel Samples Source Code
"},{"location":"kits/neopixel/#getting-started","title":"Getting Started","text":"Getting Started Lab
"},{"location":"kits/neopixel/#_1","title":"Introduction","text":""},{"location":"kits/neopixel/01-getting-started/","title":"Getting Started with the NeoPixel Kit","text":"Your kit should have several parts. Place them all on a table and hook them up. When you power on the kit with your USB cable the default main.py
program will be running.
Button Basics
"},{"location":"kits/neopixel/03-buttons/#working-with-two-buttons","title":"Working with Two Buttons","text":""},{"location":"kits/neopixel/03-buttons/#changing-color-with-buttons","title":"Changing Color With Buttons","text":""},{"location":"kits/neopixel/03-buttons/#using-modes","title":"Using Modes","text":"Our LED kit can run a single \"main\" loop forever. But we often want to have the main loop run a different subprogram. Each subprogram can be triggered by pressing a button. The button will update a variable and the main loop will use this variable to redirect the main to the appropriate function.
The mode
variable is traditionally used to store the current subprogram being run. You are not limited to a single mode. You can also have modes within modes.
In this lab, we use a rotary encoder to change which pixel is turned on. Turning the knob will appear to move the pixel.
"},{"location":"kits/rfid-rc522/","title":"RFIG Reader RC-522","text":"The RC-522 is a popular RFID reader that has strong support in the MicroPython community.
"},{"location":"kits/rfid-rc522/#pinout","title":"Pinout","text":"NAME PICO GPIO COLOR SDA 1 Yellow SCK 2 Orange MOSI 4 Purple MISO 3 Blue IRQ 7 Brown GND GND Black RST 0 Green 3.3V 3.3v Out Red"},{"location":"kits/rfid-rc522/#wires-at-the-rc522","title":"Wires at the RC522","text":""},{"location":"kits/rfid-rc522/#wires-at-the-pico","title":"Wires at the Pico","text":""},{"location":"kits/rfid-rc522/#config-file","title":"Config File","text":"Place this in the config.py
# reader = MFRC522(spi_id=0, sck=2, miso=4, mosi=3, cs=1, rst=0)\nSPI_ID = 0\nRESET_PIN = 0 # Green OUT\nSDA_PIN = 1 # Yellow OUT but used a Chip Select CS \nSCK_PIN = 2 # Orange OUT clock going from Pico to RC522\nMISO_PIN = 3 # Blue \nMOSI_PIN = 4 # Purple\nIRQ_PIN = 7 # Brown, OUT but not used in the reader demo\n\n# GND is Black\n# Red goes to 3.3v out\n
"},{"location":"kits/rfid-rc522/#reader","title":"Reader","text":"from mfrc522 import MFRC522\nimport utime\n\n\ndef uidToString(uid):\n mystring = \"\"\n for i in uid:\n mystring = \"%02X\" % i + mystring\n return mystring\n\n\nreader = MFRC522(spi_id=0,sck=2,miso=4,mosi=3,cs=1,rst=0)\n\nprint(\"\")\nprint(\"Please place card on reader\")\nprint(\"\")\n\n\n\nPreviousCard = [0]\n\ntry:\n while True:\n\n reader.init()\n (stat, tag_type) = reader.request(reader.REQIDL)\n #print('request stat:',stat,' tag_type:',tag_type)\n if stat == reader.OK:\n (stat, uid) = reader.SelectTagSN()\n if uid == PreviousCard:\n continue\n if stat == reader.OK:\n print(\"Card detected {} uid={}\".format(hex(int.from_bytes(bytes(uid),\"little\",False)).upper(),reader.tohexstring(uid)))\n defaultKey = [255,255,255,255,255,255]\n reader.MFRC522_DumpClassic1K(uid, Start=0, End=64, keyA=defaultKey)\n print(\"Done\")\n PreviousCard = uid\n else:\n pass\n else:\n PreviousCard=[0]\n utime.sleep_ms(50) \n\nexcept KeyboardInterrupt:\n pass\n
"},{"location":"kits/rfid-rc522/#references","title":"References","text":"We are mostly a group of unpaid volunteers without a large budget for distributed content management software. However, we still want to provide a great user experience for both our mentors and our students. So we use free open-source tools whenever we can. Here is how we built this site.
We wish to acknowledge the work of everyone that contributes to open-source projects. Without these systems this work would not be possible.
If you would like to contribute lesson we welcome your contribution as a git pull request. You can read our CoderDojo Twin CitiesAuthoring Guide if you would like to help out.
"},{"location":"misc/contact/","title":"Cybersecurity for Kids Contacts","text":""},{"location":"misc/contact/#general-code-savvy-contact","title":"General Code Savvy Contact","text":"kidscode@codesavvy.org
"},{"location":"misc/contact/#contact-for-coderdojo-twin-cities","title":"Contact for CoderDojo Twin Cities","text":"hello@coderdojotc.org
"},{"location":"misc/contact/#specific-questions-on-this-repository","title":"Specific questions on this repository","text":"Dan McCreary
"},{"location":"misc/contributing/","title":"MicroPython Book Contributing Guide","text":"The goal of this microsite is to provide MicroPython content to STEM educational programs around the world. We value inclusion and we love low-cost projects that promote STEM instruction in communities that don't have large budgets. We value a focus on instructional design that teaches computational thinking and uses evidence of the effective delivery of this content to underprivileged communities.
We welcome anyone that would like to add content to this microsite with the following guidelines:
"},{"location":"misc/contributing/#contribution-license","title":"Contribution License","text":"Under these terms teachers and mentors are free to:
The licensor cannot revoke these freedoms as long as you follow the license terms. Under the following terms:
Attribution \u2014 You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
NonCommercial \u2014 You may not use the material for commercial purposes.
ShareAlike \u2014 If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original.
No additional restrictions \u2014 You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits.
Avoid too much focus on building games that don't promote teaching computational thinking skills. Simple remote control robots might be fun, but sometime the fun of driving a robot around gets in the way of learning new concepts.
"},{"location":"misc/contributing/#lesson-structure","title":"Lesson Structure","text":"Please see our page on Teaching Computational Thinking.
"},{"location":"misc/glossary/","title":"Micropython Glossary of Terms","text":""},{"location":"misc/glossary/#ampy","title":"Ampy","text":"An obsolete MicroPython support tool created by Adafruit but no longer supported.
Check out other MicroPython tools like rshell or mpfshell for more advanced interaction with boards.
"},{"location":"misc/glossary/#analog-to-digital-converter","title":"Analog to Digital Converter","text":"A component that takes an analogue signal and changes it to a digital one.
Every ADC has two parameters, its resolution, measured in digital bits, and its channels, or how many analogue signals it can accept and convert at once.
A special form of copy operation; it copies a rectangular area of pixels from one framebuffer to another. It is used in MicroPython when doing drawing to a display such as an OLED display.
"},{"location":"misc/glossary/#bootsel","title":"BOOTSEL","text":"A button on the pico that when pressed during power up will allow you to mount the device as a USB drive. You can then drag-and-drop any uf2 image file to reset or update the runtime libraries.
Plated through holes or vias located in the edges of a printed circuit board that make it easier to solder onto another circuit board.
The word \"Castellated\" means having grooves or slots on an edge and is derived from the turrets of a castle.
"},{"location":"misc/glossary/#dupont-connectors","title":"Dupont Connectors","text":"Pre-made low-cost used and used to connect breadboards to hardware such as sensors and displays.
The connectors are available in male and female ends and are typically sold in lengths of 10 or 20cm. They have a with a 2.54mm (100mill) pitch so they are easy to align with our standard breadboards. They are typically sold in a ribbon of mixed colors for around $2.00 US for 40 connectors.
A series of low-cost, low-power system on a chip microcontrollers with integrated Wi-Fi and dual-mode Bluetooth.
Typical costs for the ESP32 is are around $10 US on eBay.
The ability to use a simplified syntax to format strings by added the letter \"f\" before the string. Values within curly braces are formatted from variables.
name = \"Lisa\"\nage = 12\nf\"Hello, {name}. You are {age}.\"\n
returns
Hello, Lisa. You are 12.\n
Formatted string support was added to MicroPython in release 1.17. Most formats work except the date and time formats. For these we must write our own formatting functions.
A region of your microcontroller RAM that stores a bitmap image of your display.
For a 128X64 monochrome display this would be 128 * 64 = 8,192 bits or 1,024 bytes (1K). Color displays must store up to 8 bytes per color for each color (red, green and blue).
A type of signal used to pause a program and execute a different program. We use interrupts to pause our program and execute a different program when a button is pressed.
"},{"location":"misc/glossary/#i2c","title":"I2C","text":"A communications protocol common in microcontroller-based systems, particularly for interfacing with sensors, memory devices and liquid crystal displays.
I2C is similar to SPI, it's a synchronous protocol because it uses a clock line.
A set of Python libraries and tools developed specifically for microcontrollers.
MicroPython was originally developed by Damien George and first released in 2014. It includes many of the features of mainstream Python, while adding a range of new ones designed to take advantage of the facilities available on Raspberry Pi Pico and other microcontroller boards like the ESP32.
A simple MicroPython shell based file explorer for ESP8266 and WiPy MicroPython based devices.
The shell is a helper for up/downloading files to the ESP8266 (over serial line and Websockets) and WiPy (serial line and telnet). It basically offers commands to list and upload/download files on the flash FS of the device.
GitHub Repo for MPFShell
"},{"location":"misc/glossary/#oled-display","title":"OLED Display","text":"OLED (Organic polymer light emitting diode) dispays are small but bright displays with high contrast, low power and a wide viewing angle. We use these displays throughout our labs. The small displays are around 1\" (diagonal) and only cost around $4 to $5. Larger 2.24\" displays cost around $20. These displays work both with 4-wire I2C and 7-wire SPI connections.
Typical chips that control the OLED include the SSD1306 driver chips.
The company that builds the Raspberry Pi hardware and provides some software.
"},{"location":"misc/glossary/#raspberry-pi-pico","title":"Raspberry Pi Pico","text":"A microcontroller designed by the Raspberry Pi foundation for doing real-time control systems.
The Pico was introduced in 2020 with a retail list price of $4. It was a key development because it used a custom chip that had 100 times the RAM of an Arduino Nano.
"},{"location":"misc/glossary/#pico-pinout-diagram","title":"Pico Pinout Diagram","text":"The Pico pinout diagram shows you the ways that each Pin can be used. Different colors are used for GPIO numbers, I2C, and SPI interfaces.
A type of output signal used to control items with continuous values. For example, we use PWM to control the brightness of a light or the speed of a motor. We use pulse-width modulation (PWM) to control the speed of our DC motors.
"},{"location":"misc/glossary/#rp2040-chip","title":"RP2040 chip","text":"A custom chip created by the Raspberry Pi Foundation to power the Raspberry Pi Pico.
"},{"location":"misc/glossary/#rshell","title":"rshell","text":"A MicroPython shell that runs on the host and uses MicroPython's raw-REPL to send python snippets to the pyboard in order to get filesystem information, and to copy files to and from MicroPython's filesystem.
It also has the ability to invoke the regular REPL, so rshell can be used as a terminal emulator as well.
Note: With rshell you can disable USB Mass Storage and still copy files into and out of your pyboard.
RShell GitHub Repo
"},{"location":"misc/glossary/#spi","title":"SPI","text":"An interface bus commonly used to send data between microcontrollers and small peripherals such as sensors, displays and SD cards. SPI uses separate clock and data lines, along with a select line to choose the device you wish to talk to.
Also known as: Serial Peripheral Interface See also: I2C
"},{"location":"misc/glossary/#thonny","title":"Thonny","text":"A lightweight Python IDE ideal for writing simple Python programs for first time users.
Thonny runs on Mac, Windows and Linux.
The file that must be uploaded into the Raspberry Pi Pico folder to allow it to be used.
The file name format looks like this:
rp2-pico-20210205-unstable-v1.14-8-g1f800cac3.uf2
MicroPython on Unicorn is completely open source Micropython emulator
MicroPython.org Glossary
"},{"location":"misc/mermaid-test/","title":"Mermaid test","text":"graph LR\n p[Pico] -->|ADC_VREF 36 row=6| pos(Positive)\n p[Pico] -->|AGND 33 row=8| neg(Negative)\n p[Pico] -->|GP26 pin=26 ADC0 31 row=10| tap(Center Tap)\n pos(Positive) --- pot(Potentiometer)\n neg(Negative) --- pot(Potentiometer)\n tap(Center Tap) --- pot(Potentiometer)\n
graph LR\nMyApp --> DB(<font color=white>fa:fa-database MySQL)\nstyle DB fill:#00758f\n
"},{"location":"misc/projects/","title":"CoderDojo MicroPython Projects","text":"Projects are groups lesson plans that need to work together and frequently build on other projects. Key project areas include:
Please connect with Dan McCreary if you are interested in helping out with these projects.
Name Status Description Ping HC-SR04 50%) Create an OLED ping lab Motion Sensor 0% Create a lesson that uses a motion sensor. Use an OLED to display motion vs. time Photosensor 0% Create a lesson that will monitor light on a photo sensor DS18B Temp Sensor 0% Create a lesson that will read and display a waterproof temperature sensor JoyStick 0% Create a lesson that will read the X, Y and button of a JoyStick OLED JoyStick 0% Use a Joystick to move a ball around an OLED and paint pixels OLED Game of Life 0% Write a Game of Life with an OLED. Add a Joystick to paint initial conditions"},{"location":"misc/references/","title":"References for MicroPython","text":""},{"location":"misc/references/#micropython-references","title":"Micropython References","text":"Comparison of RP2040 Boards
"},{"location":"misc/references/#components-references","title":"Components References","text":"Micropython Snake Game
"},{"location":"misc/upip/","title":"MicroPython PIP (UPIP)","text":"MicroPython also has a package manager that can be run directly on the microcontoller.
"},{"location":"misc/upip/#install-upip-from-thonny-package-manager","title":"Install UPIP From Thonny Package Manager","text":""},{"location":"misc/upip/#install-a-package","title":"Install A Package","text":"Connecting to WiFi Network Name: anndan-2.4 Waiting for connection... 1.2.3.4.Connect Time: 4641 Ping the following address: 10.0.0.49 Installing to: /lib/ Warning: micropython.org SSL certificate is not validated Installing micropython-pystone_lowmem 3.4.2.post4 from https://micropython.org/pi/pystone_lowmem/pystone_lowmem-3.4.2.post4.tar.gz
"},{"location":"motors/01-intro/","title":"Using MicroPython to Control Motors and Servos","text":"Controlling motors are one of the funnest ways to learn how to program! They give us quick hand-on feedback on what are programs are doing. Motors are used in almost all our robot project. Robots are used in many STEM courses and coding clubs around the world. Programs like the AI Racing League allow students to learn topics like Machine Learning, Computer Vision and AI. These programs all depend on our understanding of how motors work and how to control them.
"},{"location":"motors/01-intro/#motor-types","title":"Motor Types","text":"There are three types of motors we will learn how to control:
In this lab we will use MicroPython to turn a motor on and off. We will use the digital output from the Raspberry Pi Pico to control a transistor as a switch to control the current to a motor. We will also learn how to use a diode to to protect the transistor from flyback current.
"},{"location":"motors/01-intro/#theory-what-is-an-h-bridge-circuit","title":"Theory: What is an H-Bridge Circuit?","text":"This lab shows a sample circuit with four switches arranged in the shape of the letter \"H\" with the motor at the center of the letter. By closing switches in opposite corners we can make the motor reverse direction.
"},{"location":"motors/01-intro/#lab-2-controlling-a-motor-speed-with-the-l293-h-bridge-chip","title":"Lab 2: Controlling a Motor Speed with the L293 H-Bridge Chip","text":"In this lab we will use a PWM signal to control the speed of a motor.
"},{"location":"motors/01-intro/#lab-3-changing-motor-direction","title":"Lab 3: Changing Motor Direction","text":"In this lab we will make a motor go both forward and backward and change the speed.
"},{"location":"motors/02-transistor/","title":"Using an Transistor to Control a Motor","text":""},{"location":"motors/02-transistor/#power-requirements-for-motors","title":"Power Requirements for Motors","text":"Motors need about 200 milliamps to work. But a microcontroller like the Raspberry Pi Pico only can switch about 18 milliamps. So we need a way to control more power.
The Pico has 26 general purpose input and output pins. However, each pin's power is designed to digitally communicate with other devices and has a limited current capacity of around 17 milliamps according to the Raspberry Pi Pico Datasheet Table 5. The solution is to either use the digital output signal to turn on and off a switch such as a transistor of to use a motor driver chip such as an L293D chip.
"},{"location":"motors/02-transistor/#basic-transistor-circuit","title":"Basic Transistor Circuit","text":"Set the frequency to 50Hz (one cycle per 20ms) and the duty value to between 51 (51/1023 * 20ms = 1ms) and 102 (102/1023 * 20ms = 2ms)
"},{"location":"motors/02-transistor/#sample-coder","title":"Sample Coder","text":"import machine\n\n# set the 7th from the bottom on right as our motor pin\nmotor_pin = machine.Pin(21, machine.Pin.OUT)\n# allocate a PWM object for controlling the motor speed\nmotor_pwm = machine.PWM(motor_pin)\nmotor_pwm.freq(50) # 50 hertz\nmotor_pwm.duty(51)\n
"},{"location":"motors/02-transistor/#references","title":"References","text":"H-Bridge circuits are use to drive a motor both forward and backward. The circuit is called an \"H-Bridge\" because the arrangement of the switches around a motor form the letter \"H\".
"},{"location":"motors/03-h-bridge/#h-bridge-circuit-operation","title":"H-Bridge Circuit Operation","text":"If you connect a 5 volt power supply to a motor you will turn the motor in a specific direction such as clockwise. If you reverse the connections to the motor, the motor will turn the opposite direction such as counter-clockwise.
In order to turn on the motor, switches 1 and 4 must be closed to allow current to flow through the motor. Switches 2 and 3 must be turned off.
To reverse the motor direction you must open switches 1 and 4 and close switches 2 and three in the upper right and lower left portion of the diagram.
"},{"location":"motors/03-h-bridge/#references","title":"References","text":"The L293D motor driver IC has two power input pins viz. \u2018Vcc1\u2019 and \u2018Vcc2\u2019.
Vcc1 is used for driving the internal logic circuitry which should be 5V.
From Vcc2 pin the H-Bridge gets its power for driving the motors which can be 4.5V to 36V. And they both sink to a common ground named GND.
"},{"location":"motors/04-l293d/#sample-program","title":"Sample Program","text":""},{"location":"motors/04-l293d/#references","title":"References","text":"Last Minute Engineer L293D DC Motor Tutorial (Arduino version)
"},{"location":"motors/06-servos/","title":"Controlling a Servo Motor with MicroPython","text":""},{"location":"motors/06-servos/#types-of-servos","title":"Types of Servos","text":"Although there are may types of servos you can purchase, in our labs there are two main types of servos that we use:
There are other variations that have 360 degree or continuous rotation servos.
"},{"location":"motors/06-servos/#servo-connections","title":"Servo Connections","text":"Almost all servos have a three pin connector that are spaced 1/10th of an inch apart so they will work with our breadboards.
Stepper motors are specialized motors that precisely control the angle of rotation of the shaft of a motor. They are often used to carefully position items that move along an axis. For example you can use stepper motors to control the position the printing head of a 3D printer. Stepper motors are also quite a bit more expensive than our DC hobby motors and mini servos, so we don't use them frequently in our classes.
"},{"location":"motors/07-stepper-motors/#sample-code","title":"Sample Code","text":"# Code example from YoungWorks blog on how to use a stepper motor\n# https://www.youngwonks.com/blog/How-to-use-a-stepper-motor-with-the-Raspberry-Pi-Pico\nfrom machine import Pin\nimport utime\n\npins = [\n Pin(15, Pin.Out),\n Pin(14, Pin.Out),\n Pin(16, Pin.Out),\n Pin(17, Pin.Out),\n]\n\n# one hot encoding vectors\nfull_step_sequence = [\n [1.0.0.0],\n [0.1.0.0],\n [0.0.1.0],\n [0.0.0.1]\n]\n\nwhile True:\n for step in full_step_sequence:\n for i in rang(len(pins)):\n pins[i].value(step[i])\n utime.sleep(0.001)\n
"},{"location":"motors/07-stepper-motors/#references","title":"References","text":"This is a box with three potentiometers and a NeoPixel strip. Changing the potentiometers changes the mix of Red, Green and Blue colors.
"},{"location":"projects/01-intro/#alarm-clock","title":"Alarm Clock","text":"https://github.com/wahlencraft/pico-alarm-clock
"},{"location":"projects/02-rgb-box/","title":"Raspberry Pi RGB Box","text":"This is a box with three potentiometers and a NeoPixel strip. Changing the potentiometers changes the mix of Red, Green and Blue colors. We use this at many science fairs or demonstration projects that has kids as young as three years old! As the kids learn to adjust the knobs, we say \"Hey, your a programmer!\".
"},{"location":"projects/02-rgb-box/#related-labs","title":"Related Labs","text":"Before you do this project, it is a good idea to get familiar with the Potentiometer lab. This lab will show you how to hook up a single potentiometer to the Raspberry Pi Pico and read it's values.
"},{"location":"projects/02-rgb-box/#required-tools","title":"Required Tools","text":"Although we will be using a solderless breadboard to connect the components, we use a hot-glue gun to make sure the wires don't get dislocated when the box gets bumped around.
With a bit of clever shopping, you can get the total part costs: under about $15. If you purchase the parts in Quantity 10+ you can get the costs under $10/kit.
"},{"location":"projects/02-rgb-box/#circuit-diagram","title":"Circuit Diagram","text":"TBD
"},{"location":"projects/02-rgb-box/#assembly","title":"Assembly","text":"Solder six-inches of hookup wire to each of the three pins on the three potentiometers.
"},{"location":"projects/02-rgb-box/#sample-code","title":"Sample Code","text":""},{"location":"projects/02-rgb-box/#test-the-neopixel-connection","title":"Test the NeoPixel Connection","text":"Our first step will be to verify we have the NeoPixel strip connected correctly and that we have the right configuration. There are two items you might have to change:
We use two Python variables to configure
NEOPIXEL_PIN = 0\nNUMBER_PIXELS = 10\n
import machine, neopixel\nfrom utime import sleep\nfrom neopixel import Neopixel\n\nNEOPIXEL_PIN = 0\nNUMBER_PIXELS = 10\nstrip = Neopixel(NUMBER_PIXELS, 0, NEOPIXEL_PIN, \"GRB\")\n\nprint('flashing pixel 0 red')\ndelay=.3\nwhile True:\n strip.set_pixel(0, (255,0,0)) // turn pixel 0 red\n strip.show()\n sleep(delay)\n strip.set_pixel(0, (0,0,0)) // turn pixel 0 off\n strip.show()\n sleep(delay)\n
"},{"location":"projects/03-neopixel-strip-two-buttons/","title":"NeoPixel Two Button Kit","text":"This is a low-cost (around $10) kit that is used for hackathons and activities such as a Halloween costume contest.
"},{"location":"projects/03-neopixel-strip-two-buttons/#contents","title":"Contents","text":"See the Basic Example for the NeoPixel Strip lab.
"},{"location":"projects/03-neopixel-strip-two-buttons/#labs","title":"Labs","text":"We supply a small set of \"getting started\" labs to demonstrate how to program colors on the LED strip and give the perception of motion up and down the strip.
"},{"location":"projects/03-neopixel-strip-two-buttons/#blink","title":"Blink","text":""},{"location":"projects/03-neopixel-strip-two-buttons/#move","title":"Move","text":""},{"location":"projects/03-neopixel-strip-two-buttons/#fade-in-and-out","title":"Fade In and Out","text":"from neopixel import NeoPixel\nfrom time import sleep\n\nNUMBER_PIXELS = 60\nLED_PIN = 0\n\nstrip = NeoPixel(machine.Pin(LED_PIN), NUMBER_PIXELS)\n\ndelay = .005\n\nwhile True:\n for i in range(0, 255):\n strip[0] = (i,0,0) # red=255, green and blue are 0\n strip.write() # send the data from RAM down the wire\n sleep(delay) # keep on 1/10 of a second\n for i in range(255, 0, -1):\n strip[0] = (i,0,0) # red=255, green and blue are 0\n strip.write() # send the data from RAM down the wire\n sleep(delay) # keep on 1/10 of a second\n
"},{"location":"projects/03-rotary-neopixel/","title":"Rotary NeoPixel","text":"This project has a rotary encoder and a button. Spinning the rotary encoder changes the pixel index. Pressing the knob of the encoder changes the color. Pressing the button changes the pattern.
"},{"location":"projects/03-rotary-neopixel/#circuit","title":"Circuit","text":"We connect the two ends of the rotary (A and B) to pins 14 and 15. We connect the center pin to the 3.3v rail of the breadboard.
Next, we connect the rotary button and the other button to pins 16 and 17 and to the 3.3v rail.
All the buttons use PULL.DOWN option when they are configured.
"},{"location":"projects/03-rotary-neopixel/#sample-code","title":"Sample Code","text":"We use the rotary.py library
from machine import Pin\nfrom rotary import Rotary\n\nENCODER_A = 15\nENCODER_B = 14\nSWITCH = 17\nrotary = Rotary(ENCODER_A, ENCODER_B, SWITCH)\n
You can change the order of A and B to match the turn direction.
"},{"location":"projects/03-rotary-neopixel/#full-source-code","title":"Full Source Code","text":"from machine import Pin\nfrom rotary import Rotary\nfrom utime import sleep, ticks_ms\nfrom neopixel import NeoPixel\n\nNEOPIXEL_PIN = 0\nNUMBER_PIXELS = 12\n\nstrip = NeoPixel(machine.Pin(NEOPIXEL_PIN), NUMBER_PIXELS)\n\n# GPIO Pins 16 and 17 are for the encoder pins. 18 is the button press switch.\nENCODER_A = 15\nENCODER_B = 14\nSWITCH = 17\nrotary = Rotary(ENCODER_A, ENCODER_B, SWITCH)\n\nbutton_pin = machine.Pin(16, machine.Pin.IN, machine.Pin.PULL_DOWN)\nmode = 0 # mode to display\nmode_names = ['dot', 'hole', 'compare', 'chase', 'rainbow']\n\nbutton_presses = 0 # the count of times the button has been pressed\nlast_time = 0 # the last time we pressed the button\ndef button_pressed_handler(pin):\n global button_presses, last_time, mode\n new_time = ticks_ms()\n # if it has been more that 1/5 of a second since the last event, we have a new event\n if (new_time - last_time) > 200: \n mode +=1\n last_time = new_time\n # make mode 0 or 1\n mode = mode % 5\n print('mode=', mode, mode_names[mode])\n# now we register the handler function when the button is pressed\nbutton_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\n\nval = 0 # value of the LED strip index set by the rotary know\n\nred = (255, 0, 0)\norange = (140, 60, 0)\nyellow = (255, 255, 0)\ngreen = (0, 255, 0)\nblue = (0, 0, 255)\ncyan = (0, 255, 255)\nindigo = (75, 0, 130)\nviolet = (138, 43, 226)\nwhite = (128, 128, 128)\ncolors = (red, orange, yellow, green, blue, cyan, indigo, violet)\ncolor_count = len(colors)\n\n\n# this function is called whenever the rotory is changed\ndef rotary_changed(change):\n global val, button_press, color_index\n if change == Rotary.ROT_CW:\n val = val + 1\n elif change == Rotary.ROT_CCW:\n val = val - 1 \n elif change == Rotary.SW_PRESS:\n print('PRESS')\n # button_press = 1\n elif change == Rotary.SW_RELEASE:\n print('RELEASE')\n color_index += 1\n color_index = color_index % (color_count - 1)\n val = val % NUMBER_PIXELS\n print(val) \n\nrotary.add_handler(rotary_changed)\n\ncolor_index = 0\ncolor = red\nwhile True:\n if mode == 0:\n for i in range(0, NUMBER_PIXELS):\n if i == val:\n strip[i] = color\n else:\n strip[i] = (0,0,0)\n elif mode == 1:\n for i in range(0, NUMBER_PIXELS):\n if i == val:\n strip[i] = (0,0,0)\n else:\n strip[i] = color\n elif mode == 2:\n for i in range(0, NUMBER_PIXELS):\n if i > val:\n strip[i] = (0,0,0)\n else:\n strip[i] = color\n elif mode == 3:\n for i in range(0, NUMBER_PIXELS):\n if (i-val) % 3:\n strip[i] = (0,0,0)\n else:\n strip[i] = color \n elif mode == 4:\n # if the val + offset is larger than the number of pixels we need to do a modulo\n strip[val % (NUMBER_PIXELS)] = violet\n strip[(val+1) % (NUMBER_PIXELS)] = indigo\n strip[(val+2) % (NUMBER_PIXELS)] = blue\n strip[(val+3) % (NUMBER_PIXELS)] = green\n strip[(val+4) % (NUMBER_PIXELS)] = yellow\n strip[(val+5) % (NUMBER_PIXELS)] = orange\n strip[(val+6) % (NUMBER_PIXELS)] = red\n # turn off the rest\n strip[(val+7) % (NUMBER_PIXELS)] = (0,0,0)\n strip[(val+8) % (NUMBER_PIXELS)] = (0,0,0)\n strip[(val+9) % (NUMBER_PIXELS)] = (0,0,0)\n strip[(val+10) % (NUMBER_PIXELS)] = (0,0,0)\n strip[(val+11) % (NUMBER_PIXELS)] = (0,0,0)\n strip.write()\n # print('color index', color_index)\n color = colors[color_index]\n
"},{"location":"robots/","title":"Introduction to MicroPython Robots","text":"Robots are the most powerful learning machines in our curriculum. They allow our students to control motion with their own programs. Not only are they incredibly fun for our students, our students learn computational thinking and enable them to quickly proceed to our advanced AI Racing League projects.
The Raspberry Pi Pico and the Maker Pi RP2040 components have truly been transformative for our clubs. Instead of being trapped in the 2K RAM on our Arduino systems and being forced to learn \"C\", our students are programming in their favorite Python language!
"},{"location":"robots/#robot-journey-map","title":"Robot Journey Map","text":"This section of the course takes you on a tour of our base $25 collision avoidance robots. It then builds on this robot by adding an OLED display, programming controls and servos. Here is a Journey Map of these lessons:
Note that the $25 price assumes you purchase low-cost parts from suppliers like eBay. You can lower the cost per robot by purchasing the parts in higher quantities for classroom use. You can also purchase older Arduino robot kits and upgrade the processors to use the Raspberry Pi Pico.
Note
This section only covers using the Raspberry Pi Pico robots with external motor drivers. We now have many additional robot lessons that use the Cytron Maker Pi RP2040 robotics board. These robots are still low-cost ($20) but have much more integrated power and are easier for younger students to use since they don't require any breadboards or soldering.
Our base robot is a collision avoidance robot that is ideal for teaching beginning robotics principles. The robots have one or more distance sensors on the front and will continue to move forward until they get near an object in front of them. They then will reverse and turn in another direction. We test our robots on the floor in a \"Robot Corral\" that has six-inch high walls. Students can adjust various parameters to allow the robot to navigate around the corral without colliding with the walls.
Base Bot - This is the foundational robot that the other projects are built on. The base includes a standard Smart Car chassis, two DC hobby motors, a battery pack and wheels. On top of the chassis we add a breadboard, jumpers, a motor controller, a distance sensor, and our $4 Raspberry Pi microcontroller.
Rainbow Bot This takes our base robot and adds a low-cost LED strip so that students can change the color of the LED based on what the robot is sensing and doing. For example when the robot is turning right the LEDs can turn red.
IR Sensor Bot This takes our base robot and adds a low-cost LED strip so that students can change the color of the LED based on what the robot is sensing and doing. For example when the robot is turning right the LEDs can turn red.
Face Bot - We extend the Base Bot by adding a $4 128x64 OLED display. This allows students to see the values of the distance sensor and to hear a sound when a key event occurs.
Adjustable Parameter Bot - We extend the face-bot to add some buttons and knobs to allow our users to change the collision avoidance parameters such as forward speed and turning threshold distance.
Our beginning Base Bot
"},{"location":"robots/#chassis","title":"Chassis","text":"SmartCar Chassis
"},{"location":"robots/#sensors","title":"Sensors","text":"This lesson describes our base robot kit in the CoderDojo Twin Cities coding club. This robot is programmed entirely in Python to be consistent with our Python Courses.
"},{"location":"robots/02-base-bot/#base-robot-design","title":"Base Robot Design","text":"Our goal is to build a robotics platform for teaching computational thinking. Here are our main design goals:
Here is a video of the collision avoidance robot in action:
YouTube Video
Note that the forward-speed and distance-before-you-turn can be adjusted. You can see I didn't quite get the distance right and the robot bumps into some of the barriers.
"},{"location":"robots/02-base-bot/#connection-diagram","title":"Connection Diagram","text":"Here is a connection diagram of the base robot.
"},{"location":"robots/02-base-bot/#power-regulation","title":"Power Regulation","text":"Note that the power comes from the battery at 6 volts and is connected to the input voltage of the motor controller board. The motor controller has a voltage regulator that converts any input voltage from 6 to 12 volts down to 5 volts. The output voltage of the motor controller is then connected to the power rail on the left, which is in turn connected to the VSYS input to the Pico. The Pico, in turn, has another voltage regulator that drop the input from VSYS down to 3.3 volts on the 3.3V OUT pin. This voltage is then used to power the distance sensor.
One of the downsides to this design is that as the batteries get low, once they drop below around 5 volts the double voltage drops cause the 3.3 OUT to become too low and the sensor becomes unreliable. A better design would be to find a motor controller that produces a stable 3.3 volts as the batteries slowly run down. Let us know if you can find one of these designs that cost under $2.
"},{"location":"robots/02-base-bot/#hardware-description","title":"Hardware Description","text":"Here is a summary of some of the parts we use in this robot and their approximate prices as of June 2021. Some parts come from China so you might need to wait 2-3 weeks for them to arrive.
Here is a Google sheet with these parts:
Detailed Parts List Google Sheet
"},{"location":"robots/02-base-bot/#two-wheel-drive-smart-car-chassis","title":"Two Wheel Drive Smart Car Chassis","text":"Our cars all use a standard Two Wheel Drive (2WD) SmartCar Chassis that is available in many retail outlets online.
All software is written in MicroPython.
"},{"location":"robots/02-base-bot/#time-of-flight-distance-sensor","title":"Time-of-Flight Distance Sensor","text":"We are using the VL53L0X time-of-flight distance sensor. This works on an I2C bus. After you have hooked up the Power (VCC to the 3.3 rail and GND) you must hook up the I2C data and clock.
sda=machine.Pin(16) # Lower right corner of the Pico with the USB on top\nscl=machine.Pin(17) # One up from the lower right corner of the Pico\ni2c=machine.I2C(0, sda=sda, scl=scl)\n
Many of our older robots used the ultrasonic ping sensors. The are unreliable with voltage drops as our batteries wear down.
"},{"location":"robots/02-base-bot/#testing-the-sensor-connections-with-the-i2c-scanner","title":"Testing the Sensor Connections with the I2C Scanner","text":"import machine\nsda=machine.Pin(16) # Lower right corner of the Pico with the USB on top\nscl=machine.Pin(17) # One up from the lower right corner of the Pico\ni2c=machine.I2C(0, sda=sda, scl=scl)\nprint(\"Device found at decimal\", i2c.scan())\n
You should see a decimal number returned. By default the I2C address is 41 (decimal) or x29 (hexadecimal).
"},{"location":"robots/02-base-bot/#download-the-vl53l0x-driver","title":"Download the VL53L0X Driver","text":"You will need to add a VL53L0X driver file to the file system on the pico.
We have a copy here: https://raw.githubusercontent.com/CoderDojoTC/micropython/main/src/drivers/VL53L0X.py
"},{"location":"robots/02-base-bot/#time-of-flight-sensor-test","title":"Time-of-Flight Sensor Test","text":"Once the driver file is loaded we are ready to test the time-of-flight distance sensor.
import time\nfrom machine import Pin\nfrom machine import I2C\nimport VL53L0X\n\nsda=machine.Pin(16) # row one on our standard Pico breadboard\nscl=machine.Pin(17) # row two on our standard Pico breadboard\ni2c=machine.I2C(0, sda=sda, scl=scl)\n\n# Create a VL53L0X object\ntof = VL53L0X.VL53L0X(i2c)\ntof.start() # startup the sensor\nwhile True:\n# Start ranging\n dist = tof.read()\n print(dist)\n time.sleep(.1)\n
When you run this program a sequence of integers will appear in the console. The numbers usually will range from around 30 if there is an object directly in front of the sensor to a number around 1,300 for a object that is about 1.3 meters away from the sensor. There is a 1/10th of a second pause between each measurement. This can be changed in the last line of the program.
"},{"location":"robots/02-base-bot/#motor-drive-test","title":"Motor Drive Test","text":"After we have the four wires connected to the motor driver, we need to make sure we get the right wires to the right motors and motor directions. This program will help you debug this.
from machine import Pin, PWM\nimport time # sleep\n\nPOWER_LEVEL = 65025\n# lower right pins with USB on top\nRIGHT_FORWARD_PIN = 21\nRIGHT_REVERSE_PIN = 20\nLEFT_FORWARD_PIN = 18\nLEFT_REVERSE_PIN = 19\n\nright_forward = PWM(Pin(RIGHT_FORWARD_PIN))\nright_reverse = PWM(Pin(RIGHT_REVERSE_PIN))\nleft_forward = PWM(Pin(LEFT_FORWARD_PIN))\nleft_reverse = PWM(Pin(LEFT_REVERSE_PIN))\n\n\ndef spin_wheel(pwm):\n pwm.duty_u16(POWER_LEVEL)\n time.sleep(3)\n pwm.duty_u16(0)\n time.sleep(2)\n\nwhile True:\n print('right forward')\n spin_wheel(right_forward)\n\n print('right reverse')\n spin_wheel(right_reverse)\n\n print('left foward')\n spin_wheel(left_forward)\n\n print('left_reverse')\n spin_wheel(left_reverse)\n
One thing to remember is that the \"Right\" refers to our orientation from the rear of the car or if we were sitting inside the car. If the robot is facing you with the sensor in the front, it is the wheel on the left that we call the \"RIGHT\" wheel. Very confusing! Using this naming convention will pay of as we are walking behind larger robots.
"},{"location":"robots/02-base-bot/#sample-drive-and-turn-functions","title":"Sample Drive and Turn Functions","text":"We will need a set of function to drive our robot:
Our challenge is for each of these operations we must change the value of all four PWM signals. We can never have a motor be going both forward and reverse. Here are some sample drive functions:
def turn_motor_on(pwm):\n pwm.duty_u16(POWER_LEVEL)\n\ndef turn_motor_off(pwm):\n pwm.duty_u16(0)\n\ndef forward():\n turn_motor_on(right_forward)\n turn_motor_on(left_forward)\n turn_motor_off(right_reverse)\n turn_motor_off(left_reverse)\n\ndef reverse():\n turn_motor_on(right_reverse)\n turn_motor_on(left_reverse)\n turn_motor_off(right_forward)\n turn_motor_off(left_forward)\n\ndef turn_right():\n turn_motor_on(right_forward)\n turn_motor_on(left_reverse)\n turn_motor_off(right_reverse)\n turn_motor_off(left_forward)\n\ndef turn_left():\n turn_motor_on(right_reverse)\n turn_motor_on(left_forward)\n turn_motor_off(right_forward)\n turn_motor_off(left_reverse)\n\ndef stop():\n turn_motor_off(right_forward)\n turn_motor_off(right_reverse)\n turn_motor_off(left_forward)\n turn_motor_off(left_reverse)\n
"},{"location":"robots/02-base-bot/#turning-logic","title":"Turning Logic","text":"while True:\n dist = read_sensor() \n if dist < TURN_THRESHOLD:\n print('object detected')\n reverse()\n sleep(BACKUP_TIME)\n turn_right()\n sleep(TURN_TIME)\n else:\n forward()\n
"},{"location":"robots/02-base-bot/#test-motor-connections","title":"Test Motor Connections","text":"from machine import Pin, PWM\nimport time # sleep\n\nPOWER_LEVEL = 65025 # usually a number from 30,000 to max of 65,025\n# lower right pins with USB on top\nRIGHT_FORWARD_PIN = 21\nRIGHT_REVERSE_PIN = 20\nLEFT_FORWARD_PIN = 18\nLEFT_REVERSE_PIN = 19\n\nright_forward = PWM(Pin(RIGHT_FORWARD_PIN))\nright_reverse = PWM(Pin(RIGHT_REVERSE_PIN))\nleft_forward = PWM(Pin(LEFT_FORWARD_PIN))\nleft_reverse = PWM(Pin(LEFT_REVERSE_PIN))\n\n\ndef spin_wheel(pwm):\n pwm.duty_u16(POWER_LEVEL)\n time.sleep(3)\n pwm.duty_u16(0)\n time.sleep(2)\n\nwhile True:\n print('right forward')\n spin_wheel(right_forward)\n\n print('right reverse')\n spin_wheel(right_reverse)\n\n print('left foward')\n spin_wheel(left_forward)\n\n print('left_reverse')\n spin_wheel(left_reverse)\n
After you load this program, watch which wheels turn and in what direction.
"},{"location":"robots/02-base-bot/#drive-functions","title":"Drive Functions","text":"We will define Python functions for forward, reverse, turn right and turn left.
POWER_LEVEL = 65025\n\ndef turn_motor_on(pwm):\n pwm.duty_u16(POWER_LEVEL)\n\ndef turn_motor_off(pwm):\n pwm.duty_u16(0)\n\ndef forward():\n turn_motor_on(right_forward)\n turn_motor_on(left_forward)\n turn_motor_off(right_reverse)\n turn_motor_off(left_reverse)\n\ndef reverse():\n turn_motor_on(right_reverse)\n turn_motor_on(left_reverse)\n turn_motor_off(right_forward)\n turn_motor_off(left_forward)\n\ndef turn_right():\n turn_motor_on(right_forward)\n turn_motor_on(left_reverse)\n turn_motor_off(right_reverse)\n turn_motor_off(left_forward)\n\ndef turn_left():\n turn_motor_on(right_reverse)\n turn_motor_on(left_forward)\n turn_motor_off(right_forward)\n turn_motor_off(left_reverse)\n\ndef stop():\n turn_motor_off(right_forward)\n turn_motor_off(right_reverse)\n turn_motor_off(left_forward)\n turn_motor_off(left_reverse)\n
"},{"location":"robots/02-base-bot/#stop-all-motors-program","title":"Stop All Motors Program","text":"One other thing to remember is that the PWM signals continue to be generated even after the main loop has stopped. This is because on the Pico, the four PWM signals are being continuously generated by an independent processors. To stop the motors you must run a separate stop program like this:
stop-all-motors.py:
from machine import Pin, PWM\nfrom time import sleep\n\n# lower right pins with USB on top\nRIGHT_FORWARD_PIN = 19\nRIGHT_REVERSE_PIN = 21\nLEFT_FORWARD_PIN = 18\nLEFT_REVERSE_PIN = 20\n\nright_forward = PWM(Pin(RIGHT_FORWARD_PIN))\nright_reverse = PWM(Pin(RIGHT_REVERSE_PIN))\nleft_forward = PWM(Pin(LEFT_FORWARD_PIN))\nleft_reverse = PWM(Pin(LEFT_REVERSE_PIN))\n\nright_forward.duty_u16(0)\nright_reverse.duty_u16(0)\nleft_forward.duty_u16(0)\nleft_reverse.duty_u16(0)\n
This can be frustrating at times when you can't find the stop program. I like to bring the stop program up in a separate tab when I am writing robot motor code.
To
figure out how to write an interrup handler so that when the IDE STOP function is pressed the stop motors (and speaker) are stopped.
"},{"location":"robots/02-base-bot/#collision-avoidance-logic","title":"Collision Avoidance Logic","text":""},{"location":"robots/02-base-bot/#final-program","title":"Final Program","text":"To get this to work on battery power up you must name the program main.py and save it on the Raspberry Pi Pico.
Note
Make sure you have the VL53L0X distance sensor driver installed.
from machine import Pin, PWM\nfrom utime import sleep\nimport VL53L0X\n\n# used to blink the onboard LED\nled_onboard = machine.Pin(25, machine.Pin.OUT)\n\n# driving parameters\nPOWER_LEVEL = 65025 # use a value from 20000 to 65025\nTURN_THRESHOLD = 400 # 25 cm\nTURN_TIME = .25 # seconds of turning\nBACKUP_TIME = .75 # seconds of backing up if obstacle detected\n\n# Motor pins to the L293 H-Bridge\nRIGHT_FORWARD_PIN = 21\nRIGHT_REVERSE_PIN = 20\nLEFT_FORWARD_PIN = 18\nLEFT_REVERSE_PIN = 19\n\n# setup the PWM objects\nright_forward = PWM(Pin(RIGHT_FORWARD_PIN))\nright_reverse = PWM(Pin(RIGHT_REVERSE_PIN))\nleft_forward = PWM(Pin(LEFT_FORWARD_PIN))\nleft_reverse = PWM(Pin(LEFT_REVERSE_PIN))\n\nsda=machine.Pin(16) # row one on our standard Pico breadboard\nscl=machine.Pin(17) # row two on our standard Pico breadboard\ni2c=machine.I2C(0, sda=sda, scl=scl)\n\n# Create a VL53L0X object\ntof = VL53L0X.VL53L0X(i2c)\n\ndef turn_motor_on(pwm):\n pwm.duty_u16(POWER_LEVEL)\n\ndef turn_motor_off(pwm):\n pwm.duty_u16(0)\n\ndef forward():\n turn_motor_on(right_forward)\n turn_motor_on(left_forward)\n turn_motor_off(right_reverse)\n turn_motor_off(left_reverse)\n\ndef reverse():\n turn_motor_on(right_reverse)\n turn_motor_on(left_reverse)\n turn_motor_off(right_forward)\n turn_motor_off(left_forward)\n\ndef turn_right():\n turn_motor_on(right_forward)\n turn_motor_on(left_reverse)\n turn_motor_off(right_reverse)\n turn_motor_off(left_forward)\n\ndef turn_left():\n turn_motor_on(right_reverse)\n turn_motor_on(left_forward)\n turn_motor_off(right_forward)\n turn_motor_off(left_reverse)\n\ndef stop():\n turn_motor_off(right_forward)\n turn_motor_off(right_reverse)\n turn_motor_off(left_forward)\n turn_motor_off(left_reverse)\n\ndef read_sensor_avg():\n total = 0\n for i in range(10):\n total = total + tof.read()\n sleep(.01)\n return int(total/10)\n\ntof.start() # startup the sensor\n\nwhile True:\n dist = read_sensor_avg();\n print(dist)\n\n if dist < TURN_THRESHOLD:\n print('object detected')\n reverse()\n sleep(BACKUP_TIME)\n led_onboard.high()\n turn_right()\n sleep(TURN_TIME)\n else:\n if dist > 1300:\n print('no signal')\n led_onboard.low()\n else:\n print('Go forward')\n led_onboard.high()\n forward()\n
"},{"location":"robots/02-base-bot/#more-to-explore-labs","title":"More To Explore Labs","text":"Instead of our time-of-flight sensor used in our base robot, this robot uses three low-cost IR distance sensors.
"},{"location":"robots/03-ir-sensor-bot/#purchasing-ir-distance-sensors","title":"Purchasing IR Distance Sensors","text":""},{"location":"robots/03-ir-sensor-bot/#connecting-the-ir-sensors","title":"Connecting the IR Sensors","text":"# connections to the three IR distance sensors\nleft = Pin(8, Pin.IN, Pin.PULL_DOWN)\ncenter = Pin(7, Pin.IN, Pin.PULL_DOWN)\nright = Pin(6, Pin.IN, Pin.PULL_DOWN)\n
"},{"location":"robots/03-ir-sensor-bot/#connecting-the-speaker","title":"Connecting the Speaker","text":"This robot has an optional speaker connected to GPIO Pin 21. This allows us to \"hear\" what signals are coming into the robot It will generate a different tone if the left, center or right sensor is detecting an object and an different tone for going straight, reversing and turning.
The speaker is a small buzzer or a Piezoelectric speaker that can be purchased for around $1. It has one wire connected to the GPIO pin and the other connected to any GND pin or GND rail on the breadboard.
Here are the lines related to setting up the speaker code.
SPEAKER_PIN = 21\n# create a Pulse Width Modulation Object on this pin\nspeaker = PWM(Pin(SPEAKER_PIN))\n
"},{"location":"robots/03-ir-sensor-bot/#drive-logic","title":"Drive Logic","text":"The three IR sensors go LOW if there is an item in front of them. So the statement:
center.value()\n
will normally be HIGH if there is nothing in front of the robot.
Our main logic look will look like the following:
while True:\n if left.value()==0:\n turn_right()\n if center.value()==0:\n reverse()\n if right.value()==0:\n turn_left()\n if left.value() and center.value() and right.value():\n forward()\n
"},{"location":"robots/03-ir-sensor-bot/#full-program","title":"Full Program","text":"from machine import Pin, PWM\nfrom utime import sleep\nimport ssd1306\n\n# Motor pins to the L293 H-Bridge\nRIGHT_FORWARD_PIN = 17\nRIGHT_REVERSE_PIN = 16\nLEFT_FORWARD_PIN = 18\nLEFT_REVERSE_PIN = 19\n\nright_forward = PWM(Pin(RIGHT_FORWARD_PIN))\nright_reverse = PWM(Pin(RIGHT_REVERSE_PIN))\nleft_forward = PWM(Pin(LEFT_FORWARD_PIN))\nleft_reverse = PWM(Pin(LEFT_REVERSE_PIN))\n\n# connections to the three IR distance sensors\nleft = Pin(8, Pin.IN, Pin.PULL_DOWN)\ncenter = Pin(7, Pin.IN, Pin.PULL_DOWN)\nright = Pin(6, Pin.IN, Pin.PULL_DOWN)\n\nSPEAKER_PIN = 21\n# create a Pulse Width Modulation Object on this pin\nspeaker = PWM(Pin(SPEAKER_PIN))\n\nWIDTH = 128\nHEIGHT = 64\nCS = machine.Pin(1)\nSCL = machine.Pin(2)\nSDA = machine.Pin(3)\nDC = machine.Pin(4)\nRES = machine.Pin(5)\nspi=machine.SPI(0, sck=SCL, mosi=SDA)\noled = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)\n\ndef turn_motor_on(pwm):\n pwm.duty_u16(65025)\n\ndef turn_motor_off(pwm):\n pwm.duty_u16(0)\n\ndef forward():\n turn_motor_on(right_forward)\n turn_motor_on(left_forward)\n\ndef reverse():\n turn_motor_on(right_reverse)\n turn_motor_on(left_reverse)\n\ndef turn_right():\n turn_motor_on(right_forward)\n turn_motor_on(left_reverse)\n\ndef turn_left():\n turn_motor_on(right_reverse)\n turn_motor_on(left_forward)\n\ndef sound_off():\n speaker.duty_u16(0)\n\ndef left_tone():\n speaker.duty_u16(1000)\n speaker.freq(700) # 1 Kilohertz\n sleep(.5) # wait a 1/4 second\n sound_off()\n\ndef center_tone():\n speaker.duty_u16(1000)\n speaker.freq(900)\n sleep(.5)\n sound_off()\n\ndef right_tone():\n speaker.duty_u16(1000)\n speaker.freq(600)\n sleep(.5)\n sound_off()\n\ndef forward_tone():\n speaker.duty_u16(1000)\n speaker.freq(400)\n sleep(.1)\n speaker.freq(900)\n sleep(.1)\n speaker.freq(1200)\n sleep(.1)\n sound_off()\n\ndef update_oled():\n oled.fill(0)\n oled.text(\"CoderDojo Rocks!\", 0, 0, 1)\n\n oled.text(\"Left:\", 0, 10, 1)\n oled.text(str(left.value()), 50, 10, 1)\n\n\n oled.text(\"Center:\", 0, 20, 1)\n oled.text(str(center.value()), 60, 20, 1)\n\n oled.text(\"Right:\", 0, 30, 1)\n oled.text(str(right.value()), 55, 30, 1)\n\n BAR_WIDTH = 40\n BAR_HEIGHT = 20\n if left.value():\n oled.fill_rect(WIDTH-40, 50, BAR_WIDTH, BAR_HEIGHT, 0)\n else:\n oled.fill_rect(WIDTH-40, 50, BAR_WIDTH, BAR_HEIGHT, 1)\n\n if center.value():\n oled.fill_rect(50, 50, BAR_WIDTH, BAR_HEIGHT, 0)\n else:\n oled.fill_rect(50, 50, BAR_WIDTH, BAR_HEIGHT, 1)\n\n if right.value():\n oled.fill_rect(0, 50, BAR_WIDTH, BAR_HEIGHT, 0)\n else:\n oled.fill_rect(0, 50, BAR_WIDTH, BAR_HEIGHT, 1)\n\n oled.show()\n\n\n\n# 0=stopped, 1=forward, 2=turing right, 3=turning left\ndrive_state = 0\ncounter = 0\nwhile True:\n if left.value()==0:\n print('Left')\n #left_tone()\n turn_right()\n update_oled()\n drive_state = 2\n if center.value()==0:\n print('Center')\n center_tone()\n reverse()\n update_oled()\n drive_state = 0\n if right.value()==0:\n print('Right')\n #right_tone()\n turn_left()\n update_oled()\n drive_state = 3\n\n # if (left.value()==1) and (center.value()==1) and (right.value()==1):\n if left.value() and center.value() and right.value():\n print('Go forward!') \n drive_state = 1\n # forward_tone()\n forward()\n update_oled()\n print(\"counter: \", counter)\n counter += 1\n sleep(.25)\n
Pins GP6, 7, 8 and 9
"},{"location":"robots/03-rainbow-bot/","title":"Rainbow Bot","text":"This robot takes our base robot and adds an LED strip arranged in a 12X6 pixel grid to display colors and patterns based on what the robot is doing or thinking about.
We use the same materials as our Base Robot but we add a low-cost addressable LED strips that are easy to hook up with just power, ground and data wires added to our breadboard. The LED is known as an addressable LED strip since you can individually program each LED. The standard is called the WS-2812B LED strip and is often called a NeoPixel LED strip (The Adafruit Term). We also used a Python library called a Neopixel micropython library, although the library is not created or maintained by Adafruit.
Of course, you can also add longer LED strips and program the patterns in interesting ways.
"},{"location":"robots/03-rainbow-bot/#part-1-ordering-the-led-strip","title":"Part 1: Ordering The LED Strip","text":"The LED strips come in a variety of lengths, density and packing. We use the 1 meter long strips that have 60 pixels/meter. These strips are easy to cut apart and solder. We like the black backgrounds but they also come with white. The LED strips come with three packaging options:
The waterproofing options tend to be a little more expensive but can also provide a bit more protection for the components on the strips. Waterproofing keeps moisture and dust out of the circuits, but does not mean that they can be submerged under water.
A sample place to purchase them is here
We can take a $3 strip of 60 LEDs and cut them up into six segments of 10 LEDs each for a cost of around 50 cents per strip. We solder stranded wire to the segments and then put 22 gauge solid wire to make them easy to put in the breadboards.
"},{"location":"robots/03-rainbow-bot/#connecting-the-led-strips","title":"Connecting the LED Strips","text":""},{"location":"robots/03-rainbow-bot/#adding-a-standoff","title":"Adding a Standoff","text":""},{"location":"robots/03-rainbow-bot/#upgrading-to-9-volt-power","title":"Upgrading to 9 Volt Power","text":"Our base robot only needed power for the motors. This robot has 72 RGB LEDs so it might draw more power. So we upgraded the 6 volt battery pack with 4 AA batteries to two packs of 3 batteries for a total of 9 volts. This allows the robot to continue to run even when the batteries are partially drained. The battery packs must be wired in series to deliver the full 9 volts to the input of the motor controller where it powers the motors and also runs though a voltage regulator to power the reset of the robot.
"},{"location":"robots/03-rainbow-bot/#72-pixel-configuration","title":"72 Pixel Configuration","text":"Here is the top view of the LEDs shining through the clear plexiglass.
You can see the individual LEDs in this configuration. By adding a small space between the plexiglass and a diffusion layer you can get a much more uniform color distribution over the top surface of the robot.
"},{"location":"robots/03-rainbow-bot/#part-2-making-the-connections","title":"Part 2: Making The Connections","text":"The LED strips use 5 volts of power and have a GND and a data connector. To make the connections we connect the center pin to Pin 0 (upper left corner of the Pico), the GND to the ground rail and the 5 volt to the 5 volt power rail.
"},{"location":"robots/03-rainbow-bot/#part-3-adding-the-neopixel-library","title":"Part 3: Adding the Neopixel Library","text":""},{"location":"robots/03-rainbow-bot/#part-4-testing-your-code","title":"Part 4: Testing Your Code","text":"In our first test, we will just make the first pixel on the LED strip blink bright red.
import machine, neopixel, time\n# Set the pin number and number of pixels\nLED_PIN = machine.Pin(4)\nNUMBER_PIXELS = 12\nnp = neopixel.NeoPixel(LED_PIN, NUMBER_PIXELS)\n\n# blink the first pixel red\n\nwhile True:\n np[0] = (255, 0, 0)\n np.write()\n time.sleep(1)\n np[0] = (0, 0, 0)\n np.write()\n time.sleep(1)\n
"},{"location":"robots/03-rainbow-bot/#functions-for-drawing-on-matrix","title":"Functions For Drawing on Matrix","text":"The numbering of the pixels is a bit odd. The first 12 are 0 to 11, but the second 12 pixels are in reverse order, so the second row counts from 23 down to 13. Here are some functions that demonstrate this:
import time\nfrom neopixel import Neopixel\n\nnumpix = 72\nstrip = Neopixel(numpix, 0, 0, \"GRB\")\n\nred = (255, 0, 0)\norange = (255, 150, 0)\nyellow = (255, 255, 0)\ngreen = (0, 255, 0)\nblue = (0, 0, 255)\nindigo = (75, 0, 130)\nviolet = (138, 43, 226)\ncolors = (red, orange, yellow, green, blue, indigo, violet)\n\nstrip.brightness(255)\n\ndef color_wipe():\n for color in colors:\n for i in range(numpix):\n strip.set_pixel(i, color)\n strip.show()\n time.sleep(0.01)\n\ndef color_wipe_2():\n for color in colors:\n for i in range(12):\n strip.set_pixel(i, color)\n strip.set_pixel(i+12, color)\n strip.set_pixel(i+24, color)\n strip.set_pixel(i+36, color)\n strip.set_pixel(i+48, color)\n strip.set_pixel(i+60, color)\n strip.show()\n time.sleep(0.01)\n\ndef color_wipe_3():\n for color in colors:\n for i in range(12):\n strip.set_pixel(i, color)\n strip.set_pixel(23-i, color)\n strip.set_pixel(i+24, color)\n strip.set_pixel(47-i, color)\n strip.set_pixel(48+i, color)\n strip.set_pixel(71-i, color)\n strip.show()\n time.sleep(0.3)\n\n# offset is the color to start (0 to 6)\n# dir is 1 for forward and -1 for reverse\ndef color_wipe_4(offset, dir):\n for i in range(12):\n if dir == 1:\n this_color = colors[ ((i-offset) %7 )]\n else:\n this_color = colors[ ((i+offset) %7 )]\n strip.set_pixel(i, this_color)\n strip.set_pixel(23-i, this_color)\n strip.set_pixel(i+24, this_color)\n strip.set_pixel(47-i, this_color)\n strip.set_pixel(48+i, this_color)\n strip.set_pixel(71-i, this_color)\n strip.show()\n # time.sleep(0.01)\n\nwhile True:\n for counter in range(100):\n color_wipe_4(counter %7, 1)\n for counter in range(100):\n color_wipe_4(counter%7, -1) \n
"},{"location":"robots/03-rainbow-bot/#full-source-code","title":"Full Source Code","text":"We now combine the motor controls, the distance sensor and the LED functions so that a moving rainbow pattern moves from to back as the robot moves forward. If the robot encounters an obstacle, the robot will backup and change the direction of the rainbow. After it backs up a bit it will turn and move forward again.
main.py
from machine import Pin, PWM\nfrom time import sleep\nfrom machine import Pin\nfrom machine import I2C\nimport VL53L0X\nfrom neopixel import Neopixel\n\n# Motor Code\n# lower right pins with USB on top\nRIGHT_FORWARD_PIN = 19\nRIGHT_REVERSE_PIN = 18\nLEFT_FORWARD_PIN = 20\nLEFT_REVERSE_PIN = 21\n\nright_forward = PWM(Pin(RIGHT_FORWARD_PIN))\nright_reverse = PWM(Pin(RIGHT_REVERSE_PIN))\nleft_forward = PWM(Pin(LEFT_FORWARD_PIN))\nleft_reverse = PWM(Pin(LEFT_REVERSE_PIN))\n\n# Sensor Code\nsda=machine.Pin(16)\nscl=machine.Pin(17)\ni2c=machine.I2C(0, sda=sda, scl=scl)\n\n# Create a VL53L0X object\ntof = VL53L0X.VL53L0X(i2c)\ntof.start() # startup the sensor\n\n# used to blink the onboard LED\nled_onboard = machine.Pin(25, machine.Pin.OUT)\n\n# LED Code\nnumpix = 72\nstrip = Neopixel(numpix, 0, 0, \"GRB\")\n# we turn the brightness way down to not oversaturate the brightness in the video\nstrip.brightness(20)\n\n# driving parameters\nPOWER_LEVEL = 30000 # use a value from 20000 to 65025\nTURN_THRESHOLD = 400 # 25 cm\nTURN_TIME = .25 # seconds of turning\nBACKUP_TIME = .75 # seconds of backing up if obstacle detected\n\nred = (255, 0, 0)\norange = (255, 165, 0)\nyellow = (255, 255, 0)\ngreen = (0, 255, 0)\nblue = (0, 0, 255)\nindigo = (75, 0, 130)\nviolet = (138, 43, 226)\ncolors = (red, orange, yellow, green, blue, indigo, violet)\n\ndef turn_motor_on(pwm):\n pwm.duty_u16(POWER_LEVEL)\n\ndef turn_motor_off(pwm):\n pwm.duty_u16(0)\n\ndef forward():\n turn_motor_on(right_forward)\n turn_motor_on(left_forward)\n turn_motor_off(right_reverse)\n turn_motor_off(left_reverse)\n #for i in range(numpix):\n # strip.set_pixel(i, green)\n #strip.show()\n\ndef reverse():\n turn_motor_on(right_reverse)\n turn_motor_on(left_reverse)\n turn_motor_off(right_forward)\n turn_motor_off(left_forward)\n #for i in range(numpix):\n # strip.set_pixel(i, red)\n #strip.show()\n\ndef turn_right():\n turn_motor_on(right_forward)\n turn_motor_on(left_reverse)\n turn_motor_off(right_reverse)\n turn_motor_off(left_forward)\n #for i in range(numpix):\n # strip.set_pixel(i, blue)\n #strip.show()\n\ndef turn_left():\n turn_motor_on(right_reverse)\n turn_motor_on(left_forward)\n turn_motor_off(right_forward)\n turn_motor_off(left_reverse)\n #for i in range(numpix):\n # strip.set_pixel(i, yellow)\n #strip.show()\n\ndef stop():\n turn_motor_off(right_forward)\n turn_motor_off(right_reverse)\n turn_motor_off(left_forward)\n turn_motor_off(left_reverse)\n for i in range(numpix):\n strip.set_pixel(i, violet)\n strip.show()\n\ndef read_sensor_avg():\n total = 0\n for i in range(10):\n total = total + tof.read()\n sleep(.01)\n return int(total/10)\n\n# offset is the color to start (0 to 6)\n# dir is 1 for forward and -1 for reverse\ndef color_wipe_4(offset, dir):\n for i in range(12):\n if dir == 1:\n this_color = colors[ ((i-offset) %7 )]\n else:\n this_color = colors[ ((i+offset) %7 )]\n strip.set_pixel(i, this_color)\n strip.set_pixel(23-i, this_color)\n strip.set_pixel(i+24, this_color)\n strip.set_pixel(47-i, this_color)\n strip.set_pixel(48+i, this_color)\n strip.set_pixel(71-i, this_color)\n strip.show()\n # time.sleep(0.01)\n\ncounter = 0\nwhile True:\n dist = read_sensor_avg() \n if dist < TURN_THRESHOLD:\n print('object detected')\n reverse()\n\n color_wipe_4(counter % 7, -1)\n sleep(.1)\n counter += 1\n\n color_wipe_4(counter % 7, -1)\n sleep(.1)\n counter += 1\n\n color_wipe_4(counter % 7, -1)\n sleep(.1)\n counter += 1 \n\n color_wipe_4(counter % 7, -1)\n sleep(.1)\n counter += 1 \n\n color_wipe_4(counter % 7, -1)\n sleep(.1)\n counter += 1 \n\n turn_right()\n color_wipe_4(counter % 7, -1)\n sleep(.1)\n counter += 1 \n\n color_wipe_4(counter % 7, -1)\n sleep(.1)\n counter += 1 \n\n color_wipe_4(counter % 7, -1)\n sleep(.1)\n counter += 1 \n\n else:\n forward()\n color_wipe_4(counter % 7, 1)\n counter += 1\n
Rainbow Bot Source Code
"},{"location":"robots/03-rainbow-bot/#references","title":"References","text":"This lesson will allow you to draw simple faces on the display on the front of our robots. The faces can reflect the emotions of your robot. We are inspired by the faces on the
"},{"location":"robots/05-ajusta-bot/","title":"Adjusta Bot","text":"This robot uses buttons and a rotary encoder to allow the user to reprogram the Pico and adjust the collision avoidance parameters.
"},{"location":"robots/20-faqs/","title":"Robots Frequently Asked Questions","text":""},{"location":"robots/20-faqs/#why-a-two-motor-three-wheel-robot-not-four-motor-four-wheel-robot","title":"Why a two-motor three wheel robot, not four motor four wheel robot?","text":"We evaluated both the two-motor three wheel robot and the four motor four wheel robots and found that we could achieve all our learning objectives with the two-motor three wheel version. The kits are lower cost, have a simpler assembly process and have plenty of power for our projects.
"},{"location":"robots/20-faqs/#why-do-we-use-the-l293d-vs-l298n-motor-driver","title":"Why do we use the L293D vs L298N Motor Driver?","text":"Both of these popular motor driver chips are commonly used in robot kits. Our focus is teaching the principals of computational thinking using low-cost parts. Since the L293D is lower cost and has plenty of power for our two-motor robots we chose that. There is a detailed side-by-side comparison [here] (https://www.etechnophiles.com/l293d-vs-l298n-motor-driver-differences-specifications-and-pinouts/)
"},{"location":"robots/20-faqs/#why-dont-we-just-create-a-remote-control-robot-car","title":"Why don't we just create a remote control robot car?","text":"Remote control cars are very fun to play with. But we find that it often detracts from our mission of learning how to code. So we try to minimize the time students spend just driving their cars around our tracks.
"},{"location":"sensors/01-intro/","title":"Sensors in MicroPython","text":""},{"location":"sensors/01-intro/#light-sensor","title":"Light Sensor","text":""},{"location":"sensors/01-intro/#ping","title":"Ping","text":""},{"location":"sensors/01-intro/#temperature","title":"Temperature","text":""},{"location":"sensors/02-photosensor/","title":"Light Sensor with Raspberry Pi Pico in MicroPython","text":"A photoresistor is a sensor that decreases resistance when light is shined on its surface. With no light a photoresistor has high resistance in the range of megaohms. As light shines on the surface the resistance drops to kiloohms. We can use this effect as a light sensor.
To convert the variable resistance of a photoresistor to something we can measure with our microcontroller we will need to build a small circuit that includes a 10K ohm resistor. We then measure the voltage between the photoresistor and the 10K resistor as light falls on the sensor. The top and bottom of the circuit are tied to ground and a power rail. This will move the voltage of the midpoint of the circuit.
"},{"location":"sensors/02-photosensor/#circuit","title":"Circuit","text":"We want to make sure that we use the stable analog ground (AGND) and analog to reference voltage at either end of the circuit to protect the circuit from all the noise of the power in our processor. Using other power and ground pins will work, but power fluctuations will make the result noisy.
"},{"location":"sensors/02-photosensor/#sample-code","title":"Sample Code","text":"Our program will first use the Analog to Digital Circuit (ADC0) as an input. On the Pico this is on pin 26.
import machine\nimport time\nphoto_pin = machine.ADC(26)\n\nwhile True:\n val = photo_pin.read_u16()\n print(val)\n time.sleep(.2)\n
When you run the program a series of print values is displayed in the shell every 1/5th of a second. You can also use the Thonny plot window to see how the numbers change and you cover and uncover detector from a light source.
"},{"location":"sensors/02-photosensor/#experiments","title":"Experiments","text":"Video Demo
In this lab we will hook up a Potentiometer to the Analog to Digital Converter of the Raspberry Pi Pico.
"},{"location":"sensors/02-pot/#connections","title":"Connections","text":"Potentiometers have three wires:
The rails are hooked up to fixed reference voltages. As you turn the know, the center tap returns a value that varies between the ground and the positive rail voltages.
On many microcontrollers, the positive voltage is very \"noisy\" in that it contains voltage that varies as the components draw power. The Raspberry Pi has a special region for high-quality data gathering that is isolated from this noise. To use this section you MUST hook the rails to the right pins.
ADC_VREF is the ADC power supply (and reference) voltage, and is generated on Pico by filtering the 3.3V supply. This pin can be used with an external reference if better ADC performance is required.
AGND is the ground reference for GPIO26-29, there is a separate analog ground plane running under these signals and terminating at this pin.
<!-- ->
graph LR\np[Pico]-->|ADC_VREF 36 row=6| pos(Positive)\np[Pico]-->|AGND 33 row=8| neg(Negative)\np[Pico]-->|GP26 pin=26 ADC0 31 row=10| tap(Center Tap)\n pos(Positive) --- pot(Potentiometer)\n neg(Negative) --- pot(Potentiometer)\n tap(Center Tap) --- pot(Potentiometer)\n
Connect the positive to pin 35 ADC_REF (row 6 on the breadboard) and the negative to pin 33 AGND (row 8 on the breadboard). The Pico has special noise reduction circuits to avoid power supply jitter on these reference pins.
"},{"location":"sensors/02-pot/#sampling-data","title":"Sampling data","text":"Sometimes the data coming from your Potentiometer is noisy. You can sample the value multiple times and then average the values.
Here is a sample program. Just pass in the pin and a count and it will return the average values. This version waits 5 milliseconds between samples.
def sample_pot(pin, count):\n total = 0\n for i in range(count):\n total += int(pin.read_u16())\n utime.sleep_ms(5)\n return int(total / count)\n
pot_pin_1 = machine.ADC(26)\n# return a value after sampling 10 times\nsample_pot(pot_pin_1, 10)\n
"},{"location":"sensors/03-ping/","title":"Ultrasonic Ping Sensor","text":"The HC-SR04 is a low cost ($4) sensor that measures the distance to an object in front of it.
"},{"location":"sensors/03-ping/#wiring-diagram","title":"Wiring Diagram","text":"# Sample code to test HC-SR04 Ultrasonice Ping Sensor\n# Connect GND to any GND pin on the Pico\n# Connnect VCC to VBUS or 5 Volt power\n\nfrom machine import Pin, Timer\nimport utime\n\nTRIGGER_PIN = 15 # With USB on the top, this pin is the bottom left corner\nECHO_PIN = 14 # One up from bottom left corner\n\n# Init HC-SR04 pins\ntrigger = Pin(TRIGGER_PIN, Pin.OUT) # send trigger out to sensor\necho = Pin(ECHO_PIN, Pin.IN) # get the delay interval back\n\ndef ping():\n trigger.low()\n utime.sleep_us(2) # Wait 2 microseconds low\n trigger.high()\n utime.sleep_us(5) # Stay high for 5 miroseconds\n trigger.low()\n while echo.value() == 0:\n signaloff = utime.ticks_us()\n while echo.value() == 1:\n signalon = utime.ticks_us()\n timepassed = signalon - signaloff\n distance = (timepassed * 0.0343) / 2\n return distance\n\nwhile True:\n print(\"Distance:\", ping(), \" cm\")\n utime.sleep(.25)\n
"},{"location":"sensors/03-ping/#oled","title":"OLED","text":"If you have a small OLED screen, you can also display the results of the distance measurement directly on an OLED screen.
See the OLED example here: OLED Ping Example
"},{"location":"sensors/04-temp-dht11/","title":"Sensing Temperature and Humidity with the DHT11 Sensor","text":""},{"location":"sensors/04-temp-dht11/#sample-code","title":"Sample Code","text":"from machine import Pin\nimport utime as time\nfrom dht import DHT11, InvalidChecksum\n\nDHT_PIN = 15\n# this is a bit odd, since although it is an input, we need a pull down set\ndhtSensor = DHT11(Pin(DHT_PIN, Pin.OUT, Pin.PULL_DOWN))\n\nwhile True:\n temp = dhtSensor.temperature\n humidity = dhtSensor.humidity/100\n print(\"Temp: {}\u00b0C\".format(temp), \"| Hum: {0:.1%}\".format(humidity))\n
"},{"location":"sensors/04-temp-dht11/#references","title":"References","text":"https://randomnerdtutorials.com/micropython-ds18b20-esp32-esp8266/
```py
"},{"location":"sensors/05-temp-dsb20/#complete-project-details-at-httpsrandomnerdtutorialscom","title":"Complete project details at https://RandomNerdTutorials.com","text":"import machine, onewire, ds18x20, time
ds_pin = machine.Pin(4) ds_sensor = ds18x20.DS18X20(onewire.OneWire(ds_pin))
roms = ds_sensor.scan() print('Found DS devices: ', roms)
while True: ds_sensor.convert_temp() time.sleep_ms(750) for rom in roms: print(rom) print(ds_sensor.read_temp(rom)) time.sleep(5) ```
"},{"location":"sensors/06-accelerometer-compass/","title":"MPU-9250 Accelerometer Gyroscope Compass","text":"The MPU-9250 by InvenSense is a nine-axis motion tracking device. It includes:
(Gyro + Accelerometer + Compass) MEMS MotionTracking\u2122 Device
1PCS GY-91 10DOF Accelerometer Gyroscope Compass Temp/Pressure MPU-9250 BMP-280
You only need to hook the 3.3 to VIN, the GND to GND and the SCL and SDA. The other connections are not needed.
"},{"location":"sensors/06-accelerometer-compass/#i2c-scanner-results","title":"I2C Scanner Results","text":"import machine\nsda=machine.Pin(16) # row one on our standard Pico breadboard\nscl=machine.Pin(17) # row two on our standard Pico breadboard\ni2c=machine.I2C(0, sda=sda, scl=scl, freq=400000)\nprint(\"Device found at decimal\", i2c.scan())\n
Device found at decimal [104, 118]
"},{"location":"sensors/06-accelerometer-compass/#mpu9250-drivers","title":"MPU9250 Drivers","text":"MicroPython Driver
import utime\nfrom machine import I2C, Pin\nfrom mpu9250 import MPU9250\n\ni2c = I2C(scl=Pin(22), sda=Pin(21))\nsensor = MPU9250(i2c)\n\nprint(\"MPU9250 id: \" + hex(sensor.whoami))\n\nwhile True:\n print(sensor.acceleration)\n print(sensor.gyro)\n print(sensor.magnetic)\n print(sensor.temperature)\n\n utime.sleep_ms(1000)\n
"},{"location":"sensors/06-accelerometer-compass/#lsm6ds3-accelerometer-and-gyroscope","title":"LSM6DS3 Accelerometer and Gyroscope","text":"The LSM6DS3 is a combined 3-axis accelerometer gyroscope with an 8kb FIFO buffer and an embedded processing interrupt function for the gyro sensor, specifically for the mobile phone market. Specifications:
With LSM6DS3 breakout, you will be able to detect the motion, impact, and tilt.
"},{"location":"sensors/06-accelerometer-compass/#selecting-the-accelerometer-sensitivity-range","title":"Selecting the Accelerometer Sensitivity Range","text":"The sensitivity ratings for an accelerometer specify the maximum measurable acceleration that the device can sense along its measurement axis (or axes). When an accelerometer states its sensitivity as \u00b12/\u00b14/\u00b116/\u00b18 g, it usually means that the device can be set to different ranges or scales of measurement. Here's what each of those ratings mean:
"},{"location":"sensors/06-accelerometer-compass/#2g-sensitivity-setting","title":"\u00b12g Sensitivity Setting","text":"The accelerometer can measure accelerations between -2g and +2g. This is a lower range, so it can detect small changes in acceleration with high precision. This range would be ideal for applications that require precise measurements of small movements. For example, the orientation of a screen as it is being tilted or rotated.
"},{"location":"sensors/06-accelerometer-compass/#4g-sensitivity-setting","title":"\u00b14g Sensitivity Setting","text":"In this setting, the accelerometer can measure accelerations between -4g and +4g. This provides a slightly larger range than the \u00b12g setting, making it suitable for applications that might experience slightly stronger forces or movements but still require a good amount of precision.
One example is wearable fitness trackers, like smartwatches and sport bands, often have built-in accelerometers to monitor and analyze user movement. For regular walking or jogging, a \u00b12g accelerometer might suffice. However, for more intense activities like plyometric exercises, sprinting, or aggressive directional changes in sports like basketball or soccer, the forces exerted on the tracker can exceed 2g. These activities involve rapid starts, stops, jumps, and changes in direction.
Why \u00b14g?: The accelerometer needs to capture both the subtleties of everyday motion and the higher-intensity bursts of activity without saturating or clipping the data. A \u00b14g range provides a good balance between sensitivity and maximum measurable acceleration, making it suitable for wearable devices intended for diverse and dynamic physical activities.
With this capability, fitness trackers can more accurately measure and analyze the user's movements across a wider range of activities, providing better data and feedback on their workouts and performance.
\u00b116g: The accelerometer can measure accelerations between -16g and +16g. This is a much broader range and is less sensitive to small movements. It's ideal for applications that are exposed to stronger forces or rapid movements, such as crash detection or certain sports applications.
\u00b18g: The accelerometer can measure accelerations between -8g and +8g. This provides a balance between sensitivity and range, suitable for applications that might experience moderate forces.
In practice, these settings allow the user (or designer) to choose the best range for their application. For applications where precision in detecting small accelerations is essential, a lower range (like \u00b12g) would be selected. In contrast, for applications where large accelerations may occur, a higher range (like \u00b116g) would be more appropriate to prevent the sensor from saturating or clipping the readings.
"},{"location":"sensors/06-accelerometer-compass/#selecting-gyroscope-sensitivity-ranges","title":"Selecting Gyroscope Sensitivity Ranges","text":"Gyroscopes measure the rate of angular change, often referred to as the angular velocity or angular rate. The unit for this rate of change is typically represented in degrees per second (DPS).
The sensitivity ranges for a gyroscope describe the maximum rate of rotation it can measure. When a gyroscope has sensitivity ranges listed as \u00b1125/245/500/\u00b11000/\u00b12000 DPS, it means the device has multiple selectable scales or ranges of measurement. Let's break down each of these ranges:
\u00b1125 DPS: At this setting, the gyroscope can measure angular velocities between -125 and +125 degrees per second. This is a low range and would provide high precision for slow rotational movements. For example, a slowly turning sailboat.
\u00b1245 DPS: The device can measure angular velocities between -245 and +245 degrees per second. This offers a slightly larger range than the \u00b1125 DPS setting.
\u00b1500 DPS: This setting allows the gyroscope to measure between -500 and +500 degrees per second.
\u00b11000 DPS: The device can measure angular velocities ranging between -1000 and +1000 degrees per second. This is suitable for applications that might experience moderately fast rotations.
\u00b12000 DPS: At this maximum setting, the gyroscope can capture angular velocities between -2000 and +2000 degrees per second. It's best for very fast rotational movements. For example, inside a rapidly spinning ball thown in the air.
"},{"location":"sensors/06-accelerometer-compass/#gyroscope-sensitivity-ranges-and-their-applications","title":"Gyroscope Sensitivity Ranges and Their Applications","text":"Here are some examples of different applications that might be used in each of these ranges.
"},{"location":"sensors/06-accelerometer-compass/#125-dps","title":"\u00b1125 DPS","text":"Module size: 13 * 18mm
Package Include: 1 x GY-LSM6DS3 IIC/SPI Accelerometer Tilt Angle Gyro Sensor Breakout Transfer Module
"},{"location":"sensors/06-accelerometer-compass/#settings-for-robot-tracking-orientation-and-position","title":"Settings for Robot Tracking Orientation and Position","text":"If we are Tracking Orientation and Position for a Small Robot we need the following:
"},{"location":"sensors/06-accelerometer-compass/#1-accelerometer","title":"1. Accelerometer:","text":"Remember to test and calibrate in real-world scenarios for optimal performance.
"},{"location":"sensors/06-accelerometer-compass/#references","title":"References","text":"The Bosch BME280 is a low-cost ($2) temperature, and pressure sensor that has an I2C interface. This is an ideal way to learn how to use an I2C interface.
Note mbe280 is different from the BMP280 and does not read humidity. The default address is Hex x76 or decimal 118.
"},{"location":"sensors/06-bme280/#circuit","title":"Circuit","text":"The BME280 has a standard I2C interface with four wires:
After you have connected your sensor you can check the connection by running a quick \"I2C Scanner\" to find the address of the sensor.
import machine\nsda=machine.Pin(0) # This is GP0 on row one of our standard Pico breadboard with the USB on top\nscl=machine.Pin(1) # Row two of our standard Pico breadboard\ni2c=machine.I2C(0, sda=sda, scl=scl, freq=400000)\nprint(\"Device found at decimal\", i2c.scan())\n
Results:
[118]\n
This is decimal of hex 0x76
If the scanner does not return a number then your connections might not be working.
"},{"location":"sensors/06-bme280/#bme280-driver","title":"BME280 Driver","text":"You should be able to find the BME280 driver by using the Thonny Tool -> Manage Packages... menu. If that does not work you can try a github search:
Search GitHub for MBE 280 Python Code
"},{"location":"sensors/06-bme280/#test-code","title":"Test Code","text":"import machine\nfrom utime import sleep\nimport BME280\nsda=machine.Pin(0) # This is GP0 on row one of our standard Pico breadboard with the USB on top\nscl=machine.Pin(1) # Row two of our standard Pico breadboard\ni2c=machine.I2C(0, sda=sda, scl=scl, freq=400000)\n\n# initialize the bme class using the default address\nbme = BME280()\n(chip_id, chip_version) = bme.getID()\nprint( \"Chip ID:\", chip_id)\nprint( \"Version:\", chip_version)\n\nwhile True():\n # get new data from the \n temperature,pressure,humidity = bme.getData()\n #adj_bar = bme.adj_baro(pressure, temperature)\n print(\"Adj {}\".format(bme.adj_baro(pressure, temperature)))\n\n print( \"Temperature: {}C\".format(temperature))\n print( \"Pressure: {}hpa, {}In\".format(pressure, round(pressure * 0.02953, 2)))\n\n sleep(1)\n
"},{"location":"sensors/06-bme280/#code-2","title":"Code 2","text":"from machine import I2C\nimport BME280\nfrom time import sleep\n\nsda=machine.Pin(16)\nscl=machine.Pin(17)\ni2c=machine.I2C(0,sda=sda, scl=scl, freq=400000)\nbme = BME280.BME280(i2c=i2c)\n# print(i2c.scan())\n\nwhile True:\n\n temp = bme.temperature\n hum = bme.humidity\n pres = bme.pressure\n # uncomment for temperature in Fahrenheit\n temp = (bme.read_temperature()/100) * (9/5) + 32\n #temp = str(round(temp, 2)) + 'F'\n print('Temperature: ', temp, end='')\n print(' Humidity:', hum, end='')\n print(' Pressure:', pres)\n\n sleep(5)\n
"},{"location":"sensors/06-bme280/#references","title":"References","text":"BME280 4. BME280 logger 5. The Electronics India
"},{"location":"sensors/07-VL53L0X_GY/","title":"VL53L0X Time-of-Flight Laser Ranging Module IR Distance Sensor","text":"Figure: VL53L0X in the GY-530 package.
The VL53L0X is a low-cost ($5) time-of-flight light-based distance sensor that is easy to use. It comes packaged in a I2C board and gives precise distance measurements up to 1.5 meters away. It measures the time that light pulses take to travel to an object and back to estimate distance. Light travels about 1 foot every nanosecond, so the timing inside this little chip must be very accurate.
The VL53L0X integrates a group of Single Photon Avalanche Diodes (SPAD) and embeds ST Electronic's second generation FlightSense\u2122 patented technology. The VL53L0X\u2019s 940 nm emitter Vertical Cavity Surface-Emitting Laser (VCSEL), is safe for kids and totally invisible to the human eye. Coupled with internal physical infrared filters, the sensor enables longer ranging distance, higher immunity to ambient light, and better robustness to cover glass optical crosstalk.
"},{"location":"sensors/07-VL53L0X_GY/#circuit","title":"Circuit","text":"Hook the VCC to the 3.3 out of the Pico, the GND of the sensor to andy of the GND pins of the Pico and then connect the Clock and Data to two pins such as GPIO pins 16 and 17.
"},{"location":"sensors/07-VL53L0X_GY/#i2c-scanner-test","title":"I2C Scanner Test","text":"We first run the I2C scanner program to verify that the sensor is connected correctly and is responding to the I2C bus scan.
import machine\nsda=machine.Pin(0) # row one on our standard Pico breadboard\nscl=machine.Pin(1) # row two on our standard Pico breadboard\ni2c=machine.I2C(0, sda=sda, scl=scl, freq=400000)\nprint(\"Device found at decimal\", i2c.scan())\n
This should return a single decimal number.
"},{"location":"sensors/07-VL53L0X_GY/#download-the-vl53l0x-driver","title":"Download The VL53L0X Driver","text":"If you are using Thonny, you can try to use the \"Manage Packages\" menu and search for the driver.
We have a sample of the driver here
"},{"location":"sensors/07-VL53L0X_GY/#create-a-test-program","title":"Create a Test Program","text":"# Test program for VL53L0X\nimport time\nfrom machine import Pin\nfrom machine import I2C\nimport VL53L0X\n\nsda=machine.Pin(16) # lower right pin\nscl=machine.Pin(17) # one up from lower right pin\ni2c=machine.I2C(0, sda=sda, scl=scl, freq=400000)\n\n# Create a VL53L0X object\ntof = VL53L0X.VL53L0X(i2c)\n\nwhile True:\n tof.start()\n tof.read()\n print(tof.read())\n tof.stop()\n time.sleep(0.1)\n
"},{"location":"sensors/07-VL53L0X_GY/#use-the-thonny-plot","title":"Use the Thonny Plot","text":""},{"location":"sensors/07-VL53L0X_GY/#reference-purchase-links","title":"Reference Purchase links","text":"qty 10 for $25
"},{"location":"sensors/08-ir-distance-sensor/","title":"IR Distance Sensor","text":""},{"location":"sensors/08-ir-distance-sensor/#ir-distance-sensors","title":"IR Distance Sensors","text":"IR distance sensors are low cost (five for $3) but may have problems working in rooms with outdoor lighting. They have an adjustable potentiometer on them that can be used to adjust a triggering distance call the threshold distance. The sensors return a HIGH signal if there is no object within the threshold distance and a LOW signal if there is an object within this distance. Since the sensor threshold distance can not be adjusted programmatically they are best suited when you can manually adjust the potentiometer to change the threshold.
"},{"location":"sensors/08-ir-distance-sensor/#connections","title":"Connections","text":"These sensors have three wires:
# connections to the three IR distance sensors\nleft = Pin(8, Pin.IN, Pin.PULL_DOWN)\ncenter = Pin(7, Pin.IN, Pin.PULL_DOWN)\nright = Pin(6, Pin.IN, Pin.PULL_DOWN)\n
"},{"location":"sensors/08-ir-distance-sensor/#the-ky-032","title":"The KY-032","text":"The KY-032 obstacle avoidance sensor is a four-wire distance-adjustable, infrared proximity sensor designed for wheeled robots. Also known as AD-032.
The sensor detection distance ranges from 2cm to 40cm, it can be adjusted by turning the potentiometer knob. The operating voltage is 3.3V-5V so it is suitable for a variety of microcontrollers like Arduino, ESP32, Teensy, ESP8266, Raspberry Pi, and others.
It has strong adaptability to ambient light and it is fairly accurate to sense changes in the surrounding environment.
"},{"location":"sensors/08-ir-distance-sensor/#speaker-test","title":"Speaker Test","text":"from machine import Pin, PWM\nfrom utime import sleep\n\n\nleft = Pin(8, Pin.IN, Pin.PULL_DOWN)\ncenter = Pin(7, Pin.IN, Pin.PULL_DOWN)\nright = Pin(6, Pin.IN, Pin.PULL_DOWN)\n\nSPEAKER_PIN = 21\n# create a Pulse Width Modulation Object on this pin\nspeaker = PWM(Pin(SPEAKER_PIN))\n\ndef sound_off():\n speaker.duty_u16(0)\n\ndef left_tone():\n speaker.duty_u16(1000)\n speaker.freq(300) # 1 Kilohertz\n sleep(.5) # wait a 1/4 second\n sound_off()\n\ndef center_tone():\n speaker.duty_u16(1000)\n speaker.freq(800)\n sleep(.5)\n sound_off()\n\ndef right_tone():\n speaker.duty_u16(1000)\n speaker.freq(400)\n sleep(.5)\n sound_off()\n\ndef right_tone():\n speaker.duty_u16(1000)\n speaker.freq(800)\n sleep(.25)\n sound_off()\n\ndef forward_tone():\n speaker.duty_u16(1000)\n speaker.freq(400)\n sleep(.1)\n speaker.freq(900)\n sleep(.1)\n speaker.freq(1200)\n sleep(.1)\n sound_off()\n\n# 0=stopped, 1=forward, 2=turing right, 3=turning left\ndrive_state = 0\nwhile True:\n if left.value()==0:\n print('Left')\n left_tone()\n drive_state = 2\n if center.value()==0:\n print('Center')\n center_tone()\n drive_state = 0\n if right.value()==0:\n print('Right')\n right_tone()\n drive_state = 3\n\n # if (left.value()==1) and (center.value()==1) and (right.value()==1):\n if left.value() and center.value() and right.value():\n print('Go forward!')\n drive_state = 1\n forward_tone()\n sleep(.25)\n
"},{"location":"sensors/08-ir-distance-sensor/#full-program","title":"Full Program","text":"```py from machine import Pin, PWM from utime import sleep import ssd1306
"},{"location":"sensors/08-ir-distance-sensor/#motor-pins-to-the-l293-h-bridge","title":"Motor pins to the L293 H-Bridge","text":"RIGHT_FORWARD_PIN = 17 RIGHT_REVERSE_PIN = 16 LEFT_FORWARD_PIN = 18 LEFT_REVERSE_PIN = 19
right_forward = PWM(Pin(RIGHT_FORWARD_PIN)) right_reverse = PWM(Pin(RIGHT_REVERSE_PIN)) left_forward = PWM(Pin(LEFT_FORWARD_PIN)) left_reverse = PWM(Pin(LEFT_REVERSE_PIN))
"},{"location":"sensors/08-ir-distance-sensor/#connections-to-the-three-ir-distance-sensors","title":"connections to the three IR distance sensors","text":"left = Pin(8, Pin.IN, Pin.PULL_DOWN) center = Pin(7, Pin.IN, Pin.PULL_DOWN) right = Pin(6, Pin.IN, Pin.PULL_DOWN)
SPEAKER_PIN = 21
"},{"location":"sensors/08-ir-distance-sensor/#create-a-pulse-width-modulation-object-on-this-pin","title":"create a Pulse Width Modulation Object on this pin","text":"speaker = PWM(Pin(SPEAKER_PIN))
WIDTH = 128 HEIGHT = 64 CS = machine.Pin(1) SCL = machine.Pin(2) SDA = machine.Pin(3) DC = machine.Pin(4) RES = machine.Pin(5) spi=machine.SPI(0, sck=SCL, mosi=SDA) oled = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)
def turn_motor_on(pwm): pwm.duty_u16(65025)
def turn_motor_off(pwm): pwm.duty_u16(0)
def forward(): turn_motor_on(right_forward) turn_motor_on(left_forward)
def reverse(): turn_motor_on(right_reverse) turn_motor_on(left_reverse)
def turn_right(): turn_motor_on(right_forward) turn_motor_on(left_reverse)
def turn_left(): turn_motor_on(right_reverse) turn_motor_on(left_forward)
def sound_off(): speaker.duty_u16(0)
def left_tone(): speaker.duty_u16(1000) speaker.freq(700) # 1 Kilohertz sleep(.5) # wait a 1/4 second sound_off()
def center_tone(): speaker.duty_u16(1000) speaker.freq(900) sleep(.5) sound_off()
def right_tone(): speaker.duty_u16(1000) speaker.freq(600) sleep(.5) sound_off()
def forward_tone(): speaker.duty_u16(1000) speaker.freq(400) sleep(.1) speaker.freq(900) sleep(.1) speaker.freq(1200) sleep(.1) sound_off()
def update_oled(): oled.fill(0) oled.text(\"CoderDojo Rocks!\", 0, 0, 1)
oled.text(\"Left:\", 0, 10, 1)\noled.text(str(left.value()), 50, 10, 1)\n\n\noled.text(\"Center:\", 0, 20, 1)\noled.text(str(center.value()), 60, 20, 1)\n\noled.text(\"Right:\", 0, 30, 1)\noled.text(str(right.value()), 55, 30, 1)\n\nBAR_WIDTH = 40\nBAR_HEIGHT = 20\nif left.value():\n oled.fill_rect(WIDTH-40, 50, BAR_WIDTH, BAR_HEIGHT, 0)\nelse:\n oled.fill_rect(WIDTH-40, 50, BAR_WIDTH, BAR_HEIGHT, 1)\n\nif center.value():\n oled.fill_rect(50, 50, BAR_WIDTH, BAR_HEIGHT, 0)\nelse:\n oled.fill_rect(50, 50, BAR_WIDTH, BAR_HEIGHT, 1)\n\nif right.value():\n oled.fill_rect(0, 50, BAR_WIDTH, BAR_HEIGHT, 0)\nelse:\n oled.fill_rect(0, 50, BAR_WIDTH, BAR_HEIGHT, 1)\n\noled.show()\n
"},{"location":"sensors/08-ir-distance-sensor/#0stopped-1forward-2turing-right-3turning-left","title":"0=stopped, 1=forward, 2=turing right, 3=turning left","text":"drive_state = 0 counter = 0 while True: if left.value()==0: print('Left') #left_tone() turn_right() update_oled() drive_state = 2 if center.value()==0: print('Center') center_tone() reverse() update_oled() drive_state = 0 if right.value()==0: print('Right') #right_tone() turn_left() update_oled() drive_state = 3
# if (left.value()==1) and (center.value()==1) and (right.value()==1):\nif left.value() and center.value() and right.value():\n print('Go forward!') \n drive_state = 1\n # forward_tone()\n forward()\n update_oled()\nprint(\"counter: \", counter)\ncounter += 1\nsleep(.25)\n```\n\n## More to Explore\n\n1. Try to change the values of the potentiometers on the sensors. What is the minimum and maximum distance you can detect and object?\n2. Does the reflectivity of the object impact the distance of the object?\n3. If you put a small mirror in front of the sensor what happens to the distance measured?\n4. Place the robot near bright sun in a window or try the robot outdoors on both a cloudy day and a sunny day? What is the change is accuracy of the sensors under these conditions? What about running the robot in the dark?\n
"},{"location":"sensors/10-rotary-encoder/","title":"Rotary Encoder","text":"A rotary encoder, or more specifically a directional rotary encoder, may look similar to a potentiometer in some ways. Both have a knob that you turn to adjust a value. But unlike a potentiometer, a rotary encoder is far more flexible in the range and precision of values it can control. Our students love to use them in their projects to change the color or patterns in an LED strip.
Rotary encoders can be thought of as two concentric rings of switches that go on and off as you turn the knob. The switches are placed so that you can tell the direction of rotation by the order the two switches get turned on and off. They turn on and off quickly so we need a high-quality function to quickly detect their changes. And as we learned in the Button lab, switches can be noisy and have a complex state transition that must be debounced to get a good quality signal.
"},{"location":"sensors/10-rotary-encoder/#learning-how-to-monitor-the-rotary-switch-transitions","title":"Learning How to Monitor the Rotary Switch Transitions","text":"We will be using a low-cost ($1 USD) encoder that has five connectors, three for the direction and one for a momentary switch that is closed when you press the knob in. Here is the circuit that we will be using:
We hooked up the outer pins of the encoder to GPIO pins 16 and 17 in the lower right corner of the Pico.
Then we hooked the center pin to the 3.3 volt rail. The Pico likes to pull switches down from the 3.3 volt rail. This means that we will not be connecting any of the pins to GND.
We also hooked up the central press button to GPIO 22 and the 3.3 volt rail.
We then ran this code and turned the knob:
import time\nfrom machine import Pin\n\nrotaryA = Pin(16, Pin.IN, Pin.PULL_DOWN)\nrotaryB = Pin(17, Pin.IN, Pin.PULL_DOWN)\n\nwhile True:\n print(rotaryA.value(), end='')\n print(rotaryB.value())\n time.sleep(.1)\n
the results look like the following lines:
00\n00\n00\n10\n01\n00\n00\n00\n11\n00\n00\n00\n01\n11\n00\n
Note that the bit values the encoders switches (on or off as 0 and 1) are place next to each other on the same line. We did this by making the end of the first print statement be the null string not the default newline character.
This program prints out a LONG stream of numbers, mostly of the value 00
. The values are printed 10 times each second. Now let's take a closer look at only the values that change.
What we would like to do is now only print numbers if there is a change. To do this we will \"pack\" binary values into a two bit number by shifting the A pin value to the left:
import time\nfrom machine import Pin\n\nrotaryA = Pin(16, Pin.IN, Pin.PULL_DOWN)\nrotaryB = Pin(17, Pin.IN, Pin.PULL_DOWN)\n\n# we set the old value to zero for both bits being off\nold_combined = 0\nwhile True:\n A_val = rotaryA.value()\n B_val = rotaryB.value()\n # a sifts by one bit and then is ORed with the B calue\n new_combined = (A_val << 1) | B_val\n if new_combined != old_combined:\n print(A_val, end='')\n print(B_val)\n old_combined = new_combined\n time.sleep(.1)\n
Now we get values that look like this:
01\n11\n00\n01\n11\n00\n01\n11\n10\n00\n10\n00\n01\n11\n00\n10\n
Turning the knob clockwise we see the 01
before the 11 frequently Turning the know counterclockwise:
00\n10\n11\n00\n10\n11\n00\n11\n00\n10\n00\n10\n01\n00\n
Here we see the reverse 10
pattern occur more frequently. But there is noise in the switches as they open and close due to small variations in the contacts as they move.
import time\nfrom machine import Pin\n\nrotaryA = Pin(16, Pin.IN, Pin.PULL_DOWN)\nrotaryB = Pin(17, Pin.IN, Pin.PULL_DOWN)\n\n# we set the old value to zero for both bits being off\nold_combined = 0\nwhile True:\n A_val = rotaryA.value()\n B_val = rotaryB.value()\n # a sifts by one bit and then is ORed with the B calue\n new_combined = (A_val << 1) | B_val\n if new_combined != old_combined:\n #print(A_val, end='')\n #print(B_val)\n old_combined = new_combined\n if A_val == 0 and B_val == 1:\n print('clock')\n elif A_val == 1 and B_val == 0:\n print('counter clock')\n time.sleep(.1)\n
"},{"location":"sensors/10-rotary-encoder/#the-rotary-class","title":"The Rotary Class","text":"Here is one rotary class:
Mike Teachman Rotary Class. This is preferred since it does not call a slow scheduler within an interrupt.
"},{"location":"sensors/10-rotary-encoder/#using-a-scheduler","title":"Using a Scheduler","text":"There is another class Gurgle Apps Rotary Encoder that uses a scheduler within an interrupt which is not a best practice. However, we can show how this does work work with the one she created. The numbers incremented, but they didn't decrement. I had to change the pins to use the PULL_DOWN settings in the init method.
import machine\nimport utime as time\nfrom machine import Pin\nimport micropython\n\nclass Rotary:\n\n ROT_CW = 1\n ROT_CCW = 2\n SW_PRESS = 4\n SW_RELEASE = 8\n\n def __init__(self,dt,clk,sw):\n self.dt_pin = Pin(dt, Pin.IN, Pin.PULL_DOWN)\n self.clk_pin = Pin(clk, Pin.IN, Pin.PULL_DOWN)\n self.sw_pin = Pin(sw, Pin.IN, Pin.PULL_DOWN)\n self.last_status = (self.dt_pin.value() << 1) | self.clk_pin.value()\n self.dt_pin.irq(handler=self.rotary_change, trigger=Pin.IRQ_FALLING | Pin.IRQ_RISING )\n self.clk_pin.irq(handler=self.rotary_change, trigger=Pin.IRQ_FALLING | Pin.IRQ_RISING )\n self.sw_pin.irq(handler=self.switch_detect, trigger=Pin.IRQ_FALLING | Pin.IRQ_RISING )\n self.handlers = []\n self.last_button_status = self.sw_pin.value()\n\n def rotary_change(self, pin):\n new_status = (self.dt_pin.value() << 1) | self.clk_pin.value()\n if new_status == self.last_status:\n return\n transition = (self.last_status << 2) | new_status\n if transition == 0b1110:\n micropython.schedule(self.call_handlers, Rotary.ROT_CW)\n elif transition == 0b1101:\n micropython.schedule(self.call_handlers, Rotary.ROT_CCW)\n self.last_status = new_status\n\n def switch_detect(self,pin):\n if self.last_button_status == self.sw_pin.value():\n return\n self.last_button_status = self.sw_pin.value()\n if self.sw_pin.value():\n micropython.schedule(self.call_handlers, Rotary.SW_RELEASE)\n else:\n micropython.schedule(self.call_handlers, Rotary.SW_PRESS)\n\n def add_handler(self, handler):\n self.handlers.append(handler)\n\n def call_handlers(self, type):\n for handler in self.handlers:\n handler(type)\n
The following were the lines that I changed:
self.dt_pin = Pin(dt, Pin.IN, Pin.PULL_DOWN)\n self.clk_pin = Pin(clk, Pin.IN, Pin.PULL_DOWN)\n self.sw_pin = Pin(sw, Pin.IN, Pin.PULL_DOWN)\n
"},{"location":"sensors/10-rotary-encoder/#testing-script","title":"Testing Script","text":"from rotary import Rotary\nimport utime as time\nfrom machine import Pin\n\n# GPIO Pins 16 and 17 are for the encoder pins. 22 is the button press switch.\nrotary = Rotary(16, 17, 22)\nval = 0\n\ndef rotary_changed(change):\n global val\n if change == Rotary.ROT_CW:\n val = val + 1\n print(val)\n elif change == Rotary.ROT_CCW:\n val = val - 1\n print(val)\n elif change == Rotary.SW_PRESS:\n print('PRESS')\n elif change == Rotary.SW_RELEASE:\n print('RELEASE')\n\nrotary.add_handler(rotary_changed)\n\nwhile True:\n time.sleep(0.1)\n
"},{"location":"sensors/10-rotary-encoder/#adding-plot","title":"Adding Plot","text":"Now I can move the knob back and forth and get consistent values that go up and down. You can turn on the plot function of Thonny to see the values consistently go up and down.
"},{"location":"sensors/10-rotary-encoder/#mysterious-runtime-error-on-scheduling-queue","title":"Mysterious Runtime Error on Scheduling Queue","text":"I did notice that the Shell output did register the following errors:
Traceback (most recent call last):\n File \"rotary.py\", line 30, in rotary_change\nRuntimeError: schedule queue full\n
The error also occurred on line 32.
The following lines generated this error:
micropython.schedule(self.call_handlers, Rotary.ROT_CW)\nmicropython.schedule(self.call_handlers, Rotary.ROT_CCW)\n
This error did not seem to impact the execution of the code. My suspicion is that this is a bug in the Micropython firmware.
As a fix, you can use the try/except block an catch any runtime error. The pass function is a no-op (no operation)
def rotary_change(self, pin):\n new_status = (self.dt_pin.value() << 1) | self.clk_pin.value()\n if new_status == self.last_status:\n return\n transition = (self.last_status << 2) | new_status\n try:\n if transition == 0b1110:\n micropython.schedule(self.call_handlers, Rotary.ROT_CW)\n elif transition == 0b1101:\n micropython.schedule(self.call_handlers, Rotary.ROT_CCW)\n except RuntimeError:\n pass\n self.last_status = new_status\n
"},{"location":"sensors/10-rotary-encoder/#references","title":"References","text":"Digital Compass Lab Video
This lab will use the popular HML5883L chip to show how we can sense a magnetic field and allow our robots to sense the direction they are going.
"},{"location":"sensors/11-compass/#about-the-hml5883l","title":"About the HML5883L","text":"The HMC5883L is a multi-chip module able to measure magnetic sensing. It can communicate with an I2C digital interface with other devices in order to let creating applications such as low-cost compass, being used in many electronic fields like mobile phones, netbooks, consumer electronics, auto navigation systems, and personal navigation devices.
The module works with a low voltage supply (from 2.16 to 3.6V) and also has a low power consumption (100 \u03bcA).
HMC5883L has a 1\u00b0 to 2\u00b0 degrees heading accuracy, which is enough for a wide number of applications. It also allows setting its internal gain to increase resolution with a 3-bit gain control register. But increasing gain also increases noise effects.
"},{"location":"sensors/11-compass/#pinout","title":"Pinout","text":"The HMC5883L pinout has 5 PINs:
You can use a standard i2c-scanner program to verify that the four pins are connected correctly:
import machine\n\n# Change this if you are using other ports\nI2C_SDA_PIN = 0\nI2C_SCL_PIN = 1\ni2c=machine.I2C(0,sda=machine.Pin(I2C_SDA_PIN), scl=machine.Pin(I2C_SCL_PIN), freq=400000)\n\nprint('Scanning I2C bus.')\ndevices = i2c.scan() # this returns a list of devices\n\ndevice_count = len(devices)\n\nif device_count == 0:\n print('No i2c device found.')\nelse:\n print(device_count, 'devices found.')\n\nfor device in devices:\n print('Decimal address:', device, \", Hex address: \", hex(device))\n
For my system this returns the following report:
Scanning I2C bus.\n1 devices found.\nDecimal address: 30 , Hex address: 0x1e\n
You will notice that the scanner found a single device at hex address 0x1e
.
Like many sensors, this sensor uses a \"memory map\" to transmit it's data. You can write a quick test to verify that the values are changing by doing a read from this i2c address like this:
data = array('B', [0] * 6)\nwhile True:\n i2c.readfrom_mem_into(0x1e, 0x03, data)\n
Here is a program that will display the raw values. You can rotate the sensor to verify the raw values are changing.
from array import array\n# create an incomming data array of 6 bytes\nI2C_SDA_PIN = 0\nI2C_SCL_PIN = 1\ni2c=machine.I2C(0,sda=machine.Pin(I2C_SDA_PIN), scl=machine.Pin(I2C_SCL_PIN), freq=400000)\ndata = array('B', [0] * 6)\nwhile True:\n i2c.readfrom_mem_into(0x1e, 0x03, data)\n
Note that these are \"raw\" values because they have not been converted to easy-to-understand values by the driver.
from hmc5883l import HMC5883L\nfrom utime import sleep\n\nI2C_SDA_PIN = 0\nI2C_SCL_PIN = 1\n\n# i2c=machine.I2C(0,sda=machine.Pin(I2C_SDA_PIN), scl=machine.Pin(I2C_SCL_PIN), freq=400000)\n\n# The default address is address=0x1e\n\nsensor = HMC5883L(scl=I2C_SDA_PIN, sda=I2C_SCL_PIN)\n\nwhile True:\n x, y, z = sensor.read()\n # print(sensor.format_result(x, y, z))\n if x <400:\n print(x)\n sleep(.1)\n
"},{"location":"sensors/11-compass/#references","title":"References","text":"The APDS-9960 is an I2C device with advanced Gesture detection, Proximity detection, Digital Ambient Light Sense (ALS) and Color Sense (RGBC). It incorporates an IR LED and factory-calibrated LED driver.
"},{"location":"sensors/13-gesture/#gesture-detection","title":"Gesture detection","text":"Gesture detection utilizes four directional photodiodes to sense reflected IR energy (sourced by the integrated LED) to convert physical motion information (i.e. velocity, direction and distance) to a digital information. The architecture of the gesture engine features automatic activation (based on Proximity engine results), ambient light subtraction, cross-talk cancelation, dual 8-bit data converters, power saving inter-conversion delay, 32-dataset FIFO, and interrupt driven I2C communication. The gesture engine accommodates a wide range of mobile device gesturing requirements: simple UP-DOWN-RIGHT-LEFT gestures or more complex gestures can be accurately sensed.
"},{"location":"sensors/13-gesture/#code","title":"Code","text":"import machine\nfrom time import sleep_ms\nfrom uPy_APDS9960.apds9960LITE import APDS9960LITE\n\n#Init I2C Buss on RP2040\nsda=machine.Pin(12)\nscl=machine.Pin(13)\ni2c = machine.I2C(0,scl=scl, sda=sda)\n\napds9960=APDS9960LITE(i2c) # Enable sensor\napds9960.prox.enableSensor() # Enable Proximit sensing\n\nwhile True:\n sleep_ms(25) # wait for readout to be ready\n print(apds9960.prox.proximityLevel) #Print the proximity value\n
Returns
0 1 1 1 1 1 0 1 1 2 2 1 1 1 1 1 0 \n
"},{"location":"sensors/13-gesture/#ambient-light","title":"Ambient Light","text":"# from https://github.com/rlangoy/uPy_APDS9960\nimport machine\nfrom time import sleep,sleep_ms\nfrom APDS9960LITE import APDS9960LITE\n\n#Init I2C Buss on RP2040\nsda=machine.Pin(12)\nscl=machine.Pin(13)\ni2c=machine.I2C(0, sda=sda, scl=scl, freq=400000)\n\nprint(i2c)\n# create the driver\napds9960=APDS9960LITE(i2c)\napds9960.als.enableSensor() # Enable Light sensor\nsleep_ms(25) # Wait for readout to be ready\n\n#apds9960.powerOn(True)\n#print(apds9960.statusRegister())\n\nwhile True:\n print(apds9960.als.ambientLightLevel, ' ', end='')\n sleep(.1)\n
returns
1 1 0 0 0 0 0 0 0 0 1 1 1 4 8 10 10 10 12 16 17 19 19 20 21 21 22 25 27 30 33 35 36 37 38 33 33 34 34 34 34 34 35 36 36 38 39 40 38 37 38 40 42 45 48 49 55 61 66 73 76 76 82 93 109 132 156 169 171 173 173 171 164 160 169 165 162 162 164 163 159 158 152 148 148 145 141 135 122 106 96 101 108 89 62 48 33 20 10 5 3 1 0 0 0 0 0 0 0 1 2 4 8 14 26 45 65 91 110 121 124 127 132 150 191 225 244 244 254 288 295 243 219 259 299 287 204 99 31 10 2 1 1 1 0\n
"},{"location":"sensors/13-gesture/#reading-rgb","title":"Reading RGB","text":"while True:\n print(apds9960.als.ambientLightLevel,'', end='')\n print(apds9960.als.redLightLevel,'', end='')\n print(apds9960.als.greenLightLevel,'', end='')\n print(apds9960.als.blueLightLevel)\n sleep(.1)\n
1 0 0 0\n2 0 0 1\n4 1 0 1\n8 3 2 2\n17 6 5 5\n28 14 10 8\n38 19 12 11\n37 27 15 12\n38 30 15 12\n43 29 15 14\n35 22 11 11\n26 17 9 7\n18 12 6 5\n13 7 4 3\n9 4 3 2\n
"},{"location":"sensors/13-gesture/#rgb-display-graph","title":"RGB Display Graph","text":"This program displays the red, green and blue light values as bars on an OLED display.
# from https://github.com/rlangoy/uPy_APDS9960\nfrom machine import Pin, I2C\nimport ssd1306\nfrom time import sleep,sleep_ms\nfrom APDS9960LITE import APDS9960LITE\n\n#Init I2C Buss on RP2040\nsda=Pin(12)\nscl=Pin(13)\ni2c=I2C(0, sda=sda, scl=scl, freq=400000)\n\nprint(i2c)\n# create the driver\napds9960=APDS9960LITE(i2c)\napds9960.als.enableSensor() # Enable Light sensor\nsleep_ms(25) # Wait for readout to be ready\n\n# display\nWIDTH = 128\nONE_THIRD_WIDTH = int(WIDTH/3)\nHEIGHT = 64\nclock=Pin(2)\ndata=Pin(3)\nRES = machine.Pin(4)\nDC = machine.Pin(5)\nCS = machine.Pin(6)\nspi=machine.SPI(0, sck=clock, mosi=data)\noled = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)\n\ndef update_display(red, green, blue):\n oled.fill(0)\n # scale red, green and blue to the height\n red = int(red*4)\n green = int(green*4)\n blue = int(blue*3)\n\n oled.fill(0)\n # rect_fill(x, y, width, height)\n oled.fill_rect(0, HEIGHT - red, ONE_THIRD_WIDTH, red, 1)\n oled.fill_rect(ONE_THIRD_WIDTH, HEIGHT - green, ONE_THIRD_WIDTH, green, 1)\n oled.fill_rect(ONE_THIRD_WIDTH * 2, HEIGHT - blue, ONE_THIRD_WIDTH, blue, 1)\n oled.text(str(red), 0, 56, 0)\n oled.text(str(green), ONE_THIRD_WIDTH, 56, 0)\n oled.text(str(blue), ONE_THIRD_WIDTH*2, 56, 0)\n oled.show()\n\nwhile True:\n # print(apds9960.als.ambientLightLevel,'', end='')\n red = apds9960.als.redLightLevel\n green = apds9960.als.greenLightLevel\n blue = apds9960.als.blueLightLevel\n update_display(red, green, blue)\n sleep(.05)\n
"},{"location":"sensors/13-gesture/#references","title":"References","text":""},{"location":"sensors/13-gesture/#using-tinyml","title":"Using TinyML","text":"https://www.hackster.io/mjrobot/tinyml-motion-recognition-using-raspberry-pi-pico-6b6071?f=1
"},{"location":"sensors/13-gesture/#datasheet","title":"Datasheet","text":"https://cdn.sparkfun.com/assets/learn_tutorials/3/2/1/Avago-APDS-9960-datasheet.pdf
"},{"location":"sensors/13-gesture/#other-references","title":"Other References","text":"https://robojax.com/learn/arduino/?vid=robojax-gesture-APDS9960
https://upy-apds9960.readthedocs.io/en/latest/#
https://www.youtube.com/watch?v=wH9HpP9bKwE
https://github.com/rlangoy/uPy_APDS9960/blob/master/uPy_APDS9960/apds9960LITE.py
"},{"location":"sensors/dht11-temp-humidity/","title":"The DHT22 (AM2302) Temperature and Humidity Sensor","text":"The DHT22 is a low-cost ($2) digital temperature and humidity sensor. It uses a capacitive humidity sensor and a thermistor to measure the surrounding air, and transmits a digital signal on the data pin.
These devices are easy to use since they have an internal analog to digital conversion. They are good for 20-80% humidity readings with 5% accuracy and 0-50\u00b0C temperature readings with \u00b12\u00b0C accuracy. They are designed to be polled about once every two seconds.
"},{"location":"sensors/dht11-temp-humidity/#circuit","title":"Circuit","text":""},{"location":"sensors/dht11-temp-humidity/#references","title":"References","text":"DHT22 Datasheet (PDF)
"},{"location":"sims/","title":"Simulators for Learning MicroPython","text":"Knowledge Graph
Wokwi
Breadboard Simulator Over Image - simulator written in P5.js
Vector Breadboard With Zoom
Vector Drawn Breadboard Simulator
"},{"location":"sims/knolwedge-graph/","title":"Knowledge Graphs","text":"```linenums=\"0\" Raspberry Pi Pico, and solderless breadboards. In our labs, we also use many parts such as photosensors, potentiometers, LEDs, resistors, transistors, time-0f-flight sensors, WS2812B LED strips, 128x64 OLED displays, motors, and the Cytron Maker Pi RP2040, which has the same RP2040 microcontroller.
We have a lot of material and lesson plans right now, but I would like an overall list of the 200 key concepts we need to cover in this course.
For this course, please create a list of the core learning concepts that we need to present in this course and the order in which they should be taught.
Please create a list of the 200 most essential concepts in this course. Return the concept's preferred label in a numbered list for each learning concept. Make sure that the concept names are distinct and not synonyms. Don't repeat
Start with the most basic foundational concepts and then proceed to the most complex concepts. Return just one concept label per line. Avoid using acronyms or abbreviations.
"},{"location":"sims/knolwedge-graph/concept-enumeration/#response","title":"Response","text":"ChatGPT o1-preview Thought for 22 seconds
I am very pleased with these results. Now, can you suggest a\ntaxonomy to classify each of these learning concepts? Perhaps\nabout 10 different classifications?\n
ChatGPT o1-min Thought for a couple of seconds
I'm glad to hear that you're pleased with the results so far! Creating a taxonomy to classify your 197 learning concepts will help organize the curriculum, ensure comprehensive coverage, and facilitate a logical progression of topics. Below is a suggested taxonomy with 10 classifications, each accompanied by a description and examples of relevant concepts from your list.
"},{"location":"sims/knolwedge-graph/concept-taxonomy/#1-computational-thinking-foundations","title":"1. Computational Thinking Foundations","text":"Description: Covers the basic principles and strategies of computational thinking, essential for problem-solving and algorithm development.
Relevant Concepts:
Description: Introduces the core concepts of programming that are applicable across various programming languages and environments.
Relevant Concepts:
Description: Focuses on programming using MicroPython, including syntax, environment setup, and MicroPython-specific features.
Relevant Concepts:
Description: Explores the physical components used in projects and how to interface them with the Raspberry Pi Pico.
Relevant Concepts:
Description: Introduces fundamental electronics concepts necessary for building and understanding circuits.
Relevant Concepts:
Description: Covers various sensors, their functionalities, and how to integrate and interpret data from them.
Relevant Concepts:
Description: Focuses on controlling various actuators such as motors, servos, and relays to perform physical actions.
Relevant Concepts:
Description: Details various communication protocols used for data transmission between devices and components.
Relevant Concepts:
Description: Explores IoT concepts, connectivity, data transmission, and integration with cloud services.
Relevant Concepts:
Description: Includes advanced programming techniques, best practices, version control, and emerging technologies.
Relevant Concepts:
Cross-Cutting Themes: Some concepts may span multiple categories. For example, \"Pulse Width Modulation\" could relate to both \"Electronics Basics\" and \"Actuator Control.\"
Prerequisites: Ensure that foundational categories like \"Computational Thinking Foundations\" and \"Programming Fundamentals\" are taught first, as they underpin many other topics.
Modular Organization: Consider organizing your curriculum into modules based on these classifications to provide a structured learning path.
Scalability: This taxonomy allows for the addition of new concepts or subcategories as the course evolves or as new technologies emerge.
For better visualization, here's a hierarchical representation of the taxonomy:
Here's how some of the concepts map to the suggested taxonomy:
Concept ID Concept Name Classification 1 Intro to Computational Thinking Computational Thinking Foundations --- --- --- 7 Variables in Programming Programming Fundamentals 14 Introduction to MicroPython MicroPython Programming 26 Microcontroller Basics Hardware Components and Interfacing 31 Circuit Basics Electronics Basics 99 Introduction to Sensors Sensor Integration 107 Introduction to Actuators Actuator Control 122 Serial Communication Protocols Communication Protocols 128 Internet of Things Concepts Internet of Things (IoT) 136 Programming Best Practices Advanced Topics and Best Practices"},{"location":"sims/knolwedge-graph/concept-taxonomy/#final-remarks","title":"Final Remarks","text":"This taxonomy provides a structured framework to classify and organize your 197 learning concepts effectively. It ensures that foundational knowledge is established before progressing to more advanced topics, facilitating a coherent and comprehensive learning experience for high school students. Feel free to adjust the classifications or add subcategories to better fit the specific needs and goals of your course.
o1-mini
requestAnimationFrame((function(){window.__oai_logTTI?window.__oai_logTTI():window.__oai_SSR_TTI=window.__oai_SSR_TTI??Date.now()}))
"},{"location":"sims/knolwedge-graph/concpt-dependency/","title":"Concept Dependency","text":""},{"location":"sims/knolwedge-graph/concpt-dependency/#prompt","title":"Prompt","text":"Our next step is to create a fully connected dependency graph.\nIn this step, for each line or concept on this list, \nplease create a new list of other concepts on which it depends.\nIf a concept depends on a specific concept that is not listed,\nthen we must add it as a new concept.\nThis will help us determine the teaching order of the concepts.\n\nReturn the new list as a CSV file using the following format:\n\nThe first column is a numeric concept ID you assigned, \nthe second column is the concept name,\nand the third column is a pipe-delimited list of the concept IDs\nthat the concept depends upon.\n\nCheck your results to make sure that all concepts are\nconnected to at least one other concept.\n
"},{"location":"sims/knolwedge-graph/concpt-dependency/#response","title":"Response","text":"ChatGPT o1-mini Thought for 53 seconds
Here is the CSV file containing the concepts along with their dependencies:
ID,Concept Name,Dependencies (IDs)\n1,Intro to Computational Thinking,\n2,Problem Decomposition,1\n3,Pattern Recognition,1\n4,Abstraction,1\n5,Algorithm Design,1|2|3|4\n6,Basic Programming Concepts,1|5\n7,Variables in Programming,6\n8,Data Types in Programming,6|7\n9,Operators,6|7|8\n10,Control Structures,6|7|8|9\n11,Conditional Statements,10|9\n12,Loops,10\n13,General Functions in Programming,6|7|8|10\n14,Introduction to MicroPython,6\n15,Setting Up MicroPython Environment,14\n16,Syntax in MicroPython,14|15\n17,Functions in MicroPython,13|16\n18,Modules in MicroPython,16|17\n19,Input and Output,16|7|8\n20,Working with Strings,8|16\n21,Working with Numbers,8|16\n22,Lists,8|16\n23,Tuples,8|16\n24,Dictionaries,8|16\n25,Exception Handling,16|10\n26,Microcontroller Basics,\n27,The Raspberry Pi Pico,26\n28,Understanding GPIO Pins,26|27\n29,Solderless Breadboards,32\n30,Breadboard Usage,29|32\n31,Circuit Basics,\n32,Ohm's Law,31\n33,Resistors in Circuits,31|32\n34,LEDs in Circuits,31\n35,Using Resistors with LEDs,33|34|32\n36,Current Limiting Resistors,33|35\n37,Connecting LEDs,34|30\n38,Blinking an LED with MicroPython,37|16|28\n39,Digital Output Signals,28|16\n40,Digital Input Signals,28|16\n41,Reading Button Inputs,40|30\n42,Debouncing Switches,41|10\n43,Pull-up and Pull-down Resistors,33|41\n44,Controlling Multiple LEDs,37|38|16\n45,Using Breadboard Jumpers,30\n46,Potentiometers in Circuits,31|33\n47,Reading Analog Inputs,46|28\n48,Analog-to-Digital Conversion,47\n49,Using ADC in MicroPython,48|16\n50,Reading Potentiometer Values,49|46\n51,Pulse Width Modulation Fundamentals,6|10\n52,Implementing Pulse Width Modulation in MicroPython,51|16\n53,LED Brightness Control with Pulse Width Modulation,52|37\n54,Fading LEDs Using Pulse Width Modulation,53|12\n55,Advanced Pulse Width Modulation Techniques,54|52\n56,Servo Motors Overview,31\n57,Controlling Servo Motors,52|28|56\n58,DC Motors Overview,31\n59,Transistors as Switches,31|33\n60,Controlling Motors with Transistors,59|58\n61,Transistor Biasing Resistors,59|33\n62,Power Management with Transistors,59|61\n63,Photocells,31|33\n64,Making a Night Light,63|28|16\n65,Voltage Dividers,33|31\n66,Voltage Dividers and Sensors,65|63\n67,Time-of-Flight Sensors,31|28\n68,Interfacing Time-of-Flight Sensors,67|16\n69,I2C Protocol,70|16\n70,SPI Protocol,28|16\n71,Connecting I2C Devices,69|28\n72,LED Displays Overview,34|31\n73,Organic Light-Emitting Diode Displays,72\n74,Display Drawing,73|16\n75,Drawing Circles and Ellipses,74\n76,Drawing Polygons,74\n77,Changing Fonts,74\n78,Advanced I2C,69|16\n79,Advanced SPI,70|16\n80,WS2812B LED Strips,34|31\n81,LED Strip Code,80|16\n82,Moving Pixels on LED Strips,81|12\n83,LED Strip Patterns,81|82\n84,LED Strip Button Demos,83|41\n85,Sensors and Actuators,31|28\n86,Integrating Multiple Sensors,85|16\n87,Event-Driven Programming,6|10\n88,Interrupts,28|87\n89,Using Interrupts with GPIO Pins,88|28\n90,Debouncing with Interrupts,89|42\n91,Timers,6|16\n92,Delays and Timing Functions,91|16\n93,Using the Time Module,92|16\n94,Data Logging with MicroPython,16|97\n95,Storing Data on Raspberry Pi Pico,94|27\n96,File Handling,16|95\n97,Universal Serial Bus Communication,27|16\n98,Universal Asynchronous Receiver-Transmitter Communication,27|16\n99,Introduction to Sensors,31\n100,Temperature Sensors,99|28\n101,Interfacing Temperature Sensors,100|16\n102,Humidity Sensors,99|28\n103,Using DHT11 and DHT22 Sensors,101|102\n104,Reading Sensor Data,103|16\n105,Interpreting Sensor Data,104\n106,Calibrating Sensors,105\n107,Introduction to Actuators,31\n108,Controlling Relays,107|16\n109,Relays Overview,107\n110,Relays Safety,109\n111,Stepper Motors,31\n112,Controlling Stepper Motors,111|16\n113,Using Stepper Motor Drivers,112|28\n114,Generating Sound with Buzzers,31|28\n115,Using Piezoelectric Buzzers,114|16\n116,Generating Tones and Melodies,115|12\n117,Infrared Communication,28|16\n118,Using Infrared Sensors,117|31\n119,Infrared Remote Control,118|16\n120,Ultrasonic Sensors,99|28\n121,Serial Communication Protocols,27|16\n122,I2C vs SPI vs UART,69|70|98\n123,Data Communication Principles,121\n124,Wireless Communication Basics,123\n125,Bluetooth Low Energy,124|27\n126,Wi-Fi Connectivity,124|27\n127,Python Modules Overview,16\n128,Internet of Things Concepts,124|27\n129,Connecting to the Internet,126|16\n130,Sending Data to Web Servers,129|16\n131,Hypertext Transfer Protocol Basics,130\n132,Message Queuing Telemetry Transport Protocol,130\n133,IoT Applications,128|130\n134,Cloud Services Integration,133\n135,Data Visualization Tools,134\n136,Programming Best Practices,6\n137,Code Optimization Techniques,136\n138,Debugging Techniques,16|136\n139,Using Debugging Tools,138\n140,Version Control Systems,6\n141,Using Git for Code Management,140\n142,Collaborative Coding Practices,141\n143,Documentation Standards,136\n144,Commenting Code Effectively,136\n145,Advanced MicroPython Features,16\n146,Object-Oriented Programming,6|136\n147,Classes and Objects,146\n148,Inheritance and Polymorphism,147\n149,Encapsulation in Object-Oriented Programming,147\n150,Designing Reusable Code,146|149\n151,Memory Management,16|136\n152,Managing Resources Efficiently,151\n153,Energy Efficiency in Programming,152\n154,Sleep Modes in Microcontrollers,153|27\n155,Battery-Powered Projects,154\n156,Power Management Techniques,155\n157,Advanced Electronics Concepts,31\n158,Capacitors in Circuits,157\n159,Inductors and Coils,157\n160,Diodes and Rectification,157\n161,Voltage Regulation,160\n162,Using Voltage Regulators,161\n163,Building Power Supplies,162\n164,Analog Signals vs Digital Signals,31|157\n165,Signal Conditioning,164|157\n166,Filtering Noise in Circuits,165\n167,Interference and Shielding,166\n168,Electromagnetic Compatibility,167\n169,Circuit Design Principles,157\n170,Using Simulation Tools,169\n171,Prototyping Techniques,169\n172,Schematic Diagram Reading,169\n173,Understanding Datasheets,169\n174,Component Specifications,173\n175,Safety in Electronics,157\n176,Electrostatic Discharge Precautions,175\n177,Soldering Techniques,175\n178,Printed Circuit Board Design,169\n179,Using PCB Design Software,178\n180,Fabricating Custom PCBs,179\n181,Advanced Sensor Integration,85|157\n182,Gesture Recognition Sensors,181|28\n183,Touch Sensors,181|28\n184,Environmental Monitoring,181|105\n185,Robotics Basics,31|157\n186,Building Simple Robots,185|16\n187,Line-Following Robots,186|83\n188,Obstacle Avoidance Robots,186|120\n189,Control Systems Basics,185\n190,Feedback Mechanisms,189\n191,Proportional-Integral-Derivative Controllers,190\n192,Machine Learning Concepts,6|136\n193,Using MicroPython for Artificial Intelligence,192|16\n194,Edge Computing,193|128\n195,Ethics in Technology,6\n196,Future Trends in Computing,195\n
Notes:
|
) as per your instructions.Concept 1: Intro to Computational Thinking
Concept 26: Microcontroller Basics
Dependencies: None (introduces the hardware aspect).
Concept 27: The Raspberry Pi Pico
Dependencies: Depends on Microcontroller Basics (26) for understanding microcontrollers.
Concept 38: Blinking an LED with MicroPython
Dependencies: Connecting LEDs (37), Syntax in MicroPython (16), Understanding GPIO Pins (28).
Concept 57: Controlling Servo Motors
Dependencies: Implementing PWM in MicroPython (52), Understanding GPIO Pins (28), Servo Motors Overview (56).
Concept 95: Storing Data on Raspberry Pi Pico
Dependencies: Data Logging with MicroPython (94), The Raspberry Pi Pico (27).
Concept 128: Internet of Things Concepts
Dependencies: Wireless Communication Basics (124), The Raspberry Pi Pico (27).
Concept 193: Using MicroPython for Artificial Intelligence
Dependencies: Machine Learning Concepts (192), Syntax in MicroPython (16).
HTML Page for Dependency Graph
"},{"location":"sims/knolwedge-graph/graph/#adjusting-visualization","title":"Adjusting Visualization","text":"In the JavaScript file you can change the display options. Here is the portion of the JavaScript that changes the options.
var options = {\n nodes: {\n shape: 'dot',\n size: 10,\n font: {\n size: 14,\n },\n },\n edges: {\n arrows: 'to',\n smooth: true,\n },\n physics: {\n stabilization: false,\n },\n};\n
"},{"location":"sims/knolwedge-graph/graph/#left-to-right-layout","title":"Left to Right Layout","text":"HTML Page for Dependency Graph
var options = {\nlayout: {\n hierarchical: {\n direction: 'LR', // Left to right\n sortMethod: 'directed', // Sort nodes based on dependencies\n nodeSpacing: 200, // Adjust spacing if needed\n levelSeparation: 150 // Adjust for horizontal space between levels\n }\n }\n}\n
"},{"location":"sims/knolwedge-graph/graph/#full-layout-options","title":"Full Layout Options","text":"layout: {\n randomSeed: undefined,\n improvedLayout:true,\n clusterThreshold: 150,\n hierarchical: {\n enabled:false,\n levelSeparation: 150,\n nodeSpacing: 100,\n treeSpacing: 200,\n blockShifting: true,\n edgeMinimization: true,\n parentCentralization: true,\n direction: 'LR', // UD, DU, LR, RL\n sortMethod: 'hubsize', // hubsize, directed\n shakeTowards: 'leaves' // roots, leaves\n }\n }\n ```\n\n## Hierarchical Layout User Defined\nhttps://visjs.github.io/vis-network/examples/network/layout/hierarchicalLayoutUserdefined.html\n\n```js\n var options = {\n edges: {\n smooth: {\n type: \"cubicBezier\",\n forceDirection:\n directionInput.value == \"UD\" || directionInput.value == \"DU\"\n ? \"vertical\"\n : \"horizontal\",\n roundness: 0.4,\n },\n },\n layout: {\n hierarchical: {\n direction: directionInput.value,\n },\n },\n physics: false,\n };\n
"},{"location":"sims/knolwedge-graph/graph/#category-colors","title":"Category Colors","text":"We can use generative AI to categorize each concept. Here are 11 categories of the concepts
CategoryID Color CategoryLabel 1 red Fundamental Concepts 2 orange Passive Components 3 yellow Active Components and Semiconductor Devices 4 green Circuit Analysis Techniques 5 cyan AC Circuit Concepts 6 blue Transient Analysis 7 purple Signal Processing and Filters 8 pink Amplifiers and Analog Circuits 9 gray Power Electronics 10 olive Control Systems and Stability 11 brown Types of CircuitsCategory Colors Demo
"},{"location":"sims/knolwedge-graph/graph/#reference","title":"Reference","text":"Vis.js Network Layout Methods
"},{"location":"sims/knolwedge-graph/graph/concept-taxonomy/","title":"Concept Taxonomy","text":""},{"location":"sims/knolwedge-graph/graph/concept-taxonomy/#prompt","title":"Prompt","text":"I'm glad to hear that you're pleased with the results so far! Creating a taxonomy to classify your 197 learning concepts will help organize the curriculum, ensure comprehensive coverage, and facilitate a logical progression of topics. Below is a suggested taxonomy with **10 classifications**, each accompanied by a description and examples of relevant concepts from your list.\n
"},{"location":"sims/knolwedge-graph/graph/concept-taxonomy/#1-computational-thinking-foundations","title":"1. Computational Thinking Foundations","text":"Description: Covers the basic principles and strategies of computational thinking, essential for problem-solving and algorithm development.
Relevant Concepts:
Description: Introduces the core concepts of programming that are applicable across various programming languages and environments.
Relevant Concepts:
Description: Focuses on programming using MicroPython, including syntax, environment setup, and MicroPython-specific features.
Relevant Concepts:
Description: Explores the physical components used in projects and how to interface them with the Raspberry Pi Pico.
Relevant Concepts:
Description: Introduces fundamental electronics concepts necessary for building and understanding circuits.
Relevant Concepts:
Description: Covers various sensors, their functionalities, and how to integrate and interpret data from them.
Relevant Concepts:
Description: Focuses on controlling various actuators such as motors, servos, and relays to perform physical actions.
Relevant Concepts:
Description: Details various communication protocols used for data transmission between devices and components.
Relevant Concepts:
Description: Explores IoT concepts, connectivity, data transmission, and integration with cloud services.
Relevant Concepts:
Description: Includes advanced programming techniques, best practices, version control, and emerging technologies.
Relevant Concepts:
Cross-Cutting Themes: Some concepts may span multiple categories. For example, \"Pulse Width Modulation\" could relate to both \"Electronics Basics\" and \"Actuator Control.\"
Prerequisites: Ensure that foundational categories like \"Computational Thinking Foundations\" and \"Programming Fundamentals\" are taught first, as they underpin many other topics.
Modular Organization: Consider organizing your curriculum into modules based on these classifications to provide a structured learning path.
Scalability: This taxonomy allows for the addition of new concepts or subcategories as the course evolves or as new technologies emerge.
For better visualization, here's a hierarchical representation of the taxonomy:
Here's how some of the concepts map to the suggested taxonomy:
Concept ID Concept Name Classification 1 Intro to Computational Thinking Computational Thinking Foundations --- --- --- 7 Variables in Programming Programming Fundamentals 14 Introduction to MicroPython MicroPython Programming 26 Microcontroller Basics Hardware Components and Interfacing 31 Circuit Basics Electronics Basics 99 Introduction to Sensors Sensor Integration 107 Introduction to Actuators Actuator Control 122 Serial Communication Protocols Communication Protocols 128 Internet of Things Concepts Internet of Things (IoT) 136 Programming Best Practices Advanced Topics and Best Practices"},{"location":"sims/knolwedge-graph/graph/concept-taxonomy/#final-remarks","title":"Final Remarks","text":"This taxonomy provides a structured framework to classify and organize your 197 learning concepts effectively. It ensures that foundational knowledge is established before progressing to more advanced topics, facilitating a coherent and comprehensive learning experience for high school students. Feel free to adjust the classifications or add subcategories to better fit the specific needs and goals of your course.
o1-mini
"},{"location":"sims/knolwedge-graph/graph/concept-taxonomy/#you-said","title":"You said:","text":"I love this taxonomy. For each of the ten classifiers, use the number as a group ID. Next, regenerate the csv file, but add one more column. That column is the group ID for that learning concept.
"},{"location":"sims/knolwedge-graph/graph/concept-taxonomy/#notes-on-classification","title":"Notes on Classification","text":"Group 1: Computational Thinking Foundations
Group 2: Programming Fundamentals
Concepts 6-8, 12-13, 127
Group 3: MicroPython Programming
Concepts 14-19, 20-25, 38-42, 49-57, 61-65, 145-149
Group 4: Hardware Components and Interfacing
Concepts 26-28, 30-37, 39-44, 72-84, 85-89, 95-113, 185-188
Group 5: Electronics Basics
Concepts 31-36, 43-48, 59-61, 65-67, 157-177
Group 6: Sensor Integration
Concepts 47-50, 63-68, 99-106, 120-124, 181-184, 182-183
Group 7: Actuator Control
Concepts 51-57, 60-62, 112-116
Group 8: Communication Protocols
Concepts 69-70, 71-73, 78-80, 88-90, 117-119, 121-123
Group 9: Internet of Things (IoT)
Concepts 125-135, 128-134
Group 10: Advanced Topics and Best Practices
Concepts 136-156, 189-194, 195-196
Overlap: Some concepts naturally span multiple groups. For simplicity, each concept is assigned to the most relevant single group.
Dependencies: Ensure that prerequisite concepts (as per the dependencies) are taught before dependent concepts to maintain a coherent learning progression.
Modular Structure: Organizing the course into these groups can help structure modules or units, allowing for focused learning and easier curriculum management.
This taxonomy and classification will aid in structuring your course effectively, ensuring that foundational knowledge is established before advancing to more complex topics. It also facilitates identifying any gaps in the curriculum and ensuring a balanced coverage of computational thinking, programming, hardware interfacing, and advanced concepts.
"},{"location":"sound/","title":"Introduction to Sound and Music in MicroPython","text":""},{"location":"sound/#how-microcontrollers-generate-sound","title":"How Microcontrollers Generate Sound","text":"Microcontrollers are really great at generating digital outputs on their GPIO pins. These digital signals that quickly switch between zero and a positive voltage like 3.3 or 5 volts. However, they are not designed to create \"analog\" output of a continuous varying voltage. However, we can use a technique called \"Pulse Width Modulation\" to simulate the various frequencies of sound using digital only outputs.
Pulse Width Modulation is the process of changing not the height of a electrical signal, but the width between the pulses of digital signals. By changing the distance of the spaces between the digital signals we can generate a signal that will sound like it has a higher or lower frequency or pitch.
MicroPython provides a powerful library of tools for you to easily generate pulses of different shapes. This is called the PWM library. Will will use this in our sound and music programs. Here is a sample of how this is called in our code:
"},{"location":"sound/#duty-cycle","title":"Duty Cycle","text":"The Duty Cycle is what percent of time a pulse is high.
For working with sound, we want to generate smooth sound waves that are on 1/2 of the time and off 1/2 of the time. So our duty cycles will be set to be 50%. On the Raspberry Pi Pico we can achieve this by the following function:
speaker.duty_u16(1000)\n
When we are done playing a tone, we must always explicitly turn the duty cycle back to 0.
speaker.duty_u16(0)\n
If we forget to add this line, the tone will continue to play despite the main program stopping. This shows you that the part of the chip that generates the tone pulses is an independent processor that is not dependant on the main program running!
from machine import Pin, PWM\nfrom utime import sleep\n
Note that we will also need to pause between notes, so will use the sleep library to pause execution of our sound generation.
"},{"location":"sound/#connecting-a-sound-device","title":"Connecting a Sound Device","text":"There are several different ways that you can connect a sound device to you MicroController. Here are three options:
"},{"location":"sound/#references","title":"References","text":"
1, Juke Phone in C - convert an old land-line telephone to an MP3-player Jukebox. Although this is written in C, it could be converted into MicroPython.
"},{"location":"sound/02-play-tone/","title":"Play Tones Using the PWM","text":"Since we will be using the sleep function many times, we will import it by name from the Micropthon time library like this:
from utime import sleep\n
Now, instead of putting utime.sleep(.5)
we can just reference sleep directly like this:
sleep(.5)\n
This will pause for 1/2 a second. This is how long we wait for a tone to stay on or go off. The nice thing about this menthod is that our code is a little smaller. However, you can't run other functions in the utime library. So if you want to add them later you will need to import them also, just like we did for the sleep function.
"},{"location":"sound/02-play-tone/#lab-1-play-a-single-tone","title":"Lab 1: Play A Single Tone","text":"from machine import Pin, PWM\nfrom utime import sleep\n\n# lower right corner with USB connector on top\nSPEAKER_PIN = 16\n\n# create a Pulse Width Modulation Object on this pin\nspeaker = PWM(Pin(SPEAKER_PIN))\n# set the duty cycle to be 50%\nspeaker.duty_u16(1000)\nspeaker.freq(1000) # 50% on and off\nsleep(1) # wait a second\nspeaker.duty_u16(0)\n# turn off the PWM circuits off with a zero duty cycle\nspeaker.duty_u16(0)\n
Note
The tone will keep sounding until you turn the speaker duty to 0. This shows you that the circuitry that is generating the sound is independent of the main CPU.
"},{"location":"sound/02-play-tone/#experiments","title":"Experiments","text":"speaker.freq(1000)
and rerunning the program. Try using values from 10 to 10000. What values seem the loudest to your ear?In this lesson we will play three consecutive tones. Each tone will have a specific time on and we will put a time between the tones.
from machine import Pin, PWM\nfrom utime import sleep\n\n# lower right corner with USB connector on top\nSPEAKER_PIN = 16\n\n# create a Pulse Width Modulation Object on this pin\nspeaker = PWM(Pin(SPEAKER_PIN))\n\nspeaker.duty_u16(1000)\nspeaker.freq(300) # 1 Kilohertz\nsleep(.5) # wait a 1/4 second\nspeaker.duty_u16(0)\nsleep(.25)\n\nspeaker.duty_u16(1000)\nspeaker.freq(800)\nsleep(.5)\nspeaker.duty_u16(0)\nsleep(.25)\n\nspeaker.duty_u16(1000)\nspeaker.freq(400)\nsleep(.5)\n\n# turn off the PWM \nspeaker.duty_u16(0)\n
"},{"location":"sound/03-play-three-tones/#using-variables","title":"Using Variables","text":"We can also put the time each tone stays on and the space between the tones into variables so it is easier to modify the values in a single place.
# set the time each tone will be on\nONTIME = .5\n# the time between the tones\nOFFTIME = .100\n
"},{"location":"sound/03-play-three-tones/#three-tones-with-variables","title":"Three Tones With Variables","text":"from machine import Pin, PWM\nfrom utime import sleep\n\n# lower right corner with USB connector on top\nSPEAKER_PIN = 16\n\n# create a Pulse Width Modulation Object on this pin\nspeaker = PWM(Pin(SPEAKER_PIN))\n\n# the time each tone will be on\nON_TIME = .25\n# the time between the tones\nOFF_TIME = .1\n\n# Low tone\nspeaker.duty_u16(1000)\nspeaker.freq(300)\nsleep(ON_TIME)\nspeaker.duty_u16(0)\nsleep(OFF_TIME)\n\n# High tone\nspeaker.duty_u16(1000)\nspeaker.freq(800)\nsleep(ON_TIME)\nspeaker.duty_u16(0)\nsleep(OFF_TIME)\n\n# Medium tone\nspeaker.duty_u16(1000)\nspeaker.freq(400)\nsleep(ON_TIME)\n\n# turn off the PWM \nspeaker.duty_u16(0)\n
"},{"location":"sound/03-play-three-tones/#experiments","title":"Experiments","text":"In this lesson, we will learn about how to automatically generate various pitches. We will see that the spacing between lower pitches is different from the spacing between higher pitches.
Here is a program that plays a scale of notes from a starting frequency of 30 hertz to an upper frequency of around 10,000 hertz. Note that
from machine import Pin, PWM\nfrom utime import sleep\n\n# lower right corner with USB connector on top\nSPEAKER_PIN = 16\n\n# create a Pulse Width Modulation Object on this pin\nspeaker = PWM(Pin(SPEAKER_PIN))\n\ndef playtone(frequency):\n speaker.duty_u16(1000)\n speaker.freq(frequency)\n sleep(0.3)\n\ndef bequiet():\n speaker.duty_u16(0)\n\nfreq = 30\n\nfor i in range(64):\n print(freq)\n playtone(freq)\n freq = int(freq * 1.1)\n\n# Turn off the PWM\nspeaker.duty_u16(0)\n
"},{"location":"sound/04-play-scale/#new-frequency-spacing","title":"New Frequency Spacing","text":"When you run the prior example, note the frequencies printed to the console. Are they evenly spaced?
Take a close look at the line that creates a new frequency:
freq = int(freq * 1.1)\n
The effect of this line is to create a new frequency that is 10% higher than the prior frequency.
"},{"location":"sound/04-play-scale/#experiments","title":"Experiments","text":"freq = freq +100
This program will play the theme music from the Mario video game.
from machine import Pin, PWM\nfrom utime import sleep\nbuzzer = PWM(Pin(16))\n\ntones = {\n\"B0\": 31,\"C1\": 33,\"CS1\": 35,\"D1\": 37,\"DS1\": 39,\"E1\": 41,\"F1\": 44,\"FS1\": 46,\n\"G1\": 49,\"GS1\": 52,\"A1\": 55,\"AS1\": 58,\"B1\": 62,\"C2\": 65,\n\"CS2\": 69,\"D2\": 73,\"DS2\": 78,\"E2\": 82,\"F2\": 87,\"FS2\": 93,\"G2\": 98,\n\"GS2\": 104,\"A2\": 110,\"AS2\": 117,\"B2\": 123,\"C3\": 131,\"CS3\": 139,\n\"D3\": 147,\"DS3\": 156,\"E3\": 165,\"F3\": 175,\"FS3\": 185,\n\"G3\": 196,\"GS3\": 208,\"A3\": 220,\"AS3\": 233,\"B3\": 247,\"C4\": 262,\"CS4\": 277,\"D4\": 294,\"DS4\": 311,\n\"E4\": 330,\"F4\": 349,\"FS4\": 370,\"G4\": 392,\"GS4\": 415,\"A4\": 440,\"AS4\": 466,\"B4\": 494,\"C5\": 523,\"CS5\": 554,\"D5\": 587,\"DS5\": 622,\"E5\": 659,\"F5\": 698,\n\"FS5\": 740,\"G5\": 784,\"GS5\": 831,\"A5\": 880,\"AS5\": 932,\"B5\": 988,\"C6\": 1047,\"CS6\": 1109,\"D6\": 1175,\"DS6\": 1245,\"E6\": 1319,\"F6\": 1397,\"FS6\": 1480,\"G6\": 1568,\"GS6\": 1661,\n\"A6\": 1760,\"AS6\": 1865,\"B6\": 1976,\"C7\": 2093,\"CS7\": 2217,\"D7\": 2349,\"DS7\": 2489,\"E7\": 2637,\"F7\": 2794,\"FS7\": 2960,\"G7\": 3136,\"GS7\": 3322,\"A7\": 3520,\n\"AS7\": 3729,\"B7\": 3951,\"C8\": 4186,\"CS8\": 4435,\"D8\": 4699,\"DS8\": 4978\n}\n\nsong = [\"E5\",\"G5\",\"A5\",\"P\",\"E5\",\"G5\",\"B5\",\"A5\",\"P\",\"E5\",\"G5\",\"A5\",\"P\",\"G5\",\"E5\"]\nmario = [\"E7\", \"E7\", 0, \"E7\", 0, \"C7\", \"E7\", 0, \"G7\", 0, 0, 0, \"G6\", 0, 0, 0, \"C7\", 0, 0, \"G6\",\n 0, 0, \"E6\", 0, 0, \"A6\", 0, \"B6\", 0, \"AS6\", \"A6\", 0, \"G6\", \"E7\", 0, \"G7\", \"A7\", 0, \"F7\", \"G7\",\n 0, \"E7\", 0,\"C7\", \"D7\", \"B6\", 0, 0, \"C7\", 0, 0, \"G6\", 0, 0, \"E6\", 0, 0, \"A6\", 0, \"B6\", 0,\n \"AS6\", \"A6\", 0, \"G6\", \"E7\", 0, \"G7\", \"A7\", 0, \"F7\", \"G7\", 0, \"E7\", 0,\"C7\", \"D7\", \"B6\", 0, 0]\n\ndef playtone(frequency):\n buzzer.duty_u16(1000)\n buzzer.freq(frequency)\n\ndef bequiet():\n buzzer.duty_u16(0)\n\ndef playsong(mysong):\n for i in range(len(mysong)):\n if (mysong[i] == \"P\" or mysong[i] == 0 ):\n bequiet()\n else:\n playtone(tones[mysong[i]])\n sleep(0.3)\n bequiet()\nplaysong(mario)\n
"},{"location":"sound/06-eight-key-piano/","title":"Eight Key Piano","text":"In this lab we wire up eight momentary press buttons so that when each one is pressed it will play a different note.
To create this project, you will need the following parts:
Each \"key\" is a momentary press button that is wired up between a GPIO pin and the +3.3 volt rail on the breadboard connected to 3V3(OUT) pin. We do not need to use any resistors to pull the signals low since we configure the pins to be inputs with the PULL_DOWN resistor like this:
button_pin_1 = machine.Pin(10, machine.Pin.IN, machine.Pin.PULL_DOWN)\n
"},{"location":"sound/06-eight-key-piano/#the-play-tone-functions","title":"The Play Tone Functions","text":"We will use two Python functions, one for playing a tone of a given frequency and one for turning off the sound.
def playtone(frequency):\n speaker.duty_u16(1000) # turn the PWM duty to 50%\n speaker.freq(frequency)\n builtin_led.high() # turn builtin LED on\n\ndef bequiet():\n speaker.duty_u16(0) # turn off the speaker PWM\n builtin_led.low() # turn builtin LED off\n
We will also use the .value()
method on each pin to detect if it is HIGH (1) like this:
if button_pin_1.value() == 1:\n playtone(220) # A3\n
We will be playing \"notes\" generating square waves with various frequencies from our lowest note of A3 at 220 Hz up to A4 at 440 Hz.
"},{"location":"sound/06-eight-key-piano/#sample-code","title":"Sample Code","text":"# play a tone durning button down\nfrom machine import Pin, PWM\nfrom utime import sleep, ticks_ms\n\nSPEAKER_PIN = 22 # pass through a speaker and tie the other end to GND\nspeaker = PWM(Pin(SPEAKER_PIN))\n\nbuiltin_led = machine.Pin(25, Pin.OUT)\n\n# Connect these GP pins through a button to the +3.3 volt rail\nbutton_pin_1 = machine.Pin(10, machine.Pin.IN, machine.Pin.PULL_DOWN)\nbutton_pin_2 = machine.Pin(11, machine.Pin.IN, machine.Pin.PULL_DOWN)\nbutton_pin_3 = machine.Pin(12, machine.Pin.IN, machine.Pin.PULL_DOWN)\nbutton_pin_4 = machine.Pin(13, machine.Pin.IN, machine.Pin.PULL_DOWN)\nbutton_pin_5 = machine.Pin(14, machine.Pin.IN, machine.Pin.PULL_DOWN)\nbutton_pin_6 = machine.Pin(15, machine.Pin.IN, machine.Pin.PULL_DOWN)\nbutton_pin_7 = machine.Pin(16, machine.Pin.IN, machine.Pin.PULL_DOWN)\nbutton_pin_8 = machine.Pin(17, machine.Pin.IN, machine.Pin.PULL_DOWN)\n\ndef playtone(frequency):\n speaker.duty_u16(1000) # turn the PWM duty to 50%\n speaker.freq(frequency)\n builtin_led.high() # turn builtin LED on\n\ndef bequiet():\n speaker.duty_u16(0) # turn off the speaker PWM\n builtin_led.low() # turn builtin LED off\n\nwhile True:\n if button_pin_1.value() == 1:\n playtone(220) # A3\n elif button_pin_2.value() == 1:\n playtone(247) # B3\n elif button_pin_3.value() == 1:\n playtone(262) # C4\n elif button_pin_4.value() == 1:\n playtone(294) # D4\n elif button_pin_5.value() == 1:\n playtone(330) # E4\n elif button_pin_6.value() == 1:\n playtone(349) # F4\n elif button_pin_7.value() == 1:\n playtone(392) # G4\n elif button_pin_8.value() == 1:\n playtone(440) # A4\n else:\n bequiet()\n
"},{"location":"sound/06-eight-key-piano/#exercises","title":"Exercises","text":"Note
This lesson is still a work in progress. The wavePlayer is in an early draft form and has unpredictable interactions with other components. See the wav file test results section below. We also are having problems when different GPIO pins are used. If you stick to pins for GPIO 2 and 3 for the two channels the test run OK.
"},{"location":"sound/07-play-audio-file/#playing-sounds-on-the-rp2040-chip","title":"Playing Sounds on The RP2040 Chip","text":"Although we can play tones of various pitches on the PR2040 using the PMW to generate square waves, the quality of this sound is not close to high-fidelity sound like you would expect in a personal MP3 audio player.
In this lesson we will demonstrate how to play a high-quality audio files that are stored on the two megabytes of non-volatile static memory of the Pico. According to the specification of the Raspberry Pi Pico, the system comes with 2MB on-board QSPI Flash that we can use to store sound files. By combining our Pico with an SD card reader we can also play many sounds and even full-length music and full albums.
"},{"location":"sound/07-play-audio-file/#background-on-audio-encoding","title":"Background on Audio Encoding","text":"Audio files are encoded in many formats. For this lab there are two key concepts to understand:
For our labs, we will be using mono files (not stereo) a sampling rate of 8,000 samples per second (8K Hz) and a sampling rate of 16-bit depth. This is a good compromise between smaller size and sound fidelity in typical robots.
Our robots typically will play a short 1-second sound to tell us they are performing an action like stopping, backing up or turning. This one-second 8K Hz WAV file will be about 20K. Our flash budget for the Raspberry Pi Pico is 2M, so we can easily store 10 sound effects in 200K or 1/10 of our available flash memory. We can also add a SD card if we need more flash memory.
We will be using standard .WAV files with Pulse Code Modulation Encoding in .WAV files. WAV files do take more space than compressed MP3 files, but they are easier to play because the decoding steps are trivial for a microcontroller to perform.
"},{"location":"sound/07-play-audio-file/#overall-architecture","title":"Overall Architecture","text":"/sounds
import os\n\nwaveFolder= \"/sounds\"\n\ntotal = 0\n# get a list of .wav files and their sizes\nfor file in os.listdir(waveFolder):\n size = os.path.getsize(file)\n print(file, size)\n total += size\nprint('Total sound directory size:', total)\n
"},{"location":"sound/07-play-audio-file/#connections","title":"Connections","text":"Some of this documentation was take from Dan Perron's Pico Audio GitHub Repo.
In these tests we used GPIO pins 2 and 3 to drive the left and right channels of audio that are sent to a stereo amplifier. You can use both an amplifier or head phone with a 1K resistor in series on the pins to limit the current from the 3.3v output signals.
The myPWM subclass set the maximum count to 255 (8 bits) or 1023(10bits) at a frequency around 122.5KHz.
The PWM is now on 10 bits (0..1023)
The myDMA class allows to use direct memory access to transfer each frame at the current sample rate.
We will need to install the wave.py and chunk.py from Jokey GitHub Awesome MicroPython Repo on root file system or the /lib folder on the pico file system.
Don't forget to increase the SPI clock up to 3Mhz. (TODO not sure what this means)
The following documentation was take from Daniel Perron's Github page.
wave.py
which will set the sample rate and read the audio data by chunkgit clone https://github.com/danjperron/PicoAudioPWM\ncd PicoAudioPWM\n
"},{"location":"sound/07-play-audio-file/#download-some-test-robot-wav-files","title":"Download some test robot wav files","text":"The following GitHub location:
https://github.com/CoderDojoTC/robot-media/tree/master/wav-8k
contains a set of 8K Hz 16 bit robot sounds that you can use with your robot.
"},{"location":"sound/07-play-audio-file/#converting-mp3-to-wav-files","title":"Converting .MP3 to .WAV files","text":"This software only currently support playing .wav files since it is easy to convert these files into a format that can be played. WAV files store uncompressed audio, so they are larger than MP3 files. Wav files are simple ways to store sound patterns. MP3 files are much more complex and require complex algorithms to convert into sound outputs.
The usual bitstream encoding is the linear pulse-code modulation (LPCM) format.
If you have just a few MP3 files, can use the following web-site to convert MP3 files into wave files:
Cloud Convert Service that Converts MP3 to WAV files
The next lab shows you how to convert an entire folder of MP3 files to 8K Hz 16-bit WAV files using a shell script.
"},{"location":"sound/07-play-audio-file/#copy-sound-files-to-the-pico","title":"Copy Sound Files to the Pico","text":"Your pico has 2MB of static memory. A typical robot sound effect you want to play when you bump into and object will play for around 1 second. You can copy many sound effect files to the pico file system and play them. Some IDEs may allow you to do this or you can use the rshell program.
Here is an example of using the rshell program to copy a directory of wav files to the pico. Lines that start with pc$ are commands that you type into your PC or MAC's terminal. Lines that start with rs$ are commands that you type into the rshell.
# list the devices (only works on Mac and UNIX)\npc$ ls /dev/cu.modem*\n# start the rshell\npc$ rshell -p /dev/cu.modem*\n# change the name of the device to be \"pico\"\nrs$ echo 'name=\"pico\"' > /pyboard/board.py\n# exit from rshell - can also use exit\nCONTROL-C\n# reconnect with the rshell\n$pc rshell -p /dev/cu.modem*\n# go into the /pico file systems\n$rs cd /pico\n# create a directory for all our sound files\nmkdir sounds\n# copy files from hour PC's home ~/tmp/sounds dir to the pico\nrs$ cp ~/tmp/sounds/*.wav /pico/sounds\nrs$ ls /pico/sounds\n
"},{"location":"sound/07-play-audio-file/#listing-the-wave-files","title":"Listing the Wave Files","text":"After you have a list of Wave files loaded you can verify them by using the os listdir() function.
import os\n\nwaveFolder= \"/sounds\"\nwavelist = []\n\n# get a list of .wav files\nfor i in os.listdir(waveFolder):\n if i.find(\".wav\")>=0:\n wavelist.append(waveFolder+\"/\"+i)\n elif i.find(\".WAV\")>=0:\n wavelist.append(waveFolder+\"/\"+i)\n\nif not wavelist :\n print(\"Warning NO '.wav' files\")\nelse:\n for i in wavelist:\n print(i)\n
Sample console output
/sounds/cylon-attention.wav\n/sounds/cylon-by-your-command.wav\n/sounds/cylon-excellent.wav\n/sounds/cylon-eye-scanner.wav\n/sounds/cylon-see-that-the-humans.wav\n/sounds/cylon-those-are-not-the-sounds.wav\n
"},{"location":"sound/07-play-audio-file/#checking-the-wav-file-format","title":"Checking the WAV File Format","text":"There is a standard Python module called `wave.py
that reads the .wav files and shows the metadata for the file. Wave files come in many formats, single channel, stereo and different bit rates. The wave player can show us all this data that describes the wave file.
The report shows you how to use fixed-width formatting since the file names and data should fit in columns to make it easier to read.
import os\nimport wave\n\nwaveFolder= \"/sounds\"\nwavelist = []\n\n# get a list of .wav files\nfor i in os.listdir(waveFolder):\n if i.find(\".wav\")>=0:\n wavelist.append(waveFolder+\"/\"+i)\n elif i.find(\".WAV\")>=0:\n wavelist.append(waveFolder+\"/\"+i)\n\nif not wavelist :\n print(\"Warning NO '.wav' files\")\nelse:\n print(\"{0:<45}\".format('File Path'), 'Frame Rate Width Chans Frames')\n for filename in wavelist:\n f = wave.open(filename,'rb')\n # the format string \"{0:<50}\" says print left justified from chars 0 to 50 in a fixed with string\n print(\"{0:<50}\".format(filename),\n \"{0:>5}\".format(f.getframerate()),\n \"{0:>5}\".format(f.getsampwidth()),\n \"{0:>6}\".format(f.getnchannels()),\n \"{0:>6}\".format(f.getnframes())\n )\n
Sample Response
File Path Frame Rate Width Chans Frames\n/sounds/cylon-attention.wav 8000 1 1 6399\n/sounds/cylon-by-your-command.wav 11025 1 1 12583\n/sounds/cylon-excellent.wav 22050 1 1 48736\n/sounds/cylon-eye-scanner.wav 16000 2 2 24768\n/sounds/cylon-see-that-the-humans.wav 11025 1 1 30743\n/sounds/cylon-those-are-not-the-sounds.wav 22050 1 1 64137\n
"},{"location":"sound/07-play-audio-file/#adding-an-interrupt","title":"Adding an Interrupt","text":"If you halt the RP2040 while it is playing a sound, the independent PWM controllers will continue to generate sound. In order to shut the independent PWM controllers, an interrupt controller system must be used to cleanly disable all the sound. Here is an example of this using a try/except block of code.
import os as uos\nfrom wavePlayer import wavePlayer\nplayer = wavePlayer()\n\ntry:\n while True:\n # repeat this over and over until the keyboard shuts down the circuit\n player.play('/sounds/cylon-eye-scanner.wav')\nexcept KeyboardInterrupt:\n player.stop()\n print(\"wave player terminated\")\n
"},{"location":"sound/07-play-audio-file/#playing-the-same-sound-repeatedly","title":"Playing the Same Sound Repeatedly","text":"import os as uos\nfrom wavePlayer import wavePlayer\nplayer = wavePlayer()\n\ntry:\n while True:\n player.play('/sounds/cylon-eye-scanner.wav')\nexcept KeyboardInterrupt:\n player.stop()\n print(\"wave player terminated\")\n
"},{"location":"sound/07-play-audio-file/#downloading-the-audio-libraries","title":"Downloading the Audio Libraries","text":"Both the wave.py and the chunck.py files are here:
https://github.com/joeky888/awesome-micropython-lib/tree/master/Audio
"},{"location":"sound/07-play-audio-file/#references","title":"References","text":"In this lab, we will be testing stereo audio ports. We will be sending a 1 kilohertz square wave to the left and then right ports. This program allows you to test your stereo connections and make sure that both channels are working correctly.
from machine import Pin, PWM\nfrom utime import sleep\n\n# You will need to configure these two digital output ports\nAUDIO_LEFT_PIN = 18\nAUDIO_RIGHT_PIN = 19\n\n# create a Pulse Width Modulation Object on this pin\nleft_speaker = PWM(Pin(AUDIO_LEFT_PIN))\n# set the duty cycle to be 50%\nleft_speaker.duty_u16(1000)\nleft_speaker.freq(1000) # 50% on and off\nsleep(1) # wait a second\nleft_speaker.duty_u16(0)\n# turn off the PWM circuits off with a zero duty cycle\nleft_speaker.duty_u16(0)\nsleep(1)\n\n# create a Pulse Width Modulation Object on this pin\nright_speaker = PWM(Pin(AUDIO_RIGHT_PIN))\n# set the duty cycle to be 50%\nright_speaker.duty_u16(1000)\nright_speaker.freq(1000) # 50% on and off\nsleep(1) # wait a second\nright_speaker.duty_u16(0)\n# turn off the PWM circuits off with a zero duty cycle\nright_speaker.duty_u16(0)\n
"},{"location":"sound/07a-test-audio-ports/#references","title":"References","text":"https://en.wikipedia.org/wiki/Phone_connector_(audio)#Computer_sound
"},{"location":"sound/08-i2s-standard/","title":"I2S Standard","text":"I2S (Inter-IC Sound) pronounced \"eye-two-ess\", is an electrical serial bus interface standard used for connecting digital audio devices together. It is a general interface to communicate PCM audio data between integrated circuits in an electronic device.
Note that I2S is unrelated to the similar sounding I2C bus protocol.
Since MicroPython 1.17 we have a library that allows us to play
"},{"location":"sound/09-converting-mp3-to-wav/","title":"Converting MP3 to WAV formats","text":"In last lab we will learned how to play an audio file stored on the flash memory or SD card. We used an early version of a Python module that plays .WAV files in a fixed format: 8,000 samples per second using 16-bit encoding. Because there are hundreds of different formats of audio files, we need a consistent way to convert all of these formats to 8K samples/second 16-bit .WAV formats.
The audio data file conversions are done on your host PC, not the microcontroller. This allows us to use a rich library of tools that don't need to run on our microcontroller.
"},{"location":"sound/09-converting-mp3-to-wav/#method-1-use-the-ffmpeg-command-line-tool","title":"Method 1: Use the ffmpeg Command Line Tool","text":"One of the easiest ways to get started is to go to the web site ffmpeg.org and download the program that does the conversion using a command line. Note that on a Mac you will need to go into your System Preferences and indicate that the following programs are trusted:
Here are the direct links for MacOS
This will download a zip file which you will need to unzip and place in a location such as ~/bin
. After you do this make sure you add `~/bin
to your path by adding the following file to your .bash_profile:
PATH=$PATH:~/bin
After you source your .bash_profile type in the following:
which ffmpeg
This should return the location that it finds the ffmpeg shell script. You can then see the many file-format MPEG options:
ffmpeg --help
To get the format we need for the MicroPython wave player class we just specific -i
for the input file and use the -ar 8000
to specify the output bit rate of 8K samples per second. The final parameter is a file name that must in .wav
so the command knows to use WAV PCM encoding. The default value is 16 gits per sample.
ffmpeg -i r2d2-beeping.mp3 -ar 8000 r2d2-beeping-8k.wav\n
"},{"location":"sound/09-converting-mp3-to-wav/#bulk-conversions","title":"Bulk Conversions","text":"We can use unix shell commands to do a batch conversion of all the . The following is an example of using awk and sed to convert all the .mp3 files in a directory and convert them to 8K Hz WAV files and put them in a sibling directory.
ls -1 *.mp3 | awk '{print \"ffmpeg -i \" $1 \" -ar 8000 ../wav-8k/\"$1}' | sed s/mp3$/wav/ | sh\n
"},{"location":"sound/09-converting-mp3-to-wav/#inspect-the-files-using-the-unix-file-command","title":"Inspect the Files Using the UNIX file command:","text":"cd ../wav-8k\nfile *.wav\n
returns
r2d2-another-beep.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 8000 Hz\nr2d2-beeping-2.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 8000 Hz\nr2d2-beeping-4.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 8000 Hz\nr2d2-beeping-8k.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 8000 Hz\nr2d2-beeping-like-an-alarm.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 8000 Hz\nr2d2-beeping.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 8000 Hz\nr2d2-cheerful.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 8000 Hz\nr2d2-determined.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 8000 Hz\nr2d2-excited.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 8000 Hz\nr2d2-laughing.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 8000 Hz\nr2d2-more-chatter.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 8000 Hz\nr2d2-processing.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 8000 Hz\nr2d2-sad.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 8000 Hz\nr2d2-shocked.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 8000 Hz\nr2d2-surprised.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 8000 Hz\nr2d2-taking-to-himself.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 8000 Hz\nr2d2-unsure.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 8000 Hz\n
Note that these are WAVE audio, Pulse-code Modulated (PCM), 16 bit and mono at 8K Hz.
"},{"location":"sound/09-converting-mp3-to-wav/#method-2-use-the-pydub-python-module","title":"Method 2: Use the pydub Python Module","text":"Note
This section is only for experienced Python developers.
"},{"location":"sound/09-converting-mp3-to-wav/#install-conda-and-the-python-libraries","title":"Install Conda and the Python libraries","text":"conda create -n mp3-to-wav python=3\nconda activate mp3-to-wav\npip install pydub ffprobe ffmpeg\n
Check your versions:
pip freeze\n
returns:
ffmpeg==1.4\nffprobe==0.5\npydub==0.25.1\n
"},{"location":"sound/09-converting-mp3-to-wav/#running-pydub","title":"Running pydub","text":"from pydub import AudioSegment\n\nsound = AudioSegment.from_mp3(\"r2d2-beeping.mp3\")\nsound.export(\"r2d2-beeping.wav\", format=\"wav\", tags={'Robot name': 'R2D2'})\n
"},{"location":"sound/09-converting-mp3-to-wav/#transferring-the-files-with-rshell","title":"Transferring the Files with Rshell","text":"cd ../wav-8k\nrshell -p /dev/cu.usbmodem14101\ncp *.wav /pico/sounds\n
"},{"location":"sound/09-converting-mp3-to-wav/#references","title":"References","text":"Warnining
I am not an expert on MIDI and I don't have any MIDI gear. This is mostly a collection of material I have for students that want to use MicroPython to generate music.
"},{"location":"sound/10-midi/#sample-code","title":"Sample Code","text":""},{"location":"sound/10-midi/#ascending-notes","title":"Ascending Notes","text":"# Play Test MIDI Ascending Notes\nfrom machine import Pin, UART\nimport time\nuart = UART(1, baudrate=31250, tx=Pin(4), rx=Pin(5))\nuart.init(bits=8, parity=None, stop=1)\nled=Pin(\"LED\", Pin.OUT)\nnote = 24\n\nwhile True:\n midimessage = bytearray([0x90, note, 64])\n uart.write(midimessage)\n led.toggle()\n # change third parameter to be 0\n midimessage = bytearray([0x90, note, 0])\n # pause 1/8 of a second\n time.sleep(0.125)\n note += 2\n if note > 64:\n note=24\n
"},{"location":"sound/10-midi/#references","title":"References","text":"Raspberry Pi Pico MIDI Development Board Midimuso for $20 US. This board has all the MIDI connectors on it.
Simple DIY electromusic Project
DIY Electro Music SDEMP MicroPython Code on GitHub
Pico MIDI by Barb Arach
PicoMIDI manual v1.0
"},{"location":"sound/11-sound-parts/","title":"Sound Parts","text":"PAM8302A Miniature 2.5W Class D Mono Audio Power Amplifier Module Board
PAM8302A 10pcs
These lessons are designed to give our students an understanding of how wireless communications work with MicroPython.
"},{"location":"wireless/01-intro/#raspberry-pi-pico-w","title":"Raspberry Pi Pico W","text":"One June 30th, 2022 the Raspberry Pi Foundation announced the availability of the Raspberry Pi Pico W. This $6 microprocessor now supports WiFi and with a software upgrade it may soon support Bluetooth.
The Pico W supports 2.4 Ghz 802.11n wireless networking. For MicroPython, we can use a MicroPython library built around the lwip TCP/IP stack. This stack is accessible using the MicroPython network functions.
The WiFi chip used is the Infineon CYW43439 chip. This chip also uses an ARM architecture and has extensive support for Bluetooth wireless communication.
You can read more about the capabilities of the WiFi/Bluetooth chip by reading the Infineon CYW43439 Datasheet. I found it interesting that the CYW43439 chip has 512KB of SRAM - almost double what the RP2040 chip contains!
"},{"location":"wireless/01-intro/#esp32-wireless","title":"ESP32 Wireless","text":"We have not integrated the ESP32 into our labs. We suggest you try the following links:
ESP32 MicroPython: Connecting to a WiFi Network on Tech Tutorials SX
MicroPython: Wi-Fi Manager with ESP32 (ESP8266 compatible) on Random Nerd Tutorials
"},{"location":"wireless/02-connecting-to-wifi/","title":"Connecting to a WiFi Network","text":""},{"location":"wireless/02-connecting-to-wifi/#setting-up-your-wifi-secretspy","title":"Setting Up Your WIFI secrets.py","text":"By convention, we put both our SSID and password in a python file called \"secrets.py\". This file should never be checked into a public source code repository. We can add secrets.py
to the .gitignore file to make sure the secrets.py is never checked into GitHub and exposing your passwords to everyone.
SSID = \"MY_WIFI_NETWORK_NAME\"\nPASSWORD = \"MY_WIFI_PASSWORD\"\n
By importing the secrets.py file you can then reference your network name like this:
print('Connecting to WiFi Network Name:', secrets.SSID)\n
"},{"location":"wireless/02-connecting-to-wifi/#testing-your-wifi-access-point-connection","title":"Testing Your WiFi Access Point Connection","text":"Here is a very simple script to test see if your network name and password are correct. This script may work, but as we will see, it is both slow and potentially unreliable.
import network\nimport secrets\nfrom utime import sleep\n\nprint('Connecting to WiFi Network Name:', secrets.SSID)\nwlan = network.WLAN(network.STA_IF)\nwlan.active(True) # power up the WiFi chip\nprint('Waiting for wifi chip to power up...')\nsleep(3) # wait three seconds for the chip to power up and initialize\nwlan.connect(secrets.SSID, secrets.PASSWORD)\nprint('Waiting for access point to log us in.')\nsleep(2)\nif wlan.isconnected():\n print('Success! We have connected to your access point!')\n print('Try to ping the device at', wlan.ifconfig()[0])\nelse:\n print('Failure! We have not connected to your access point! Check your secrets.py file for errors.')\n
Returns:
Connecting to WiFi Network Name: MY_WIFI_NETWORK_NAME\nWaiting for wifi chip to power up...\nWaiting for access point to log us in...\nSuccess! We have connected to your access point!\nTry to ping the device at 10.0.0.70\n
If the result is a Failure
you should check the name of the network and the password and that you are getting a strong WiFi signal where you are testing.
Note that we are using the sleep()
function to insert delays into our code. However, the results may actually be faster or slower than our sleep times. Our next step is to add logic that will test to see if the networking device is ready and if our local access point allows us to login correctly.
Sometimes we want to keep checking if our access point is connected before we begin using our connection. To do this we can create a while loop and continue in the loop while we are not connected.
import network\nimport secrets\nfrom utime import sleep, ticks_ms, ticks_diff\n\nprint('Connecting to WiFi Network Name:', secrets.SSID)\nwlan = network.WLAN(network.STA_IF)\nwlan.active(True)\n\nstart = ticks_ms() # start a millisecond counter\n\nif not wlan.isconnected():\n wlan.connect(secrets.SSID, secrets.PASSWORD)\n print(\"Waiting for connection...\")\n counter = 0\n while not wlan.isconnected():\n sleep(1)\n print(counter, '.', sep='', end='', )\n counter += 1\n\ndelta = ticks_diff(ticks_ms(), start)\nprint(\"Connect Time:\", delta, 'milliseconds')\nprint('IP Address:', wlan.ifconfig()[0])\n
This code also supports a timer that will display the number of seconds for the access point to become valid in the console. The first time after you power on, this may take several seconds. After you are connected the connection will be cached and the time will be 0 milliseconds.
First run upon power on might take several seconds:
>>> %Run -c $EDITOR_CONTENT\nConnecting to WiFi Network Name: MY_NETWORK_NAME\nWaiting for connection...\n0.1.2.3.Connect Time: 4640\nIP Address: 10.0.0.70\n
The second and consecutive runs will use a cached connection.
>>> %Run -c $EDITOR_CONTENT\nConnecting to WiFi Network Name: MY_NETWORK_NAME\nConnect Time: 0 milliseconds\nIP Address: 10.0.0.70\n>>>\n
"},{"location":"wireless/02-connecting-to-wifi/#error-handling","title":"Error Handling","text":"lan = network.WLAN(network.STA_IF)\nwlan.active(True)\nwlan.connect(ssid, password)\n\n# Wait for connect or fail\nmax_wait = 10\nwhile max_wait > 0:\n if wlan.status() < 0 or wlan.status() >= 3:\n break\n max_wait -= 1\n print('waiting for connection...')\n time.sleep(1)\n\n# Handle connection error\nif wlan.status() != 3:\n raise RuntimeError('network connection failed')\nelse:\n print('connected')\n status = wlan.ifconfig()\n print( 'ip = ' + status[0] )\n
The full TCP/IP stack is running on your Pico W. You should be able to ping the pico using the IP address returned by the status[0] of the wlan.ifconfig() function above.
"},{"location":"wireless/03-http-get/","title":"HTTP GET","text":""},{"location":"wireless/03-http-get/#testing-http-get","title":"Testing HTTP GET","text":"The following example was taken from Tom's Hardware
import network\nimport secrets\nfrom utime import sleep, ticks_ms, ticks_diff\nimport urequests\n\nwlan = network.WLAN(network.STA_IF)\nwlan.active(True)\nwlan.connect(secrets.SSID, secrets.PASSWORD)\n\nstart = ticks_ms() # start a millisecond counter\n\nastronauts = urequests.get(\"http://api.open-notify.org/astros.json\").json()\n\ndelta = ticks_diff(ticks_ms(), start)\n\nnumber = astronauts['number']\nprint('There are', number, 'astronauts in space.')\nfor i in range(number):\n print(i+1, astronauts['people'][i]['name'])\n\nprint(\"HTTP GET Time in milliseconds:\", delta)\n
Returns:
There are 10 astronauts in space.\n1 Oleg Artemyev\n2 Denis Matveev\n3 Sergey Korsakov\n4 Kjell Lindgren\n5 Bob Hines\n6 Samantha Cristoforetti\n7 Jessica Watkins\n8 Cai Xuzhe\n9 Chen Dong\n10 Liu Yang\nHTTP GET Time in milliseconds: 786\n
"},{"location":"wireless/04-web-server/","title":"Web Server with MicroPython","text":"This program turns your Pico W into a small web server. The web page has two links on it. One link will turn the on-board LED on and the other link will turn the LED off.
Screen image of Pico W Web Server:
# Code taken from https://www.cnx-software.com/2022/07/03/getting-started-with-wifi-on-raspberry-pi-pico-w-board/\nimport network\nimport socket\nimport time\nimport secrets\n\nfrom machine import Pin\n\n# Select the onboard LED\nled = machine.Pin(\"LED\", machine.Pin.OUT)\n\nwlan = network.WLAN(network.STA_IF)\nwlan.active(True)\nwlan.connect(secrets.SSID, secrets.PASSWORD)\nstateis = \"LED is OFF\"\n\nhtml = \"\"\"<!DOCTYPE html>\n<html>\n <head>\n <title>Web Server On Pico W </title>\n </head>\n <body>\n <h1>Pico Wireless Web Server</h1>\n <p>%s</p>\n <a href=\"/light/on\">Turn On</a>\n <a href=\"/light/off\">Turn Off</a>\n </body>\n</html>\n\"\"\"\n\n# Wait for connect or fail\nmax_wait = 10\nwhile max_wait > 0:\n if wlan.status() < 0 or wlan.status() >= 3:\n break\n max_wait -= 1\n print('waiting for connection...')\n time.sleep(1)\n\n# Handle connection error\nif wlan.status() != 3:\n raise RuntimeError('network connection failed')\nelse:\n print('We are connected to WiFI access point:', secrets.SSID)\n status = wlan.ifconfig()\n print( 'The IP address of the pico W is:', status[0] )\n\n# Open socket\naddr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]\nprint('addr:', addr)\ns = socket.socket()\n#if not addr:\ns.bind(addr)\ns.listen(1)\n\nprint('listening on', addr)\n\n# Listen for connections\nwhile True:\n try:\n cl, addr = s.accept()\n print('client connected from', addr)\n request = cl.recv(1024)\n print(request)\n request = str(request)\n led_on = request.find('/light/on')\n led_off = request.find('/light/off')\n print( 'led on = ' + str(led_on))\n print( 'led off = ' + str(led_off))\n\n if led_on == 6:\n print(\"led on\")\n led.value(1)\n stateis = \"LED is ON\"\n\n if led_off == 6:\n print(\"led off\")\n led.value(0)\n stateis = \"LED is OFF\"\n # generate the we page with the stateis as a parameter\n response = html % stateis\n cl.send('HTTP/1.0 200 OK\\r\\nContent-type: text/html\\r\\n\\r\\n')\n cl.send(response)\n cl.close()\n\n except OSError as e:\n cl.close()\n print('connection closed')\n
"},{"location":"wireless/04-web-server/#references","title":"References","text":""},{"location":"wireless/05-mac-address/","title":"Getting Your MAC Address","text":""},{"location":"wireless/05-mac-address/#what-is-a-mac-address","title":"What is a MAC Address?","text":"Every device that works with Ethernet and WiFi must have a Wikipedia Page on MAC Address.
A MAC address (media access control address) is a unique identifier assigned to a network interface controller (NIC) for use as a network address in communications within a network segment. This use is common in most IEEE 802 networking technologies, including Ethernet, Wi-Fi, and Bluetooth.
Note
The MAC address has nothing to do with the Apple Macintosh or Mac OS. They just use the same name to mean different things.
Understanding how devices use MAC addresses is essential to understanding how networks work and debugging them.
The MAC address is six bytes or \"octets\". The first three octets are assigned to the organization that created the device. The second three octets are assigned by the organization that created the device. See the Wikipedia Page on MAC Address for more information. If you run this on your Pico W the first octets should be similar.
Here are the two MAC addresses for two different Pico W devices that were purchase together. They came on adjacent parts of a \"real\" of devices.
28:cd:c1:1:35:54\n28:cd:c1:1:35:58\n
Because they were purchased together, their MAC address are very similar and only differ in the last few bits.
This Pico W came from another distributer.
28:cd:c1:0:9e:19\n
You can see that the first three octets are the same. A MAC address often can give clues about the origins of packets on your network. They can also be used as \"serial numbers\" for each device that connects to WiFi or Ethernet since they are usually burned into ROM at device manufacturing time and are designed to be unique.
"},{"location":"wireless/05-mac-address/#getting-the-macethernet-access","title":"Getting the MAC/Ethernet Access","text":"You can get the device MAC/Ethernet address and test the roundtrip time between the RP2040 and the WiFi chip using the MAC address function.
import network\nfrom utime import sleep, ticks_us, ticks_diff\n\nprint('Getting MAC/Ethernet Address for this device.')\n\nstart = ticks_us() # start a millisecond counter\nwlan = network.WLAN(network.STA_IF)\nwlan.active(True) # this line powers up the chip - it takes about 2.5 seconds\n\n# This returns a byte array of hex numbers\nmac_addess = wlan.config('mac')\nprint('Time in microseconds:', ticks_diff(ticks_us(), start))\n# each MAC address is 6 bytes or 48 bits\nprint(\"Hex byte array:\", mac_addess, 'length:', len(mac_addess))\n\n# This should be in hex per the Notational Conventions\n# https://en.wikipedia.org/wiki/MAC_address#Notational_conventions\n# b'(\\xcd\\xc1\\x015X'\n# 28:cd:c1:1:35:58\n# format in MAC Notational Convention\nfor digit in range(0,5):\n print(str(hex(mac_addess[digit]))[2:4], ':', sep='', end = '')\nprint(str(hex(mac_addess[5]))[2:4] )\n
First Time After Power On Results:
Getting MAC/Ethernet Address for this device.\nTime in microseconds: 2584424\nHex byte array: b'(\\xcd\\xc1\\x015X' length: 6\n28:cd:c1:1:35:58\n
Note that it takes about 2.5 seconds just to power on the chip before we get the MAC address.
Subsequent Times
Getting MAC/Ethernet Address for this device.\nTime in microseconds: 211\nHex byte array: b'(\\xcd\\xc1\\x015X' length: 6\n28:cd:c1:1:35:58\n
Note
We must add the wlan.active(True)
line to this code. If we don't do this, the wifi device will not be powered up and we can't get the MAC address. The function will return all zeros.
I ran this program on my Pico W and I got times of between 214 and 222 microseconds. This shows you that it takes about 100 microseconds to send a request from the RP2040 to the CYW43439 WiFi chip and about 100 milliseconds to return the results. This time lag represents some of the key performance limitations in using the Pico W for high-performance networking.
"},{"location":"wireless/05-mac-address/#mac-addresses-and-privacy","title":"MAC Addresses and Privacy","text":"MAC addresses are never sent to web servers to protect a clinet's privacy, but if you are debugging networks and looking at tools such as WireShark, they become visible to a local LAN administrator. MAC addresses are also visible to WiFi access points, so your local coffee shop that provides free WiFi could keep track of every time repeat customers use their WiFi access points.
Some computers such as Apple generate a different MAC address for each different access point they connect to. This prevents multiple stores from connecting your profiles together. So Starbucks can't use your MAC address to offer you a free coffee every 10th visit to different stores, but if you visit a local coffee shop they can use their WiFi access logs to see their same-store repeat customers.
"},{"location":"wireless/06-upip/","title":"MicroPython PIP (UPIP)","text":"MicroPython also has a package manager called upip
that can be run directly on the microcontroller. This means that every time you get a new microcontroller, you can automatically download all the python packages you need directly from the Internet using a single python program after upip
has been installed.
This also means that you don't need copies of these libraries installed on your local PC.
"},{"location":"wireless/06-upip/#install-upip-from-thonny-package-manager","title":"Install UPIP From Thonny Package Manager","text":"Go to the Thonny Tools -> Manage Packages... menu. Type \"upip\" in the search field and click the Search on PyPL button:
The first search result will be for **micropython-upip\". Click on that link and you will see the details.
"},{"location":"wireless/06-upip/#install-a-package","title":"Install A Package","text":"The following program assumes that you have followed the steps in our Connecting to WiFi and have a secrets.py file that have your WiFi network name (SSID) and password (PASSWORD).
import upip\nimport network\nimport secrets\nfrom utime import sleep, ticks_ms, ticks_diff\n\nprint('Connecting to WiFi Network Name:', secrets.SSID)\nwlan = network.WLAN(network.STA_IF)\nwlan.active(True)\n\nstart = ticks_ms() # start a millisecond counter\n\nif not wlan.isconnected():\n wlan.connect(secrets.SSID, secrets.PASSWORD)\n print(\"Waiting for connection...\")\n counter = 0\n while not wlan.isconnected():\n sleep(1)\n counter += 1\n print(counter, '.', sep='', end='', )\n\ndelta = ticks_diff(ticks_ms(), start)\nprint(\"Connect Time:\", delta)\n\n# This is the line you must modify for each package you want to install\n# pystone is a CPU performance benchmarking tool that is easy to run to and test installation\n\nstart = ticks_ms()\n\nupip.install(\"micropython-pystone_lowmem\")\n\nprint(\"Download Time:\", ticks_diff(ticks_ms(), start), milliseconds)\n
Results:
Connecting to WiFi Network Name: MY_NETWORK_NAME\nWaiting for connection...\n1.2.3.4.Connect Time: 4641\nInstalling to: /lib/\nWarning: micropython.org SSL certificate is not validated\nInstalling micropython-pystone_lowmem 3.4.2.post4 from https://micropython.org/pi/pystone_lowmem/pystone_lowmem-3.4.2.post4.tar.gz\nDownload Time: 4918 milliseconds\n
"},{"location":"wireless/06-upip/#testing-your-newly-installed-library","title":"Testing Your Newly Installed Library","text":"import pystone_lowmem\npystone_lowmem.main()\n
Results:
Pystone(1.2) time for 500 passes = 410ms\nThis machine benchmarks at 1219 pystones/second\n
"},{"location":"wireless/06-upip/#getting-upip-help","title":"Getting UPIP Help","text":"import upip\nupip.help()\n
returns:
upip - Simple PyPI package manager for MicroPython\nUsage: micropython -m upip install [-p <path>] <package>... | -r <requirements.txt>\nimport upip; upip.install(package_or_list, [<path>])\n\nIf <path> isn't given, packages will be installed to sys.path[1], or\nsys.path[2] if the former is .frozen (path can be set from MICROPYPATH\nenvironment variable if supported).\nDefault install path: /lib\n\nNote: only MicroPython packages (usually, named micropython-*) are supported\nfor installation, upip does not support arbitrary code in setup.py.\n
"},{"location":"wireless/07-web-server-rgb-neopixels/","title":"Web Server NeoPixel RGB","text":""},{"location":"wireless/07-web-server-rgb-neopixels/#sample-code-with-css","title":"Sample Code with CSS","text":"# Code taken from https://www.cnx-software.com/2022/07/03/getting-started-with-wifi-on-raspberry-pi-pico-w-board/\nfrom machine import Pin\nfrom neopixel import NeoPixel\nimport network\nimport socket\nfrom time import sleep\nimport secrets\n\nNEOPIXEL_PIN = 0\nNUMBER_PIXELS = 30\nPERCENT_COLOR_WHEEL = round(255/NUMBER_PIXELS)\n\n# setup\nstrip = NeoPixel(machine.Pin(NEOPIXEL_PIN), NUMBER_PIXELS)\n\nred = (255,0,0)\ngreen = (0,255,0)\nblue = (0,0,255)\noff = (0,0,0)\n\ndef set_color(color):\n for i in range(0, NUMBER_PIXELS):\n strip[i] = color\n strip.write()\n\n# Select the onboard LED\nled = machine.Pin(\"LED\", machine.Pin.OUT)\n\nwlan = network.WLAN(network.STA_IF)\nwlan.active(True)\nwlan.connect(secrets.SSID, secrets.PASSWORD)\nstateis = \"LED is OFF\"\n\nhtml = \"\"\"<!DOCTYPE html>\n<html>\n <head>\n <title>Web Server On Pico W </title>\n <style>\n body {\n font-size:24px;\n font-family:'Helvetica'\n }\n\n .button-row {\n display: inline-block;\n padding: 2px 3px 6px 3px;\n margin: 5px;\n }\n\n a:visited {\n color: yellow;\n background-color: transparent;\n text-decoration: none;\n }\n\n a:hover {\n color: orange;\n background-color: transparent;\n text-decoration: none;\n }\n\n .button {\n font: bold 16px Arial;\n text-decoration: none;\n padding: 2px 6px 2px 6px;\n border-top: 2px solid #CCCCCC;\n border-right: 2px solid #333333;\n border-bottom: 2px solid #333333;\n border-left: 2px solid #CCCCCC;\n }\n </style>\n </head>\n <body>\n <h1>Pico Wireless Web Server</h1>\n <p>Onboard LED State: %s</p>\n\n\n <div class=\"buttons\">\n\n <span class=\"button-row\" style=\"background-color:gray\">\n <a href=\"/light/on\" class=\"button\">Turn Onboard LED On</a>\n <a href=\"/light/off\" class=\"button\">Turn Onboard LED Off</a>\n </span><br/>\n\n <span class=\"button-row\" style=\"background-color:red\">\n <a href=\"/led/red/on\" class=\"button\">Red LED On</a>\n <a href=\"/led/red/off\" class=\"button\">Red LED Off</a>\n </span><br/>\n\n <span class=\"button-row\" style=\"background-color:green\">\n <a href=\"/led/green/on\" class=\"button\">Green LED On</a>\n <a href=\"/led/green/off\" class=\"button\">Green LED Off</a>\n </span><br/>\n\n <span class=\"button-row\" style=\"background-color:blue\">\n <a href=\"/led/blue/on\" class=\"button\">Blue LED On</a>\n <a href=\"/led/blue/off\" class=\"button\">Blue LED Off</a>\n </span>\n\n </div>\n </body>\n</html>\n\"\"\"\n\n# Wait for connect or fail\nmax_wait = 10\nwhile max_wait > 0:\n if wlan.status() < 0 or wlan.status() >= 3:\n break\n max_wait -= 1\n print('waiting for connection...')\n sleep(1)\n led.toggle()\n\n# Handle connection error\nif wlan.status() != 3:\n raise RuntimeError('network connection failed')\nelse:\n print('We are connected to WiFI access point:', secrets.SSID)\n status = wlan.ifconfig()\n print( 'The IP address of the pico W is:', status[0] )\n\n# Open socket\naddr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]\n# print('addr:', addr)\n\ns = socket.socket()\n# this prevents the \"OSError: [Errno 98] EADDRINUSE\" error for repeated tests\ns.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n# print('Got a sockert. s:', s)\n\n#if not addr:\n\n# print('going to run bind to addr on the socket')\ns.bind(addr)\n\n# print('going to run listen(1) on socket')\ns.listen(1)\n\n# print('listening on', addr)\n\n# Listen for connections\nwhile True:\n try:\n cl, addr = s.accept()\n # print('client connected from', addr)\n request = cl.recv(1024)\n # print(request)\n request = str(request)\n led_on = request.find('/light/on')\n led_off = request.find('/light/off')\n\n red_on = request.find('/led/red/on')\n red_off = request.find('/led/red/off')\n\n green_on = request.find('/led/green/on')\n green_off = request.find('/led/green/off')\n\n blue_on = request.find('/led/blue/on')\n blue_off = request.find('/led/blue/off')\n\n # print( 'led on = ' + str(led_on))\n # print( 'led off = ' + str(led_off))\n\n if led_on == 6:\n print(\"led on\")\n led.on()\n stateis = \"LED is ON\"\n\n if led_off == 6:\n print(\"led off\")\n led.off()\n stateis = \"LED is OFF\"\n\n if red_on == 6:\n print(\"red on\")\n set_color(red)\n if red_off == 6:\n print(\"red off\")\n set_color(off)\n\n if green_on == 6:\n print(\"green on\")\n set_color(green)\n if green_off == 6:\n print(\"green off\")\n set_color(off)\n\n if blue_on == 6:\n print(\"blue on\")\n set_color(blue)\n if blue_off == 6:\n print(\"blue off\")\n set_color(off)\n\n # generate the we page with the stateis as a parameter\n response = html % stateis\n cl.send('HTTP/1.0 200 OK\\r\\nContent-type: text/html\\r\\n\\r\\n')\n cl.send(response)\n cl.close()\n\n except OSError as e:\n cl.close()\n s.close()\n print('connection closed')\n
"},{"location":"wireless/07-web-server-rgb-neopixels/#references","title":"References","text":"Clear Fix Floating Boxes Example
https://www.w3schools.com/css/tryit.asp?filename=trycss_float_boxes
<style>\n* {\n box-sizing: border-box;\n}\n\n.box {\n float: left;\n width: 33.33%;\n padding: 30px 20px;\n}\n\n.clearfix::after {\n content: \"\";\n clear: both;\n display: table;\n}\n</style>\n</head>\n<body>\n\n <h2>Grid of Boxes</h2>\n <p>Float boxes side by side:</p>\n\n <div class=\"clearfix\">\n <div class=\"box\" style=\"background-color:silver\">\n <p>Some text inside the box.</p>\n </div>\n <div class=\"box\" style=\"background-color:gray\">\n <p>Some text inside the box.</p>\n </div>\n </div>\n
"},{"location":"wireless/08-get-weather/","title":"Get the Weather Forecast","text":"This demo uses the free web service Open Weather Map.
The Open Weather Map service returns the predicted temperatures and conditions (sun, cloudy, rain etc.) for three-hour intervals for the next 40 intervals at your specified location.
You can see this using the UNIX curl command:
curl 'http://api.openweathermap.org/data/2.5/forecast?\\\n units=imperial&\\\n id=5037649&\\\n appid=f2b1...'\n
In this example, we are asking it to use US standard Fahrenheit (imperial units) for the city with id 5037649 which is Minneapolis, MN in the USA. You can use the Open Weather site to find the id for your city or specify the longitude and latitude of the point you want to get weather forecasts for. You can also use the GeoNames to find your city ID number.
"},{"location":"wireless/08-get-weather/#how-to-use-the-open-weather-map-api","title":"How to Use the Open Weather Map API","text":"To use this service, you must register and get an API key. You then put your key in the secrets.py file:
Content of secrets.py
appid='f2b1...'\n
The secrets.py file is then imported into your program. Make sure to put secrets.py into your .gitignore file so it will not be checked into your public GitHub repo.
The URL for the service is then created by concatenating the base URL, the city location ID and the application id:
base = 'http://api.openweathermap.org/data/2.5/forecast?units=imperial&'\nlocation = '5037649' # GeoNames ID for Minneapolis in Minnesota, USA\nurl = base + 'id=' + location + '&appid=' + secrets.appid\n
"},{"location":"wireless/08-get-weather/#parsing-the-json-file","title":"Parsing the JSON file","text":"The service returns a JSON file with the following format:
{\n \"cod\": \"200\",\n \"message\": 0,\n \"cnt\": 40,\n \"list\": [\n {\n \"dt\": 1660078800,\n \"main\": {\n \"temp\": 80.83,\n \"feels_like\": 81.23,\n \"temp_min\": 80.83,\n \"temp_max\": 84.67,\n \"pressure\": 1019,\n \"sea_level\": 1019,\n \"grnd_level\": 988,\n \"humidity\": 47,\n \"temp_kf\": -2.13\n },\n \"weather\": [\n {\n \"id\": 800,\n \"main\": \"Clear\",\n \"description\": \"clear sky\",\n \"icon\": \"01d\"\n }\n ],\n \"clouds\": {\n \"all\": 0\n },\n \"wind\": {\n \"speed\": 6.08,\n \"deg\": 226,\n \"gust\": 6.38\n },\n \"visibility\": 10000,\n \"pop\": 0,\n \"sys\": {\n \"pod\": \"d\"\n },\n \"dt_txt\": \"2022-08-09 21:00:00\"\n }, \n /* ...repeated 40 times */\n ,\n \"city\": {\n \"id\": 5037649,\n \"name\": \"Minneapolis\",\n \"coord\": {\n \"lat\": 44.98,\n \"lon\": -93.2638\n },\n \"country\": \"US\",\n \"population\": 0,\n \"timezone\": -18000,\n \"sunrise\": 1660043269,\n \"sunset\": 1660094963\n }\n}\n
The bulk of the data is in the list structure with 40 items with a small set of data before and after the list. The data about the HTTP status (200) and the count (40) is before the list and the data about the city is after the list.
Each block of data in the list JSON object contains data about the main data (temperature, min, max, pressure, humidity), cloud cover, wind speed and visibility. It is up to you to decide what data you would like to display within this data. Once you decide what data you want to access you can use JSON path statements to pull the right data out. For example, to get the main temperature for each time period you would run:
# get the temp and humidity for the next 40 3-hour intervals\nfor i in range(0, 39):\n print('temp:', weather['list'][i]['main']['temp'], end='')\n print('humidity:', weather['list'][i]['main']['humidity'])\n
"},{"location":"wireless/08-get-weather/#sample-output","title":"Sample Output","text":"Here is the output on the Thonny shell of the first 16 temperature values:
"},{"location":"wireless/08-get-weather/#plotting-the-forecast-with-thonny-plot","title":"Plotting the Forecast with Thonny Plot","text":"Here is a plot of the temp and the \"feels like\" temp using the Thonny Plotter panel.
Each line has:
for each value plotted. There can be multiple values per line.
Here is that format:
Temperature: 63 Feels Like: 63.52\nTemperature: 62 Feels Like: 62.56\nTemperature: 70 Feels Like: 69.69\n
"},{"location":"wireless/08-get-weather/#sample-code","title":"Sample Code","text":"import network\nimport secrets\nimport urequests\nfrom utime import sleep, ticks_ms, ticks_diff\n\nprint('Connecting to WiFi Network Name:', secrets.SSID)\nwlan = network.WLAN(network.STA_IF)\nwlan.active(True)\n\nstart = ticks_ms() # start a millisecond counter\n\nif not wlan.isconnected():\n wlan.connect(secrets.SSID, secrets.PASSWORD)\n print(\"Waiting for connection...\")\n counter = 0\n while not wlan.isconnected():\n sleep(1)\n print(counter, '.', sep='', end='', )\n counter += 1\n\ndelta = ticks_diff(ticks_ms(), start)\nprint(\"Connect Time:\", delta, 'milliseconds')\nprint(\"IP Address:\", wlan.ifconfig()[0])\n\nbase = 'http://api.openweathermap.org/data/2.5/forecast?units=imperial&'\nlocation = '5037649' # twin cities\nurl = base + 'id=' + location + '&appid=' + secrets.appid\n\nweather = urequests.get(url).json()\nprint('City:', weather['city']['name'])\nprint('Timezone:', weather['city']['timezone'])\n\nmax_times = 16\n# for i in range(0, 39):\nfor i in range(0, max_times):\n print(weather['list'][i]['dt_txt'][5:13], ' ', sep='', end='')\nprint()\nfor i in range(0, max_times): \n print(round(weather['list'][i]['main']['temp']), ' ', end='')\n # print('feels like:', weather['list'][i]['main']['feels_like'])\n # print(weather['list'][i]['weather'][0]['description'])\n # print(weather['list'][i]['dt_txt'])\n\n# print(weather)\n
"},{"location":"wireless/08-get-weather/#displaying-predicted-temperatures-in-a-thonny-plot","title":"Displaying Predicted Temperatures in a Thonny Plot","text":"Thonny has a Plot object that you can use to display the relative temperature for the next 40 3-hour cycles.
To do this, we only need to print out the temperatures each on a separate line:
print()\nfor i in range(0, max_times): \n print(round(weather['list'][i]['main']['temp']))\n
"},{"location":"wireless/09-get-weather-display/","title":"Get Weather on an OLED Display","text":"In this lesson we will use the Pico W to get a weather forecast from a web service and display the current and forecasted temperatures on a 128x64 OLED display. The display above shows the city and current temperature on the top row of the display and then a plot of the predicted temperatures for the next 120 hours. The max temperature is 87 degrees and the minimum is 60 degrees Fahrenheit.
"},{"location":"wireless/09-get-weather-display/#calling-the-weather-api","title":"Calling the Weather API","text":"We will use the same method as in the previous lesson to get the weather forecast. However, in this lesson we will not just plot the temperature on the Thonny plot screen, we will use a OLED screen.
To do this we will need to write a function that will display the temperature data. The function will display the current location city name, the current temperature, and then draw a plot of the next 40 3-hour intervals. This lesson is a bit harder because we have to manually do all the work of scaling and plotting our data. This is all done for us in the prior Thonny plotting lab.
To achieve this we will need to scale the data to fit the display grid. We will reserve the top 10 pixels for the city and current temp and then draw the plot on the remaining 54 pixel high and 128 pixel wide plot region.
To scale the data we will need to find the min and max temperatures. These will also be displayed on the screen. The scale is then the ratio of the graph height over the temperature range. For example if the range of temperatures is 27 degrees and the height of the display is 54 we will need to scale the temperature vertically by 2.
The horizontal axis will go from 0 to 120. Since we have 40 points, each point will occur every 3rd pixel. We can then look at the difference between the current point and the next point to interpolate the dots between the two points.
The math can be a little confusing since higher temperatures are closer to the top of the display, so they have a lower Y coordinate value. Note the y_delta is subtracted from the next value:
y_delta = -round((y - y_next)/3) # a small positive or negative number for interpolation\n
def display_weather():\n global weather, city, current_temp\n oled.fill(0)\n min = 120\n max = -50\n for i in range(0, 39):\n temp = round(weather['list'][i]['main']['temp'])\n if temp < min:\n min = temp\n if temp > max:\n max = temp\n min = round(min)\n max = round(max)\n temp_range_height = max - min\n graph_height = 54\n scale = graph_height/temp_range_height\n print('min/max/range/scale:', min, max, temp_range_height, scale)\n\n # display city name, current temp, min and max\n oled.text(city + ': ' + str(current_temp), 0, 0, 1)\n oled.text(str(min), 0, 57, 1) # bottom left corner\n oled.text(str(max), 0, 10, 1) # under top row\n\n max_points = 39\n\n # graph temps for the next n periods\n print('Date Tmp TNx Y Y2 Del')\n for i in range(0, max_points):\n temp = round(weather['list'][i]['main']['temp'])\n x = i * 3 # scaled x\n y = 63 - round((temp - min)*scale)\n oled.pixel(x, y, 1)\n\n # now draw the next two points\n if i < max_points:\n temp_next = round(weather['list'][i+1]['main']['temp'])\n y_next = 63 - round((temp_next - min)*scale)\n y_delta = -round((y - y_next)/3) # a small positive or negative number\n\n # for debugging - fixed with columns with leading spaces\n print(weather['list'][i]['dt_txt'][0:13],\n '{: 3.3d}'.format(temp),\n '{: 3.3d}'.format(temp_next),\n '{: 3.3d}'.format(y),\n '{: 3.3d}'.format(y_next),\n '{: 3.3d}'.format(y_delta))\n\n # should be 1/3 of the way to the next point\n oled.pixel(x+1, y + y_delta, 1)\n # should be 2/3 of the way to the next point\n oled.pixel(x+2, y + 2*y_delta, 1)\n oled.show()\n
"},{"location":"wireless/09-get-weather-display/#the-main-loop","title":"The Main Loop","text":"The main loop repeats forever, pausing every hour between updates. It gets first calls the rest service, extracts the city name and current temperature and then calls the display_weather() function using global variables for the JSON file, city and current temperature.
while True:\n # globals: weather, city, current_temp\n weather = urequests.get(url).json()\n # print(weather)\n city = weather['city']['name']\n current_temp = round(weather['list'][0]['main']['temp'])\n display_weather()\n print('Going to sleep for one hour')\n sleep(3600) # sleep one hour\n
"},{"location":"wireless/09-get-weather-display/#full-sample-code","title":"Full Sample Code","text":"import network\nimport ssd1306\nimport secrets\nimport urequests\nfrom utime import sleep, ticks_ms, ticks_diff\n\n# startup\n\nprint('Connecting to WiFi Network Name:', secrets.SSID)\nwlan = network.WLAN(network.STA_IF)\nwlan.active(True)\n\nWIDTH = 128\nHEIGHT = 64\nSCK=machine.Pin(2)\nSDL=machine.Pin(3)\nspi=machine.SPI(0,baudrate=100000,sck=SCK, mosi=SDL)\nCS = machine.Pin(0)\nDC = machine.Pin(1)\nRES = machine.Pin(4)\noled = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)\noled.poweron()\n\ndef display_startup(counter):\n oled.fill(0)\n oled.text('Running startup', 0, 10, 1)\n oled.text('Connecting to', 0, 20, 1)\n oled.text(secrets.SSID, 0, 30, 1)\n oled.text(str(counter), 0, 40, 1)\n oled.show()\n\ndef display_status(counter):\n oled.fill(0)\n # display the network name\n oled.text('n:' + secrets.SSID, 0, 0, 1)\n\n # display the connection time\n oled.text('t:', 0, 10, 1)\n oled.text(str(connection_time)+ ' ms', 15, 10, 1)\n oled.show()\n\ndef display_weather():\n global weather, city, current_temp\n oled.fill(0)\n min = 120\n max = -50\n for i in range(0, 39):\n temp = round(weather['list'][i]['main']['temp'])\n if temp < min:\n min = temp\n if temp > max:\n max = temp\n min = round(min)\n max = round(max)\n temp_range_height = max - min\n graph_height = 54\n scale = graph_height/temp_range_height\n print('min/max/range/scale:', min, max, temp_range_height, scale)\n\n # display city name, current temp, min and max\n oled.text(city + ': ' + str(current_temp), 0, 0, 1)\n oled.text(str(min), 0, 57, 1) # bottom left corner\n oled.text(str(max), 0, 10, 1) # under top row\n\n max_points = 39\n\n # graph temps for the next n periods\n print('Date Tmp TNx Y Y2 Del')\n for i in range(0, max_points):\n temp = round(weather['list'][i]['main']['temp'])\n x = i * 3 # scaled x\n y = 63 - round((temp - min)*scale)\n oled.pixel(x, y, 1)\n\n # now draw the next two points\n if i < max_points:\n temp_next = round(weather['list'][i+1]['main']['temp'])\n y_next = 63 - round((temp_next - min)*scale)\n y_delta = -round((y - y_next)/3) # a small positive or negative number\n\n print(weather['list'][i]['dt_txt'][0:13],\n '{: 3.3d}'.format(temp),\n '{: 3.3d}'.format(temp_next),\n '{: 3.3d}'.format(y),\n '{: 3.3d}'.format(y_next),\n '{: 3.3d}'.format(y_delta))\n\n # should be 1/3 of the way to the next point\n oled.pixel(x+1, y + y_delta, 1)\n # should be 2/3 of the way to the next point\n oled.pixel(x+2, y + 2*y_delta, 1)\n oled.show()\n\nstart = ticks_ms() # start a millisecond counter\n\nif not wlan.isconnected():\n wlan.connect(secrets.SSID, secrets.PASSWORD)\n print(\"Waiting for connection...\")\n counter = 0\n while not wlan.isconnected():\n sleep(1)\n print(counter, '.', sep='', end='', )\n counter += 1\n display_startup(counter)\n\ndelta = ticks_diff(ticks_ms(), start)\n#print(\"Connect Time:\", delta, 'milliseconds')\n#print(\"IP Address:\", wlan.ifconfig()[0])\n\nbase = 'http://api.openweathermap.org/data/2.5/forecast?units=imperial&'\nlocation = '5037649' # Minneapolis, MN USA\nurl = base + 'id=' + location + '&appid=' + secrets.appid\n#print(url)\n\nmax_times = 39\n#for i in range(0, max_times): \n #print(' Temp: ', weather['list'][i]['main']['temp'])\n\nwhile True:\n # globals: weather, city, current_temp\n weather = urequests.get(url).json()\n # print(weather)\n city = weather['city']['name']\n current_temp = round(weather['list'][0]['main']['temp'])\n display_weather()\n print('Going to sleep for one hour.')\n sleep(3600) # sleep one hour\n
"},{"location":"wireless/09-get-weather-display/#sample-debugging","title":"Sample Debugging","text":"To allow you to see the math for the plotting and interpolation we have added a print that prints the temperatures and y coordinates in fixed with format. Here is an example of this output:
Connecting to WiFi Network Name: anndan-2.4\nmin/max/range/scale: 60 87 27 2.0\nDate Tmp Tnx Y Y2 Del\n2022-08-13 03 68 68 47 47 0\n2022-08-13 06 68 68 47 47 0\n2022-08-13 09 68 65 47 53 2\n2022-08-13 12 65 72 53 39 -5\n2022-08-13 15 72 83 39 17 -7\n2022-08-13 18 83 87 17 9 -3\n2022-08-13 21 87 82 9 19 3\n2022-08-14 00 82 71 19 41 7\n2022-08-14 03 71 66 41 51 3\n2022-08-14 06 66 62 51 59 3\n2022-08-14 09 62 60 59 63 1\n2022-08-14 12 60 71 63 41 -7\n2022-08-14 15 71 82 41 19 -7\n2022-08-14 18 82 85 19 13 -2\n2022-08-14 21 85 82 13 19 2\n2022-08-15 00 82 73 19 37 6\n2022-08-15 03 73 69 37 45 3\n2022-08-15 06 69 66 45 51 2\n2022-08-15 09 66 64 51 55 1\n2022-08-15 12 64 74 55 35 -7\n2022-08-15 15 74 83 35 17 -6\n2022-08-15 18 83 85 17 13 -1\n2022-08-15 21 85 81 13 21 3\n2022-08-16 00 81 75 21 33 4\n2022-08-16 03 75 70 33 43 3\n2022-08-16 06 70 69 43 45 1\n2022-08-16 09 69 66 45 51 2\n2022-08-16 12 66 66 51 51 0\n2022-08-16 15 66 79 51 25 -9\n2022-08-16 18 79 80 25 23 -1\n2022-08-16 21 80 70 23 43 7\n2022-08-17 00 70 65 43 53 3\n2022-08-17 03 65 64 53 55 1\n2022-08-17 06 64 63 55 57 1\n2022-08-17 09 63 61 57 61 1\n2022-08-17 12 61 72 61 39 -7\n2022-08-17 15 72 82 39 19 -7\n2022-08-17 18 82 85 19 13 -2\n2022-08-17 21 85 81 13 21 3\nGoing to sleep for one hour\n
"},{"location":"wireless/10-wifi-clock/","title":"WiFi Clock","text":"In the US, we can access a service called the \"Network Time Protocol\" or NTP. This service allows you to get the precise time using your WiFi network.
Calling the NTP service in MicroPython is simple once you have connected to your local wireless access point:
import ntptime\nntptime.host = 'us.pool.ntp.org'\nntptime.timeout = 10\nntptime.settime()\n
After running this code (and checking for errors) your internal Real-time Clock (RTC) will be synced to the NTP server.
The rest of this code does the work of adjusting the clock to your local timezone and correcting for any changes in daylight savings time in your area.
"},{"location":"wireless/10-wifi-clock/#the-config-file","title":"The Config File","text":"We always put local credentials in a separate file so it does not get checked into GitHub.
wifi_ssid = 'mywifinetworkname'\nwifi_pass = 'mypassword'\n
"},{"location":"wireless/10-wifi-clock/#full-program","title":"Full Program","text":"import ntptime, network\nfrom machine import RTC\nfrom utime import sleep, sleep_ms, time, localtime, mktime\nimport config\n\n# US Central\ntimeZone = -6\n# try one of these\nntptime.host = 'us.pool.ntp.org' #'time.nist.gov' #'pool.ntp.org'\nntptime.timeout = 10\n\ndef wifiConnect():\n wifi = network.WLAN(network.STA_IF)\n wifi.active(True)\n wifi.config(pm = 0xa11140) # disables wifi sleep mode\n if not wifi.isconnected():\n wifi.connect(config.wifi_ssid, config.wifi_pass)\n print('Connecting..', end='')\n max_wait = 10\n while max_wait > 0:\n if wifi.status() < 0 or wifi.status() >= 3: break\n sleep_ms(1000)\n print('.', end='')\n max_wait -= 1\n print()\n if wifi.status() != 3: print('Could not connect to wifi!')\n # print('Connected: ',wifi.isconnected(),'\\nIP: ',wifi.ifconfig()[0])\n sleep_ms(100)\n return wifi\n\n# daylight savings time\ndef dst():\n year, weekday = localtime()[0], localtime()[6]\n dst_start = mktime((year, 3, (8 - weekday) % 7 + 8, 2, 0, 0, 0, 0))\n dst_end = mktime((year, 11, (1 - weekday) % 7 + 1, 2, 0, 0, 0, 0))\n return dst_start <= time() < dst_end\n\ndef setRTC():\n timeset = False\n timetries = 0\n maxtries = 5\n while not timeset and timetries < maxtries:\n timetries += 1\n try:\n ntptime.settime() # update time from ntp server\n timeset = True\n except:\n print(f'NTP update attempt # {timetries} of {maxtries} failed!', 'Retrying in 15 seconds..' if timetries < maxtries else 'Check connection/config.')\n if timetries < maxtries: sleep_ms(15000)\n if timeset:\n sleep_ms(200)\n rtc = RTC()\n tz_offset = (timeZone + 1) * 3600 if dst() else timeZone * 3600\n #tz_offset = timeZone * 3600 # without daylight savings\n myt = localtime(time() + tz_offset)\n rtc.datetime((myt[0], myt[1], myt[2], myt[6], myt[3], myt[4], myt[5], 0))\n sleep_ms(200)\n dtime = rtc.datetime()\n timestr = '%2d:%02d%s' %(12 if dtime[4] == 0 else dtime[4] if dtime[4] < 13 else dtime[4] - 12, dtime[5], 'am' if dtime[4] < 12 else 'pm')\n datestr = f'{dtime[1]}/{dtime[2]}/{dtime[0] % 100}'\n # print('Time set to:', timestr, datestr)\n print(timestr, datestr)\n return True\n print('ERROR! Unable to update time from server!')\n return False\n\ndef update():\n success = False\n wifi = wifiConnect()\n sleep_ms(100)\n if wifi.isconnected():\n success = setRTC()\n sleep_ms(100)\n return wifi, success\n\nif __name__ == '__main__':\n while True:\n update()\n sleep(60)\n
"},{"location":"wireless/10-wifi-clock/#sample-output","title":"Sample Output:","text":"The console will display the following:
Connecting.........\nConnected: True \nIP: 10.0.0.118\nNTP update attempt # 1 of 5 failed! Retrying in 15 seconds..\n...\n7:41pm 9/10/23\n7:42pm 9/10/23\n7:43pm 9/10/23\n
Note that connecting to the NTP server failed the first time but worked on the second attempt.
This program assumes you have a config.py file in the same folder that the clock.py program runs.
"},{"location":"wireless/10-wifi-clock/#references","title":"References","text":"NikoKun
"},{"location":"wireless/11-advanced-labs/","title":"Advanced Wireless Labs","text":""},{"location":"wireless/11-advanced-labs/#secure-communications-with-https","title":"Secure Communications with HTTPS","text":"In our documentation we frequently refer to secure communications as using a \"Secure Sockets Layer\". Although the term \"SSL\" is common, we are actually using a protocol called Transport Layer Security (TLS).
TLS replaces SSL. It is an Internet Engineering Task Force (IETF) standard protocol that provides authentication, privacy and data integrity between two communicating computer applications.
Note
The standard Python request library does not yet support HTTPS on urequest on the Pico W. This is because there are additional tools that require us to use keys and certificates to validate data on an encrypted SSL stream.
See the MicroPython SSL/TLS Library
"},{"location":"wireless/11-advanced-labs/#testing-ssltls-on-standard-python","title":"Testing SSL/TLS on Standard Python","text":"import socket\nimport ssl\n\nhostname = 'www.python.org'\ncontext = ssl.create_default_context()\n\nwith socket.create_connection((hostname, 443)) as sock:\n with context.wrap_socket(sock, server_hostname=hostname) as ssock:\n print(ssock.version())\n
returns: TLSv1.3
This tells you that the standard Python socket libraries use the TLS v1.3 protocol.
"},{"location":"wireless/11-advanced-labs/#performance-monitoring-with-uiperf3","title":"Performance Monitoring with uiperf3","text":"iperf3 is a standard Python program for internet performance testing. For micropython, we have our own stripped down version called uiperf3.
IPerf3 uses a client-server testing model and measures networking performance between two system using various protocoos such as
It can also be used to measure total wireless throughput.
"},{"location":"wireless/11-advanced-labs/#upip-install","title":"UPIP Install","text":"upip.install(\"uiperf3\")\n
"},{"location":"wireless/11-advanced-labs/#testing-client-performance","title":"Testing Client Performance","text":"import uiperf3\n uiperf3.client('MY_IP_ADDRESS')\n
"},{"location":"wireless/12-display-clock/","title":"Display Clock","text":"This is similar to the WiFi Clock but it uses an OLED display to show the time and date.
from machine import Pin\nimport network\nimport ntptime\nimport ssd1306\n# where we keep the WiFi password\nimport secrets\nfrom utime import sleep, ticks_ms, ticks_diff\n\nWIDTH = 128\nHEIGHT = 64\nSCK=machine.Pin(2)\nSDL=machine.Pin(3)\nspi=machine.SPI(0,baudrate=100000,sck=SCK, mosi=SDL)\nCS = machine.Pin(4)\nDC = machine.Pin(5)\nRES = machine.Pin(6)\noled = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)\noled.poweron()\n\n# US Central\ntimeZone = -6\n# try one of these\nntptime.host = 'us.pool.ntp.org' #'time.nist.gov' #'pool.ntp.org'\nntptime.timeout = 10\n\ndef wifiConnect():\n wifi = network.WLAN(network.STA_IF)\n wifi.active(True)\n wifi.config(pm = 0xa11140) # disables wifi sleep mode\n if not wifi.isconnected():\n wifi.connect(config.wifi_ssid, config.wifi_pass)\n print('Connecting..', end='')\n max_wait = 10\n while max_wait > 0:\n if wifi.status() < 0 or wifi.status() >= 3: break\n sleep_ms(1000)\n print('.', end='')\n max_wait -= 1\n print()\n if wifi.status() != 3: print('Could not connect to wifi!')\n # print('Connected: ',wifi.isconnected(),'\\nIP: ',wifi.ifconfig()[0])\n sleep_ms(100)\n return wifi\n\n# daylight savings time\ndef dst():\n year, weekday = localtime()[0], localtime()[6]\n dst_start = mktime((year, 3, (8 - weekday) % 7 + 8, 2, 0, 0, 0, 0))\n dst_end = mktime((year, 11, (1 - weekday) % 7 + 1, 2, 0, 0, 0, 0))\n return dst_start <= time() < dst_end\n\ntimestr = ''\ndatestr = ''\ndtime = []\ndef setRTC():\n global timestr, datestr, dtime\n timeset = False\n timetries = 0\n maxtries = 5\n while not timeset and timetries < maxtries:\n timetries += 1\n try:\n ntptime.settime() # update time from ntp server\n timeset = True\n except:\n print(f'NTP update attempt # {timetries} of {maxtries} failed!', 'Retrying in 15 seconds..' if timetries < maxtries else 'Check connection/config.')\n if timetries < maxtries: sleep_ms(15000)\n if timeset:\n sleep_ms(200)\n rtc = RTC()\n tz_offset = (timeZone + 1) * 3600 if dst() else timeZone * 3600\n #tz_offset = timeZone * 3600 # without daylight savings\n myt = localtime(time() + tz_offset)\n rtc.datetime((myt[0], myt[1], myt[2], myt[6], myt[3], myt[4], myt[5], 0))\n sleep_ms(200)\n dtime = rtc.datetime()\n # set globals\n # hh:mm in 12hr am/pm format\n timestr = '%2d:%02d%s' %(12 if dtime[4] == 0 else dtime[4] if dtime[4] < 13 else dtime[4] - 12, dtime[5], 'am' if dtime[4] < 12 else 'pm')\n # mm/dd/yy\n datestr = f'{dtime[1]}/{dtime[2]}/{dtime[0] % 100}'\n # print('Time set to:', timestr, datestr)\n print(timestr, datestr)\n return True\n print('ERROR! Unable to update time from server!')\n return False\n\ndef update():\n success = False\n wifi = wifiConnect()\n sleep_ms(100)\n if wifi.isconnected():\n success = setRTC()\n sleep_ms(100)\n return wifi, success\n\ndef update_display():\n global timestr, datestr,dtime\n oled.fill(0)\n oled.text(timestr + ' ' + datestr, 0, 10, 1)\n oled.show()\n\nwhile True:\n update()\n update_display()\n sleep(60)\n
"},{"location":"wireless/20-display/","title":"Wireless With Display","text":"In this lesson, we will add a 128x64 OLED display to our Pico \"W\" to display network information.
The display we are using is a 2.42\" Diymore OLED display as describe in the Display Graphics.
# make a number scroll to the right and increment the number and move down one pixel\n# this test patten shows the device is working and will not burn any single pixel if it runs a long time\nfrom machine import Pin\nimport network\nimport ssd1306\nimport secrets\nfrom utime import sleep, ticks_ms, ticks_diff\n\nWIDTH = 128\nHEIGHT = 64\nSCK=machine.Pin(2)\nSDL=machine.Pin(3)\nspi=machine.SPI(0,baudrate=100000,sck=SCK, mosi=SDL)\nCS = machine.Pin(0)\nDC = machine.Pin(1)\nRES = machine.Pin(4)\noled = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)\noled.poweron()\n\ndef mac_address_fmt():\n mac_addess = wlan.config('mac')\n s=\"\"\n for digit in range(0,5):\n s+=str(hex(mac_addess[digit]))[2:4] + ':'\n s+= hex(mac_addess[5])[2:4]\n return s\n\ndef display_startup(counter):\n oled.fill(0)\n oled.text('Running startup', 0, 10, 1)\n oled.text('Connecting to', 0, 20, 1)\n oled.text(secrets.SSID, 0, 30, 1)\n oled.text(str(counter), 0, 40, 1)\n oled.show()\n\ndef display_status(counter):\n oled.fill(0)\n # display the network name\n oled.text('n:' + secrets.SSID, 0, 0, 1)\n\n # display the connection time\n oled.text('t:', 0, 10, 1)\n oled.text(str(connection_time)+ ' ms', 15, 10, 1)\n\n # display the MAC address\n oled.text(mac_address_fmt(), 0, 20, 1)\n\n # display the IP address\n oled.text('ip:' + wlan.ifconfig()[0], 0, 30, 1)\n oled.text('c:' + str(counter), 0, 40, 1)\n oled.show()\n# startup\nled = Pin(\"LED\", Pin.OUT)\nled.on()\n\nstart = ticks_ms() # start a millisecond counter\n\nprint('Connecting to WiFi Network Name:', secrets.SSID)\nwlan = network.WLAN(network.STA_IF)\nwlan.active(True)\n\nif not wlan.isconnected():\n # this should normally take 3-5 seconds...\n wlan.connect(secrets.SSID, secrets.PASSWORD)\n print(\"Waiting for connection...\")\n counter = 0\n display_startup(counter)\n while not wlan.isconnected():\n sleep(1)\n counter += 1\n led.toggle()\n display_startup(counter)\n print(counter, '.', sep='', end='')\n\nconnection_time = ticks_diff(ticks_ms(), start)\nmac_addess = wlan.config('mac')\nprint('Connected to', secrets.SSID)\nprint('Total connect milliseconds:', connection_time)\n\ncounter = 0\nwhile True:\n led.toggle()\n counter += 1\n display_status(counter)\n sleep(1)\n print(counter, 'listening on', wlan.ifconfig()[0])\n
"}]}
\ No newline at end of file
+{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"CoderDojo Twin Cities Micropython","text":"This website and GitHub repository are for sharing resources to teach MicroPython to students in 5th to 12th grades (10-18 years old). The course assumes that either a mentor, teacher or students have access to at least one microcontroller such as the $4 Raspberry Pi Pico or the $10 ESP32. Students should also have access to some low-cost sensors (buttons, potentiometers, ultrasonic distance sensor) and displays such as LEDs or OLED displays.
If you are looking for a specific topic, please remember to use the search function in the upper right of the website. The website is best displayed on a wide screen to see the navigation bar on the left although the website also works on the small screens of mobile phones and tablets.
"},{"location":"#course-outline","title":"Course Outline","text":"You can use the navigation area on the left side panel to navigate to different parts of the website. Here is a high-level overview of the main sections of the site.
"},{"location":"#section-1-introduction-to-physical-computing","title":"Section 1: Introduction to Physical Computing","text":"This part is a high-level overview of what MicroPython is and why is has become the most popular way to do physical computing, program microcontrollers and build robots. We also discuss the different types of microcontrollers available, their price and features and how to purchase them independently or in kits.
"},{"location":"#section-2-getting-started-with-micropython","title":"Section 2: Getting Started with MicroPython","text":"This part will help you get started programming MicroPython on your microcontroller and learn how to hook up parts on a solderless breadboard. We discuss the need for a desktop Integrated Development Environment (IDE) and how to get started writing simple programs
"},{"location":"#section-3-basic-examples","title":"Section 3: Basic Examples","text":"These ten lessons are the foundations for learning MicroPython. They include learning how to blink one or more LEDs, monitor for button presses, fade LEDs in and out using PWM signals, read analog values from potentiometers, read light sensors, turn motors and servos and display rainbow patterns on a NeoPixel strip. Many other labs are variations of these 10 labs.
Introduction to Basic MicroPython Examples
"},{"location":"#section-4-sensors","title":"Section 4: Sensors","text":"This section will give you more examples of how to use different types of sensors such as heat sensors, current sensors, rotary encoders, accelerometers, gesture sensors, and magnetic field sensors.
Reading Sensors with MicroPython
"},{"location":"#section-5-motors-and-robots","title":"Section 5: Motors and Robots","text":"This is our student's favorite part of this site! Once you can make a motor go forward and reverse, you are close to being able to make a robot move. We walk you through the basics of using a simple transistor to control a motor, to using simple motor controllers like the L293D chips.
Introduction to Motors and Robots with MicroPython
Note that we have many other advanced labs that use our $11 Cytron Maker Pi RP2040 Kits. These incredible boards have everything integrated to build robots with lights and sounds.
"},{"location":"#section-6-displays","title":"Section 6: Displays","text":"This section shows you how to use many different types of displays, from simple 7-segment digital displays to complex OLED graphic displays. On the old 2K Arduino controllers these graphics labs used to be hard, but now we have 264K of RAM on the Raspberry Pi RP2040 microcontrollers. Now these labs are easy!
Having powerful microcontrollers allows us to generate complex sounds, play tones and even playback recoded sound effects.
Introduction to Sound and Music with MicroPython
"},{"location":"#section-8-advanced-labs","title":"Section 8: Advanced Labs","text":"We have now covered all the things you need to build hundreds of projects. This section contains deeper dives into other topics such as how to use the MicroPython Remote ```pmremote`````` tools to automate the loading of software onto your microcontroller.
Advanced Topics
"},{"location":"#section-9-kits","title":"Section 9: Kits","text":"This section contains detailed steps to use the popular educational kits that are now integrating MicroPython and the RP2040 microcontroller. There are many kits and these lessons contain full working programs to build complex projects like a collision avoidance robot with OLED displays.
MicroPython Kits
"},{"location":"#related-and-reference-material","title":"Related and Reference Material","text":"Lastly, we have a large glossary of terms, contact information and references to other websites that might be useful in your projects. Many of our more advanced projects have been moved into separate websites. Here are a few of these sites:
Glossary of MicroPython Terms
"},{"location":"#references","title":"References","text":"This is an annotated list of other on-line resources to help you learn MicroPython and use microcontrollers.
Micropython References - links to other useful sites.
If you have suggestions for additional references projects, please let us know!
"},{"location":"#contact","title":"Contact","text":"Contact
"},{"location":"status/","title":"Status of MicroPython Labs","text":""},{"location":"status/#project-boxes","title":"Project Boxes","text":""},{"location":"status/#10-element-led-box","title":"10 Element LED Box","text":"This section includes some advanced topics that might be useful for helping you create and debug MicroPython projects.
Topics include:
Wokwi ESP32 Simulator for the Raspberry Pi Pico
"},{"location":"advanced-labs/01-intro/","title":"Advanced Labs","text":"This section includes some advanced topics that might be useful for helping you create and debug MicroPython projects.
Topics include:
An Interrupt Handler (also called an ISR for Interrupt Service Request) is a special Python function that is called when specific events occur such as a button being pressed. ISRs are the preferred way to detect external events, as opposed to polling methods that are inconsistent and inefficient. However, they are a bit tricky to setup and debug. So a good design should be as simple as possible and avoid using complex features unless you really know you need them.
"},{"location":"advanced-labs/02-interrupt-handlers/#polling-vs-interrupt-handlers","title":"Polling vs. Interrupt Handlers","text":"So why are ISRs so important? Let's illustrate this is a story.
Imagine you have 10 friends each with a button at their home. In the polling method you would need to drive to each of their houses and ask them \"Is the button get pressed\"? You would have to do this frequently in case the button was pressed and released too quickly. This is a slow and painful process and takes a lot of CPU cycles.
An interrupt handler on the other hand has each friend tell you directly if their button has been pressed. The messages are quick and efficient. They don't use a lot of extra CPU power and the results get handled quickly.
However, there are specific rules about what we can and can't do within an ISR function. They need to be quick and efficient. We can't wonder off and do crazy things like printing debugging lines within a good ISR. Our job is typically update a global value and finish ASAP. A good ISR should be as efficient as possible.
"},{"location":"advanced-labs/02-interrupt-handlers/#simple-button-press-isr-example","title":"Simple Button Press ISR Example","text":"This is our first ISR example. It has several parts:
# Use an interrupt function count the number of times a button has been pressed\nfrom machine import Pin\nimport micropython\nimport time\n\n# global value\nbutton_pressed_count = 0\n\n# Interrupt Service Routine for Button Pressed Events - with no debounce\ndef button1_pressed(change):\n global button_pressed_count\n button_pressed_count += 1\n\n# we define button1 as being connected to GP14 and to use the internal Pico PULL_DOWN resistor\nbutton1 = Pin(14, Pin.IN, Pin.PULL_DOWN)\n\n# here is how we associate the falling value on the input pin with the callback function\nbutton1.irq(handler=button1_pressed, trigger=Pin.IRQ_FALLING)\n\nbutton_pressed_count_old = 0\nwhile True:\n if button_pressed_count_old != button_pressed_count:\n print('Button 1 value:', button_pressed_count)\n button_pressed_count_old = button_pressed_count\n
Now if you run this program, you will see that it prints to the Terminal each time the button is pressed and it also tells us how many times the button has been pressed.
example output:
Button 1 value: 1\nButton 1 value: 2\nButton 1 value: 3\nButton 1 value: 4\nButton 1 value: 5\nButton 1 value: 6\nButton 1 value: 8\nButton 1 value: 9\nButton 1 value: 10\nButton 1 value: 11\nButton 1 value: 12\nButton 1 value: 13\nButton 1 value: 14\nButton 1 value: 15\nButton 1 value: 16\nButton 1 value: 17\nButton 1 value: 18\nButton 1 value: 19\nButton 1 value: 20\nButton 1 value: 21\n
But if you are careful, you will note something slightly unexpected might happen. I the example above, I actually only pressed the button about 10 times. But the button value is 21! What could be going on here? Could there be a bug in the code?
The answer is that buttons are not perfect on/off switches. They are essentially noisy on/off devices that may go through a transition of off/on/off/on each time we press the button.
As a switch goes from open to closed, it moves from a stable state, through an unstable transition state and then it finally arrives at a new stable state. This is illustrated in the drawing below.
We can reduce this \"noise\" with a small capacitor next to the button. The capacitor will quickly absorb the energy of the button transition and it will \"smooth\" out the spikes. This will give us a more consistent readout of the number of button presses and avoid accidental \"double presses\" that were not intended.
However, we can also get a clean signal by using software. The key is when we first detect that a transition may be happening we \"stop listening\" for a short period of time until we are confident that the unstable transition state is over. This is typically around 20 milliseconds, but there may be a few stray signals left. Since we may not have to detect changes more frequently than 5 presses per second, we can go to sleep in our ISR for up to 200 milliseconds. This will give us a nice stable reading from the button.
These are general rules but for our breadboard mounted momentary switches, the values are appropriate.
"},{"location":"advanced-labs/02-interrupt-handlers/#debounced-version-of-a-button-press-detection","title":"Debounced Version of a Button Press Detection","text":"Now let's show you the code that does the hard work of debouncing a signal from a button or switch.
In this example, our ISR is called button_pressed_handler(pin)
. As soon as it is called, it checks the number of milliseconds since it was last called. If the time difference is under 200 milliseconds we are good to go and we update the button_presses global variable. If we are under the 200 millisecond window, we might be in that transition state and we don't do anything.
new_time = utime.ticks_ms()\n# if it has been more that 1/5 of a second since the last event, we have a new event\n if (new_time - last_time) > 200: \n button_presses +=1\n last_time = new_time\n
The net effect is that the presses variable will ONLY be incremented once, and not multiple times during the transition. Here is the full code:
import utime\nfrom machine import Pin\n\n# Sample Raspberry Pi Pico MicroPython button press example with a debounce delay value of 200ms in the interrupt handler\n\nbutton_presses = 0 # the count of times the button has been pressed\nlast_time = 0 # the last time we pressed the button\n\nbuiltin_led = machine.Pin(25, Pin.OUT)\n# the lower left corner of the Pico has a wire that goes through the buttons upper left and the lower right goes to the 3.3 rail\nbutton_pin = machine.Pin(14, machine.Pin.IN, machine.Pin.PULL_DOWN)\n\n# this function gets called every time the button is pressed\ndef button_pressed_handler(pin):\n global button_presses, last_time\n new_time = utime.ticks_ms()\n # if it has been more that 1/5 of a second since the last event, we have a new event\n if (new_time - last_time) > 200: \n button_presses +=1\n last_time = new_time\n\n# now we register the handler function when the button is pressed\nbutton_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\n\n# This is for only printing when a new button press count value happens\nold_presses = 0\nwhile True:\n # only print on change in the button_presses value\n if button_presses != old_presses:\n print(button_presses)\n builtin_led.toggle()\n old_presses = button_presses\n
"},{"location":"advanced-labs/02-interrupt-handlers/#isr-with-deactivation","title":"ISR with Deactivation","text":"Although there are benefits to the simplicity of the code above, some microcontrollers developers suggest that you simply deactivate the IRQ during the debounce sleep. This makes sense since there is two small calculation of the time differences (a subtraction and a compare operation) that do not need to be performed.
The key lines we add are a deactivate of the IRQ, a sleep for 200 milliseconds and a re-enable of the IRQ after the sleep. Both approaches have worked for me and I will let you decide the tradeoffs.
import machine, utime\n\n# the lower right coner has a wire that goes throuh\ncount_input = machine.Pin(14, machine.Pin.IN, machine.Pin.PULL_DOWN)\npresses = 0\n\ndef count_handler(pin):\n global presses\n # disable the IRQ during our debounce check\n count_input.irq(handler=None)\n presses +=1\n # debounce time - we ignore any activity diring this period \n utime.sleep_ms(200)\n # re-enable the IRQ\n count_input.irq(trigger=machine.Pin.IRQ_FALLING, handler = count_handler)\n\ncount_input.irq(trigger=machine.Pin.IRQ_FALLING, handler = count_handler)\n\nold_presses = 0\nwhile True:\n # only print on change\n if presses != old_presses:\n if presses > old_presses + 1:\n print('double counting in irq. Fixing...')\n presses = old_presses + 1\n print(presses)\n old_presses = presses\n
"},{"location":"advanced-labs/02-interrupt-handlers/#debounce-without-disabling-the-irq","title":"Debounce Without Disabling the IRQ","text":""},{"location":"advanced-labs/02-interrupt-handlers/#references","title":"References","text":"The Raspberry Pi Pico has an internal temperature sensor that can be access using machine.ADC(4)
. This might be useful to see if your RP2040 CPY is running \"hot\" and might benefit from a cooling fan.
import machine\nimport utime\nsensor_temp = machine.ADC(4)\nwhile True:\n reading = sensor_temp.read_u16() * conversion_factor\n temperature = 27 - (reading - 0.706)/0.001721\n print(temperature)\n print('\\n')\n
"},{"location":"advanced-labs/03-internal-temperature/#logging-the-temperature","title":"Logging the Temperature","text":"import machine\nimport utime\nsensor_temp = machine.ADC(machine.ADC.CORE_TEMP)\nconversion_factor = 3.3 / (65535)\nfile = open(\"temps.txt\", \"w\")\nwhile True:\n reading = sensor_temp.read_u16() * conversion_factor\n temperature = 27 - (reading - 0.706)/0.001721\n file.write(str(temperature))\n file.flush()\n utime.sleep(10)\n
"},{"location":"advanced-labs/03-timing-functions/","title":"Timing Functions","text":"We often need to calculate how much time has elapsed since an event occurred. To to this we can use the ticks
functions in the MicroPython utime library.
There are one million microseconds in a single second. The utime
library allows us to count the number of microseconds that have elapsed since the processor was powered up.
The following example times the sleep function and measures the difference in the number of clock ticks in microseconds between the two events.
import machine, utime\n\nstart_time = utime.ticks_us()\n# sleep for 1 second\nutime.sleep(1)\nend_time = utime.ticks_us()\n\nwhile True:\n print(\"Start Time:\", start_time)\n print(\"End Time:\", end_time)\n print(\"Delta Time:\", end_time - start_time)\n print(\"\")\n
results:
Start Time: 403122147\nEnd Time: 404122241\nDelta Time: 1000094\n\nStart Time: 403122147\nEnd Time: 404122241\nDelta Time: 1000096\n
You will note that the difference between the start and end time should be one million microseconds. However, the run-time libraries on the pico have some variability, so you will see the actual time vary by a few microseconds. Most of the time you can use milliseconds to compare time intervals.
"},{"location":"advanced-labs/03-timing-functions/#millisecond-timer","title":"Millisecond Timer","text":"import machine, utime\n\nstart_time = utime.ticks_ms()\nutime.sleep(1)\nend_time = utime.ticks_ms()\n\nwhile True:\n print(\"Start Time:\", start_time)\n print(\"End Time:\", end_time)\n print(\"Delta Time:\", end_time - start_time)\n print(\"\")\n
results:
Start Time: 855845\nEnd Time: 856845\nDelta Time: 1000\n\nStart Time: 858031\nEnd Time: 859032\nDelta Time: 1001\n
These results are almost always 1000 with an occasional 1001 value.
"},{"location":"advanced-labs/04-conda-env/","title":"Creating a Conda Environment for MicroPython","text":"Conda is a powerful tool for building consistent and stable Python environments. These environments include all the Python libraries that you need to be a productive MicroPython developer. Using Conda allows you to keep each of your Python projects cleanly separated. This may not be important on your 2nd or 3rd Python project, but as you do more Python projects you will benefit from isolated environments that each have their own versions of each Python libraries that will not conflict with each other.
One other important fact to remember is that if you use a conda virtual environment you should never need to use sudo (root) to install Python libraries. This means your installation scripts are more secure and less likely to introduce security problems and accidentally remove libraries that other projects depend on.
"},{"location":"advanced-labs/04-conda-env/#getting-started","title":"Getting Started","text":"To get started, it is best to go directly to the Conda web site and follow the installation instructions for you specific version of your operating system. There are many variations of installation not just for Windows, Mac and Linux, but each version my had different steps required.
Once you can open a terminal and type in conda --version
you have successfully installed Conda. For this document we have used conda:
conda --version\n
which returns:
conda 4.10.1\n
"},{"location":"advanced-labs/04-conda-env/#creating-your-conda-environment","title":"Creating Your Conda Environment","text":"Our fist job is to create a desktop environment that allows us to run Python programs that support the MicroPython development process.
conda create -n micropython python=3\n
conda activate micropython\n
After this your prompt should now show that you are in the micropython environment.
This process may take about five minutes, since all the most current libraries must be downloaded onto your desktop. Once this process finsihes you must remember to deactivate your current conda environment (if you have one and then activate your new micropython environment.
"},{"location":"advanced-labs/04-conda-env/#references","title":"References","text":""},{"location":"advanced-labs/04-conda-env/#raspberry-pi-pico-forum-on-micropython-site","title":"Raspberry Pi Pico Forum on MicroPython Site","text":"MicroPython Pico Forum
"},{"location":"advanced-labs/04-conda-env/#micropython-pycopy-pycopy","title":"MicroPython PyCopy (pycopy)","text":"MicroPython PyCopy
"},{"location":"advanced-labs/05-os-functions/","title":"Raspberry Pi Pico OS Functions","text":"In the Thonny tool, open the Terminal. At the REPL prompt type:
help()
which returns
help()\n
returns
Welcome to MicroPython!\n\nFor online help please visit https://micropython.org/help/.\n\nFor access to the hardware use the 'machine' module. RP2 specific commands\nare in the 'rp2' module.\n\nQuick overview of some objects:\n machine.Pin(pin) -- get a pin, eg machine.Pin(0)\n machine.Pin(pin, m, [p]) -- get a pin and configure it for IO mode m, pull mode p\n methods: init(..), value([v]), high(), low(), irq(handler)\n machine.ADC(pin) -- make an analog object from a pin\n methods: read_u16()\n machine.PWM(pin) -- make a PWM object from a pin\n methods: deinit(), freq([f]), duty_u16([d]), duty_ns([d])\n machine.I2C(id) -- create an I2C object (id=0,1)\n methods: readfrom(addr, buf, stop=True), writeto(addr, buf, stop=True)\n readfrom_mem(addr, memaddr, arg), writeto_mem(addr, memaddr, arg)\n machine.SPI(id, baudrate=1000000) -- create an SPI object (id=0,1)\n methods: read(nbytes, write=0x00), write(buf), write_readinto(wr_buf, rd_buf)\n machine.Timer(freq, callback) -- create a software timer object\n eg: machine.Timer(freq=1, callback=lambda t:print(t))\n\nPins are numbered 0-29, and 26-29 have ADC capabilities\nPin IO modes are: Pin.IN, Pin.OUT, Pin.ALT\nPin pull modes are: Pin.PULL_UP, Pin.PULL_DOWN\n\nUseful control commands:\n CTRL-C -- interrupt a running program\n CTRL-D -- on a blank line, do a soft reset of the board\n CTRL-E -- on a blank line, enter paste mode\n\nFor further help on a specific object, type help(obj)\nFor a list of available modules, type help('modules')\n
followed by
help(modules)
help('modules')\n__main__ gc uasyncio/funcs uos\n_boot machine uasyncio/lock urandom\n_onewire math uasyncio/stream ure\n_rp2 micropython ubinascii uselect\n_thread onewire ucollections ustruct\n_uasyncio rp2 uctypes usys\nbuiltins uarray uerrno utime\ncmath uasyncio/__init__ uhashlib uzlib\nds18x20 uasyncio/core uio\nframebuf uasyncio/event ujson\n
"},{"location":"advanced-labs/05-os-functions/#os-functions","title":"OS Functions","text":"import os\nprint(dir(os))\n
returns
['__class__', '__name__', 'remove', 'VfsFat', 'VfsLfs2', 'chdir', 'getcwd', 'ilistdir', 'listdir', 'mkdir', 'mount', 'rename', 'rmdir', 'stat', 'statvfs', 'umount', 'uname', 'urandom']\n
"},{"location":"advanced-labs/05-os-functions/#real-time-clock","title":"Real Time Clock","text":"from machine import RTC\n\nrtc = RTC()\nrtc.datetime((2017, 8, 23, 2, 12, 48, 0, 0)) # set a specific date and time\nrtc.datetime() # get date and time\n
See MicroPython Real Time Clock
"},{"location":"advanced-labs/06-i2c/","title":"Raspberry Pi Pico I2C","text":"The Pico has two I2C hardware controllers. Each controller can talk to multiple IIC devices as long as all the devices communicating on each controller have distinct addresses.
import machine\n\nI2C_SDA_PIN = 0\nI2C_SCL_PIN = 1\ni2c=machine.I2C(0,sda=machine.Pin(I2C_SDA_PIN), scl=machine.Pin(I2C_SCL_PIN), freq=400000)\n\nprint('Scanning I2C bus.')\ndevices = i2c.scan() # this returns a list of devices\n\ndevice_count = len(devices)\n\nif device_count == 0:\n print('No i2c device found.')\nelse:\n print(device_count, 'devices found.')\n\nfor device in devices:\n print('Decimal address:', device, \", Hex address: \", hex(device))\n
"},{"location":"advanced-labs/06-i2c/#i2c-scanner-for-both-i2c-0-and-1","title":"I2C Scanner for Both I2C 0 and 1","text":"import machine\n\nI2C0_SDA_PIN = 0\nI2C0_SCL_PIN = 1\nI2C1_SDA_PIN = 2\nI2C1_SCL_PIN = 3\ni2c0=machine.I2C(0,sda=machine.Pin(I2C0_SDA_PIN), scl=machine.Pin(I2C0_SCL_PIN), freq=400000)\ni2c1=machine.I2C(1,sda=machine.Pin(I2C1_SDA_PIN), scl=machine.Pin(I2C1_SCL_PIN), freq=400000)\n\nprint('Scanning I2C bus 0.')\ndevices = i2c0.scan() # this returns a list of devices\n\ndevice_count = len(devices)\n\nif device_count == 0:\n print('No i2c device found on bus 0.')\nelse:\n print(device_count, 'devices found.')\n\nfor device in devices:\n print('Decimal address:', device, \", Hex address: \", hex(device))\n\nprint('Scanning I2C bus 1.')\ndevices = i2c1.scan() # this returns a list of devices\n\ndevice_count = len(devices)\n\nif device_count == 0:\n print('No i2c device found on bus 1.')\nelse:\n print(device_count, 'devices found.')\n\nfor device in devices:\n print('Decimal address:', device, \", Hex address: \", hex(device))\n
Results for both a OLED display on I2C 0 and a time-of-flight sensor on I2C 1
Scanning I2C bus 0.\n1 devices found.\nDecimal address: 60 , Hex address: 0x3c\nScanning I2C bus 1.\n1 devices found.\nDecimal address: 41 , Hex address: 0x29\n
"},{"location":"advanced-labs/06-i2c/#references","title":"References","text":"MicroPython.org docs on framebuf
"},{"location":"advanced-labs/08-minicom/","title":"Minicom","text":""},{"location":"advanced-labs/08-minicom/#installation","title":"Installation","text":""},{"location":"advanced-labs/08-minicom/#installation-on-a-mac","title":"Installation on a Mac","text":"brew install minicom\n
"},{"location":"advanced-labs/08-minicom/#installation-on-linux-raspberry-pi-os","title":"Installation on Linux (Raspberry Pi OS)","text":"sudo apt install minicom\n
"},{"location":"advanced-labs/08-minicom/#verification-of-installation","title":"Verification of Installation","text":"which minicom\n
/usr/local/bin/minicom\n
minicom --version\n
minicom version 2.8 (compiled Jan 4 2021)\n
minicom --help\n
returns:
Usage: minicom [OPTION]... [configuration]\nA terminal program for Linux and other unix-like systems.\n\n -b, --baudrate : set baudrate (ignore the value from config)\n -D, --device : set device name (ignore the value from config)\n -s, --setup : enter setup mode\n -o, --noinit : do not initialize modem & lockfiles at startup\n -m, --metakey : use meta or alt key for commands\n -M, --metakey8 : use 8bit meta key for commands\n -l, --ansi : literal; assume screen uses non IBM-PC character set\n -L, --iso : don't assume screen uses ISO8859\n -w, --wrap : Linewrap on\n -H, --displayhex : display output in hex\n -z, --statline : try to use terminal's status line\n -7, --7bit : force 7bit mode\n -8, --8bit : force 8bit mode\n -c, --color=on/off : ANSI style color usage on or off\n -a, --attrib=on/off : use reverse or highlight attributes on or off\n -t, --term=TERM : override TERM environment variable\n -S, --script=SCRIPT : run SCRIPT at startup\n -d, --dial=ENTRY : dial ENTRY from the dialing directory\n -p, --ptty=TTYP : connect to pseudo terminal\n -C, --capturefile=FILE : start capturing to FILE\n --capturefile-buffer-mode=MODE : set buffering mode of capture file\n -F, --statlinefmt : format of status line\n -R, --remotecharset : character set of communication partner\n -v, --version : output version information and exit\n -h, --help : show help\n configuration : configuration file to use\n\nThese options can also be specified in the MINICOM environment variable.\nThis variable is currently unset.\nThe configuration directory for the access file and the configurations\nis compiled to /usr/local/Cellar/minicom/2.8/etc.\n\nReport bugs to <minicom-devel@lists.alioth.debian.org>.\n
"},{"location":"advanced-labs/08-minicom/#references","title":"References","text":""},{"location":"advanced-labs/09-micro-sd-card-reader/","title":"Micro SD Card Reader","text":"Secure Digital (SD) is a non-volatile memory card format for use in portable devices such as cameras, MP3 players and portable devices.
On Microcontrollers SD cards are usually access through an SPI interface although there are also devices that use I2C interfaces.
"},{"location":"advanced-labs/09-micro-sd-card-reader/#maker-pi-pico-connections","title":"Maker Pi Pico Connections","text":"GPIO Pin SD Mode SPI Mode GP10 CLK SCK GP11 CMD SDI GP12 DAT0 SD0 GP13 DAT1 X GP14 DAT2 X GP15 CD/DAT3 CSn"},{"location":"advanced-labs/09-micro-sd-card-reader/#maker-pi-pico-example-code","title":"Maker Pi Pico Example Code","text":""},{"location":"advanced-labs/09-micro-sd-card-reader/#pin-definitions","title":"Pin Definitions","text":"# SD Mode Definitions\nSDCARD_CLK = 10\nSDCARD_CMD = 11\nSDCARD_DAT0 = 12\nSDCARD_DAT1 = 13\nSDCARD_DAT2 = 14\nSDCARD_CD_DAT3 = 15\n\n# SPI Mode Definitions\nSDCARD_SCK = 10\nSDCARD_SDI = 11\nSDCARD_SD0 = 12\nSDCARD_X1 = 13\nSDCARD_X2 = 14\nSDCARD_CS = 15\n
"},{"location":"advanced-labs/09-micro-sd-card-reader/#sample-code-for-spi-mode","title":"Sample Code for SPI Mode","text":"import machine, os, sdcard\n\n# Assign chip select (CS) pin (and start it high)\ncs = machine.Pin(15, machine.Pin.OUT)\n# Intialize SPI peripheral (start with 1 MHz)\nspi = machine.SPI(1,\n baudrate=1000000,\n polarity=0,\n phase=0,\n bits=8,\n firstbit=machine.SPI.MSB,\n sck=machine.Pin(10),\n mosi=machine.Pin(11),\n miso=machine.Pin(12))\n# Initialize SD card\nsd = sdcard.SDCard(spi, cs)\n\n# OR this simpler initialization code should works on Maker Pi Pico too...\n#sd = sdcard.SDCard(machine.SPI(1), machine.Pin(15))\n\nos.mount(sd, '/sd')\n# check the content\nos.listdir('/sd')\n\n# try some standard file operations\nfile = open('/sd/test.txt', 'w')\nfile.write('Testing SD card on Maker Pi Pico')\nfile.close()\nfile = open('/sd/test.txt', 'r')\ndata = file.read()\nprint(data)\nfile.close()\n
Results:
Testing SD card on Maker Pi Pico\n
"},{"location":"advanced-labs/09-micro-sd-card-reader/#references","title":"References","text":"MicroPython was created in by Australian programmer Damian George in May of 2014. Although Adafruit originally supported MicroPython, in July 2017 Adafruit created a fork of MicroPython and called it CircuitPython. We can only speculate why this decisive action was taken, but the result is it divided the community into two incompatible branches and it doubled the amount of work needed to be done to introduce a new device to the Python community. Unfortunately, the consequence is that many programs written in CircuitPython are difficult to port to MicroPython.
Today, according to Google Trends, MicroPython is still four more popular than CircuitPython when we look at worldwide web search comparisons. However, in the US, the popularity is more equal. Although the claim was that CircuitPython was done for \"simplicity\" there is little evidence that CircuitPython programs are smaller or easier to maintain than MicroPython.
The one thing is clear, CircuitPython has lots of drivers for unusual hardware devices. If you find a driver you need in MicroPython you will need to convert it to MicroPython. This is usually done manually on a line-by-line basis.
"},{"location":"advanced-labs/10-converting-circuitpython-to-micropython/#setting-up-a-circuitpython-virtual-environment","title":"Setting up a CircuitPython Virtual Environment","text":"Because MicroPython and CircuitPython are incompatible, it is important that you don't intermix your Python libraries.
Here is how we setup a virtual environment for CircuitPython using Conda.
conda create -n circuitpython python=3\nconda activate circuitpython\n
"},{"location":"advanced-labs/10-converting-circuitpython-to-micropython/#installing-the-ssd1306-circuitpython-library","title":"Installing the SSD1306 CircuitPython Library","text":"Per Directions Here
pip3 install adafruit-circuitpython-ssd1306\n
pip3 install adafruit-circuitpython-displayio-ssd1306\n
Note
ERROR: Could not find a version that satisfies the requirement adafruit-circuitpython-displayio-ssd1306 ERROR: No matching distribution found for adafruit-circuitpython-displayio-ssd1306
Successfully installed Adafruit-Blinka-6.3.2 Adafruit-PlatformDetect-3.2.0 Adafruit-PureIO-1.1.8 adafruit-circuitpython-busdevice-5.0.6 adafruit-circuitpython-framebuf-1.4.6 adafruit-circuitpython-ssd1306-2.11.1 pyftdi-0.52.9 pyserial-3.5 pyusb-1.1.1\n
# Basic example of clearing and drawing pixels on a SSD1306 OLED display.\n# This example and library is meant to work with Adafruit CircuitPython API.\n# Author: Tony DiCola\n# License: Public Domain\n\n# Import all board pins.\nfrom board import SCL, SDA\nimport busio\n\n# Import the SSD1306 module.\nimport adafruit_ssd1306\n\n\n# Create the I2C interface.\ni2c = busio.I2C(SCL, SDA)\n\n# Create the SSD1306 OLED class.\n# The first two parameters are the pixel width and pixel height. Change these\n# to the right size for your display!\ndisplay = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c)\n# Alternatively you can change the I2C address of the device with an addr parameter:\n#display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, addr=0x31)\n\n# Clear the display. Always call show after changing pixels to make the display\n# update visible!\ndisplay.fill(0)\n\ndisplay.show()\n\n# Set a pixel in the origin 0,0 position.\ndisplay.pixel(0, 0, 1)\n# Set a pixel in the middle 64, 16 position.\ndisplay.pixel(64, 16, 1)\n# Set a pixel in the opposite 127, 31 position.\ndisplay.pixel(127, 31, 1)\ndisplay.show()\n
"},{"location":"advanced-labs/10-converting-circuitpython-to-micropython/#using-chatgpt-to-convert-circuitpython-to-micropython","title":"Using ChatGPT to convert CircuitPython to MicroPython","text":"ChatGPT does a good job of automatically converting CircuitPython to MicroPython. In your prompt you just add the following instruction:
Convert the following CircuitPython code to MicroPython:\n
An example of this working with the basic blink example is shown below:
"},{"location":"advanced-labs/10-converting-circuitpython-to-micropython/#trend-analysis","title":"Trend analysis","text":"As of March 2021, MicroPython is about two to four times more popular than CircuitPython.
Google Worldwide Search Trends
"},{"location":"advanced-labs/11-mpremote/","title":"MicroPython Remote","text":"MicroPython now has a standard format for all remote access. The program is called mpremote. There is ample documentation on the site, and there is a higher chance it will include the latest features.
"},{"location":"advanced-labs/11-mpremote/#why-use-micropython-remote","title":"Why Use MicroPython Remote","text":"There are three main reasons to use mpremote:
A partial list of the most frequently used commands are:
$EDITOR
program and then copy the file back to the Pico.Note that you can only be connected to one remote device at a time to use many commands.
"},{"location":"advanced-labs/11-mpremote/#installing","title":"Installing","text":"The first time:
pip install --user mpremote\n
pip install --upgrade --user mpremote\n
"},{"location":"advanced-labs/11-mpremote/#install-log","title":"Install Log","text":"I use conda and you can see that it found the mpremote package version 1.20.0
Requirement already satisfied: mpremote in /Users/dan/opt/miniconda3/envs/mkdocs/lib/python3.6/site-packages (1.20.0)\nRequirement already satisfied: pyserial>=3.3 in /Users/dan/opt/miniconda3/envs/mkdocs/lib/python3.6/site-packages (from mpremote) (3.5)\nRequirement already satisfied: importlib-metadata>=1.4 in /Users/dan/opt/miniconda3/envs/mkdocs/lib/python3.6/site-packages (from mpremote) (4.8.3)\nRequirement already satisfied: zipp>=0.5 in /Users/dan/opt/miniconda3/envs/mkdocs/lib/python3.6/site-packages (from importlib-metadata>=1.4->mpremote) (3.4.0)\nRequirement already satisfied: typing-extensions>=3.6.4 in /Users/dan/opt/miniconda3/envs/mkdocs/lib/python3.6/site-packages (from importlib-metadata>=1.4->mpremote) (3.7.4.3)\n
"},{"location":"advanced-labs/11-mpremote/#testing-the-version","title":"Testing the Version","text":"The version of mpremote is not yet working, but eventually, it will be used like this:
$ mpremote --version\n
mpremote 0.0.0-unknown\n
"},{"location":"advanced-labs/11-mpremote/#getting-help","title":"Getting Help","text":"$ mpremote --help\n
"},{"location":"advanced-labs/11-mpremote/#help-results","title":"Help Results","text":"mpremote -- MicroPython remote control\nSee https://docs.micropython.org/en/latest/reference/mpremote.html\n\nList of commands:\n connect connect to given device\n disconnect disconnect current device\n edit edit files on the device\n eval evaluate and print the string\n exec execute the string\n fs execute filesystem commands on the device\n help print help and exit\n mip install packages from micropython-lib or third-party sources\n mount mount local directory on device\n repl connect to given device\n resume resume a previous mpremote session (will not auto soft-reset)\n run run the given local script\n soft-reset perform a soft-reset of the device\n umount unmount the local directory\n version print version and exit\n\nList of shortcuts:\n --help \n --version \n a0 connect to serial port \"/dev/ttyACM0\"\n a1 connect to serial port \"/dev/ttyACM1\"\n a2 connect to serial port \"/dev/ttyACM2\"\n a3 connect to serial port \"/dev/ttyACM3\"\n bootloader make the device enter its bootloader\n c0 connect to serial port \"COM0\"\n c1 connect to serial port \"COM1\"\n c2 connect to serial port \"COM2\"\n c3 connect to serial port \"COM3\"\n cat \n cp \n devs list available serial ports\n df \n ls \n mkdir \n reset reset the device after delay\n rm \n rmdir \n setrtc \n touch \n u0 connect to serial port \"/dev/ttyUSB0\"\n u1 connect to serial port \"/dev/ttyUSB1\"\n u2 connect to serial port \"/dev/ttyUSB2\"\n u3 connect to serial port \"/dev/ttyUSB3\"```\n
"},{"location":"advanced-labs/11-mpremote/#examples-of-file-system-commands","title":"Examples of File System Commands","text":"Here is the syntax of the copy file command:
mpremote fs cp main.py :main.py\n
This copies the local file \"main.py\" to your pico. The colon \":\" is the root of the pico.
"},{"location":"advanced-labs/11-mpremote/#setting-up-a-unix-alias","title":"Setting Up a UNIX Alias","text":"If you get tired of typing \"mpremote fs cp\" you can create an command-line alias called \"pcp\" for Pico Copy:
alias pcp='mpremote fs cp'\n
The copy command the becomes simply:
pcp main.py :main.py\n
If you place this line in your .bashrc or similar shell startup it saves you a lot of typing.
File systems examples include:
cat <file..>
to show the contents of a file or files on the devicels
to list the current directoryls <dirs...>
to list the given directoriescp [-r] <src...> <dest>
to copy filesrm <src...>
to remove files on the devicemkdir <dirs...>
to create directories on the devicermdir <dirs...>
to remove directories on the devicetouch <file..>
to create the files (if they don\u2019t already exist)For large classrooms that teach MicroPython using kits, we recommend that you arrange all the mkdir
and copy (cp
) file shell commands in a single UNIX shell script for consistency.
Here are the steps:
mkdir
If you follow these steps, then when the students connect to the Pico using Thonny they will see all the labs in the right order from simple to the most complex.
"},{"location":"advanced-labs/11-os/","title":"Sample MicroPython OS functions","text":"MicroPython provides a small list of os functions that allow you to manipulate files on the local filesystem of a MicroController.
These functions for filesystem access, and mounting, terminal redirection and duplication, and the uname and urandom functions.
These commands include:
import os\nuname = os.uname()\nprint(uname)\n
Returns:
(\n sysname='rp2',\n nodename='rp2',\n release='1.19.1',\n version='v1.19.1-88-g74e33e714 on 2022-06-30 (GNU 11.2.0 MinSizeRel)',\n machine='Raspberry Pi Pico W with RP2040'\n)\n
Thonny does not easily allow you to delete files. To do this you will need to use the \"os\" functions.
import os\nos.listdir()\nos.remove('myfile')\nos.listdir()\n
To find out all the os functions use:
import os\nprint(dir(os))\n``\n\nReturns\n
['class', 'name', 'remove', 'VfsFat', 'VfsLfs2', 'chdir', 'getcwd', 'ilistdir', 'listdir', 'mkdir', 'mount', 'rename', 'rmdir', 'stat', 'statvfs', 'umount', 'uname', 'urandom']`"},{"location":"advanced-labs/11-os/#percent-storage-full-and-free-ram","title":"Percent Storage Full and Free RAM","text":"import gc\nimport os\n\ndef df():\n s = os.statvfs('//')\n return ('{0} MB'.format((s[0]*s[3])/1048576))\n\ndef free(full=False):\n F = gc.mem_free()\n A = gc.mem_alloc()\n T = F+A\n P = '{0:.2f}%'.format(F/T*100)\n if not full: return P\n else : return ('Total:{0} Free:{1} ({2})'.format(T,F,P))\n\ndef free-gc(full=False):\n gc.collect()\n F = gc.mem_free()\n A = gc.mem_alloc()\n T = F+A\n P = '{0:.2f}%'.format(F/T*100)\n if not full: return P\n else : return ('Total:{0} Free:{1} ({2})'.format(T,F,P))\n\nprint(df())\n\nprint(free())\n\nprint(free-gc())\n
"},{"location":"advanced-labs/11-os/#machine-clock-frequency","title":"Machine Clock frequency","text":"print('Machine Clock Frequency:', '{:,} MHz'.format(machine.freq()/1000000))\n
returns:
Machine Clock Frequency: 125 MHz\n
"},{"location":"advanced-labs/11-os/#references","title":"References","text":"https://www.youtube.com/watch?v=jnSX8ZMmHZ4
"},{"location":"advanced-labs/11-rshell/","title":"Using rshell on the Raspberry Pi Pico","text":"Note
MicroPython now has standardized file transfers with the new \"MicroPython Remote\" or mpremote commands. See the mpremote docs for details. We will be updating all our documentation to use this standard. This page is mostly for older systems.
Using an IDE such as Thonny you can copy many files at a time to the Raspberry Pi Pico by using the GUI.
However, this process becomes error prone if you want to copy a large number of files. To do this we will use the \"remote shell\" program from the command line. In our classes we often want to copy a dozen or more files to a new robot for student to try out.
Rshell was written by David Hyland. The source code and installations are documented on Dave's GitHub repository here: https://github.com/dhylands/rshell. Dave added support for the Raspberry Pi Pico in release 0.0.30 in March of 2021.
Rshell's primary use is to to get filesystem information on the pico (ls), and to copy files to and from MicroPython's filesystem. It can also be used as a terminal to run interactive REPL commands.
"},{"location":"advanced-labs/11-rshell/#conda-setup","title":"Conda Setup","text":"If you are new to Python and you don't have any previous virtual environments set up you can skip this step. Experienced Python developers have many different environments that they want to keep separated due to library incompatibility issues. Here is how to create a new Python Conda environment that keeps your rshell libraries separated.
conda create -n pico python=3\nconda deactivate\nconda activate pico\n
Your prompt should now indicate you are in the pico environment.
"},{"location":"advanced-labs/11-rshell/#install-rshell-in-your-pico-environment","title":"Install rshell in your pico environment","text":"We will now use the standard pip installer tool to install the rshell command.
python -m pip install rshell\n
You can check that rshell has been correctly installed in your command PATH by running the UNIX which
command.
which rshell\n
"},{"location":"advanced-labs/11-rshell/#running-shell","title":"Running shell","text":"Rshell communicates with the Pico through the USB port. When you plug in the Pico you should see a new file created in the UNIX /dev directory. It typically begins with the letters /dev/cu.modem
. One way to test this is to unlpug the pico and run the following command:
ls /dev/cu.usbmodem*\n
With the pico unplugged, there should be no files that match the ls wildcard pattern. However, after you plug in the pico the following should be returned: /dev/cu.usbmodem14101\n
This is the port you will use to connect to the pico. We will use the -p
for port option to startup rshell.
rshell -p /dev/cu.usbmodem14101\n
Using buffer-size of 128\nConnecting to /dev/cu.usbmodem14101 (buffer-size 128)...\nTrying to connect to REPL connected\nRetrieving sysname ... rp2\nTesting if ubinascii.unhexlify exists ... Y\nRetrieving root directories ... /LCD_SPI.py/ /lcd-spi-test.py/ /lcd-test.py/ /lcd.py/\nSetting time ... Oct 28, 2021 20:30:56\nEvaluating board_name ... pyboard\nRetrieving time epoch ... Jan 01, 1970\nWelcome to rshell. Use Control-D (or the exit command) to exit rshell.\n
"},{"location":"advanced-labs/11-rshell/#boards","title":"Boards","text":"The boards command will list the boards rshell is connected to:
boards pyboard @ /dev/cu.usbmodem14101 connected Epoch: 1970 Dirs: /pyboard/hello.py /pyboard/main.py
"},{"location":"advanced-labs/11-rshell/#listing-files","title":"Listing Files","text":"We can see that the board is called \"pyboard\" and you can use that as a path to list the files on the board.
ls /pyboard\nhello.py main.py\n
"},{"location":"advanced-labs/11-rshell/#giving-your-board-a-name","title":"Giving Your Board a Name","text":"rshell will look for a program called board.py when it connects to the board. If this file contains a board name it will use that as the board name the next time it connects to the board. You can use the \"echo\" command to generate the file. In the example below, we will call our board \"pico\"
echo 'name=\"pico\"' > /pyboard/board.py\n
After you use the CONTROL-C and reconnect you will see the following:
pico @ /dev/cu.usbmodem14101 connected Epoch: 1970 Dirs: /pico/hello.py /pico/main.py\n
Remember you must disconnect from rshell and reconnect before the boards.py function is used.
For the remainder of this lesson we will assume you have renamed your board \"pico\".
You can then type cd /pico
followed by a ls
to see the files on your pico.
You can enter the REPL loop using the repl
command and use the same commands that you used in the Thonny shell.
repl\nprint('Hello World!')\n
returns
Hello World!\n
"},{"location":"advanced-labs/11-rshell/#getting-help-on-rshell-commands","title":"Getting Help on rshell commands","text":"You can type the help
command to see all the rshell commands:
help\n\nDocumented commands (type help <topic>):\n========================================\nargs cat connect date edit filesize help mkdir rm shell\nboards cd cp echo exit filetype ls repl rsync\n\nUse Control-D (or the exit command) to exit rshell.\n
"},{"location":"advanced-labs/11-rshell/#running-backup","title":"Running Backup","text":"If you want to copy all the python files from the pico to a backup directory you can use the following command:
cd /pico\ncp *.py /Users/dan/backup\n
You will need to create the /Users/dan/backup directory before you do this. You can also use the tilde ~
character to stand in for your home directory like this:
cp *.py /Users/dan/backup\n
Copying '/pico/hello.py' to '/Users/dan/backup/hello.py' ... Copying '/pico/main.py' to '/Users/dan/backup/main.py' ...
"},{"location":"advanced-labs/11-rshell/#installing-files","title":"Installing files","text":"If you have a directory called ~/build
that contains many files you want to install on the pico file system you can use the following command:
cp ~/build/* /pico\n
If you have done a clone to the CoderDojoTC micropython repository and put it in your home directory under ~/micropython then following command will copy the python files from the Maker Pi RP2040 kit to your pico:
mkdir /pico/lib\ncp ~/micropython/src/drivers/*.py /pico/lib\ncp ~/micropython/src/kits/maker-pi-rp2040/*.py /pico\ncp ~Documents/ws/micropython/src/kits/maker-pi-rp2040-robots/*.py /pico/lib\n
Note that the drivers will be placed in the /lob directory.
"},{"location":"advanced-labs/11-rshell/#direct-command-execution","title":"Direct Command Execution","text":"You do not need to use an interactive session to run a command with rshell. You can just add the command you would like to run to the end of the rshell command like this:
rshell -p /dev/cu.usbmodem14101 ls /pico\n
returns:
hello.py main.py\n
"},{"location":"advanced-labs/12-string-formatting/","title":"MicroPython String Formatting","text":"The normal Python has advanced string formatting functions using \"%\" and .format methods documented at the PyFormat website.
Although most of these functions work, there are some exceptions when using date formats.
The following % formats do not work under MicroPython:
The following example prints a floating point number with two decimals of precision in a field of six characters with leading zeros.
print('{:06.2f}'.format(3.141592653589793))\n
returns:
003.14\n
"},{"location":"advanced-labs/13-timers/","title":"Timers in MicroPython","text":"When clocks are used to trigger future events, they are called timers. Timers are used to efficiently use CPU resources. In microcontrollers, the work of keeping track of timers is frequently delegated to hardware outside of the main CPU loop. This makes your microcontroller more efficient, more reliable, and makes your code easier to read.
Timers work independently of the main CPU's clock. As a result, they are called asynchronous objects. Using timers can be both efficient and reliable, but they can be complex to debug. Errors within Timers and interrupts can be difficult to get information on.
"},{"location":"advanced-labs/13-timers/#types-of-timers","title":"Types of Timers","text":"Timers are used to schedule events in the future. There are two types:
Both of these timer objects need a callback function to be specified when they are initialized. These callback functions are also called interrupt service routines (ISRs). This is the function that will be executed when the timer gets triggered. So we must define an ISR function before timers are properly initialized.
Periodic timers are usually initialized with a period
parameter. This is the amount of time in milliseconds between each event. They are useful for doing like checking if new data is available on a network connection or checking if a sensor is still working.
One-shot timers are also initialized with a period
parameter often called a \"timeout\" period. This is the amount of time in milliseconds before the timer will fire. One-shot timers are used if you want to do something in the future but don't want to deal with it now. You can think of this as a reminder service.
Here is how both Periodic and one-shot timers are setup:
myOneShotTimer = Timer()\n# call once in 10 seconds from now\nmyOneShotTimer.init(mode=Timer.ONE_SHOT, callback=myCallbackFunction, period=10000)\n\n# call every two seconds until the timer is deactivated\nmyPeriodicTimer = Timer()\nmyPeriodicTimer.init(mode=Timer.PERIODIC, callback=myCallbackFunction, period=2000) \n
"},{"location":"advanced-labs/13-timers/#using-frequency-on-periodic-timers","title":"Using Frequency on Periodic Timers","text":"A Periodic timer can be configured to use either a period or a frequency as a parameter. The frequency is the number of times the timer will fire per second. The period is the amount of time in milliseconds between each event. The frequency is used to calculate the period. The following are equivalent:
# 50ms between events\nmyTimer.init(period=50, mode=Timer.PERIODIC, callback=move_pixel) \n# 20 events per second\nmyTimer.init(freq=20, mode=Timer.PERIODIC, callback=move_pixel) \n
You can always convert between the two by taking the inverse and multiplying by 1000.
frequency = 1 / period * 1000\nperiod = 1 / frequency * 1000\n
"},{"location":"advanced-labs/13-timers/#hardware-vs-virtual-software-timers","title":"Hardware vs. Virtual Software Timers","text":"Older microcontrollers have fixed hardware associated with each timer. These are call \"hardware timers\". When you initialize a hardware timer, you specify what set of hardware to use using a fixed ID (0,1,2,3 etc.). On the Raspberry Pi Pico all timers are \"virtual\" and are implemented in software. All virtual timers have an ID of -1 and the timer ID does not need to be specified.
"},{"location":"advanced-labs/13-timers/#sample-timer-program","title":"Sample Timer Program","text":"In the following program we will create timer that will toggle the built-in LED on the Raspberry Pi Pico every second. We will create a new function called toggle_led()
that will toggle the builtin LED on and off each time it is called. There are three key lines in this program.
Here is a sample code to toggle the builtin LED on the Raspberry Pi Pico and off.
from machine import Pin, Timer\nfrom utime import sleep\n\n# create an LED object using the onboard LED\nmyLED = machine.Pin(\"LED\", machine.Pin.OUT)\n\n# create an uninitialized timer object\nmyTimer = Timer()\n\n# create a function to be called when the timer goes off\n# this function just toggles the onboard LED\ndef toggle_led(timer):\n myLED.toggle()\n\n# initialize the timer object to tick every second (1,000 milliseconds)\nmyTimer.init(period=1000, mode=Timer.PERIODIC, callback=toggle_led)\n\nwhile True:\n sleep(10)\n print('just sleeping here')\n
"},{"location":"advanced-labs/13-timers/#sample-code-to-animate-an-led-strip-with-a-timer","title":"Sample Code to Animate an LED Strip With a Timer","text":"Many times you want to animate an LED strip but not complicate up your main event loop with this code. For example, you might want to light up the entire strip with a pattern of motion on the LED strip. You can do this by creating a new function that will conditionally turn one pixel to a color and increment a global variable that keeps track of which pixel to turn on. This function will be called every time the timer goes off.
from machine import Pin, Timer\nfrom utime import sleep\nfrom neopixel import NeoPixel\n\nNEOPIXEL_PIN = 0\nNUMBER_PIXELS = 8\nstrip = NeoPixel(machine.Pin(NEOPIXEL_PIN), NUMBER_PIXELS)\n\n# create an uninitialized timer object\nmyTimer = Timer()\n\ncounter = 0\n# a callback function. Use this with a timer that triggers 20 times a second\ndef move_pixel(myTimer):\n global counter\n for i in range(0, NUMBER_PIXELS):\n if i == counter:\n strip[i] = (10,0,0)\n else:\n strip[i] = (0,0,10)\n strip.write()\n counter += 1\n if counter > NUMBER_PIXELS:\n counter = 0\n\n# initialize the timer object to tick 20 times per second (50 milliseconds)\nmyTimer.init(period=50, mode=Timer.PERIODIC, callback=move_pixel)\n\nwhile True:\n print('Just sleeping here. The timer is doing all the work flashing the LED...', counter)\n sleep(5) # sleep for five seconds\n
For example, if a robot has a set of \"modes\" like driving forward, turning left, turning right, backing up etc. You can use a timer to flash the LED strip in different colors with different patterns to indicate the robot is in different modes.
"},{"location":"advanced-labs/13-timers/#removing-a-timer","title":"Removing a Timer","text":"The Timer.deinit()
can be used to remove a timer from the system. This is useful if you want to stop a timer that is currently running. If you have many timers that are each using memory, it is a good practice to remove them when you are done with them.
In MicroPython on the RP2040, there are no limits placed on the number of timers other than you must have enough memory available. Because there are no specific hardware limits, these are often referred to as \"virtual\" timers. The number of virtual timers is limited by the amount of memory available to the microcontroller.
Other implementations of MicroPython on different hardware have stricter limits placed on the number of timers. The ESP32 MicroPython port currently only has four hardware timers and a numeric ID of 0 to 3 must be used when you setup these timers. For the pyboard has a limit of 14 timers. Be aware of these limits if you are interested in creating portable MicroPython code.
"},{"location":"advanced-labs/13-timers/#drawbacks-of-timers-and-isrs","title":"Drawbacks of Timers and ISRs","text":"Unfortunately, different hardware implementations of MicroPython have different ways to setup and use timers. Some hardware requires timers to each have an ID tied to a specific resource. Because of this, be cautious about using timers in your code if you require portability between hardware.
As we mentioned, timers need callback functions are a type of function called interrupt service requests (ISRs). In multi-core systems like the RP2040 these interrupts can only be called from the core that executed the timer. This means that if you are using a timer to trigger an interrupt, you must make sure that the interrupt is only called from the core that is executing the timer.
There are special limitations on what can and can't be done within interrupts in most systems. For example, you are not allowed to allocate dynamic memory within an interrupt. Your interrupt handler should be a short function that performs the minimum work to change external variables. This is because the interrupt handler is called in a separate thread and can't allocate memory.
In general, doing complex logic within timers and interrupts is not a good idea. If you are interested in doing complex logic, you should use a different method that is easy to debug. This will make your code easier to understand and maintain.
"},{"location":"advanced-labs/13-timers/#exercises","title":"Exercises","text":"machine.reset()
if it gets stuck? Hint: See Watchdog TimersIn the recent past, memory management has been a challenge for embedded systems. Before 2020 microcontrollers like the Arduino usually only came with a few kilobytes of RAM, and careful attention needed to be paid to each byte using clever memory management trickery. The memory management system in recent years has become much easier. Even a low-cost $4 Raspberry Pi Pico now comes with 264K RAM! This is an almost 100-fold increase over the older Arduino Uno which only had 2K of RAM. That being said, there are always developers that push their hardware to the limit, and knowing the basics of memory management is important to understand how embedded systems work.
MicroPython is very clever about how memory is allocated. It is very stingy about memory use and will only allocate memory for what is needed. This is very different from the standard Python that allocates memory for everything that is needed up-front assuming that most laptops, desktops and servers have gigabytes of RAM and virtual memory support. This is an important difference, and it is important to understand if you want to push the limits of your microcontroller.
This lab will cover the basics of memory management, and how to use the memory management system in MicroPython. We will cover the following topics:
The memory management system is responsible for allocating and freeing memory. The memory management system must allocate memory for data variables, and for freeing this memory when a variable is no longer needed. The system is also responsible for allocating memory for the parameters used by functions, and for freeing memory when a function is no longer needed. You can read more about memory management on the MicroPython Memory Management Docs website.
The heap is the area of memory that is used to store general data. In MicroPython the heap is located in the lower memory and it grows upwards in memory address space. The exception is memory used as parameters to functions, which is stored in the stack.
The stack is the area of memory that is used to store all the parameters to function calls. In MicroPython the stack is located in the upper memory and it grows downwards in memory address space. If you are calling highly recursive functions, the stack will get a lot of use.
The garbage collector is a process to reuse memory that is no longer in use. The garbage collector is automatically run when the heap is full, but you can also run it manually to reclaim unused memory to get finer-grain control over memory usage and to avoid memory fragmentation.
In MicroPython most of these operations are done automatically for you. You don't really need to worry about how memory works unless you are reaching the limits of what your microcontroller can do.
One other key concept is continuous memory. If you are allocating a large amount of memory in an array or a long string, we need to allocate all this memory in one large chunk. As programs run for a long time memory becomes fragmented (many small free sections) and can't be used for storing large arrays.
You can read more about how MicroPython is clever about memory usage by reading the MicroPython Optimizations Docs website.
"},{"location":"advanced-labs/14-memory-management/#functions-to-show-free-and-used-memory","title":"Functions to show free and used memory","text":"We will be using the \"gc\" module to show the amount of free and used memory. \"gc\" orignally stood for \"garbage collection\", but the model has been generalized to perform other memory management tasks.
Here are the key functions to show the amount of free and used memory and current memory usage on a Raspberry Pi Pico with an RP2040 microcontroller: gc.mem_free()
and gc.mem_alloc()
:
import gc\nimport micropython\n\nprint('Memory Free:', \"{:,}\".format(gc.mem_free()), 'bytes')\nprint('Memory Allocated:', \"{:,}\".format(gc.mem_alloc()), 'bytes')\n
results for RP2040:
Memory Free: 187,232 bytes\nMemory Allocated: 4,864 bytes\n
You can see that although the RP2040 chip has a specification of 264K of RAM, it only has 187,232 bytes of RAM available for program use. The other RAM us used to store the MicroPython interpreter software. You can also see that the heap is currently using 4,864 bytes of RAM. This is typical of the additional overhead that MicroPython requires to run a program.
"},{"location":"advanced-labs/14-memory-management/#viewing-memory-layout","title":"Viewing Memory Layout","text":"You can use the micropython.mem_info(1)
function to view the memory layout of the MicroPython interpreter. This function returns a list of tuples, each tuple containing the address, size and type of each memory block. The address on the left of each row is the memory address of the start of the block within the heap. In MicroPython, memory blocks are each typically 16 bytes.
```python\nimport micropython\nprint(micropython.mem_info(1))\n
results:
stack: 532 out of 7936\nGC: total: 192064, used: 4896, free: 187168\n No. of 1-blocks: 52, 2-blocks: 13, max blk sz: 64, max free sz: 11650\nGC memory layout; from 200084a0:\n00000: h=MhhhBDhhBTTBDhTBDBBBh===DBDh====B=BBBBBBTB=BTB=BBBTB=TBTB=Bh==\n00400: =BB=h===========TB=h=h===================h=====h==h=============\n00800: ====h===========================================================\n00c00: ====h===========================================================\n01000: ====h=hBhh=ShhS..h=.........Sh=.................................\n01400: ....Shh=======h========h====h=====..............................\n (181 lines all free)\n2ec00: ....................................\nNone\n
This shows that the garbage collector is responsible for managing a total of 192,064 bytes of memory. The other numbers give you an indication of how fragmented you heap is.
Each of the letters represents the type of memory at that position on the heap:
| Letter | Description | ||| |.|Free memory| |h|head of a block of memory| |=|tail of a block of memory| |T|Tuple| |L|List| |D|Dictionary| |S|String| |A|Array or Byte Array| |F|Float| |B|Function BC| |M|Module|
If the heap is 192,064 bytes and each block is 16 bytes then there should be 12,004 blocks on the heap. If each row in the report displays 62 characters then there are 12,004/62=193 rows in the report. To keep the report short, the function will only show the rows that are not free. The report indicates that there are 181 lines all free blocks, so it will only show the non-free lines which in the example above is about six non-free rows in lower heap memory.
"},{"location":"advanced-labs/14-memory-management/#functions-to-manually-allocate-and-free-memory","title":"Functions to manually allocate and free memory","text":"You can manually run the garbage collector using the gc.collect()
functions. This function is used to force garbage collection exactly when you want to, not when the heap is full. This may occur at a time that is inconvenient for the program when it must be sending data at a specific speed.
Note
Still under development.
import gc\nimport micropython\n\ndef show_memory():\n print('Memory Free:', \"{:,}\".format(gc.mem_free()), 'bytes')\n print('Memory Allocated:', \"{:,}\".format(gc.mem_alloc()), 'bytes')\n\nshow_memory()\n# allocate a block of memory\nprint('Allocating a block of memory')\nmyBigBlockOfMemory = matrix[100][100]\nshow_memory()\nprint('Freeing the block of memory')\n# free the block of memory\ngc.mem_free(ptr)\nshow_memory()\n
"},{"location":"advanced-labs/14-memory-management/#image-size","title":"Image Size","text":"You can determine how much space we have available on the Pico flash after programming an ELF or UF2 file. For example, if we have an ELF file that's 1 MB and we were to program via openocd, then where should we offset my user data in flash? (i.e. XIP_BASE + OFFSET)
With an elf or uf2 file, using picotool info -a
on it will show the start and end of the binary (the start is usually 0x10000000 unless you did something to change it).
Inside your code, you can use the symbols defined by the linker script __flash_binary_start (defined here) and __flash_binary_end (defined here) like this:
extern char __flash_binary_start; // defined in linker script\nextern char __flash_binary_end; // defined in linker script\nuintptr_t start = (uintptr_t) &__flash_binary_start;\nuintptr_t end = (uintptr_t) &__flash_binary_end;\nprintf(\"Binary starts at %08x and ends at %08x, size is %08x\\n\", start, end, end-start);\n
"},{"location":"advanced-labs/14-memory-management/#references","title":"References","text":"You need to set GP25 to output and set it high and also set GP29 to input with no pull resistors before reading. And don't forget that the input from VSYS to ADC is divided by 3, so you have to multiply your result to get real value. When I do that I get around 4.7 V when powered from USB, so it definitely works.
https://forums.raspberrypi.com/viewtopic.php?t=301152
import machine\n\n# Vsys = machine.ADC(3)\nVsys = machine.ADC(29)\nconversion_factor = (3.3 / (65535)) * 3\n\nreading = Vsys.read_u16() * conversion_factor\n\nprint(reading)\n
"},{"location":"advanced-labs/17-file-system/","title":"MicroPython File Systems","text":"Unlike older Arduino systems, MicroPython has full support for a \"virtual file system\" (VFS) that we use to store and retrieve programs and data. The way we access the file systems in MicroPython is similar to the way we access files in standard Python.
"},{"location":"advanced-labs/17-file-system/#blocks-and-fragments","title":"Blocks and Fragments","text":"First, we need to define two key terms: blocks and fragments.
The block size refers to the size of the fundamental unit of storage on the file system. All files and directories occupy an integral number of blocks, with the size of each file and directory being a multiple of the block size. The block size is typically a power of 2, and can vary depending on the file system and the size of the storage medium.
The fragment size, on the other hand, refers to the smallest unit of space that can be allocated for a file or directory. Files and directories may occupy a number of fragments that is not necessarily an integer multiple of the fragment size. Fragmentation occurs when the file system is unable to allocate a contiguous block of storage for a file or directory, resulting in the file or directory being spread out over multiple fragments. The fragment size is typically smaller than the block size, and may also vary depending on the file system and the size of the storage medium.
"},{"location":"advanced-labs/17-file-system/#file-systems-statistics","title":"File Systems Statistics","text":"In MicroPython, the os.statvfs('/') function provides information about the root file system. Among the information it provides is the block size and fragment size of the file system.
In the os.statvfs('/')
function, the block size and fragment size are reported as the first and second elements of the tuple returned by the function, respectively. Specifically, stats[0] contains the block size, and stats[1] contains the fragment size. These values can be used to calculate various file system statistics, such as the total size of the file system, the total number of blocks and fragments, and the amount of free space available.
You can also mount file systems on other flash drives. You can get the stats of these file systems by using the new mount point with the os.statvfs()
function.
\"\"\"\nPrint the statistics from the virtual file system (vfs)\n\nhttps://docs.micropython.org/en/latest/library/os.html?highlight=os#os.statvfs\n\n0 f_bsize \u2013 file system block size\n1 f_frsize \u2013 fragment size\n2 f_blocks \u2013 size of fs in f_frsize units\n3 f_bfree \u2013 number of free blocks\n4 f_bavail \u2013 number of free blocks for unprivileged users\n5 f_files \u2013 number of inodes\n6 f_ffree \u2013 number of free inodes\n7 f_favail \u2013 number of free inodes for unprivileged users\n8 f_flag \u2013 mount flags\n9 f_namemax \u2013 maximum filename length\n\"\"\"\n\nimport os\n\nstats = os.statvfs(\"/\")\nprint(stats)\n\nblock_size = stats[0]\nfragment_size = stats[1]\ntotal_blocks = stats[2]\n\nfree_blocks = stats[3]\navailable_blocks = stats[4]\n\nmount_flags = stats[8]\nmax_filename_length = stats[9]\n\n# byte calculations\ntotal_bytes = total_blocks * fragment_size\nfree_bytes = free_blocks * fragment_size\navailable_bytes = available_blocks * fragment_size\n\nprint(\"File system block size: {:,} bytes\".format(block_size))\nprint(\"Fragement size: {:,} bytes\".format(fragment_size))\nprint(\"Size of entire file system in fragement blocks: {:,}\".format(total_blocks))\nprint(\"Size of entire file system in bytes: {:,}\".format(total_bytes))\n\nprint(\"Total free blocks for system and users: {:,}\".format(free_blocks))\nprint(\"Number of free blocks for unprivileged users: {:,} bytes\".format(available_blocks))\n\nprint(\"Free size for system and users: {:,} bytes\".format(free_bytes))\nprint(\"Free size for users: {:,} bytes\".format(available_bytes))\nprint(\"Mount flags: {:,}\".format(mount_flags))\nprint(\"Max filename length: {:,}\".format(max_filename_length))\n
"},{"location":"advanced-labs/20-tinyml/","title":"TinyML","text":"TinyML is designed to help you do machine learning and embedded systems. Once you have trained a deep learning model, you can use TinyML perform data analysis (inference) on a microcontroller or a cell phone.
"},{"location":"advanced-labs/20-tinyml/#references","title":"References","text":"import re\n\ndef extract_gpio_pin(input_string):\n # Use a regular expression to find the GPIO number\n match = re.search(r\"GPIO(\\d+)\", input_string)\n if match:\n # Convert the extracted number to an integer to remove leading zeros\n return int(match.group(1))\n else:\n # Return None if no match is found (or raise an exception if that's preferable)\n return None\n\n# Test the function with examples\nprint(extract_gpio_pin(\"Pin(GPIO15, mode=IN, pull=PULL_UP)\")) # Output: 15\nprint(extract_gpio_pin(\"Pin(GPIO7, mode=IN, pull=PULL_UP)\")) # Output: 7\nprint(extract_gpio_pin(\"Pin(GPIO03, mode=IN, pull=PULL_UP)\")) # Output: 3\n
https://chat.openai.com/share/6e6d8123-ed4d-4dc6-a915-030fe2245dfe
"},{"location":"advanced-labs/30-spectrum-analyzer/","title":"Spectrum Analyzer","text":""},{"location":"advanced-labs/30-spectrum-analyzer/#chatgpt-prompt","title":"ChatGPT Prompt","text":"I would like to create an audio spectrum analyzer using a Raspberry Pi Pico and a 128x64 OLED display using MicroPython. How would you suggest I go about this project. Break it down step-by-step.\n
"},{"location":"advanced-labs/30-spectrum-analyzer/#chatgpt-gpt-4-response","title":"ChatGPT GPT-4 Response","text":"Creating an audio spectrum analyzer with a Raspberry Pi Pico and a 128x64 OLED display using MicroPython is a fascinating project that combines hardware interfacing with digital signal processing. Below is a step-by-step guide to help you get started on this project.
"},{"location":"advanced-labs/30-spectrum-analyzer/#step-1-gather-the-components","title":"Step 1: Gather the Components","text":"This project involves a mix of hardware setup, digital signal processing, and real-time data visualization. Take it step by step, and don't hesitate to seek out community forums and resources if you encounter challenges.
"},{"location":"advanced-labs/30-spectrum-analyzer/01-test-incoming-sound/","title":"Test Incoming Sound","text":""},{"location":"advanced-labs/30-spectrum-analyzer/01-test-incoming-sound/#prompt","title":"Prompt","text":"OK, I now have a Raspberry Pi Pico and the OLED display hooked up. I also have a microphone hooked up to ADC0 on port GPIO26. How can I test that the signal is coming into the ADC? Can you write a MicroPython program that displays the audio signal on the OLED?\n\nI am using a ssd1306 and I use the following MicroPython:\n
from machine import Pin\nimport ssd1306\n\nWIDTH = 128\nHEIGHT = 64\n\nclock=Pin(2) #SCL\ndata=Pin(3) #SDA\nRES = machine.Pin(4)\nDC = machine.Pin(5)\nCS = machine.Pin(6)\n\nspi=machine.SPI(0, sck=clock, mosi=data)\ndisplay = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)\n# clear the display\ndisplay.fill(0)\n# draw a diagonal line\ndisplay.line(0, 0, WIDTH, HEIGHT, 1)\n
"},{"location":"advanced-labs/30-spectrum-analyzer/01-test-incoming-sound/#gpt-4-reponse","title":"GPT-4 Reponse","text":"To test that the signal is coming into the ADC (Analog-to-Digital Converter) on the Raspberry Pi Pico via GPIO26, and to display the audio signal on your SSD1306 OLED, you can use the following MicroPython script. This script will read the analog values from the microphone connected to GPIO26 and plot these values in real-time on the OLED display, creating a simple waveform visualization.
Note: Before running this script, ensure you have properly initialized and connected your microphone to GPIO26 (ADC0), and the OLED display is correctly wired for SPI communication as per your provided initialization.
import machine\nimport utime\nfrom machine import Pin, ADC, SPI\nimport ssd1306\n\n# OLED display width and height\nWIDTH = 128\nHEIGHT = 64\n\n# SPI pins for OLED\nclock = Pin(2) # SCL\ndata = Pin(3) # SDA\nRES = Pin(4)\nDC = Pin(5)\nCS = Pin(6)\n\n# Initialize SPI and OLED Display\nspi = SPI(0, sck=clock, mosi=data)\ndisplay = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)\n\n# Initialize ADC for GPIO26 (ADC0)\nadc = ADC(Pin(26))\n\ndef plot_signal():\n display.fill(0) # Clear the display\n old_x = 0\n old_y = HEIGHT // 2\n\n # For simplicity, we're plotting every other pixel\n for x in range(0, WIDTH, 2):\n # Read from ADC (values will be from 0 to 4095)\n val = adc.read_u16()\n # Scale the ADC value to fit the OLED height\n y = int((val / 65535) * HEIGHT)\n # Invert y to plot correctly on the OLED\n y = HEIGHT - y\n # Draw a line from the last point to the new point\n display.line(old_x, old_y, x, y, 1)\n old_x, old_y = x, y\n\n display.show() # Update the display with the new data\n\nwhile True:\n plot_signal()\n utime.sleep(0.1) # Small delay to reduce flickering\n
This script continuously reads the analog signal from the microphone and plots a simplified version of the waveform across the OLED display. Here's a breakdown of the plotting part:
display.fill(0)
.display.show()
to visualize the waveform.This example provides a basic way to visualize audio signals. The resolution and quality of the visualization can be further enhanced or adjusted according to your requirements.
"},{"location":"advanced-labs/30-spectrum-analyzer/02-gain-pot/","title":"Adding A Gain Potentiometer.","text":""},{"location":"advanced-labs/30-spectrum-analyzer/02-gain-pot/#prompt","title":"Prompt","text":"The signal is very low on the display. I have added a Potentiometer on ADC1 which is on GPIO PIN 27. Can you please regenerate the entire program with sound on ADC0 and gain on ADC1. sound = ADC(Pin(26)) gain = ADC(Pin(27))
"},{"location":"advanced-labs/30-spectrum-analyzer/02-gain-pot/#chatgpt-","title":"ChatGPT-","text":"Certainly! To adjust the signal's amplitude on the display based on the gain from a potentiometer connected to ADC1 (GPIO PIN 27), you can modify the program as follows. This updated script will use the value from the potentiometer to scale the audio signal from the microphone, making the waveform larger or smaller on the OLED display according to the potentiometer's position.
import machine\nimport utime\nfrom machine import Pin, ADC, SPI\nimport ssd1306\n\n# OLED display width and height\nWIDTH = 128\nHEIGHT = 64\n\n# SPI pins for OLED\nclock = Pin(2) # SCL\ndata = Pin(3) # SDA\nRES = Pin(4)\nDC = Pin(5)\nCS = Pin(6)\n\n# Initialize SPI and OLED Display\nspi = SPI(0, sck=clock, mosi=data)\ndisplay = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)\n\n# Initialize ADC for sound input (GPIO26) and gain control (GPIO27)\nsound = ADC(Pin(26))\ngain = ADC(Pin(27))\n\ndef plot_signal_with_gain():\n display.fill(0) # Clear the display\n old_x = 0\n old_y = HEIGHT // 2\n\n # Read gain control (potentiometer) value\n gain_value = gain.read_u16() + 1 # Adding 1 to avoid division by zero\n\n for x in range(0, WIDTH, 2):\n # Read from ADC (sound input)\n val = sound.read_u16()\n\n # Adjust the sound value based on the gain\n # Note: This scaling might need adjustment depending on your specific potentiometer and desired sensitivity\n adjusted_val = min(((val * gain_value) >> 16), 65535) # Ensure the adjusted value does not exceed ADC's max value\n\n # Scale the adjusted value to fit the OLED height\n y = int((adjusted_val / 65535) * HEIGHT)\n # Invert y to plot correctly on the OLED\n y = HEIGHT - y\n # Draw a line from the last point to the new point\n display.line(old_x, old_y, x, y, 1)\n old_x, old_y = x, y\n\n display.show() # Update the display with the new data\n\nwhile True:\n plot_signal_with_gain()\n utime.sleep(0.1) # Small delay to reduce flickering\n
In this script, the gain control (potentiometer) reading is used to adjust the amplitude of the sound signal before plotting it. The gain_value
is read from ADC1 and is used to scale the audio signal from the microphone connected to ADC0. The line adjusted_val = min(((val * gain_value) >> 16), 65535)
scales the microphone input based on the potentiometer's position, and the bit shift >> 16
is used to keep the value within the 16-bit range of the ADC's output.
This method should help make the displayed signal more responsive to the potentiometer's adjustments, allowing you to visually enhance or reduce the waveform amplitude on the OLED display.
"},{"location":"advanced-labs/30-spectrum-analyzer/03-display-scroll/","title":"Display Scroll","text":""},{"location":"advanced-labs/30-spectrum-analyzer/03-display-scroll/#prompt","title":"Prompt","text":"The display is a bit slow. Can you rewrite the plot\\_signal\\_with\\_gain function so that the scroll() function is used to move the display to the left by one pixel?\n
"},{"location":"advanced-labs/30-spectrum-analyzer/03-display-scroll/#chatgpt-gpt-4-response","title":"ChatGPT GPT-4 Response","text":"To make the display update faster and to create a scrolling effect that moves the waveform to the left by one pixel, you can modify the plot_signal_with_gain
function to use the scroll()
method of the SSD1306 library. This will shift the entire display content to the left by one pixel, and then you can plot the new data point at the right edge of the display. Here's how you can implement this:
def plot_signal_with_gain():\n # Scroll the display content to the left by one pixel\n display.scroll(-1, 0)\n\n # Read gain control (potentiometer) value\n gain_value = gain.read_u16() + 1 # Adding 1 to avoid division by zero\n\n # Read from ADC (sound input)\n val = sound.read_u16()\n\n # Adjust the sound value based on the gain\n adjusted_val = min(((val * gain_value) >> 16), 65535) # Ensure the adjusted value does not exceed ADC's max value\n\n # Scale the adjusted value to fit the OLED height\n y = int((adjusted_val / 65535) * HEIGHT)\n # Invert y to plot correctly on the OLED\n y = HEIGHT - y\n\n # Since we've scrolled, we only need to update the last column (x = WIDTH - 1)\n # Clear the last column first (optional, depending on the desired visual effect)\n for clear_y in range(HEIGHT):\n display.pixel(WIDTH - 1, clear_y, 0)\n\n # Draw the new data point at the right edge of the display\n display.pixel(WIDTH - 1, y, 1)\n\n # Update the display with the new data\n display.show()\n
In this updated function, the display is scrolled left by one pixel at the beginning of each update cycle using display.scroll(-1, 0)
. This creates room to draw the new data point at the far right edge (WIDTH - 1
) of the display. The loop that clears the last column before drawing the new point is optional and can be adjusted or removed depending on the visual effect you're aiming for.
Remember, while scroll()
improves the visual fluidity by creating a continuous scrolling effect, the update rate can still be limited by the display's physical refresh rate and the speed at which your MicroPython script can run. Experiment with the utime.sleep(0.1)
delay at the end of the main loop to find a balance between smoothness and speed that works for your specific application.
This lesson assumes that you have at least skimmed the Getting Started sections and have selected one of the MicroPython IDEs to write your code.
The lesson will introduce you to the basic concepts of using MicroPython using the $4 Raspberry Pi Pico or a similar microcontroller.
The first two examples just use the Raspberry Pi Pico and don't require a breadboard or wiring. All the subsequent examples will require you to place components on a solderless breadboard.
"},{"location":"basics/03-blink/","title":"Blink in MicroPython","text":""},{"location":"basics/03-blink/#overview","title":"Overview","text":"In this lab, we will use MicroPython to make the green on-board LED on the Raspberry Pi Pico blink on and off every half second. The only things you need to run this program are
The pico has a single built in green LED wired to logical pin 25. We call this GPIO 25. GPIO means General Purpose Input and Output pin. Here is a sample program that you can use. Don't worry about understanding each line yet. We will cover the various parts of the file in later sections.
# Setup - run once\nfrom machine import Pin # Get the Pin function from the machine module.\nfrom time import sleep # Get the sleep library from the time module.\n\n# This is the built-in green LED on the Pico.\nBUILT_IN_LED_PIN = 25\n# change this to the following named pin on the \"W\"\n# BUILT_IN_LED_PIN = Pin(\"LED\", Pin.OUT)\n\n# The line below indicates we are configuring this as an output (not input)\nled = machine.Pin(BUILT_IN_LED_PIN, machine.Pin.OUT)\n\n# Main loop: Repeat the forever...\nwhile True:\n led.high() # turn on the LED\n sleep(0.5) # leave it on for 1/2 second\n led.low() # Turn off the LED\n sleep(0.5) # leave it off for 1/2 second\n
This program has two main parts. The first part is often called the preamble or the setup code. This code gets executed only once when the program is run. In this example, the setup loads the right libraries and initializes global variables.
The second part is the main event loop. This program continues to run until the device is powered down or the program is reset.
The from machine import Pin
statement is required to define the characteristics of our physical machine. In this case, we are loading the Pin python library.
The from time import sleep
library is required for the python sleep function. Note that some programs use utime
for MicroPython time. These both work the same. They are really just synonyms or alias of each other. Although the use of utime
is technically a little bit more precise - the reader knows that the code is using the actual MicroPython time library, the use of the time
alias makes our code on character smaller and can make our code more portable to other systems.
Note that the text after the hash or pound characters are comments. Comments are ignored by the Python interpreter but it is a good practice to put in comments in your code to help others understand your program.
"},{"location":"basics/03-blink/#changing-the-blink-speed","title":"Changing the Blink Speed","text":"Next, lets create a Python global variable for the delay that the LED is on and off.
from machine import Pin\nfrom time import sleep\nBUILT_IN_LED_PIN = 25\n# this is the builtin LED on the Pico\nled = Pin(BUILT_IN_LED_PIN, machine.Pin.OUT)\n\n# global variables\ndelay = .25\n\n# repeat forever\nwhile True:\n led.high() # turn on the LED\n sleep(delay) # leave it on for 1/2 second\n led.low() # Turn off the LED\n sleep(delay) # leave it off for 1/2 second\n
This program will blink the built-in LED on and off every 1/4 of a second. By changing the delay variable you can make the LED blink faster and slower.
Challenge
What is the fastest you can make the LED blink and still see it changing? What does this tell you about the human eye?
"},{"location":"basics/03-blink/#using-toggle","title":"Using Toggle","text":"Instead of using the on()
and off()
methods, we can also just use the toggle()
function.
from machine import Pin\nfrom time import sleep\nled_onboard = machine.Pin(25, machine.Pin.OUT)\nwhile True:\n led_onboard.toggle()\n sleep(.25)\n
The toggle()
function looks at the state of the output pin. If it is high, it sets the value low. If it is low, it sets it high. Whatever the value currently is, it will set it to the opposite value.
If you save the file as main.py, this program will run when the microcontroller starts up without the BOOTSEL being pressed.
"},{"location":"basics/03-blink/#blinking-an-external-led","title":"Blinking an External LED","text":"Although the builtin LED is convenient, you can use the almost the code to blink any external LED that is connected through a 330 ohm resister in series to the LED.
We will assume that an LED is connected to pin GIO16 and is connected via a 330 ohm resistor to ground.
Here is the code that will blink an LED that is connected to PIN GIO16, which is in the upper right corner of the Pico.
from machine import Pin\nfrom time import sleep\n# this is the lower right corner pin on the Pico with USB on the bottom\nEXTERNAL_LED_PIN = 16\nled = machine.Pin(EXTERNAL_LED_PIN, machine.Pin.OUT)\n\n# repeat forever\nwhile True:\n led.high() # turn on the LED\n time.sleep(0.5) # leave it on for 1/2 second\n led.low() # Turn off the LED\n sleep(0.5) # leave it off for 1/2 second\n
Here is that same program using the toggle()
function:
from machine import Pin\nfrom time import sleep\nEXTERNAL_LED_PIN = 16\nexternal_led = machine.Pin(EXTERNAL_LED_PIN, machine.Pin.OUT)\n\nwhile True:\n external_led.toggle()\n sleep(5)\n
"},{"location":"basics/03-blink/#solution-to-led-row-lab","title":"Solution to LED Row Lab","text":"from machine import Pin\nfrom time import sleep\nEXTERNAL_LED_PIN1 = 16\nEXTERNAL_LED_PIN2 = 17\nEXTERNAL_LED_PIN3 = 18\nEXTERNAL_LED_PIN4 = 19\nled1 = machine.Pin(EXTERNAL_LED_PIN1, machine.Pin.OUT)\nled2 = machine.Pin(EXTERNAL_LED_PIN2, machine.Pin.OUT)\nled3 = machine.Pin(EXTERNAL_LED_PIN3, machine.Pin.OUT)\nled4 = machine.Pin(EXTERNAL_LED_PIN4, machine.Pin.OUT)\n\nwhile True:\n # blink the first LED\n led1.high()\n sleep(.5)\n led1.low()\n\n # blink the 2nd LED\n led2.high()\n sleep(.5)\n led2.low()\n\n\n # blink the 3rd LED\n led3.high()\n sleep(.5)\n led3.low()\n\n\n # blink the 4th LED\n led4.high()\n sleep(.5)\n led4.low()\n
"},{"location":"basics/03-blink/#extra-credit-lab","title":"Extra credit lab","text":"Can you rewrite the program above using an array of pin values like this:
LED_PINS = [16,17,28,19]\n
"},{"location":"basics/03-blink/#more-to-explore","title":"More to Explore","text":"In this lesson we will hook a single momentary push button up to our Raspberry Pi Nano. We will use it to toggle the built-in LED. We will start out with simply polling the button 10 times a second to check it's state. Then we will show how to use an interrupt handler function to monitor events from the button.
In the example above, we are connecting the button on the left to the lower-left corner pin of the Raspberry Pi Pico. This is GPIO Pin 15 and is in row number 20 of our breadboard.
"},{"location":"basics/03-button/#momentary-switch-buttons","title":"Momentary Switch Buttons","text":"We use \"B3F\" tactile switch buttons that can be mounted directly on our breadboards. When the button is pressed, it connects a wire that joins two pins on one side to the two pins on the other side. The buttons can be mounted directly over the trough in the center of the breadboard. They typically cost under $2 for 10 buttons or about 20 cents per button.
Here are the internal connections within the switch.
This is the connection diagram that shows how the button is connected to the GPIO connector in the lower-left corner of the Raspberry Pi Pico. This corresponds to GP15 or Pin #15 in our code.
"},{"location":"basics/03-button/#sample-button-polling-code","title":"Sample Button Polling Code","text":"Here is our fist example that uses a simple \"watching\" loop to check if the button value has change 10 times per second. In this case, the built-in LED is connected to pin 25.
from machine import Pin\nimport time\n\n# GPIO is the internal built-in LED\nled = Pin(25, Pin.OUT)\n# input on the lower left of the Pico using a built-in pull-down resistor to keep the value from floating\nbutton = Pin(15, Pin.IN, Pin.PULL_DOWN) \n\nwhile True:\n if button.value(): # if the value changes\n led.toggle()\n time.sleep(0.1) # wait 1/10th of a second\n
"},{"location":"basics/03-button/#interrupt-handler-version","title":"Interrupt Handler Version","text":"Although the polling version is simple, it does take a lot of the CPU resources. The button.value() is checked 10 times a second, even though the button might only be pressed once a day!
A more efficient version uses a strategy called an interrupt handler. This is a function that is \"registered\" by micropython to handel external events such as a button press.
# Use an interrupt function count the number of times a button has been pressed\nfrom machine import Pin\nimport micropython\nimport time\n\n# global value\nbutton_pressed_count = 0\n\n# Interrupt Service Routine for Button Pressed Events - with no debounce\ndef button1_pressed(change):\n global button_pressed_count\n button_pressed_count += 1\n\nbutton1 = Pin(14, Pin.IN, Pin.PULL_DOWN)\nbutton1.irq(handler=button1_pressed, trigger=Pin.IRQ_FALLING)\n\nbutton_pressed_count_old = 0\nwhile True:\n if button_pressed_count_old != button_pressed_count:\n print('Button 1 value:', button_pressed_count)\n button_pressed_count_old = button_pressed_count\n
"},{"location":"basics/03-button/#interrupt-handler-with-a-debounce-feature","title":"Interrupt Handler with a Debounce Feature","text":"One of the problems with most switches is that they don't turn on and off perfectly each time. As the connection is getting close to closing some electrons jump the gap and the switch appears to turn on for a few microseconds. So to a computer, this looks like someone quickly pressing a button rapidly until it is firmly closed or completely open. This intermediate stage between completely open and closed is called the \"bounce\" stage of a switch opening and closing.
To remove this problem and get a clean signal, we can use either a hardware solution (wiring a capacitor to remove the high frequency noise) or we can be clever and solve the problem with a few extra lines of code.
The secret is to setup a timer when the switch is first closed or opened. We then ignore all the crazy stuff that happens for about 1/5th of a second (200 milliseconds). By then we usually have a solid indication that the button is changing state and we can return the new value.
Here is a example of this \"Debounce\" code in MicroPython:
import utime\nfrom machine import Pin\n\n# Sample Raspberry Pi Pico MicroPython button press example with a debounce delay value of 200ms in the interrupt handler\n\nbutton_presses = 0 # the count of times the button has been pressed\nlast_time = 0 # the last time we pressed the button\n\nbuiltin_led = machine.Pin(25, Pin.OUT)\n# The lower left corner of the Pico has a wire that goes through the buttons upper left and the lower right goes to the 3.3 rail\nbutton_pin = machine.Pin(14, machine.Pin.IN, machine.Pin.PULL_DOWN)\n\n# This function gets called every time the button is pressed. The parameter \"pin\" is not used.\ndef button_pressed_handler(pin):\n global button_presses, last_time\n new_time = utime.ticks_ms()\n # if it has been more that 1/5 of a second since the last event, we have a new event\n if (new_time - last_time) > 200: \n button_presses +=1\n last_time = new_time\n\n# now we register the handler function when the button is pressed\nbutton_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\n\n# This is for only printing when a new button press count value happens\nold_presses = 0\nwhile True:\n # only print on change in the button_presses value\n if button_presses != old_presses:\n print(button_presses)\n builtin_led.toggle()\n old_presses = button_presses\n
"},{"location":"basics/03-button/#two-button-example","title":"Two Button Example","text":"This example uses two buttons. One adds one to a counter, and the other decrements the counter. In the IRQ we look for the string '''14''' in the incoming pin event string. If the string is present, then we increment the button_presses
variable. If it is not, then we decrement the counter.
This example is useful whenever you have two buttons that control the mode
of a microcontroller.
Note that you can tune the delay period. If you have clean buttons and you want to allow for fast double-click events you can lower the time. 200 milliseconds is good for low-cost noisy buttons that may have many spikes in the open and closing transitions.
import utime\nfrom machine import Pin\n\n# Sample two button Raspberry Pi Pico MicroPython example\n# with a debounce delay value of 200ms in the interrupt handler\n# https://www.coderdojotc.org/micropython/basics/03-button/\n\n# these are the pins in the lower-left corner (USB on top)\nBUTTON_PIN_A = 14\nBUTTON_PIN_B = 15\n\nbutton_presses = 0 # the count of times the button has been pressed. A is +1, B is -1\nlast_time = 0 # the last time we pressed the button\n\n# we toggle the builtin LED to get visual feedback\nbuiltin_led = machine.Pin(25, Pin.OUT)\n\n# The lower left corner of the Pico has a wire that goes through the buttons upper left and the lower right goes to the 3.3 rail\nbutton_a = machine.Pin(BUTTON_PIN_A, machine.Pin.IN, machine.Pin.PULL_DOWN)\nbutton_b = machine.Pin(BUTTON_PIN_B, machine.Pin.IN, machine.Pin.PULL_DOWN)\n\n# this is the interrupt callback handler\n# get in and out quickly\ndef button_callback(pin):\n global button_presses, last_time\n new_time = utime.ticks_ms()\n # if it has been more that 1/5 of a second since the last event, we have a new event\n if (new_time - last_time) > 200:\n # print(pin)\n if '14' in str(pin):\n button_presses +=1\n else:\n button_presses -= 1\n last_time = new_time\n\n# now we register the handler functions when either of the buttons is pressed\nbutton_a.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_callback)\nbutton_b.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_callback)\n\n# This is for only printing when a new button press count value happens\nold_presses = 0\n\nprint(button_presses)\nwhile True:\n # only print on change in the button_presses value\n if button_presses != old_presses:\n print(button_presses)\n builtin_led.toggle()\n old_presses = button_presses\n
"},{"location":"basics/03-button/#references","title":"References","text":"In this lab we will use a 10K ohm potentiometer to demonstrate how a turn of a knob can result in getting a continuous variable from a user into our code. We will show how we can use a potentiometer to change the blinking speed of on LED.
"},{"location":"basics/03-potentiometer/#about-analog-to-digital-converters","title":"About Analog to Digital Converters","text":"Digital microcontrollers are inherently noisy. They have clocks that pull power from the power supply and cause voltage fluctuations when we compare a signal to these power lines. This makes it difficult to get
ADC_VREF is the ADC power supply (and reference) voltage, and is generated on Pico by filtering the 3.3V supply. This pin can be used with an external reference if better ADC performance is required. AGND is the ground reference for GPIO26-29, there is a separate analog ground plane running under these signals and terminating at this pin.
"},{"location":"basics/03-potentiometer/#circuit-diagram","title":"Circuit Diagram","text":"Note: to get an accurate noise-free reading from the potentiometer you must use the ADC_VREF and the AGND pins. These are special pins designed to reduce the noise on the power areas of the pico.
"},{"location":"basics/03-potentiometer/#sample-code-to-print-potentiometer-values","title":"Sample Code To Print Potentiometer Values","text":"from machine import ADC\nfrom utime import sleep\npot = ADC(26)\nwhile True:\n print(pot.read_u16())\n sleep(.2)\n
graph LR\np[Pico]-->|ADC_VREF 36 row=6| pos(Positive)\np[Pico]-->|AGND 33 row=8| neg(Negative)\np[Pico]-->|GP26 pin=26 ADC0 31 row=10| tap(Center Tap)\n pos(Positive) --- pot(Potentiometer)\n neg(Negative) --- pot(Potentiometer)\n tap(Center Tap) --- pot(Potentiometer)\n
Connect the positive to pin 35 ADC_REF (row 6 on the breadboard) and the negative to pin 33 AGND (row 8 on the breadboard). The Pico has special noise reduction circuits to avoid power supply jitter on these reference pins.
"},{"location":"basics/03-potentiometer/#changing-blink-speed-with-a-potentiometer","title":"Changing Blink Speed with a Potentiometer","text":"from machine import ADC, Pin\nfrom utime import sleep\n\n# this is the built-in LED on the Pico\nled = Pin(25, Pin.OUT)\n\n# ADC0 is GPIO 26. Connect to row 10 the right side\npot = ADC(26)\n\nMAX_DELAY = .5 # seconds\n\n# global variables\ndelay = 0\n\n# repeat forever\nwhile True:\n pot_value = pot.read_u16() # read the value from the pot\n delay = pot_value/65025 * MAX_DELAY\n print(\"delay:\", delay)\n if delay > 0:\n print(\"frequency (toggles per second):\", 1/delay)\n led.high() # turn on the LED\n sleep(delay) # leave it on for 1/2 second\n led.low() # Turn off the LED\n sleep(delay) # leave it off for 1/2 second\n
The following video shows this script in action.
"},{"location":"basics/03-potentiometer/#changing-the-brightness-of-the-building-led","title":"Changing the Brightness of the Building LED","text":"We can change the brightness of the builtin LED by using the POT value to change the PWM duty cycle.
Here is a sample progam that does this:
from machine import ADC, Pin, PWM\nfrom utime import sleep\n\n# Pins Used\nBUILT_IN_LED_PIN = 25\nPOT_PIN = 26\n\npot = ADC(POT_PIN)\n\nbuiltin_pwm = PWM(Pin(BUILT_IN_LED_PIN))\nbuiltin_pwm.freq(1000) # 1K Hz\n\nPOLL_DELAY = .1 # poll the pot after this delay in seconds\n\n# repeat forever\nwhile True:\n pot_value = pot.read_u16() # read the value from the pot\n print(\"pot value:\", pot_value)\n builtin_pwm.duty_u16(pot_value)\n sleep(POLL_DELAY)\n
"},{"location":"basics/04-fade-in-and-out/","title":"Fade an LED in and Out","text":"In the prior Blink lab, we turned an LED on an off at different speeds. But what if we want to slowly turn on our LED on and off? In this lab we will show you how to dim your LED to any brightness level you want.
"},{"location":"basics/04-fade-in-and-out/#welcome-to-pulse-width-modulation","title":"Welcome to Pulse Width Modulation","text":"Although digital computers are good at quickly turning signals on and off, they don't really allow us to easily set an output to a given voltage level without complex circuits. But there is an easier way to adjust the brightness of an LED! We can quickly turn the signal to the LED on and off. We can do this so quickly that you can't even see it flicker. Controlling the amount of time a signal is on is all about controlling the width of the ON pulse. That is why this is called Pulse Width Modulation or PWM for short.
With a PWM design there are two things we need to tell the microcontroller:
The rate of change of the pulse is call the frequency. You can set the frequency to be 1,000 changes per second, which is much faster than the human eye can detect. This is done using the following line:
pwm.freq(1000)\n
Note that we can slow the frequency way down and the dimming effect will still work. As an experiment you can change the PWM frequency to around 20 and you will see a distinct flicker as the LED turns on.
Here is the sample program that will slowly dim the builtin LED that is on pin 25:
from machine import Pin, PWM\nfrom time import sleep\n\npwm = PWM(Pin(25))\n\npwm.freq(1000)\n\nwhile True:\n for duty in range(65025):\n pwm.duty_u16(duty)\n sleep(0.0001)\n for duty in range(65025, 0, -1):\n pwm.duty_u16(duty)\n sleep(0.0001)\n
Note that the duty cycle starts at 0 (always off) and moves slowly up to 65,025 (always on). It then does the reverse and slowly dims the LED and then repeats. There is only a 1/10,000 of a delay between these changes so the LED will completely turn on in about six seconds before it starts to dim again.
"},{"location":"basics/04-fade-in-and-out/#pwm-functions","title":"PWM Functions","text":"Here is a list of the PWM functions.
from machine import Pin, PWM\n\npwm0 = PWM(Pin(0)) # create PWM object from a pin\npwm0.freq() # get current frequency\npwm0.freq(1000) # set frequency (1000 cycles per minute)\npwm0.duty_u16() # get current duty cycle, range 0-65535\npwm0.duty_u16(200) # set duty cycle, range 0-65535\npwm0.deinit() # turn off PWM on the pin\n
Make sure you deinit()
to de-initialize the PWM controller after you are done. You may have to trap the stop to do this. For example if a PWM is driving motors, your Stop must send deinit() to each motor controller. See the Interrupt Handlers for details.
The Pico has 26 general purpose input and output pins. However, each pin's power is designed to digitally communicate with other devices and has a limited current capacity of around 17 milliamps according to the Raspberry Pi Pico Datasheet Table 5. 17 milliamps is fine for lighting up an LED. However, motors require much more power. 17 milliamps is not enough current to drive even small motors. Even our small DC hobby motors we use with our robots require around 200 milliamps.
But don't worry! We have two ways around this problem.
Set the frequency to 50Hz (one cycle per 20ms) and the duty value to between 51 (51/1023 * 20ms = 1ms) and 102 (102/1023 * 20ms = 2ms)
"},{"location":"basics/04-motor/#sample-coder","title":"Sample Coder","text":"import machine\n\n# set the 7th from the bottom on right as our motor pin\nmotor_pin = machine.Pin(21, machine.Pin.OUT)\n# allocate a PWM object for controlling the motor speed\nmotor_pwm = machine.PWM(motor_pin)\nmotor_pwm.freq(50) # 50 hertz\nmotor_pwm.duty(51)\n
"},{"location":"basics/04-motor/#references","title":"References","text":"ADC_VREF is the ADC power supply (and reference) voltage, and is generated on Pico by filtering the 3.3V supply. This pin can be used with an external reference if better ADC performance is required. AGND is the ground reference for GPIO26-29, there is a separate analog ground plane running under these signals and terminating at this pin.
graph LR\np[Pico]-->|ADC_VREF 36 row=6| pos(Positive)\np[Pico]-->|AGND 33 row=8| neg(Negative)\np[Pico]-->|GP26 pin=26 ADC0 31 row=10| tap(Center Tap)\n pos(Positive) --- pot(Potentiometer)\n neg(Negative) --- pot(Potentiometer)\n tap(Center Tap) --- pot(Potentiometer)\n
Connect the positive to pin 35 ADC_REF (row 6 on the breadboard) and the negative to pin 33 AGND (row 8 on the breadboard). The Pico has special noise reduction circuits to avoid power supply jitter on these reference pins.
"},{"location":"basics/04-read-pot/#sampling-data","title":"Sampling data","text":"Sometimes the data coming from your Potentiometer is noisy. You can sample the value multiple times and then average the values.
Here is a sample program. Just pass in the pin and a count and it will return the average values. This version waits 5 milliseconds between samples.
def sample_pot(pin, count):\n total = 0\n for i in range(count):\n total += int(pin.read_u16())\n utime.sleep_ms(5)\n return int(total / count)\n
pot_pin_1 = machine.ADC(26)\n# return a value after sampling 10 times\nsample_pot(pot_pin_1, 10)\n
"},{"location":"basics/04-servo/","title":"Micropython Servo Lab","text":"TBD
import machine\nimport pyb\n\n# The pyboard has four simple servo connections\nservo = pyb.Servo(1)\n\nservo.angle(90, 5000)\n
"},{"location":"basics/05-neopixel/","title":"NeoPixels","text":"NeoPixels are Red-Green-Blue LEDs that are designed to makes them easy to control with three wires: GND, +5V and a single serial data line. They are very popular with our students because they are powerful, easy to program and full of bling.
Note
As of March of 2022 there is now built-in support for NeoPixels in the MicroPython 1.18 runtime for the Raspberry Pi RP2040 microcontroller. Although you can still use custom libraries, this tutorial assumes you are using version 1.18 or later.
Controlling NeoPixels is challenging since the timing of data being sent must be very precise. Python alone is not fast enough to send bits out of a serial port. So a small function that uses assembly code is used. This code can be called directly from a neopixel driver file so that the user's don't need to see this code.
MicroPython Example Code on ESP8266
"},{"location":"basics/05-neopixel/#different-types-of-neopixels","title":"Different Types of NeoPixels","text":"There are many different types of NeoPixels. They come in many forms such as strips, rings and matrices.
The most common type of NeoPixels are strips. The strips come in a variety of densities and waterproofing. The most common and easiest to use are the 60 pixels-per-meter type.
"},{"location":"basics/05-neopixel/#circuit-connections","title":"Circuit connections","text":"LED Strip Pico Name Pico Pin Description GND GND 3 Ground 5v VBUS 40 Voltage from the USB bus. Top right with USB on top Data GP22 22 Row 12 on the right sideNote that you can also power most of the LED strips using the 3.3 volts available on Grove connectors. The only difference is the brightness might not be quite as high, but for most applications this will not be a problem.
"},{"location":"basics/05-neopixel/#setup-parameters","title":"Setup Parameters","text":"Our Python code will have four parts:
Here are the import statements we use:
from machine import Pin\nfrom utime import sleep\nfrom neopixel import NeoPixel\n
"},{"location":"basics/05-neopixel/#static-initialization-parameters","title":"Static Initialization Parameters","text":"There are only two values. The number of pixels in the LED strip or LED ring and the pin number the data pin is connected to.
NUMBER_PIXELS = 8\nLED_PIN = 22\n
"},{"location":"basics/05-neopixel/#initialize-the-strip-object","title":"Initialize the Strip Object","text":"To setup the NeoPixel object we just pass it the two parameters like this:
strip = NeoPixel(Pin(LED_PIN), NUMBER_PIXELS)\n
Here is the full initialization code:
from machine import Pin\nfrom utime import sleep\nfrom neopixel import NeoPixel\n\nNUMBER_PIXELS = 8\nLED_PIN = 22\nstrip = NeoPixel(Pin(LED_PIN), NUMBER_PIXELS)\n
"},{"location":"basics/05-neopixel/#sample-programs","title":"Sample Programs","text":"Now we are ready to write our first small test program!
"},{"location":"basics/05-neopixel/#move-red-pixel-across-strip","title":"Move Red Pixel Across Strip","text":"from machine import Pin\nfrom time import sleep\nfrom neopixel import NeoPixel\n\nNUMBER_PIXELS = 60\nLED_PIN = 0\n\nstrip = NeoPixel(Pin(LED_PIN), NUMBER_PIXELS)\n\nwhile True:\n for i in range(0, NUMBER_PIXELS):\n strip[i] = (255,0,0) # red=255, green and blue are 0\n strip.write() # send the data from RAM down the wire\n sleep(.1) # keep on 1/10 of a second\n strip[i] = (0,0,0) # change the RAM back but don't resend the data\n
"},{"location":"basics/05-neopixel/#fade-in-and-out","title":"Fade in and Out","text":"Make the first pixel fade the red color in and out. We do this by slowly turning up the color level of the red on strip[0].
Off: strip[0] = (0,0,0)\nRed Very Dim: strip[0] = (1,0,0)\nDim Red: strip[0] = (10,0,0)\nRed Half Brightness: strip[0] = (128,0,0)\nRed Full Brightness: strip[0] = (255,0,0)\n
We start a 0 and go up to 255. Then we go back from 255 back down to zero. We delay about 5 milliseconds between each of the 255 brightness levels.
from machine import Pin\nfrom time import sleep\nfrom neopixel import NeoPixel\n\n\nNUMBER_PIXELS = 60\nLED_PIN = 0\n\nstrip = NeoPixel(Pin(LED_PIN), NUMBER_PIXELS)\n\ndelay = .005\n\nwhile True:\n for i in range(0, 255):\n strip[0] = (i,0,0) # red=255, green and blue are 0\n strip.write() # send the data from RAM down the wire\n sleep(delay)\n for i in range(255, 0, -1):\n strip[0] = (i,0,0)\n strip.write()\n sleep(delay)\n
"},{"location":"basics/05-neopixel/#heartbeat-lab","title":"Heartbeat Lab","text":"What if you were building a robot and you wanted to flash the LED to look like a human heartbeat? Instead of slowing fading in and out, you would want the brightness to follow the electrical signals coming from the heart. This is called an electro cardiogram (EKG) and it look like this:
Notice that the signal is low for about one second and then it spikes up to maximum brightness and then comes back down. When we are moving the brightness up and down, we don't have to pause between each of the 256 brightness values. The eye can't usually see the intermediate brightness values if the brightness is changing quickly. To make our code efficient we can skip over 9 out of 10 of the brightness gradations between 0 and 255. We call this the skip_interval
in our code below.
The following code emulates this heart beat pattern:
from machine import Pin\nfrom time import sleep\nfrom neopixel import NeoPixel\n\n# Most people have a heart rate of around 60-70 beats per minute\n# If we add a once second delay between \"beats\" you can make and LED\n# look like a beating heart.\n\nNUMBER_PIXELS = 1\nLED_PIN = 0\n\nstrip = NeoPixel(Pin(LED_PIN), NUMBER_PIXELS)\n\nramp_delay = .001\nbeat_delay = 1\nskip_interval = 10\n\nwhile True:\n # ramp brightness up using the ramp_delay\n for i in range(0, 255, skip_interval):\n strip[0] = (i,0,0)\n strip.write()\n sleep(ramp_delay)\n # ramp brightness down using the same delay\n for i in range(255, 0, -skip_interval):\n strip[0] = (i,0,0)\n strip.write()\n sleep(ramp_delay)\n strip[0] = (0,0,0)\n strip.write()\n sleep(beat_delay)\n
"},{"location":"basics/05-neopixel/#move-red-green-and-blue","title":"Move Red, Green and Blue","text":"The following program will just take the block of code in the for loop above and duplicate it three times, one for red, one for blue and one for green.
from machine import Pin\nfrom neopixel import NeoPixel\nfrom time import sleep\n\nNUMBER_PIXELS = 8\nLED_PIN = 0\n\nstrip = NeoPixel(Pin(LED_PIN), NUMBER_PIXELS)\n\n# we use the same brightness for each color\nbrightness = 25\ndelay = .1\n# here we define variables for each color\nred = (brightness, 0, 0)\ngreen = (0, brightness, 0)\nblue = (0, 0, brightness)\nwhile True:\n # draw red up the strip\n for i in range(0, NUMBER_PIXELS):\n strip[i] = red\n strip.write()\n sleep(delay)\n strip[i] = (0,0,0)\n # draw blue up the strip\n for i in range(0, NUMBER_PIXELS):\n strip[i] = green\n strip.write()\n sleep(delay)\n strip[i] = (0,0,0)\n # draw green up the strip\n for i in range(0, NUMBER_PIXELS):\n strip[i] = blue\n strip.write()\n sleep(delay)\n strip[i] = (0,0,0)\n
"},{"location":"basics/05-neopixel/#rainbow-cycle","title":"Rainbow Cycle","text":"The program cycles each pixel through all the colors in a rainbow. It uses two functions:
from machine import Pin\nfrom neopixel import NeoPixel\nfrom utime import sleep\n\nNEOPIXEL_PIN = 22\nNUMBER_PIXELS = 8\nstrip = NeoPixel(Pin(NEOPIXEL_PIN), NUMBER_PIXELS)\n\ndef wheel(pos):\n # Input a value 0 to 255 to get a color value.\n # The colors are a transition r - g - b - back to r.\n if pos < 0 or pos > 255:\n return (0, 0, 0)\n if pos < 85:\n return (255 - pos * 3, pos * 3, 0)\n if pos < 170:\n pos -= 85\n return (0, 255 - pos * 3, pos * 3)\n pos -= 170\n return (pos * 3, 0, 255 - pos * 3)\n\ndef rainbow_cycle(wait):\n global NUMBER_PIXELS, strip\n for j in range(255):\n for i in range(NUMBER_PIXELS):\n rc_index = (i * 256 // NUMBER_PIXELS) + j\n # print(rc_index)\n strip[i] = wheel(rc_index & 255)\n strip.write()\n sleep(wait)\n\ncounter = 0\noffset = 0\nwhile True:\n print('Running cycle', counter)\n rainbow_cycle(.05)\n counter += 1\n
"},{"location":"basics/05-neopixel/#references","title":"References","text":"One June 30th, 2022 the Raspberry Pi Foundation announced the availability of the Raspberry Pi Pico W. This $6 microprocessor now supports WiFi and with a software upgrade it may soon support Bluetooth.
The Pico W supports 2.4 Ghz 802.11n wireless networking. For MicroPython, we can use a MicroPython library built around the lwip TCP/IP stack. This stack is accessible using the MicroPython network functions.
The WiFi chip used is the Infineon CYW43439 chip. This chip also uses an ARM architecture and has extensive support for Bluetooth wireless communication.
You can read more about the capabilities of the WiFi/Bluetooth chip by reading the Infineon CYW43439 Datasheet. I found it interesting that the CYW43439 chip has 512KB of SRAM - almost double what the RP2040 chip contains!
"},{"location":"basics/06-wireless/#compatibility-with-prior-code","title":"Compatibility with Prior Code","text":"The Pico W code is very similar to prior versions of the Pico with a few small exceptions. One of these is the fact that we must now use a symbolic label called an alias such as Pin(\"LED\")
instead of Pin(25)
to access the LED pin, not a hardwired PIN number. This allows us to keep our code more portable as the underlying hardware changes.
from machine import Pin, Timer\n\n# was Pin(25)\nled = Pin(\"LED\", Pin.OUT)\ntim = Timer()\ndef tick(timer):\n global led\n led.toggle()\n\ntim.init(freq=2.5, mode=Timer.PERIODIC, callback=tick)\n
See the new Sample Blink code on the Raspberry Pi Examples site.
"},{"location":"basics/06-wireless/#getting-the-new-pico-w-image","title":"Getting the New Pico W Image","text":"I had to download a brand new image for the Pico W runtime from the Raspberry Pi Foundation Software Site
After I downloaded the new image and ran a Stop/Reset on Thonny I got the following prompt:
MicroPython v1.19.1-88-g74e33e714 on 2022-06-30; Raspberry Pi Pico W with RP2040\nType \"help()\" for more information.\n>>> \n
Note that the \"Pico W\" is listed in the prompt. If you do not see the \"W\" then the network code will not work.
"},{"location":"basics/06-wireless/#beginner-wifi-programs","title":"Beginner WiFi Programs","text":"We will store the name of our local WiFi network we wish to connect to and the password for that name in a file called secrets.py. This is called you WiFi \"access point\" and the variable name to store the name is called the `SSID
. We will need to make sure we never save this file into a public GitHub repo by adding this file to our .gitignore file.
By convention, we put both our SSID and password in a python file called \"secrets.py\". This file should never be checked into a public source code repository. We can add secrets.py
to the .gitignore file to make sure the secrets.py is never checked into GitHub and exposing your passwords to everyone.
SSID = \"MY_WIFI_NETWORK_NAME\"\nPASSWORD = \"MY_WIFI_PASSWORD\"\n
By importing the secrets.py file you can then reference your network name like this:
print('Connecting to WiFi Network Name:', secrets.SSID)\n
"},{"location":"basics/06-wireless/#testing-your-wifi-access-point-connection","title":"Testing Your WiFi Access Point Connection","text":"Here is a very simple script to test see if your network name and password are correct. This script may work, but as we will see, it is both slow and potentially unreliable.
import network\nimport secrets\nfrom utime import sleep\n\nprint('Connecting to WiFi Network Name:', secrets.SSID)\nwlan = network.WLAN(network.STA_IF)\nwlan.active(True) # power up the WiFi chip\nprint('Waiting for wifi chip to power up...')\nsleep(3) # wait three seconds for the chip to power up and initialize\nwlan.connect(secrets.SSID, secrets.PASSWORD)\nprint('Waiting for access point to log us in.')\nsleep(2)\nif wlan.isconnected():\n print('Success! We have connected to your access point!')\n print('Try to ping the device at', wlan.ifconfig()[0])\nelse:\n print('Failure! We have not connected to your access point! Check your secrets.py file for errors.')\n
Returns:
Connecting to WiFi Network Name: MY_WIFI_NETWORK_NAME\nWaiting for wifi chip to power up...\nWaiting for access point to log us in...\nSuccess! We have connected to your access point!\nTry to ping the device at 10.0.0.70\n
If the result is a Failure
you should check the name of the network and the password and that you are getting a strong WiFi signal where you are testing.
Note that we are using the sleep()
function to insert delays into our code. However, the results may actually be faster or slower than our sleep times. Our next step is to add logic that will test to see if the networking device is ready and if our local access point allows us to login correctly.
Sometimes we want to keep checking if our access point is connected before we begin using our connection. To do this we can create a while loop and continue in the loop while we are not connected.
import network\nimport secrets\nfrom utime import sleep, ticks_ms, ticks_diff\n\nprint('Connecting to WiFi Network Name:', secrets.SSID)\nwlan = network.WLAN(network.STA_IF)\nwlan.active(True)\n\nstart = ticks_ms() # start a millisecond counter\n\nif not wlan.isconnected():\n wlan.connect(secrets.SSID, secrets.PASSWORD)\n print(\"Waiting for connection...\")\n counter = 0\n while not wlan.isconnected():\n sleep(1)\n print(counter, '.', sep='', end='', )\n counter += 1\n\ndelta = ticks_diff(ticks_ms(), start)\nprint(\"Connect Time:\", delta, 'milliseconds')\nprint('IP Address:', wlan.ifconfig()[0])\n
This code also supports a timer that will display the number of seconds for the access point to become valid in the console. The first time after you power on, this may take several seconds. After you are connected the connection will be cached and the time will be 0 milliseconds.
First run upon power on might take several seconds:
>>> %Run -c $EDITOR_CONTENT\nConnecting to WiFi Network Name: MY_NETWORK_NAME\nWaiting for connection...\n0.1.2.3.Connect Time: 4640\nIP Address: 10.0.0.70\n
The second and consecutive runs will use a cached connection.
>>> %Run -c $EDITOR_CONTENT\nConnecting to WiFi Network Name: MY_NETWORK_NAME\nConnect Time: 0 milliseconds\nIP Address: 10.0.0.70\n>>>\n
"},{"location":"basics/06-wireless/#error-handling","title":"Error Handling","text":"lan = network.WLAN(network.STA_IF)\nwlan.active(True)\nwlan.connect(ssid, password)\n\n# Wait for connect or fail\nmax_wait = 10\nwhile max_wait > 0:\n if wlan.status() < 0 or wlan.status() >= 3:\n break\n max_wait -= 1\n print('waiting for connection...')\n time.sleep(1)\n\n# Handle connection error\nif wlan.status() != 3:\n raise RuntimeError('network connection failed')\nelse:\n print('connected')\n status = wlan.ifconfig()\n print( 'ip = ' + status[0] )\n
The full TCP/IP stack is running on your Pico W. You should be able to ping the pico using the IP address returned by the status[0] of the wlan.ifconfig() function above.
"},{"location":"basics/06-wireless/#testing-http-get","title":"Testing HTTP GET","text":"The following example was taken from Tom's Hardware
import network\nimport secrets\nfrom utime import sleep, ticks_ms, ticks_diff\nimport urequests\n\nwlan = network.WLAN(network.STA_IF)\nwlan.active(True)\nwlan.connect(secrets.SSID, secrets.PASSWORD)\n\nstart = ticks_ms() # start a millisecond counter\n\nastronauts = urequests.get(\"http://api.open-notify.org/astros.json\").json()\n\ndelta = ticks_diff(ticks_ms(), start)\n\nnumber = astronauts['number']\nprint('There are', number, 'astronauts in space.')\nfor i in range(number):\n print(i+1, astronauts['people'][i]['name'])\n\nprint(\"HTTP GET Time in milliseconds:\", delta)\n
Returns:
There are 10 astronauts in space.\n1 Oleg Artemyev\n2 Denis Matveev\n3 Sergey Korsakov\n4 Kjell Lindgren\n5 Bob Hines\n6 Samantha Cristoforetti\n7 Jessica Watkins\n8 Cai Xuzhe\n9 Chen Dong\n10 Liu Yang\nHTTP GET Time in milliseconds: 786\n
"},{"location":"basics/06-wireless/#listing-the-functions-in-your-network-library","title":"Listing the Functions in Your Network Library","text":"The network library provided by the Raspberry Pi Foundation for the Pico W is new an may change as new functions are added. To get the list of functions in your network library you can use the Python help(network) at the prompt or use the dir()
function.
You can also get a list of the network functions by typing help(network)
at the Python REPL prompt.
help(network)\nobject <module 'network'> is of type module\n __name__ -- network\n route -- <function>\n WLAN -- <class 'CYW43'>\n STAT_IDLE -- 0\n STAT_CONNECTING -- 1\n STAT_WRONG_PASSWORD -- -3\n STAT_NO_AP_FOUND -- -2\n STAT_CONNECT_FAIL -- -1\n STAT_GOT_IP -- 3\n STA_IF -- 0\n AP_IF -- 1\n
"},{"location":"basics/06-wireless/#network-dir-function","title":"Network dir() Function","text":"import network\nfunction_list = dir(network)\n\nfor function in function_list:\n print(function)\n
Returns:
__class__\n__name__\nAP_IF\nSTAT_CONNECTING\nSTAT_CONNECT_FAIL\nSTAT_GOT_IP\nSTAT_IDLE\nSTAT_NO_AP_FOUND\nSTAT_WRONG_PASSWORD\nSTA_IF\nWLAN\nroute\n
"},{"location":"basics/06-wireless/#urequest","title":"Urequest","text":"It is easy to communicate with non-SSL protected HTTP protocols sites using the WLAN `urequest
function. It supports the standard GET, POST, PUT and DELETE functions.
help(urequests)\nobject <module 'urequests' from 'urequests.py'> is of type module\n head -- <function head at 0x2000b740>\n post -- <function post at 0x2000ba80>\n delete -- <function delete at 0x2000bbb0>\n get -- <function get at 0x2000b750>\n __file__ -- urequests.py\n Response -- <class 'Response'>\n patch -- <function patch at 0x2000baf0>\n put -- <function put at 0x2000ba90>\n usocket -- <module 'lwip'>\n __name__ -- urequests\n request -- <function request at 0x2000bb80>\n
"},{"location":"basics/06-wireless/#getting-the-macethernet-access","title":"Getting the MAC/Ethernet Access","text":"You can get the device MAC/Ethernet address and test the roundtrip time between the RP2040 and the WiFi chip using the MAC address function.
import network\nfrom utime import sleep, ticks_us, ticks_diff\n\nprint('Getting MAC/Ethernet Address for this device.')\n\nstart = ticks_us() # start a millisecond counter\nwlan = network.WLAN(network.STA_IF)\nwlan.active(True) # this line powers up the chip - it takes about 2.5 seconds\n\n# This returns a byte array of hex numbers\nmac_addess = wlan.config('mac')\nprint('Time in microseconds:', ticks_diff(ticks_us(), start))\n# each MAC address is 6 bytes or 48 bits\nprint(\"Hex byte array:\", mac_addess, 'length:', len(mac_addess))\n\n# This should be in hex per the Notational Conventions\n# https://en.wikipedia.org/wiki/MAC_address#Notational_conventions\n# b'(\\xcd\\xc1\\x015X'\n# 28:cd:c1:1:35:58\n# format in MAC Notational Convention\nfor digit in range(0,5):\n print(str(hex(mac_addess[digit]))[2:4], ':', sep='', end = '')\nprint(str(hex(mac_addess[5]))[2:4] )\n
First Time After Power On Results:
Getting MAC/Ethernet Address for this device.\nTime in microseconds: 2584424\nHex byte array: b'(\\xcd\\xc1\\x015X' length: 6\n28:cd:c1:1:35:58\n
Note that it takes about 2.5 seconds just to power on the chip before we get the MAC address.
Subsequent Times
Getting MAC/Ethernet Address for this device.\nTime in microseconds: 211\nHex byte array: b'(\\xcd\\xc1\\x015X' length: 6\n28:cd:c1:1:35:58\n
Note
We must add the wlan.active(True)
line to this code. If we don't do this, the wifi device will not be powered up and we can't get the MAC address. The function will return all zeros.
The MAC address is six bytes or \"octets\". The first three octets are assigned to the organization that created the device. The second three octets are assigned by the organization that created the device. See the Wikipedia Page on MAC Address for more information. If you run this on your Pico W the first octets should be similar.
Here are the two MAC addresses for two different Pico W devices:
28:cd:c1:1:35:54\n28:cd:c1:1:35:58\n
Because they were purchased together, their MAC address are very similar.
I ran this program on my Pico W and I got times of between 214 and 222 microseconds. This shows you that it takes about 100 microseconds to send a request from the RP2040 to the CYW43439 WiFi chip and about 100 milliseconds to return the results. This time lag represents some of the key performance limitations in using the Pico W for high-performance networking.
"},{"location":"basics/06-wireless/#advanced-wifi-programs","title":"Advanced WiFi Programs","text":"Once we have mastered the basics of connecting to a local access point and returning our IP address, we are no ready to build some sample Internet of Things applications.
"},{"location":"basics/06-wireless/#using-the-pico-w-as-a-web-server","title":"Using the Pico W as a Web Server","text":"This program turns your Pico W into a small web server. The web page has two links on it. One link will turn the on-board LED on and the other link will turn the LED off.
Screen image of Pico W Web Server:
# Code taken from https://www.cnx-software.com/2022/07/03/getting-started-with-wifi-on-raspberry-pi-pico-w-board/\nimport network\nimport socket\nimport time\nimport secrets\n\nfrom machine import Pin\n\n# Select the onboard LED\nled = machine.Pin(\"LED\", machine.Pin.OUT)\n\nwlan = network.WLAN(network.STA_IF)\nwlan.active(True)\nwlan.connect(secrets.SSID, secrets.PASSWORD)\nstateis = \"LED is OFF\"\n\nhtml = \"\"\"<!DOCTYPE html>\n<html>\n <head>\n <title>Web Server On Pico W </title>\n </head>\n <body>\n <h1>Pico Wireless Web Server</h1>\n <p>%s</p>\n <a href=\"/light/on\">Turn On</a>\n <a href=\"/light/off\">Turn Off</a>\n </body>\n</html>\n\"\"\"\n\n# Wait for connect or fail\nmax_wait = 10\nwhile max_wait > 0:\n if wlan.status() < 0 or wlan.status() >= 3:\n break\n max_wait -= 1\n print('waiting for connection...')\n time.sleep(1)\n\n# Handle connection error\nif wlan.status() != 3:\n raise RuntimeError('network connection failed')\nelse:\n print('We are connected to WiFI access point:', secrets.SSID)\n status = wlan.ifconfig()\n print( 'The IP address of the pico W is:', status[0] )\n\n# Open socket\naddr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]\nprint('addr:', addr)\ns = socket.socket()\n#if not addr:\ns.bind(addr)\ns.listen(1)\n\nprint('listening on', addr)\n\n# Listen for connections\nwhile True:\n try:\n cl, addr = s.accept()\n print('client connected from', addr)\n request = cl.recv(1024)\n print(request)\n request = str(request)\n led_on = request.find('/light/on')\n led_off = request.find('/light/off')\n print( 'led on = ' + str(led_on))\n print( 'led off = ' + str(led_off))\n\n if led_on == 6:\n print(\"led on\")\n led.value(1)\n stateis = \"LED is ON\"\n\n if led_off == 6:\n print(\"led off\")\n led.value(0)\n stateis = \"LED is OFF\"\n # generate the we page with the stateis as a parameter\n response = html % stateis\n cl.send('HTTP/1.0 200 OK\\r\\nContent-type: text/html\\r\\n\\r\\n')\n cl.send(response)\n cl.close()\n\n except OSError as e:\n cl.close()\n print('connection closed')\n
"},{"location":"basics/06-wireless/#shttp-support","title":"SHTTP Support","text":"Warning
This code is not working. I believe we need to get a SSL certificate for SSL to work. To do this I think we need to use a command line tool to generate a certificate for the device and store it in RAM.
import network\nimport secrets\nimport time\nimport urequests\nwlan = network.WLAN(network.STA_IF)\nwlan.active(True)\nwlan.connect(secrets.SSID, secrets.PASSWORD)\nprint(wlan.isconnected())\nmy_ip = urequests.get(\"https://api.myip.com/\").json()\nprint(im_pi)\n
"},{"location":"basics/06-wireless/#sending-notifications","title":"Sending Notifications","text":"We can connect to a remote server to send text and e-mail notifications if specific events occur on our devices. To do this you must have credentials on some system that response to messages such as IFTTT or an MQTT server.
TBD
"},{"location":"concept-cards/01-intro/","title":"MicroPython Concept Cards","text":"This is a list of concept cards that are related to MicroPython. Our goal is to print these concept cards on 1/2 sheet laminated paper and have them available at the center of the tables in our CoderDojo Classrooms.
Please let me know if you are willing to volunteer.
Each concept card will have the content stored in a MarkDown file. A Python script will convert the MarkDown into HTML with a CSS file that paginates the output.
Concept Cards
"},{"location":"concept-cards/01-intro/#beginning-concepts","title":"Beginning concepts","text":""},{"location":"concept-cards/01-intro/#physical-computing","title":"Physical Computing","text":""},{"location":"concept-cards/01-intro/#sensors","title":"Sensors","text":""},{"location":"concept-cards/01-intro/#switches-and-buttons","title":"Switches and Buttons","text":""},{"location":"concept-cards/01-intro/#sensing-light","title":"Sensing Light","text":""},{"location":"concept-cards/01-intro/#sensing-distance","title":"[Sensing Distance]","text":""},{"location":"concept-cards/01-intro/#ping-distance-sensors","title":"Ping Distance Sensors","text":""},{"location":"concept-cards/01-intro/#time-of-flight-distance-sensors","title":"Time of Flight Distance Sensors","text":""},{"location":"concept-cards/01-intro/#motors","title":"Motors","text":""},{"location":"concept-cards/01-intro/#dc-motors","title":"DC Motors","text":""},{"location":"concept-cards/01-intro/#servos","title":"Servos","text":""},{"location":"concept-cards/01-intro/#stepper-motors","title":"Stepper Motors","text":""},{"location":"concept-cards/01-intro/#batteries-and-power","title":"Batteries and Power","text":""},{"location":"concept-cards/01-intro/#aa-batteries","title":"AA Batteries","text":""},{"location":"concept-cards/01-intro/#lipo-batteries","title":"LiPo Batteries","text":""},{"location":"concept-cards/01-intro/#intermediate-concepts","title":"Intermediate Concepts","text":""},{"location":"concept-cards/01-intro/#interrupts","title":"Interrupts","text":""},{"location":"concept-cards/01-intro/#advanced-concepts","title":"Advanced Concepts","text":""},{"location":"concept-cards/01-intro/#multicore-programming","title":"MultiCore Programming","text":""},{"location":"concept-cards/01-intro/#references","title":"References","text":"https://www.sitepoint.com/css-printer-friendly-pages/
https://www.jotform.com/blog/css-perfect-print-stylesheet-98272/
"},{"location":"debugging/28-debugging-python/","title":"How to Debug Micropython","text":""},{"location":"debugging/28-debugging-python/#listing-the-modules","title":"Listing the Modules","text":"help('modules')\n
Result:
__main__ gc uasyncio/funcs uos\n_boot machine uasyncio/lock urandom\n_onewire math uasyncio/stream ure\n_rp2 micropython ubinascii uselect\n_thread onewire ucollections ustruct\n_uasyncio rp2 uctypes usys\nbuiltins uasyncio/__init__ uhashlib utime\nds18x20 uasyncio/core uio uzlib\nframebuf uasyncio/event ujson\nPlus any modules on the filesystem\n
"},{"location":"debugging/28-debugging-python/#micropython-issues","title":"Micropython issues","text":"https://github.com/micropython/micropython/issues
"},{"location":"debugging/29-debugging-spi/","title":"Debugging SPI","text":"In this lab we use a logic analyzer to debug the SPI protocol being used to drive a sample OLED device. We will be using the the 8 port Saleae Logic Analyser. The retail cost is about $399.00 although there are lower cost logic analyzer available.
"},{"location":"debugging/29-debugging-spi/#the-ssd1306-spi-oled-timing-diagram","title":"The SSD1306 SPI OLED Timing Diagram","text":"The OLED display is a read-only interface. It does not send any data back to the microcontroller, so there is no MOSI connection. The data is transmitted on the SDK line when the SCL line goes high. The CS line must be low for the OLED to be active.
For details, see section 8.1.3 MCU Serial Interface on page 21 of the SSD1305 132 x 64 Dot Matrix OLED/PLED Segment/Common Driver with Controller.
"},{"location":"debugging/29-debugging-spi/#oled-spi-settings","title":"OLED SPI settings","text":"Our OLED device has seven wires. In addition to power and ground there a five data connections we will be observing on our logic analyzer.
Saleae Logic Analyser SPI Logic Analyser Settings
"},{"location":"debugging/29-debugging-spi/#check-a-working-device","title":"Check a Working Device","text":"The first thing we want to see is what the signals to a working SPI OLED should be. There are plenty of working drivers for the Arduino, so I hooked one up to the Logic analizer to see what they were.
"},{"location":"debugging/29-debugging-spi/#viewing-data-clock-and-res","title":"Viewing Data Clock and RES","text":"All five signals
DC and CS signals have a larger period.
Our Clock (SCL) has 8 positive pulses with a width of .4167 microseconds. This means that the positve/negative combined width has a period of 2 * .4167 = .8333 microseconds. This can be converted into a frequency of 1.2 megahertz.
"},{"location":"debugging/29-debugging-spi/#references","title":"References","text":"Video on how to use the Saleae Logic Analyzer
https://www.youtube.com/watch?v=Ak9R4yxQPhs
"},{"location":"debugging/29a-debugging-i2c/","title":"Debugging I2C Bus","text":""},{"location":"displays/graph/01-intro/","title":"Introduction to OLED Displays","text":"Four colors of 2.44\" OLED displays from DIY More. We can purchase them on EBay for around $18 each.
We use small OLED displays in many of our labs because:
The first step is to find out what type of display graphics chip is used in your OLED.
In these lessons we will assume you have a 128X64 or similar OLED display. Many of these displays can be purchased for around $4 on eBay. Many of these displays use the popular SSD1306 chip to drive the displays. There are also to communication options:
Updating a 128X64 display using I2C takes around 37ms. When using the SPI interface, updating the display can be reduced to around 2.79ms.
These labs will assume these parameters, but you can modify the labs to use different sizes and display driver chips by only modifying a few lines of code.
"},{"location":"displays/graph/01-intro/#i2c-scanner","title":"I2C Scanner","text":"Because your microcontroller might have multiple displays on it, their must be some way to address the devices using an address. Most of the devices come with a default address of decimal value 60 (hex value X3C). To test this the i3c module has a i2c scan function.
import machine\nsda=machine.Pin(0) # row one on our standard Pico breadboard\nscl=machine.Pin(1) # row two on our standard Pico breadboard\ni2c=machine.I2C(0, sda=sda, scl=scl, freq=400000)\nprint(\"Device found at decimal\", i2c.scan())\n
If you don't see a return value of \"60\" or similar, then you need to check your wiring and make sure that you have an I2C (not an SPI) device.
"},{"location":"displays/graph/01-intro/#references","title":"References","text":"At the beginning of of your Python programs there is usually a few lines of setup instruction to tell the system which libraries to use, what pins to assign and what devices to initialize.
We will first look at the simple I2C setup. Then we will look at the SPI setup.
"},{"location":"displays/graph/02-oled-setup/#i2c-scanner","title":"I2C Scanner","text":"Because your microcontroller might have multiple I2C devices and displays on it, there must be some way to address the devices using an address. Most of the devices come with a default address of decimal value 60 (hex value X3C). To test this the i3c module has a i2c scan function.
import machine\nsda=machine.Pin(0)\nscl=machine.Pin(1)\ni2c=machine.I2C(0, sda=sda, scl=scl, freq=400000)\nprint(\"Device found at decimal\", i2c.scan())\n
returns: [60]
returns: [60]
"},{"location":"displays/graph/02-oled-setup/#ssd1306-examples","title":"SSD1306 Examples","text":""},{"location":"displays/graph/02-oled-setup/#ssd1306-i2c-setup","title":"SSD1306 I2C Setup","text":"from ssd1306 import SSD1306_I2C\noled = SSD1306_I2C(128, 64, i2c)\noled.text('Hello World!', 0, 0, 1)\noled.show()\n
"},{"location":"displays/graph/02-oled-setup/#ssd1306-spi-setup","title":"SSD1306 SPI Setup","text":"Back connections:
Front labels on OLED with SPI:
Here is the connection diagram:
Here is the code:
import machine import ssd1306\nspi_sck=machine.Pin(2)\nspi_tx=machine.Pin(3)\nspi=machine.SPI(0, baudrate=100000, sck=spi_sck, mosi=spi_tx)\nCS = machine.Pin(1)\nDC = machine.Pin(4)\nRES = machine.Pin(5)\noled = ssd1306.SSD1306_SPI(128, 64, spi, DC, RES, CS)\noled.text('Hello World!', 0, 0, 1)\noled.show()\n
"},{"location":"displays/graph/02-oled-setup/#ssh1106-i2c-setup","title":"SSH1106 I2C Setup","text":"from machine import Pin, I2C\nimport sh1106\n\nsda=machine.Pin(0)\nscl=machine.Pin(1)\ni2c = I2C(0, scl=scl, sda=sda, freq=400000)\noled = SH1106_I2C(128, 64, i2c)\noled.text('Hello World!', 0, 0, 1)\noled.show()\n
"},{"location":"displays/graph/02-oled-setup/#references","title":"References","text":"Micropython SSD1306 Driver on GitHub
"},{"location":"displays/graph/03-basic-drawing/","title":"Basic Drawing","text":""},{"location":"displays/graph/03-basic-drawing/#basic-draw-functions","title":"Basic Draw Functions","text":"For our beginning labs we will just do some basic drawing. We will start out with just four functions:
Let's assume that we have a four wire OLED that uses the popular SSD1306 chip with 128X64 pixels. We call our oled \"oled\" using the following line:
from ssd1306 import SSD1306_I2C\noled = SSD1306_I2C(128, 64, i2c)\n
Function Description Parameters oled.fill(0) Fill the display with white or black 0=black and 1=white oled.text(\"Hello\", Draw text String, x (horizontal from left edge) and y (vertical from the top)Example: Draw \"Hello World\" 40 over and 10 down. oled.text(\"Hello World!\", 40, 10) show Show the display Send the current frame buffer to the display. You must do this after you make and changes to the Framebuffer. The full program would look like this:
from ssd1306 import SSD1306_I2C\noled = SSD1306_I2C(128, 64, i2c)\noled.fill(0)\noled.text(\"Hello World!\", 0, 0)\noled.show()\n
This would display the following:
"},{"location":"displays/graph/03-basic-drawing/#full-list-of-drawing-functions","title":"Full list of Drawing Functions","text":"Every drawing library might have slightly different functions. But we can quickly see the functions that we want by using the dir() function on the SSD1306_I2C class.
from ssd1306 import SSD1306_I2C\nprint(dir(SSD1306_I2C))\n
This returns the following list: ['__class__', '__init__', '__module__', '__name__', '__qualname__',\n'__bases__', '__dict__', 'blit', 'fill', 'fill_rect', 'hline',\n'invert', 'line', 'pixel', 'rect', 'scroll', 'text', 'vline',\n'init_display', 'write_cmd', 'show', 'poweroff', 'poweron',\n'contrast', 'write_data']\n
Technically, these are called methods of the SSD1306_I2C class. The ones that begin and end with double underscores are class methods for creating new object instances. The rest of the items on the list are the drawing functions. The following are relevant for the SSD1306_I2C display.
The display has (0,0) in the upper left corner. X is horizontal (width) and Y is vertical (height). The state or color is 0=off (black) and 1=on (white).
Function Description Example blit(fbuf, x, y, color) Bit Level Transfer blit(fbuf, 1, 1) fill(state) Fill Fill with black (0) or white(1) fill_rect Fill a rectangle hline(x, x, length, state) Draw a horizontal line Draw a horizontal line at the top of the display: oled.hline(0, 0, 127, 1) invert() invert the display Filp the orientation of the display line(x1,y1,x2,y2) draw a line at any angle Horizontal oled.line(0,0, 127, 63, 1) pixel(x,y, color) Draw a single point on the screen rect(x, y, width, height) Draw an empty rectangle scroll(x,y) Scroll the display text(x,y,color) Write text at a point vline(x,y,length, color) Draw a Vertical Line oled.vline(width - 1, 0, height - 1, 1) # right edge init_display() Initialize the display write_cmd Write a command to the display show() Update the display from the frame buffer poweroff() poweron() contrast() write_data()"},{"location":"displays/graph/03-basic-drawing/#pixel-drawing-example","title":"Pixel Drawing Example","text":"ICON = [\n [ 0, 0, 0, 0, 0, 0, 0, 0, 0],\n [ 0, 1, 1, 0, 0, 0, 1, 1, 0],\n [ 1, 1, 1, 1, 0, 1, 1, 1, 1],\n [ 1, 1, 1, 1, 1, 1, 1, 1, 1],\n [ 1, 1, 1, 1, 1, 1, 1, 1, 1],\n [ 0, 1, 1, 1, 1, 1, 1, 1, 0],\n [ 0, 0, 1, 1, 1, 1, 1, 0, 0],\n [ 0, 0, 0, 1, 1, 1, 0, 0, 0],\n [ 0, 0, 0, 0, 1, 0, 0, 0, 0],\n]\n\ndisplay.fill(0) # Clear the display\nfor y, row in enumerate(ICON):\n for x, c in enumerate(row):\n display.pixel(x, y, c) \n\ndisplay.show()\n
"},{"location":"displays/graph/03-basic-drawing/#drawing-tutorial-and-primitives","title":"Drawing Tutorial and Primitives","text":"Taken from the MicroPython site: Using a SSD1306 OLED display - although the path name imply the ESP8266, these functions also run on the Raspberry Pi Pico.
display.poweroff() # power off the display, pixels persist in memory\ndisplay.poweron() # power on the display, pixels redrawn\ndisplay.contrast(0) # dim\ndisplay.contrast(255) # bright\ndisplay.invert(1) # display inverted\ndisplay.invert(0) # display normal\ndisplay.rotate(True) # rotate 180 degrees\ndisplay.rotate(False) # rotate 0 degrees\ndisplay.show() # write the contents of the FrameBuffer to display memory\n
display.fill(0) # fill entire screen with colour=0\ndisplay.pixel(0, 10) # get pixel at x=0, y=10\ndisplay.pixel(0, 10, 1) # set pixel at x=0, y=10 to colour=1\ndisplay.hline(0, 8, 4, 1) # draw horizontal line x=0, y=8, width=4, colour=1\ndisplay.vline(0, 8, 4, 1) # draw vertical line x=0, y=8, height=4, colour=1\ndisplay.line(0, 0, 127, 63, 1) # draw a line from 0,0 to 127,63\ndisplay.rect(10, 10, 107, 43, 1) # draw a rectangle outline 10,10 to 107,43, colour=1\ndisplay.fill_rect(10, 10, 107, 43, 1) # draw a solid rectangle 10,10 to 107,43, colour=1\ndisplay.text('Hello World', 0, 0, 1) # draw some text at x=0, y=0, colour=1\ndisplay.scroll(20, 0) # scroll 20 pixels to the right\n
"},{"location":"displays/graph/03-basic-drawing/#working-with-the-framebuf","title":"Working with the framebuf","text":"A frame buffer is a region of RAM that holds an exact image of what is on the display. The data can be copied from the framebuffer memory with the blit()
(BLock Transfer) operation that copies a rectangular area of one framebuffer to another framebuffer.
Here is an example of the blit() function:
oled.blit(my_frame_buf, 10, 10, 0)\n
# draw another FrameBuffer on top of the current one at the given coordinates\nimport framebuf\nfbuf = framebuf.FrameBuffer(bytearray(8 * 8 * 1), 8, 8, framebuf.MONO_VLSB)\nfbuf.line(0, 0, 7, 7, 1)\ndisplay.blit(fbuf, 10, 10, 0) # draw on top at x=10, y=10, key=0\ndisplay.show()\n
"},{"location":"displays/graph/03-basic-drawing/#references","title":"References","text":"A Framebuffer is the core data structure we use when drawing bitmaps.
"},{"location":"displays/graph/03-bitmaps/#block-image-transfers-blit","title":"Block Image Transfers (blit)","text":"The basic function we use to draw a rectangular region of the screen is called the blit()
function:
display.blit(frame_buffer, x, y)\n
This function moves all the data within any frame buffer to the given (x,y) position of the display. The function will check the dimensions of the frame buffer to know how much data to move to the display. You just need to tell the function where to start drawing.
"},{"location":"displays/graph/03-bitmaps/#blit-functions-are-efficient","title":"Blit Functions Are Efficient","text":"Blit operations can be much more efficient then the display.show()
function when you are just updating a small region of the screen. This is because the display.show()
function transfers the entire screen image each time it is called. Using blit
functions can be written to only update the area of the screen that changes.
For example, if you are writing a video game that has a ball moving across the screen, you only need to update the pixels around the ball, not the entire screen image. The exact performance difference between show()
and blit()
operations will depend on the size of the screen, the size of the blit update and the speed of the transfer of data from the framebuffer to the display device.
The key disadvantage of using blit()
functions is that you must consider what other artifacts there are on the screen that you might overwrite. Keeping track of the differences requires more computation by the microcontroller. The more powerful your microcontroller is relative to the communication speed, the more difference computations you can do.
Not all display drivers will let you write directly from the microcontroller resident image directly to a region of the display. Sometimes you must follow your blit() operations with a show() to transfer the entire framebuffer to the display.
"},{"location":"displays/graph/03-bitmaps/#working-with-bytearrays","title":"Working with ByteArrays","text":"MicroPython blit operations use a data representation format for images called a ByteArray. These are sequences of the bytes that will be sent in a blit operation. They are coded using the following notation:
my_bytearray = (b\"\\xFF\\xFF\\xFF\\xBF\\xDF\\xEF\\xF7\\xFF\\xFB\\xFF\\xFD\")\n
Note that the letter b
begins the parameter to show the Python interpreter that the all the characters between the double quotes are byte array values. The characters \\x
indicate that there are hexadecimals useds to encode the bit values.
Sometimes you want to update an entire region of the screen with a block of pixels that are all on or off. You can do this with the following steps
# create a 10x10 matrix of on pixels\n# allocate an array of 20 bytes = \non_buffer = bytearray(20)\n# put all 1's in that buffer\non_buffer[:] = b'\\xff' * 20\n# create a frame buffer using monochrome \non_square = framebuf.FrameBuffer(on_buffer, 10, 10, framebuf.MONO_HLSB)\n\noled.blit(logo, i, 0)\n
"},{"location":"displays/graph/03-bitmaps/#image-encoding-options","title":"Image Encoding Options","text":"There are several alternate methods to encode the bits of an image into a byte array. The bits can be coded left to right or top to bottom. You can also put the bits in most-significant bit first or least-significant bit first. All these options and controlled when you interface with a framebuffer.
"},{"location":"displays/graph/03-bitmaps/#vertical-least-significant-bit-layout","title":"Vertical Least Significant Bit Layout","text":"framebuf.MONO_VLSB Monochrome (1-bit) color format This defines a mapping where the bits in a byte are vertically mapped with bit 0 being nearest the top of the screen. Consequently each byte occupies 8 vertical pixels. Subsequent bytes appear at successive horizontal locations until the rightmost edge is reached. Further bytes are rendered at locations starting at the leftmost edge, 8 pixels lower.
"},{"location":"displays/graph/03-bitmaps/#horizontal","title":"Horizontal","text":"MONO_HLSB. Monochrome (1-bit) color format This defines a mapping where the bits in a byte are horizontally mapped. Each byte occupies 8 horizontal pixels with bit 7 being the leftmost. Subsequent bytes appear at successive horizontal locations until the rightmost edge is reached.
"},{"location":"displays/graph/03-bitmaps/#references","title":"References","text":"MicroPython Bitmap Tool Video - this video created by Lucky Resistor is a good overview of the image formats used by MicroPython.
"},{"location":"displays/graph/04-extended-drawing-functions/","title":"Extending Drawing Functions","text":"Although there are several drawing functions available in most of the standard graphics libraries, most of them lack some basic shapes such as a circle. To draw circles on your display, you will need to add new Python functions. Here are some examples of these custom drawing functions.
"},{"location":"displays/graph/04-extended-drawing-functions/#circle","title":"Circle","text":"Here is a function to draw a circle at a given (x,y) point with a radius of r and fill indicator.
Here are the parameters for circle functions
from math import sqrt\n\ndef draw_circle(cx, cy, r, color):\n diameter = r*2\n upper_left_x = cx - r\n upper_left_y = cy - r \n # scan through all pixels and only turn on pixels within r of the center\n for i in range(upper_left_x, upper_left_x + diameter):\n for j in range(upper_left_y, upper_left_y + diameter):\n # distance of the current point (i, j) from the center (cx, cy)\n d = sqrt( (i - cx) ** 2 + (j - cy) ** 2 )\n if d < r:\n oled.pixel(i, j, color)\n
"},{"location":"displays/graph/04-extended-drawing-functions/#testing-circle-drawing","title":"Testing Circle Drawing","text":"from machine import Pin\nfrom utime import sleep\nfrom math import sqrt\nimport ssd1306\n\nWIDTH = 128\nHEIGHT = 64\nclock=Pin(2) # SCL\ndata=Pin(3) # SDA\nRES = machine.Pin(4)\nDC = machine.Pin(5)\nCS = machine.Pin(6)\n\nspi=machine.SPI(0, sck=clock, mosi=data)\noled = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)\n\ndef circle(cx, cy, r, color):\n diameter = r*2\n upper_left_x = cx - r\n upper_left_y = cy - r \n # scan through all pixels and only turn on pixels within r of the center\n for i in range(upper_left_x, upper_left_x + diameter):\n for j in range(upper_left_y, upper_left_y + diameter):\n # distance of the current point (i, j) from the center (cx, cy)\n d = sqrt( (i - cx) ** 2 + (j - cy) ** 2 )\n if d < r:\n oled.pixel(i, j, color)\n\nHALF_WIDTH = int(WIDTH/2)\nHALF_HEIGHT = int(HEIGHT/2)\nwhile True:\n for rad in range(1,HALF_HEIGHT+2):\n draw_circle(HALF_WIDTH, HALF_HEIGHT, rad, 1)\n oled.show()\n sleep(.1)\n sleep(3)\n oled.fill(1)\n for rad in range(1,HALF_HEIGHT+2):\n circle(HALF_WIDTH, HALF_HEIGHT, rad, 0)\n oled.show()\n sleep(.1)\n oled.fill(0)\n sleep(3)\n
"},{"location":"displays/graph/04-extended-drawing-functions/#drawing-a-face","title":"Drawing a Face","text":"If we assume we have a 64x128 display we can call two circle functions to draw eyes
display.fill(0) # Clear the display. display.circle(32, 32, 10, 1) # draw the left eye
"},{"location":"displays/graph/05-timing-draw-speed/","title":"Timing Drawing Speed","text":"If you are writing a video game and want fast drawing times for objects on the screen, there are several different algorithms you can try. You can use the MicroPython time_us
function to record the time before and after you call a drawing function and return the difference to get an idea of the time saved in different versions of your drawing functions.
from utime import ticks_us\n\nstart = ticks_us()\nmy_function()\nend = ticks_us()\nprint('Execution time in microseconds:', end - start)\n
MicroPython also supports the ticks_cpu()
function which could return a smaller granularity for precise time measurements. However, on the Raspberry Pi implementation, the results are exactly the same as the ticks_us()
function.
In the following code, we compare two circle drawing algorithms.
sine()
and cosine()
functions to calculate the x and y distance from the center of the circle to that point.For small circles, it is very inefficient to calculate all 360 points. Scanning all the points in a 5X5 grid only takes 25 calculations. However, the larger the circle becomes, the more points there are to calculate in the row scanner method. A 20X20 circle will need to run the distance calculation 400 times.
from utime import sleep, ticks_cpu, ticks_us\nimport math\nimport ssd1306\n\n# this is the built-in LED on the Pico\nled = Pin('LED', Pin.OUT)\n\nWIDTH = 128\nHEIGHT = 64\nclock=Pin(2)\ndata=Pin(3)\nRES = machine.Pin(4)\nDC = machine.Pin(5)\nCS = machine.Pin(6)\n\nspi=machine.SPI(0, sck=clock, mosi=data)\noled = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)\n\n# \ndef fast_circle(x, y, r, color):\n # draw points around a circle skiping every 4th one\n for theta in range(0, 360, 2):\n # we can save 5% of the time by only doing this once\n radians = math.radians(theta)\n x_pos = int(x + r * math.cos(radians))\n y_pos = int(y + r * math.sin(radians))\n # check if we are within range\n #if 0 <= x_pos < 128 and 0 <= y_pos < 64:\n # we can cut another 5% by not doing these checks\n oled.pixel(x_pos, y_pos, color)\n\ndef circle(x, y, r, color):\n diameter1 = (r - 0.5) ** 2\n diameter2 = (r + 0.5) ** 2\n x_min = max(0, int(x - r))\n x_max = min(128, int(x + r + 1))\n y_min = max(0, int(y - r))\n y_max = min(64, int(y + r + 1))\n\n for y_pos in range(y_min, y_max):\n for x_pos in range(x_min, x_max):\n if ((x_pos - x) ** 2 + (y_pos - y) ** 2 >= diameter1) and ((x_pos - x) ** 2 + (y_pos - y) ** 2 <= diameter2):\n oled.pixel(x_pos, y_pos, color)\n\n\nstart = ticks_us()\ncircle(32, 32, 10, 1)\nend = ticks_us()\nprint('Standard scanner circle draw time in cpu ticks', end - start)\noled.show()\nsleep(1)\nstart = ticks_us()\nfast_circle(96, 32, 10, 1)\nend = ticks_us()\nprint('Fast draw time in cpu ticks', end - start)\noled.show()\n
Challenge
`for theta in range(0, 360, 2):
. Can you dynamically change the number of points skipped as the circle becomes smaller?In this lesson, we will draw a box around the edge of the display using the commands that draw horizontal and vertical lines: hline
and vline
. Then we will draw a ball that bounces off these edges.
import machine\nimport utime\nfrom ssd1306 import SSD1306_I2C\n\nsda=machine.Pin(0)\nscl=machine.Pin(1)\ni2c=machine.I2C(0,sda=sda, scl=scl)\n# Screen size\nwidth=128\nheight=64\noled = SSD1306_I2C(width, height, i2c)\n\noled.hline(0, 0, width - 1, 1) # top edge\noled.hline(0, height - 1, width - 1, 1) # bottom edge\noled.vline(0, 0, height - 1, 1) # left edge\noled.vline(width - 1, 0, height - 1, 1) # right edge\noled.show()\n
"},{"location":"displays/graph/10-oled-bounce/#make-a-ball-bounce-around-inside-the-wall","title":"Make a Ball Bounce Around Inside the Wall","text":"import machine\nimport utime\nfrom ssd1306 import SSD1306_I2C\n\nsda=machine.Pin(0)\nscl=machine.Pin(1)\ni2c=machine.I2C(0,sda=sda, scl=scl)\n# Screen size\nwidth=128\nheight=64\noled = SSD1306_I2C(width, height, i2c)\n\noled.fill(0) # clear to black\n\n# note that OLEDs have problems with screen burn it - don't leave this on too long!\ndef border(width, height):\n oled.hline(0, 0, width - 1, 1) # top edge\n oled.hline(0, height - 1, width - 1, 1) # bottom edge\n oled.vline(0, 0, height - 1, 1) # left edge\n oled.vline(width - 1, 0, height - 1, 1) # right edge\n\n# ok, not really a circle - just a square for now\ndef draw_ball(x,y, size, state):\n if size == 1:\n oled.pixel(x, y, state) # draw a single pixel\n else:\n for i in range(0,size): # draw a box of pixels of the right size\n for j in range(0,size):\n oled.pixel(x + i, y + j, state)\n # TODO: for size above 4 round the corners\n\nborder(width, height)\n\nball_size = 2\n# start in the middle of the screen\ncurrent_x = int(width / 2)\ncurrent_y = int(height / 2)\n# start going down to the right\ndirection_x = 1\ndirection_y = -1\n# delay_time = .0001\n\n# Bounce forever\nwhile True:\n draw_ball(current_x,current_y, ball_size,1)\n oled.show()\n # utime.sleep(delay_time)\n draw_ball(current_x,current_y,ball_size,0)\n # reverse at the edges\n # left edge test\n if current_x < 2:\n direction_x = 1\n # right edge test\n if current_x > width - ball_size -2:\n direction_x = -1\n # top edge test\n if current_y < 2:\n direction_y = 1\n # bottom edge test\n if current_y > height - ball_size - 2:\n direction_y = -1\n # update the ball\n current_x = current_x + direction_x\n current_y = current_y + direction_y\n
"},{"location":"displays/graph/11-lcd-waveshare/","title":"Waveshare LCD","text":""},{"location":"displays/graph/11-lcd-waveshare/#specification","title":"Specification","text":"Waveshare spec Waveshare wiki Demo Code ST7735S Datasheet
"},{"location":"displays/graph/11-oled-ping/","title":"OLED PING","text":""},{"location":"displays/graph/11-oled-ping/#circuit","title":"Circuit","text":""},{"location":"displays/graph/11-oled-ping/#coder","title":"Coder","text":"from machine import Pin, I2C, Timer\nfrom ssd1306 import SSD1306_I2C\nimport utime\n\n\n# global toggle button variable\nmeasure_on = False\n\n# debounce for button\ndef debounce(pin):\n timer.init(mode=Timer.ONE_SHOT, period=200, callback=on_pressed)\n\n# if button pressed, toggle measure_on\ndef on_pressed(timer):\n global measure_on\n measure_on = not measure_on\n\n# Init button\nbutton = Pin(16, Pin.IN, Pin.PULL_DOWN)\ntimer = Timer()\nbutton.irq(debounce, Pin.IRQ_RISING)\n\n# Init Display\ni2c = I2C(0,sda=Pin(0),scl=Pin(1),freq=40000)\noled = SSD1306_I2C(128,64,i2c)\n\n# Init HC-SR04 pins\ntrigger = Pin(14, Pin.OUT)\necho = Pin(13, Pin.IN)\n\n\ndef ultra():\n trigger.low()\n utime.sleep_us(2)\n trigger.high()\n utime.sleep_us(5)\n trigger.low()\n while echo.value() == 0:\n signaloff = utime.ticks_us()\n while echo.value() == 1:\n signalon = utime.ticks_us()\n timepassed = signalon - signaloff\n distance = (timepassed * 0.0343) / 2\n return distance\n\ntry:\n while True:\n oled.fill(0)\n if measure_on:\n result = ultra()\n oled.text(\"Distance:\",0,0)\n oled.text(str(result) + \" cm\",0,10)\n oled.show()\n utime.sleep(1) \nexcept KeyboardInterrupt:\n pass\n
"},{"location":"displays/graph/11-oled-sh1106-i2c/","title":"OLED SSD1306 I2C Examples","text":"We use small OLED displays in many of our labs because:
The first step is to find out what type of display graphics chip is used in your OLED.
"},{"location":"displays/graph/11-oled-sh1106-i2c/#sh1106-example","title":"SH1106 Example","text":"from machine import Pin, I2C\nimport sh1106\n\nsda=machine.Pin(0)\nscl=machine.Pin(1)\ni2c = I2C(0, scl=scl, sda=sda, freq=400000)\n\ndisplay = sh1106.SH1106_I2C(128, 64, i2c, Pin(4), 0x3c)\ndisplay.sleep(False)\n\ndisplay.fill(0)\ndisplay.text('CoderDojo', 0, 0, 1)\ndisplay.show()\n\nprint('done')\n
"},{"location":"displays/graph/11-oled-sh1106-i2c/#counter-example","title":"Counter Example","text":"In this example we will updated the display 50 times with a 1/10th of a second pause between each refresh. A counter will cycle from 1 to 50.
import machine\nimport utime\nfrom ssd1306 import SSD1306_I2C\n\nsda=machine.Pin(0)\nscl=machine.Pin(1)\ni2c=machine.I2C(0,sda=sda, scl=scl, freq=400000)\noled = SSD1306_I2C(128, 64, i2c)\n\nfor i in range(1, 51): # count 1 to 50\n oled.fill(0) # clear to black\n oled.text('CoderDojo Rocks!', 0, 0, 1) # at x=0, y=0, white on black\n oled.text(str(i), 40, 20, 1) # move 30 pixels horizontal and 20 down from the top\n oled.show() # update display\n utime.sleep(0.1) #wait 1/10th of a second\n\nprint('done')\n
"},{"location":"displays/graph/11-oled-sh1106-i2c/#animated-box","title":"Animated Box","text":"This draws a title and four lines around a drawing area. It then draws boxes that move to the right.
from machine import Pin, I2C\nimport sh1106\nimport utime\n\nsda=machine.Pin(0)\nscl=machine.Pin(1)\ni2c = I2C(0, scl=scl, sda=sda, freq=400000)\n\n## note that we can only draw from 0 to 62\ndisplay = sh1106.SH1106_I2C(128, 64, i2c, Pin(4), 0x3c)\ndisplay.sleep(False)\n\ndisplay.fill(0) # clear to black\ndisplay.text('CoderDojo Rocks', 0, 0, 1) # at x=0, y=0, white on black\n# line under title\ndisplay.hline(0, 9, 127, 1)\n# bottom of display\ndisplay.hline(0, 30, 127, 1)\n# left edge\ndisplay.vline(0, 10, 32, 1)\n# right edge\ndisplay.vline(127, 10, 32, 1)\n\nfor i in range(0, 118):\n # box x0, y0, width, height, on\n display.fill_rect(i,10, 10, 10, 1)\n # draw black behind number\n display.fill_rect(10, 21, 30, 8, 0)\n display.text(str(i), 10, 21, 1)\n display.show() # update display\n # utime.sleep(0.001)\n\nprint('done')\n
"},{"location":"displays/graph/11-oled-sh1106-i2c/#bounce-on-the-sh1106-display-using-i2c","title":"Bounce on the SH1106 Display using I2C","text":"This example is a ball that bounces around the inside of a border rectangle. Is similar to other bounce examples with the exception that you can't draw on the last row of pixels.
import machine\nimport utime\n# from ssd1306 import SSD1306_I2C\nimport sh1106\n\nsda=machine.Pin(0)\nscl=machine.Pin(1)\ni2c=machine.I2C(0,sda=sda, scl=scl)\n# Screen size\nwidth=128\nheight=64 # we could make this be 63 but the init method should use the full value\n# oled = SSD1306_I2C(width, height, i2c)\noled = sh1106.SH1106_I2C(width, height, i2c, machine.Pin(4), 0x3c)\n\noled.fill(0) # clear to black\n\n# note that OLEDs have problems with screen burn it - don't leave this on too long!\ndef border(width, height):\n oled.hline(0, 0, width - 1, 1) # top edge\n oled.hline(0, height - 2, width - 1, 1) # bottom edge\n oled.vline(0, 0, height - 1, 1) # left edge\n oled.vline(width - 1, 0, height - 1, 1) # right edge\n\n# ok, not really a circle - just a square for now\ndef draw_ball(x,y, size, state):\n if size == 1:\n oled.pixel(x, y, state) # draw a single pixel\n else:\n for i in range(0,size): # draw a box of pixels of the right size\n for j in range(0,size):\n oled.pixel(x + i, y + j, state)\n # TODO: for size above 4 round the corners\n\nborder(width, height)\n\nball_size = 5\ncurrent_x = int(width / 2)\ncurrent_y = int(height / 2)\ndirection_x = 1\ndirection_y = -1\n# delay_time = .0001\n\n# oled.line(0, height-2, width-1, height-2, 1)\n\n# Bounce forever\nwhile True:\n draw_ball(current_x,current_y, ball_size,1)\n oled.show()\n # utime.sleep(delay_time)\n draw_ball(current_x,current_y,ball_size,0)\n # reverse at the edges\n # left edge test\n if current_x < 2:\n direction_x = 1\n # right edge test\n if current_x > width - ball_size -2:\n direction_x = -1\n # top edge test\n if current_y < 2:\n direction_y = 1\n # bottom edge test\n if current_y > height - ball_size - 3:\n direction_y = -1\n # update the ball\n current_x = current_x + direction_x\n current_y = current_y + direction_y\n\nprint('done')\n
"},{"location":"displays/graph/11-oled-sh1106-i2c/#sh1106-references","title":"SH1106 References","text":"You can now use the Thonny \"Tools -> Manage Packages...\" menu to add the Python driver for the SSD1306 device. You will need to do this for every new device you use.
If the Manage Packages menu is disabled, then you will need to go into the shell and add it with the pip command.
"},{"location":"displays/graph/11-oled-ssd1306-i2c/#i2c-hello-world","title":"I2C Hello World","text":"import machine\nfrom ssd1306 import SSD1306_I2C\n\nsda=machine.Pin(0)\nscl=machine.Pin(1)\ni2c=machine.I2C(0,sda=sda, scl=scl, freq=400000)\noled = SSD1306_I2C(128, 64, i2c)\noled.fill(0)\noled.text(\"Hello World!\", 0, 0)\noled.show()\nprint('Done')\n
After this program runs you should see the text on your OLED display.
"},{"location":"displays/graph/11-oled-ssd1306-i2c/#sh1106-example","title":"SH1106 Example","text":"from machine import Pin, I2C\nimport sh1106\n\nsda=machine.Pin(0)\nscl=machine.Pin(1)\ni2c = I2C(0, scl=scl, sda=sda, freq=400000)\n\ndisplay = sh1106.SH1106_I2C(128, 64, i2c, Pin(4), 0x3c)\ndisplay.sleep(False)\n\ndisplay.fill(0)\ndisplay.text('CoderDojo', 0, 0, 1)\ndisplay.show()\n\nprint('done')\n
"},{"location":"displays/graph/11-oled-ssd1306-i2c/#counter-example","title":"Counter Example","text":"In this example we will updated the display 50 times with a 1/10th of a second pause between each refresh. A counter will cycle from 1 to 50.
import machine\nimport utime\nfrom ssd1306 import SSD1306_I2C\n\nsda=machine.Pin(0)\nscl=machine.Pin(1)\ni2c=machine.I2C(0,sda=sda, scl=scl, freq=400000)\noled = SSD1306_I2C(128, 64, i2c)\n\nfor i in range(1, 51): # count 1 to 50\n oled.fill(0) # clear to black\n oled.text('CoderDojo Rocks!', 0, 0, 1) # at x=0, y=0, white on black\n oled.text(str(i), 40, 20, 1) # move 30 pixels horizontal and 20 down from the top\n oled.show() # update display\n utime.sleep(0.1) #wait 1/10th of a second\n\nprint('done')\n
"},{"location":"displays/graph/11-oled-ssd1306-i2c/#animated-box","title":"Animated Box","text":"This draws a title and four lines around a drawing area. It then draws boxes that move to the right.
from machine import Pin, I2C\nimport sh1106\nimport utime\n\nsda=machine.Pin(0)\nscl=machine.Pin(1)\ni2c = I2C(0, scl=scl, sda=sda, freq=400000)\n\ndisplay = sh1106.SH1106_I2C(128, 64, i2c, Pin(4), 0x3c)\ndisplay.sleep(False)\n\ndisplay.fill(0) # clear to black\ndisplay.text('CoderDojo Rocks', 0, 0, 1) # at x=0, y=0, white on black\n# line under title\ndisplay.hline(0, 9, 127, 1)\n# bottom of display\ndisplay.hline(0, 30, 127, 1)\n# left edge\ndisplay.vline(0, 10, 32, 1)\n# right edge\ndisplay.vline(127, 10, 32, 1)\n\nfor i in range(0, 118):\n # box x0, y0, width, height, on\n display.fill_rect(i,10, 10, 10, 1)\n # draw black behind number\n display.fill_rect(10, 21, 30, 8, 0)\n display.text(str(i), 10, 21, 1)\n display.show() # update display\n # utime.sleep(0.001)\n\nprint('done')\n
"},{"location":"displays/graph/11-oled-ssd1306-i2c/#install-ssd1306-module","title":"Install SSD1306 Module","text":""},{"location":"displays/graph/11-oled-ssd1306-i2c/#ssd1306-module","title":"ssd1306 module","text":"SSD1306 Library - click the RAW button and then right click to do a \"Save As\"
"},{"location":"displays/graph/11-oled-ssd1306-i2c/#ssd1306-vs-sh1106","title":"SSD1306 vs. SH1106","text":"There is only one small difference between SSD1306 and SH1106: The SH1106 controller has an internal RAM of 132x64 pixel. The SSD1306 only has 128x64 pixel.
"},{"location":"displays/graph/11-oled-ssd1306-i2c/#the-spi-interface","title":"The SPI interface","text":"The four wire I2C interface is great for kids that don't want to hook up more than four wires. But there are times when we want a higher performance screen with faster refresh times. This is when the SPI interface comes in handy.
"},{"location":"displays/graph/11-oled-ssd1306-i2c/#spi-baudrate","title":"SPI Baudrate","text":"https://raspberrypi.github.io/pico-sdk-doxygen/group__hardware__spi.html#ga37f4c04ce4165ac8c129226336a0b66c
The seven wires on the back of the SPI OLED screens are the following as read from the top to bottom looking at the back of the display:
# Sample code sections\n 28 # ------------ SPI ------------------\n 29 # Pin Map SPI\n 30 # - 3v - xxxxxx - Vcc\n 31 # - G - xxxxxx - Gnd\n 32 # - D7 - GPIO 13 - Din / MOSI fixed\n 33 # - D5 - GPIO 14 - Clk / Sck fixed\n 34 # - D8 - GPIO 4 - CS (optional, if the only connected device)\n 35 # - D2 - GPIO 5 - D/C\n 36 # - D1 - GPIO 2 - Res\n
SCK is the clock - hook this to the oled SCL MOSI is the line taking data from your Pico to the peripheral device. Hook this to SDA
From the SDK: https://datasheets.raspberrypi.org/pico/raspberry-pi-pico-python-sdk.pdf Section 3.7
This contradicts p122 in GET STARTED WITH MICROPYTHON ON RASPBERRY PI PICO
spi_sck=machine.Pin(2)\nspi_tx=machine.Pin(3)\nspi_rx=machine.Pin(4)\n
"},{"location":"displays/graph/11-oled-ssd1306-i2c/#spi-terms","title":"SPI Terms","text":"Master Out Slave In (MOSI)
We send the data to the SPI RX (Receive) port on the Pico. These are pin 1 (GP0) or pin 6 (GP4)
"},{"location":"displays/graph/11-oled-ssd1306-i2c/#sample-nonworking-spi-code","title":"Sample Nonworking SPI Code","text":"From the documentation:
From
spi is an SPI object, which has to be created beforehand and tells the ports for SCLJ and MOSI. MISO is not used.
dc is the GPIO Pin object for the Data/Command selection. It will be initialized by the driver.
res is the GPIO Pin object for the reset connection. It will be initialized by the driver. If it is not needed, it can be set to None or omitted. In this case the default value of None applies.
cs is the GPIO Pin object for the CS connection. It will be initialized by the driver. If it is not needed, it can be set to None or omitted. In this case the default value of None applies.
import machine\nimport machine\nimport utime\nimport ssd1306\nled = machine.Pin(25, machine.Pin.OUT)\n\n# From: https://github.com/robert-hh/SH1106\n# display = sh1106.SH1106_SPI(width, height, spi, dc, res, cs)\n#MOSI=machine.Pin(7)\n#SCK=machine.Pin(6)\n#spi = machine.SPI(0, baudrate=400000, sck=SCK, mosi=MOSI)\nspi_sck=machine.Pin(6)\nspi_tx=machine.Pin(7)\n# spi_rx=machine.Pin(4)\nspi=machine.SPI(0,baudrate=100000,sck=spi_sck, mosi=spi_tx)\n\nCS = machine.Pin(8)\nDC = machine.Pin(9)\nRES = machine.Pin(10)\n\noled = ssd1306.SSD1306_SPI(128, 64, spi, DC, RES, CS)\n\n# flash all pixels on\noled.fill(1)\noled.show()\nutime.sleep(0.5)\n\noled.fill(0)\noled.text('CoderDojo Rocks!', 0, 0, 1)\noled.show()\n\n# flash the LED to show end\nled.high()\nutime.sleep(0.5)\nled.low()\n\nprint('Done')\n
"},{"location":"displays/graph/11-oled-ssd1306-i2c/#references","title":"References","text":"MicroPython Tutorial on the SSD1306
robert-hh's SH1106 Driver
M Fitzp OLED Display i2c Article
Adafruit Stats
DIY More OLED Product Description
You can now use the Thonny \"Tools -> Manage Packages...\" menu to add the Python driver for the SSD1306 device. You will need to do this for every new device you use.
If the Manage Packages menu is disabled, then you will need to go into the shell and add it with the pip command.
"},{"location":"displays/graph/11-oled-ssd1306-spi/#install-ssd1306-module","title":"Install SSD1306 Module","text":""},{"location":"displays/graph/11-oled-ssd1306-spi/#ssd1306-module","title":"ssd1306 module","text":"SSD1306 Library - click the RAW button and then right click to do a \"Save As\"
SSD1306 Library Searchable
"},{"location":"displays/graph/11-oled-ssd1306-spi/#the-spi-interface","title":"The SPI interface","text":"The four wire I2C interface is great for kids that don't want to hook up more than four wires. But there are times when we want a higher performance screen with faster refresh times. This is when the SPI interface comes in handy.
"},{"location":"displays/graph/11-oled-ssd1306-spi/#displaying-spi-defaults","title":"Displaying SPI Defaults","text":"from machine import Pin\nfrom ssd1306 import SSD1306_SPI\n# default is data (MOSI) on GP7 and clock (sck) on GP6\nspi=machine.SPI(0)\nprint(spi)\nSPI(0, baudrate=992063, polarity=0, phase=0, bits=8, sck=6, mosi=7, miso=4)\n### SPI Baudrate\nhttps://raspberrypi.github.io/pico-sdk-doxygen/group__hardware__spi.html#ga37f4c04ce4165ac8c129226336a0b66c\n\nThe seven wires on the back of the SPI OLED screens are the following as read from the top to bottom looking at the back of the display:\n\n![](../../img/oled-back-connections.png)\n\n1. CS - Chip Select - pin 4\n2. DC - Data/Command - pin 5\n3. RES - Reset - pin 6\n4. SDA - Data - SPIO TX GP7 pin 10\n5. SCL - Clock - Connect to SPIO SCK GP6 pin 9\n6. VCC - Connect to the 3.3V Out pin 36\n7. GND - pin 38 or 3 any other GND pin\n\n### Pico Pins\n
"},{"location":"displays/graph/11-oled-ssd1306-spi/#sample-code-sections","title":"Sample code sections","text":"28 # ------------ SPI ------------------ 29 # Pin Map SPI 30 # - 3v - xxxxxx - Vcc 31 # - G - xxxxxx - Gnd 32 # - D7 - GPIO 13 - Din / MOSI fixed 33 # - D5 - GPIO 14 - Clk / Sck fixed 34 # - D8 - GPIO 4 - CS (optional, if the only connected device) 35 # - D2 - GPIO 5 - D/C 36 # - D1 - GPIO 2 - Res
* SCK is the clock - hook this to the oled SCL\n* MOSI is the line taking data from your Pico to the peripheral device. Hook this to SDA\n\nFrom the SDK:\nhttps://datasheets.raspberrypi.org/pico/raspberry-pi-pico-python-sdk.pdf\nSection 3.7\n\n1. SPI0_SCK - pin 6\n2. SPI0_MOSI - pin 7\n3. SPI0_MISO - pin 8\n\nThis contradicts p122 in GET STARTED WITH MICROPYTHON ON RASPBERRY PI PICO\n
spi_sck=machine.Pin(2) spi_tx=machine.Pin(3) spi_rx=machine.Pin(4) We send the data to the SPI RX (Receive) port on the Pico. These are pin 1 (GP0) or pin 6 (GP4)\n\n## Sample Nonworking SPI Code\n\nFrom the documentation:\n\n!!! From Raspberry Pi Pico Documentation\n **spi** is an SPI object, which has to be created beforehand and tells the ports for SCLJ and MOSI. MISO is not used.\n\n **dc** is the GPIO Pin object for the Data/Command selection. It will be initialized by the driver.\n\n **res** is the GPIO Pin object for the reset connection. It will be initialized by the driver. If it is not needed, it can be set to None or omitted. In this case the default value of None applies.\n\n **cs** is the GPIO Pin object for the CS connection. It will be initialized by the driver. If it is not needed, it can be set to None or omitted. In this case the default value of None applies.\n\n```py\nimport machine\nimport utime\nimport ssd1306\nled = machine.Pin(25, machine.Pin.OUT)\n\nspi_sck=machine.Pin(6)\nspi_tx=machine.Pin(7)\n# spi_rx=machine.Pin(4)\nspi=machine.SPI(0,baudrate=100000,sck=spi_sck, mosi=spi_tx)\n\nCS = machine.Pin(8)\nDC = machine.Pin(9)\nRES = machine.Pin(10)\n\noled = ssd1306.SSD1306_SPI(128, 64, spi, DC, RES, CS)\n\n# flash all pixels on\noled.fill(1)\noled.show()\nutime.sleep(0.5)\n\noled.fill(0)\noled.text('CoderDojo Rocks!', 0, 0, 1)\noled.show()\n\n# flash the LED to show end\nled.high()\nutime.sleep(0.5)\nled.low()\n\nprint('Done')\n
"},{"location":"displays/graph/11-oled-ssd1306-spi/#references","title":"References","text":"robert-hh's SH1106 Driver
MicroPython SSD1306 Class
https://www.mfitzp.com/article/oled-displays-i2c-micropython/
https://github.com/adafruit/Adafruit_CircuitPython_SSD1306/blob/master/examples/ssd1306_stats.py
https://github.com/robert-hh/SH1106/blob/master/sh1106.py
DIY More OLED Product Description
"},{"location":"displays/graph/11-oled-ssd1306-spi/#ssd1306","title":"SSD1306","text":"https://www.solomon-systech.com/en/product/advanced-display/oled-display-driver-ic/ssd1306/
"},{"location":"displays/graph/11-oled-ssd1306-spi/#ssd1307","title":"SSD1307","text":"https://www.solomon-systech.com/en/product/advanced-display/oled-display-driver-ic/ssd1307/
"},{"location":"displays/graph/11-pong/","title":"Pong","text":"Using a low-cost OLED device you can write a pong game. If you use a small 128X64 OLED the price can be around $12.
"},{"location":"displays/graph/11-pong/#part-list","title":"Part list","text":"Part Name Price Link Description Raspberry Pi Pico $4 Microcenter With 264K RAM it has plenty of room for storing the framebuffer 1/2 Size Solderless Breadboard $2 link 400 tie breadboard 128X64 OLED $5 eBay You can also get larger 2.42\" displays for around $20 2 10K Potentiometers $1.5 each eBay You can purchase these in QTY 10 for less. Use the part number B10K to narrow your search. Clear Plastic Box $4 The Container Store Shallow Narrow Stackable Rectangle Clear with Lids 8-1/4\" x 3-1/2\" x 1-7/8\" h. The link is to the white lids.Raspberry Pi Pico for $4.
OLED with I2C Interface. Note the pins are VCC, GND, SCL (clock), SDA (data).
1/2 size 400 connector solderless breadboard
10K potentiometer with pre-soldered connectors. You will need two of these. You can use a male-to-male header to connect it to the breadboard.
"},{"location":"displays/graph/11-pong/#connections","title":"Connections","text":"To run this program, you will need a MicroPython display driver. Our display in this example is the popular SSD1306 driver chip. Your OLED might have a slightly different driver type.
Here is the line that must be customized for your display:
from ssd1306 import SSD1306_I2C\n
"},{"location":"displays/graph/11-pong/#testing-the-oled","title":"Testing the OLED","text":"This test will verify that your OLED connections are correct.
from machine import Pin, I2C\nfrom ssd1306 import SSD1306_I2C\nWIDTH = 128\nHEIGHT = 64\nsda=machine.Pin(0)\nscl=machine.Pin(1)\ni2c=machine.I2C(0,sda=sda, scl=scl)\noled = SSD1306_I2C(WIDTH, HEIGHT, i2c)\noled.fill(0)\noled.text(\"CoderDojo Rocks\",0,0)\noled.show()\n
"},{"location":"displays/graph/11-pong/#drawing-the-border","title":"Drawing the Border","text":"def border(WIDTH, HEIGHT):\n oled.rect(0, 0, WIDTH, HEIGHT, 1)\n
"},{"location":"displays/graph/11-pong/#full-program","title":"Full Program","text":"# Pong game on Raspberry Pi Pico with a OLED and two Potentimeters\nfrom machine import Pin, PWM, SPI\nimport ssd1306\nfrom utime import sleep\nimport random # random direction for new ball\n\nspi_sck=machine.Pin(2)\nspi_tx=machine.Pin(3)\nspi=machine.SPI(0,baudrate=100000,sck=spi_sck, mosi=spi_tx)\nCS = machine.Pin(1)\nDC = machine.Pin(4)\nRES = machine.Pin(5)\noled = ssd1306.SSD1306_SPI(128, 64, spi, DC, RES, CS)\n# connect the center tops of the potentiometers to ADC0 and ADC1\npot_pin_1 = machine.ADC(26)\npot_pin_2 = machine.ADC(26) # make them the same for testing\n\n# lower right corner with USB connector on top\nSPEAKER_PIN = 16\n# create a Pulse Width Modulation Object on this pin\nspeaker = PWM(Pin(SPEAKER_PIN))\n\n# globals variables\n# static variables are constants are uppercase variable names\nWIDTH = 128\nHALF_WIDTH = int(WIDTH / 2)\nHEIGHT = 64\nHALF_HEIGHT = HEIGHT\nBALL_SIZE = 3 # 2X2 pixels\nPAD_WIDTH = 2\nPAD_HEIGHT = 8\nHALF_PAD_WIDTH = int(PAD_WIDTH / 2)\nHALF_PAD_HEIGHT = int(PAD_HEIGHT / 2)\nPOT_MIN = 3000\nPOT_MAX = 65534\nMAX_ADC_VALUE = 65534 # Maximum value from the Analog to Digital Converter is 2^16 - 1\n# dynamic global variables use lowercase\npaddle1_vel = 0\npaddle2_vel = 0\nl_score = 0\nr_score = 0\n# continiuous update of the paddle and ball\n# play_startup_sound()\n# start with the ball in the center\nball_x = int(WIDTH / 2)\nball_y = int(HEIGHT / 2)\n# set the initial directinon to down to the right\nball_x_dir = 1\nball_y_dir = 1\n\ndef play_startup_sound():\n speaker.duty_u16(1000)\n speaker.freq(600)\n sleep(.25)\n speaker.freq(800)\n sleep(.25)\n speaker.freq(1200)\n sleep(.25)\n speaker.duty_u16(0)\n\ndef play_bounce_sound():\n speaker.duty_u16(1000)\n speaker.freq(900)\n sleep(.25)\n speaker.duty_u16(0)\n\ndef play_score_sound():\n speaker.duty_u16(1000)\n speaker.freq(600)\n sleep(.25)\n speaker.freq(800)\n sleep(.25)\n speaker.duty_u16(0)\n\n# note that OLEDs have problems with screen burn it - don't leave this on too long!\ndef border(WIDTH, HEIGHT):\n oled.rect(0, 0, WIDTH, HEIGHT, 1)\n\n\n# Takes an input number vale and a range between high-and-low and returns it scaled to the new range\n# This is similar to the Arduino map() function\ndef valmap(value, istart, istop, ostart, ostop):\n return int(ostart + (ostop - ostart) * ((value - istart) / (istop - istart)))\n\n# draw a vertical bar\ndef draw_paddle(paddle_no, paddle_center):\n if paddle_no == 1:\n x = 0\n else:\n x = WIDTH - 2\n y = paddle_center - HALF_PAD_HEIGHT\n oled.fill_rect(x, y, PAD_WIDTH, PAD_HEIGHT, 1) # fill with 1s\n\ndef draw_ball():\n oled.fill_rect(ball_x, ball_y, BALL_SIZE, BALL_SIZE, 1) # square balls for now\n\n# The main event loop\nwhile True:\n oled.fill(0) # clear screen\n oled.vline(int(WIDTH / 2), 0, HEIGHT, 1)\n # border(WIDTH, HEIGHT)\n # read both the pot values\n pot_val_1 = pot_pin_1.read_u16()\n pot_val_2 = pot_pin_1.read_u16()\n # print(pot_val_1)\n\n # scale the values from the max value of the input is a 2^16 or 65536 to 0 to HEIGHT - PAD_HEIGHT\n # ideally, it should range from 5 to 58\n pot_val_1 = valmap(pot_val_1, POT_MIN, POT_MAX, HALF_PAD_HEIGHT, HEIGHT - HALF_PAD_HEIGHT - 2)\n pot_val_2 = valmap(pot_val_2, POT_MIN, POT_MAX, HALF_PAD_HEIGHT, HEIGHT - HALF_PAD_HEIGHT - 2)\n\n # print(pot_val, pot_scaled)\n draw_paddle(1, pot_val_1 + HALF_PAD_HEIGHT)\n draw_paddle(2, pot_val_2 + HALF_PAD_HEIGHT)\n draw_ball()\n\n #update ball position with the current directions\n ball_x = ball_x + ball_x_dir\n ball_y = ball_y + ball_y_dir\n\n # update the ball direction if we are at the top or bottom edge\n if ball_y < 0:\n ball_y_dir = 1\n #play_bounce_sound()\n if ball_y > HEIGHT - 3:\n ball_y_dir = -1\n #play_bounce_sound()\n\n # if it hits the paddle bounce else score\n if ball_x < 1:\n top_paddle = pot_val_1 - HALF_PAD_HEIGHT\n bottom_paddle = pot_val_1 + HALF_PAD_HEIGHT\n if ball_y > top_paddle and ball_y < bottom_paddle:\n # we have a hit\n ball_x_dir = 1\n ball_x = 2\n play_bounce_sound()\n print('paddle hit on left edge', pot_val_1, top_paddle, bottom_paddle)\n else:\n # we have a score for the right player\n play_score_sound()\n r_score += 1\n ball_x = int(WIDTH / 2)\n ball_y = int(HEIGHT / 2)\n ball_x_dir = random.randint(-1, 2)\n if ball_x_dir == 0:\n ball_x_dir = 1\n ball_y_dir = random.randint(-1, 2)\n print('score on left edge', pot_val_1, top_paddle, bottom_paddle)\n sleep(.25)\n\n if ball_x > WIDTH - 3:\n ball_x = WIDTH - 4\n top_paddle = pot_val_2 - HALF_PAD_HEIGHT\n bottom_paddle = pot_val_2 + HALF_PAD_HEIGHT\n if ball_y > top_paddle and ball_y < bottom_paddle:\n ball_x_dir = -1\n print('bounce on right paddle', pot_val_1, top_paddle, bottom_paddle)\n else:\n l_score += 1\n play_score_sound()\n ball_x = int(WIDTH / 2)\n ball_y = int(HEIGHT / 2)\n ball_x_dir = random.randint(-1, 2)\n if ball_x_dir == 0:\n ball_x_dir = 1\n ball_y_dir = random.randint(-1, 2)\n play_bounce_sound()\n print('score on right edge', pot_val_1, top_paddle, bottom_paddle)\n sleep(.25)\n\n oled.text(str(l_score), HALF_WIDTH - 20, 5, 1)\n\n oled.text(str(r_score), HALF_WIDTH + 5, 5, 1)\n\n oled.show()\n
YouTube Video
"},{"location":"displays/graph/12-e-paper-display/","title":"Raspberry Pi E-Paper Displays with","text":""},{"location":"displays/graph/12-e-paper-display/#specifications","title":"Specifications","text":"In this lesson, we will use a potentiometer to change the value of an OLED display. We will use a small SSD1306 OLED with an I2C interface.
A potentiometer has three wires. The two outside wires connect to GND and the 3.3 volt output. The center wire, called the \"tap\" wire will connect to the pin that converts an continuous analog voltage value into a digital number.
Wikipedia Page on Potentiometer
"},{"location":"displays/graph/12-oled-pot/#circuit-diagram","title":"Circuit Diagram","text":""},{"location":"displays/graph/12-oled-pot/#sample-code","title":"Sample Code","text":""},{"location":"displays/graph/12-oled-pot/#testing-the-pot","title":"Testing the POT","text":"Our first task is to find what pin to use for our first Analog to Digital concerter. GP26 is the same as ADC0. This is pin number 31 on the Pico.
import machine\nimport utime\npot = machine.ADC(26)\nwhile True:\n print(pot.read_u16())\n utime.sleep(.2)\n
"},{"location":"displays/graph/12-oled-pot/#sample-16-bit-output","title":"Sample 16 bit output","text":"A 16-bit integer can store 216 (or 65,536) distinct values. In an unsigned representation, these values are the integers between 0 and 65,535. So we are expecting numbers from 0 to 65,535.
Sample results as we move the potentiometer from the minimum to the maximum values.
65535\n52844\n31047\n7745\n256\n352\n19140\n41114\n62239\n65535\n57277\n33384\n10114\n352\n288\n19940\n28086\n
"},{"location":"displays/graph/12-oled-pot/#testing-the-oled","title":"Testing the OLED","text":""},{"location":"displays/graph/12-oled-pot/#getting-the-defaults","title":"Getting the defaults","text":"from machine import Pin, I2C\n# i2c=machine.I2C(0)\ni2c=machine.I2C(0)\nprint(\"Device found at decimal\", i2c.scan())\nprint(i2c)\n
Results: This tells you the default pins and frequency that the I2C bus is running at.
Device found at decimal [60]\nI2C(0, freq=399361, scl=9, sda=8)\n
Device found at decimal [60]\nI2C(0, freq=399361, scl=1, sda=0)\n
This tells us that the default pins are GP9 (row 12) for clock and GP8 (row 11) for data.
from machine import Pin, I2C\nfrom ssd1306 import SSD1306_I2C\nWIDTH = 128\nHEIGHT = 64\ni2c = I2C(0) # Init I2C using I2C0 defaults SCL on GP9 (12) and SDA on GP8 (11) \noled = SSD1306_I2C(WIDTH, HEIGHT, i2c)\noled.fill(0)\noled.text(\"CoderDojo Rocks\",0,0)\noled.show()\n
"},{"location":"displays/graph/12-oled-pot/#continuous-text-display-on-oled","title":"Continuous Text Display on OLED","text":"from machine import Pin, I2C\nfrom ssd1306 import SSD1306_I2C\nWIDTH = 128\nHEIGHT = 32\ni2c = I2C(0) # Init I2C using I2C0 defaults SCL on GP9 (12) and SDA on GP8 (11) \noled = SSD1306_I2C(WIDTH, HEIGHT, i2c)\n\nPOT_PIN = machine.ADC(26)\n\nwhile True:\n oled.fill(0)\n oled.text(POT_PIN.read_u16())\n oled.show()\n utime.sleep(.2)\n
"},{"location":"displays/graph/12-oled-pot/#bar-chart-and-text-display-of-pot-value","title":"Bar Chart and Text Display of Pot Value","text":"import machine\nimport utime\nimport sh1106\n\nsda=machine.Pin(0)\nscl=machine.Pin(1)\npot_pin = machine.ADC(26)\n\ni2c=machine.I2C(0,sda=sda, scl=scl)\n# Screen size\nwidth=128\nheight=64\nhalf_height = int(height / 2)\n# oled = SSD1306_I2C(width, height, i2c)\noled = sh1106.SH1106_I2C(width, height, i2c, machine.Pin(4), 0x3c)\n\noled.fill(0) # clear to black\n\n# note that OLEDs have problems with screen burn it - don't leave this on too long!\ndef border(width, height):\n oled.hline(0, 0, width - 1, 1) # top edge\n oled.hline(0, height - 2, width - 1, 1) # bottom edge\n oled.vline(0, 0, height - 1, 1) # left edge\n oled.vline(width - 1, 0, height - 1, 1) # right edge\n\n# Takes an input number vale and a range between high-and-low and returns it scaled to the new range\n# This is similar to the Arduino map() function\ndef valmap(value, istart, istop, ostart, ostop):\n return int(ostart + (ostop - ostart) * ((value - istart) / (istop - istart)))\n\n# draw a horizontal bar\ndef draw_hbar(inval, height, state):\n oled.fill(0) # clear screen\n border(width, height) # draw a border\n oled.fill_rect(0, 1, inval, height, 1) # fill with 1\n utime.sleep(.1) # wait a bit\n\n# continuous update\nwhile True:\n pot_val = int(pot_pin.read_u16())\n # the max value of the input is a 2^16 or 65536\n pot_scaled = valmap(pot_val, 0, 65536, 0, 127)\n print(pot_val, pot_scaled)\n draw_hbar(pot_scaled, half_height, 1)\n\n oled.text('raw:', 0, half_height + 5, 1)\n oled.text(str(pot_val), 30, half_height + 5, 1)\n\n oled.text('scaled:', 0, half_height + 15, 1)\n oled.text(str(pot_scaled), 60, half_height + 15, 1)\n oled.show() \n
"},{"location":"displays/graph/12-oled-pot/#gif-of-oled","title":"Gif of OLED","text":"Gif of small .96\" OLED
Gif of larger 2.42\" OLED
"},{"location":"displays/graph/12-oled-ssd1306-spi-v1/","title":"OLED SSD1306 SPI V1","text":""},{"location":"displays/graph/12-oled-ssd1306-spi-v1/#oled-spi-demo","title":"OLED SPI Demo","text":"This code was provide by Jim Tannenbaum (aka Jet)
"},{"location":"displays/graph/12-oled-ssd1306-spi-v1/#image","title":"Image","text":""},{"location":"displays/graph/12-oled-ssd1306-spi-v1/#code","title":"Code","text":"import machine import ssd1306\nspi_sck=machine.Pin(2)\nspi_tx=machine.Pin(3)\nspi=machine.SPI(0,baudrate=100000,sck=spi_sck, mosi=spi_tx)\nCS = machine.Pin(1)\nDC = machine.Pin(4)\nRES = machine.Pin(5)\noled = ssd1306.SSD1306_SPI(128, 64, spi, DC, RES, CS)\n# flash all pixels on oled.fill(0)\noled.show()\noled.text('Hello Jet', 0, 0, 1)\noled.show()\n
"},{"location":"displays/graph/13-pixel-draw/","title":"Sample Pixel-Based Drawing Program","text":"Code example provided by Jim Tannenbaum.
This program will act like an Etch-A-Sketch(TM) program. It will use potentiometers with the center tap on GPIO pins GP26 and GP27 and draw as you move the potentiometers to control the X and Y dimensions.
from machine import Pin, SPI, ADC\nimport ssd1306\nimport time\n\n# Takes an input number value and a range between high-and-low and returns it scaled to the new range\n# This is similar to the Arduino map() function\ndef scaled(value, istart, istop, ostart, ostop):\n return int(ostart + (ostop - ostart) * ((int(value) - istart) / (istop - istart)))\n\n# Define the pins for SPI Clock and Transmit\nspi_sck = Pin(2)\nspi_tx = Pin(3)\nspi = SPI(0, baudrate=100000, sck=spi_sck, mosi=spi_tx)\n\n# Define the pins for Chip Select, DC (Command), and Reset\nCS = Pin(1)\nDC = Pin(4)\nRES = Pin(5)\n\noled = ssd1306.SSD1306_SPI(128, 64, spi, DC, RES, CS)\n\n# Turn all pixels off\noled.fill(0)\noled.show()\n\n# Provide info to user\noled.text('Etch-A-Sketch', 0, 0, 1)\noled.text('Hit the reset', 0, 20, 1)\noled.text('button to clear', 0, 30, 1)\noled.text('the screen', 0, 40, 1)\noled.show()\n\n# Define the pin for the reset button\nresetButton = Pin(14, Pin.IN, Pin.PULL_DOWN)\n\n# Wait unti the user hits the button to clear the screen and start drawing\nwhile resetButton.value() != 1:\n time.sleep(.25)\n\noled.fill(0)\noled.show()\n\n# Define the Horizontal and Vertical inputs from the Rheostats\nvert = ADC(26)\nhoriz = ADC(27)\n\n# Calculate where to start the line\nx = newX = scaled(vert.read_u16(), 0, 65536, 0, 128)\ny = newY = scaled(horiz.read_u16(), 0, 65536, 0, 64)\n\n# Loop forever\n# Draw the line, look for a reset to clear the screen, and get the new end points for the line\nwhile True:\n oled.line(x, y, newX, newY, 1)\n x = newX\n y = newY\n if resetButton.value():\n oled.fill(0)\n oled.show()\n time.sleep(.2)\n newX = scaled(vert.read_u16(), 0, 65536, 0, 128)\n newY = scaled(horiz.read_u16(), 0, 65536, 0, 64)\n
"},{"location":"displays/graph/14-lcd-st7789V/","title":"MicroPython ST7789V LCD Display","text":"The Sitronix ST7789 is a driver chip for small color IPS LCD displays that supports SPI interfaces. This example uses a 2-inch color LDC display manufactured by Waveshare with a retail price of approximately $13 or $14.75 on Amazon Prime.
"},{"location":"displays/graph/14-lcd-st7789V/#specifications","title":"Specifications","text":"Note: The ST7789 uses a SPI interfaces but not a true standard SPI protocol. The device only uses MOSI (DIN) to send data from master to slave for LCD display. Only four wires are needed to connect from the Pico to the device.
"},{"location":"displays/graph/14-lcd-st7789V/#device-interface","title":"Device Interface","text":""},{"location":"displays/graph/14-lcd-st7789V/#interface","title":"Interface","text":"Although the device has eight wires, your Pico only needs a few of them to be controlled by the GPIO ports.
"},{"location":"displays/graph/14-lcd-st7789V/#uploading-the-st7789v-python-firmware","title":"Uploading the ST7789V Python Firmware","text":"The firmware contains pre-compiled objects for various devices with the st7789 C driver and frozen python font files.
The library for the driver is delivered as a single firmware.uf2 file available here:
https://github.com/russhughes/st7789_mpy/tree/master/firmware/RP2
To load this file you will need to hold down the BOTSEL button on the Pico and drag this file into the RP2 folder that is mounted.
"},{"location":"displays/graph/14-lcd-st7789V/#micropython-initialization","title":"MicroPython Initialization","text":"I used the following SPI Device ID 1 pinout on the lower left corner of the Pico:
Pin GP Number Label on LCD 14 (GP10) BL 15 (GP11) RST 16 (GP12) DC 17 (GP13) CS 18 (GND) GND 19 (GP14) CLK 20 (GP15) DIN"},{"location":"displays/graph/14-lcd-st7789V/#sample-device-initialize","title":"Sample Device Initialize","text":"from machine import Pin, SPI\nimport st7789\n\nBACKLIGHT_PIN = 10\nRESET_PIN = 11\nDC_PIN = 12\nCS_PIN = 13\nCLK_PIN = 14\nDIN_PIN = 15 # lower left corner\n\nimport vga1_bold_16x32 as font\n\nspi = SPI(1, baudrate=31250000, sck=Pin(CLK_PIN), mosi=Pin(DIN_PIN))\ntft = st7789.ST7789(spi, 240, 320,\n reset=Pin(RESET_PIN, Pin.OUT),\n cs=Pin(CS_PIN, Pin.OUT),\n dc=Pin(DC_PIN, Pin.OUT),\n backlight=Pin(BACKLIGHT_PIN, Pin.OUT),\n rotation=3)\ntft.init()\n# draw white letters on a back background at 10 over and 20 down\ntft.text(font, \"Hello World!\", 10, 20, st7789.color565(255,255,255), st7789.color565(0,0,0))\n
"},{"location":"displays/graph/14-lcd-st7789V/#sample-hello-world-in-four-colors","title":"Sample Hello World In Four Colors","text":"from machine import Pin, SPI\nimport st7789\n\nBACKLIGHT_PIN = 10\nRESET_PIN = 11\nDC_PIN = 12\nCS_PIN = 13\nCLK_PIN = 14\nDIN_PIN = 15 # lower left corner\n\nimport vga1_bold_16x32 as font\n\nspi = SPI(1, baudrate=31250000, sck=Pin(CLK_PIN), mosi=Pin(DIN_PIN))\ntft = st7789.ST7789(spi, 240, 320,\n reset=Pin(RESET_PIN, Pin.OUT),\n cs=Pin(CS_PIN, Pin.OUT),\n dc=Pin(DC_PIN, Pin.OUT),\n backlight=Pin(BACKLIGHT_PIN, Pin.OUT),\n rotation=3)\ntft.init()\n\ntft.text(font, \"Hello World!\",10, 0, st7789.color565(255,255,255), st7789.color565(0,0,0))\ntft.text(font, \"Hello World!\",10, 50, st7789.color565(255,0,0), st7789.color565(0,0,0))\ntft.text(font, \"Hello World!\",10, 100, st7789.color565(0,255,0), st7789.color565(0,0,0))\ntft.text(font, \"Hello World!\",10, 150, st7789.color565(0,0,255), st7789.color565(0,0,0))\n
"},{"location":"displays/graph/14-lcd-st7789V/#driver-implementation-notes","title":"Driver Implementation Notes","text":"The ST7789V supports RGB444, RGB565 and RGB666 three formats. The Waveshare LCD uses RGB565. For most LCD controllers, the communication method of the controller can be configured, they are usually using 8080 parallel interface, 3-line SPI, 4-line SPI, and other communication methods. This LCD uses a 4-line SPI interface for reducing GPIO and fast speed.LCD
You can see what data needs to be changing from the timing diagram below:
This combination of two bits provides 4 modes of SPI data transmission. The commonly used is SPI0 mode, i.e. GPHL=0 and CPOL=0.
According to the figure above, data transmitting begins at the first falling edge, 8bit data are transmitted at one clock cycle. It is SPI0. MSB.
"},{"location":"displays/graph/14-lcd-st7789V/#references","title":"References","text":"This program uses the MicroPython urandom
library to generate random X and Y positions on the display. It then uses an array of binary values to draw a heart icon at that location.
from machine import Pin, PWM, SPI\nimport urandom\nimport ssd1306\nfrom utime import sleep\nimport random # random direction for new ball\n\nWIDTH = 128\nHEIGHT = 64\nCS = machine.Pin(1)\nspi_sck=machine.Pin(2)\nspi_tx=machine.Pin(3)\nDC = machine.Pin(4)\nRES = machine.Pin(5)\nspi=machine.SPI(0,baudrate=100000,sck=spi_sck, mosi=spi_tx)\n\noled = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)\n\nHEART = [\n [ 0, 0, 0, 0, 0, 0, 0, 0, 0],\n [ 0, 1, 1, 0, 0, 0, 1, 1, 0],\n [ 1, 1, 1, 1, 0, 1, 1, 1, 1],\n [ 1, 1, 1, 1, 1, 1, 1, 1, 1],\n [ 1, 1, 1, 1, 1, 1, 1, 1, 1],\n [ 0, 1, 1, 1, 1, 1, 1, 1, 0],\n [ 0, 0, 1, 1, 1, 1, 1, 0, 0],\n [ 0, 0, 0, 1, 1, 1, 0, 0, 0],\n [ 0, 0, 0, 0, 1, 0, 0, 0, 0],\n]\n\ndef draw_heart(xofs, yofs):\n for y, row in enumerate(HEART):\n for x, c in enumerate(row):\n oled.pixel(x + xofs, y + yofs, c)\n\ndef random_heart():\n xofs = urandom.getrandbits(7)\n yofs = urandom.getrandbits(6)\n print(xofs, yofs)\n draw_heart(xofs, yofs)\n\n\noled.fill(0)\nfor n in range(10):\n random_heart()\n\noled.show()\n
"},{"location":"displays/graph/15-cu1609c-led/","title":"CU1609C LED Display","text":"Note: This is a work-in-progress. We have not found a MicroPython driver for this display.
The UC1609 is a graphic LED driver chip with an SPI interface. Because it is low cost ($4) and 2 inches across it is ideal for low-cost robot displays.
192X62 LCD display for $4 USB device
"},{"location":"displays/graph/15-cu1609c-led/#connections","title":"Connections","text":"Pin Name Description 1 K Backlight Cathode Connect to GND rail 2 A Backlight Anode Connect via 200 ohm to 3.2v rail to limit backlight current to 3 milliamps. The current for backlight is limited to 20 milliamps. 3 GND Ground Connect to GND rail 4 VDD Power Supply connect to +3.3v rail 5 SCK Serial clock input. Connect to SPI CLK 6 SDA Serial data input. Connect to SPI Data SCL 7 RST Connect to 3.3v rail. 8 CD It determines whether the access is related to data or command. Connect to GPIO 9 CS Chip select input. Connect to GND when LCD is use."},{"location":"displays/graph/15-cu1609c-led/#connection-notes","title":"Connection Notes","text":"When RST=L, all control registers are re-initialized by their default sates. Since UC1609c has built-in Power-on Reset, the RST pin is not required for proper chip operation. An RC filter has been included on-chip. There is no need for external RC noise filter. When RST is not used, connect the pin to High.
CS determines whether the access is related to data or command. When CS=\u201cH\u201d : Display data. When CS=\u201cL\u201d : Command.
"},{"location":"displays/graph/15-cu1609c-led/#hello-world","title":"Hello World","text":"from machine import Pin, SPI\n\nSPI_CLK = 2 # SPI clock\nSPI_SDA = 3\nCD = 5 # command or data\n# RST is tied to 3.3v\n# CS is tied to GND\n\n# SPI(0, baudrate=992063, polarity=0, phase=0, bits=8, sck=2, mosi=3, miso=4)\nspi = SPI(0, baudrate=31250000, sck=Pin(SPI_CLK), mosi=Pin(SPI_SDA))\nprint(spi)\n
"},{"location":"displays/graph/15-cu1609c-led/#similar-drivers","title":"Similar Drivers","text":"There are two similar drivers. One is for the LCD160CR
LCD160CR Display Driver
The other is the Arduino C version by Gavin Lyones that has been around for a long time.
Gavin Lyons GitHub Repo supporting the UC1609
Our goal is to port Gavin's C code to use the same function as the LCD160CR driver.
"},{"location":"displays/graph/15-cu1609c-led/#references","title":"References","text":"In this lesson, we will show how you can display interesting repeating patterns on your OLED screen. Our program will write a pattern into the framebuffer using a simple math equation. The oled.show() will then update the pattern on the display.
This lesson was suggested by Parker Erickson.
"},{"location":"displays/graph/15-oled-patterns/#math-functions","title":"Math Functions","text":"We will use a few unusual functions to create repeating patterns:
The modulo function is written %
. It returns the integer remainder after a division. So 7 % 3
is 1 and 7 % 4
is 3. The Power function of X to the Y power is written in python as pow(x,y)
. For example pow(7, 2) is seven squared = 49.
The bitwise and is written as x & y
for i in range(8):\n 13 & i\n
Function Returns 13 & 0 0 13 & 1 1 13 & 2 0 13 & 3 1 13 & 4 4 13 & 5 5 13 & 6 4 13 & 7 5 13 & 8 8 13 & 9 9 13 & 10 8 13 & 11 9 13 & 12 12"},{"location":"displays/graph/15-oled-patterns/#some-sample-equations","title":"Some Sample Equations","text":"Note there are other patterns that use the power pow(x,y)
or Exponentiation **
function but I can't get these to work with Micropython.
This program evaluates the function x % (y+1)
for each of the pixels on the screen. If the function returns a non-zero the pixel will be off. If the pixel is zero, the pixel will be on.
draw-patterns-ssd1306-spi.py
import machine\nimport ssd1306\n\nWIDTH = 128\nHEIGHT = 64\nspi_sck=machine.Pin(2)\nspi_tx=machine.Pin(3)\nspi=machine.SPI(0,baudrate=100000,sck=spi_sck, mosi=spi_tx)\nCS = machine.Pin(1)\nDC = machine.Pin(4)\nRES = machine.Pin(5)\noled = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)\n\noled.fill(0) # clear display\nfor x in range(WIDTH):\n for y in range(HEIGHT):\n if x % (y+1):\n oled.pixel(x,y,0)\n else:\n oled.pixel(x,y,1)\noled.show()\n
"},{"location":"displays/graph/15-oled-patterns/#adding-a-list-of-patterns","title":"Adding a List of Patterns","text":""},{"location":"displays/graph/15-oled-patterns/#the-eval-function","title":"The Eval Function","text":"The eval() function takes any string and passes it to the python interpreter for evaluation within the current context of variables that are in scope. We can use eval to pass an expression that should be evaluated to any function.
list = [\"x+y\", \"x-y\", \"x*y\", \"x % (y+1)\"]\n\nfor i in range(0, 4):\n print(list[i], ': ', sep='', end='')\n for x in range(5):\n for y in range(5):\n print(eval(list[i]), '', end='')\n print('')\n
Output:
x+y: 0 1 2 3 4 1 2 3 4 5 2 3 4 5 6 3 4 5 6 7 4 5 6 7 8 \nx-y: 0 -1 -2 -3 -4 1 0 -1 -2 -3 2 1 0 -1 -2 3 2 1 0 -1 4 3 2 1 0 \nx*y: 0 0 0 0 0 0 1 2 3 4 0 2 4 6 8 0 3 6 9 12 0 4 8 12 16 \nx % (y+1): 0 0 0 0 0 0 1 1 1 1 0 0 2 2 2 0 1 0 3 3 0 0 1 0 4 \n
"},{"location":"displays/graph/15-oled-patterns/#the-command-design-pattern","title":"The Command Design Pattern","text":"The command pattern holds a list of commands in an array. Each command is executed in the sequence it appears in the list of commands.
In the following program we have the equations in a list. The program steps through each item in the list and displays that equation on the OLED display.
import machine\nimport ssd1306\nfrom utime import sleep, time\n\nWIDTH = 128\nHEIGHT = 64\nspi_sck=machine.Pin(2)\nspi_tx=machine.Pin(3)\nspi=machine.SPI(0,baudrate=100000,sck=spi_sck, mosi=spi_tx)\nCS = machine.Pin(1)\nDC = machine.Pin(4)\nRES = machine.Pin(5)\noled = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)\n\nequations = ['(x * y) & 24', '(x * y) & 47', '(x * y) & 64', 'x & y', 'x % y', '(x % y) % 4', '40 % (x % y+1)']\n\nfor eqn in range(0, len(equations)):\n start = time()\n\n oled.fill(0) # clear display\n oled.text('calculating', 0, 0, 1)\n oled.text(equations[eqn], 0, 10, 1)\n oled.show()\n for x in range(WIDTH):\n for y in range(1, HEIGHT):\n if eval(equations[eqn]):\n oled.pixel(x,y,0)\n else:\n oled.pixel(x,y,1)\n oled.show()\n sleep(5)\n\n end = time()\n duration = str(end - start)\n print(equations[eqn])\n print(duration, ' seconds')\n\noled.text('done', 0, 0, 1)\noled.show()\nprint('done')\n
"},{"location":"displays/graph/15-oled-patterns/#sample-screen-images","title":"Sample Screen Images","text":""},{"location":"displays/graph/15-oled-patterns/#x-modulo-y","title":"X Modulo Y","text":"x % y
Sierpinsky Triangles Bitwise and of x and y
"},{"location":"displays/graph/15-oled-patterns/#x-y-24","title":"(x * y) & 24","text":""},{"location":"displays/graph/15-oled-patterns/#x-y-64","title":"(x * y) & 64","text":""},{"location":"displays/graph/15-oled-patterns/#40-x-y1","title":"40 % x % (y+1)","text":""},{"location":"displays/graph/15-oled-patterns/#reference","title":"Reference","text":"Martin Kleppe Post on Twitter
"},{"location":"displays/graph/16-tft-ili9341/","title":"LI9341 TDF Display","text":"This is a 3.2\" $10 240X320 color display that is easy to set up on the Raspberry Pi Pico using the SPI interface. The hardware supports a touch screen and an SD card, but we could not locate drivers for these components.
Sample $10 part on Amazon or ebay
The TFT uses a 16-bit representation of the color of each pixel:
This requires us to include the color565
library for doing color operations. So for example, to get the color yellow, you would need to do the following:
yellow = color565(255, 255, 0)\n
"},{"location":"displays/graph/16-tft-ili9341/#sample-spi-hello-world-example","title":"Sample SPI Hello World Example","text":"# print out \"Hello World!\" using the rotation=3 using 32-bit high font\n# the default is white text on a black background\nfrom ili934x import ILI9341\nfrom machine import Pin, SPI\nimport tt32\n\n# Use these PIN definitions. SCK must be on 2 and data (SDL) on 3\nSCK_PIN = 2\nMISO_PIN = 3 # labeled SDI(MOSI) on the back of the display\nDC_PIN = 4\nRESET_PIN = 5\nCS_PIN = 6\n\n# mosi=Pin(23)\n# miso=Pin(MISO_PIN)\nspi = SPI(0, baudrate=20000000, mosi=Pin(MISO_PIN),sck=Pin(SCK_PIN))\ndisplay = ILI9341(spi, cs=Pin(CS_PIN), dc=Pin(DC_PIN), rst=Pin(RESET_PIN), w=320, h=240, r=3)\ndisplay.erase()\ndisplay.set_font(tt32)\ndisplay.set_pos(0,0)\ndisplay.print('Hello World!')\n
"},{"location":"displays/graph/16-tft-ili9341/#draw-random-rectangles","title":"Draw Random Rectangles","text":"from ili934x import ILI9341, color565\nfrom machine import Pin, SPI\nfrom utime import sleep\nfrom random import randint\n\nWIDTH = 320\nHALF_WIDTH = int(WIDTH/2)\nHEIGHT = 240\nHALF_HEIGHT = int(HEIGHT/2)\nROTATION = 3 # landscape with 0,0 in upper left and pins on left\n\nSCK_PIN = 2\nMISO_PIN = 3\nDC_PIN = 4\nRST_PIN = 5\nCS_PIN = 6\n\n# mosi=Pin(23)\n# miso=Pin(MISO_PIN)\nspi = SPI(0, baudrate=20000000, mosi=Pin(MISO_PIN), sck=Pin(SCK_PIN))\ndisplay = ILI9341(spi, cs=Pin(CS_PIN), dc=Pin(DC_PIN), rst=Pin(RST_PIN), w=WIDTH, h=HEIGHT, r=ROTATION)\ndisplay.erase()\n\n# color defintions converted to 565 represnetations\nblack = color565(0, 0, 0)\nwhite = color565(255, 255, 255)\nred = color565(255, 0, 0)\ngreen = color565(0, 255, 0)\nblue = color565(0, 0, 255)\nyellow = color565(255, 255, 0)\ncyan = color565(0, 255, 255)\nmagenta = color565(255, 0, 255)\ngray = color565(128, 128, 128)\nlight_gray = color565(192, 192, 192)\ndark_gray = color565(64, 64, 64)\nbrown = color565(165, 42, 42)\norange = color565(255, 60, 0)\n# 150 for the green and blue wash out the colors\npink = color565(255, 130, 130)\npurple = color565(128, 0, 128)\nlavender = color565(150, 150, 200)\nbeige = color565(200, 200, 150)\n# by definition, maroon is 50% of the red on, but 128 is way too bright\nmaroon = color565(105, 0, 0)\nolive = color565(128, 128, 0)\nturquoise = color565(64, 224, 208)\ndark_green = color565(0,100,0)\ncolor_list = [white, red, green, blue, yellow, cyan, magenta,\n gray, light_gray, dark_gray, brown, orange, pink, purple, lavender,\n beige, maroon, olive, turquoise, dark_green, black]\ncolor_num = len(color_list)\n\n# Draw forever\nwhile True:\n # rect_fill(x, y, width, height, color)\n x = randint(0, HALF_WIDTH)\n y = randint(0, HALF_HEIGHT)\n width = randint(0, HALF_WIDTH)\n height = randint(0, HALF_HEIGHT)\n color = color_list[randint(0, color_num-1)]\n print('fill_rectangle(', x, y, width, height, color)\n display.fill_rectangle(x, y, width, height, color)\n
"},{"location":"displays/graph/16-tft-ili9341/#draw-color-lists","title":"Draw Color Lists","text":"One of the best ways to study the color values is to display a rectangle and list the color name and values under the rectangle.
Here is a sample program that will do this.
from ili934x import ILI9341, color565\nfrom machine import Pin, SPI\nfrom utime import sleep\nfrom random import randint\nimport tt32\n\nWIDTH = 320\nHALF_WIDTH = int(WIDTH/2)\nHEIGHT = 240\nHALF_HEIGHT = int(HEIGHT/2)\nROTATION = 3 # landscape with 0,0 in upper left and pins on left\nSCK_PIN = 2\nMISO_PIN = 3\nDC_PIN = 4\nRST_PIN = 5\nCS_PIN = 6\n\n# mosi=Pin(23)\n# miso=Pin(MISO_PIN)\nspi = SPI(0, baudrate=20000000, mosi=Pin(MISO_PIN),sck=Pin(SCK_PIN))\ndisplay = ILI9341(spi, cs=Pin(CS_PIN), dc=Pin(DC_PIN), rst=Pin(RST_PIN), w=WIDTH, h=HEIGHT, r=ROTATION)\ndisplay.set_font(tt32)\ndisplay.erase()\n\n# color defintions convered to 565 represnetations\nblack = color565(0, 0, 0)\nwhite = color565(255, 255, 255)\nred = color565(255, 0, 0)\ngreen = color565(0, 255, 0)\nblue = color565(0, 0, 255)\nyellow = color565(255, 255, 0)\ncyan = color565(0, 255, 255)\nmagenta = color565(255, 0, 255)\ngray = color565(128, 128, 128)\nlight_gray = color565(192, 192, 192)\ndark_gray = color565(64, 64, 64)\nbrown = color565(165, 42, 42)\norange = color565(255, 60, 0)\n# 150 for the green and blue wash out the colors\npink = color565(255, 130, 130)\npurple = color565(128, 0, 128)\nlavender = color565(150, 150, 200)\nbeige = color565(200, 200, 150)\n# by definition, maroon is 50% of the red on, but 128 is way too bright\nmaroon = color565(105, 0, 0)\nolive = color565(128, 128, 0)\nturquoise = color565(64, 224, 208)\ndark_green = color565(0,100,0)\ncolor_list = [white, red, green, blue, yellow, cyan, magenta,\n gray, light_gray, dark_gray, brown, orange, pink, purple, lavender,\n beige, maroon, olive, turquoise, dark_green, black]\ncolor_names = ['white (255,255,255)', 'red (255,0,0)', 'green (0,255,0)', 'blue (0,0,255)', 'yellow (255,255,0)',\n 'cyan (0,255,255)', 'magenta (255,0,255)',\n 'gray (128,128,128)', 'light gray (192,192,192)', 'dark gray (64,64,64)',\n 'brown (165,42,42)', 'orange (255,60,0)', 'pink (255,130,130)', 'purple (128,0,128)',\n 'lavender (150,150,200)',\n 'beige (200,200,150)', 'maroon (105,0,0)', 'olive (128,128,0)', 'turquoise (64,224,208)',\n 'dark green (0,100,0)', 'black (0,0,0)']\ncolor_num = len(color_list)\n\ndisplay.fill_rectangle(0, 0, WIDTH, HEIGHT, black)\nwhile True:\n for i in range(0, color_num):\n display.fill_rectangle(0, 0, WIDTH, HEIGHT-33, color_list[i])\n # black behind the white text\n display.fill_rectangle(0, HEIGHT-32, WIDTH, 32, black)\n\n display.set_pos(0,HEIGHT-32)\n display.print(color_names[i])\n print(color_names[i])\n sleep(1)\n
"},{"location":"displays/graph/16-tft-ili9341/#screen-update-speed","title":"Screen Update Speed","text":"One large disadvantage of this display is the very slow refresh rate. Transmitting the entire screen of 240X320 with two bytes per pixel takes a long time over SPI. This makes this setup difficult to use for animation.
"},{"location":"displays/graph/16-tft-ili9341/#ball-bounce-animation","title":"Ball Bounce Animation","text":"Here is a very slow \"ball bounce\" animation that is slow and has a lot of flicker.
from ili934x import ILI9341, color565\nfrom machine import Pin, SPI\nfrom utime import sleep\n\nWIDTH = 320\nHEIGHT = 240\nROTATION = 3 # landscape with 0,0 in upper left and pins on left\nSCK_PIN = 2\nMISO_PIN = 3\nDC_PIN = 4\nRST_PIN = 5\nCS_PIN = 6\n\n# mosi=Pin(23)\n# miso=Pin(MISO_PIN)\nspi = SPI(0, baudrate=20000000, mosi=Pin(MISO_PIN),sck=Pin(SCK_PIN))\ndisplay = ILI9341(spi, cs=Pin(CS_PIN), dc=Pin(DC_PIN), rst=Pin(RST_PIN), w=WIDTH, h=HEIGHT, r=ROTATION)\ndisplay.erase()\n\n# color defintions convered to 565 represnetations\nblack = color565(0, 0, 0)\nwhite = color565(255, 255, 255)\nred = color565(255, 0, 0)\ngreen = color565(0, 255, 0)\nblue = color565(0, 0, 255)\n\n# ok, not really a circle - just a square for now\ndef draw_ball(x,y, size, color):\n if size == 1:\n display.pixel(x, y, color) # draw a single pixel\n else:\n display.fill_rectangle(x, y, size, size, color)\n\nball_size = 20\n# start in the middle of the screen\ncurrent_x = int(WIDTH / 2)\ncurrent_y = int(HEIGHT / 2)\n# start going down to the right\ndirection_x = 1\ndirection_y = -1\n# delay_time = .0001\n\n# Bounce forever\nwhile True:\n # draw the square ball in white\n draw_ball(current_x,current_y, ball_size, white)\n sleep(.1)\n # the erase the old ball takes too long and causes a flicker\n draw_ball(current_x,current_y,ball_size, black)\n if current_x < 0:\n direction_x = 1\n # right edge test\n if current_x > WIDTH - ball_size -2:\n direction_x = -1\n # top edge test\n if current_y < 0:\n direction_y = 1\n # bottom edge test\n if current_y > HEIGHT - ball_size - 2:\n direction_y = -1\n # update the ball\n current_x = current_x + direction_x\n current_y = current_y + direction_y\n
"},{"location":"displays/graph/16-tft-ili9341/#references","title":"References","text":"Jeffmer's GitHub library which includes four fonts - the sizes are 8, 14, 24 and 32 pixels.
Amazon HiLetgo ILI9341 2.8\" SPI TFT LCD Display Touch Panel 240X320 with PCB 5V/3.3V STM32
ebay Listing
The SSD1351 is a $40 color OLED measures 1.5\" diagonal and contains 128x128 RGB pixels. It supports a SPI interface.
"},{"location":"displays/graph/17-ssd1352/#drawing-shapes","title":"Drawing Shapes","text":"This demo shows how to draw shapes on the display. It starts out with simple lines and rectangles then progresses to more complex shapes such as circles and ellipses.
\"\"\"SSD1351 demo (shapes).\"\"\"\nfrom time import sleep\nfrom ssd1351 import Display, color565\nfrom machine import Pin, SPI\n\n\ndef test():\n \"\"\"Test code.\"\"\"\n # Baud rate of 14500000 seems about the max\n spi = SPI(2, baudrate=14500000, sck=Pin(18), mosi=Pin(23))\n print('spi started')\n display = Display(spi, dc=Pin(17), cs=Pin(5), rst=Pin(16))\n print('display started')\n\n display.clear(color565(64, 0, 255))\n sleep(1)\n\n display.clear()\n\n display.draw_hline(10, 127, 63, color565(255, 0, 255))\n sleep(1)\n\n display.draw_vline(10, 0, 127, color565(0, 255, 255))\n sleep(1)\n\n display.fill_hrect(23, 50, 30, 75, color565(255, 255, 255))\n sleep(1)\n\n display.draw_hline(0, 0, 127, color565(255, 0, 0))\n sleep(1)\n\n display.draw_line(127, 0, 64, 127, color565(255, 255, 0))\n sleep(2)\n\n display.clear()\n\n coords = [[0, 63], [78, 80], [122, 92], [50, 50], [78, 15], [0, 63]]\n display.draw_lines(coords, color565(0, 255, 255))\n sleep(1)\n\n display.clear()\n display.fill_polygon(7, 63, 63, 50, color565(0, 255, 0))\n sleep(1)\n\n display.fill_rectangle(0, 0, 15, 127, color565(255, 0, 0))\n sleep(1)\n\n display.clear()\n\n display.fill_rectangle(0, 0, 63, 63, color565(128, 128, 255))\n sleep(1)\n\n display.draw_rectangle(0, 64, 63, 63, color565(255, 0, 255))\n sleep(1)\n\n display.fill_rectangle(64, 0, 63, 63, color565(128, 0, 255))\n sleep(1)\n\n display.draw_polygon(3, 96, 96, 30, color565(0, 64, 255),\n rotate=15)\n sleep(3)\n\n display.clear()\n\n display.fill_circle(32, 32, 30, color565(0, 255, 0))\n sleep(1)\n\n display.draw_circle(32, 96, 30, color565(0, 0, 255))\n sleep(1)\n\n display.fill_ellipse(96, 32, 30, 16, color565(255, 0, 0))\n sleep(1)\n\n display.draw_ellipse(96, 96, 16, 30, color565(255, 255, 0))\n\n sleep(5)\n display.cleanup()\n\n\ntest()\n
"},{"location":"displays/graph/17-ssd1352/#ssd1351-micropython-driver","title":"SSD1351 MicroPython Driver","text":"The driver is here RDagger GitHub
SSD1351 MicroPython Driver on RDagger GitHub
"},{"location":"displays/graph/17-ssd1352/#ssd1351-datasheet","title":"SSD1351 Datasheet","text":"Datasheet on NewHaven Displays
"},{"location":"displays/graph/18-neopixel-matrix/","title":"NeoPixel Matrix Display","text":""},{"location":"displays/graph/18-neopixel-matrix/#introduction","title":"Introduction","text":"This lesson uses MicroPython to control display that uses a 8X32 matrix of WS2812 RGB LEDs to display information. The entire display is controlled by three wires, a ground, +5V, and a serial data signal. We will use the MicroPython builtin NeoPixel library to control the display. You can use many of the programs in the NeoPixel Basics lesson to control the display. The key difference is that we will need to convert matrix coordinates to NeoPixel index numbers.
"},{"location":"displays/graph/18-neopixel-matrix/#purchasing-hardware","title":"Purchasing Hardware","text":"You can purchase a matrix of 8X32 WS2812 RGB LED on eBay for about $12 on eBay or about $100 on Adafruit. They are also available in 16X16 versions and the devices can be chained together to make larger displays. On our version tested here, we have a total of 8*32 = 256 pixels.
"},{"location":"displays/graph/18-neopixel-matrix/#basic-software-setup","title":"Basic Software Setup","text":"We must first create a function that will draw a pixel at a given x and y position. This is complicated by the fact that the matrix is not a regular grid, but rather a grid that is connected in a zig-zag serpentine pattern illustrated below.
Note that the math for doing even and odd columns is different. The even columns are drawn from the top down and the odd columns are drawn from the bottom to the top which is the order the pixels are wired together in the matrix.
To use the functions that draw pixels, we must first create a function that will convert the x and y coordinates to a NeoPixel index. This is done by the following function. We will then pass this function into the library that will draw characters on the screen.
from machine import Pin\nfrom neopixel import NeoPixel\n\nNEOPIXEL_PIN = 0\nROWS = 8\nCOLS = 32\nNUMBER_PIXELS = ROWS * COLS\n# Allocate memory for the NeoPixel matrix\nmatrix = NeoPixel(Pin(NEOPIXEL_PIN), NUMBER_PIXELS)\n\ndef write_pixel(x, y, value):\n if y >= 0 and y < ROWS and x >=0 and x < COLS:\n # odd count rows 1, 3, 5 the wire goes from bottup\n if x % 2: \n strip[(x+1)*ROWS - y - 1] = value \n else: # even count rows, 0, 2, 4 the wire goes from the top down up\n strip[x*ROWS + y] = value\n
"},{"location":"displays/graph/18-neopixel-matrix/#testing-your-write-pixel-function","title":"Testing Your Write Pixel Function","text":"We can then test the function by calling it at four corners with different colors.
# draw four colors at each corner of the matrix\nwrite_pixel(0, 0, (255, 0, 0)) # draw a red pixel at the top left corner\nwrite_pixel(7, 0, (0, 255, 0)) # draw a green pixel at the lower left corner\nwrite_pixel(0, 7, (0, 0, 255)) # draw a blue pixel at the top right corner\nwrite_pixel(7, 7, (255, 255, 255)) # draw a white pixel at the lower right corner\n
"},{"location":"displays/graph/18-neopixel-matrix/#bounce-a-ball","title":"Bounce a Ball","text":"To test the write_pixel() function, lets write a function that will draw a ball at a given x and y position. We will move the ball around the screen and reverse the direction when the ball hits the edge of the screen.
# Bounce a ball around a NeoPixel Matrix\nfrom neopixel import NeoPixel\nfrom utime import sleep\n\nNEOPIXEL_PIN = 0\nROWS = 8\nCOLS = 32\nNUMBER_PIXELS = ROWS * COLS\nstrip = NeoPixel(machine.Pin(NEOPIXEL_PIN), NUMBER_PIXELS)\n\n# matrix = [[0 for _ in range(cols)] for _ in range(rows)]\ndef clear():\n for i in range(0, NUMBER_PIXELS):\n strip[i] = (0,0,0)\n strip.write()\n\ndef write_pixel(x, y, value):\n if y >= 0 and y < ROWS and x >=0 and x < COLS:\n # odd count rows 1, 3, 5 the wire goes from bottup\n if x % 2: \n strip[(x+1)*ROWS - y - 1] = value \n else: # even count rows, 0, 2, 4 the wire goes from the top down up\n strip[x*ROWS + y] = value\n\ndef show():\n strip.write()\n\nbrightness=1\nx=0\ny=0\ndx = 1\ndy = 1\ncounter = 0\nwhile True:\n if x <= 0:\n dx = 1\n if y <= 0:\n dy = 1\n if x >= COLS-1:\n dx = -1\n if y >= ROWS-1:\n dy = -1\n print(x,y)\n if counter < 100:\n write_pixel(x, y, (brightness,0,0)) # blue\n elif counter < 200:\n write_pixel(x, y, (0,brightness,0)) # blue\n elif counter < 300:\n write_pixel(x, y, (0,0,brightness)) # blue\n show()\n x += dx\n y += dy\n counter += 1\n if counter > 300:\n counter = 0\n if not counter % 150:\n x += 1\n sleep(.1)\n
"},{"location":"displays/graph/18-neopixel-matrix/#bitmap-library","title":"Bitmap LIbrary","text":"# MicroPython basic bitmap font renderer.\n# Author: Tony DiCola\n# License: MIT License (https://opensource.org/licenses/MIT)\ntry:\n import ustruct\nexcept ImportError:\n import struct as ustruct\n\n\nclass BitmapFont:\n\n def __init__(self, width, height, pixel, font_name='font5x8.bin'):\n # Specify the drawing area width and height, and the pixel function to\n # call when drawing pixels (should take an x and y param at least).\n # Optionally specify font_name to override the font file to use (default\n # is font5x8.bin). The font format is a binary file with the following\n # format:\n # - 1 unsigned byte: font character width in pixels\n # - 1 unsigned byte: font character height in pixels\n # - x bytes: font data, in ASCII order covering all 255 characters.\n # Each character should have a byte for each pixel column of\n # data (i.e. a 5x8 font has 5 bytes per character).\n self._width = width\n self._height = height\n self._pixel = pixel\n self._font_name = font_name\n\n def init(self):\n # Open the font file and grab the character width and height values.\n # Note that only fonts up to 8 pixels tall are currently supported.\n self._font = open(self._font_name, 'rb')\n self._font_width, self._font_height = ustruct.unpack('BB', self._font.read(2))\n\n def deinit(self):\n # Close the font file as cleanup.\n self._font.close()\n\n def __enter__(self):\n self.init()\n return self\n\n def __exit__(self, exception_type, exception_value, traceback):\n self.deinit()\n\n def draw_char(self, ch, x, y, *args, **kwargs):\n # Don't draw the character if it will be clipped off the visible area.\n if x < -self._font_width or x >= self._width or \\\n y < -self._font_height or y >= self._height:\n return\n # Go through each column of the character.\n for char_x in range(self._font_width):\n # Grab the byte for the current column of font data.\n self._font.seek(2 + (ord(ch) * self._font_width) + char_x)\n line = ustruct.unpack('B', self._font.read(1))[0]\n # Go through each row in the column byte.\n for char_y in range(self._font_height):\n # Draw a pixel for each bit that's flipped on.\n if (line >> char_y) & 0x1:\n self._pixel(x + char_x, y + char_y, *args, **kwargs)\n\n def text(self, text, x, y, *args, **kwargs):\n # Draw the specified text at the specified location.\n for i in range(len(text)):\n self.draw_char(text[i], x + (i * (self._font_width + 1)), y,\n *args, **kwargs)\n\n def width(self, text):\n # Return the pixel width of the specified text message.\n return len(text) * (self._font_width + 1)\n
"},{"location":"displays/graph/18-neopixel-matrix/#full-code","title":"Full Code","text":"# LED Matrix message scroller demo.\n\nimport bitmapfont\nimport machine\nimport utime\nfrom neopixel import NeoPixel\n\nNEOPIXEL_PIN = 0\nROWS = 8\nCOLS = 32\nNUMBER_PIXELS = ROWS * COLS\nmatrix = NeoPixel(machine.Pin(NEOPIXEL_PIN), NUMBER_PIXELS)\n\ndef fill(val):\n for i in range(0, NUMBER_PIXELS):\n matrix[i] = val\n\n# Configuration:\nDISPLAY_WIDTH = 32 # Display width in pixels.\nDISPLAY_HEIGHT = 8 # Display height in pixels.\nSPEED = 20.0 # Scroll speed in pixels per second.\n\ndef show():\n matrix.write()\n\ndef write_pixel_value(x, y, value):\n if y >= 0 and y < ROWS and x >=0 and x < COLS:\n # odd count rows 1, 3, 5 the wire goes from bottup\n if x % 2: \n matrix[(x+1)*ROWS - y - 1] = value \n else: # even count rows, 0, 2, 4 the wire goes from the top down up\n matrix[x*ROWS + y] = value\n\ndef write_pixel(x, y):\n write_pixel_value(x, y, (1,1,2))\n\ndef scroll_text(message):\n\n with bitmapfont.BitmapFont(DISPLAY_WIDTH, DISPLAY_HEIGHT, write_pixel) as bf:\n # Global state:\n pos = DISPLAY_WIDTH # X position of the message start.\n message_width = bf.width(message) # Message width in pixels.\n last = utime.ticks_ms() # Last frame millisecond tick time.\n speed_ms = SPEED / 1000.0 # Scroll speed in pixels/ms.\n # Main loop:\n while True:\n # Compute the time delta in milliseconds since the last frame.\n current = utime.ticks_ms()\n delta_ms = utime.ticks_diff(current, last)\n last = current\n # Compute position using speed and time delta.\n pos -= speed_ms*delta_ms\n if pos < -message_width:\n pos = DISPLAY_WIDTH\n # Clear the matrix and draw the text at the current position.\n fill((0,0,0))\n bf.text(message, int(pos), 0)\n # Update the matrix LEDs.\n show()\n # Sleep a bit to give USB mass storage some processing time (quirk\n # of SAMD21 firmware right now).\n utime.sleep_ms(20)\n\nwrite_pixel(0,0)\nshow()\n#scroll_text('Dan Loves Ann!')\nscroll_text('MicroPython Rocks')\n
"},{"location":"displays/graph/18-neopixel-matrix/#references","title":"References","text":""},{"location":"displays/graph/19-wiring-harness/","title":"Display Wiring Harness","text":"Unlike simple sensors that only have a few wires, displays have up to seven wires that need to be connected. This can be tricky when we use breadboards where we can accidentally pull one wire out.
To keep our displays running reliably, we can use a 20 cm ribbon cable and some hot glue to make a connector that is easy to hook up. It will be very reliable.
We start by purchasing some 20 cm long Male-Female Dupont ribbon connectors from eBay. The price should be about $8 for 120 connectors. Make sure to get the Male-Female version.
We then will separate 7 of these wires making sure to put the black and red colors in the GND and VCC edge of the group of wires.
You can see a close-up of each of the colors and their connections in the picture below.
At the other end of the cable, we need to make a small change in the order of the cable. Here are the changes:
We can then plug this group of wires directly into the breadboard from breadboard rows 3 to 9. This is shown below.
We designed these connections with the following rules:
We have found that once we create these cable assemblies with hot glue to keep the pins in the right order it makes it much easier to connect the displays.
Warning
Note that we still MUST make sure that the black wire in the wiring harness is connected to the GND. It is easy to get the cable reversed so make sure to double-check the cable orientation before you use it.
"},{"location":"displays/graph/19-wiring-harness/#cable-wiring-diagram","title":"Cable Wiring Diagram","text":"Here is the detailed wiring diagram showing the wires as they route from the back of the OLED display to the pins on the breadboard:
"},{"location":"displays/graph/19-wiring-harness/#sample-python-code","title":"Sample Python Code","text":"from machine import Pin\n\n# Customize these GPIO numbers for your layout\n# Note these are not breadboard row numbers\n# The breadboard row numbers are 4,5,6,7 and 9 with GND on row 8\nSCL_PIN = 2\nSDA_PIN = 3\nRES_PIN = 4\nDC_PIN = 5\nCS_PIN = 6\n\n# create the Pin objects\nscl=Pin(SCL_PIN)\nsda=Pin(SDA_PIN)\nres=Pin(RES_PIN)\ndc=Pin(DC_PIN)\ncs = Pin(CS_PIN)\n\nspi=machine.SPI(0, sck=scl, mosi=sda)\noled = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, dc, res, cs)\n
"},{"location":"displays/graph/19-wiring-harness/#building-a-harness-for-the-cytron-maker-pi-rp2040-board","title":"Building A Harness for the Cytron Maker Pi RP2040 Board","text":"We can also make a display harness for the Cytron Maker Pi RP2040 Board. To do this we will need to use three grove connectors. We use all four wires of the first Grove connector, two of the data signals on the second and just a single wire on the third Grove connector. This connector is shown below.
The MicroPython code for this harness is the following:
from machine import Pin\n\n# Customize these GPIO pin numbers for your layout\nSCL_PIN = 2\nSDA_PIN = 3\nRES_PIN = 4\nDC_PIN = 5\nCS_PIN = 16\n\n# create the Pin objects\nscl=Pin(SCL_PIN)\nsda=Pin(SDA_PIN)\nres=Pin(RES_PIN)\ndc=Pin(DC_PIN)\ncs = Pin(CS_PIN)\n\nspi=machine.SPI(0, sck=scl, mosi=sda)\noled = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, dc, res, cs)\n
Note
This code is exactly the same as the Pico version above with the exception of the CS_PIN which was on GPIO 6 but we now moved it to GPIO 16.
"},{"location":"displays/graph/20-seven-segments/","title":"Drawing a Seven Segment Display","text":"https://en.wikipedia.org/wiki/Seven-segment_display
Wikipedia Seven Segment Display
"},{"location":"displays/graph/40-oled-references/","title":"OLED References","text":"In this section cover small non-graphical displays such as LED bar displays, LCD character displays and other displays that don't require a drawing library and support for framebuffers. There is a separate section for graphical displays.
"},{"location":"displays/non-graph/02-led-button/","title":"LED Buttons Lab","text":"LED Button
"},{"location":"displays/non-graph/03-10-bar-leds/","title":"Ten Bar LED Display","text":""},{"location":"displays/non-graph/03-10-bar-leds/#goals-for-the-lesson","title":"Goals for the Lesson","text":"These LED displays can be purchased on eBay for around 40 cents each in quantity 10. These displays are ideal for showing a reading such as a battery charge or a signal strength.
Our goal is to learn how to use python lists to turn on and off a row of 10 LEDs.
"},{"location":"displays/non-graph/03-10-bar-leds/#circuit","title":"Circuit","text":"The LEDs come in a dual-in-line package with each of the LEDs connected by the pins aligned across the package.
In the circuit below, I connected the positive (anode) of each LED to a GPIO pin and the negative (cathode) through a 330-ohm resistor to the GND rail of the solderless breadboard.
Note! You MUST use a current limiting resistor or you will burn out the LED.
One end of each of the bars will go to one of the power rails and the other to a GIPO pin. I used the pis on the lower part of the Raspberry Pi Pico for this demo.
"},{"location":"displays/non-graph/03-10-bar-leds/#programming","title":"Programming","text":"We will create a list that has each of the GPIO pins for output.
pin_ids = [12,13,14,15,20,19,18,17,16]\n
For each pin on this list, we will create a new list that contains the pin object that we can turn on or off.
from machine import Pin\nfrom utime import sleep\n\npin_ids = [12,13,14,15,20,19,18,17,16]\npins = []\npin_ids\nfor i in pin_ids:\n pins.append(machine.Pin(pin_ids[i], machine.Pin.OUT))\n
We will use this same preamble code in all our examples.
"},{"location":"displays/non-graph/03-10-bar-leds/#code-to-blink-all-10-leds","title":"Code to Blink all 10 LEDs","text":"from machine import Pin\nfrom utime import sleep\n\npin_ids = [12,13,14,15,20,19,18,17,16]\npins = []\npin_ids\nfor i in pin_ids:\n pins.append(machine.Pin(pin_ids[i], machine.Pin.OUT))\n\ndelay = .5\nwhile True:\n # turn all the pins on\n for pin in pins:\n pins.on()\n sleep(delay) # wait\n # turn all the pins off\n for pin in pins:\n pins[i].off()\n sleep(delay)\n
"},{"location":"displays/non-graph/03-10-bar-leds/#sample-running-lights-example","title":"Sample Running Lights Example","text":"The \"running lights\" pattern gives the impression that there is a red object that is moving up and down a row. We do this by successively turning on adjacently LEDs and then turning them off. This give the illusion of motion.
from machine import Pin\nfrom utime import sleep\n\npin_ids = [12,13,14,15,20,19,18,17,16]\npins = []\n\nfor i in range(0, 9):\n pins.append(machine.Pin(pin_ids[i], machine.Pin.OUT))\n\ndelay = .1\nwhile True:\n for i in range(0, 9):\n pins[i].on()\n sleep(delay)\n pins[i].off()\n for i in range(8, 1, -1):\n pins[i].on()\n sleep(delay)\n pins[i].off()\n
"},{"location":"displays/non-graph/03-10-bar-leds/#swipe","title":"swipe","text":"The swipe pattern turns each LED on but keeps it on until the direction is reversed.
"},{"location":"displays/non-graph/03-10-bar-leds/#adding-a-binary-counter-patterns","title":"Adding a Binary Counter Patterns","text":"We can also create another patten that will demonstrate binary counting. In this pattern, the least significant bit flickers on and off. For each cycle the adjacent pixel toggles once. The happens for each adjacent pixel. The most significant bit will only change every 1024 cycles of the least significant bit.
"},{"location":"displays/non-graph/04-8x8-led-matrix/","title":"MAX7219 8x8 LED Matrix","text":"This is a low-cost ($3) and easy-to-program device that is perfect for small projects that don't need a full graphical display. You will be surprised at how creative our students are with just an 8x8 display!
eBay Search for \"MAX7219 8x8 matrix\"
The device comes with five connectors:
We can communicate with the device using the standard SPI interface. There is also an 8x8 driver supplied by Mike Causer Here is an excerpt of how we configured the driver to use a single display:
from machine import SPI, Pin\nimport max7219\nfrom utime import sleep\nCLOCK_PIN = 2\nDATA_PIN = 3\nCS_PIN = 4\nspi0=SPI(0,baudrate=10000000, polarity=1, phase=0, sck=Pin(CLOCK_PIN), mosi=Pin(DATA_PIN))\ncs = Pin(CS_PIN, Pin.OUT)\nmatrix = max7219.Matrix8x8(spi0, cs , 1)\n# display text a x=0, y=0 and state = 1 (on)\nmatrix.text('1234', 0, 0, 1)\nmatrix.show()\n
You can change the last parameter from \"1\" to \"4\" if you have 4 displays wired together:
import max7219\nfrom machine import Pin, SPI\nspi = SPI(1)\nmatrix = max7219.Matrix8x8(spi0, cs , 4)\ndisplay.text('1234',0,0,1)\ndisplay.show()\n
The displays can also be \"cascaded\"
"},{"location":"displays/non-graph/04-8x8-led-matrix/#basic-program","title":"Basic Program","text":"from machine import SPI, Pin\nimport max7219\nfrom utime import sleep\nCLOCK_PIN = 2\nDATA_PIN = 3\nCS_PIN = 4\nspi0=SPI(0,baudrate=10000000, polarity=1, phase=0, sck=Pin(CLOCK_PIN), mosi=Pin(DATA_PIN))\n\ncs = Pin(CS_PIN, Pin.OUT)\n\nmatrix = max7219.Matrix8x8(spi0, cs , 1)\n\nmatrix.text('A', 0, 0, 1)\nmatrix.show()\nsleep(delay_time)\n
"},{"location":"displays/non-graph/04-8x8-led-matrix/#full-demo","title":"Full Demo","text":"from machine import SPI, Pin\nimport max7219\nfrom utime import sleep\nspi0=SPI(0,baudrate=10000000, polarity=1, phase=0, sck=Pin(2), mosi=Pin(3))\n\ncs = Pin(4, Pin.OUT)\n\nmatrix = max7219.Matrix8x8(spi0, cs , 1)\n\ndelay_time = 1\nwhile True:\n # Draw a single character\n\n matrix.text('A', 0, 0, 1)\n matrix.show()\n sleep(delay_time)\n\n # Draw an X in a box\n matrix.fill(0)\n matrix.line(0, 0, 7, 7, 1)\n matrix.show()\n sleep(delay_time)\n\n matrix.line(7, 0, 0, 7, 1)\n matrix.show()\n sleep(delay_time)\n\n matrix.rect(0, 0, 8, 8, 1)\n matrix.show()\n sleep(delay_time)\n matrix.fill(0)\n\n # Smile Face\n matrix.pixel(1, 1, 1)\n matrix.pixel(6, 1, 1)\n matrix.pixel(0, 4, 1)\n matrix.pixel(7, 4, 1)\n matrix.pixel(1, 5, 1)\n matrix.pixel(6, 5, 1)\n matrix.pixel(2, 6, 1)\n matrix.pixel(5, 6, 1)\n matrix.pixel(3, 7, 1)\n matrix.pixel(4, 7, 1)\n matrix.show()\n sleep(delay_time)\n matrix.fill(0)\n matrix.show()\n sleep(delay_time)\n
"},{"location":"displays/non-graph/05-4-digit/","title":"Four Digit LED Display","text":"In this lesson, we will use a 4-digit LED display to create a clock that displays the time of day. These clocks will use Mike Causer's tm1637 library to communicate with the four-digit display. Some of these displays also have a \"colon\" between the hour and minute digits that flashes every second.
You can purchase 4-digit LED displays on eBay for about $2 each.
"},{"location":"displays/non-graph/05-4-digit/#connections","title":"Connections","text":"These displays have four pins:
In our examples, we will connect the power to our 3.3 regulated output of the Pico. We will connect Data to GP0 and Clock to GP1.
The following example
from machine import Pin\nfrom time import sleep\nimport tm1637\n\n# data and clock pins\nDIO_PIN = 0\nCLK_PIN = 1\n\ntm = tm1637.TM1637(clk=Pin(CLK_PIN), dio=Pin(DIO_PIN))\n\n# display \"1234\"\ntm.write([1, 2, 3, 4])\n
The tm.write()
function takes a sequence of numbers and will shifts them in from right to left.
We can create a simple clock by using the localtime()
function when the programs first starts up and then we just update the time after the sleep() functions run for a second. This also can updates the colon between the hours and minutes.
localtime()
returns an array of numbers for date, hour, minute and second. In our example here, we only need the hour and minutes.
# a simple clock that only grabs the time from the server on startup\nimport tm1637\nfrom machine import Pin\nfrom utime import sleep, localtime\n\ntm = tm1637.TM1637(clk=Pin(1), dio=Pin(0))\n\nnow = localtime()\nhour = now[3]\n# use AM/PM 12 hour time\nif hour > 12:\n hour = hour - 12\nminute = now[4]\nsec = now[5]\nprint(hour, ':', minute, ' ', sec, sep='')\n\n# update from the first time\nwhile True:\n # turn the colon on\n tm.numbers(hour,minute,colon=True)\n sleep(0.5)\n # turn the colon off\n tm.numbers(hour,minute,colon=False)\n sleep(0.5)\n sec = sec + 1\n if sec == 60:\n minute = minute + 1\n sec = 0\n if minute == 60:\n hour = hour + 1\n minute = 0\n if hour == 24:\n hour = 0\n
A more accurate version will access the new time from the server every minute.
"},{"location":"displays/non-graph/05-4-digit/#accurate-clock","title":"Accurate Clock","text":"# a more accurate clock that only grabs the time from the server once per minute\nimport tm1637\nfrom machine import Pin\nfrom utime import sleep, localtime\n\nhour = 0\nminute = 0\nsec = 0\n\ndef update_time():\n global hour, minute, second\n now = localtime()\n hour = now[3]\n # use AM/PM\n if hour > 12:\n hour = hour - 12\n minute = now[4]\n sec = now[5]\n\ntm = tm1637.TM1637(clk=Pin(1), dio=Pin(0))\n\nupdate_time()\n# loop every second\nwhile True:\n tm.numbers(hour,minute,colon=True)\n sleep(0.5)\n tm.numbers(hour,minute,colon=False)\n sleep(0.5)\n sec = sec + 1\n if sec == 60:\n # get the new time from the host\n update_time()\n print(hour, ':', minute, ' ', sec, sep='')\n minute = minute + 1\n sec = 0\n if minute == 60:\n hour = hour + 1\n minute = 0\n if hour == 24:\n hour = 0\n
"},{"location":"displays/non-graph/05-4-digit/#references","title":"References","text":"This lesson is for using the LCM1602 I2C LCD interface. It is a popular It has four wires:
The photo above shows the use of a 3.3 to 5v voltage converter. This allows us to use the full 5v to the LCD backlight so we get bright contrast. You can connect the VCC to the 3V3(out) pin but the display will be harder to read.
"},{"location":"displays/non-graph/10-character-lcd-display/#i2c-address-scanner-test","title":"I2C Address Scanner Test","text":"Our first task is to make sure that the 1602 chip's I2C circuits are working. We use the following I2C scanner code to do this.
import machine\nI2C_SDA_PIN = 0\nI2C_SCL_PIN = 1\ni2c=machine.I2C(0,sda=machine.Pin(I2C_SDA_PIN), scl=machine.Pin(I2C_SCL_PIN), freq=400000)\n\nprint('Scanning I2C bus.')\ndevices = i2c.scan() # this returns a list of devices\ndevice_count = len(devices)\nif device_count == 0:\n print('No i2c device found.')\nelse:\n print(device_count, 'devices found.')\nfor device in devices:\n print('Decimal address:', device, \", Hex address: \", hex(device))\n
"},{"location":"displays/non-graph/10-character-lcd-display/#scanner-result","title":"Scanner Result","text":"Scanning I2C bus.\n1 devices found.\nDecimal address: 39 , Hex address: 0x27\n
"},{"location":"displays/non-graph/10-character-lcd-display/#testing-the-lcd","title":"Testing the LCD","text":"from machine import I2C\nfrom lcd_api import LcdApi\nfrom pico_i2c_lcd import I2cLcd\n\nI2C_ADDR = 0x27\nI2C_NUM_ROWS = 2\nI2C_NUM_COLS = 16\n\ni2c = I2C(0, sda=machine.Pin(0), scl=machine.Pin(1), freq=400000)\nlcd = I2cLcd(i2c, I2C_ADDR, I2C_NUM_ROWS, I2C_NUM_COLS) \nlcd.putstr(\"CoderDojo Rocks!\")\n
"},{"location":"displays/non-graph/10-character-lcd-display/#putting-the-device-through-display-option-tests","title":"Putting the Device Through Display Option Tests","text":"Now that we know how to display text on the device, we can learn how other functions work:
import utime\n\nimport machine\nfrom machine import I2C\nfrom lcd_api import LcdApi\nfrom pico_i2c_lcd import I2cLcd\n\nI2C_ADDR = 0x27\nI2C_NUM_ROWS = 2\nI2C_NUM_COLS = 16\n\ndef test_main():\n #Test function for verifying basic functionality\n print(\"Running test_main\")\n i2c = I2C(0, sda=machine.Pin(0), scl=machine.Pin(1), freq=400000)\n lcd = I2cLcd(i2c, I2C_ADDR, I2C_NUM_ROWS, I2C_NUM_COLS) \n lcd.putstr(\"CoderDojo Rocks!\")\n utime.sleep(10)\n lcd.clear()\n count = 0\n while True:\n lcd.clear()\n time = utime.localtime()\n lcd.putstr(\"{year:>04d}/{month:>02d}/{day:>02d} {HH:>02d}:{MM:>02d}:{SS:>02d}\".format(\n year=time[0], month=time[1], day=time[2],\n HH=time[3], MM=time[4], SS=time[5]))\n if count % 10 == 0:\n print(\"Turning cursor on\")\n lcd.show_cursor()\n if count % 10 == 1:\n print(\"Turning cursor off\")\n lcd.hide_cursor()\n if count % 10 == 2:\n print(\"Turning blink cursor on\")\n lcd.blink_cursor_on()\n if count % 10 == 3:\n print(\"Turning blink cursor off\")\n lcd.blink_cursor_off() \n if count % 10 == 4:\n print(\"Turning backlight off\")\n lcd.backlight_off()\n if count % 10 == 5:\n print(\"Turning backlight on\")\n lcd.backlight_on()\n if count % 10 == 6:\n print(\"Turning display off\")\n lcd.display_off()\n if count % 10 == 7:\n print(\"Turning display on\")\n lcd.display_on()\n if count % 10 == 8:\n print(\"Filling display\")\n lcd.clear()\n string = \"\"\n for x in range(32, 32+I2C_NUM_ROWS*I2C_NUM_COLS):\n string += chr(x)\n lcd.putstr(string)\n count += 1\n utime.sleep(2)\n\n#if __name__ == \"__main__\":\ntest_main()\n
"},{"location":"displays/non-graph/10-character-lcd-display/#references","title":"References","text":"MFitzp article on OLED displays
Adafruit SSD1306 Driver
Adafruit LCD Guide
"},{"location":"displays/non-graph/seven-segment/","title":"Sample Seven Segment Display Lab","text":""},{"location":"displays/non-graph/seven-segment/#4-digit-seven-segment-display","title":"4 Digit Seven Segment Display","text":"Make sure to put a current limiting resistor in series with each LED. A 330 ohm resistor is a generally safe value for 5 volt circuits and you can use a 220 ohm resistor for 3.3 volt circuits.
This code was provided by Jaison Miller from his GitHub Repo.
```py from machine import Pin, PWM, Timer import utime
"},{"location":"displays/non-graph/seven-segment/#constants-where-the-pins-are-currently-plugged-into-etc","title":"Constants - where the pins are currently plugged into, etc.","text":"number_bitmaps = { 0: 0b00111111, 1: 0b00000110, 2: 0b01011011, 3: 0b01001111, 4: 0b01100110, 5: 0b01101101, 6: 0b01111101, 7: 0b00000111, 8: 0b01111111, 9: 0b01100111 } segment_masks = { 'a': 0b00000001, 'b': 0b00000010, 'c': 0b00000100, 'd': 0b00001000, 'e': 0b00010000, 'f': 0b00100000, 'g': 0b01000000 } pin_segments = { 'a': 10, 'b': 11, 'c': 12, 'd': 17, 'e': 16, 'f': 13, 'g': 14} pin_others = { 'decimal': 22, 'colon': 6, 'dash': 8 } pin_digits = { 1: 18, 2: 19, 3: 20, 4: 21 } pin_control_others = { 'colon': 27, 'dash': 7 }
"},{"location":"displays/non-graph/seven-segment/#initial-setup-of-the-pins-alternatives-include-using-pwm-to-set-the-brightness","title":"initial setup of the pins, alternatives include using PWM to set the brightness","text":""},{"location":"displays/non-graph/seven-segment/#if-not-using-pwm-then-make-sure-to-use-appropriate-resistors-to-avoid-blowing-the-leds-in-the-display-like-i-have","title":"if not using PWM then make sure to use appropriate resistors to avoid blowing the LEDs in the display (like I have)","text":"segment_maps = {}
for segment, pin in pin_segments.items(): segment_maps[segment] = Pin(pin, Pin.OUT)
other_pin_maps = {}
for feature, pin in pin_others.items(): other_pin_maps[feature] = Pin(pin, Pin.OUT)
digit_maps = {}
for digit, pin in pin_digits.items(): digit_maps[digit] = Pin(pin, Pin.OUT)
other_maps = {}
for feature, pin in pin_control_others.items(): other_maps[feature] = Pin(pin, Pin.OUT)
def render_digit_display(show_digit=1, number=8, decimal=False):
# turn everything off\nfor segment, mask in segment_masks.items():\n segment_maps[segment].value(1)\n\nother_pin_maps['decimal'].value(1)\n\n# turn on the digit required to be displayed\nfor digit, digit_pin in digit_maps.items():\n if show_digit == digit:\n digit_pin.value(1)\n # print(\"\\n\\nDigit: {} - Pin: {} - Number: {}\\n\".format(digit, pin, number))\n else:\n digit_pin.value(0)\n\nutime.sleep(0.001)\n\ndisplay_number_bitmap = number_bitmaps[number]\n\n# check every\nfor segment, mask in segment_masks.items():\n # print(\"segment: {}\\nmask: {}\".format(segment, mask))\n\n if display_number_bitmap & mask == mask:\n # print(\"segment OFF: {}\".format(segment))\n segment_maps[segment].value(0)\n else:\n segment_maps[segment].value(1)\n\n# show decimal\nif decimal:\n other_pin_maps['decimal'].value(0)\nelse:\n other_pin_maps['decimal'].value(1)\n\nutime.sleep(0.001)\n
def render_feature_display(show_colon=False, show_dash=False): if show_colon: other_pin_maps['colon'].value(0) other_maps['colon'].value(1) else: other_pin_maps['colon'].value(0) other_maps['colon'].value(0)
if show_dash:\n other_pin_maps['dash'].value(0)\n other_maps['dash'].value(1)\nelse:\n other_pin_maps['dash'].value(0)\n other_maps['dash'].value(0)\n
while True:
lt_year, lt_month, lt_mday, lt_hour, lt_minute, lt_second, lt_weekday, lt_yearday = utime.localtime()\n\n# testing out all the features of the display\ndigit_1_decimal = (lt_second % 4 == 0)\ndigit_2_decimal = (lt_second % 4 == 1)\ndigit_3_decimal = (lt_second % 4 == 2)\ndigit_4_decimal = (lt_second % 4 == 3)\n\nrender_digit_display(1, lt_minute // 10, digit_1_decimal)\nrender_digit_display(2, lt_minute % 10, digit_2_decimal)\nrender_digit_display(3, lt_second // 10, digit_3_decimal)\nrender_digit_display(4, lt_second % 10, digit_4_decimal)\n\nif (lt_second % 2 == 0):\n render_feature_display(True, False)\nelse:\n render_feature_display(False, True)\n\n ```\n
"},{"location":"getting-started/01-intro/","title":"Welcome to the CoderDojo course on MicroPython","text":"This course is about MicroPython, a variation of the popular Python programming language that is used to program microcontrollers.
"},{"location":"getting-started/01-intro/#intended-audience","title":"Intended Audience","text":"User should be familiar with the the following Python concepts:
imports
Our intent is to allow teachers and mentors around the world to integrate MicroPython into their courses without any fees. We want you to be able to use this content freely with a few conditions: please give us attribution and please don't resell our content for profit.
Note that we use the same Creative Commons licensing as the Raspberry Pi Foundation and the CoderDojo Foundation:
Creative Commons Attribution NonCommercial ShareAlike
This means you are free to reuse and remix this content for non-commercial educational purposes as long as you keep the attribution and preserve the license agreement.
"},{"location":"getting-started/01b-libraries/","title":"What is MicroPython?","text":"MicroPython is an implementation of the Python 3 programming language that includes a small subset of the Python standard library and is optimized to run on microcontrollers. (From micropython.org)
MicroPython was originally created by the Australian programmer and physicist Damien George. It is written in C.
MicroPython is now an OpenSource project and the source code is available in GitHub.
"},{"location":"getting-started/01b-libraries/#micropython-libraries","title":"Micropython Libraries","text":"When you start up your IDE, it may have a list of python modules built in. You can list the current modules you have installed by running the help('modules')
command.
help('modules')\n
"},{"location":"getting-started/01b-libraries/#micropython-builtin-functions","title":"MicroPython Builtin Functions","text":"MicroPython is designed to run quickly in a small memory system. So it has trimmed down many of the standard Python libraries to fit the needs of microcontrollers. Most of these libraries start with the letter \"u\" so that you are aware they are designed to run on microcontrollers.
cmath \u2013 mathematical functions for complex numbers\ngc \u2013 control the garbage collector\nmath \u2013 mathematical functions\nuarray \u2013 arrays of numeric data\nuasyncio \u2014 asynchronous I/O scheduler\nubinascii \u2013 binary/ASCII conversions\nucollections \u2013 collection and container types\nuerrno \u2013 system error codes\nuhashlib \u2013 hashing algorithms\nuheapq \u2013 heap queue algorithm\nuio \u2013 input/output streams\nujson \u2013 JSON encoding and decoding\nuos \u2013 basic \u201coperating system\u201d services\nure \u2013 simple regular expressions\nuselect \u2013 wait for events on a set of streams\nusocket \u2013 socket module\nussl \u2013 SSL/TLS module\nustruct \u2013 pack and unpack primitive data types\nusys \u2013 system specific functions\nutime \u2013 time related functions\nuzlib \u2013 zlib decompression\n_thread \u2013 multithreading support\n
"},{"location":"getting-started/01b-libraries/#micropython-specific-libraries","title":"MicroPython Specific Libraries","text":"btree \u2013 simple BTree database\nframebuf \u2014 frame buffer manipulation\nmachine \u2014 functions related to the hardware\nmicropython \u2013 access and control MicroPython internals\nnetwork \u2014 network configuration\nubluetooth \u2014 low-level Bluetooth\nucryptolib \u2013 cryptographic ciphers\nuctypes \u2013 access binary data in a structured way\n
"},{"location":"getting-started/01b-libraries/#adding-a-module","title":"Adding a module","text":"When you are using python and you attempt to use a module that python can't find you will get an error. You must then use the python pip
installer tool to add the new library.
Filter Only MicroPython Libraries
"},{"location":"getting-started/01b-libraries/#full-list-of-modules","title":"Full List of modules","text":"ESP-test audioop filecmp random\n__future__ base64 fileinput re\n_abc bcrypt fnmatch readline\n_ast bdb formatter reedsolo\n_asyncio binascii fractions reprlib\n_bisect binhex ftplib resource\n_blake2 bisect functools rlcompleter\n_bootlocale bitstring gc runpy\n_bz2 blink-builtin-led genericpath sched\n_cffi_backend brain_argparse getopt secrets\n_codecs brain_attrs getpass select\n_codecs_cn brain_builtin_inference gettext selectors\n_codecs_hk brain_collections glob send2trash\n_codecs_iso2022 brain_crypt grp serial\n_codecs_jp brain_curses gzip setuptools\n_codecs_kr brain_dataclasses hashlib sh1106\n_codecs_tw brain_dateutil heapq sh1106-test\n_collections brain_fstrings hmac shelve\n_collections_abc brain_functools html shlex\n_compat_pickle brain_gi http shutil\n_compression brain_hashlib i2c-display signal\n_contextvars brain_http i2c-scanner site\n_crypt brain_io i2c_lcd six\n_csv brain_mechanize i2clcd smtpd\n_ctypes brain_multiprocessing imaplib smtplib\n_ctypes_test brain_namedtuple_enum imghdr sndhdr\n_curses brain_nose imp socket\n_curses_panel brain_numpy_core_fromnumeric importlib socketserver\n_datetime brain_numpy_core_function_base inspect spi-debug\n_dbm brain_numpy_core_multiarray io sqlite3\n_decimal brain_numpy_core_numeric ipaddress sre_compile\n_dummy_thread brain_numpy_core_numerictypes isort sre_constants\n_elementtree brain_numpy_core_umath itertools sre_parse\n_functools brain_numpy_ndarray jedi ssl\n_hashlib brain_numpy_random_mtrand json stat\n_heapq brain_numpy_utils keyword statistics\n_imp brain_pkg_resources lazy_object_proxy string\n_io brain_pytest led-strip stringprep\n_json brain_qt lib2to3 struct\n_locale brain_random linecache subprocess\n_lsprof brain_re list-modules sunau\n_lzma brain_six locale symbol\n_markupbase brain_ssl logging symtable\n_md5 brain_subprocess lzma sys\n_multibytecodec brain_threading macpath sysconfig\n_multiprocessing brain_typing mailbox syslog\n_opcode brain_uuid mailcap tabnanny\n_operator builtins marshal tarfile\n_osx_support bz2 math telnetlib\n_pickle cProfile mccabe tempfile\n_posixsubprocess calendar mimetypes termios\n_py_abc certifi mmap test\n_pydecimal cffi modulefinder textwrap\n_pyio cgi multiprocessing this\n_queue cgitb mypy thonny\n_random chunk mypy_extensions threading\n_scproxy clonevirtualenv mypyc time\n_sha1 cmath nacl timeit\n_sha256 cmd netrc tkinter\n_sha3 code nis token\n_sha512 codecs nntplib tokenize\n_signal codeop ntpath toml\n_sitebuiltins collections nturl2path trace\n_socket colorsys numbers traceback\n_sqlite3 compileall opcode tracemalloc\n_sre concurrent operator tty\n_ssl configparser optparse turtle\n_stat contextlib os turtledemo\n_string contextvars paramiko typed_ast\n_strptime copy parser types\n_struct copyreg parso typing\n_symtable crypt pathlib typing_extensions\n_sysconfigdata_m_darwin_darwin cryptography pdb unicodedata\n_testbuffer csv pickle unittest\n_testcapi ctypes pickletools urllib\n_testimportmultiple curses pip uu\n_testmultiphase dataclasses pipenv uuid\n_thread datetime pipes venv\n_threading_local dbm pkg_resources virtualenv\n_tkinter decimal pkgutil virtualenv_support\n_tracemalloc difflib platform warnings\n_uuid dir-example plistlib wave\n_warnings dis poplib weakref\n_weakref distutils posix webbrowser\n_weakrefset doctest posixpath websockets\n_xxtestfuzz docutils pprint wheel\nabc dummy_threading profile wrapt\naifc easy_install pstats wsgiref\nantigravity ecdsa pty xdrlib\nargparse email ptyprocess xml\narray encodings pwd xmlrpc\narray-test ensurepip py_compile xxlimited\nast enum pyclbr xxsubtype\nastroid errno pycparser zipapp\nasttokens espefuse pydoc zipfile\nasynchat espressif pydoc_data zipimport\nasyncio espsecure pyexpat zlib\nasyncore esptool pylint \nat faulthandler queue \natexit fcntl quopri \n
"},{"location":"getting-started/02-boards/","title":"Micropython Boards","text":"Technically, any computer that has at least 16K of RAM can run MicroPython as long as someone has ported the MicroPython runtime to use that instruction set.
"},{"location":"getting-started/02-boards/#raspberry-pi-pico","title":"Raspberry Pi Pico","text":"Most of these lessons use a low-cost ($4 retail list price) Raspberry Pi Pico(../glossary.md#pico). The microcontroller was designed by the Raspberry Pi Foundation specifically to provide a low-cost way for student to learn how to program MicroPython. The Raspberry Pi Foundation has also worked with the Thonny developers to create a simple clean kid-friendly interface that is ideal for beginning students.
"},{"location":"getting-started/02-boards/#esp32","title":"ESP32","text":"The ESP32 is similar to the Raspberry Pi Pico but ESP32 also has both WiFi and bluetooth.
"},{"location":"getting-started/02-boards/#cables","title":"Cables","text":"You will need a USB cable to program your microcontroller. These cables are frequently sold at high margin rates at retail stores. If you plan ahead, you can usually find these cables on eBay for about 50% less. Classroom purchases make this a good option.
"},{"location":"getting-started/02-boards/#getting-machine-statistics","title":"Getting Machine Statistics","text":"import machine\nhelp(machine)\n
"},{"location":"getting-started/02-breadboards/","title":"Breadboards","text":"We use standard solderless mini breadboards in our labs. The breadboards have holes that are spaced 1/10th of an inch apart which is a standard for most electronics in the US.
Our breadboards are usually 1/2 size with 400-ties. They have a central trough and power rails on the left and right edges.
"},{"location":"getting-started/02-breadboards/#breadboard-regions-and-connections","title":"Breadboard Regions and Connections","text":"Learning how a breadboard works is critical for building your projects. In the figure above you will see that there are two types of regions of the breadboard
a, b, c, d and e
are all electrically connected. Within any row, columns f, h, i, j, and k
are also electrically connected. However, there is a gap between columns e and f called the center gap or component slot that parts are usually placed over. Components like buttons and chips usually have their pins straddle the component slot.For most of our labs we place the Pico so that pin 1 of the Pico is in row 1 of the breadboard as in the image below.
This means that the GND connections to the Pico are always in rows 3, 8, 13 and 18 on both sides of the breadboard. One of the ground pins is usually hooked up to the vertical blue power rails on the sides of the breadboard.
"},{"location":"getting-started/02-breadboards/#pico-placement-annotations","title":"Pico Placement Annotations","text":"3V3_EN connects to the on-board SMPS enable pin, and is pulled high (to VSYS) via a 100K resistor. To disable the 3.3V (which also de-powers the RP2040), short this pin low. In effect by making the 3V3_EN LOW you are turning off the Pico.
"},{"location":"getting-started/02-breadboards/#breadboard-connections","title":"Breadboard Connections","text":""},{"location":"getting-started/02-esp32/","title":"ESP32 TTGO","text":"The ESP32 is a low-cost (under $10) microcontroller with both built-in WiFi and Bluetooth. This lab demonstrates using a version of the ESP32 that includes an integrated display.
"},{"location":"getting-started/02-esp32/#step-1-install-the-usb-to-uart-bridge-vcp-drivers","title":"Step 1: Install the USB to UART Bridge VCP Drivers","text":"Follow the directions here:
https://www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers
Test this by running the ``ls -l /dev/cu*``` and verify you see:
/dev/cu.SLAB_USBtoUART
If you don't see this try to reboot.
Mac: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/establish-serial-connection.html https://github.com/loboris/MicroPython_ESP32_psRAM_LoBo/raw/master/MicroPython_BUILD/firmware/MicroPython_LoBo_esp32_all.zip
"},{"location":"getting-started/02-esp32/#step-2-create-a-python-conda-environment-for-esp32","title":"Step 2: Create a Python Conda Environment for ESP32","text":"This is so we don't mess up other Python projects on your system.
conda create -n esp32 python=3\nconda activate esp32\n
"},{"location":"getting-started/02-esp32/#step-3-install-the-esptool","title":"Step #3: Install the esptool","text":"$ pip3 install esptool\nCollecting esptool\n Downloading esptool-3.0.tar.gz (149 kB)\n |\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 149 kB 2.9 MB/s \n ...\nInstalling collected packages: pycparser, six, cffi, reedsolo, pyserial, ecdsa, cryptography, bitstring, esptool\nSuccessfully installed bitstring-3.1.7 cffi-1.14.5 cryptography-3.4.6 ecdsa-0.16.1 esptool-3.0 pycparser-2.20 pyserial-3.5 reedsolo-1.5.4 six-1.15.0\n
"},{"location":"getting-started/02-esp32/#step-4-erase-the-old-firmware","title":"Step 4: Erase the Old Firmware","text":"esptool.py --port /dev/cu.SLAB_USBtoUART erase_flash\n
"},{"location":"getting-started/02-esp32/#step-5-download-the-new-firmware","title":"Step 5: Download the New Firmware","text":"Get the ESP32_All prebuilt binary:
https://github.com/loboris/MicroPython_ESP32_psRAM_LoBo/wiki/firmwares
"},{"location":"getting-started/02-esp32/#step-6-reflash-the-new-esp32-firmware","title":"Step 6: Reflash the new ESP32 Firmware","text":"cd esp32_all/\n../flash.sh -p /dev/cu.SLAB_USBtoUART\n
this will run...
$ esptool.py --port /dev/cu.SLAB_USBtoUART erase_flash\nesptool.py v3.0\nSerial port /dev/cu.SLAB_USBtoUART\nConnecting........_\nDetecting chip type... ESP32\nChip is ESP32-D0WDQ6 (revision 1)\nFeatures: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None\nCrystal is 40MHz\nMAC: 24:62:ab:ca:62:84\nUploading stub...\nRunning stub...\nStub running...\nErasing flash (this may take a while)...\nChip erase completed successfully in 2.5s\nHard resetting via RTS pin...\n
"},{"location":"getting-started/02-esp32/#configure-thonny","title":"Configure Thonny","text":"You must configure Thonny to use the ESP32.
"},{"location":"getting-started/02-esp32/#set-the-serial-port","title":"Set the Serial Port","text":"First, you must tell Thonny how to find the right port.
"},{"location":"getting-started/02-esp32/#set-the-interpreter","title":"Set the Interpreter","text":"Next, yo must tell Thonny to use the ESP32 interpreter.
"},{"location":"getting-started/02-esp32/#run-a-test","title":"Run a test","text":"import machine, display, time, math, network, utime\n\n\ntft = display.TFT()\ntft.init(tft.ST7789,bgr=False,rot=tft.LANDSCAPE, miso=17,backl_pin=4,backl_on=1, mosi=19, clk=18, cs=5, dc=16)\n\ntft.setwin(40,52,320,240)\n\nfor i in range(0,241):\n color=0xFFFFFF-tft.hsb2rgb(i/241*360, 1, 1)\n tft.line(i,0,i,135,color) \n\ntft.set_fg(0x000000) \ntft.ellipse(120,67,120,67) \ntft.line(0,0,240,135) \n\ntext=\"CoderDojo Rocks!\" \ntft.text(120-int(tft.textWidth(text)/2),67-int(tft.fontSize()[1]/2),text,0xFFFFFF)\n
You should see the following on the ESP32 display:
"},{"location":"getting-started/02-esp32/#references","title":"References","text":"https://www.instructables.com/TTGO-color-Display-With-Micropython-TTGO-T-display/
"},{"location":"getting-started/02-pi-pico/","title":"Getting Started with the Raspberry Pi RP2040 Microcontroller","text":"The Raspberry Pi RP2040 is a custom silicon microcontroller built by the Raspberry Pi Foundation. The RP2040 is used in the Raspberry Pi Pico with a retail list prices of $4. With 264K SRAM, it has around 100 times the RAM of an Arduino Uno (2K). It is ideal for projects that need more RAM such as projects that require drawing to an OLED display.
\"H\" is with headers.
"},{"location":"getting-started/02-pi-pico/#specs","title":"Specs","text":"The Raspberry Pi Pico uses a USB-micro connector. You can purchase USB Micro-B to USB-A or USB-C (Mac) cables on e-bay for under $2 or for $5 at Microcenter. - image from ebay
"},{"location":"getting-started/02-pi-pico/#pico-pinout","title":"Pico Pinout","text":"The pinout diagram for the Raspberry Pi Pico is shown below.
It features: * 26 \u00d7 multi-function GPIO pins * 2 \u00d7 SPI, 2 \u00d7 I2C, 2 \u00d7 UART, 3 \u00d7 12-bit ADC, 16 \u00d7 controllable PWM
Raspberry Pi Pico\u2019s 40 pins with pin 1 in the upper right corner with the USB connector at the top. The pin numbers are incremented as you go counterclockwise around the board. You go down the left side and then continue up on the right side until you get to pin 40 in the upper right corner.
When you program the Pico, you use the machine.Pin()
but you always use the GP* number, never the pin number on the board pin numbers.
The diagram above shows the top view where pins 1, 2 and 40 are printed next to the pins.
Next to each pin is the primary label of what the pin does. Pins 3, 8, 13, 18, 23, 28, 33 and 38 with the black background are all GND pins.
Pins are numbered 0-29, and 26-29 have ADC capabilities Pin IO modes are: Pin.IN, Pin.OUT, Pin.ALT Pin pull modes are: Pin.PULL_UP, Pin.PULL_DOWN
Label Name Description V3 3.3 volts power A source of 3.3 V power, the same voltage your Pico runs at internally, generated from the VSYS input. This power supply can be switched on and off using the 3V3_EN pin above it, which also switches your Pico off. VSYS ~2-5 volts power A pin directly connected to your Pico\u2019s internal power supply, which cannot be switched off without also switching Pico off. VBUS 5 volts power A source of 5 V power taken from your Pico\u2019s micro USB port, and used to power hardware which needs more than 3.3 V. GND 0 volts ground A ground connection, used to complete a circuit connected to a power source. Several of these pins are dotted around your Pico to make wiring easier. GPxx General-purpose input/output pin number \u2018xx The GPIO pins available for your program, labelled \u2018GP0\u2019 through to \u2018GP28\u2019. GPxx_ADCx General-purpose input/output pin number \u2018xx\u2019, with analogue input number \u2018x\u2019 A GPIO pin which ends in \u2018ADC\u2019 and a number can be used as an analogue input as well as a digital input or output \u2013 but not both at the same time. ADC_VREF Analogue-to-digital converter (ADC) voltage reference A special input pin which sets a reference voltage for any analogue inputs. AGND Analogue-to-digital converter (ADC) 0 volts ground A special ground connection for use with the ADC_VREF pin. RUN Enables or disables your Pico The RUN header is used to start and stop your Pico from another microcontroller."},{"location":"getting-started/02-pi-pico/#steps-to-get-micropython-running-on-the-mac","title":"Steps To Get Micropython Running on the Mac","text":"Thonny is a free lightweight Python development tool.
Downloading 465408 bytes from https://github.com/raspberrypi/micropython/releases/download/pico-20210120/pico_micropython_20210121.uf2\nWriting to /Volumes/RPI-RP2/firmware\n100%\nWaiting for the port...\nFound 2e8a:0005 at /dev/cu.usbmodem0000000000001\n\nDone!\n
"},{"location":"getting-started/02-pi-pico/#getting-the-bootloader-running-from-the-thonny-python-shell","title":"Getting The Bootloader Running from the Thonny Python Shell","text":"Although you can hold down the BOOTSEL button as you are plugging in the Pico, there is a much easier way. Just type the following into the Thonny shell:
machine.bootloader()\n
This will make the Pico go into the Bootloader Mode and mount the file system. You can then copy the bootloader file using the drag-and-drop from your file system or use a UNIX copy command. Once the copy is finished the Pico will automaticaly restart using the new uf2 file.
"},{"location":"getting-started/02-pi-pico/#using-the-onboard-led","title":"Using the Onboard LED","text":"from machine import Pin\nimport utime\nled_onboard = machine.Pin(25, machine.Pin.OUT)\nled_onboard.value(1)\n
from machine import Pin\nimport utime\n\n# right uppermost pin with USB on the left\nled = Pin(16, Pin.OUT)\nled.low()\nwhile True:\n led.toggle()\n utime.sleep(1)\n
Press the Play Button"},{"location":"getting-started/02-pi-pico/#adding-gnd-markers","title":"Adding GND Markers","text":"One of the key disadvantages
"},{"location":"getting-started/02-pi-pico/#references","title":"References","text":""},{"location":"getting-started/02-pi-pico/#getting-started-guide","title":"Getting Started Guide","text":"Raspberry Pi Getting Started
"},{"location":"getting-started/02-pi-pico/#micropython-rp2040-reference","title":"MicroPython RP2040 Reference","text":"MicroPython RP2040 Quick Reference - this web page has details on how MicroPython was ported to the RP2040 Microcontroller.
"},{"location":"getting-started/02-pi-pico/#book-pdf","title":"Book PDF","text":"Raspberry Pi Book PDF Download from HackSpace Commons Attribution-NonCommercial-ShareAlike 3.0 Unported (CC BY-NC-SA 3.0)
"},{"location":"getting-started/02c-thonny/","title":"Thonny Python IDE","text":"A lightweight Python integrated development environment (IDE) that is ideal for beginners writing simple Python programs for first-time users. It has been modified to work well with the Raspberry Pi Pico. It supports different ways of stepping through the code, step-by-step expression evaluation, detailed visualization of the call stack and a mode for explaining the concepts of references and heap.
We strongly suggest that classes begin with Thonny for the first several weeks. As students want to do more complex functions such as build automatic deployment scripts other IDEs are more appropriate.
Thonny 3.3.3 (2021-01-21) was the first version to support the Raspberry Pi Pico. There have also been several enhancements since that release. For a release history, see the Thonny Release History. We suggest checking this link monthly for updates.
"},{"location":"getting-started/02c-thonny/#installing-thonny","title":"Installing Thonny","text":"The best way to install Thonny is to go to the Thonny website an look for the \"Download\" area for your operating system. That link is here:
https://thonny.org/
Make sure you upgrade to the latest version of Thonny if you already have a Thonny installed on your computer.
You can find more tips on getting started with Thonny on the Raspberry Pi website:
https://projects.raspberrypi.org/en/projects/getting-started-with-the-pico/2
Thonny runs on Mac, Windows and Linux.
"},{"location":"getting-started/02c-thonny/#upgrading-thonny","title":"Upgrading Thonny","text":"Although you can always upgrade Thonny by removing it and reinstalling a new version, on Mac and Linux systems there is an easier method.
Run the following shell command:
sudo yum upgrade thonny\n
or
sudo apt-get upgrade thonny\n
"},{"location":"getting-started/02c-thonny/#running-help","title":"Running help()","text":"You can enter the help() function in the main script area and then press the Play button. This will tell you
MicroPython v1.14 on 2021-02-02; Raspberry Pi Pico with RP2040\nType \"help()\" for more information.\n>>> %Run -c $EDITOR_CONTENT\nWelcome to MicroPython!\n\nFor online help please visit https://micropython.org/help/.\n\nFor access to the hardware use the 'machine' module. RP2 specific commands\nare in the 'rp2' module.\n\nQuick overview of some objects:\n machine.Pin(pin) -- get a pin, eg machine.Pin(0)\n machine.Pin(pin, m, [p]) -- get a pin and configure it for IO mode m, pull mode p\n methods: init(..), value([v]), high(), low(), irq(handler)\n machine.ADC(pin) -- make an analog object from a pin\n methods: read_u16()\n machine.PWM(pin) -- make a PWM object from a pin\n methods: deinit(), freq([f]), duty_u16([d]), duty_ns([d])\n machine.I2C(id) -- create an I2C object (id=0,1)\n methods: readfrom(addr, buf, stop=True), writeto(addr, buf, stop=True)\n readfrom_mem(addr, memaddr, arg), writeto_mem(addr, memaddr, arg)\n machine.SPI(id, baudrate=1000000) -- create an SPI object (id=0,1)\n methods: read(nbytes, write=0x00), write(buf), write_readinto(wr_buf, rd_buf)\n machine.Timer(freq, callback) -- create a software timer object\n eg: machine.Timer(freq=1, callback=lambda t:print(t))\n\nPins are numbered 0-29, and 26-29 have ADC capabilities\nPin IO modes are: Pin.IN, Pin.OUT, Pin.ALT\nPin pull modes are: Pin.PULL_UP, Pin.PULL_DOWN\n\nUseful control commands:\n CTRL-C -- interrupt a running program\n CTRL-D -- on a blank line, do a soft reset of the board\n CTRL-E -- on a blank line, enter paste mode\n\nFor further help on a specific object, type help(obj)\nFor a list of available modules, type help('modules')\n>>>\n
"},{"location":"getting-started/02c-thonny/#save-options","title":"Save Options","text":"You can save a python file in Thonny to either the Pico or to your local computer's file system.
first stop execution of any program you are running.
"},{"location":"getting-started/02c-thonny/#downloading-the-firmware","title":"Downloading the Firmware","text":"After you start up Thonny there will be a button in the lower right corner.
After you click on it you will see the following:
Downloading 465408 bytes from https://github.com/raspberrypi/micropython/releases/download/pico-20210120/pico_micropython_20210121.uf2\nWriting to /Volumes/RPI-RP2/firmware\n100%\nWaiting for the port...\nFound 2e8a:0005 at /dev/cu.usbmodem0000000000001\n\nDone!\n
"},{"location":"getting-started/02c-thonny/#version","title":"Version","text":"After you press play the following will appear in the console.
MicroPython v1.13-290-g556ae7914 on 2021-01-21; Raspberry Pi Pico with RP2040\nType \"help()\" for more information.\n>>> %Run -c $EDITOR_CONTENT\n
"},{"location":"getting-started/02c-thonny/#plotting-values-on-thonny","title":"Plotting Values on Thonny","text":"If you are reading sensor values and want to see a nice plot of the values, you can use Thonny's Plot function to view the values. Simply add numeric print values to your main loop and they will be displayed in the plot window. This is very useful for any analog to digital conversions and can be used as a simple tool to view anomalies in incoming data. For example if you accidentally hook up a potentiometer's positive rail to 3.3OUT instead of the 3.3REF you will see noise in the incoming data caused by spikes on the power rails.
"},{"location":"getting-started/02c-thonny/#background-on-thonny","title":"Background on Thonny","text":"MicroPython was originally developed by Damien George and first released in 2014. However, MicroPython did not have a development environment that was easy for students to use. Thonny was developed to provide an easy to use tool just for MicroPython development. Thonny was created at the University of Tartu Institute of Computer Science in Estonia for this purpose. They continue to support Thonny.
Several feature for Thonny were sponsored by the Raspberry Pi Foundation and we continue to see a close relationship between the Raspberry Pi Foundation and the Thonny development team.
Although the Thonny IDE is a great way for kids to start programming in Python on the Raspberry Pi Pico, it has limited advanced features and no large library of extensions.
For intermediate to advanced Python developers, the Visual Studio Code IDE is a good options when it is used with an appropriate extension such as the Pico Go extension by Chris Wood.
Note that Visual Studio Code is sometimes just called VS Code.
Code extensions provide code auto-completion and allows you to communicate with your Raspberry Pi Pico board using the built-in REPL console. You can a single file on your board, sync your entire project or directly type and execute commands. Because the files are stored on your local computer, it makes it easier to use version control software to allow you to work in teams with remote developers.
"},{"location":"getting-started/02d-vscode/#installing-visual-studio-code","title":"Installing Visual Studio Code","text":"Visual Studio Code runs on Windows, Mac and Linux systems such as the operating systems that run on the Raspberry Pi 3 or 4. You typically need around 2GB of RAM to run VS-Code.
See the VS Code Requirements.
If you have a Raspberry Pi the installation instructions are here.
"},{"location":"getting-started/02d-vscode/#adding-the-pico-go-extension","title":"Adding the Pico Go Extension","text":"After you have installed VS-Code you must download the Pico Go Extension:
Pico Go Quick Start
"},{"location":"getting-started/02d-vscode/#references","title":"References","text":"One of the key values of CoderDojo clubs around the world is to not charge any fees for attending these clubs. Parts need to be purchase by limited funds raised through donations. Club organizers need to be frugal about getting low-cost parts for the participants.
Our experience is that is difficult to get the right parts at low cost using firms that promise quick delivery times of a few days. The lowest price parts often must be purchased weeks in advance from places like China and Taiwan. As a result, clubs that use funds carefully must plan weeks if not months in advance of classes and events. So we strongly suggest bringing an robot part procurement team together two months before you begin to offer robot courses where students can each have their own robots.
"},{"location":"getting-started/03-suggested-parts/#purchasing-the-raspberry-pi-kits","title":"Purchasing The Raspberry Pi Kits","text":"As of June 2021, the Raspberry Pi Pico did not come integrated into low-cost development kits that include robot kit parts. You frequently must to purchase the independent parts yourself.
Here are some of the parts we recommend.
"},{"location":"getting-started/03-suggested-parts/#the-raspberry-pi-pico","title":"The Raspberry Pi Pico","text":"In the US, our best source of these has been Microcenter stores. They sell them for $3.99 and they often have them on sale for $1.99.
Microcenter Pico Part Listing
Microcenter has around 25 Locations in the US.
"},{"location":"getting-started/03-suggested-parts/#usb-cable","title":"USB Cable","text":"For a Mac with USB-C connectors, you will need to get a USB micro to C cable:
We need male breakaway headers with standard 1/10th inch spacing. The Raspberry Pi Pico will need 20 pins on each side. If you get 40-pin versions they can easily be broken in half.
EBay: Ebay 40-Pin Male Header 0.1\" 2.54mm Breadboard PCB Strip Connectors 5pc - these ship from China in a few weeks and you can get five of them for $1 (including shipping).
Amazon: Amazon MCIGICM 10pcs Male Header Pin, 40 Pin Header Strip (2.54 mm) for Arduino Connector
We like the 1/2 size boards that have 400 connection points. We like to mount them with the USB connector at the top with the numbers running down the left side to align with the pin numbers.
EBay: Solderless Breadboard 400 Point Tie Prototype Test Bread Board PCB For Arduino
Amazon: Amazon 4 Pieces for $6
Use 22 gauge wire. Get a large spool of black and red and smaller spools of other colors. We use
"},{"location":"getting-started/03-suggested-parts/#breakable-40-pin-headers","title":"Breakable 40-Pin Headers","text":"If you purchase the Raspberry Pi Pico kit that does not have the header pins soldered in, you can use the following to add your own header pins in so the Pico's can be used with the header pins.
40 pin Breakable Pin Header 2.54mm Single Row Male Header Connector Kit on eBay. I purchased 12 of these for $5 or $4.24 per pack of 12 when I purchase 4 packs.
"},{"location":"getting-started/03-suggested-parts/#male-to-male-breadboard-jumper-wires","title":"Male-to-Male Breadboard Jumper Wires","text":"You can use 22 gauge wire to build your projects, however kids that don't have fine motor skills sometimes get frustrated with raw wires. You can purchase a Jumper Wire Kit with 65 jumpers on EBay for under $2 that has nice plastic ends that are easier to use.
Ebay: 65pcs Jumper Wire cable kit for Solderless Breadboard New - 65 of various lengths for $1.50
Ebay: 65X 65PCS Jumper Wire Cable Kit For Solderless Breadboard
Amazon: ZYAMY 195pcs Breadboard Jumper Wire 12cm 16cm 20cm 24cm Solderless Flexible Breadboard Connecting Line Cables Male to Male Breadboard Jumpers (65pcs/Set x 3) - three packs of 65 for about $8
A B3F Momentary Press Button with a blue cap.
Note the schematic in the lower right corner that shows the internal connections of the button.
We like the B3F-4055 12 x12x7.3mm Tactile Switch Momentary Press Buttons that have small dimples on the bottom that fit into the breadboard center trough. They can be purchased for under 10 cents per unit on eBay. You can by them with our without colored caps. The links below have example listings on eBay.
Although not all of these items are really \"sensors\" (some are displays), these kits provide high value at a low price-per item cost. Note that some of these kits contain tilt switches that contain Mercery. I remove these from the kits.
We use linear 10K potentiometers in many labs and kits. The pre-wired options are very handy but you will need some male-to-male jumpers.
We like these small momentary press buttons because they are easy to mount directly on the breadboard. They provide ideal \"Mode Programming\" buttons to put the microcontroller into a programming mode.
"},{"location":"getting-started/03-suggested-parts/#switches","title":"Switches","text":"These are ideal for turning your project boxes on and off.
"},{"location":"getting-started/03-suggested-parts/#small-096-oled-displays","title":"Small 0.96\" OLED displays","text":"We love these low-cost 128X64 OLED displays. They are bright and draw very little power. There are two different connectors: 4-wire I2C and 7-wire SPI. I would suggest the simpler I2C for most starter projects.
For our robot projects our students like to view the values from a distance. For them we use these $17 OLED displays that are about twice the size.
2.42\" OLED Display wired with SPI
"},{"location":"getting-started/03-suggested-parts/#non-rechargeable-aa-and-aaa-battery-packs","title":"Non-rechargeable AA and AAA Battery Packs","text":""},{"location":"getting-started/03-suggested-parts/#rechargeable-battery-packs","title":"Rechargeable Battery Packs","text":"If you are work on project that need long-lasting portable power such as LED strip costumes, there are a wide variety of long-lasting rechargeable battery packs available from prices around $9 to $15. My favorites are ones that have percentage of power remaining displayed.
"},{"location":"getting-started/03-suggested-parts/#ultrasonic-distance-sensors","title":"Ultrasonic Distance Sensors","text":"These inexpensive \"ping\" sensors are used in many robot projects.
We like two motor three wheel robots in our classrooms. They need a H-Bridge circuit for controlling the motor direction. The popular L293D chip takes four PWM signals and will use these to drive two 3-12v DC motors. The L293D chip can be mounted directly on your breadboard. However, we like the low-cost Mini motor controller boards that are only $2 that also have handy screw headers for easily attaching and removing the motor and power wires.
Here are the specs: Here
600mA output per channel (per motor)
See section 4.4 in the Raspberry Pi Pico Datasheet on the Powerchain - note that the input VSYS, the main system input voltage, is only designed to go up to up to 5.5 volts. VSYS feeds the RVT6150 buck-boost SMPS.
Mini Motor Drive Shield Expansion Board L293D Module For Arduino UNO MEGA2560 R3 $3
Note that the L293D Mini Motor Drive shield also has a voltage regulator that delivers a constant 5 volt signal to the robot microcontroller.
"},{"location":"getting-started/04-power/","title":"Powering Your MicroPython Projects","text":"If you are just using MicroPython to learn how to code you can use the USB connector from your Mac or PC to power your project. However, if you are creating a device that can't be connected to the USB cable such as a robot, then you will need to hook up portable power supply like a battery pack to power your device.
The good news is that most microcontrollers like the Raspberry Pi Pico or ESP32 have many options and they are easy to use. Just be careful about applying too much power since unlike the old 5V Arduino boards, these devices only use 3.3v power supplies. Connecting the Pico to a 4 AA batteries (4 X 1.5 volts = 6 volt total) can damage the Pico microcontroller.
"},{"location":"getting-started/04-power/#power-connectors","title":"Power Connectors","text":"The Raspberry Pi Pico has three important power connectors you should learn about.
There is large and growing market for rechargeable cell-phone power packs that are ideal for applications such as robotics and powering a remote microcontroller for a long time. They can be purchased in many power storage levels from 2500 milliamp hours up to over 1 million milliamp hours.
"},{"location":"getting-started/04-power/#preventing-usb-power-pack-autoshutdown","title":"Preventing USB Power Pack Autoshutdown","text":"The one issue to be aware of with battery packs is that they automatically power down if they don't sense a minimum current being drawn such as about 10 milliamps. In many applications the Pico draws less than that amount. One fix is to simply add LED power indicator that draws 10 milliamps. This will
"},{"location":"getting-started/04-power/#battery-power","title":"Battery Power","text":"3 AA alkaline batteries wired in series provide plenty of power for small Pico-based MicroPython projects. Each battery is 1.5 volts which give a total of 4.5 volts which is well within the maximum power use by the VSYS input on the Pico.
As an alternative, you can also use 4 rechargeable NiCad batteries that have a nominal rating of 1.2 volts each. This is a total of 4.8 volts, which is still under the 5.5 volt limit.
Warning
Do not connect 4 AA batteries directly to VSYS. 6 volts is too high for the Pico's power system and could damage it. Use a voltage regulator such as is found on motor driver boards. Another alternative is to use a DC-to-DC voltage regulator such as a Buck Converter.
"},{"location":"getting-started/04-power/#monitoring-usb-power","title":"Monitoring USB Power","text":"On the Pico, GP24 can be used to indicate if power is being drawn from the USB cable. You can also use this information to change the behavior such as drop into low-power mode when disconnected from a USB source.
See here
Here is some sample MicroPython code that displays this value:
import machine\nimport utime\n\nled_onboard = machine.Pin(25, machine.Pin.OUT)\nUSBpower = machine.Pin(24, machine.Pin.IN) \n\nwhile True:\n led_onboard.value(1)\n utime.sleep(0.5)\n led_onboard.value(0)\n utime.sleep(0.5)\n if USBpower() != 1:\n utime.sleep(1)\n
This program prints out the value of the USB Power indicator.
import machine\nimport utime\n\nled_onboard = machine.Pin(25, machine.Pin.OUT)\nUSBpower = machine.Pin(24, machine.Pin.IN) \n\nif USBpower() = 1:\n print('drawing power from the USB')\nelse\n print('drawing power from VSYS - a battery or external power source')\n
Power consumption when running this code is approximately 0.1W (19mA at 4.99V, so 4 x AA batteries (@ 2,000mAh each) would keep the Pico running for well over 4 days.
"},{"location":"getting-started/04-power/#running-both-usb-and-external-battery-power-on-the-raspberry-pi-pico","title":"Running both USB and External Battery Power on the Raspberry Pi Pico","text":"The battery should provide a voltage greater than 1.8v and less than 5.5v. Importantly if both a battery and a micro USB cable are connected at the same time a Schottky diode should be placed between the battery positive and VSYS [see section 4.4 & 4.5 of the Raspberry Pi Pico Datasheet. As long as the battery voltage is less than that coming in from the USB cable, power will be drawn from the USB supply and not the battery and, when you unplug the Pico from its USB supply, the Pico will keep on running, using power from the battery (and visa versa when you plug it back in).
"},{"location":"getting-started/04-power/#monitoring-batter-power-level-on-the-raspberry-pi-pico","title":"Monitoring Batter Power Level on the Raspberry Pi Pico","text":"You can use one of the three analog to digital converters to allow the Pico to monitor the power remaining in an external battery. For example if you have 3 AA batteries you can connect two 100K ohm resistors in series and connect the top and bottom to the power and ground. Then connect the midpoint to one of the three ADC inputs. This will give you a way to monitor the power remaining in an external battery. A fully charge battery pack voltage such at 4.5 volts will generate a voltage of 1/2 the maximum level. As the voltage drops to 1.8 volts it should display a value of 0%. An OLED can provide an ideal way to display the power level remaining.
"},{"location":"getting-started/06-yd-2040/","title":"The YD-RP2040","text":"The VCC-GND YD-RP2040 is a microcontroller that sells for $5-$10 on e-Bay. It is rumored to come with either 4MB or 16MB flash, but the units I have received only have 2MB.
"},{"location":"getting-started/06-yd-2040/#additions","title":"Additions","text":"The board has a W25Q32 (32M-bit) Serial Flash chip.
From the W25Q32 Datasheet:
It provides a storage solution for systems with limited space, pins and power. The 25Q series offers flexibility and performance well beyond ordinary Serial Flash devices. They are ideal for code shadowing to RAM, executing code directly from Dual/Quad SPI (XIP) and storing voice, text and data. The devices operate on a single 2.7V to 3.6V power supply with current consumption as low as 5mA active and 1\u00b5A for power-down. All devices are offered in space-saving packages.
"},{"location":"getting-started/06-yd-2040/#demo-program","title":"Demo Program","text":"This program shows the blue LED flashing and the NeoPixel cycling through colors. You can press the USR button to change the cycle speed.
"},{"location":"getting-started/06-yd-2040/#references","title":"References","text":"In the past, the memory available in an standard Arduino Uno (2K bytes) was too small to add high quality displays. With the arrival of the ESP32 and the Raspberry Pi Pico this has all changed. These microcontrollers have around 100 times that RAM - typically around 200K bytes. So we are integrating low-cost OLED displays into many of our CoderDojo projects!
"},{"location":"getting-started/10-displays/#display-types","title":"Display Types","text":"There are four main types of display technology that use for small microcontrollers.
240X240 TFT Display
Full Color LCD TFT Display SPI HD 65K Module ST7735
"},{"location":"getting-started/10-displays/#concepts","title":"Concepts","text":"Before you begin to use these displays, there are a few things to understand to use them effectively. Based on your project needs, you can use this knowledge to find the right solution for you.
"},{"location":"getting-started/10-displays/#framebuffers","title":"Framebuffers","text":"A framebuffer is a copy of the display information that is resident within the RAM of the microcontroller. It must be as large as the display. For a 128X64 monochrome display this would be 128 * 64 = 8192 bits or 1,024 bytes (1K). A full color 240X240 TFT which uses 8 bits for red, green and blue would require 3 X 8 X 240 X 240 = 1,382,400 bits or 172K bytes.
Not all all displays need framebuffers. Some displays can take a series of vector drawing commands such as \"draw line\" and \"draw text\". These displays can be useful if you don't have a large amount of RAM.
"},{"location":"getting-started/10-displays/#display-chip-types","title":"Display Chip Types","text":"There are two common versions:
You can usually look on the back of the display device and see what type of check controls your OLED display.
"},{"location":"getting-started/10-displays/#communication-protocols","title":"Communication Protocols","text":"In addition to the multiple types of displays and types of chips driving the displays, there are also two options on how you want to communicate between your microcontroller and the display.
For our beginning labs we will just do some basic drawing. We will start out with just four functions:
Let's assume that we have a four wire OLED that uses the popular SSD1306 chip with 128X64 pixels. We call our oled \"oled\" using the following line:
from ssd1306 import SSD1306_I2C\noled = SSD1306_I2C(128, 64, i2c)\n
Function Description Parameters oled.fill(0) Fill the display with white or black 0=black and 1=white oled.text(\"Hello\", Draw text String, x (horizontal from left edge) and y (vertical from the top)Example: Draw \"Hello World\" 40 over and 10 down. oled.text(\"Hello World!\", 40, 10) show Show the display Send the current frame buffer to the display. You must do this after you make and changes to the Framebuffer. The full program would look like this:
from ssd1306 import SSD1306_I2C\noled = SSD1306_I2C(128, 64, i2c)\noled.fill(0)\noled.text(\"Hello World!\", 0, 0)\noled.show()\n
This would display the following:
"},{"location":"getting-started/10-displays/#full-list-of-drawing-functions","title":"Full list of Drawing Functions","text":"Every drawing library might have slightly different functions. But we can quickly see the functions that we want by using the dir() function on the SSD1306_I2C class.
from ssd1306 import SSD1306_I2C\nprint(dir(SSD1306_I2C))\n
This returns the following list: ['__class__', '__init__', '__module__', '__name__', '__qualname__',\n'__bases__', '__dict__', 'blit', 'fill', 'fill_rect', 'hline',\n'invert', 'line', 'pixel', 'rect', 'scroll', 'text', 'vline',\n'init_display', 'write_cmd', 'show', 'poweroff', 'poweron',\n'contrast', 'write_data']\n
Technically, these are called methods of the SSD1306_I2C class. The ones that begin and end with double underscores are class methods for creating new object instances. The rest of the items on the list are the drawing functions. The following are relevant for the SSD1306_I2C display.
The display has (0,0) in the upper left corner. X is horizontal (width) and Y is vertical (height). The state is 0=off (black) and 1=on (white).
Function Description Example blit fill(state) Fill Fill with black (0) or white(1) fill_rect Fill a rectangle hline(x1, x2, y, state) Draw a horizontal line Draw a horizontal line at the top of the display: oled.hline(0, 0, 127, 1) invert invert the display line(x1,y1,x2,y2) draw a line at any angle Horizontal oled.line(0,0, 127, 63, 1) pixel Draw a single point on the screen rect Draw an empty rectangle scroll Scroll the display text Write text at a point vline Draw a Vertical Line oled.vline(width - 1, 0, height - 1, 1) # right edge init_display Initialize the display write_cmd Write a command to the display show Update the display from the frame buffer poweroff poweron contrast write_data"},{"location":"getting-started/10-displays/#interfaces","title":"Interfaces","text":""},{"location":"getting-started/10-displays/#i2c","title":"I2C","text":"Pros: Simple four wire interface
Pin Purpose Description"},{"location":"getting-started/10-displays/#spi","title":"SPI","text":"Example: 128X64 pixel monochrome displays
"},{"location":"getting-started/10-displays/#types-of-displays","title":"Types of Displays","text":""},{"location":"getting-started/10-displays/#summary-table","title":"Summary Table","text":"Display Type Cost Links Notes"},{"location":"getting-started/10-displays/#lcd","title":"LCD","text":""},{"location":"getting-started/10-displays/#oled","title":"OLED","text":""},{"location":"getting-started/10-displays/#tft-displays","title":"TFT Displays","text":""},{"location":"getting-started/10-displays/#references","title":"References","text":"ST7735 Micropython Driver by Anthony Norman
"},{"location":"intro/01-about/","title":"About MicroPython for Kids","text":"The this site provides a rich collection of resources to teach computational thinking to students from 10 to 16 years old using fun Python programs that control the physical world around us.
The sub-field of computer science that reads sensors and controls lights and motors is called Physical Computing. We cover physical computing in our next section.
"},{"location":"intro/01-about/#content-licenses","title":"Content Licenses","text":"All the content on this website is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike. This means if your preserve the attribution and license you can use the content for free in your classrooms and modify and extend the curriculum to meet your needs. However, you can not charge your students additional feeds for the content or resell the content.
"},{"location":"intro/01-about/#contributing-to-this-website","title":"Contributing to This Website","text":"We invite all students, teachers and mentors to help us build a better website. You can read our publishing process on the CoderDojo Twin Cities Content Authoring Guide.
Please make sure you using original content and avoid using any images that you have not created yourself. It is always a good idea to have a friend check your spelling, typos and links.
There are several ways to contribute to get your content on this website.
"},{"location":"intro/01-about/#git-pull-requests","title":"Git Pull Requests","text":"If you know how, you can student submit a Git Pull Request. This tells our team that you have content to contribute. Don't be scared about learning how to do this. There are lots of examples online and please reach out if you are having trouble. This is our preferred approach, but we realize that the first time you do this there are several things you need to know.
"},{"location":"intro/01-about/#adding-a-new-issues","title":"Adding a New Issues","text":"You can just open a new [Issue](https://github.com/CoderDojoTC/micropython/issues and put your content in Markdown. You will also need to tell us where you store any images and videos. It might take us some time get this content into a new release.
"},{"location":"intro/01-about/#let-us-setup-a-edit-page","title":"Let Us Setup a Edit Page","text":"If learning how to do pull requests is too daunting for you, don't worry! You are not alone. If you want us to setup a web page you can edit using the simple \"Edit\" button on GitHub we can do that for you. Just tell us where you want the page located and give us your GitHub ID and we will set this up. You will need to let us know when your content is ready to be merged in to our releases.
"},{"location":"intro/01-about/#manual-methods","title":"Manual Methods","text":"What if you have an urgent class coming up and don't have time to learn Markdown? If this happens, you can send us your raw content in MS-Word, PowerPoint or a Google Doc. Since we are an all-volunteer organization, we will need time to find a volunteer to convert your content into Markdown. All urgent requests should go to:
info@codesavvy.org
"},{"location":"intro/02-physical-computing/","title":"What is Physical Computing?","text":"Physical Computing is the process of using computers to read data from sensors about the world around us and then taking actions on this incoming data stream. These actions are typically doing things like blinking and LED, moving a motor or updating a display.
"},{"location":"intro/02-physical-computing/#physical-computing-in-teaching-computational-thinking","title":"Physical Computing in Teaching Computational Thinking","text":"Physical computing plays an important role in teaching the core concepts in Computational Thinking. Often times students quickly lose interest when only abstract concepts are used. Physical computing allows direct hands-on experiences that keeps students engaged and gives them immediate feedback. Although our labs start slowly with simply blinking LEDs, they quick move to controlling motors and building robots.
The material in this website is designed to leverage everything we have learned to make our lessons be fun, engaging and allow students to come up with their own ideas for project-based learning.
"},{"location":"intro/02-physical-computing/#why-physical-computing-has-become-so-popular","title":"Why Physical Computing Has Become So Popular","text":"In the past, the chips we used to teach physical computing (called microcontrollers) were slow, had huge memory limitations, and were expensive. They were also hard to program and since we could not use the Python language that is popular in most classrooms today.
This all changed in January of 2021 when the Raspberry Pi Foundation released a $4 microcontroller called the Pico that has 200 times the memory of the most popular microcontroller (The $25 Arduino Uno). Now teachers could purchase an entire classroom full of microcontrollers that were powerful enough to even do machine learning.
One way to measure the cost effectiveness of this system is to compare the cost per kilobyte of RAM. The Arduino Uno was about $12.50 per kilobyte. The new Raspberry Pi Pico, with 264K RAM cost only 2 cents per kilobyte!
There was also one other HUGE advantage of these microcontrollers. They ran Python! Python is the most popular language for students today. There are millions of on-line websites that show kids how to learn Python like our own CoderDojo Beginning Python classes. Teachers now had the big three factors:
But there was a small problem. Although there were millions of sample programs for the old under-powered Arduino microcontrollers, we needed high-quality lessons for our students to learn to use the new microcontrollers. And these lessons need to be flexible so teachers around the world could build new courses out of this content without having to purchase expensive textbooks. That is why this website is so important. We hope you see how excited we are to bring you this new content!
"},{"location":"intro/02-physical-computing/#examples-of-sensors","title":"Examples of Sensors","text":"Here are some sample sensors that we use:
Here are some of the Actuators we use:
In the real world, sensor data can be complex. We often need to look for complex patterns in an incoming data stream. For example, how can we detect complex motion such as gestures from a simple distance measurement? Other questions that we have to consider include:
Left to right: Raspberry Pi Pico, Cytron Maker Pi Nano, Cytron Maker Pi 2040, Cytron Maker Pi Pico, ESP-32, ESP TTGO
This lesson is an overview of microcontrollers and their role in teaching physical computing.
A microcontroller is a small low-cost computer used to control physical devices such as LED, servos and motors. Microcontroller boards typically cost around $4 to $6 and are an ideal way to learn about computer science because you can use them to build fun projects. For example you can control a row of LEDs, move a robot and sense the world around us with a variety of light, sound and motion sensors.
"},{"location":"intro/03-microcontrollers/#types-of-microcontroller-boards-used-in-these-labs","title":"Types of Microcontroller Boards Used in These Labs","text":"We use a variety of microcontroller boards that are based on either the Raspberry Pi RP2040 chip or the ESP32. The boards we use are all low-cost (under $10) but have a variety of on-board devices such as buttons, LEDs, speakers and motor controllers. Which board you use depends on how many of these components you need in your projects. The RP2040 has 264K RAM and the ESP32 has 520K RAM.
Some of the boards are mounted on breadboards and others use Grove connectors to connect sensors and actuators.
"},{"location":"intro/03-microcontrollers/#raspberry-pi-pico","title":"Raspberry Pi Pico","text":"This microcontroller is low cost (retail list price is $4) and can be mounted directly on a breadboard. The Pico has just a single LED and no buttons.
In the past, microcontrollers were difficult for younger students to program. They were also too expensive for every student to purchase and take home. A typical Arduino kit could easily cost over $20 and required you to learn C to program it.
Today, microcontrollers such as the Raspberry Pi Pico and the ESP32 cost as little as four dollars. And these devices are designed to be programmed in Python, the most popular programming language for students.
"},{"location":"intro/03-microcontrollers/#what-is-physical-computing","title":"What is Physical Computing?","text":"Physical Computing is a field of study that can sense and respond to the world around us. Unlike programming a cell phone or a laptop computer, our focus is reading sensor values and quickly responding to changes. Physical Computing is widely used to teach principals of computer science because students can create their own projects and express creativity such a controlling the patterns of lights or creating complex sounds.
"},{"location":"intro/03-microcontrollers/#how-microcontrollers-are-used","title":"How Microcontrollers are Used","text":"Microcontrollers do three things:
Here is a general diagram to think about when you are designing microcontroller systems:
Here is a specific example instance of what inputs and outputs might do.
"},{"location":"intro/03-microcontrollers/#programming-a-microcontroller","title":"Programming a Microcontroller","text":"In this class, we will use the MicroPython to program our Microcontroller.
Our programs will usually have the following structure:
The following is an example code in Micropython:
# Import Section\nimport machine\nimport time\n\n# Setup Section\nled = machine.Pin(16, machine.Pin.OUT)\n\n# Main Loop\nwhile True:\n led.high()\n time.sleep(0.5)\n led.low()\n time.sleep(0.5)\n
Almost all our programs will start with the import machine
line. This tells the system that we need to gather all the libraries that understand our physical machine.
If you couldn't understand the example program - don't worry! We will be going through it in detail as we work on our labs.
"},{"location":"intro/03-microcontrollers/#references","title":"References","text":"This section review several kits for learning MicroPython. Most of them use the RP2040 chip, but there are some that also use the ESP32 when wireless communication is needed.
"},{"location":"kits/01-intro/#solderless-connectors","title":"Solderless Connectors","text":"There are several types of solderless connectors used in these kits. They connect sensors and motors to these kits without the need for soldering. They are ideal for student labs that don't want the fire-hazards associated with soldering or where solderless breadboards and hot-glue is not flexible enough.
These are usually 3 and 4-wire connectors that support analog and digital input and output as well as I2C bus and UART communications. They are typically designed to carry about 1 amp of current.
The following list is not design to be an exhaustive list of all MicroPython development kits available on the market. We focus on value-based kits that will help our students have fun learning computational thinking.
You can also use the MicroPython \"tag\" to search all the kits on the SparkFun site:
https://www.sparkfun.com/categories/tags/micropython
"},{"location":"kits/01-intro/#references","title":"References","text":"Here are kits that we have seen but have not yet evaluated:
"},{"location":"kits/01-intro/#waveshare-picogo-robot","title":"Waveshare PicoGo Robot","text":"PicoGo Mobile Robot is a $43 robot based on Raspberry Pi Pico.
Vilros Getting Started With MicroPython on Raspberry Pi Pico Kit
This kit includes:
The only problem with the parts is the lack of connectors for the potentiometers don't work well directly on the breadboard. You will need to solder wires to use them on the breadboard.
"},{"location":"kits/microbit/","title":"MicroBit","text":"Although the BBC MicroBit is a good educational product for learning MicroPython, we don't use it in our MicroPython courses for the following reason:
The Larson Scanner is a light pattern special effect named after Glen A. Larson. Larson used this pattern to give his Battlestar Galactica Cylon and KITT robot eyes a sense of sentience. See Knight Rider for the backstory.
This project uses a 144 pixel/meter LED strip and a Raspberry Pi Pico to produce this effect.
I used a craft pumpkin from Michaels. I cut a slit in it and used hot-glue to hold the LED strip in place.
"},{"location":"kits/larson-scanner/01-intro/#parts-list","title":"Parts List","text":"This is a screen image from e-bay showing a 1/2 meter of LED strip for $8.
"},{"location":"kits/larson-scanner/01-intro/#sample-code","title":"Sample Code","text":"This code shows a five-pixel wide \"eye\" moving back-an-forth over a 27 pixel strip. There is a central bright red LED surrounded by dimmer red LEDs that move back-and-forth. We are using the NeoPixel library supplied by Bla\u017e Rolih.
The example below has a delay of 1/10th of a second between drawing events. You can make the delay smaller to speed up the speed of the eye movement.
from utime import sleep\n# We are using https://github.com/blaz-r/pi_pico_neopixel\nfrom neopixel import Neopixel\n\nNUMBER_PIXELS = 27\nSTATE_MACHINE = 0\nLED_PIN = 0\n\nstrip = Neopixel(NUMBER_PIXELS, STATE_MACHINE, LED_PIN, \"GRB\")\n\n# Color RGB values\nred = (255, 0, 0)\nred_med = (32, 0, 0)\nred_light = (8, 0, 0)\noff = (0,0,0)\n\ndelay = .1\nwhile True:\n for i in range(2, NUMBER_PIXELS-2):\n strip.set_pixel(i-2, red_light)\n strip.set_pixel(i-1, red_med)\n strip.set_pixel(i, red)\n strip.set_pixel(i+1, red_med)\n strip.set_pixel(i+2, red_light)\n if i > 0: strip.set_pixel(i-3, off)\n strip.show()\n sleep(delay)\n for i in range(NUMBER_PIXELS-4, 1, -1):\n if i < NUMBER_PIXELS-2: strip.set_pixel(i+3, off)\n strip.set_pixel(i-2, red_light)\n strip.set_pixel(i-1, red_med)\n strip.set_pixel(i, red)\n strip.set_pixel(i+1, red_med)\n strip.set_pixel(i+2, red_light)\n strip.show()\n sleep(delay)\n
"},{"location":"kits/larson-scanner/01-intro/#adding-some-color","title":"Adding Some Color","text":"The pattern above is faithful to the original Cylon robot pattern, but to be honest, it is a little boring. We can spruce it up a bit by adding some color and the comet-tail pattern.
This program cycles through a \"moving rainbow\" pattern and then the comet pattern for 10 colors.
from utime import sleep\n# We are using https://github.com/blaz-r/pi_pico_neopixel\nfrom neopixel import Neopixel\n\nNUMBER_PIXELS = 25\nSTATE_MACHINE = 0\nLED_PIN = 0\n\n# The Neopixels on the Maker Pi RP2040 are the GRB variety, not RGB\nstrip = Neopixel(NUMBER_PIXELS, STATE_MACHINE, LED_PIN, \"GRB\")\n\n# Color RGB values\nred = (255, 0, 0)\noff = (0,0,0)\norange = (255, 60, 0) # Gamma corrected from G=128 to be less like yellow\nyellow = (255, 150, 0)\ngreen = (0, 255, 0)\nblue = (0, 0, 255)\ncyan = (255, 0, 255)\nindigo = (75, 0, 130) # purple?\nviolet = (138, 43, 226) # mostly pink\ncolor_names = ('red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet')\nnum_colors = len(color_names)\ncolors = (red, orange, yellow, green, blue, indigo, violet)\n\n# set to be 1 to 100 for percent brightness\nstrip.brightness(100)\n\ndef draw_eye_7(r, g, b):\n for i in range(6, NUMBER_PIXELS): \n strip.set_pixel(i, (r, g, b))\n # step back from the current to 6 back halfing the intensity each time\n for j in range(0,7):\n strip.set_pixel(i-j, (int(r/pow(2,j)), int(g/pow(2,j)), int(b/pow(2,j))))\n if i > 6: strip.set_pixel(i-7, (0,0,0))\n strip.show()\n sleep(delay)\n strip.set_pixel(i, off)\n for i in range(NUMBER_PIXELS-6, 0, -1):\n strip.set_pixel(i, (r, g, b)) \n for j in range(7,0):\n strip.set_pixel(i+j, (int(r/pow(2,j)), int(g/pow(2,j)), int(b/pow(2,j))))\n if i < NUMBER_PIXELS-7: strip.set_pixel(i+7, (0,0,0))\n strip.show()\n sleep(delay)\n\ndef draw_rainbow():\n for i in range(0, NUMBER_PIXELS-7):\n strip.set_pixel(i, violet)\n strip.set_pixel(i+1, indigo)\n strip.set_pixel(i+2, blue)\n strip.set_pixel(i+3, green)\n strip.set_pixel(i+4, yellow)\n strip.set_pixel(i+5,orange)\n strip.set_pixel(i+6, red)\n if i > 6: strip.set_pixel(i-7, (0,0,0))\n strip.show()\n sleep(delay)\n strip.set_pixel(i, off)\n for i in range(NUMBER_PIXELS-7, 1, -1):\n strip.set_pixel(i, red)\n strip.set_pixel(i+1, orange)\n strip.set_pixel(i+2, yellow)\n strip.set_pixel(i+3, green)\n strip.set_pixel(i+4, blue)\n strip.set_pixel(i+5, indigo)\n strip.set_pixel(i+6, violet)\n if i < NUMBER_PIXELS-7: strip.set_pixel(i+7, (0,0,0))\n strip.show()\n sleep(delay)\n\n# delay = .031\n\ndelay = .06\ncolor_index = 0\nwhile True:\n draw_rainbow()\n draw_eye_7(255,0,0) #red\n draw_eye_7(255,60,0) #orange\n draw_eye_7(255,255,0) # yellow\n draw_eye_7(0,255,0) # green\n draw_eye_7(0,0,255) # b;ie\n draw_eye_7(0,255,255) # cyan\n draw_eye_7(75,30,130) # indigo\n draw_eye_7(255,0,255) # violet\n draw_eye_7(255,255,255) # white\n
"},{"location":"kits/larson-scanner/01-intro/#adding-the-cylon-scanner-sounds","title":"Adding the Cylon Scanner Sounds","text":"You can also add the Cylon eye scanner sound by addint a .wav file to the pico and using the playWave library. This is covered in the Sound and Music Play Audio File lesson of this microsite.
"},{"location":"kits/larson-scanner/01-intro/#more-to-explore","title":"More to Explore","text":"The Cytron Nano RP2040 is a low-cost ($9), high-functionality board.
"},{"location":"kits/maker-nano-rp2040/01-intro/#features","title":"features","text":"from machine import Pin # get the Pin function from the machine module\nfrom time import sleep # get the sleep library from the time module\n# this is the built-in green LED on the Pico\nled = machine.Pin(0, machine.Pin.OUT)\n\n# repeat forever\nwhile True:\n led.high() # turn on the LED\n sleep(0.5) # leave it on for 1/2 second\n led.low() # Turn off the LED\n sleep(0.5) # leave it off for 1/2 second\n
"},{"location":"kits/maker-pi-pico/","title":"Cytron Maker Pi Pico","text":"The Cytron Maker Pi Pico is a $9.99 breakout board for the Raspberry Pi Pico with many features.
Cytron Maker Pi Pico Github Repo
"},{"location":"kits/maker-pi-pico/02-running-lights/","title":"Running lights","text":"This program turns on all 24 blue LEDs on the board, one at a time. It then turns them all off.
TODO - record a GIF or video.
import machine\nimport utime\n\n# RUNNING LIGHT\n\nfor i in range(29): # from 0 to 28 \n if i != 23 and i != 24: # pin 23 and 24 are not GPIO pins\n machine.Pin(i,machine.Pin.OUT) # set the pins to output\n\nwhile True:\n for i in range(29): \n if i != 23 and i != 24: \n machine.Pin(i).value(0) # turn off the LED\n utime.sleep(0.1) # sleep for 100ms\n machine.Pin(i).value(1) # turn on the LED\n\n for i in range(28,-1,-1): # from 28 to 0\n if i != 23 and i != 24:\n machine.Pin(i).value(1) # turn on the LED\n utime.sleep(0.1)\n machine.Pin(i).value(0) # turn off the LED\n
"},{"location":"kits/maker-pi-pico/02-running-lights/#references","title":"References","text":"This program was taken from tje Cytron GitHub site here.
"},{"location":"kits/maker-pi-pico/09-micro-sd-card-reader/","title":"Micro SD Card Reader","text":"Secure Digital (SD) is a non-volatile memory card format for use in portable devices such as cameras, MP3 players and portable devices.
On Microcontrollers SD cards are usually access through an SPI interface although there are also devices that use I2C interfaces.
"},{"location":"kits/maker-pi-pico/09-micro-sd-card-reader/#maker-pi-pico-connections","title":"Maker Pi Pico Connections","text":"GPIO Pin SD Mode SPI Mode GP10 CLK SCK GP11 CMD SDI GP12 DAT0 SD0 GP13 DAT1 X GP14 DAT2 X GP15 CD/DAT3 CSn"},{"location":"kits/maker-pi-pico/09-micro-sd-card-reader/#maker-pi-pico-example-code","title":"Maker Pi Pico Example Code","text":""},{"location":"kits/maker-pi-pico/09-micro-sd-card-reader/#pin-definitions","title":"Pin Definitions","text":"# SD Mode Definitions\nSDCARD_CLK = 10\nSDCARD_CMD = 11\nSDCARD_DAT0 = 12\nSDCARD_DAT1 = 13\nSDCARD_DAT2 = 14\nSDCARD_CD_DAT3 = 15\n\n# SPI Mode Definitions\nSDCARD_SCK = 10\nSDCARD_SDI = 11\nSDCARD_SD0 = 12\nSDCARD_X1 = 13\nSDCARD_X2 = 14\nSDCARD_CSn = 15\n
"},{"location":"kits/maker-pi-pico/09-micro-sd-card-reader/#sample-code-for-spi-mode","title":"Sample Code for SPI Mode","text":"import machine, os, sdcard\n\n# Assign chip select (CS) pin (and start it high)\ncs = machine.Pin(15, machine.Pin.OUT)\n# Intialize SPI peripheral (start with 1 MHz)\nspi = machine.SPI(1,\n baudrate=1000000,\n polarity=0,\n phase=0,\n bits=8,\n firstbit=machine.SPI.MSB,\n sck=machine.Pin(10),\n mosi=machine.Pin(11),\n miso=machine.Pin(12))\n# Initialize SD card\nsd = sdcard.SDCard(spi, cs)\n\n# OR this simpler initialization code should works on Maker Pi Pico too...\n#sd = sdcard.SDCard(machine.SPI(1), machine.Pin(15))\n\nos.mount(sd, '/sd')\n# check the content\nos.listdir('/sd')\n\n# try some standard file operations\nfile = open('/sd/test.txt', 'w')\nfile.write('Testing SD card on Maker Pi Pico')\nfile.close()\nfile = open('/sd/test.txt', 'r')\ndata = file.read()\nprint(data)\nfile.close()\n
Results:
Testing SD card on Maker Pi Pico\n
"},{"location":"kits/maker-pi-pico/09-micro-sd-card-reader/#references","title":"References","text":"The Maker Pi PR2040 kit from Cytron Technologies is a $9.90 US kit that is designed to simplify learning robotics using the RP2040 chip. It became available in April of 2021, but demand has been very high and it is out-of-stock on many retailers sites. We can understand this. The Maker Pi PR2040 is the most powerful robotics board we have ever seen for under $10!
The photo above is our base robot kit. It includes the Maker Pi RP2040 board mounted on a standards Smart Car chassis. The image above shows the Time-of-flight distance sensor mounted in the front below the chassis. Note that the batteries are mounted on the bottom of the robot.
Below is the top view of the Cytron Maker Pi RP2040 robotics board.
"},{"location":"kits/maker-pi-rp2040/#features","title":"Features","text":"All our robots are built around a RP2040 and a motor driver with a few low-cost sensors and displays. With the exception of the OLED display, this board packs in a huge number of features for a low cost of $9.90.
These labs don't need anything except the Maker Pi RP2040 board.
These labs require additional parts such as DC hobby motors and servos
There are literally hundreds of sensors that have Grove connectors on them. In addition, we can purchase Grove connectors for as low as 30 cents each. Any other sensors with male pins can be easily connected with these 38 cent connectors with female Dupont jumper connectors. Classrooms with a large collection of these sensors can allow students to try new sensors and outputs without needing a breadboard or soldering. We will focus initially on two sensors we will use for our collision avoidance robot. We prefer the Time-of-Flight sensor because it uses a standard I2C interface and thus could share the I2C bus with other devices.
When this kit is combined with a standard 2 Wheel Drive Smart Car Chassis and a distance sensor it becomes a great low-cost way of getting started with Python and robots.
"},{"location":"kits/maker-pi-rp2040/#clearly-labeled-pin-numbers","title":"Clearly Labeled Pin Numbers","text":"One of the biggest disadvantages of the Raspberry Pi Pico is the fact that pin labels are NOT visible when it is mounted on a breadboard. We have to take the Pico out of the breadboard to read the pin numbers on the bottom of the board. A much better design would be to follow the best practices and put the labels on the top of the board where they are visible. This is clearly done on the Maker Pi RP2040 board!
Note the pin labels, GND, 3.3V, GP0 and GP1 are clearly printed on the top of the board.
Note the circuit in the upper right corner displays how you can use the analog input port to read the battery level of the robot.
"},{"location":"kits/maker-pi-rp2040/#removing-the-default-circuitpython","title":"Removing the Default CircuitPython","text":"Cytron Technologies has a wonderful YouTube videos on how to program the Maker Pi RP2040 using MicroPython. Unfortunately, this board does NOT come with our standard MicroPython loaded! :-O It uses the non-standard Adafruit CircuitPython that is incompatible with most MicroPython programs being used today. This is a sad state of affairs that confuses our students and makes it difficult to share code and libraries for MicroPython. According to Google trends, over the last 12 months for worldwide searches, MicroPython has almost five time the interest of CircuitPython. Preloading the board with CircuitPython sends a very confusing message to the marketplace.
"},{"location":"kits/maker-pi-rp2040/#flash-nuke","title":"Flash Nuke","text":"I want to make sure that my RP2040 was starting out with a clean image. I downloaded the flash_nuke.uf2 file to remove the default CircuitPython runtime and all the related files.
Note that the board must be fully powered down after this load for it to work. I had 4 AA batteries connected to the VIN screw headers, so it was not resetting correctly and the reset was not working until I disconnected the batteries.
The latests MicroPython runtimes are here
"},{"location":"kits/maker-pi-rp2040/#easy-motor-testing-buttons","title":"Easy Motor Testing Buttons","text":"One of the things I love about this board is how incredibly easy it is for students to test their motors. The board provides four very convenient motor test buttons right on the board. By pressing each one you can make both motors go forward and backwards. This is a great way for students to learn about how we can generate PWM signals to simulate these four buttons. Whoever design this board clearly had their students in mind!
"},{"location":"kits/maker-pi-rp2040/#references","title":"References","text":"The retail list price is $9.99. Thie Cytron Maker Pi RP2040 kit includes 3 Grove connectors, screwdriver and feet) Cytron
Here are some online retailers that seel this kit:
There is also a YouTube Video that demonstrates the features of the board.
"},{"location":"kits/maker-pi-rp2040/01-getting-started/#install-the-micropython-runtime-library","title":"Install the MicroPython Runtime Library","text":"The Maker Pi RP2040 comes with an incompatible CircuitPython run-time. Our first step is to re-initialize the board with the Raspberry Pi flash_nuke.uf2 runtime. We can then load the latest MicroPython libraries. When we wrote these labs we were using MicroPython version 1.7 that was released in September of 2021.
To install MicroPython you mush hold down the BOTSEL button on the main board while you turn on the board using the on-board power switch. This will make the board look like a USB drive. You can then just drag the flash-nuke file onto the drive and the board will be initialized. Make sure to power the board off and back on.
You can now repeat this process with the Raspberry Pi MicroPython Runtime. Just remember to press and hold down the BOTSEL before you turn on the board and reboot after the image has been copied to the microcontroller.
If you have never used MicroPython, the Raspberry Pi Foundation has a nice Getting Started Guide that can be helpful.
"},{"location":"kits/maker-pi-rp2040/01-getting-started/#get-familiar-with-your-ide-thonny-and-the-basic-programs","title":"Get Familiar with your IDE (Thonny) and the Basic Programs","text":"There are many Integrated Development Environments (IDEs) that work with the Raspberry Pi RP2040 chip. The one you chose just should support MicroPython and be able to upload and run new programs. Once you turn on the board you should be able to configure Thonny to use the Raspberry Pi MicroPython interface. When you press the Stop/Reset button you should see the MicroPython REPL prompt.
"},{"location":"kits/maker-pi-rp2040/01-getting-started/#_1","title":"Getting Started","text":""},{"location":"kits/maker-pi-rp2040/01-getting-started/#test-the-motor-connections","title":"Test the Motor Connections","text":"Use the Motor Connection Lab
"},{"location":"kits/maker-pi-rp2040/01-getting-started/#getting-help","title":"Getting Help","text":"MicroPython on the RP2040 is the most powerful low-cost system on the market today. With 264K of RAM, it will take a LOT of work to run out of memory. But with all things new, there is a lock of good documentation, drivers and sample code. To help you along, we suggest the following resources:
Once you have the MicroPython runtime installed and your IDE setup, this board is easy to program!
Let's take a look at the classic \"Blink\" program that turns a single LED on and off every second.
"},{"location":"kits/maker-pi-rp2040/02-blue-led-lab/#blink-first-blue-led","title":"Blink First Blue LED","text":"The Maker Pi RP2040 has a row of 13 small blue LEDs that monitor the digital input/output of 13 of the GPIO signals. If you set any of the output pins to be HIGH, the LED will be on. If you set the pin to be LOW, the blue LED will be off. These LEDs make it easy for you to view the state of your GPIO pins and can help debugging your programs.
Just remember that if you are using the pins for communication, you can't use the LEDs for other purposes.
Here is a small program that will blink the first blue LED:
import machine\nimport time\n# setup the first LED as an output signal\nfirst_led = machine.Pin(0, machine.Pin.OUT)\n\nwhile True:\n first_led.toggle()\n time.sleep(1)\n
Note that the first four lines are the \"setup\" of the program. These lines will only run once when the program starts. The code indented after the while True:
line will continue to run until the device is reset or powered down.
Here is a demo using the 13 nice blue LEDs used to show the status of the pins.
import machine\nimport time\n\n# The Maker Pi RP2040 has 13 fantastic blue GPIO status LEDs\nblue_led_pins = [0,1,2,3,4,5,6,7,16,17,26,27,28]\nnumber_leds = len(blue_led_pins)\nled_ports = []\ndelay = .05\n\n# create a list of the ports\nfor i in range(number_leds):\n led_ports.append(machine.Pin(blue_led_pins[i], machine.Pin.OUT))\n\n# loop forever\nwhile True:\n # blue up\n for i in range(0, number_leds):\n led_ports[i].high()\n time.sleep(delay)\n led_ports[i].low()\n # blue down\n for i in range(number_leds - 1, 0, -1):\n led_ports[i].high()\n time.sleep(delay)\n led_ports[i].low()\n
This demo uses a list of all the 13 digital I/O ports. For each port it sets the port to be a digital output. In the main loop it then goes up and down the strip of LEDs, turning each one on for 1/20th of a second (.05 seconds).
"},{"location":"kits/maker-pi-rp2040/03-neopixel-lab/","title":"NeoPixel Lab","text":""},{"location":"kits/maker-pi-rp2040/03-neopixel-lab/#neopixel-demo-lab","title":"NeoPixel Demo Lab","text":"The Maker Pi RP2040 comes with two built-in NeoPixels. Each NeoPixel has a red, green and blue LED inside it. Each of these LEDs can be set to any one of 256 values from 0 (off) to 255 (brightest value).
"},{"location":"kits/maker-pi-rp2040/03-neopixel-lab/#neopixel-setup","title":"NeoPixel Setup","text":"from neopixel import Neopixel\n\nNUMBER_PIXELS = 2\nSTATE_MACHINE = 0\nLED_PIN = 18\n\n# The Neopixels on the Maker Pi RP2040 are the GRB variety, not RGB\nstrip = Neopixel(NUMBER_PIXELS, STATE_MACHINE, LED_PIN, \"GRB\")\n
"},{"location":"kits/maker-pi-rp2040/03-neopixel-lab/#neopixel-blink-lab","title":"NeoPixel Blink Lab","text":"In this lab, we will turn the first NeoPixel element on red for 1/2 second and then turn it off for 1/2 second. We repeat this until the program is terminated.
"},{"location":"kits/maker-pi-rp2040/03-neopixel-lab/#setting-up-the-neopixel-library","title":"Setting up the NeoPixel Library","text":"We will be calling a NeoPixel driver in the /lib directory. We initiaze our NeoPixel strip by calling the init method all Neopixel() and pass it three parameters:
from utime import sleep\n# We are using https://github.com/blaz-r/pi_pico_neopixel\nfrom neopixel import Neopixel\n\nNUMBER_PIXELS = 2\nSTATE_MACHINE = 0\nLED_PIN = 18\n\n# The Neopixels on the Maker Pi RP2040 are the GRB variety, not RGB\nstrip = Neopixel(NUMBER_PIXELS, STATE_MACHINE, LED_PIN, \"GRB\")\n\nwhile True:\n # turn on first pixel red for 1/2 second\n strip.set_pixel(0, (255, 0, 0))\n strip.show()\n sleep(.5) \n strip.set_pixel(0, (0, 0, 0)) # turn all colors off\n strip.show()\n sleep(.5)\n
import time\n# We are using https://github.com/blaz-r/pi_pico_neopixel\nfrom neopixel import Neopixel\n\nNUMBER_PIXELS = 2\nSTATE_MACHINE = 0\nLED_PIN = 18\n\n# The Neopixels on the Maker Pi RP2040 are the GRB variety, not RGB\nstrip = Neopixel(NUMBER_PIXELS, STATE_MACHINE, LED_PIN, \"GRB\")\n\n# Color RGB values\nred = (255, 0, 0)\norange = (255, 60, 0) # Gamma corrected from G=128 to be less like yellow\nyellow = (255, 150, 0)\ngreen = (0, 255, 0)\nblue = (0, 0, 255)\nindigo = (75, 0, 130) # purple?\nviolet = (138, 43, 226) # mostly pink\ncolor_names = ('red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet')\nnum_colors = len(color_names)\ncolors = (red, orange, yellow, green, blue, indigo, violet)\n\n# set to be 1 to 100 for percent brightness\nstrip.brightness(100)\n\ncolor_index = 0\nwhile True:\n for color in colors:\n for i in range(NUMBER_PIXELS):\n print(i, color_names[color_index])\n strip.set_pixel(i, color)\n strip.show()\n time.sleep(1)\n color_index += 1\n if color_index >= num_colors: color_index = 0\n
"},{"location":"kits/maker-pi-rp2040/04-button-lab/","title":"Two Button Press","text":"We learned how to write code to monitor a button press in the Button Lab.
Recall we talked about how to remove the \"debounce noise\" when a button is pressed by adding a timer to make sure we had a clean transition (debouncing the switch):
We did this by waiting for the transition to settle down to its new state.
import utime\nfrom machine import Pin\n\n# Sample Raspberry Pi Pico MicroPython button press example with a debounce delay value of 200ms in the interrupt handler\n\nbutton_presses = 0 # the count of times the button has been pressed\nlast_time = 0 # the last time we pressed the button\n\nbuiltin_led = machine.Pin(25, Pin.OUT)\n# The lower left corner of the Pico has a wire that goes through the buttons upper left and the lower right goes to the 3.3 rail\nfaster_pin = machine.Pin(20, machine.Pin.IN, machine.Pin.PULL_DOWN)\nslower_pin = machine.Pin(21, machine.Pin.IN, machine.Pin.PULL_DOWN)\n\n# This function gets called every time the button is pressed. The parameter \"pin\" is not used.\ndef button_pressed_handler(pin):\n global button_presses, last_time\n new_time = utime.ticks_ms()\n # if it has been more that 1/5 of a second since the last event, we have a new event\n if (new_time - last_time) > 200:\n # this should be pin.id but it does not work\n if '20' in str(pin):\n button_presses +=1\n else:\n button_presses -=1\n last_time = new_time\n\n\n# now we register the handler function when the button is pressed\nfaster_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\nslower_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\n\n# This is for only printing when a new button press count value happens\nold_presses = 0\n\nwhile True:\n # only print on change in the button_presses value\n if button_presses != old_presses:\n print(button_presses)\n builtin_led.toggle()\n old_presses = button_presses\n
"},{"location":"kits/maker-pi-rp2040/04-button-lab/#making-the-buttons-change-the-neopixel-color","title":"Making the Buttons Change the NeoPixel Color","text":"In this lab, we will combine the button press lab with our NeoPixel lab to allow you to change the NeoPixel colors if a button on the board is pressed. Each button will control the color of one of the pixels.
# press buttons to change the color of the NeoPixels\nimport utime\nfrom machine import Pin\nfrom neopixel import Neopixel\n\nNUMBER_PIXELS = 2\nSTATE_MACHINE = 0\nLED_PIN = 18\nBUTTON_A_PIN = 20\nBUTTON_B_PIN = 21\n# Sample Raspberry Pi Pico MicroPython button press example with a debounce delay value of 200ms in the interrupt handler\n# The Neopixels on the Maker Pi RP2040 are the GRB variety, not RGB\nstrip = Neopixel(NUMBER_PIXELS, STATE_MACHINE, LED_PIN, \"GRB\")\n\n# Color RGB values\nred = (255, 0, 0)\norange = (125, 60, 0) # Gamma corrected from G=128 to be less like yellow\nyellow = (255, 150, 0)\ngreen = (0, 255, 0)\nblue = (0, 0, 255)\ncyan = (0, 255, 255)\nindigo = (75, 0, 130) # purple?\nviolet = (138, 43, 226) # mostly pink\nwhite = (255, 255, 255)\ncolor_names = ('red', 'orange', 'yellow', 'green', 'blue', 'cyan', 'indigo', 'violet', 'white')\nnum_colors = len(color_names)\ncolors = (red, orange, yellow, green, blue, cyan, indigo, violet, white)\n\n# color index into colors list\nneopixel_a = 0\nneopixel_b = 0\n# set to be 1 to 100 for percent brightness\nstrip.brightness(100)\n\nbutton_presses = 0 # the count of times the button has been pressed\nlast_time = 0 # the last time we pressed the button\n\n# The lower left corner of the Pico has a wire that goes through the buttons upper left and the lower right goes to the 3.3 rail\nbutton_a = machine.Pin(BUTTON_A_PIN, machine.Pin.IN, machine.Pin.PULL_DOWN)\nbutton_b = machine.Pin(BUTTON_B_PIN, machine.Pin.IN, machine.Pin.PULL_DOWN)\n\n# This function gets called every time the button is pressed. The parameter \"pin\" is not used.\ndef button_pressed_handler(pin):\n global button_presses, last_time, num_colors, neopixel_a, neopixel_b\n new_time = utime.ticks_ms()\n # if it has been more that 1/5 of a second since the last event, we have a new event\n if (new_time - last_time) > 200:\n # this should be pin.id but it does not work\n button_presses += 1\n if '20' in str(pin):\n neopixel_a +=1\n if neopixel_a > num_colors - 1:\n neopixel_a = 0 \n else:\n neopixel_b +=1\n if neopixel_b > num_colors - 1:\n neopixel_b = 0 \n last_time = new_time\n\n# now we register the handler function when the button is pressed\nbutton_a.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\nbutton_b.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\n\n# This is for only printing when a new button press count value happens\nold_presses = 0\nprint('Running NeoPixel Button Lab')\nstrip.set_pixel(0, (4,5,5))\nstrip.set_pixel(1, (4,5,5))\nstrip.show()\n\ndef main():\n global button_presses, old_presses, colors, neopixel_a, neopixel_b\n while True:\n # only print on change in the button_presses value\n if button_presses != old_presses:\n print(button_presses)\n print('NeoPixel A:', color_names[neopixel_a], 'index:', neopixel_a)\n print('NeoPixel B:', color_names[neopixel_b], 'index:', neopixel_b)\n strip.set_pixel(0, colors[neopixel_a])\n strip.set_pixel(1, colors[neopixel_b])\n strip.show()\n old_presses = button_presses\n\ntry:\n main()\nexcept KeyboardInterrupt:\n print('Got ctrl-c')\nfinally:\n # Cleanup code\n print('Turning off NeoPixels')\n strip.set_pixel(0, (0,0,0))\n strip.set_pixel(1, (0,0,0))\n strip.show()\n
"},{"location":"kits/maker-pi-rp2040/05-sound-lab/","title":"Play Mario on MicroPython","text":"This program will play the theme music from the Mario video game.
from machine import Pin, PWM\nfrom utime import sleep\nbuzzer = PWM(Pin(22))\n\ntones = {\n\"B0\": 31,\"C1\": 33,\"CS1\": 35,\"D1\": 37,\"DS1\": 39,\"E1\": 41,\"F1\": 44,\"FS1\": 46,\n\"G1\": 49,\"GS1\": 52,\"A1\": 55,\"AS1\": 58,\"B1\": 62,\"C2\": 65,\n\"CS2\": 69,\"D2\": 73,\"DS2\": 78,\"E2\": 82,\"F2\": 87,\"FS2\": 93,\"G2\": 98,\n\"GS2\": 104,\"A2\": 110,\"AS2\": 117,\"B2\": 123,\"C3\": 131,\"CS3\": 139,\n\"D3\": 147,\"DS3\": 156,\"E3\": 165,\"F3\": 175,\"FS3\": 185,\n\"G3\": 196,\"GS3\": 208,\"A3\": 220,\"AS3\": 233,\"B3\": 247,\"C4\": 262,\"CS4\": 277,\"D4\": 294,\"DS4\": 311,\n\"E4\": 330,\"F4\": 349,\"FS4\": 370,\"G4\": 392,\"GS4\": 415,\"A4\": 440,\"AS4\": 466,\"B4\": 494,\"C5\": 523,\"CS5\": 554,\"D5\": 587,\"DS5\": 622,\"E5\": 659,\"F5\": 698,\n\"FS5\": 740,\"G5\": 784,\"GS5\": 831,\"A5\": 880,\"AS5\": 932,\"B5\": 988,\"C6\": 1047,\"CS6\": 1109,\"D6\": 1175,\"DS6\": 1245,\"E6\": 1319,\"F6\": 1397,\"FS6\": 1480,\"G6\": 1568,\"GS6\": 1661,\n\"A6\": 1760,\"AS6\": 1865,\"B6\": 1976,\"C7\": 2093,\"CS7\": 2217,\"D7\": 2349,\"DS7\": 2489,\"E7\": 2637,\"F7\": 2794,\"FS7\": 2960,\"G7\": 3136,\"GS7\": 3322,\"A7\": 3520,\n\"AS7\": 3729,\"B7\": 3951,\"C8\": 4186,\"CS8\": 4435,\"D8\": 4699,\"DS8\": 4978\n}\n\nsong = [\"E5\",\"G5\",\"A5\",\"P\",\"E5\",\"G5\",\"B5\",\"A5\",\"P\",\"E5\",\"G5\",\"A5\",\"P\",\"G5\",\"E5\"]\nmario = [\"E7\", \"E7\", 0, \"E7\", 0, \"C7\", \"E7\", 0, \"G7\", 0, 0, 0, \"G6\", 0, 0, 0, \"C7\", 0, 0, \"G6\",\n 0, 0, \"E6\", 0, 0, \"A6\", 0, \"B6\", 0, \"AS6\", \"A6\", 0, \"G6\", \"E7\", 0, \"G7\", \"A7\", 0, \"F7\", \"G7\",\n 0, \"E7\", 0,\"C7\", \"D7\", \"B6\", 0, 0, \"C7\", 0, 0, \"G6\", 0, 0, \"E6\", 0, 0, \"A6\", 0, \"B6\", 0,\n \"AS6\", \"A6\", 0, \"G6\", \"E7\", 0, \"G7\", \"A7\", 0, \"F7\", \"G7\", 0, \"E7\", 0,\"C7\", \"D7\", \"B6\", 0, 0]\n\ndef playtone(frequency):\n buzzer.duty_u16(1000)\n buzzer.freq(frequency)\n\ndef bequiet():\n buzzer.duty_u16(0)\n\ndef playsong(mysong):\n for i in range(len(mysong)):\n if (mysong[i] == \"P\" or mysong[i] == 0 ):\n bequiet()\n else:\n playtone(tones[mysong[i]])\n sleep(0.3)\n bequiet()\nplaysong(mario)\n
"},{"location":"kits/maker-pi-rp2040/06-up-down-lab/","title":"Up Down Mode Lab","text":"In this lab, we will combine the two buttons with the blue LEDs, the NeoPixels and the buzzer labs. The We will make the LED, NeoPixels and sound all change for each button press. You will be able to up and down the color spectrum and the sound frequency.
We will start with the material from our button lab. We will create two functions that will be triggered by the two buttons. One will increment a counter (add one) and the other will decrement the counter (subtract 1). By pressing one of the two buttons you will cycle through the modes of the program.
The diagram has eight different modes. The default mode is usually mode=0. When you press the left button the mode will increase by one. The NeoPixels will change from red to orange. Pressing the left button will increase the mode by one going to the orange mode. Pressing the right button will subtract one from the mode going from mode 1 (orange) back to model 0 (red).
Within our Interrupt Request Handler (IRQ) function we will also have to add two lines to deal with the wrap around logic like this:
# wrap around to first mode\n if mode >= mode_count: mode = 0\n if mode < 0: mode = mode_count - 1\n
"},{"location":"kits/maker-pi-rp2040/06-up-down-lab/#full-program","title":"Full Program","text":"# Mode Up/Down Lab\n# Change a mode using the buttons on the Maker Pi RP2040 board\n# Changes the NeoPixel color and the blue GPIO status LEDs\nimport time\nfrom machine import Pin, PWM\n# We are using a MicroPython NeoPixel library from here: https://github.com/blaz-r/pi_pico_neopixel\nfrom neopixel import Neopixel\n\nBUZZER_PORT = 22\nbuzzer = PWM(Pin(BUZZER_PORT))\n\nNUMBER_PIXELS = 2\nSTATE_MACHINE = 0\nNEOPIXEL_PIN = 18\n\n# The Neopixels on the Maker Pi RP2040 are the GRB variety, not RGB\nstrip = Neopixel(NUMBER_PIXELS, STATE_MACHINE, NEOPIXEL_PIN, \"GRB\")\n\n# have up to 13 that we can use\nblue_led_pins = [0,1,2,3,4,5,6,7,16,17,26,27,28]\nnumber_leds = len(blue_led_pins)\nled_ports = []\n# create a list of the port pin object instances\nfor i in range(number_leds):\n led_ports.append(machine.Pin(blue_led_pins[i], machine.Pin.OUT))\n\n# Color RGB values as tuples - needs some Gamma corrections\nred = (255, 0, 0)\norange = (255, 60, 0) # Gamma corrected from G=128 to be less like yellow\nyellow = (255, 150, 0)\ngreen = (0, 255, 0)\nblue = (0, 0, 255)\nindigo = (75, 0, 130) # purple?\nviolet = (138, 43, 226) # mostly pink\ncyan = (0, 255, 255)\nlightgreen = (100, 255, 100)\nwhite = (128, 128, 128) # not too bright\ncolor_names = ('red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet', 'cyan', 'lightgreen', 'white')\nnum_colors = len(color_names)\ncolors = (red, orange, yellow, green, blue, indigo, violet, cyan, lightgreen, white)\n\n# set to be 1 to 100 for percent brightness\nstrip.brightness(100)\n\n# Sample Raspberry Pi Pico MicroPython button press example with a debounce delay value of 200ms in the interrupt handler\n\nmode = 0 # the default mode on powerup and reset\nmode_count = len(color_names)\nlast_time = 0 # the last time we pressed the button\n\nbuiltin_led = machine.Pin(25, Pin.OUT)\n# Give our pins some logical names\nnext_mode_pin = machine.Pin(20, machine.Pin.IN, machine.Pin.PULL_DOWN)\nprevious_mode_pin = machine.Pin(21, machine.Pin.IN, machine.Pin.PULL_DOWN)\n\n# This function gets called every time the button is pressed. The parameter \"pin\" is not used.\ndef button_pressed_handler(pin):\n global mode, last_time\n new_time = time.ticks_ms()\n # if it has been more that 1/5 of a second since the last event, we have a new event\n if (new_time - last_time) > 200:\n # this should be pin.id but it does not work\n if '20' in str(pin):\n mode +=1\n else:\n mode -=1\n # wrap around to first mode\n if mode >= mode_count: mode = 0\n if mode < 0: mode = mode_count - 1\n last_time = new_time\n\ndef set_blue_led_mode(mode):\n global num_colors\n for i in range(0, num_colors):\n if i == mode:\n led_ports[i].high()\n else:\n led_ports[i].low()\n\n# Register the handler function when either button is pressed\nnext_mode_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\nprevious_mode_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\n\n# Note the non-linear increases in frequency - note that some are louder\ntone_freq = [100, 150, 210, 280, 350, 450, 580, 750, 850, 950, 1000]\ndef playtone(frequency):\n buzzer.duty_u16(1000)\n buzzer.freq(frequency)\n\ndef bequiet():\n buzzer.duty_u16(0)\n\n# This is for only printing when a new button press count value happens\nold_mode = -1\n\nprint('found ', mode_count, ' modes.')\nwhile True:\n # only print on change in the button_presses value\n if mode != old_mode:\n print('new mode:', mode, color_names[mode], tone_freq[mode])\n # get the color mode\n color = colors[mode]\n strip.set_pixel(0, color)\n strip.set_pixel(1, color)\n strip.show()\n set_blue_led_mode(mode)\n playtone(tone_freq[mode])\n time.sleep(.2)\n bequiet()\n old_mode = mode\n
"},{"location":"kits/maker-pi-rp2040-robot/","title":"Cytron Maker Pi RP2040 Collision Avoidance Robot","text":"We have been working on designing a run robot that can be used to teach computational thinking since 2014. We have gone through many generations, and now we think we have a fantastic design that is powerful, flexible, extendible and inexpensive. We love this robot because:
This version uses the time-of-flight sensor.
The robot can be built ia a few hours using a screwdriver and soldering four wires onto the motors.
"},{"location":"kits/maker-pi-rp2040-robot/#sample-parts-list","title":"Sample Parts List","text":"This kit is a $25 robot kit that we will use in our CoderDojo robotics classes. This kit includes:
You will need to provide 4 AA batteries and a Micro USB connector that works with your PC or Mac.
"},{"location":"kits/maker-pi-rp2040-robot/02-assembly/#assemble-the-smartcar-chassis","title":"Assemble the SmartCar Chassis","text":"In this version of the kit, the wires are pre-soldered onto the motors.
Here is the robot kit in all the packaging:
Your first task is to remove the protective backing from the acrylic body.
Here are all the parts removed from the packaging:
We mount the motors with the wires on the inside and the heads of the bolts on the outside. This photo shows cable ties I have added so that the wires don't get pulled out by our students. These cable ties are optional.
Next, we position the battery pack on the BOTTOM so that we have more room on the top for our circuit board, sensors and add-on displays.
I used a 1/8th inch drill bit to put holes where the battery pack should be mounted.
Next, I put the flat-head screws in the battery pack. We want to make sure the top of the screw is all the way in so that it does not get in the way of the battery.
Next, we mount the rubber feet on the bottom of the Maker Pi RP2040 circuit board so that we have some space between the PC board and the main chassis. I use the space next to the four corners to mount the feet. Note that we must put the drag wheel on before we put the PC board on top of the chassis.
Next, we put the four screws and spacers in the four holes at the bottom rear of the robot directly behind the battery pack.
We then add the four screws to mount the drag wheel.
Now is a good time to check the spacing of the battery pack and the read drag wheel. The rear drag wheel must be able to spin freely in a full circle without bumping into the battery. If it bumps you might need to remount the battery pack before you proceed to the next step.
This figure has the switch that comes with the battery pack. For our work, we will not need this switch since the Maker Pi RP2040 circuit board has an no-board power switch. Most of our students put the switch in if they ever need to change circuit boards that don't have a built-in power switch. If you do this, you can solder the switch between the red power of the battery and the positive terminal of VIN.
Next, line up the printed circuit board with the USB connector facing the rear. Note where the holes are in the board and drill two 1/8\" holes to mount the board.
This photo shows the holes drilled with the screws in them.
This is the side-view from the rear of the screws holding on the circuit board.
Next use two 6 mm M3 screws to mount the ultrasonic distance sensor on top front of the robot. Some of our students like to mount the ultrasonic sensor under the chassis and point the sensor up a little so the sensor does not reflect off the floor. You can use a heat gun to soften the plastic mount to change the angle.
Next I added a drop of hot-glue under the front screws that mount the pc board. I did this because the battery pack and motor mounts get in the way of adding a nut under the board.
Next, I used a small rubber coated twist tie to keep the wires under the robot away from the wheels and battery. We don't want them to drag on the floor.
Next, we connect the motors up to the screw headers on the printed circuit board. There is a screwdriver that comes with the Cytron Maker Pi RP2040 that is handy for tightening the screws. Don't worry about getting the connections all correct. They can be adjusted in your software.
Press the wheels on the motors.
Lastly, we connect the battery to the VIN jumper, making sure to connect the red wire to the \"+\" terminal and the black wire to the \"-\" terminal.
Connect the Maker Pi RP2040 board to the top with the USB connector facing the rear.
Here is a short video of the assembly of a SmartCar Chassis. Note that this video puts the battery on the top, where we put it on the bottom.
There are many videos online how to assemble to motors to the chassis. The trick is orienting the motors correctly and making sure the bolts don't get in the way of the wheels.
"},{"location":"kits/maker-pi-rp2040-robot/06-up-down-motor-lab/","title":"Up Down Motor Speed Lab","text":"In this lab, we will make the motor speed change as the mode changes.
# Motor Setup\n# motors just barely turn at this power level\nMIN_POWER_LEVEL = 10000\nMAX_POWER_LEVEL = 65025\nPOWER_STEP = int((MAX_POWER_LEVEL - MIN_POWER_LEVEL) / 10)\n# lower right pins with USB on top\nRIGHT_FORWARD_PIN = 8\nRIGHT_REVERSE_PIN = 9\nLEFT_FORWARD_PIN = 11\nLEFT_REVERSE_PIN = 10\n\nright_forward = PWM(Pin(RIGHT_FORWARD_PIN))\nright_reverse = PWM(Pin(RIGHT_REVERSE_PIN))\nleft_forward = PWM(Pin(LEFT_FORWARD_PIN))\nleft_reverse = PWM(Pin(LEFT_REVERSE_PIN))\n\ndef drive_speed(power_level):\n right_forward.duty_u16(power_level)\n left_forward.duty_u16(power_level)\n
In the main we have:
power_level = MIN_POWER_LEVEL + mode * POWER_STEP\n# turn off the motor if we are at mode 0\nif mode == 0: power_level = 0\ndrive_speed(power_level)\n
"},{"location":"kits/maker-pi-rp2040-robot/06-up-down-motor-lab/#full-program","title":"Full Program","text":"# Mode Up/Down Lab\n# Change a mode using the buttons on the Maker Pi RP2040 board\n# Changes the NeoPixel color and the blue GPIO status LEDs\nimport time\nfrom machine import Pin, PWM\n# We are using a MicroPython NeoPixel library from here: https://github.com/blaz-r/pi_pico_neopixel\nfrom neopixel import Neopixel\n\nBUZZER_PORT = 22\nbuzzer = PWM(Pin(BUZZER_PORT))\n\nNUMBER_PIXELS = 2\nSTATE_MACHINE = 0\nNEOPIXEL_PIN = 18\n\n# The Neopixels on the Maker Pi RP2040 are the GRB variety, not RGB\nstrip = Neopixel(NUMBER_PIXELS, STATE_MACHINE, NEOPIXEL_PIN, \"GRB\")\n\n# have up to 13 that we can use\nblue_led_pins = [0,1,2,3,4,5,6,7,16,17,26,27,28]\nnumber_leds = len(blue_led_pins)\nled_ports = []\n# create a list of the port pin object instances\nfor i in range(number_leds):\n led_ports.append(machine.Pin(blue_led_pins[i], machine.Pin.OUT))\n\n# Color RGB values as tuples - needs some Gamma corrections\nred = (255, 0, 0)\norange = (255, 60, 0) # Gamma corrected from G=128 to be less like yellow\nyellow = (255, 150, 0)\ngreen = (0, 255, 0)\nblue = (0, 0, 255)\nindigo = (75, 0, 130) # purple?\nviolet = (138, 43, 226) # mostly pink\ncyan = (0, 255, 255)\nlightgreen = (100, 255, 100)\nwhite = (128, 128, 128) # not too bright\ncolor_names = ('red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet', 'cyan', 'lightgreen', 'white')\nnum_colors = len(color_names)\ncolors = (red, orange, yellow, green, blue, indigo, violet, cyan, lightgreen, white)\n\n# set to be 1 to 100 for percent brightness\nstrip.brightness(100)\n\n# Sample Raspberry Pi Pico MicroPython button press example with a debounce delay value of 200ms in the interrupt handler\n\n# Motor Setup\n# motors just barely turn at this power level\nMIN_POWER_LEVEL = 10000\nMAX_POWER_LEVEL = 65025\nPOWER_STEP = int((MAX_POWER_LEVEL - MIN_POWER_LEVEL) / 10)\n# lower right pins with USB on top\nRIGHT_FORWARD_PIN = 8\nRIGHT_REVERSE_PIN = 9\nLEFT_FORWARD_PIN = 11\nLEFT_REVERSE_PIN = 10\n\nright_forward = PWM(Pin(RIGHT_FORWARD_PIN))\nright_reverse = PWM(Pin(RIGHT_REVERSE_PIN))\nleft_forward = PWM(Pin(LEFT_FORWARD_PIN))\nleft_reverse = PWM(Pin(LEFT_REVERSE_PIN))\n\ndef drive_speed(power_level):\n right_forward.duty_u16(power_level)\n left_forward.duty_u16(power_level)\n\nmode = 0 # the default mode on powerup and reset\nmode_count = len(color_names)\nlast_time = 0 # the last time we pressed the button\n\nbuiltin_led = machine.Pin(25, Pin.OUT)\n# Give our pins some logical names\nnext_mode_pin = machine.Pin(20, machine.Pin.IN, machine.Pin.PULL_DOWN)\nprevious_mode_pin = machine.Pin(21, machine.Pin.IN, machine.Pin.PULL_DOWN)\n\n# This function gets called every time the button is pressed. The parameter \"pin\" is not used.\ndef button_pressed_handler(pin):\n global mode, last_time, power_level\n new_time = time.ticks_ms()\n # if it has been more that 1/5 of a second since the last event, we have a new event\n if (new_time - last_time) > 200:\n # this should be pin.id but it does not work\n if '20' in str(pin):\n mode +=1\n # power_level += POWER_STEP\n else:\n mode -=1\n # power_level -= POWER_STEP\n # wrap around to first mode\n if mode >= mode_count: mode = 0\n if mode < 0: mode = mode_count - 1\n last_time = new_time\n\ndef set_blue_led_mode(mode):\n global num_colors\n for i in range(0, num_colors):\n if i == mode:\n led_ports[i].high()\n else:\n led_ports[i].low()\n\n# Register the handler function when either button is pressed\nnext_mode_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\nprevious_mode_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\n\n# non-linear increase is frequency - note that some are lowder\ntone_freq = [100, 150, 210, 280, 350, 450, 580, 750, 850, 950, 1000]\ndef playtone(frequency):\n buzzer.duty_u16(1000)\n buzzer.freq(frequency)\n\ndef bequiet():\n buzzer.duty_u16(0)\n\n# This is for only printing when a new button press count value happens\nold_mode = -1\n\npower_level = MIN_POWER_LEVEL\nprint('found ', mode_count, ' modes.')\nwhile True:\n # only print on change in the button_presses value\n if mode != old_mode:\n print('new mode:', mode, color_names[mode], tone_freq[mode], power_level)\n # get the color mode\n color = colors[mode]\n strip.set_pixel(0, color)\n strip.set_pixel(1, color)\n strip.show()\n set_blue_led_mode(mode)\n playtone(tone_freq[mode])\n time.sleep(.2)\n bequiet()\n power_level = MIN_POWER_LEVEL + mode * POWER_STEP\n # turn off the motor if we are at mode 0\n if mode == 0: power_level = 0\n drive_speed(power_level)\n old_mode = mode\n
"},{"location":"kits/maker-pi-rp2040-robot/07-motor-connection-lab/","title":"Motor Drive Connection Test","text":""},{"location":"kits/maker-pi-rp2040-robot/07-motor-connection-lab/#built-in-motor-driver","title":"Built-In Motor Driver","text":"The Maker Pi RP2040 board contains a MX1508 dual channel H-bridge chip and easy-to-connect screw headers for power and motor connections. This is fantastic for teaching robotics since students can driver two motors without ever having to use a soldering iron.
!!! Note that the is designed to work with small DC-hobby motors and there is no documentation on exactly what motor driver chip is used or its precise current and power limitations.
The documentation only indicates that the maximum current is 1A continuous power and 1.5A for up to 5 seconds. The input voltage is only rated at 6 volts, which find for our standard 4 AA battery packs.
If this motor driver chip is similar to the ubiquitous L293x motor controllers, and the current should be 1A per motor.
I suspect that if you glued a small heat sink like a 16 pin DIP fin to the unknown motor driver IC on the main board you could drive slightly larger motors.
Close-up of the motor driver chip. I can't quite make out the numbers on the chip, but the logo is not \"TI\".
"},{"location":"kits/maker-pi-rp2040-robot/07-motor-connection-lab/#testing-the-connections","title":"Testing The Connections","text":"In our standard robot, the M1 is the right wheel as you are looking from the top-back of the robot. The M2 wheel is the left wheel. I connect the red to the right of the two connectors and it is also the right terminal of the motors as you are looking from the rear.
Look at the buttons near the motor connectors. Press the M1A button and verify that the right wheel is moving forward. Press the M1B and the motor should turn in reverse. Similarly the M2B button should turn the left wheel forward and the M2A should turn the left wheel in reverse. If you don't wire these connections the same way I did it is not a worry. It is easy to change the code.
"},{"location":"kits/maker-pi-rp2040-robot/07-motor-connection-lab/#motor-pin-definitions","title":"Motor Pin Definitions","text":"Now that we know what buttons control what motors and directions they turn, we are ready to define the pins that are associated with each robot movement. We have four pin assignments: both forward and reverse for both the right and left motors.
RIGHT_FORWARD_PIN = 8\nRIGHT_REVERSE_PIN = 9\nLEFT_FORWARD_PIN = 11\nLEFT_REVERSE_PIN = 10\n
"},{"location":"kits/maker-pi-rp2040-robot/07-motor-connection-lab/#testing-your-pin-definitions","title":"Testing Your Pin Definitions","text":"The following program is called our motor connection test. It will turn each motor direction for three seconds and it will print out the motor and direction in the console.
from machine import Pin, PWM\nimport time\n\nPOWER_LEVEL = 65025\n# lower right pins with USB on top\nRIGHT_FORWARD_PIN = 8\nRIGHT_REVERSE_PIN = 9\nLEFT_FORWARD_PIN = 11\nLEFT_REVERSE_PIN = 10\n\nright_forward = PWM(Pin(RIGHT_FORWARD_PIN))\nright_reverse = PWM(Pin(RIGHT_REVERSE_PIN))\nleft_forward = PWM(Pin(LEFT_FORWARD_PIN))\nleft_reverse = PWM(Pin(LEFT_REVERSE_PIN))\n\ndef spin_wheel(pwm):\n pwm.duty_u16(POWER_LEVEL)\n time.sleep(3)\n pwm.duty_u16(0)\n time.sleep(2)\n\nwhile True:\n print('right forward')\n spin_wheel(right_forward)\n\n print('right reverse')\n spin_wheel(right_reverse)\n\n print('left foward')\n spin_wheel(left_forward)\n\n print('left_reverse')\n spin_wheel(left_reverse)\n
"},{"location":"kits/maker-pi-rp2040-robot/07b-drive-square-lab/","title":"Drive Square Lab","text":""},{"location":"kits/maker-pi-rp2040-robot/07b-drive-square-lab/#prerequsites","title":"Prerequsites","text":"This lab assumes you have your Maker Pi RP2040 mounted on a SmartCar chassis with two motors and a battery hooked up.
In this lab we will program our robot to drive in a square pattern. We will start out doing a \"bench test\" that will require you to put the robot up on a block so you can see the wheels turn, but it will not drive off your desktop. You can also observe the red LED lights on the many board to see which motor direction is on.
The main loop will look like this:
while True:\n forward()\n sleep(FWD_TIME)\n\n stop()\n sleep(STOP_TIME)\n\n turn_right()\n sleep(TURN_TIME)\n\n stop()\n sleep(STOP_TIME)\n
We will need to adjust the TURN_TIME parameter to have the robot turn 90 degrees. A good value for most robots is about 1/2 second or sleep(.5).
Since we will be calling the sleep function many times we will use the following import format to keep our code tidy:
from utime import sleep\n
This says that whenever we want to pause our system we just use the sleep(time)
function we mean to use the sleep function in the micropython time library. This keeps our code small and portable."},{"location":"kits/maker-pi-rp2040-robot/07b-drive-square-lab/#adding-a-keyboard-interrupt-handler-control-c","title":"Adding a Keyboard Interrupt Handler (Control-C)","text":"It is also a problem that when we stop a program running that the PWM circuits keep generating signals, which means the robot keeps moving even after we press the STOP/RESET button. To clean this up we will allow you to run a special cleanup handler that will add a function to set all the motors to off using the stop()
function.
try:\n main()\nexcept KeyboardInterrupt:\n print('Got ctrl-c')\nfinally:\n # Optional cleanup code\n print('Cleaning up')\n print('Powering down all motors now.')\n stop()\n
"},{"location":"kits/maker-pi-rp2040-robot/07b-drive-square-lab/#full-program","title":"Full Program","text":"You are now ready to test the full program. Save the following to the main.py file, disconnect the USB connector and turn on the power on the main board. Your robot should not we driving in a square!
from machine import Pin, PWM\nfrom utime import sleep\n\nPOWER_LEVEL = 65025\n# lower right pins with USB on top\nRIGHT_FORWARD_PIN = 8\nRIGHT_REVERSE_PIN = 9\nLEFT_FORWARD_PIN = 11\nLEFT_REVERSE_PIN = 10\n\nright_forward = PWM(Pin(RIGHT_FORWARD_PIN))\nright_reverse = PWM(Pin(RIGHT_REVERSE_PIN))\nleft_forward = PWM(Pin(LEFT_FORWARD_PIN))\nleft_reverse = PWM(Pin(LEFT_REVERSE_PIN))\n\nFWD_TIME = 2\nTURN_TIME = .5 # adjust this to get the turn to be 90 degrees\nSTOP_TIME = 2\n\nright_forward = PWM(Pin(RIGHT_FORWARD_PIN))\nright_reverse = PWM(Pin(RIGHT_REVERSE_PIN))\nleft_forward = PWM(Pin(LEFT_FORWARD_PIN))\nleft_reverse = PWM(Pin(LEFT_REVERSE_PIN))\n\n\ndef turn_motor_on(pwm):\n pwm.duty_u16(POWER_LEVEL)\n\ndef turn_motor_off(pwm):\n pwm.duty_u16(0)\n\ndef forward():\n turn_motor_on(right_forward)\n turn_motor_on(left_forward)\n turn_motor_off(right_reverse)\n turn_motor_off(left_reverse)\n\ndef reverse():\n turn_motor_on(right_reverse)\n turn_motor_on(left_reverse)\n turn_motor_off(right_forward)\n turn_motor_off(left_forward)\n\ndef turn_right():\n turn_motor_on(right_forward)\n turn_motor_on(left_reverse)\n turn_motor_off(right_reverse)\n turn_motor_off(left_forward)\n\ndef turn_left():\n turn_motor_on(right_reverse)\n turn_motor_on(left_forward)\n turn_motor_off(right_forward)\n turn_motor_off(left_reverse)\n\ndef stop():\n turn_motor_off(right_forward)\n turn_motor_off(right_reverse)\n turn_motor_off(left_forward)\n turn_motor_off(left_reverse)\n\nprint('Running Drive Square Lab')\nprint('Use Control-C to Stop All Motors')\n\ndef main():\n while True:\n print('forward')\n forward()\n sleep(FWD_TIME)\n\n print('stop')\n stop()\n sleep(STOP_TIME)\n\n print('turning right')\n turn_right()\n sleep(TURN_TIME)\n\n print('stop')\n stop()\n sleep(STOP_TIME)\n\ntry:\n main()\nexcept KeyboardInterrupt:\n print('Got ctrl-c')\nfinally:\n # Optional cleanup code\n print('Cleaning up')\n print('Powering down all motors now.')\n stop()\n
"},{"location":"kits/maker-pi-rp2040-robot/08-servo-lab/","title":"Maker Pi RP2040 Servo Lab","text":"Servo motors are ideal for controlling the angle of an item such as a steering angle or the direction of a sensor. The servos used in these labs are inexpensive SG90 micro-servos that draw very little power and are ideal for a teaching lab. They can be purchased for about $3 each US on eBay. To control a 180 degree servo, you just tell it what angle you would like it to move to. The range of values is typically -90 to 90 degrees with 0 being the nominal resting position for many applications such as the steering wheel angle of a car.
The Maker Pi RP2040 has four servo ports in the upper left corner of the board (with the USB on the bottom) that use ports GP12, GP13, GP14 and GP15. You can connect any small micro servo directly to these ports. Just make sure to get the polarity correct. The colors for servos may vary somewhat, but the two most common standards are:
The general rule is that the lighter colors of orange and white will be the signal and the brown and black will be ground.
"},{"location":"kits/maker-pi-rp2040-robot/08-servo-lab/#servo-control","title":"Servo Control","text":"We will use the PWM functions in our MicroPython library to send a PWM signal to each of the servos. Servos are not controlled by the duty cycle directly. They are controlled by the width of the pulses. But we can control the approximate with of the pulses by holding the frequency constant and changing the duty cycle.
We will use a 40 hertz signal to send a PWM signal to each of the servos like this.
SERVO_FREQ_HZ = 40\n# SERVO_PERIOD_MS = 1000 / SERVO_FREQ_HZ is a 25 millisecond pulse width\nmy_pwm.freq(SERVO_FREQ_HZ)\n
"},{"location":"kits/maker-pi-rp2040-robot/08-servo-lab/#calibration-of-the-servo","title":"Calibration of the Servo","text":"There are small manufacturing variations in servos. This means to get the full sweep of a 180% servo you have to adjust the duty cycle.
By some experimentation I got the following results
SERVO_MIN_DUTY = 1725 # -90 degrees\nSERVO_MAX_DUTY = 6378 # 90 degrees\n
We can use a linear mapping function to convert the angle (from -90 to 90):
# This will take in integers of range in (min and max) return a integer in the output range (min and max)\n# Used to convert one range of values into another using a linear function like the Arduino map() function\ndef convert(x, in_min, in_max, out_min, out_max):\n return (x - in_min) * (out_max - out_min) // (in_max - in_min) + out_min\n\nangle = 0\nduty = convert(angle, -90, 90, SERVO_MIN_DUTY, SERVO_MAX_DUTY)\nprint('For angle: ', angle, ' the duty is: ', duty)\npwm.duty_u16(duty)\n
"},{"location":"kits/maker-pi-rp2040-robot/08-servo-lab/#checking-your-servo-calibration-with-buttons","title":"Checking your Servo Calibration with Buttons","text":"We can also use the buttons on the Maker Pi RP2040 to verify that the extreme angles are correct. One button will increase the angle and one will decrease the angle.
# Maker Pi RP2040 program to check the limits of a 180 degree servo such as a SG90 micro servo\nfrom machine import Pin, PWM\nimport time\n\nBUTTON_1_PIN = 20 # increment the angle\nBUTTON_2_PIN = 21 # decrement the angle\n\nSERVO_1_PIN = 12\nSERVO_2_PIN = 13 # MAX=5749@40\nSERVO_3_PIN = 14\nSERVO_4_PIN = 15\n# this is ususlly standard across most servos\nSERVO_FREQ_HZ = 40\n\npwm = PWM(Pin(SERVO_2_PIN))\n\n# the two button on the Maker Pi RP2040\nincrement_angle_button_pin = machine.Pin(BUTTON_1_PIN, machine.Pin.IN, machine.Pin.PULL_DOWN)\ndecrement_angle_button_pin = machine.Pin(BUTTON_2_PIN, machine.Pin.IN, machine.Pin.PULL_DOWN)\n\n\n# return int( ( (0.0015*SERVO_FREQ_HZ) + ((angle/90) * (0.0005*SERVO_FREQ_HZ)) ) * 65535 )\n# This will take in integers of range in (min and max) return a integer in the output range (min and max)\n# Used to convert one range of values into another using a linear function like the Arduino map() function\ndef convert(x, in_min, in_max, out_min, out_max):\n return (x - in_min) * (out_max - out_min) // (in_max - in_min) + out_min\n\n# globals\nangle = -90\nlast_time = 0 # the last time we pressed the button\n\n# if the pin is 20 then increment, else decement\ndef button_pressed_handler(pin):\n global angle, last_time\n new_time = time.ticks_ms()\n # if it has been more that 1/5 of a second since the last event, we have a new event\n if (new_time - last_time) > 200:\n # this should be pin.id but it does not work\n if '20' in str(pin):\n angle +=1\n else:\n angle -=1\n last_time = new_time\n # now we register the handler function when the button is pressed\nincrement_angle_button_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\ndecrement_angle_button_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\n\npwm.freq(SERVO_FREQ_HZ)\nold_angle = -1\n\nwhile True:\n # only print on change in the button_presses value\n if angle != old_angle:\n duty = ServoDuty(angle)\n print('new angle:', angle, 'duty: ', duty)\n pwm.duty_u16(duty)\n old_angle = angle\n
"},{"location":"kits/maker-pi-rp2040-robot/08-servo-lab/#sample-sweep-code","title":"Sample Sweep Code","text":"from machine import Pin, PWM\nimport time\n\nBUTTON_1_PIN = 20\nBUTTON_2_PIN = 21\n\nSERVO_1_PIN = 12\nSERVO_2_PIN = 13\nSERVO_3_PIN = 14\nSERVO_4_PIN = 15\nSERVO_FREQ_HZ = 50\nSERVO_MIN_DUTY = 1725\nSERVO_MAX_DUTY = 6378\n# this is ususlly standard across most servos\nSERVO_FREQ_HZ = 40\n\npwm = PWM(Pin(SERVO_2_PIN))\n\n# the two button on the Maker Pi RP2040\nclock_button_pin = machine.Pin(BUTTON_1_PIN, machine.Pin.IN, machine.Pin.PULL_DOWN)\ncounter_clock_button_pin = machine.Pin(BUTTON_2_PIN, machine.Pin.IN, machine.Pin.PULL_DOWN)\n\n# globals\nangle = 90\nlast_time = 0 # the last time we pressed the button\n\ndef button_pressed_handler(pin):\n global angle, last_time\n new_time = time.ticks_ms()\n # if it has been more that 1/5 of a second since the last event, we have a new event\n if (new_time - last_time) > 200:\n # this should be pin.id but it does not work\n if '20' in str(pin):\n angle +=1\n else:\n angle -=1\n # wrap around to first mode\n if mode >= mode_count: mode = 0\n if mode < 0: mode = mode_count - 1\n last_time = new_time\n\n# now we register the handler function when the button is pressed\nclock_button_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\ncounter_clock_button_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler) \n# return int( ( (0.0015*SERVO_FREQ_HZ) + ((angle/90) * (0.0005*SERVO_FREQ_HZ)) ) * 65535 )\n\n# Thisw will take in integers of range in (min and max) return a integer in the output range (min and max)\n# Used to convert one range of values into another using a linear function like the Arduino map() function\ndef convert(x, in_min, in_max, out_min, out_max):\n return (x - in_min) * (out_max - out_min) // (in_max - in_min) + out_min\n\n# -90 should generate 1725\n# 90 should generate 7973\n\nold_angle = -1\n\npwm.freq(50)\nwhile True:\n for angle in range(-90, 90):\n duty = convert(angle, -90, 90, SERVO_MIN_DUTY, SERVO_MAX_DUTY)\n print('angle:', angle, 'duty: ', duty)\n pwm.duty_u16(duty)\n old_angle = angle\n time.sleep(.01)\n for angle in range(90, -90, -1):\n duty = convert(angle, -90, 90, SERVO_MIN_DUTY, SERVO_MAX_DUTY)\n print('angle:', angle, 'duty: ', duty)\n pwm.duty_u16(duty)\n old_angle = angle\n time.sleep(.01)\n
"},{"location":"kits/maker-pi-rp2040-robot/08-servo-lab/#shutting-down-all-servos","title":"Shutting Down All Servos","text":"from machine import Pin, PWM\nimport time\n\nSERVO_1_PIN = 12\nSERVO_2_PIN = 13\nSERVO_3_PIN = 14\nSERVO_4_PIN = 15\n\nprint('shutting down all servos!')\nfor i in range(12, 16):\n print('Servo', i, 'shutting down')\n pwm1 = PWM(Pin(SERVO_1_PIN))\n pwm1.duty_u16(0)\n
"},{"location":"kits/maker-pi-rp2040-robot/08-servo-lab/#adding-cleanup-code","title":"Adding Cleanup Code","text":"PWM signals continue to be generated even after you do a STOP/RESET on your microcontroller. This could drain batteries and wear out your servo motors. To stop the servos from getting PWM signals you can add an interrupt to your code to catch these signals and set the PWM duty cycle back to zero. This
"},{"location":"kits/maker-pi-rp2040-robot/08-servo-lab/#references","title":"References","text":"MicroPython Reference Page - this page is not very helpful. The implication is that servo controls are standardized across MicroPython system. This does not appear to be the case.
"},{"location":"kits/maker-pi-rp2040-robot/09-i2c-scanner-test/","title":"I2C Scanner Test","text":"How do we know that our connection to the distance sensor is wired correctly? The quick way to test this is to run a program called the I2C scanner. It will return a list of all the devices it finds on the I2C bus.
We first run the I2C scanner program to verify that the sensor is connected correct and is responding to the I2C bus scan.
import machine\n# Pins on the Grove Connector 1 on the Maker Pi RP2040 are GP0 and GP1\nsda=machine.Pin(0)\nscl=machine.Pin(1)\ni2c=machine.I2C(0, sda=sda, scl=scl, freq=400000)\nprint(\"I2C device ID list:\", i2c.scan())\n
This should return a list of the devices it finds. If you just have the Time-of-Flight sensor it will look like this:
[41]\n``\n\n```py\ndevice_id = i2c.scan()[0]\n
"},{"location":"kits/maker-pi-rp2040-robot/09-i2c-scanner-test/#testing-for-the-time-of-flight-sensor","title":"Testing for the Time-of-Flight Sensor","text":"import machine\nsda=machine.Pin(0)\nscl=machine.Pin(1)\ni2c=machine.I2C(0, sda=sda, scl=scl, freq=400000)\n\n# i2c.scan() returns a list of devices that have been found\n# i2c.scan()[0] is the first device found\ndevice_id = i2c.scan()[0]\nprint(\"Device found at decimal\", device_id)\n\nif device_id == 41:\n print(\"TEST PASS\")\nelse:\n print(\"No device found at decimal 41\")\n print(\"TEST FAIL\")\n
"},{"location":"kits/maker-pi-rp2040-robot/10-time-of-flight-lab/","title":"Time of Flight Distance Sensor Lab","text":"In this lab we create a program that will show the distance measured by the Time-of-Flight sensor by printing the distance on the console and also displaying the distance on 11 blue LEDs.
First, make sure you have your driver for the Time-of-Flight sensor installed.
You can copy the code from here and save it in the file VL53L0X.py. Note the zero between the \"L\" and \"X\" in the file name, not the letter \"O\".
We use a non-linear distance scale as we get closer to an object. We store the numbers of each LED and the distance it should change in a lists:
blue_led_pins = [2, 3, 4, 5, 6, 7, 16, 17, 26, 27, 28]\ndist_scale = [2, 6, 10, 20, 30, 40, 50, 60, 80, 110, 150]\n
"},{"location":"kits/maker-pi-rp2040-robot/10-time-of-flight-lab/#calibration","title":"Calibration","text":"There are three numbers you can change when you calibrate the sensor:
ZERO_DIST = 60 # The value of the sensor when an object is 0 CM away\nMAX_DIST = 1200 # max raw distance we are able to read\nSCALE_DIST = .3 # multiplier for raw to calibrated distance in CM\n
"},{"location":"kits/maker-pi-rp2040-robot/10-time-of-flight-lab/#full-program","title":"Full Program","text":"# Demo for Maker Pi RP2040 board using the VL32L0X time of flight distance sensor\n# Note the driver I used came from here: https://github.com/CoderDojoTC/micropython/blob/main/src/drivers/VL53L0X.py\n# Perhaps derived from here: https://github.com/uceeatz/VL53L0X/blob/master/VL53L0X.py\n\n# This demo makes the blue LEDs show the distance and prints the distance on the console\nimport machine\nimport time\nimport VL53L0X\n\nsda=machine.Pin(0) # row one on our standard Pico breadboard\nscl=machine.Pin(1) # row two on our standard Pico breadboard\ni2c=machine.I2C(0, sda=sda, scl=scl, freq=400000)\n# print(\"Device found at decimal\", i2c.scan())\n\n# The Maker Pi RP2040 has 13 fantastic blue GPIO status LEDs which we can use 11\n# The distance scale is non linear\n# GP0 and GP1 will always be on since they are the I2C Data and Clock\nblue_led_pins = [2, 3, 4, 5, 6, 7, 16, 17, 26, 27, 28]\ndist_scale = [2, 6, 10, 20, 30, 40, 50, 60, 80, 110, 150]\nnumber_leds = len(blue_led_pins)\nled_ports = []\ndelay = .05\n\n# initial calibration parameters\nZERO_DIST = 60\nMAX_DIST = 1200 # max raw distance we are able to read\nSCALE_DIST = .3 # multiplier for raw to calibrated distance\n\n# create a list of the ports\nfor i in range(number_leds):\n led_ports.append(machine.Pin(blue_led_pins[i], machine.Pin.OUT))\n\n# Create a VL53L0X object\ntof = VL53L0X.VL53L0X(i2c)\n\n\n# get the normalized time-of-flight distance\ndef get_distance():\n global zero_dist, scale_factor\n tof_distance = tof.read()\n if tof_distance > MAX_DIST:\n return tof_distance\n # if our current time-of-flight distance is lower than our zero distance then reset the zero distance\n if tof_distance < ZERO_DIST:\n zero_dist = tof_distance\n return int((tof_distance - ZERO_DIST) * SCALE_DIST)\n\n# use the dist_scale to turn on LEDs\ndef led_show_dist(in_distance):\n global number_leds\n for led_index in range(0, number_leds):\n if in_distance > dist_scale[led_index]:\n led_ports[led_index].high()\n else:\n led_ports[led_index].low()\n\nprint('Using', number_leds, ' blue leds to show distance.')\n\n# blue up\nfor i in range(0, number_leds):\n led_ports[i].high()\n time.sleep(delay)\n led_ports[i].low()\n# blue down\nfor i in range(number_leds - 1, 0, -1):\n led_ports[i].high()\n time.sleep(delay)\n led_ports[i].low()\n\n# start our time-of-flight sensor\ntof.start()\n# autocalibrate the minimum distance\nmin_distance = 1000\n\n\n# loop forever\nwhile True:\n raw_distance = get_distance()\n # recalibrate if we have a new min distance\n if raw_distance < min_distance:\n min_distance = raw_distance\n calibrated_distance = raw_distance - min_distance\n print(raw_distance, calibrated_distance)\n led_show_dist(calibrated_distance)\n time.sleep(0.05)\n\n# clean up\ntof.stop()\n
"},{"location":"kits/maker-pi-rp2040-robot/10-time-of-flight-lab/#references","title":"References","text":"Kevin McAleer's GitHub Repo on the Vl53lx0 Kevin McAleer's 662 line driver - I am not sure we need all 662 lines of code. Kevin McAleer's Time of Flight Test
"},{"location":"kits/maker-pi-rp2040-robot/11-ping-lab/","title":"Ultrasonic Ping Sensor Lab","text":"The Grove sensors on our Maker Pi RP2040 only supply 3.3 volts. So the standard very popular low cost HC-SR04 will not work, since it requires 5 volts of power. We have two options. One is to get a separate 5V power source, but the other is to purchase the new HC-SR04P (for Pico?) sensor that will work with our 3.3 volt power on our Grove connector.
Using the Grove 4 connection wire the HC-SP04P sensor with the trigger on GPIO-16 (White cable) and the echo on GPIO-17 (Yellow cable), VCC (Red cable), and GND (Black cable)
All wired up
# Sample code to test HC-SR04 Ultrasonice Ping Sensor\n# Connect GND to any GND pin on the Pico\n# Connnect VCC to VBUS or 5 Volt power\n\nfrom machine import Pin, Timer\nimport utime\n\nTRIGGER_PIN = 16 # With USB on the top, this pin is the bottom left corner\nECHO_PIN = 17 # One up from bottom left corner\n\n# Init HC-SR04 pins\ntrigger = Pin(TRIGGER_PIN, Pin.OUT) # send trigger out to sensor\necho = Pin(ECHO_PIN, Pin.IN) # get the delay interval back\n\ndef ping():\n trigger.low()\n utime.sleep_us(2) # Wait 2 microseconds low\n trigger.high()\n utime.sleep_us(5) # Stay high for 5 miroseconds\n trigger.low()\n while echo.value() == 0:\n signaloff = utime.ticks_us()\n while echo.value() == 1:\n signalon = utime.ticks_us()\n timepassed = signalon - signaloff\n distance = (timepassed * 0.0343) / 2\n return distance\n\nwhile True:\n print(\"Distance:\", ping(), \"cm\")\n utime.sleep(.25)\n
More advanced version with sound
# Sample code to test HC-SR04 Ultrasonice Ping Sensor\n# Connect GND to any GND pin on the Pico\n# Connnect VCC to VBUS or 5 Volt power\n\nfrom machine import Pin, Timer, PWM\nimport utime\n\nTRIGGER_PIN = 16 # With USB on the top, this pin is the bottom left corner\nECHO_PIN = 17 # One up from bottom left corner\n\n# Init HC-SR04 pins\ntrigger = Pin(TRIGGER_PIN, Pin.OUT) # send trigger out to sensor\necho = Pin(ECHO_PIN, Pin.IN) # get the delay interval back\n\nBUZZER_PORT = 22\nbuzzer = PWM(Pin(BUZZER_PORT))\n\n# Note the non-linear increases in frequency - note that some are louder\ntone_freq = [100, 150, 210, 280, 350, 450, 580, 750, 850, 950, 1000]\ndef playtone(frequency):\n buzzer.duty_u16(1000)\n buzzer.freq(frequency)\n\ndef bequiet():\n buzzer.duty_u16(0)\n\ndef ping():\n trigger.low()\n utime.sleep_us(2) # Wait 2 microseconds low\n trigger.high()\n utime.sleep_us(5) # Stay high for 5 miroseconds\n trigger.low()\n while echo.value() == 0:\n signaloff = utime.ticks_us()\n while echo.value() == 1:\n signalon = utime.ticks_us()\n timepassed = signalon - signaloff\n distance = (timepassed * 0.0343) / 2\n return distance\n\nwhile True:\n dist=round(ping())\n print(\"Distance:\", dist, \"cm\")\n if dist < 20:\n print(\"Panic\")\n playtone(350)\n # Beep faster the closer you get\n utime.sleep(.05/(20/dist))\n bequiet()\n utime.sleep(.1)\n
"},{"location":"kits/maker-pi-rp2040-robot/11-ping-lab/#link-to-sample-ping-lab","title":"Link to Sample Ping Lab","text":"This code is very similar to the previous ping lab but with the different GPIO lines used.
Link to Standard Ping Lab
"},{"location":"kits/maker-pi-rp2040-robot/12-time-of-flight-sound-lab/","title":"Time of Flight Distance Sensor Test","text":"# Demo for Maker Pi RP2040 board\n\nfrom machine import Pin,PWM\nimport time\nimport VL53L0X\nbuzzer=PWM(Pin(22))\n\nsda=machine.Pin(0) # row one on our standard Pico breadboard\nscl=machine.Pin(1) # row two on our standard Pico breadboard\ni2c=machine.I2C(0, sda=sda, scl=scl, freq=400000)\n# print(\"Device found at decimal\", i2c.scan())\n\n# The Maker Pi RP2040 has 13 fantastic blue GPIO status LEDs\nblue_led_pins = [2, 3, 4, 5, 6, 7, 16, 17, 26, 27, 28]\n# dist_scale = [2, 4, 6, 8, 10, 13, 16, 20, 25, 35, 50, 75, 100]\ndist_scale = [2, 4, 6, 8, 10, 15, 20, 25, 50, 100, 150, 200, 300]\n\nnumber_leds = len(blue_led_pins)\nled_ports = []\ndelay = .05\n\n# calibration parameters\nzero_dist = 65 # distance measure when an object is about 1/2 cm away\nmax_dist = 350 # max distance we are able to read\nscale_factor = .2\n\n# create a list of the ports\nfor i in range(number_leds):\n led_ports.append(machine.Pin(blue_led_pins[i], machine.Pin.OUT))\n\n# Create a VL53L0X object\ntof = VL53L0X.VL53L0X(i2c)\n\n# blue up\nfor i in range(0, number_leds):\n led_ports[i].high()\n time.sleep(delay)\n led_ports[i].low()\n# blue down\nfor i in range(number_leds - 1, 0, -1):\n led_ports[i].high()\n time.sleep(delay)\n led_ports[i].low()\n\n# get the normalized time-of-flight distance\ndef get_distance():\n global zero_dist, scale_factor\n tof_distance = tof.read()\n if tof_distance > max_dist:\n return tof_distance\n # if our current time-of-flight distance is lower than our zero distance then reset the zero distance\n if tof_distance < zero_dist:\n zero_dist = tof_distance\n return int((tof_distance - zero_dist) * scale_factor)\n\ndef led_show_dist(in_distance):\n global number_leds\n for led_index in range(0, number_leds):\n if in_distance > dist_scale[led_index]:\n led_ports[led_index].high()\n else:\n led_ports[led_index].low()\n\ndef playtone(frequency):\n buzzer.duty_u16(1000)\n buzzer.freq(frequency)\n\ndef bequiet():\n buzzer.duty_u16(0)\n\ndef play_no_signal():\n playtone(100)\n time.sleep(0.1)\n bequiet()\n\ndef play_turn():\n playtone(500)\n time.sleep(0.1)\n bequiet()\n\n# start our time-of-flight sensor\ntof.start()\nvalid_distance = 1\n\n# loop forever\ndef main():\n while True:\n global valid_distance\n distance = get_distance()\n if distance > 1000:\n # only print if we used to have a valid distance\n if valid_distance == 1:\n print('no signal')\n\n valid_distance = 0\n else:\n print(distance)\n if distance < 30:\n play_turn()\n valid_distance = 1\n led_show_dist(distance)\n time.sleep(0.05)\n\n# clean up\n\n\n# This allows us to stop the sound by doing a Stop or Control-C which is a keyboard intrrup\ntry:\n main()\nexcept KeyboardInterrupt:\n print('Got ctrl-c')\nfinally:\n # Optional cleanup code\n print('turning off sound')\n buzzer.duty_u16(0)\n tof.stop()\n
"},{"location":"kits/maker-pi-rp2040-robot/20-collision-avoidance-robot/","title":"Maker Pi RP2040 Collision Avoidance Robot","text":"This robot works very similar to our standard CoderDojo Collision Avoidance Robot but all the pins are now configured to use the connections on the Maker Pi RP2040 board.
The board is mounted on a SmartCar Chassis and Grove Connector 0 is used to connect to a Time-of-Flight distance sensor that is using the I2C bus.
"},{"location":"kits/maker-pi-rp2040-robot/20-collision-avoidance-robot/#random-turn-direction","title":"Random Turn Direction","text":"if dist < TURN_DIST:\n play_reverse()\n reverse()\n sleep(REVERSE_TIME)\n # half right and half left turns\n if urandom.random() < .5:\n turn_right()\n play_turn_right()\n else:\n turn_left()\n play_turn_left()\n sleep(TURN_TIME)\n forward()\n
# Demo for Maker Pi RP2040 board\n\nfrom machine import Pin,PWM\nfrom time import sleep, sleep_ms\nimport urandom\nimport VL53L0X\n\n# Piezo Buzzer is on GP22\nbuzzer=PWM(Pin(22))\n\n# this is the max power level\nPOWER_LEVEL = 65025\n\n# Motor Pins are A: 8,9 and B: 10,11\nRIGHT_FORWARD_PIN = 8\nRIGHT_REVERSE_PIN = 9\nLEFT_FORWARD_PIN = 11\nLEFT_REVERSE_PIN = 10\n\n# our PWM objects\nright_forward = PWM(Pin(RIGHT_FORWARD_PIN))\nright_reverse = PWM(Pin(RIGHT_REVERSE_PIN))\nleft_forward = PWM(Pin(LEFT_FORWARD_PIN))\nleft_reverse = PWM(Pin(LEFT_REVERSE_PIN))\n\n\ndef turn_motor_on(pwm):\n pwm.duty_u16(65025)\n\ndef turn_motor_off(pwm):\n pwm.duty_u16(0)\n\ndef forward():\n turn_motor_on(right_forward)\n turn_motor_on(left_forward)\n turn_motor_off(right_reverse)\n turn_motor_off(left_reverse)\n\ndef reverse():\n turn_motor_on(right_reverse)\n turn_motor_on(left_reverse)\n turn_motor_off(right_forward)\n turn_motor_off(left_forward)\n\ndef turn_right():\n turn_motor_on(right_forward)\n turn_motor_on(left_reverse)\n turn_motor_off(right_reverse)\n turn_motor_off(left_forward)\n\ndef turn_left():\n turn_motor_on(right_reverse)\n turn_motor_on(left_forward)\n turn_motor_off(right_forward)\n turn_motor_off(left_reverse)\n\ndef stop():\n turn_motor_off(right_forward)\n turn_motor_off(right_reverse)\n turn_motor_off(left_forward)\n turn_motor_off(left_reverse)\n\n# Time of flight sensor is on the I2C bus on Grove connector 0\nsda=machine.Pin(0) # row one on our standard Pico breadboard\nscl=machine.Pin(1) # row two on our standard Pico breadboard\ni2c=machine.I2C(0, sda=sda, scl=scl, freq=400000)\n# print(\"Device found at decimal\", i2c.scan())\n\n# The Maker Pi RP2040 has 13 fantastic blue GPIO status LEDs\nblue_led_pins = [2, 3, 4, 5, 6, 7, 16, 17, 26, 27, 28]\n# dist_scale = [2, 4, 6, 8, 10, 13, 16, 20, 25, 35, 50, 75, 100]\ndist_scale = [2, 4, 6, 8, 10, 15, 20, 25, 50, 100, 150, 200, 300]\n\nnumber_leds = len(blue_led_pins)\nled_ports = []\ndelay = .05\n\n# calibration parameters\nzero_dist = 65 # distance measure when an object is about 1/2 cm away\nmax_dist = 350 # max distance we are able to read\nscale_factor = .2\n\n# create a list of the ports\nfor i in range(number_leds):\n led_ports.append(machine.Pin(blue_led_pins[i], machine.Pin.OUT))\n\n# Create a VL53L0X object\ntof = VL53L0X.VL53L0X(i2c)\n\n# blue up\nfor i in range(0, number_leds):\n led_ports[i].high()\n time.sleep(delay)\n led_ports[i].low()\n# blue down\nfor i in range(number_leds - 1, 0, -1):\n led_ports[i].high()\n time.sleep(delay)\n led_ports[i].low()\n\n# get the normalized time-of-flight distance\ndef get_distance():\n global zero_dist, scale_factor\n tof_distance = tof.read()\n if tof_distance > max_dist:\n return tof_distance\n # if our current time-of-flight distance is lower than our zero distance then reset the zero distance\n if tof_distance < zero_dist:\n zero_dist = tof_distance\n return int((tof_distance - zero_dist) * scale_factor)\n\ndef led_show_dist(in_distance):\n global number_leds\n for led_index in range(0, number_leds):\n if in_distance > dist_scale[led_index]:\n led_ports[led_index].high()\n else:\n led_ports[led_index].low()\n\ndef playtone(frequency):\n buzzer.duty_u16(1000)\n buzzer.freq(frequency)\n\ndef bequiet():\n buzzer.duty_u16(0)\n\ndef play_no_signal():\n playtone(100)\n time.sleep(0.1)\n bequiet()\n\ndef play_turn():\n playtone(500)\n sleep(0.1)\n bequiet()\n\n# start our time-of-flight sensor\ntof.start()\nvalid_distance = 1\n\n# loop forever\ndef main():\n global valid_distance\n while True: \n distance = get_distance()\n if distance > 1000:\n # only print if we used to have a valid distance\n if valid_distance == 1:\n print('no signal') \n valid_distance = 0\n else:\n print(distance)\n if distance < 30:\n play_turn()\n # back up for 1/2 second\n reverse()\n sleep(0.5)\n turn_right()\n sleep(0.75)\n forward()\n else:\n print('forward')\n forward()\n valid_distance = 1\n led_show_dist(distance)\n sleep(0.05)\n\n# clean up\n\n\n# This allows us to stop the sound by doing a Stop or Control-C which is a keyboard intrrup\ntry:\n main()\nexcept KeyboardInterrupt:\n print('Got ctrl-c')\nfinally:\n # Optional cleanup code\n print('turning off sound')\n buzzer.duty_u16(0)\n print('powering down all motors')\n stop()\n print('stopping time of flight sensor')\n tof.stop()\n
"},{"location":"kits/maker-pi-rp2040-robot/21-collision-avoidance-ping/","title":"Maker Pi RP2040 Collision Avoidance Robot With Ping Sensor","text":"This robot works very similar to our standard CoderDojo Collision Avoidance Robot but all the pins are now configured to use the connections on the Maker Pi RP2040 board.
The board is mounted on a SmartCar Chassis and Grove Connector 4 is used to connect the ultrasonic ping sensor. Connect the Trigger on white and Echo on yellow. The black should be connected to GND and the Red is connected to the VCC which on the
The robot has an initial mode of 0, which will run the blue LEDs and change colors on the Neopixels. By pressing the on-board button you will start the collision avoidance program.
"},{"location":"kits/maker-pi-rp2040-robot/21-collision-avoidance-ping/#robot-parameters","title":"Robot Parameters","text":"There are four different robot parameters you can adjust. They change the speed and distance before the robot backs up. You can also adjust the time the robots goes into reverse and the time it turns.
POWER_LEVEL = 35000 # max is \nTURN_DISTANCE = 20 # distance in cm we decide to turn - try 20\nREVERSE_TIME = .4 # how long we backup\nTURN_TIME = .4 # how long we turn\n
"},{"location":"kits/maker-pi-rp2040-robot/21-collision-avoidance-ping/#full-source-code","title":"Full Source Code","text":"# Demo for Maker Pi RP2040 board using Ping sensor\nfrom machine import Pin, PWM, Timer\nimport utime\nimport urandom\nfrom neopixel import Neopixel\n\n# Adjust these parameters to tune the collision avoidance behavior\n\nPOWER_LEVEL = 35000\nTURN_DISTANCE = 20 # distance we decide to turn - try 20\nREVERSE_TIME = .4 # how long we backup\nTURN_TIME = .4 # how long we turn\n\n# startup mode is 0 - motors off and LEDs flashing\n# mode 1 is slow\n# mode 2 is medium\n# mode 3 is fast\nmode = 0\n\n# Use the Grove 4 Connector and put trigger on white and echo on yellow\nTRIGGER_PIN = 16 # With USB on the top, this pin is the bottom left corner\nECHO_PIN = 17 # One up from bottom left corner\n\n# Init HC-SR04P pins\ntrigger = Pin(TRIGGER_PIN, Pin.OUT) # send trigger out to sensor\necho = Pin(ECHO_PIN, Pin.IN) # get the delay interval back\n\nfaster_pin = machine.Pin(20, machine.Pin.IN, machine.Pin.PULL_DOWN)\nslower_pin = machine.Pin(21, machine.Pin.IN, machine.Pin.PULL_DOWN)\n\nlast_time = 0 # the last time we pressed the button\n\n# This function gets called every time the button is pressed. The parameter \"pin\" is not used.\ndef button_pressed_handler(pin):\n global mode, last_time\n new_time = utime.ticks_ms()\n # if it has been more that 1/5 of a second since the last event, we have a new event\n if (new_time - last_time) > 200:\n # this should be pin.id but it does not work\n if '20' in str(pin):\n mode +=1\n else:\n mode -=1\n # deal with ends\n if mode > 4: mode = 2\n if mode < 0: mode = 0\n last_time = new_time\n\n# now we register the handler function when the button is pressed\nfaster_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\nslower_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\n\n# Piezo Buzzer is on GP22\nbuzzer=PWM(Pin(22))\n\nMAX_POWER_LEVEL = 65025\n\nMAX_DISTANCE = 100 # ignore anything above this\n\n# Motor Pins are A: 8,9 and B: 10,11\nRIGHT_FORWARD_PIN = 11\nRIGHT_REVERSE_PIN = 10\nLEFT_FORWARD_PIN = 9\nLEFT_REVERSE_PIN = 8\n\n# our PWM objects\nright_forward = PWM(Pin(RIGHT_FORWARD_PIN))\nright_reverse = PWM(Pin(RIGHT_REVERSE_PIN))\nleft_forward = PWM(Pin(LEFT_FORWARD_PIN))\nleft_reverse = PWM(Pin(LEFT_REVERSE_PIN))\n\n# returns distance in cm\ndef ping():\n print('in ping')\n trigger.low()\n utime.sleep_us(2) # Wait 2 microseconds low\n trigger.high()\n utime.sleep_us(5) # Stay high for 5 miroseconds\n trigger.low()\n while echo.value() == 0:\n signaloff = utime.ticks_us()\n print('echo is 1')\n while echo.value() == 1:\n signalon = utime.ticks_us()\n timepassed = signalon - signaloff\n distance = (timepassed * 0.0343) / 2\n print(distance)\n return int(distance)\n\ndef turn_motor_on(pwm):\n pwm.duty_u16(65025)\n\ndef turn_motor_off(pwm):\n pwm.duty_u16(0)\n\ndef forward():\n turn_motor_on(right_forward)\n turn_motor_on(left_forward)\n turn_motor_off(right_reverse)\n turn_motor_off(left_reverse)\n\ndef reverse():\n turn_motor_on(right_reverse)\n turn_motor_on(left_reverse)\n turn_motor_off(right_forward)\n turn_motor_off(left_forward)\n\ndef turn_right():\n turn_motor_on(right_forward)\n turn_motor_on(left_reverse)\n turn_motor_off(right_reverse)\n turn_motor_off(left_forward)\n\ndef turn_left():\n turn_motor_on(right_reverse)\n turn_motor_on(left_forward)\n turn_motor_off(right_forward)\n turn_motor_off(left_reverse)\n\ndef stop():\n turn_motor_off(right_forward)\n turn_motor_off(right_reverse)\n turn_motor_off(left_forward)\n turn_motor_off(left_reverse)\n\n# The Maker Pi RP2040 has 13 fantastic blue GPIO status LEDs\n# remove 16 and 17 since the are used for the ping sensor\nblue_led_pins = [0, 1, 2, 3, 4, 5, 6, 7, 26, 27, 28]\n# dist_scale = [2, 4, 6, 8, 10, 13, 16, 20, 25, 35, 50, 75, 100]\ndist_scale = [2, 4, 6, 8, 10, 15, 20, 25, 50, 100, 150, 200, 300]\n\nNUMBER_PIXELS = 2\nSTATE_MACHINE = 0\nNEOPIXEL_PIN = 18\n\n# The Neopixels on the Maker Pi RP2040 are the GRB variety, not RGB\nstrip = Neopixel(NUMBER_PIXELS, STATE_MACHINE, NEOPIXEL_PIN, \"GRB\")\nstrip.brightness(100)\n\nnumber_leds = len(blue_led_pins)\nled_ports = []\nred = (255, 0, 0)\norange = (255, 60, 0) # Gamma corrected from G=128 to be less like yellow\nyellow = (255, 150, 0)\ngreen = (0, 255, 0)\nblue = (0, 0, 255)\nindigo = (75, 0, 130) # purple?\nviolet = (138, 43, 226) # mostly pink\ncyan = (0, 255, 255)\nlightgreen = (100, 255, 100)\nwhite = (128, 128, 128) # not too bright\npink = (255, 128, 128)\ncolor_names = ('red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet', 'cyan', 'lightgreen', 'white')\nnum_colors = len(color_names)\ncolors = (red, orange, yellow, green, blue, indigo, violet, cyan, lightgreen, white, pink)\n\n# create a list of the ports\nfor i in range(number_leds):\n led_ports.append(machine.Pin(blue_led_pins[i], machine.Pin.OUT))\n\nLED_DELAY = .08\ndef run_lights():\n for i in range(0, number_leds):\n led_ports[i].high()\n strip.set_pixel(0, colors[i])\n strip.set_pixel(1, colors[i])\n strip.show()\n utime.sleep(LED_DELAY)\n led_ports[i].low()\n # blue down\n for i in range(number_leds - 1, 0, -1):\n led_ports[i].high()\n strip.set_pixel(0, colors[i])\n strip.set_pixel(1, colors[i])\n strip.show()\n utime.sleep(LED_DELAY)\n led_ports[i].low()\n\ndef led_show_dist(in_distance):\n global number_leds\n for led_index in range(0, number_leds):\n if in_distance > dist_scale[led_index]:\n led_ports[led_index].high()\n else:\n led_ports[led_index].low()\n\ndef play_no_signal():\n playnote(100, 0.1)\n sound_off()\n\ndef play_turn():\n playnote(500, .1)\n sound_off()\n\ndef setfreq(frequency):\n buzzer.freq(frequency)\n\ndef playnote(frequency, time):\n buzzer.duty_u16(1000)\n setfreq(frequency)\n utime.sleep(time)\n\ndef sound_off():\n buzzer.duty_u16(0)\n\ndef rest(time):\n buzzer.duty_u16(0)\n utime.sleep(time)\n\ndef play_startup():\n playnote(600, .2)\n rest(.05)\n playnote(600, .2)\n rest(.05)\n playnote(600, .2)\n rest(.1)\n playnote(800, .4)\n sound_off()\n\nvalid_distance = 1\n# loop forever\ndef main():\n global valid_distance\n print(\"running main()\")\n\n play_startup()\n\n while True:\n if mode == 0:\n stop()\n run_lights()\n else:\n distance = ping()\n print('Distance:', distance)\n if distance > MAX_DISTANCE:\n # only print if we used to have a valid distance\n if valid_distance == 1:\n print('no signal') \n valid_distance = 0\n else:\n print(distance)\n if distance < TURN_DISTANCE:\n play_turn()\n # back up for a bit\n reverse()\n utime.sleep(REVERSE_TIME)\n # half right and half left turns\n if urandom.random() < .5:\n turn_right()\n else:\n turn_left()\n utime.sleep(TURN_TIME)\n forward()\n else:\n print('forward')\n forward()\n valid_distance = 1\n led_show_dist(distance)\n utime.sleep(0.05)\n\n# clean up\n\n# This allows us to stop the sound and motors when we do a Stop or Control-C which is a keyboard interrupt\ntry:\n main()\nexcept KeyboardInterrupt:\n print('Got ctrl-c')\nfinally:\n # Optional cleanup code\n print('turning off sound')\n buzzer.duty_u16(0)\n print('shutting motors down')\n stop()\n
"},{"location":"kits/maker-pi-rp2040-robot/21-collision-avoidance-ping/#experiments","title":"Experiments","text":"This robot was inspired by my friend, Michael York.
Microswitches can be purchased for under $1. They can be mounted on the front of our robot. When the robot hits a wall in front of it the switch will open (or close) and the robot controller can make the robot go in reverse or turn.
In the example below, we attached a stiff wire to the lever of the microswitch.
In the example below, we connected three microswitches to the front of our robot.
If the left switch is activated, the robot should turn to the right. If the right switch is activated, the robot should go to the left.
This image shows how we used two of the Grove connectors to read in the values of the switches.
"},{"location":"kits/maker-pi-rp2040-robot/23-microswitch-bot/#testing-switches","title":"Testing Switches","text":"The following code can be used to test your switches. A line on the console prints out which of the three switches are activated using the pin value()
function.
from machine import Pin\nfrom time import sleep\n\n# GPIO is the internal built-in LED\nled0 = Pin(0, Pin.OUT)\nled1 = Pin(1, Pin.OUT)\nled2 = Pin(2, Pin.OUT)\n\n# input on the lower left of the Pico using a built-in pull-down resistor to keep the value from floating\nmiddle_switch = Pin(7, Pin.IN, Pin.PULL_DOWN) \nright_switch = Pin(28, Pin.IN, Pin.PULL_DOWN)\nleft_switch = Pin(27, Pin.IN, Pin.PULL_DOWN)\n\nwhile True:\n if middle_switch.value(): # if the value changes\n led0.on()\n print('middle')\n else: led0.off()\n\n if right_switch.value(): # if the value changes\n led1.on()\n print('right')\n else: led1.off()\n\n if left_switch.value(): # if the value changes\n led2.on()\n print('left')\n else: led2.off()\n sleep(.1)\n
"},{"location":"kits/maker-pi-rp2040-robot/23-microswitch-bot/#sample-collision-avoidance-robot-code","title":"Sample Collision Avoidance Robot Code","text":"from machine import Pin, PWM\nfrom time import sleep\n\n# GPIO is the internal built-in LED\nled0 = Pin(0, Pin.OUT)\nled1 = Pin(1, Pin.OUT)\nled2 = Pin(2, Pin.OUT)\n\n# input on the lower left of the Pico using a built-in pull-down resistor to keep the value from floating\nmiddle_switch = Pin(7, Pin.IN, Pin.PULL_DOWN) \nright_switch = Pin(28, Pin.IN, Pin.PULL_DOWN)\nleft_switch = Pin(27, Pin.IN, Pin.PULL_DOWN)\n\n# Go slow to avoid bending wires\nPOWER_LEVEL = 25000 # max is 65000\n\n# These values depend on motor wiring\nRIGHT_FORWARD_PIN = 10\nRIGHT_REVERSE_PIN = 11\nLEFT_FORWARD_PIN = 9\nLEFT_REVERSE_PIN = 8\n\nright_forward = PWM(Pin(RIGHT_FORWARD_PIN))\nright_reverse = PWM(Pin(RIGHT_REVERSE_PIN))\nleft_forward = PWM(Pin(LEFT_FORWARD_PIN))\nleft_reverse = PWM(Pin(LEFT_REVERSE_PIN))\n\ndef turn_motor_on(pwm):\n pwm.duty_u16(POWER_LEVEL)\n\ndef turn_motor_off(pwm):\n pwm.duty_u16(0)\n\ndef forward():\n turn_motor_on(right_forward)\n turn_motor_on(left_forward)\n turn_motor_off(right_reverse)\n turn_motor_off(left_reverse)\n\ndef reverse():\n turn_motor_on(right_reverse)\n turn_motor_on(left_reverse)\n turn_motor_off(right_forward)\n turn_motor_off(left_forward)\n\ndef turn_right():\n turn_motor_on(right_forward)\n turn_motor_on(left_reverse)\n turn_motor_off(right_reverse)\n turn_motor_off(left_forward)\n\ndef turn_left():\n turn_motor_on(right_reverse)\n turn_motor_on(left_forward)\n turn_motor_off(right_forward)\n turn_motor_off(left_reverse)\n\ndef stop():\n turn_motor_off(right_forward)\n turn_motor_off(right_reverse)\n turn_motor_off(left_forward)\n turn_motor_off(left_reverse)\n\ndef main():\n while True:\n if middle_switch.value(): # if the value changes\n print('middle')\n led0.on()\n reverse()\n sleep(1)\n turn_right()\n sleep(.75)\n forward()\n else:\n led0.off()\n forward()\n\n if right_switch.value(): # if the value changes\n print('right')\n led1.on()\n reverse()\n sleep(.75)\n turn_left()\n sleep(.5)\n forward()\n else:\n led1.off()\n forward()\n\n if left_switch.value(): # if the value changes\n led2.on()\n print('left')\n reverse()\n sleep(.75)\n turn_right()\n sleep(.5)\n forward()\n else:\n led2.off()\n forward()\n\nprint('middle', middle_switch.value())\nprint('left', left_switch.value())\nprint('right', right_switch.value())\n\ntry:\n main()\nexcept KeyboardInterrupt:\n print('Got ctrl-c')\nfinally:\n # Optional cleanup code\n print('turning off sound')\n # sound_off()\n print('turning off motors')\n stop()\n
"},{"location":"kits/maker-pi-rp2040-robot/24-ping-servo-meter/","title":"Cytron Maker Pi RP2040 Ping Servo Meter Bot","text":"This robot works very similar to our standard CoderDojo Collision Avoidance Robot. However it adds a 180 degree servo to show the distance to the object in front of it. It also uses a OLED display to present instructions and display the distance to the object.
This program was contributed by Joe Glenn for the Minneapolis Bakken Museum Droid December event in December 2021.
"},{"location":"kits/maker-pi-rp2040-robot/24-ping-servo-meter/#test-servo","title":"Test Servo","text":"This program tests the servo by sweeping the angle from 0 to 180 and back.
# servo sweep test\n# Brown: GND\n# Orange/Red : VCC\n# Yellow: Signal\n#\n# Time for high level (Radio Shack Micro-servo @ 5V)\n# 0.5 ms : 0 degree\n# 1.0 ms : 45 degree\n# 1.5 ms : 90 degree\n# 2.0 ms : 135 degree\n# 2.5 ms : 180 degree\n\nfrom machine import Pin, PWM\nfrom time import sleep\n\nSERVO_PIN = 15\nservoPin = PWM(Pin(SERVO_PIN))\nservoPin.freq(50)\n\ndef servo(degrees):\n if degrees > 180: degrees=180\n if degrees < 0: degrees=0\n maxDuty=8000 # duty*100\n minDuty=2000 # duty*100\n #maxDuty=2000 # test\n #minDuty=8000 # test\n newDuty=minDuty+(maxDuty-minDuty)*(degrees/180)\n servoPin.duty_u16(int(newDuty))\n\nwhile True:\n\n for degree in range(0,180,1):\n servo(degree)\n sleep(0.01)\n print(\"increasing -- \"+str(degree))\n\n for degree in range(180, 0, -1):\n servo(degree)\n sleep(0.01)\n print(\"decreasing -- \"+str(degree))\n
"},{"location":"kits/maker-pi-rp2040-robot/24-ping-servo-meter/#main-python-code","title":"Main Python Code","text":"# Demo for Maker Pi RP2040 board using Ping sensor, servo and i2c display\n\nfrom machine import Pin, PWM, Timer\nimport utime\nimport urandom\nfrom ssd1306 import SSD1306_I2C\nfrom neopixel import Neopixel\nfrom machine import Pin, I2C\nfrom ssd1306 import SSD1306_I2C\nimport framebuf\nimport math\nimport utime\n#\n# Ping Sensor\n# Use the Grove 4 Connector and put trigger on white and echo on yellow\n#\nPING_TRIGGER_PIN = 7 # GP7\nPING_ECHO_PIN = 28 # GP28\n\n#\n# i2c OLED 128x32\n#\nOLED_SDA_PIN = 26 # GP26\nOLED_SCL_PIN = 27 # GP27\n\n#\n# Servo\n# GND: Brown\n# VCC: Orange/Red\n# GP15 Yellow: Signal\n#\n# Time for high level (Radio Shack Micro-servo @ 5V)\n# 0.5 ms : 0 degree\n# 1.0 ms : 45 degree\n# 1.5 ms : 90 degree\n# 2.0 ms : 135 degree\n# 2.5 ms : 180 degree\nSERVO_PIN = 15\n\n# IQR Pins\nFASTER_PIN = 20\nSLOWER_PIN = 21\n\n# built-in Buzzer\nBUZZER_PIN = 22\n\n# Adjust these parameters to tune the collision avoidance behavior\n\nPOWER_LEL = 35000\nTURN_DISTANCE = 20 # distance we decide to turn - try 20\nREVERSE_TIME = .4 # how long we backup\nTURN_TIME = .4 # how long we turn\n\n# startup mode is 0 - motors off and LEDs flashing\n# mode 1 is slow\n# mode 2 is medium\n# mode 3 is fast\nmode = 0\n\n# Init HC-SR04P pins\ntrigger = Pin(PING_TRIGGER_PIN, Pin.OUT) # send trigger out to sensor\necho = Pin(PING_ECHO_PIN, Pin.IN) # get the delay interval back\n\nfaster_pin = machine.Pin(FASTER_PIN, machine.Pin.IN, machine.Pin.PULL_DOWN)\nslower_pin = machine.Pin(SLOWER_PIN, machine.Pin.IN, machine.Pin.PULL_DOWN)\n\nlast_time = 0 # the last time we pressed the button\n\n#\n# DISPLAY STUFF\n#\n# Display Image & text on I2C driven ssd1306 OLED display \n\n\nWIDTH = 128 # oled display width\nHEIGHT = 32 # oled display height\n\n# Explicit Method\nsda=machine.Pin(OLED_SDA_PIN)\nscl=machine.Pin(OLED_SCL_PIN)\ni2c=machine.I2C(1,sda=sda, scl=scl, freq=40000) # 400k is too fast and has issues\nprint( 'i2c={:02X}'.format( i2c.scan()[0] ) )\n#print(help(i2c))\n#print(help(i2c.init))\n#print(help(i2c.scan))\n#print(help(i2c.start))\n#print(help(i2c.stop))\n#print(help(i2c.readinto))\n#print(help(i2c.write))\n#print(help(i2c.readfrom))\n#print(help(i2c.readfrom_into))\n#print(help(i2c.writeto))\n#print(help(i2c.writevto))\n#print(help(i2c.readfrom_mem))\n#print(help(i2c.readfrom_mem_into))\n#print(help(i2c.writeto_mem))\n#exit\n\noled = SSD1306_I2C(128, 32, i2c)\n\n# Raspberry Pi logo as 32x32 bytearray\nbuffer = bytearray(b\"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00|?\\x00\\x01\\x86@\\x80\\x01\\x01\\x80\\x80\\x01\\x11\\x88\\x80\\x01\\x05\\xa0\\x80\\x00\\x83\\xc1\\x00\\x00C\\xe3\\x00\\x00~\\xfc\\x00\\x00L'\\x00\\x00\\x9c\\x11\\x00\\x00\\xbf\\xfd\\x00\\x00\\xe1\\x87\\x00\\x01\\xc1\\x83\\x80\\x02A\\x82@\\x02A\\x82@\\x02\\xc1\\xc2@\\x02\\xf6>\\xc0\\x01\\xfc=\\x80\\x01\\x18\\x18\\x80\\x01\\x88\\x10\\x80\\x00\\x8c!\\x00\\x00\\x87\\xf1\\x00\\x00\\x7f\\xf6\\x00\\x008\\x1c\\x00\\x00\\x0c \\x00\\x00\\x03\\xc0\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\")\n\n# Load the raspberry pi logo into the framebuffer (the image is 32x32)\nfb = framebuf.FrameBuffer(buffer, 32, 32, framebuf.MONO_HLSB)\n\ndef blk():\n oled.fill(0)\n oled.show()\n\ndef horiz(l,t,r,c): # left, right , top\n n = r-l+1 # Horizontal line\n for i in range(n):\n oled.pixel(l + i, t, c)\n\ndef vert(l,t,b,c): # left, top, bottom\n n = b-t+1 # Vertical line\n for i in range(n):\n oled.pixel(l, t+i,c)\n\ndef box(l,t,r,b,c): # left, top, right, bottom\n horiz(l,t,r,c) # Hollow rectangle\n horiz(l,b,r,c)\n vert(l,t,b,c)\n vert(r,t,b,c)\n\ndef ring2(cx,cy,r,c): # Centre (x,y), radius, colour\n for angle in range(0, 90, 2): # 0 to 90 degrees in 2s\n y3=int(r*math.sin(math.radians(angle)))\n x3=int(r*math.cos(math.radians(angle)))\n oled.pixel(cx-x3,cy+y3,c) # 4 quadrants\n oled.pixel(cx-x3,cy-y3,c)\n oled.pixel(cx+x3,cy+y3,c)\n oled.pixel(cx+x3,cy-y3,c)\n\n#print(help(oled.text()))\n#print(help())\n#help('modules')\n#help(oled)\n#help(oled.text)\n#help(framebuf.FrameBuffer)\n#help(framebuf.FrameBuffer.help())\n\n# Clear the oled display in case it has junk on it.\noled.fill(0) # Black\n\n# Blit the image from the framebuffer to the oled display\noled.blit(fb, 96, 0)\n\n# Basic stuff\noled.text(\"Raspberry Pi\",5,5)\noled.text(\"RP2040\",5,15)\noled.text(\"press GP21\",5,25)\noled.pixel(10,60,1)\n\n#ring2(50,43,20,1) # Empty circle \n# Finally update the oled display so the image & text is displayed\noled.show()\nutime.sleep(1)\n\n\n#\n# Back to the motor control stuff. (sorry... i'm soppy today)\n# \n# This function gets called every time the button is pressed. The parameter \"pin\" is not used.\ndef button_pressed_handler(pin):\n global mode, last_time\n new_time = utime.ticks_ms()\n # if it has been more that 1/5 of a second since the last event, we have a new event\n if (new_time - last_time) > 200:\n # this should be pin.id but it does not work\n if '21' in str(pin):\n mode +=1\n else:\n mode -=1\n # deal with ends\n if mode > 4: mode = 2\n if mode < 0: mode = 0\n last_time = new_time\n\n# now we register the handler function when the button is pressed\nfaster_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\nslower_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\n\n# Piezo Buzzer is on GP22\nbuzzer=PWM(Pin(BUZZER_PIN))\n\nMAX_POWER_LEVEL = 65025\n\nMAX_DISTANCE = 100 # ignore anything above this\n\n# Motor Pins are A: 8,9 and B: 10,11\nRIGHT_FORWARD_PIN = 11 # this must be wired backword?\nRIGHT_REVERSE_PIN = 10 \nLEFT_FORWARD_PIN = 9\nLEFT_REVERSE_PIN = 8\n\n# our PWM objects\nright_forward = PWM(Pin(RIGHT_FORWARD_PIN))\nright_reverse = PWM(Pin(RIGHT_REVERSE_PIN))\nleft_forward = PWM(Pin(LEFT_FORWARD_PIN))\nleft_reverse = PWM(Pin(LEFT_REVERSE_PIN))\n\n# returns distance in cm\ndef ping():\n #print('in ping')\n trigger.low()\n utime.sleep_us(2) # Wait 2 microseconds low\n trigger.high()\n utime.sleep_us(5) # Stay high for 5 miroseconds\n trigger.low()\n while echo.value() == 0:\n signaloff = utime.ticks_us()\n #print('echo is 1')\n while echo.value() == 1:\n signalon = utime.ticks_us()\n timepassed = signalon - signaloff\n distance = (timepassed * 0.0343) / 2\n print(distance)\n\n return int(distance)\n\ndef turn_motor_on(pwm):\n #pwm.duty_u16(65025)\n pwm.duty_u16(16000)\n\ndef turn_motor_off(pwm):\n pwm.duty_u16(0)\n\ndef forward():\n turn_motor_on(right_forward)\n turn_motor_on(left_forward)\n turn_motor_off(right_reverse)\n turn_motor_off(left_reverse)\n\ndef reverse():\n turn_motor_on(right_reverse)\n turn_motor_on(left_reverse)\n turn_motor_off(right_forward)\n turn_motor_off(left_forward)\n\ndef turn_right():\n turn_motor_on(right_forward)\n turn_motor_on(left_reverse)\n turn_motor_off(right_reverse)\n turn_motor_off(left_forward)\n\ndef turn_left():\n turn_motor_on(right_reverse)\n turn_motor_on(left_forward)\n turn_motor_off(right_forward)\n turn_motor_off(left_reverse)\n\ndef stop():\n turn_motor_off(right_forward)\n turn_motor_off(right_reverse)\n turn_motor_off(left_forward)\n turn_motor_off(left_reverse)\n\n# The Maker Pi RP2040 has 13 fantastic blue GPIO status LEDs\nblue_led_pins = [0, 1, 2, 3, 4, 5, 6, 7, 26, 27, 28]\nblue_led_pins.remove(PING_TRIGGER_PIN)\nblue_led_pins.remove(PING_ECHO_PIN)\nblue_led_pins.remove(OLED_SDA_PIN)\nblue_led_pins.remove(OLED_SCL_PIN)\n\n\n# dist_scale = [2, 4, 6, 8, 10, 13, 16, 20, 25, 35, 50, 75, 100]\ndist_scale = [2, 4, 6, 8, 10, 15, 20, 25, 50, 100, 150, 200, 300]\n\nNUMBER_PIXELS = 2\nSTATE_MACHINE = 0\nNEOPIXEL_PIN = 18\n\n# The Neopixels on the Maker Pi RP2040 are the GRB variety, not RGB\nstrip = Neopixel(NUMBER_PIXELS, STATE_MACHINE, NEOPIXEL_PIN, \"GRB\")\nstrip.brightness(100)\n\nnumber_leds = len(blue_led_pins)\nled_ports = []\nred = (255, 0, 0)\norange = (255, 60, 0) # Gamma corrected from G=128 to be less like yellow\nyellow = (255, 150, 0)\ngreen = (0, 255, 0)\nblue = (0, 0, 255)\nindigo = (75, 0, 130) # purple?\nviolet = (138, 43, 226) # mostly pink\ncyan = (0, 255, 255)\nlightgreen = (100, 255, 100)\nwhite = (128, 128, 128) # not too bright\npink = (255, 128, 128)\ncolor_names = ('red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet', 'cyan', 'lightgreen', 'white')\nnum_colors = len(color_names)\ncolors = (red, orange, yellow, green, blue, indigo, violet, cyan, lightgreen, white, pink)\n\n# create a list of the ports\nfor i in range(number_leds):\n led_ports.append(machine.Pin(blue_led_pins[i], machine.Pin.OUT))\n\nLED_DELAY = .08\ndef run_lights():\n for i in range(0, number_leds):\n led_ports[i].high()\n strip.set_pixel(0, colors[i])\n strip.set_pixel(1, colors[i])\n strip.show()\n utime.sleep(LED_DELAY)\n led_ports[i].low()\n # blue down\n for i in range(number_leds - 1, 0, -1):\n led_ports[i].high()\n strip.set_pixel(0, colors[i])\n strip.set_pixel(1, colors[i])\n strip.show()\n utime.sleep(LED_DELAY)\n led_ports[i].low()\n\ndef led_show_dist(in_distance):\n global number_leds\n for led_index in range(0, number_leds):\n if in_distance > dist_scale[led_index]:\n led_ports[led_index].high()\n else:\n led_ports[led_index].low()\n\ndef play_no_signal():\n playnote(100, 0.1)\n sound_off()\n\ndef play_turn():\n playnote(500, .1)\n sound_off()\n\ndef setfreq(frequency):\n buzzer.freq(frequency)\n\ndef playnote(frequency, time):\n buzzer.duty_u16(1000)\n setfreq(frequency)\n utime.sleep(time)\n\ndef sound_off():\n buzzer.duty_u16(0)\n\ndef rest(time):\n buzzer.duty_u16(0)\n utime.sleep(time)\n\ndef play_startup():\n playnote(600, .2)\n rest(.05)\n playnote(600, .2)\n rest(.05)\n playnote(600, .2)\n rest(.1)\n playnote(800, .4)\n sound_off()\n\ndef servo(degrees):\n if degrees > 180: degrees=180\n if degrees < 0: degrees=0\n maxDuty=8000 # duty*100\n minDuty=2000 # duty*100\n #maxDuty=2000 # test\n #minDuty=8000 # test\n newDuty=minDuty+(maxDuty-minDuty)*(degrees/180)\n servoPin.duty_u16(int(newDuty))\n\nservoPin = PWM(Pin(SERVO_PIN))\nservoPin.freq(50)\n\nvalid_distance = 1\n\ndef main():\n global valid_distance\n print(\"running main()\")\n\n play_startup()\n oled_count=0 # repeat every\n oled_count_max=0 # times through loop\n servo_count=0 \n servo_count_max=0 # when to update\n servo_degrees = 0\n servo(servo_degrees) # start in that pos\n\n # loop forever\n while True:\n if mode == 0:\n stop()\n run_lights()\n else:\n distance = ping()\n print('Distance:', distance)\n oled_count += 1\n if oled_count > oled_count_max:\n oled.fill(0) # Black\n oled.text(\"Distance:\",5,5)\n oled.text(\"{:f}\".format(distance),5,15)\n oled.show()\n oled_count = 0\n\n servo_count += 1\n if servo_count > servo_count_max:\n if distance > MAX_DISTANCE:\n servo_degrees = 0\n else:\n servo_degrees = 180-distance/MAX_DISTANCE*180\n servo(servo_degrees)\n servo_count = 0\n\n if distance > MAX_DISTANCE:\n # only print if we used to have a valid distance\n if valid_distance == 1:\n print('no signal') \n valid_distance = 0\n else:\n print(distance)\n #note=distance*10\n #playnote(note, .025)\n\n if distance < TURN_DISTANCE:\n play_turn()\n # back up for a bit\n reverse()\n utime.sleep(REVERSE_TIME)\n # half right and half left turns\n if urandom.random() < .5:\n turn_right()\n else:\n turn_left()\n utime.sleep(TURN_TIME)\n forward()\n else:\n print('forward')\n forward()\n valid_distance = 1\n led_show_dist(distance)\n utime.sleep(0.05)\n\n# clean up\n\n# This allows us to stop the sound and motors when we do a Stop or Control-C which is a keyboard interrupt\ntry:\n main()\nexcept KeyboardInterrupt:\n print('Got ctrl-c')\nexcept Exception as e: print(e)\nfinally:\n # Optional cleanup code\n print('turning off sound')\n buzzer.duty_u16(0)\n print('shutting motors down')\n stop()\n
"},{"location":"kits/maker-pi-rp2040-robot/25-line-follower/","title":"Line Follower Robot","text":"Line following robot projects is a favorite project for our students. They teach the students the concept of feedback to keep a robot following a line on a track or on the floor. They are not easy to get working and require careful calibration of the sensor sensitivity and carefully adjusting the right and left motor power to keep the robot on track. Once the student gets the robot working it is a time for celebration!
The total cost of the kit is about $20.
"},{"location":"kits/maker-pi-rp2040-robot/25-line-follower/#parts-list","title":"Parts List","text":"Part Name Description Price Link Cytron Board Main board with RP2040 and motor driver. Kits come with Grove connectors and a screwdriver. $12 SmartCar Chassis SmartCar chassis with wheels and 4 AA battery pack IR Sensors (2) Low cost IR light sensors $1"},{"location":"kits/maker-pi-rp2040-robot/25-line-follower/#ir-sensors","title":"IR Sensors","text":"We can purchase IR sensors on eBay for about $1 each in quantity 10. The sensors send a digital 0/1 signal to the microcontroller depending on if they are over the line. Our job is to write some MicroPython code to keep the robot following the line. Each IR sensor has a small trim potentiometer that we must adjust for the correct sensitivity for each room.
Each IR sensor has a small trim potentiometer that we must adjust for the correct sensitivity for each room. Some of our rooms have a white floor with a black line and some of our rooms have a dark floor with a white line. You may have to adjust both the trim potentiometer and the logic of the code for different situations.
Use the Grove connectors to hook up the IR sensors. I used the red (VCC), black (GDN) and white wires (Data) and I just cut off the yellow wires to keep them out of the way. I then connected the Grove connectors to ports 2 and 3 of the Cytron board.
I connected the motors to the MOTOR 1 and MOTOR 2 headers with a screwdriver and I hooked the battery wires up to the power header of the board.
from machine import Pin\nfrom utime import sleep\n\nRIGHT_SENSOR_PIN = 2\nLEFT_SENSOR_PIN = 4\n\nright_sensor = Pin(RIGHT_SENSOR_PIN)\nleft_sensor = Pin(LEFT_SENSOR_PIN)\n\nwhile True:\n r = right_sensor.value()\n l = left_sensor.value()\n print(\"r\", r, \"l=\", l)\n if r == 0:\n print(\"right over white\")\n if l == 0:\n print(\"left over white\")\n sleep(.2)\n
"},{"location":"kits/maker-pi-rp2040-robot/25-line-follower/#full-source-code","title":"Full Source Code","text":"from machine import Pin, PWM\nfrom time import sleep\n\n# sensor setup\nRIGHT_SENSOR_PIN = 2\nLEFT_SENSOR_PIN = 4\n\nright_sensor = Pin(RIGHT_SENSOR_PIN)\nleft_sensor = Pin(LEFT_SENSOR_PIN)\n\n# speaker pin on the Cytron Maker Pi RP2040\nSPEAKER_PIN = 22\n# create a Pulse Width Modulation Object on this pin\nspeaker = PWM(Pin(SPEAKER_PIN))\n# set the duty cycle\nspeaker.duty_u16(1000)\n\n# Motor setup\nRIGHT_FORWARD_PIN = 11\nRIGHT_REVERSE_PIN =10\nLEFT_FORWARD_PIN = 8\nLEFT_REVERSE_PIN = 9\n\nright_forward = PWM(Pin(RIGHT_FORWARD_PIN))\nright_reverse = PWM(Pin(RIGHT_REVERSE_PIN))\nleft_forward = PWM(Pin(LEFT_FORWARD_PIN))\nleft_reverse = PWM(Pin(LEFT_REVERSE_PIN))\n\nMAX_POWER_LEVEL = 65025\nQUARTER_POWER = 65025 >> 2\nSLOW_DRIVE_POWER = 16000\nBOOST_LEVEL = 15000\n\n# while True:\ndef spin_wheel(pwm):\n pwm.duty_u16(SLOW_DRIVE_POWER)\n sleep(2)\n pwm.duty_u16(0)\n sleep(1)\n\ndef forward():\n right_forward.duty_u16(SLOW_DRIVE_POWER)\n right_reverse.duty_u16(0)\n left_forward.duty_u16(SLOW_DRIVE_POWER)\n left_reverse.duty_u16(0)\n speaker.duty_u16(0)\n\ndef right():\n right_forward.duty_u16(SLOW_DRIVE_POWER - BOOST_LEVEL)\n right_reverse.duty_u16(0)\n left_forward.duty_u16(SLOW_DRIVE_POWER + BOOST_LEVEL)\n left_reverse.duty_u16(0)\n speaker.duty_u16(1000)\n speaker.freq(800)\n\ndef left():\n right_forward.duty_u16(SLOW_DRIVE_POWER + BOOST_LEVEL)\n right_reverse.duty_u16(0)\n left_forward.duty_u16(SLOW_DRIVE_POWER - BOOST_LEVEL)\n left_reverse.duty_u16(0)\n speaker.duty_u16(1000)\n speaker.freq(1000)\n\ndef stop():\n right_forward.duty_u16(0)\n right_reverse.duty_u16(0)\n left_forward.duty_u16(0)\n left_reverse.duty_u16(0)\n speaker.duty_u16(0)\n\ndef main():\n while True:\n r = right_sensor.value()\n l = left_sensor.value()\n if r == 0 and l == 1:\n print(\"right over white - turning left\")\n right()\n if l == 0:\n print(\"left over white\")\n left()\n else:\n forward()\n\n# end of main()\n\ntry:\n main()\nexcept KeyboardInterrupt:\n print('Got ctrl-c')\nfinally:\n # Cleanup code\n print('Cleaning up')\n print('Powering down all motors now.')\n stop()\n
"},{"location":"kits/neopixel/","title":"NeoPixel Kit","text":"This is a low-cost (under $10) kit that we use in many of our classes. It contains the following parts:
These lessons will assume you have these parts and can connect up to the Pico to your laptop using a USB cable. If you have a Mac with a USB-C connector you may need to provide a USB-C to USB-A adaptor.
"},{"location":"kits/neopixel/#sample-labs","title":"Sample Labs","text":"We start with the sample code in the Basics/NeoPixel area.
We then show how you can load full programs. Here is the source code:
NeoPixel Samples Source Code
"},{"location":"kits/neopixel/#getting-started","title":"Getting Started","text":"Getting Started Lab
"},{"location":"kits/neopixel/#_1","title":"Introduction","text":""},{"location":"kits/neopixel/01-getting-started/","title":"Getting Started with the NeoPixel Kit","text":"Your kit should have several parts. Place them all on a table and hook them up. When you power on the kit with your USB cable the default main.py
program will be running.
Button Basics
"},{"location":"kits/neopixel/03-buttons/#working-with-two-buttons","title":"Working with Two Buttons","text":""},{"location":"kits/neopixel/03-buttons/#changing-color-with-buttons","title":"Changing Color With Buttons","text":""},{"location":"kits/neopixel/03-buttons/#using-modes","title":"Using Modes","text":"Our LED kit can run a single \"main\" loop forever. But we often want to have the main loop run a different subprogram. Each subprogram can be triggered by pressing a button. The button will update a variable and the main loop will use this variable to redirect the main to the appropriate function.
The mode
variable is traditionally used to store the current subprogram being run. You are not limited to a single mode. You can also have modes within modes.
In this lab, we use a rotary encoder to change which pixel is turned on. Turning the knob will appear to move the pixel.
"},{"location":"kits/rfid-rc522/","title":"RFIG Reader RC-522","text":"The RC-522 is a popular RFID reader that has strong support in the MicroPython community.
"},{"location":"kits/rfid-rc522/#pinout","title":"Pinout","text":"NAME PICO GPIO COLOR SDA 1 Yellow SCK 2 Orange MOSI 4 Purple MISO 3 Blue IRQ 7 Brown GND GND Black RST 0 Green 3.3V 3.3v Out Red"},{"location":"kits/rfid-rc522/#wires-at-the-rc522","title":"Wires at the RC522","text":""},{"location":"kits/rfid-rc522/#wires-at-the-pico","title":"Wires at the Pico","text":""},{"location":"kits/rfid-rc522/#config-file","title":"Config File","text":"Place this in the config.py
# reader = MFRC522(spi_id=0, sck=2, miso=4, mosi=3, cs=1, rst=0)\nSPI_ID = 0\nRESET_PIN = 0 # Green OUT\nSDA_PIN = 1 # Yellow OUT but used a Chip Select CS \nSCK_PIN = 2 # Orange OUT clock going from Pico to RC522\nMISO_PIN = 3 # Blue \nMOSI_PIN = 4 # Purple\nIRQ_PIN = 7 # Brown, OUT but not used in the reader demo\n\n# GND is Black\n# Red goes to 3.3v out\n
"},{"location":"kits/rfid-rc522/#reader","title":"Reader","text":"from mfrc522 import MFRC522\nimport utime\n\n\ndef uidToString(uid):\n mystring = \"\"\n for i in uid:\n mystring = \"%02X\" % i + mystring\n return mystring\n\n\nreader = MFRC522(spi_id=0,sck=2,miso=4,mosi=3,cs=1,rst=0)\n\nprint(\"\")\nprint(\"Please place card on reader\")\nprint(\"\")\n\n\n\nPreviousCard = [0]\n\ntry:\n while True:\n\n reader.init()\n (stat, tag_type) = reader.request(reader.REQIDL)\n #print('request stat:',stat,' tag_type:',tag_type)\n if stat == reader.OK:\n (stat, uid) = reader.SelectTagSN()\n if uid == PreviousCard:\n continue\n if stat == reader.OK:\n print(\"Card detected {} uid={}\".format(hex(int.from_bytes(bytes(uid),\"little\",False)).upper(),reader.tohexstring(uid)))\n defaultKey = [255,255,255,255,255,255]\n reader.MFRC522_DumpClassic1K(uid, Start=0, End=64, keyA=defaultKey)\n print(\"Done\")\n PreviousCard = uid\n else:\n pass\n else:\n PreviousCard=[0]\n utime.sleep_ms(50) \n\nexcept KeyboardInterrupt:\n pass\n
"},{"location":"kits/rfid-rc522/#references","title":"References","text":"We are mostly a group of unpaid volunteers without a large budget for distributed content management software. However, we still want to provide a great user experience for both our mentors and our students. So we use free open-source tools whenever we can. Here is how we built this site.
We wish to acknowledge the work of everyone that contributes to open-source projects. Without these systems this work would not be possible.
If you would like to contribute lesson we welcome your contribution as a git pull request. You can read our CoderDojo Twin CitiesAuthoring Guide if you would like to help out.
"},{"location":"misc/contact/","title":"Cybersecurity for Kids Contacts","text":""},{"location":"misc/contact/#general-code-savvy-contact","title":"General Code Savvy Contact","text":"kidscode@codesavvy.org
"},{"location":"misc/contact/#contact-for-coderdojo-twin-cities","title":"Contact for CoderDojo Twin Cities","text":"hello@coderdojotc.org
"},{"location":"misc/contact/#specific-questions-on-this-repository","title":"Specific questions on this repository","text":"Dan McCreary
"},{"location":"misc/contributing/","title":"MicroPython Book Contributing Guide","text":"The goal of this microsite is to provide MicroPython content to STEM educational programs around the world. We value inclusion and we love low-cost projects that promote STEM instruction in communities that don't have large budgets. We value a focus on instructional design that teaches computational thinking and uses evidence of the effective delivery of this content to underprivileged communities.
We welcome anyone that would like to add content to this microsite with the following guidelines:
"},{"location":"misc/contributing/#contribution-license","title":"Contribution License","text":"Under these terms teachers and mentors are free to:
The licensor cannot revoke these freedoms as long as you follow the license terms. Under the following terms:
Attribution \u2014 You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
NonCommercial \u2014 You may not use the material for commercial purposes.
ShareAlike \u2014 If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original.
No additional restrictions \u2014 You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits.
Avoid too much focus on building games that don't promote teaching computational thinking skills. Simple remote control robots might be fun, but sometime the fun of driving a robot around gets in the way of learning new concepts.
"},{"location":"misc/contributing/#lesson-structure","title":"Lesson Structure","text":"Please see our page on Teaching Computational Thinking.
"},{"location":"misc/glossary/","title":"Micropython Glossary of Terms","text":""},{"location":"misc/glossary/#ampy","title":"Ampy","text":"An obsolete MicroPython support tool created by Adafruit but no longer supported.
Check out other MicroPython tools like rshell or mpfshell for more advanced interaction with boards.
"},{"location":"misc/glossary/#analog-to-digital-converter","title":"Analog to Digital Converter","text":"A component that takes an analogue signal and changes it to a digital one.
Every ADC has two parameters, its resolution, measured in digital bits, and its channels, or how many analogue signals it can accept and convert at once.
A special form of copy operation; it copies a rectangular area of pixels from one framebuffer to another. It is used in MicroPython when doing drawing to a display such as an OLED display.
"},{"location":"misc/glossary/#bootsel","title":"BOOTSEL","text":"A button on the pico that when pressed during power up will allow you to mount the device as a USB drive. You can then drag-and-drop any uf2 image file to reset or update the runtime libraries.
Plated through holes or vias located in the edges of a printed circuit board that make it easier to solder onto another circuit board.
The word \"Castellated\" means having grooves or slots on an edge and is derived from the turrets of a castle.
"},{"location":"misc/glossary/#dupont-connectors","title":"Dupont Connectors","text":"Pre-made low-cost used and used to connect breadboards to hardware such as sensors and displays.
The connectors are available in male and female ends and are typically sold in lengths of 10 or 20cm. They have a with a 2.54mm (100mill) pitch so they are easy to align with our standard breadboards. They are typically sold in a ribbon of mixed colors for around $2.00 US for 40 connectors.
A series of low-cost, low-power system on a chip microcontrollers with integrated Wi-Fi and dual-mode Bluetooth.
Typical costs for the ESP32 is are around $10 US on eBay.
The ability to use a simplified syntax to format strings by added the letter \"f\" before the string. Values within curly braces are formatted from variables.
name = \"Lisa\"\nage = 12\nf\"Hello, {name}. You are {age}.\"\n
returns
Hello, Lisa. You are 12.\n
Formatted string support was added to MicroPython in release 1.17. Most formats work except the date and time formats. For these we must write our own formatting functions.
A region of your microcontroller RAM that stores a bitmap image of your display.
For a 128X64 monochrome display this would be 128 * 64 = 8,192 bits or 1,024 bytes (1K). Color displays must store up to 8 bytes per color for each color (red, green and blue).
A type of signal used to pause a program and execute a different program. We use interrupts to pause our program and execute a different program when a button is pressed.
"},{"location":"misc/glossary/#i2c","title":"I2C","text":"A communications protocol common in microcontroller-based systems, particularly for interfacing with sensors, memory devices and liquid crystal displays.
I2C is similar to SPI, it's a synchronous protocol because it uses a clock line.
A set of Python libraries and tools developed specifically for microcontrollers.
MicroPython was originally developed by Damien George and first released in 2014. It includes many of the features of mainstream Python, while adding a range of new ones designed to take advantage of the facilities available on Raspberry Pi Pico and other microcontroller boards like the ESP32.
A simple MicroPython shell based file explorer for ESP8266 and WiPy MicroPython based devices.
The shell is a helper for up/downloading files to the ESP8266 (over serial line and Websockets) and WiPy (serial line and telnet). It basically offers commands to list and upload/download files on the flash FS of the device.
GitHub Repo for MPFShell
"},{"location":"misc/glossary/#oled-display","title":"OLED Display","text":"OLED (Organic polymer light emitting diode) dispays are small but bright displays with high contrast, low power and a wide viewing angle. We use these displays throughout our labs. The small displays are around 1\" (diagonal) and only cost around $4 to $5. Larger 2.24\" displays cost around $20. These displays work both with 4-wire I2C and 7-wire SPI connections.
Typical chips that control the OLED include the SSD1306 driver chips.
The company that builds the Raspberry Pi hardware and provides some software.
"},{"location":"misc/glossary/#raspberry-pi-pico","title":"Raspberry Pi Pico","text":"A microcontroller designed by the Raspberry Pi foundation for doing real-time control systems.
The Pico was introduced in 2020 with a retail list price of $4. It was a key development because it used a custom chip that had 100 times the RAM of an Arduino Nano.
"},{"location":"misc/glossary/#pico-pinout-diagram","title":"Pico Pinout Diagram","text":"The Pico pinout diagram shows you the ways that each Pin can be used. Different colors are used for GPIO numbers, I2C, and SPI interfaces.
A type of output signal used to control items with continuous values. For example, we use PWM to control the brightness of a light or the speed of a motor. We use pulse-width modulation (PWM) to control the speed of our DC motors.
"},{"location":"misc/glossary/#rp2040-chip","title":"RP2040 chip","text":"A custom chip created by the Raspberry Pi Foundation to power the Raspberry Pi Pico.
"},{"location":"misc/glossary/#rshell","title":"rshell","text":"A MicroPython shell that runs on the host and uses MicroPython's raw-REPL to send python snippets to the pyboard in order to get filesystem information, and to copy files to and from MicroPython's filesystem.
It also has the ability to invoke the regular REPL, so rshell can be used as a terminal emulator as well.
Note: With rshell you can disable USB Mass Storage and still copy files into and out of your pyboard.
RShell GitHub Repo
"},{"location":"misc/glossary/#spi","title":"SPI","text":"An interface bus commonly used to send data between microcontrollers and small peripherals such as sensors, displays and SD cards. SPI uses separate clock and data lines, along with a select line to choose the device you wish to talk to.
Also known as: Serial Peripheral Interface See also: I2C
"},{"location":"misc/glossary/#thonny","title":"Thonny","text":"A lightweight Python IDE ideal for writing simple Python programs for first time users.
Thonny runs on Mac, Windows and Linux.
The file that must be uploaded into the Raspberry Pi Pico folder to allow it to be used.
The file name format looks like this:
rp2-pico-20210205-unstable-v1.14-8-g1f800cac3.uf2
MicroPython on Unicorn is completely open source Micropython emulator
MicroPython.org Glossary
"},{"location":"misc/mermaid-test/","title":"Mermaid test","text":"graph LR\n p[Pico] -->|ADC_VREF 36 row=6| pos(Positive)\n p[Pico] -->|AGND 33 row=8| neg(Negative)\n p[Pico] -->|GP26 pin=26 ADC0 31 row=10| tap(Center Tap)\n pos(Positive) --- pot(Potentiometer)\n neg(Negative) --- pot(Potentiometer)\n tap(Center Tap) --- pot(Potentiometer)\n
graph LR\nMyApp --> DB(<font color=white>fa:fa-database MySQL)\nstyle DB fill:#00758f\n
"},{"location":"misc/projects/","title":"CoderDojo MicroPython Projects","text":"Projects are groups lesson plans that need to work together and frequently build on other projects. Key project areas include:
Please connect with Dan McCreary if you are interested in helping out with these projects.
Name Status Description Ping HC-SR04 50%) Create an OLED ping lab Motion Sensor 0% Create a lesson that uses a motion sensor. Use an OLED to display motion vs. time Photosensor 0% Create a lesson that will monitor light on a photo sensor DS18B Temp Sensor 0% Create a lesson that will read and display a waterproof temperature sensor JoyStick 0% Create a lesson that will read the X, Y and button of a JoyStick OLED JoyStick 0% Use a Joystick to move a ball around an OLED and paint pixels OLED Game of Life 0% Write a Game of Life with an OLED. Add a Joystick to paint initial conditions"},{"location":"misc/references/","title":"References for MicroPython","text":""},{"location":"misc/references/#micropython-references","title":"Micropython References","text":"Comparison of RP2040 Boards
"},{"location":"misc/references/#components-references","title":"Components References","text":"Micropython Snake Game
"},{"location":"misc/upip/","title":"MicroPython PIP (UPIP)","text":"MicroPython also has a package manager that can be run directly on the microcontoller.
"},{"location":"misc/upip/#install-upip-from-thonny-package-manager","title":"Install UPIP From Thonny Package Manager","text":""},{"location":"misc/upip/#install-a-package","title":"Install A Package","text":"Connecting to WiFi Network Name: anndan-2.4 Waiting for connection... 1.2.3.4.Connect Time: 4641 Ping the following address: 10.0.0.49 Installing to: /lib/ Warning: micropython.org SSL certificate is not validated Installing micropython-pystone_lowmem 3.4.2.post4 from https://micropython.org/pi/pystone_lowmem/pystone_lowmem-3.4.2.post4.tar.gz
"},{"location":"motors/01-intro/","title":"Using MicroPython to Control Motors and Servos","text":"Controlling motors are one of the funnest ways to learn how to program! They give us quick hand-on feedback on what are programs are doing. Motors are used in almost all our robot project. Robots are used in many STEM courses and coding clubs around the world. Programs like the AI Racing League allow students to learn topics like Machine Learning, Computer Vision and AI. These programs all depend on our understanding of how motors work and how to control them.
"},{"location":"motors/01-intro/#motor-types","title":"Motor Types","text":"There are three types of motors we will learn how to control:
In this lab we will use MicroPython to turn a motor on and off. We will use the digital output from the Raspberry Pi Pico to control a transistor as a switch to control the current to a motor. We will also learn how to use a diode to to protect the transistor from flyback current.
"},{"location":"motors/01-intro/#theory-what-is-an-h-bridge-circuit","title":"Theory: What is an H-Bridge Circuit?","text":"This lab shows a sample circuit with four switches arranged in the shape of the letter \"H\" with the motor at the center of the letter. By closing switches in opposite corners we can make the motor reverse direction.
"},{"location":"motors/01-intro/#lab-2-controlling-a-motor-speed-with-the-l293-h-bridge-chip","title":"Lab 2: Controlling a Motor Speed with the L293 H-Bridge Chip","text":"In this lab we will use a PWM signal to control the speed of a motor.
"},{"location":"motors/01-intro/#lab-3-changing-motor-direction","title":"Lab 3: Changing Motor Direction","text":"In this lab we will make a motor go both forward and backward and change the speed.
"},{"location":"motors/02-transistor/","title":"Using an Transistor to Control a Motor","text":""},{"location":"motors/02-transistor/#power-requirements-for-motors","title":"Power Requirements for Motors","text":"Motors need about 200 milliamps to work. But a microcontroller like the Raspberry Pi Pico only can switch about 18 milliamps. So we need a way to control more power.
The Pico has 26 general purpose input and output pins. However, each pin's power is designed to digitally communicate with other devices and has a limited current capacity of around 17 milliamps according to the Raspberry Pi Pico Datasheet Table 5. The solution is to either use the digital output signal to turn on and off a switch such as a transistor of to use a motor driver chip such as an L293D chip.
"},{"location":"motors/02-transistor/#basic-transistor-circuit","title":"Basic Transistor Circuit","text":"Set the frequency to 50Hz (one cycle per 20ms) and the duty value to between 51 (51/1023 * 20ms = 1ms) and 102 (102/1023 * 20ms = 2ms)
"},{"location":"motors/02-transistor/#sample-coder","title":"Sample Coder","text":"import machine\n\n# set the 7th from the bottom on right as our motor pin\nmotor_pin = machine.Pin(21, machine.Pin.OUT)\n# allocate a PWM object for controlling the motor speed\nmotor_pwm = machine.PWM(motor_pin)\nmotor_pwm.freq(50) # 50 hertz\nmotor_pwm.duty(51)\n
"},{"location":"motors/02-transistor/#references","title":"References","text":"H-Bridge circuits are use to drive a motor both forward and backward. The circuit is called an \"H-Bridge\" because the arrangement of the switches around a motor form the letter \"H\".
"},{"location":"motors/03-h-bridge/#h-bridge-circuit-operation","title":"H-Bridge Circuit Operation","text":"If you connect a 5 volt power supply to a motor you will turn the motor in a specific direction such as clockwise. If you reverse the connections to the motor, the motor will turn the opposite direction such as counter-clockwise.
In order to turn on the motor, switches 1 and 4 must be closed to allow current to flow through the motor. Switches 2 and 3 must be turned off.
To reverse the motor direction you must open switches 1 and 4 and close switches 2 and three in the upper right and lower left portion of the diagram.
"},{"location":"motors/03-h-bridge/#references","title":"References","text":"The L293D motor driver IC has two power input pins viz. \u2018Vcc1\u2019 and \u2018Vcc2\u2019.
Vcc1 is used for driving the internal logic circuitry which should be 5V.
From Vcc2 pin the H-Bridge gets its power for driving the motors which can be 4.5V to 36V. And they both sink to a common ground named GND.
"},{"location":"motors/04-l293d/#sample-program","title":"Sample Program","text":""},{"location":"motors/04-l293d/#references","title":"References","text":"Last Minute Engineer L293D DC Motor Tutorial (Arduino version)
"},{"location":"motors/06-servos/","title":"Controlling a Servo Motor with MicroPython","text":""},{"location":"motors/06-servos/#types-of-servos","title":"Types of Servos","text":"Although there are may types of servos you can purchase, in our labs there are two main types of servos that we use:
There are other variations that have 360 degree or continuous rotation servos.
"},{"location":"motors/06-servos/#servo-connections","title":"Servo Connections","text":"Almost all servos have a three pin connector that are spaced 1/10th of an inch apart so they will work with our breadboards.
Stepper motors are specialized motors that precisely control the angle of rotation of the shaft of a motor. They are often used to carefully position items that move along an axis. For example you can use stepper motors to control the position the printing head of a 3D printer. Stepper motors are also quite a bit more expensive than our DC hobby motors and mini servos, so we don't use them frequently in our classes.
"},{"location":"motors/07-stepper-motors/#sample-code","title":"Sample Code","text":"# Code example from YoungWorks blog on how to use a stepper motor\n# https://www.youngwonks.com/blog/How-to-use-a-stepper-motor-with-the-Raspberry-Pi-Pico\nfrom machine import Pin\nimport utime\n\npins = [\n Pin(15, Pin.Out),\n Pin(14, Pin.Out),\n Pin(16, Pin.Out),\n Pin(17, Pin.Out),\n]\n\n# one hot encoding vectors\nfull_step_sequence = [\n [1.0.0.0],\n [0.1.0.0],\n [0.0.1.0],\n [0.0.0.1]\n]\n\nwhile True:\n for step in full_step_sequence:\n for i in rang(len(pins)):\n pins[i].value(step[i])\n utime.sleep(0.001)\n
"},{"location":"motors/07-stepper-motors/#references","title":"References","text":"This is a box with three potentiometers and a NeoPixel strip. Changing the potentiometers changes the mix of Red, Green and Blue colors.
"},{"location":"projects/01-intro/#alarm-clock","title":"Alarm Clock","text":"https://github.com/wahlencraft/pico-alarm-clock
"},{"location":"projects/02-rgb-box/","title":"Raspberry Pi RGB Box","text":"This is a box with three potentiometers and a NeoPixel strip. Changing the potentiometers changes the mix of Red, Green and Blue colors. We use this at many science fairs or demonstration projects that has kids as young as three years old! As the kids learn to adjust the knobs, we say \"Hey, your a programmer!\".
"},{"location":"projects/02-rgb-box/#related-labs","title":"Related Labs","text":"Before you do this project, it is a good idea to get familiar with the Potentiometer lab. This lab will show you how to hook up a single potentiometer to the Raspberry Pi Pico and read it's values.
"},{"location":"projects/02-rgb-box/#required-tools","title":"Required Tools","text":"Although we will be using a solderless breadboard to connect the components, we use a hot-glue gun to make sure the wires don't get dislocated when the box gets bumped around.
With a bit of clever shopping, you can get the total part costs: under about $15. If you purchase the parts in Quantity 10+ you can get the costs under $10/kit.
"},{"location":"projects/02-rgb-box/#circuit-diagram","title":"Circuit Diagram","text":"TBD
"},{"location":"projects/02-rgb-box/#assembly","title":"Assembly","text":"Solder six-inches of hookup wire to each of the three pins on the three potentiometers.
"},{"location":"projects/02-rgb-box/#sample-code","title":"Sample Code","text":""},{"location":"projects/02-rgb-box/#test-the-neopixel-connection","title":"Test the NeoPixel Connection","text":"Our first step will be to verify we have the NeoPixel strip connected correctly and that we have the right configuration. There are two items you might have to change:
We use two Python variables to configure
NEOPIXEL_PIN = 0\nNUMBER_PIXELS = 10\n
import machine, neopixel\nfrom utime import sleep\nfrom neopixel import Neopixel\n\nNEOPIXEL_PIN = 0\nNUMBER_PIXELS = 10\nstrip = Neopixel(NUMBER_PIXELS, 0, NEOPIXEL_PIN, \"GRB\")\n\nprint('flashing pixel 0 red')\ndelay=.3\nwhile True:\n strip.set_pixel(0, (255,0,0)) // turn pixel 0 red\n strip.show()\n sleep(delay)\n strip.set_pixel(0, (0,0,0)) // turn pixel 0 off\n strip.show()\n sleep(delay)\n
"},{"location":"projects/03-neopixel-strip-two-buttons/","title":"NeoPixel Two Button Kit","text":"This is a low-cost (around $10) kit that is used for hackathons and activities such as a Halloween costume contest.
"},{"location":"projects/03-neopixel-strip-two-buttons/#contents","title":"Contents","text":"See the Basic Example for the NeoPixel Strip lab.
"},{"location":"projects/03-neopixel-strip-two-buttons/#labs","title":"Labs","text":"We supply a small set of \"getting started\" labs to demonstrate how to program colors on the LED strip and give the perception of motion up and down the strip.
"},{"location":"projects/03-neopixel-strip-two-buttons/#blink","title":"Blink","text":""},{"location":"projects/03-neopixel-strip-two-buttons/#move","title":"Move","text":""},{"location":"projects/03-neopixel-strip-two-buttons/#fade-in-and-out","title":"Fade In and Out","text":"from neopixel import NeoPixel\nfrom time import sleep\n\nNUMBER_PIXELS = 60\nLED_PIN = 0\n\nstrip = NeoPixel(machine.Pin(LED_PIN), NUMBER_PIXELS)\n\ndelay = .005\n\nwhile True:\n for i in range(0, 255):\n strip[0] = (i,0,0) # red=255, green and blue are 0\n strip.write() # send the data from RAM down the wire\n sleep(delay) # keep on 1/10 of a second\n for i in range(255, 0, -1):\n strip[0] = (i,0,0) # red=255, green and blue are 0\n strip.write() # send the data from RAM down the wire\n sleep(delay) # keep on 1/10 of a second\n
"},{"location":"projects/03-rotary-neopixel/","title":"Rotary NeoPixel","text":"This project has a rotary encoder and a button. Spinning the rotary encoder changes the pixel index. Pressing the knob of the encoder changes the color. Pressing the button changes the pattern.
"},{"location":"projects/03-rotary-neopixel/#circuit","title":"Circuit","text":"We connect the two ends of the rotary (A and B) to pins 14 and 15. We connect the center pin to the 3.3v rail of the breadboard.
Next, we connect the rotary button and the other button to pins 16 and 17 and to the 3.3v rail.
All the buttons use PULL.DOWN option when they are configured.
"},{"location":"projects/03-rotary-neopixel/#sample-code","title":"Sample Code","text":"We use the rotary.py library
from machine import Pin\nfrom rotary import Rotary\n\nENCODER_A = 15\nENCODER_B = 14\nSWITCH = 17\nrotary = Rotary(ENCODER_A, ENCODER_B, SWITCH)\n
You can change the order of A and B to match the turn direction.
"},{"location":"projects/03-rotary-neopixel/#full-source-code","title":"Full Source Code","text":"from machine import Pin\nfrom rotary import Rotary\nfrom utime import sleep, ticks_ms\nfrom neopixel import NeoPixel\n\nNEOPIXEL_PIN = 0\nNUMBER_PIXELS = 12\n\nstrip = NeoPixel(machine.Pin(NEOPIXEL_PIN), NUMBER_PIXELS)\n\n# GPIO Pins 16 and 17 are for the encoder pins. 18 is the button press switch.\nENCODER_A = 15\nENCODER_B = 14\nSWITCH = 17\nrotary = Rotary(ENCODER_A, ENCODER_B, SWITCH)\n\nbutton_pin = machine.Pin(16, machine.Pin.IN, machine.Pin.PULL_DOWN)\nmode = 0 # mode to display\nmode_names = ['dot', 'hole', 'compare', 'chase', 'rainbow']\n\nbutton_presses = 0 # the count of times the button has been pressed\nlast_time = 0 # the last time we pressed the button\ndef button_pressed_handler(pin):\n global button_presses, last_time, mode\n new_time = ticks_ms()\n # if it has been more that 1/5 of a second since the last event, we have a new event\n if (new_time - last_time) > 200: \n mode +=1\n last_time = new_time\n # make mode 0 or 1\n mode = mode % 5\n print('mode=', mode, mode_names[mode])\n# now we register the handler function when the button is pressed\nbutton_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler = button_pressed_handler)\n\nval = 0 # value of the LED strip index set by the rotary know\n\nred = (255, 0, 0)\norange = (140, 60, 0)\nyellow = (255, 255, 0)\ngreen = (0, 255, 0)\nblue = (0, 0, 255)\ncyan = (0, 255, 255)\nindigo = (75, 0, 130)\nviolet = (138, 43, 226)\nwhite = (128, 128, 128)\ncolors = (red, orange, yellow, green, blue, cyan, indigo, violet)\ncolor_count = len(colors)\n\n\n# this function is called whenever the rotory is changed\ndef rotary_changed(change):\n global val, button_press, color_index\n if change == Rotary.ROT_CW:\n val = val + 1\n elif change == Rotary.ROT_CCW:\n val = val - 1 \n elif change == Rotary.SW_PRESS:\n print('PRESS')\n # button_press = 1\n elif change == Rotary.SW_RELEASE:\n print('RELEASE')\n color_index += 1\n color_index = color_index % (color_count - 1)\n val = val % NUMBER_PIXELS\n print(val) \n\nrotary.add_handler(rotary_changed)\n\ncolor_index = 0\ncolor = red\nwhile True:\n if mode == 0:\n for i in range(0, NUMBER_PIXELS):\n if i == val:\n strip[i] = color\n else:\n strip[i] = (0,0,0)\n elif mode == 1:\n for i in range(0, NUMBER_PIXELS):\n if i == val:\n strip[i] = (0,0,0)\n else:\n strip[i] = color\n elif mode == 2:\n for i in range(0, NUMBER_PIXELS):\n if i > val:\n strip[i] = (0,0,0)\n else:\n strip[i] = color\n elif mode == 3:\n for i in range(0, NUMBER_PIXELS):\n if (i-val) % 3:\n strip[i] = (0,0,0)\n else:\n strip[i] = color \n elif mode == 4:\n # if the val + offset is larger than the number of pixels we need to do a modulo\n strip[val % (NUMBER_PIXELS)] = violet\n strip[(val+1) % (NUMBER_PIXELS)] = indigo\n strip[(val+2) % (NUMBER_PIXELS)] = blue\n strip[(val+3) % (NUMBER_PIXELS)] = green\n strip[(val+4) % (NUMBER_PIXELS)] = yellow\n strip[(val+5) % (NUMBER_PIXELS)] = orange\n strip[(val+6) % (NUMBER_PIXELS)] = red\n # turn off the rest\n strip[(val+7) % (NUMBER_PIXELS)] = (0,0,0)\n strip[(val+8) % (NUMBER_PIXELS)] = (0,0,0)\n strip[(val+9) % (NUMBER_PIXELS)] = (0,0,0)\n strip[(val+10) % (NUMBER_PIXELS)] = (0,0,0)\n strip[(val+11) % (NUMBER_PIXELS)] = (0,0,0)\n strip.write()\n # print('color index', color_index)\n color = colors[color_index]\n
"},{"location":"robots/","title":"Introduction to MicroPython Robots","text":"Robots are the most powerful learning machines in our curriculum. They allow our students to control motion with their own programs. Not only are they incredibly fun for our students, our students learn computational thinking and enable them to quickly proceed to our advanced AI Racing League projects.
The Raspberry Pi Pico and the Maker Pi RP2040 components have truly been transformative for our clubs. Instead of being trapped in the 2K RAM on our Arduino systems and being forced to learn \"C\", our students are programming in their favorite Python language!
"},{"location":"robots/#robot-journey-map","title":"Robot Journey Map","text":"This section of the course takes you on a tour of our base $25 collision avoidance robots. It then builds on this robot by adding an OLED display, programming controls and servos. Here is a Journey Map of these lessons:
Note that the $25 price assumes you purchase low-cost parts from suppliers like eBay. You can lower the cost per robot by purchasing the parts in higher quantities for classroom use. You can also purchase older Arduino robot kits and upgrade the processors to use the Raspberry Pi Pico.
Note
This section only covers using the Raspberry Pi Pico robots with external motor drivers. We now have many additional robot lessons that use the Cytron Maker Pi RP2040 robotics board. These robots are still low-cost ($20) but have much more integrated power and are easier for younger students to use since they don't require any breadboards or soldering.
Our base robot is a collision avoidance robot that is ideal for teaching beginning robotics principles. The robots have one or more distance sensors on the front and will continue to move forward until they get near an object in front of them. They then will reverse and turn in another direction. We test our robots on the floor in a \"Robot Corral\" that has six-inch high walls. Students can adjust various parameters to allow the robot to navigate around the corral without colliding with the walls.
Base Bot - This is the foundational robot that the other projects are built on. The base includes a standard Smart Car chassis, two DC hobby motors, a battery pack and wheels. On top of the chassis we add a breadboard, jumpers, a motor controller, a distance sensor, and our $4 Raspberry Pi microcontroller.
Rainbow Bot This takes our base robot and adds a low-cost LED strip so that students can change the color of the LED based on what the robot is sensing and doing. For example when the robot is turning right the LEDs can turn red.
IR Sensor Bot This takes our base robot and adds a low-cost LED strip so that students can change the color of the LED based on what the robot is sensing and doing. For example when the robot is turning right the LEDs can turn red.
Face Bot - We extend the Base Bot by adding a $4 128x64 OLED display. This allows students to see the values of the distance sensor and to hear a sound when a key event occurs.
Adjustable Parameter Bot - We extend the face-bot to add some buttons and knobs to allow our users to change the collision avoidance parameters such as forward speed and turning threshold distance.
Our beginning Base Bot
"},{"location":"robots/#chassis","title":"Chassis","text":"SmartCar Chassis
"},{"location":"robots/#sensors","title":"Sensors","text":"This lesson describes our base robot kit in the CoderDojo Twin Cities coding club. This robot is programmed entirely in Python to be consistent with our Python Courses.
"},{"location":"robots/02-base-bot/#base-robot-design","title":"Base Robot Design","text":"Our goal is to build a robotics platform for teaching computational thinking. Here are our main design goals:
Here is a video of the collision avoidance robot in action:
YouTube Video
Note that the forward-speed and distance-before-you-turn can be adjusted. You can see I didn't quite get the distance right and the robot bumps into some of the barriers.
"},{"location":"robots/02-base-bot/#connection-diagram","title":"Connection Diagram","text":"Here is a connection diagram of the base robot.
"},{"location":"robots/02-base-bot/#power-regulation","title":"Power Regulation","text":"Note that the power comes from the battery at 6 volts and is connected to the input voltage of the motor controller board. The motor controller has a voltage regulator that converts any input voltage from 6 to 12 volts down to 5 volts. The output voltage of the motor controller is then connected to the power rail on the left, which is in turn connected to the VSYS input to the Pico. The Pico, in turn, has another voltage regulator that drop the input from VSYS down to 3.3 volts on the 3.3V OUT pin. This voltage is then used to power the distance sensor.
One of the downsides to this design is that as the batteries get low, once they drop below around 5 volts the double voltage drops cause the 3.3 OUT to become too low and the sensor becomes unreliable. A better design would be to find a motor controller that produces a stable 3.3 volts as the batteries slowly run down. Let us know if you can find one of these designs that cost under $2.
"},{"location":"robots/02-base-bot/#hardware-description","title":"Hardware Description","text":"Here is a summary of some of the parts we use in this robot and their approximate prices as of June 2021. Some parts come from China so you might need to wait 2-3 weeks for them to arrive.
Here is a Google sheet with these parts:
Detailed Parts List Google Sheet
"},{"location":"robots/02-base-bot/#two-wheel-drive-smart-car-chassis","title":"Two Wheel Drive Smart Car Chassis","text":"Our cars all use a standard Two Wheel Drive (2WD) SmartCar Chassis that is available in many retail outlets online.
All software is written in MicroPython.
"},{"location":"robots/02-base-bot/#time-of-flight-distance-sensor","title":"Time-of-Flight Distance Sensor","text":"We are using the VL53L0X time-of-flight distance sensor. This works on an I2C bus. After you have hooked up the Power (VCC to the 3.3 rail and GND) you must hook up the I2C data and clock.
sda=machine.Pin(16) # Lower right corner of the Pico with the USB on top\nscl=machine.Pin(17) # One up from the lower right corner of the Pico\ni2c=machine.I2C(0, sda=sda, scl=scl)\n
Many of our older robots used the ultrasonic ping sensors. The are unreliable with voltage drops as our batteries wear down.
"},{"location":"robots/02-base-bot/#testing-the-sensor-connections-with-the-i2c-scanner","title":"Testing the Sensor Connections with the I2C Scanner","text":"import machine\nsda=machine.Pin(16) # Lower right corner of the Pico with the USB on top\nscl=machine.Pin(17) # One up from the lower right corner of the Pico\ni2c=machine.I2C(0, sda=sda, scl=scl)\nprint(\"Device found at decimal\", i2c.scan())\n
You should see a decimal number returned. By default the I2C address is 41 (decimal) or x29 (hexadecimal).
"},{"location":"robots/02-base-bot/#download-the-vl53l0x-driver","title":"Download the VL53L0X Driver","text":"You will need to add a VL53L0X driver file to the file system on the pico.
We have a copy here: https://raw.githubusercontent.com/CoderDojoTC/micropython/main/src/drivers/VL53L0X.py
"},{"location":"robots/02-base-bot/#time-of-flight-sensor-test","title":"Time-of-Flight Sensor Test","text":"Once the driver file is loaded we are ready to test the time-of-flight distance sensor.
import time\nfrom machine import Pin\nfrom machine import I2C\nimport VL53L0X\n\nsda=machine.Pin(16) # row one on our standard Pico breadboard\nscl=machine.Pin(17) # row two on our standard Pico breadboard\ni2c=machine.I2C(0, sda=sda, scl=scl)\n\n# Create a VL53L0X object\ntof = VL53L0X.VL53L0X(i2c)\ntof.start() # startup the sensor\nwhile True:\n# Start ranging\n dist = tof.read()\n print(dist)\n time.sleep(.1)\n
When you run this program a sequence of integers will appear in the console. The numbers usually will range from around 30 if there is an object directly in front of the sensor to a number around 1,300 for a object that is about 1.3 meters away from the sensor. There is a 1/10th of a second pause between each measurement. This can be changed in the last line of the program.
"},{"location":"robots/02-base-bot/#motor-drive-test","title":"Motor Drive Test","text":"After we have the four wires connected to the motor driver, we need to make sure we get the right wires to the right motors and motor directions. This program will help you debug this.
from machine import Pin, PWM\nimport time # sleep\n\nPOWER_LEVEL = 65025\n# lower right pins with USB on top\nRIGHT_FORWARD_PIN = 21\nRIGHT_REVERSE_PIN = 20\nLEFT_FORWARD_PIN = 18\nLEFT_REVERSE_PIN = 19\n\nright_forward = PWM(Pin(RIGHT_FORWARD_PIN))\nright_reverse = PWM(Pin(RIGHT_REVERSE_PIN))\nleft_forward = PWM(Pin(LEFT_FORWARD_PIN))\nleft_reverse = PWM(Pin(LEFT_REVERSE_PIN))\n\n\ndef spin_wheel(pwm):\n pwm.duty_u16(POWER_LEVEL)\n time.sleep(3)\n pwm.duty_u16(0)\n time.sleep(2)\n\nwhile True:\n print('right forward')\n spin_wheel(right_forward)\n\n print('right reverse')\n spin_wheel(right_reverse)\n\n print('left foward')\n spin_wheel(left_forward)\n\n print('left_reverse')\n spin_wheel(left_reverse)\n
One thing to remember is that the \"Right\" refers to our orientation from the rear of the car or if we were sitting inside the car. If the robot is facing you with the sensor in the front, it is the wheel on the left that we call the \"RIGHT\" wheel. Very confusing! Using this naming convention will pay of as we are walking behind larger robots.
"},{"location":"robots/02-base-bot/#sample-drive-and-turn-functions","title":"Sample Drive and Turn Functions","text":"We will need a set of function to drive our robot:
Our challenge is for each of these operations we must change the value of all four PWM signals. We can never have a motor be going both forward and reverse. Here are some sample drive functions:
def turn_motor_on(pwm):\n pwm.duty_u16(POWER_LEVEL)\n\ndef turn_motor_off(pwm):\n pwm.duty_u16(0)\n\ndef forward():\n turn_motor_on(right_forward)\n turn_motor_on(left_forward)\n turn_motor_off(right_reverse)\n turn_motor_off(left_reverse)\n\ndef reverse():\n turn_motor_on(right_reverse)\n turn_motor_on(left_reverse)\n turn_motor_off(right_forward)\n turn_motor_off(left_forward)\n\ndef turn_right():\n turn_motor_on(right_forward)\n turn_motor_on(left_reverse)\n turn_motor_off(right_reverse)\n turn_motor_off(left_forward)\n\ndef turn_left():\n turn_motor_on(right_reverse)\n turn_motor_on(left_forward)\n turn_motor_off(right_forward)\n turn_motor_off(left_reverse)\n\ndef stop():\n turn_motor_off(right_forward)\n turn_motor_off(right_reverse)\n turn_motor_off(left_forward)\n turn_motor_off(left_reverse)\n
"},{"location":"robots/02-base-bot/#turning-logic","title":"Turning Logic","text":"while True:\n dist = read_sensor() \n if dist < TURN_THRESHOLD:\n print('object detected')\n reverse()\n sleep(BACKUP_TIME)\n turn_right()\n sleep(TURN_TIME)\n else:\n forward()\n
"},{"location":"robots/02-base-bot/#test-motor-connections","title":"Test Motor Connections","text":"from machine import Pin, PWM\nimport time # sleep\n\nPOWER_LEVEL = 65025 # usually a number from 30,000 to max of 65,025\n# lower right pins with USB on top\nRIGHT_FORWARD_PIN = 21\nRIGHT_REVERSE_PIN = 20\nLEFT_FORWARD_PIN = 18\nLEFT_REVERSE_PIN = 19\n\nright_forward = PWM(Pin(RIGHT_FORWARD_PIN))\nright_reverse = PWM(Pin(RIGHT_REVERSE_PIN))\nleft_forward = PWM(Pin(LEFT_FORWARD_PIN))\nleft_reverse = PWM(Pin(LEFT_REVERSE_PIN))\n\n\ndef spin_wheel(pwm):\n pwm.duty_u16(POWER_LEVEL)\n time.sleep(3)\n pwm.duty_u16(0)\n time.sleep(2)\n\nwhile True:\n print('right forward')\n spin_wheel(right_forward)\n\n print('right reverse')\n spin_wheel(right_reverse)\n\n print('left foward')\n spin_wheel(left_forward)\n\n print('left_reverse')\n spin_wheel(left_reverse)\n
After you load this program, watch which wheels turn and in what direction.
"},{"location":"robots/02-base-bot/#drive-functions","title":"Drive Functions","text":"We will define Python functions for forward, reverse, turn right and turn left.
POWER_LEVEL = 65025\n\ndef turn_motor_on(pwm):\n pwm.duty_u16(POWER_LEVEL)\n\ndef turn_motor_off(pwm):\n pwm.duty_u16(0)\n\ndef forward():\n turn_motor_on(right_forward)\n turn_motor_on(left_forward)\n turn_motor_off(right_reverse)\n turn_motor_off(left_reverse)\n\ndef reverse():\n turn_motor_on(right_reverse)\n turn_motor_on(left_reverse)\n turn_motor_off(right_forward)\n turn_motor_off(left_forward)\n\ndef turn_right():\n turn_motor_on(right_forward)\n turn_motor_on(left_reverse)\n turn_motor_off(right_reverse)\n turn_motor_off(left_forward)\n\ndef turn_left():\n turn_motor_on(right_reverse)\n turn_motor_on(left_forward)\n turn_motor_off(right_forward)\n turn_motor_off(left_reverse)\n\ndef stop():\n turn_motor_off(right_forward)\n turn_motor_off(right_reverse)\n turn_motor_off(left_forward)\n turn_motor_off(left_reverse)\n
"},{"location":"robots/02-base-bot/#stop-all-motors-program","title":"Stop All Motors Program","text":"One other thing to remember is that the PWM signals continue to be generated even after the main loop has stopped. This is because on the Pico, the four PWM signals are being continuously generated by an independent processors. To stop the motors you must run a separate stop program like this:
stop-all-motors.py:
from machine import Pin, PWM\nfrom time import sleep\n\n# lower right pins with USB on top\nRIGHT_FORWARD_PIN = 19\nRIGHT_REVERSE_PIN = 21\nLEFT_FORWARD_PIN = 18\nLEFT_REVERSE_PIN = 20\n\nright_forward = PWM(Pin(RIGHT_FORWARD_PIN))\nright_reverse = PWM(Pin(RIGHT_REVERSE_PIN))\nleft_forward = PWM(Pin(LEFT_FORWARD_PIN))\nleft_reverse = PWM(Pin(LEFT_REVERSE_PIN))\n\nright_forward.duty_u16(0)\nright_reverse.duty_u16(0)\nleft_forward.duty_u16(0)\nleft_reverse.duty_u16(0)\n
This can be frustrating at times when you can't find the stop program. I like to bring the stop program up in a separate tab when I am writing robot motor code.
To
figure out how to write an interrup handler so that when the IDE STOP function is pressed the stop motors (and speaker) are stopped.
"},{"location":"robots/02-base-bot/#collision-avoidance-logic","title":"Collision Avoidance Logic","text":""},{"location":"robots/02-base-bot/#final-program","title":"Final Program","text":"To get this to work on battery power up you must name the program main.py and save it on the Raspberry Pi Pico.
Note
Make sure you have the VL53L0X distance sensor driver installed.
from machine import Pin, PWM\nfrom utime import sleep\nimport VL53L0X\n\n# used to blink the onboard LED\nled_onboard = machine.Pin(25, machine.Pin.OUT)\n\n# driving parameters\nPOWER_LEVEL = 65025 # use a value from 20000 to 65025\nTURN_THRESHOLD = 400 # 25 cm\nTURN_TIME = .25 # seconds of turning\nBACKUP_TIME = .75 # seconds of backing up if obstacle detected\n\n# Motor pins to the L293 H-Bridge\nRIGHT_FORWARD_PIN = 21\nRIGHT_REVERSE_PIN = 20\nLEFT_FORWARD_PIN = 18\nLEFT_REVERSE_PIN = 19\n\n# setup the PWM objects\nright_forward = PWM(Pin(RIGHT_FORWARD_PIN))\nright_reverse = PWM(Pin(RIGHT_REVERSE_PIN))\nleft_forward = PWM(Pin(LEFT_FORWARD_PIN))\nleft_reverse = PWM(Pin(LEFT_REVERSE_PIN))\n\nsda=machine.Pin(16) # row one on our standard Pico breadboard\nscl=machine.Pin(17) # row two on our standard Pico breadboard\ni2c=machine.I2C(0, sda=sda, scl=scl)\n\n# Create a VL53L0X object\ntof = VL53L0X.VL53L0X(i2c)\n\ndef turn_motor_on(pwm):\n pwm.duty_u16(POWER_LEVEL)\n\ndef turn_motor_off(pwm):\n pwm.duty_u16(0)\n\ndef forward():\n turn_motor_on(right_forward)\n turn_motor_on(left_forward)\n turn_motor_off(right_reverse)\n turn_motor_off(left_reverse)\n\ndef reverse():\n turn_motor_on(right_reverse)\n turn_motor_on(left_reverse)\n turn_motor_off(right_forward)\n turn_motor_off(left_forward)\n\ndef turn_right():\n turn_motor_on(right_forward)\n turn_motor_on(left_reverse)\n turn_motor_off(right_reverse)\n turn_motor_off(left_forward)\n\ndef turn_left():\n turn_motor_on(right_reverse)\n turn_motor_on(left_forward)\n turn_motor_off(right_forward)\n turn_motor_off(left_reverse)\n\ndef stop():\n turn_motor_off(right_forward)\n turn_motor_off(right_reverse)\n turn_motor_off(left_forward)\n turn_motor_off(left_reverse)\n\ndef read_sensor_avg():\n total = 0\n for i in range(10):\n total = total + tof.read()\n sleep(.01)\n return int(total/10)\n\ntof.start() # startup the sensor\n\nwhile True:\n dist = read_sensor_avg();\n print(dist)\n\n if dist < TURN_THRESHOLD:\n print('object detected')\n reverse()\n sleep(BACKUP_TIME)\n led_onboard.high()\n turn_right()\n sleep(TURN_TIME)\n else:\n if dist > 1300:\n print('no signal')\n led_onboard.low()\n else:\n print('Go forward')\n led_onboard.high()\n forward()\n
"},{"location":"robots/02-base-bot/#more-to-explore-labs","title":"More To Explore Labs","text":"Instead of our time-of-flight sensor used in our base robot, this robot uses three low-cost IR distance sensors.
"},{"location":"robots/03-ir-sensor-bot/#purchasing-ir-distance-sensors","title":"Purchasing IR Distance Sensors","text":""},{"location":"robots/03-ir-sensor-bot/#connecting-the-ir-sensors","title":"Connecting the IR Sensors","text":"# connections to the three IR distance sensors\nleft = Pin(8, Pin.IN, Pin.PULL_DOWN)\ncenter = Pin(7, Pin.IN, Pin.PULL_DOWN)\nright = Pin(6, Pin.IN, Pin.PULL_DOWN)\n
"},{"location":"robots/03-ir-sensor-bot/#connecting-the-speaker","title":"Connecting the Speaker","text":"This robot has an optional speaker connected to GPIO Pin 21. This allows us to \"hear\" what signals are coming into the robot It will generate a different tone if the left, center or right sensor is detecting an object and an different tone for going straight, reversing and turning.
The speaker is a small buzzer or a Piezoelectric speaker that can be purchased for around $1. It has one wire connected to the GPIO pin and the other connected to any GND pin or GND rail on the breadboard.
Here are the lines related to setting up the speaker code.
SPEAKER_PIN = 21\n# create a Pulse Width Modulation Object on this pin\nspeaker = PWM(Pin(SPEAKER_PIN))\n
"},{"location":"robots/03-ir-sensor-bot/#drive-logic","title":"Drive Logic","text":"The three IR sensors go LOW if there is an item in front of them. So the statement:
center.value()\n
will normally be HIGH if there is nothing in front of the robot.
Our main logic look will look like the following:
while True:\n if left.value()==0:\n turn_right()\n if center.value()==0:\n reverse()\n if right.value()==0:\n turn_left()\n if left.value() and center.value() and right.value():\n forward()\n
"},{"location":"robots/03-ir-sensor-bot/#full-program","title":"Full Program","text":"from machine import Pin, PWM\nfrom utime import sleep\nimport ssd1306\n\n# Motor pins to the L293 H-Bridge\nRIGHT_FORWARD_PIN = 17\nRIGHT_REVERSE_PIN = 16\nLEFT_FORWARD_PIN = 18\nLEFT_REVERSE_PIN = 19\n\nright_forward = PWM(Pin(RIGHT_FORWARD_PIN))\nright_reverse = PWM(Pin(RIGHT_REVERSE_PIN))\nleft_forward = PWM(Pin(LEFT_FORWARD_PIN))\nleft_reverse = PWM(Pin(LEFT_REVERSE_PIN))\n\n# connections to the three IR distance sensors\nleft = Pin(8, Pin.IN, Pin.PULL_DOWN)\ncenter = Pin(7, Pin.IN, Pin.PULL_DOWN)\nright = Pin(6, Pin.IN, Pin.PULL_DOWN)\n\nSPEAKER_PIN = 21\n# create a Pulse Width Modulation Object on this pin\nspeaker = PWM(Pin(SPEAKER_PIN))\n\nWIDTH = 128\nHEIGHT = 64\nCS = machine.Pin(1)\nSCL = machine.Pin(2)\nSDA = machine.Pin(3)\nDC = machine.Pin(4)\nRES = machine.Pin(5)\nspi=machine.SPI(0, sck=SCL, mosi=SDA)\noled = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)\n\ndef turn_motor_on(pwm):\n pwm.duty_u16(65025)\n\ndef turn_motor_off(pwm):\n pwm.duty_u16(0)\n\ndef forward():\n turn_motor_on(right_forward)\n turn_motor_on(left_forward)\n\ndef reverse():\n turn_motor_on(right_reverse)\n turn_motor_on(left_reverse)\n\ndef turn_right():\n turn_motor_on(right_forward)\n turn_motor_on(left_reverse)\n\ndef turn_left():\n turn_motor_on(right_reverse)\n turn_motor_on(left_forward)\n\ndef sound_off():\n speaker.duty_u16(0)\n\ndef left_tone():\n speaker.duty_u16(1000)\n speaker.freq(700) # 1 Kilohertz\n sleep(.5) # wait a 1/4 second\n sound_off()\n\ndef center_tone():\n speaker.duty_u16(1000)\n speaker.freq(900)\n sleep(.5)\n sound_off()\n\ndef right_tone():\n speaker.duty_u16(1000)\n speaker.freq(600)\n sleep(.5)\n sound_off()\n\ndef forward_tone():\n speaker.duty_u16(1000)\n speaker.freq(400)\n sleep(.1)\n speaker.freq(900)\n sleep(.1)\n speaker.freq(1200)\n sleep(.1)\n sound_off()\n\ndef update_oled():\n oled.fill(0)\n oled.text(\"CoderDojo Rocks!\", 0, 0, 1)\n\n oled.text(\"Left:\", 0, 10, 1)\n oled.text(str(left.value()), 50, 10, 1)\n\n\n oled.text(\"Center:\", 0, 20, 1)\n oled.text(str(center.value()), 60, 20, 1)\n\n oled.text(\"Right:\", 0, 30, 1)\n oled.text(str(right.value()), 55, 30, 1)\n\n BAR_WIDTH = 40\n BAR_HEIGHT = 20\n if left.value():\n oled.fill_rect(WIDTH-40, 50, BAR_WIDTH, BAR_HEIGHT, 0)\n else:\n oled.fill_rect(WIDTH-40, 50, BAR_WIDTH, BAR_HEIGHT, 1)\n\n if center.value():\n oled.fill_rect(50, 50, BAR_WIDTH, BAR_HEIGHT, 0)\n else:\n oled.fill_rect(50, 50, BAR_WIDTH, BAR_HEIGHT, 1)\n\n if right.value():\n oled.fill_rect(0, 50, BAR_WIDTH, BAR_HEIGHT, 0)\n else:\n oled.fill_rect(0, 50, BAR_WIDTH, BAR_HEIGHT, 1)\n\n oled.show()\n\n\n\n# 0=stopped, 1=forward, 2=turing right, 3=turning left\ndrive_state = 0\ncounter = 0\nwhile True:\n if left.value()==0:\n print('Left')\n #left_tone()\n turn_right()\n update_oled()\n drive_state = 2\n if center.value()==0:\n print('Center')\n center_tone()\n reverse()\n update_oled()\n drive_state = 0\n if right.value()==0:\n print('Right')\n #right_tone()\n turn_left()\n update_oled()\n drive_state = 3\n\n # if (left.value()==1) and (center.value()==1) and (right.value()==1):\n if left.value() and center.value() and right.value():\n print('Go forward!') \n drive_state = 1\n # forward_tone()\n forward()\n update_oled()\n print(\"counter: \", counter)\n counter += 1\n sleep(.25)\n
Pins GP6, 7, 8 and 9
"},{"location":"robots/03-rainbow-bot/","title":"Rainbow Bot","text":"This robot takes our base robot and adds an LED strip arranged in a 12X6 pixel grid to display colors and patterns based on what the robot is doing or thinking about.
We use the same materials as our Base Robot but we add a low-cost addressable LED strips that are easy to hook up with just power, ground and data wires added to our breadboard. The LED is known as an addressable LED strip since you can individually program each LED. The standard is called the WS-2812B LED strip and is often called a NeoPixel LED strip (The Adafruit Term). We also used a Python library called a Neopixel micropython library, although the library is not created or maintained by Adafruit.
Of course, you can also add longer LED strips and program the patterns in interesting ways.
"},{"location":"robots/03-rainbow-bot/#part-1-ordering-the-led-strip","title":"Part 1: Ordering The LED Strip","text":"The LED strips come in a variety of lengths, density and packing. We use the 1 meter long strips that have 60 pixels/meter. These strips are easy to cut apart and solder. We like the black backgrounds but they also come with white. The LED strips come with three packaging options:
The waterproofing options tend to be a little more expensive but can also provide a bit more protection for the components on the strips. Waterproofing keeps moisture and dust out of the circuits, but does not mean that they can be submerged under water.
A sample place to purchase them is here
We can take a $3 strip of 60 LEDs and cut them up into six segments of 10 LEDs each for a cost of around 50 cents per strip. We solder stranded wire to the segments and then put 22 gauge solid wire to make them easy to put in the breadboards.
"},{"location":"robots/03-rainbow-bot/#connecting-the-led-strips","title":"Connecting the LED Strips","text":""},{"location":"robots/03-rainbow-bot/#adding-a-standoff","title":"Adding a Standoff","text":""},{"location":"robots/03-rainbow-bot/#upgrading-to-9-volt-power","title":"Upgrading to 9 Volt Power","text":"Our base robot only needed power for the motors. This robot has 72 RGB LEDs so it might draw more power. So we upgraded the 6 volt battery pack with 4 AA batteries to two packs of 3 batteries for a total of 9 volts. This allows the robot to continue to run even when the batteries are partially drained. The battery packs must be wired in series to deliver the full 9 volts to the input of the motor controller where it powers the motors and also runs though a voltage regulator to power the reset of the robot.
"},{"location":"robots/03-rainbow-bot/#72-pixel-configuration","title":"72 Pixel Configuration","text":"Here is the top view of the LEDs shining through the clear plexiglass.
You can see the individual LEDs in this configuration. By adding a small space between the plexiglass and a diffusion layer you can get a much more uniform color distribution over the top surface of the robot.
"},{"location":"robots/03-rainbow-bot/#part-2-making-the-connections","title":"Part 2: Making The Connections","text":"The LED strips use 5 volts of power and have a GND and a data connector. To make the connections we connect the center pin to Pin 0 (upper left corner of the Pico), the GND to the ground rail and the 5 volt to the 5 volt power rail.
"},{"location":"robots/03-rainbow-bot/#part-3-adding-the-neopixel-library","title":"Part 3: Adding the Neopixel Library","text":""},{"location":"robots/03-rainbow-bot/#part-4-testing-your-code","title":"Part 4: Testing Your Code","text":"In our first test, we will just make the first pixel on the LED strip blink bright red.
import machine, neopixel, time\n# Set the pin number and number of pixels\nLED_PIN = machine.Pin(4)\nNUMBER_PIXELS = 12\nnp = neopixel.NeoPixel(LED_PIN, NUMBER_PIXELS)\n\n# blink the first pixel red\n\nwhile True:\n np[0] = (255, 0, 0)\n np.write()\n time.sleep(1)\n np[0] = (0, 0, 0)\n np.write()\n time.sleep(1)\n
"},{"location":"robots/03-rainbow-bot/#functions-for-drawing-on-matrix","title":"Functions For Drawing on Matrix","text":"The numbering of the pixels is a bit odd. The first 12 are 0 to 11, but the second 12 pixels are in reverse order, so the second row counts from 23 down to 13. Here are some functions that demonstrate this:
import time\nfrom neopixel import Neopixel\n\nnumpix = 72\nstrip = Neopixel(numpix, 0, 0, \"GRB\")\n\nred = (255, 0, 0)\norange = (255, 150, 0)\nyellow = (255, 255, 0)\ngreen = (0, 255, 0)\nblue = (0, 0, 255)\nindigo = (75, 0, 130)\nviolet = (138, 43, 226)\ncolors = (red, orange, yellow, green, blue, indigo, violet)\n\nstrip.brightness(255)\n\ndef color_wipe():\n for color in colors:\n for i in range(numpix):\n strip.set_pixel(i, color)\n strip.show()\n time.sleep(0.01)\n\ndef color_wipe_2():\n for color in colors:\n for i in range(12):\n strip.set_pixel(i, color)\n strip.set_pixel(i+12, color)\n strip.set_pixel(i+24, color)\n strip.set_pixel(i+36, color)\n strip.set_pixel(i+48, color)\n strip.set_pixel(i+60, color)\n strip.show()\n time.sleep(0.01)\n\ndef color_wipe_3():\n for color in colors:\n for i in range(12):\n strip.set_pixel(i, color)\n strip.set_pixel(23-i, color)\n strip.set_pixel(i+24, color)\n strip.set_pixel(47-i, color)\n strip.set_pixel(48+i, color)\n strip.set_pixel(71-i, color)\n strip.show()\n time.sleep(0.3)\n\n# offset is the color to start (0 to 6)\n# dir is 1 for forward and -1 for reverse\ndef color_wipe_4(offset, dir):\n for i in range(12):\n if dir == 1:\n this_color = colors[ ((i-offset) %7 )]\n else:\n this_color = colors[ ((i+offset) %7 )]\n strip.set_pixel(i, this_color)\n strip.set_pixel(23-i, this_color)\n strip.set_pixel(i+24, this_color)\n strip.set_pixel(47-i, this_color)\n strip.set_pixel(48+i, this_color)\n strip.set_pixel(71-i, this_color)\n strip.show()\n # time.sleep(0.01)\n\nwhile True:\n for counter in range(100):\n color_wipe_4(counter %7, 1)\n for counter in range(100):\n color_wipe_4(counter%7, -1) \n
"},{"location":"robots/03-rainbow-bot/#full-source-code","title":"Full Source Code","text":"We now combine the motor controls, the distance sensor and the LED functions so that a moving rainbow pattern moves from to back as the robot moves forward. If the robot encounters an obstacle, the robot will backup and change the direction of the rainbow. After it backs up a bit it will turn and move forward again.
main.py
from machine import Pin, PWM\nfrom time import sleep\nfrom machine import Pin\nfrom machine import I2C\nimport VL53L0X\nfrom neopixel import Neopixel\n\n# Motor Code\n# lower right pins with USB on top\nRIGHT_FORWARD_PIN = 19\nRIGHT_REVERSE_PIN = 18\nLEFT_FORWARD_PIN = 20\nLEFT_REVERSE_PIN = 21\n\nright_forward = PWM(Pin(RIGHT_FORWARD_PIN))\nright_reverse = PWM(Pin(RIGHT_REVERSE_PIN))\nleft_forward = PWM(Pin(LEFT_FORWARD_PIN))\nleft_reverse = PWM(Pin(LEFT_REVERSE_PIN))\n\n# Sensor Code\nsda=machine.Pin(16)\nscl=machine.Pin(17)\ni2c=machine.I2C(0, sda=sda, scl=scl)\n\n# Create a VL53L0X object\ntof = VL53L0X.VL53L0X(i2c)\ntof.start() # startup the sensor\n\n# used to blink the onboard LED\nled_onboard = machine.Pin(25, machine.Pin.OUT)\n\n# LED Code\nnumpix = 72\nstrip = Neopixel(numpix, 0, 0, \"GRB\")\n# we turn the brightness way down to not oversaturate the brightness in the video\nstrip.brightness(20)\n\n# driving parameters\nPOWER_LEVEL = 30000 # use a value from 20000 to 65025\nTURN_THRESHOLD = 400 # 25 cm\nTURN_TIME = .25 # seconds of turning\nBACKUP_TIME = .75 # seconds of backing up if obstacle detected\n\nred = (255, 0, 0)\norange = (255, 165, 0)\nyellow = (255, 255, 0)\ngreen = (0, 255, 0)\nblue = (0, 0, 255)\nindigo = (75, 0, 130)\nviolet = (138, 43, 226)\ncolors = (red, orange, yellow, green, blue, indigo, violet)\n\ndef turn_motor_on(pwm):\n pwm.duty_u16(POWER_LEVEL)\n\ndef turn_motor_off(pwm):\n pwm.duty_u16(0)\n\ndef forward():\n turn_motor_on(right_forward)\n turn_motor_on(left_forward)\n turn_motor_off(right_reverse)\n turn_motor_off(left_reverse)\n #for i in range(numpix):\n # strip.set_pixel(i, green)\n #strip.show()\n\ndef reverse():\n turn_motor_on(right_reverse)\n turn_motor_on(left_reverse)\n turn_motor_off(right_forward)\n turn_motor_off(left_forward)\n #for i in range(numpix):\n # strip.set_pixel(i, red)\n #strip.show()\n\ndef turn_right():\n turn_motor_on(right_forward)\n turn_motor_on(left_reverse)\n turn_motor_off(right_reverse)\n turn_motor_off(left_forward)\n #for i in range(numpix):\n # strip.set_pixel(i, blue)\n #strip.show()\n\ndef turn_left():\n turn_motor_on(right_reverse)\n turn_motor_on(left_forward)\n turn_motor_off(right_forward)\n turn_motor_off(left_reverse)\n #for i in range(numpix):\n # strip.set_pixel(i, yellow)\n #strip.show()\n\ndef stop():\n turn_motor_off(right_forward)\n turn_motor_off(right_reverse)\n turn_motor_off(left_forward)\n turn_motor_off(left_reverse)\n for i in range(numpix):\n strip.set_pixel(i, violet)\n strip.show()\n\ndef read_sensor_avg():\n total = 0\n for i in range(10):\n total = total + tof.read()\n sleep(.01)\n return int(total/10)\n\n# offset is the color to start (0 to 6)\n# dir is 1 for forward and -1 for reverse\ndef color_wipe_4(offset, dir):\n for i in range(12):\n if dir == 1:\n this_color = colors[ ((i-offset) %7 )]\n else:\n this_color = colors[ ((i+offset) %7 )]\n strip.set_pixel(i, this_color)\n strip.set_pixel(23-i, this_color)\n strip.set_pixel(i+24, this_color)\n strip.set_pixel(47-i, this_color)\n strip.set_pixel(48+i, this_color)\n strip.set_pixel(71-i, this_color)\n strip.show()\n # time.sleep(0.01)\n\ncounter = 0\nwhile True:\n dist = read_sensor_avg() \n if dist < TURN_THRESHOLD:\n print('object detected')\n reverse()\n\n color_wipe_4(counter % 7, -1)\n sleep(.1)\n counter += 1\n\n color_wipe_4(counter % 7, -1)\n sleep(.1)\n counter += 1\n\n color_wipe_4(counter % 7, -1)\n sleep(.1)\n counter += 1 \n\n color_wipe_4(counter % 7, -1)\n sleep(.1)\n counter += 1 \n\n color_wipe_4(counter % 7, -1)\n sleep(.1)\n counter += 1 \n\n turn_right()\n color_wipe_4(counter % 7, -1)\n sleep(.1)\n counter += 1 \n\n color_wipe_4(counter % 7, -1)\n sleep(.1)\n counter += 1 \n\n color_wipe_4(counter % 7, -1)\n sleep(.1)\n counter += 1 \n\n else:\n forward()\n color_wipe_4(counter % 7, 1)\n counter += 1\n
Rainbow Bot Source Code
"},{"location":"robots/03-rainbow-bot/#references","title":"References","text":"This lesson will allow you to draw simple faces on the display on the front of our robots. The faces can reflect the emotions of your robot. We are inspired by the faces on the
"},{"location":"robots/05-ajusta-bot/","title":"Adjusta Bot","text":"This robot uses buttons and a rotary encoder to allow the user to reprogram the Pico and adjust the collision avoidance parameters.
"},{"location":"robots/20-faqs/","title":"Robots Frequently Asked Questions","text":""},{"location":"robots/20-faqs/#why-a-two-motor-three-wheel-robot-not-four-motor-four-wheel-robot","title":"Why a two-motor three wheel robot, not four motor four wheel robot?","text":"We evaluated both the two-motor three wheel robot and the four motor four wheel robots and found that we could achieve all our learning objectives with the two-motor three wheel version. The kits are lower cost, have a simpler assembly process and have plenty of power for our projects.
"},{"location":"robots/20-faqs/#why-do-we-use-the-l293d-vs-l298n-motor-driver","title":"Why do we use the L293D vs L298N Motor Driver?","text":"Both of these popular motor driver chips are commonly used in robot kits. Our focus is teaching the principals of computational thinking using low-cost parts. Since the L293D is lower cost and has plenty of power for our two-motor robots we chose that. There is a detailed side-by-side comparison [here] (https://www.etechnophiles.com/l293d-vs-l298n-motor-driver-differences-specifications-and-pinouts/)
"},{"location":"robots/20-faqs/#why-dont-we-just-create-a-remote-control-robot-car","title":"Why don't we just create a remote control robot car?","text":"Remote control cars are very fun to play with. But we find that it often detracts from our mission of learning how to code. So we try to minimize the time students spend just driving their cars around our tracks.
"},{"location":"sensors/01-intro/","title":"Sensors in MicroPython","text":""},{"location":"sensors/01-intro/#light-sensor","title":"Light Sensor","text":""},{"location":"sensors/01-intro/#ping","title":"Ping","text":""},{"location":"sensors/01-intro/#temperature","title":"Temperature","text":""},{"location":"sensors/02-photosensor/","title":"Light Sensor with Raspberry Pi Pico in MicroPython","text":"A photoresistor is a sensor that decreases resistance when light is shined on its surface. With no light a photoresistor has high resistance in the range of megaohms. As light shines on the surface the resistance drops to kiloohms. We can use this effect as a light sensor.
To convert the variable resistance of a photoresistor to something we can measure with our microcontroller we will need to build a small circuit that includes a 10K ohm resistor. We then measure the voltage between the photoresistor and the 10K resistor as light falls on the sensor. The top and bottom of the circuit are tied to ground and a power rail. This will move the voltage of the midpoint of the circuit.
"},{"location":"sensors/02-photosensor/#circuit","title":"Circuit","text":"We want to make sure that we use the stable analog ground (AGND) and analog to reference voltage at either end of the circuit to protect the circuit from all the noise of the power in our processor. Using other power and ground pins will work, but power fluctuations will make the result noisy.
"},{"location":"sensors/02-photosensor/#sample-code","title":"Sample Code","text":"Our program will first use the Analog to Digital Circuit (ADC0) as an input. On the Pico this is on pin 26.
import machine\nimport time\nphoto_pin = machine.ADC(26)\n\nwhile True:\n val = photo_pin.read_u16()\n print(val)\n time.sleep(.2)\n
When you run the program a series of print values is displayed in the shell every 1/5th of a second. You can also use the Thonny plot window to see how the numbers change and you cover and uncover detector from a light source.
"},{"location":"sensors/02-photosensor/#experiments","title":"Experiments","text":"Video Demo
In this lab we will hook up a Potentiometer to the Analog to Digital Converter of the Raspberry Pi Pico.
"},{"location":"sensors/02-pot/#connections","title":"Connections","text":"Potentiometers have three wires:
The rails are hooked up to fixed reference voltages. As you turn the know, the center tap returns a value that varies between the ground and the positive rail voltages.
On many microcontrollers, the positive voltage is very \"noisy\" in that it contains voltage that varies as the components draw power. The Raspberry Pi has a special region for high-quality data gathering that is isolated from this noise. To use this section you MUST hook the rails to the right pins.
ADC_VREF is the ADC power supply (and reference) voltage, and is generated on Pico by filtering the 3.3V supply. This pin can be used with an external reference if better ADC performance is required.
AGND is the ground reference for GPIO26-29, there is a separate analog ground plane running under these signals and terminating at this pin.
<!-- ->
graph LR\np[Pico]-->|ADC_VREF 36 row=6| pos(Positive)\np[Pico]-->|AGND 33 row=8| neg(Negative)\np[Pico]-->|GP26 pin=26 ADC0 31 row=10| tap(Center Tap)\n pos(Positive) --- pot(Potentiometer)\n neg(Negative) --- pot(Potentiometer)\n tap(Center Tap) --- pot(Potentiometer)\n
Connect the positive to pin 35 ADC_REF (row 6 on the breadboard) and the negative to pin 33 AGND (row 8 on the breadboard). The Pico has special noise reduction circuits to avoid power supply jitter on these reference pins.
"},{"location":"sensors/02-pot/#sampling-data","title":"Sampling data","text":"Sometimes the data coming from your Potentiometer is noisy. You can sample the value multiple times and then average the values.
Here is a sample program. Just pass in the pin and a count and it will return the average values. This version waits 5 milliseconds between samples.
def sample_pot(pin, count):\n total = 0\n for i in range(count):\n total += int(pin.read_u16())\n utime.sleep_ms(5)\n return int(total / count)\n
pot_pin_1 = machine.ADC(26)\n# return a value after sampling 10 times\nsample_pot(pot_pin_1, 10)\n
"},{"location":"sensors/03-ping/","title":"Ultrasonic Ping Sensor","text":"The HC-SR04 is a low cost ($4) sensor that measures the distance to an object in front of it.
"},{"location":"sensors/03-ping/#wiring-diagram","title":"Wiring Diagram","text":"# Sample code to test HC-SR04 Ultrasonice Ping Sensor\n# Connect GND to any GND pin on the Pico\n# Connnect VCC to VBUS or 5 Volt power\n\nfrom machine import Pin, Timer\nimport utime\n\nTRIGGER_PIN = 15 # With USB on the top, this pin is the bottom left corner\nECHO_PIN = 14 # One up from bottom left corner\n\n# Init HC-SR04 pins\ntrigger = Pin(TRIGGER_PIN, Pin.OUT) # send trigger out to sensor\necho = Pin(ECHO_PIN, Pin.IN) # get the delay interval back\n\ndef ping():\n trigger.low()\n utime.sleep_us(2) # Wait 2 microseconds low\n trigger.high()\n utime.sleep_us(5) # Stay high for 5 miroseconds\n trigger.low()\n while echo.value() == 0:\n signaloff = utime.ticks_us()\n while echo.value() == 1:\n signalon = utime.ticks_us()\n timepassed = signalon - signaloff\n distance = (timepassed * 0.0343) / 2\n return distance\n\nwhile True:\n print(\"Distance:\", ping(), \" cm\")\n utime.sleep(.25)\n
"},{"location":"sensors/03-ping/#oled","title":"OLED","text":"If you have a small OLED screen, you can also display the results of the distance measurement directly on an OLED screen.
See the OLED example here: OLED Ping Example
"},{"location":"sensors/04-temp-dht11/","title":"Sensing Temperature and Humidity with the DHT11 Sensor","text":""},{"location":"sensors/04-temp-dht11/#sample-code","title":"Sample Code","text":"from machine import Pin\nimport utime as time\nfrom dht import DHT11, InvalidChecksum\n\nDHT_PIN = 15\n# this is a bit odd, since although it is an input, we need a pull down set\ndhtSensor = DHT11(Pin(DHT_PIN, Pin.OUT, Pin.PULL_DOWN))\n\nwhile True:\n temp = dhtSensor.temperature\n humidity = dhtSensor.humidity/100\n print(\"Temp: {}\u00b0C\".format(temp), \"| Hum: {0:.1%}\".format(humidity))\n
"},{"location":"sensors/04-temp-dht11/#references","title":"References","text":"https://randomnerdtutorials.com/micropython-ds18b20-esp32-esp8266/
```py
"},{"location":"sensors/05-temp-dsb20/#complete-project-details-at-httpsrandomnerdtutorialscom","title":"Complete project details at https://RandomNerdTutorials.com","text":"import machine, onewire, ds18x20, time
ds_pin = machine.Pin(4) ds_sensor = ds18x20.DS18X20(onewire.OneWire(ds_pin))
roms = ds_sensor.scan() print('Found DS devices: ', roms)
while True: ds_sensor.convert_temp() time.sleep_ms(750) for rom in roms: print(rom) print(ds_sensor.read_temp(rom)) time.sleep(5) ```
"},{"location":"sensors/06-accelerometer-compass/","title":"MPU-9250 Accelerometer Gyroscope Compass","text":"The MPU-9250 by InvenSense is a nine-axis motion tracking device. It includes:
(Gyro + Accelerometer + Compass) MEMS MotionTracking\u2122 Device
1PCS GY-91 10DOF Accelerometer Gyroscope Compass Temp/Pressure MPU-9250 BMP-280
You only need to hook the 3.3 to VIN, the GND to GND and the SCL and SDA. The other connections are not needed.
"},{"location":"sensors/06-accelerometer-compass/#i2c-scanner-results","title":"I2C Scanner Results","text":"import machine\nsda=machine.Pin(16) # row one on our standard Pico breadboard\nscl=machine.Pin(17) # row two on our standard Pico breadboard\ni2c=machine.I2C(0, sda=sda, scl=scl, freq=400000)\nprint(\"Device found at decimal\", i2c.scan())\n
Device found at decimal [104, 118]
"},{"location":"sensors/06-accelerometer-compass/#mpu9250-drivers","title":"MPU9250 Drivers","text":"MicroPython Driver
import utime\nfrom machine import I2C, Pin\nfrom mpu9250 import MPU9250\n\ni2c = I2C(scl=Pin(22), sda=Pin(21))\nsensor = MPU9250(i2c)\n\nprint(\"MPU9250 id: \" + hex(sensor.whoami))\n\nwhile True:\n print(sensor.acceleration)\n print(sensor.gyro)\n print(sensor.magnetic)\n print(sensor.temperature)\n\n utime.sleep_ms(1000)\n
"},{"location":"sensors/06-accelerometer-compass/#lsm6ds3-accelerometer-and-gyroscope","title":"LSM6DS3 Accelerometer and Gyroscope","text":"The LSM6DS3 is a combined 3-axis accelerometer gyroscope with an 8kb FIFO buffer and an embedded processing interrupt function for the gyro sensor, specifically for the mobile phone market. Specifications:
With LSM6DS3 breakout, you will be able to detect the motion, impact, and tilt.
"},{"location":"sensors/06-accelerometer-compass/#selecting-the-accelerometer-sensitivity-range","title":"Selecting the Accelerometer Sensitivity Range","text":"The sensitivity ratings for an accelerometer specify the maximum measurable acceleration that the device can sense along its measurement axis (or axes). When an accelerometer states its sensitivity as \u00b12/\u00b14/\u00b116/\u00b18 g, it usually means that the device can be set to different ranges or scales of measurement. Here's what each of those ratings mean:
"},{"location":"sensors/06-accelerometer-compass/#2g-sensitivity-setting","title":"\u00b12g Sensitivity Setting","text":"The accelerometer can measure accelerations between -2g and +2g. This is a lower range, so it can detect small changes in acceleration with high precision. This range would be ideal for applications that require precise measurements of small movements. For example, the orientation of a screen as it is being tilted or rotated.
"},{"location":"sensors/06-accelerometer-compass/#4g-sensitivity-setting","title":"\u00b14g Sensitivity Setting","text":"In this setting, the accelerometer can measure accelerations between -4g and +4g. This provides a slightly larger range than the \u00b12g setting, making it suitable for applications that might experience slightly stronger forces or movements but still require a good amount of precision.
One example is wearable fitness trackers, like smartwatches and sport bands, often have built-in accelerometers to monitor and analyze user movement. For regular walking or jogging, a \u00b12g accelerometer might suffice. However, for more intense activities like plyometric exercises, sprinting, or aggressive directional changes in sports like basketball or soccer, the forces exerted on the tracker can exceed 2g. These activities involve rapid starts, stops, jumps, and changes in direction.
Why \u00b14g?: The accelerometer needs to capture both the subtleties of everyday motion and the higher-intensity bursts of activity without saturating or clipping the data. A \u00b14g range provides a good balance between sensitivity and maximum measurable acceleration, making it suitable for wearable devices intended for diverse and dynamic physical activities.
With this capability, fitness trackers can more accurately measure and analyze the user's movements across a wider range of activities, providing better data and feedback on their workouts and performance.
\u00b116g: The accelerometer can measure accelerations between -16g and +16g. This is a much broader range and is less sensitive to small movements. It's ideal for applications that are exposed to stronger forces or rapid movements, such as crash detection or certain sports applications.
\u00b18g: The accelerometer can measure accelerations between -8g and +8g. This provides a balance between sensitivity and range, suitable for applications that might experience moderate forces.
In practice, these settings allow the user (or designer) to choose the best range for their application. For applications where precision in detecting small accelerations is essential, a lower range (like \u00b12g) would be selected. In contrast, for applications where large accelerations may occur, a higher range (like \u00b116g) would be more appropriate to prevent the sensor from saturating or clipping the readings.
"},{"location":"sensors/06-accelerometer-compass/#selecting-gyroscope-sensitivity-ranges","title":"Selecting Gyroscope Sensitivity Ranges","text":"Gyroscopes measure the rate of angular change, often referred to as the angular velocity or angular rate. The unit for this rate of change is typically represented in degrees per second (DPS).
The sensitivity ranges for a gyroscope describe the maximum rate of rotation it can measure. When a gyroscope has sensitivity ranges listed as \u00b1125/245/500/\u00b11000/\u00b12000 DPS, it means the device has multiple selectable scales or ranges of measurement. Let's break down each of these ranges:
\u00b1125 DPS: At this setting, the gyroscope can measure angular velocities between -125 and +125 degrees per second. This is a low range and would provide high precision for slow rotational movements. For example, a slowly turning sailboat.
\u00b1245 DPS: The device can measure angular velocities between -245 and +245 degrees per second. This offers a slightly larger range than the \u00b1125 DPS setting.
\u00b1500 DPS: This setting allows the gyroscope to measure between -500 and +500 degrees per second.
\u00b11000 DPS: The device can measure angular velocities ranging between -1000 and +1000 degrees per second. This is suitable for applications that might experience moderately fast rotations.
\u00b12000 DPS: At this maximum setting, the gyroscope can capture angular velocities between -2000 and +2000 degrees per second. It's best for very fast rotational movements. For example, inside a rapidly spinning ball thown in the air.
"},{"location":"sensors/06-accelerometer-compass/#gyroscope-sensitivity-ranges-and-their-applications","title":"Gyroscope Sensitivity Ranges and Their Applications","text":"Here are some examples of different applications that might be used in each of these ranges.
"},{"location":"sensors/06-accelerometer-compass/#125-dps","title":"\u00b1125 DPS","text":"Module size: 13 * 18mm
Package Include: 1 x GY-LSM6DS3 IIC/SPI Accelerometer Tilt Angle Gyro Sensor Breakout Transfer Module
"},{"location":"sensors/06-accelerometer-compass/#settings-for-robot-tracking-orientation-and-position","title":"Settings for Robot Tracking Orientation and Position","text":"If we are Tracking Orientation and Position for a Small Robot we need the following:
"},{"location":"sensors/06-accelerometer-compass/#1-accelerometer","title":"1. Accelerometer:","text":"Remember to test and calibrate in real-world scenarios for optimal performance.
"},{"location":"sensors/06-accelerometer-compass/#references","title":"References","text":"The Bosch BME280 is a low-cost ($2) temperature, and pressure sensor that has an I2C interface. This is an ideal way to learn how to use an I2C interface.
Note mbe280 is different from the BMP280 and does not read humidity. The default address is Hex x76 or decimal 118.
"},{"location":"sensors/06-bme280/#circuit","title":"Circuit","text":"The BME280 has a standard I2C interface with four wires:
After you have connected your sensor you can check the connection by running a quick \"I2C Scanner\" to find the address of the sensor.
import machine\nsda=machine.Pin(0) # This is GP0 on row one of our standard Pico breadboard with the USB on top\nscl=machine.Pin(1) # Row two of our standard Pico breadboard\ni2c=machine.I2C(0, sda=sda, scl=scl, freq=400000)\nprint(\"Device found at decimal\", i2c.scan())\n
Results:
[118]\n
This is decimal of hex 0x76
If the scanner does not return a number then your connections might not be working.
"},{"location":"sensors/06-bme280/#bme280-driver","title":"BME280 Driver","text":"You should be able to find the BME280 driver by using the Thonny Tool -> Manage Packages... menu. If that does not work you can try a github search:
Search GitHub for MBE 280 Python Code
"},{"location":"sensors/06-bme280/#test-code","title":"Test Code","text":"import machine\nfrom utime import sleep\nimport BME280\nsda=machine.Pin(0) # This is GP0 on row one of our standard Pico breadboard with the USB on top\nscl=machine.Pin(1) # Row two of our standard Pico breadboard\ni2c=machine.I2C(0, sda=sda, scl=scl, freq=400000)\n\n# initialize the bme class using the default address\nbme = BME280()\n(chip_id, chip_version) = bme.getID()\nprint( \"Chip ID:\", chip_id)\nprint( \"Version:\", chip_version)\n\nwhile True():\n # get new data from the \n temperature,pressure,humidity = bme.getData()\n #adj_bar = bme.adj_baro(pressure, temperature)\n print(\"Adj {}\".format(bme.adj_baro(pressure, temperature)))\n\n print( \"Temperature: {}C\".format(temperature))\n print( \"Pressure: {}hpa, {}In\".format(pressure, round(pressure * 0.02953, 2)))\n\n sleep(1)\n
"},{"location":"sensors/06-bme280/#code-2","title":"Code 2","text":"from machine import I2C\nimport BME280\nfrom time import sleep\n\nsda=machine.Pin(16)\nscl=machine.Pin(17)\ni2c=machine.I2C(0,sda=sda, scl=scl, freq=400000)\nbme = BME280.BME280(i2c=i2c)\n# print(i2c.scan())\n\nwhile True:\n\n temp = bme.temperature\n hum = bme.humidity\n pres = bme.pressure\n # uncomment for temperature in Fahrenheit\n temp = (bme.read_temperature()/100) * (9/5) + 32\n #temp = str(round(temp, 2)) + 'F'\n print('Temperature: ', temp, end='')\n print(' Humidity:', hum, end='')\n print(' Pressure:', pres)\n\n sleep(5)\n
"},{"location":"sensors/06-bme280/#references","title":"References","text":"BME280 4. BME280 logger 5. The Electronics India
"},{"location":"sensors/07-VL53L0X_GY/","title":"VL53L0X Time-of-Flight Laser Ranging Module IR Distance Sensor","text":"Figure: VL53L0X in the GY-530 package.
The VL53L0X is a low-cost ($5) time-of-flight light-based distance sensor that is easy to use. It comes packaged in a I2C board and gives precise distance measurements up to 1.5 meters away. It measures the time that light pulses take to travel to an object and back to estimate distance. Light travels about 1 foot every nanosecond, so the timing inside this little chip must be very accurate.
The VL53L0X integrates a group of Single Photon Avalanche Diodes (SPAD) and embeds ST Electronic's second generation FlightSense\u2122 patented technology. The VL53L0X\u2019s 940 nm emitter Vertical Cavity Surface-Emitting Laser (VCSEL), is safe for kids and totally invisible to the human eye. Coupled with internal physical infrared filters, the sensor enables longer ranging distance, higher immunity to ambient light, and better robustness to cover glass optical crosstalk.
"},{"location":"sensors/07-VL53L0X_GY/#circuit","title":"Circuit","text":"Hook the VCC to the 3.3 out of the Pico, the GND of the sensor to andy of the GND pins of the Pico and then connect the Clock and Data to two pins such as GPIO pins 16 and 17.
"},{"location":"sensors/07-VL53L0X_GY/#i2c-scanner-test","title":"I2C Scanner Test","text":"We first run the I2C scanner program to verify that the sensor is connected correctly and is responding to the I2C bus scan.
import machine\nsda=machine.Pin(0) # row one on our standard Pico breadboard\nscl=machine.Pin(1) # row two on our standard Pico breadboard\ni2c=machine.I2C(0, sda=sda, scl=scl, freq=400000)\nprint(\"Device found at decimal\", i2c.scan())\n
This should return a single decimal number.
"},{"location":"sensors/07-VL53L0X_GY/#download-the-vl53l0x-driver","title":"Download The VL53L0X Driver","text":"If you are using Thonny, you can try to use the \"Manage Packages\" menu and search for the driver.
We have a sample of the driver here
"},{"location":"sensors/07-VL53L0X_GY/#create-a-test-program","title":"Create a Test Program","text":"# Test program for VL53L0X\nimport time\nfrom machine import Pin\nfrom machine import I2C\nimport VL53L0X\n\nsda=machine.Pin(16) # lower right pin\nscl=machine.Pin(17) # one up from lower right pin\ni2c=machine.I2C(0, sda=sda, scl=scl, freq=400000)\n\n# Create a VL53L0X object\ntof = VL53L0X.VL53L0X(i2c)\n\nwhile True:\n tof.start()\n tof.read()\n print(tof.read())\n tof.stop()\n time.sleep(0.1)\n
"},{"location":"sensors/07-VL53L0X_GY/#use-the-thonny-plot","title":"Use the Thonny Plot","text":""},{"location":"sensors/07-VL53L0X_GY/#reference-purchase-links","title":"Reference Purchase links","text":"qty 10 for $25
"},{"location":"sensors/08-ir-distance-sensor/","title":"IR Distance Sensor","text":""},{"location":"sensors/08-ir-distance-sensor/#ir-distance-sensors","title":"IR Distance Sensors","text":"IR distance sensors are low cost (five for $3) but may have problems working in rooms with outdoor lighting. They have an adjustable potentiometer on them that can be used to adjust a triggering distance call the threshold distance. The sensors return a HIGH signal if there is no object within the threshold distance and a LOW signal if there is an object within this distance. Since the sensor threshold distance can not be adjusted programmatically they are best suited when you can manually adjust the potentiometer to change the threshold.
"},{"location":"sensors/08-ir-distance-sensor/#connections","title":"Connections","text":"These sensors have three wires:
# connections to the three IR distance sensors\nleft = Pin(8, Pin.IN, Pin.PULL_DOWN)\ncenter = Pin(7, Pin.IN, Pin.PULL_DOWN)\nright = Pin(6, Pin.IN, Pin.PULL_DOWN)\n
"},{"location":"sensors/08-ir-distance-sensor/#the-ky-032","title":"The KY-032","text":"The KY-032 obstacle avoidance sensor is a four-wire distance-adjustable, infrared proximity sensor designed for wheeled robots. Also known as AD-032.
The sensor detection distance ranges from 2cm to 40cm, it can be adjusted by turning the potentiometer knob. The operating voltage is 3.3V-5V so it is suitable for a variety of microcontrollers like Arduino, ESP32, Teensy, ESP8266, Raspberry Pi, and others.
It has strong adaptability to ambient light and it is fairly accurate to sense changes in the surrounding environment.
"},{"location":"sensors/08-ir-distance-sensor/#speaker-test","title":"Speaker Test","text":"from machine import Pin, PWM\nfrom utime import sleep\n\n\nleft = Pin(8, Pin.IN, Pin.PULL_DOWN)\ncenter = Pin(7, Pin.IN, Pin.PULL_DOWN)\nright = Pin(6, Pin.IN, Pin.PULL_DOWN)\n\nSPEAKER_PIN = 21\n# create a Pulse Width Modulation Object on this pin\nspeaker = PWM(Pin(SPEAKER_PIN))\n\ndef sound_off():\n speaker.duty_u16(0)\n\ndef left_tone():\n speaker.duty_u16(1000)\n speaker.freq(300) # 1 Kilohertz\n sleep(.5) # wait a 1/4 second\n sound_off()\n\ndef center_tone():\n speaker.duty_u16(1000)\n speaker.freq(800)\n sleep(.5)\n sound_off()\n\ndef right_tone():\n speaker.duty_u16(1000)\n speaker.freq(400)\n sleep(.5)\n sound_off()\n\ndef right_tone():\n speaker.duty_u16(1000)\n speaker.freq(800)\n sleep(.25)\n sound_off()\n\ndef forward_tone():\n speaker.duty_u16(1000)\n speaker.freq(400)\n sleep(.1)\n speaker.freq(900)\n sleep(.1)\n speaker.freq(1200)\n sleep(.1)\n sound_off()\n\n# 0=stopped, 1=forward, 2=turing right, 3=turning left\ndrive_state = 0\nwhile True:\n if left.value()==0:\n print('Left')\n left_tone()\n drive_state = 2\n if center.value()==0:\n print('Center')\n center_tone()\n drive_state = 0\n if right.value()==0:\n print('Right')\n right_tone()\n drive_state = 3\n\n # if (left.value()==1) and (center.value()==1) and (right.value()==1):\n if left.value() and center.value() and right.value():\n print('Go forward!')\n drive_state = 1\n forward_tone()\n sleep(.25)\n
"},{"location":"sensors/08-ir-distance-sensor/#full-program","title":"Full Program","text":"```py from machine import Pin, PWM from utime import sleep import ssd1306
"},{"location":"sensors/08-ir-distance-sensor/#motor-pins-to-the-l293-h-bridge","title":"Motor pins to the L293 H-Bridge","text":"RIGHT_FORWARD_PIN = 17 RIGHT_REVERSE_PIN = 16 LEFT_FORWARD_PIN = 18 LEFT_REVERSE_PIN = 19
right_forward = PWM(Pin(RIGHT_FORWARD_PIN)) right_reverse = PWM(Pin(RIGHT_REVERSE_PIN)) left_forward = PWM(Pin(LEFT_FORWARD_PIN)) left_reverse = PWM(Pin(LEFT_REVERSE_PIN))
"},{"location":"sensors/08-ir-distance-sensor/#connections-to-the-three-ir-distance-sensors","title":"connections to the three IR distance sensors","text":"left = Pin(8, Pin.IN, Pin.PULL_DOWN) center = Pin(7, Pin.IN, Pin.PULL_DOWN) right = Pin(6, Pin.IN, Pin.PULL_DOWN)
SPEAKER_PIN = 21
"},{"location":"sensors/08-ir-distance-sensor/#create-a-pulse-width-modulation-object-on-this-pin","title":"create a Pulse Width Modulation Object on this pin","text":"speaker = PWM(Pin(SPEAKER_PIN))
WIDTH = 128 HEIGHT = 64 CS = machine.Pin(1) SCL = machine.Pin(2) SDA = machine.Pin(3) DC = machine.Pin(4) RES = machine.Pin(5) spi=machine.SPI(0, sck=SCL, mosi=SDA) oled = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)
def turn_motor_on(pwm): pwm.duty_u16(65025)
def turn_motor_off(pwm): pwm.duty_u16(0)
def forward(): turn_motor_on(right_forward) turn_motor_on(left_forward)
def reverse(): turn_motor_on(right_reverse) turn_motor_on(left_reverse)
def turn_right(): turn_motor_on(right_forward) turn_motor_on(left_reverse)
def turn_left(): turn_motor_on(right_reverse) turn_motor_on(left_forward)
def sound_off(): speaker.duty_u16(0)
def left_tone(): speaker.duty_u16(1000) speaker.freq(700) # 1 Kilohertz sleep(.5) # wait a 1/4 second sound_off()
def center_tone(): speaker.duty_u16(1000) speaker.freq(900) sleep(.5) sound_off()
def right_tone(): speaker.duty_u16(1000) speaker.freq(600) sleep(.5) sound_off()
def forward_tone(): speaker.duty_u16(1000) speaker.freq(400) sleep(.1) speaker.freq(900) sleep(.1) speaker.freq(1200) sleep(.1) sound_off()
def update_oled(): oled.fill(0) oled.text(\"CoderDojo Rocks!\", 0, 0, 1)
oled.text(\"Left:\", 0, 10, 1)\noled.text(str(left.value()), 50, 10, 1)\n\n\noled.text(\"Center:\", 0, 20, 1)\noled.text(str(center.value()), 60, 20, 1)\n\noled.text(\"Right:\", 0, 30, 1)\noled.text(str(right.value()), 55, 30, 1)\n\nBAR_WIDTH = 40\nBAR_HEIGHT = 20\nif left.value():\n oled.fill_rect(WIDTH-40, 50, BAR_WIDTH, BAR_HEIGHT, 0)\nelse:\n oled.fill_rect(WIDTH-40, 50, BAR_WIDTH, BAR_HEIGHT, 1)\n\nif center.value():\n oled.fill_rect(50, 50, BAR_WIDTH, BAR_HEIGHT, 0)\nelse:\n oled.fill_rect(50, 50, BAR_WIDTH, BAR_HEIGHT, 1)\n\nif right.value():\n oled.fill_rect(0, 50, BAR_WIDTH, BAR_HEIGHT, 0)\nelse:\n oled.fill_rect(0, 50, BAR_WIDTH, BAR_HEIGHT, 1)\n\noled.show()\n
"},{"location":"sensors/08-ir-distance-sensor/#0stopped-1forward-2turing-right-3turning-left","title":"0=stopped, 1=forward, 2=turing right, 3=turning left","text":"drive_state = 0 counter = 0 while True: if left.value()==0: print('Left') #left_tone() turn_right() update_oled() drive_state = 2 if center.value()==0: print('Center') center_tone() reverse() update_oled() drive_state = 0 if right.value()==0: print('Right') #right_tone() turn_left() update_oled() drive_state = 3
# if (left.value()==1) and (center.value()==1) and (right.value()==1):\nif left.value() and center.value() and right.value():\n print('Go forward!') \n drive_state = 1\n # forward_tone()\n forward()\n update_oled()\nprint(\"counter: \", counter)\ncounter += 1\nsleep(.25)\n```\n\n## More to Explore\n\n1. Try to change the values of the potentiometers on the sensors. What is the minimum and maximum distance you can detect and object?\n2. Does the reflectivity of the object impact the distance of the object?\n3. If you put a small mirror in front of the sensor what happens to the distance measured?\n4. Place the robot near bright sun in a window or try the robot outdoors on both a cloudy day and a sunny day? What is the change is accuracy of the sensors under these conditions? What about running the robot in the dark?\n
"},{"location":"sensors/10-rotary-encoder/","title":"Rotary Encoder","text":"A rotary encoder, or more specifically a directional rotary encoder, may look similar to a potentiometer in some ways. Both have a knob that you turn to adjust a value. But unlike a potentiometer, a rotary encoder is far more flexible in the range and precision of values it can control. Our students love to use them in their projects to change the color or patterns in an LED strip.
Rotary encoders can be thought of as two concentric rings of switches that go on and off as you turn the knob. The switches are placed so that you can tell the direction of rotation by the order the two switches get turned on and off. They turn on and off quickly so we need a high-quality function to quickly detect their changes. And as we learned in the Button lab, switches can be noisy and have a complex state transition that must be debounced to get a good quality signal.
"},{"location":"sensors/10-rotary-encoder/#learning-how-to-monitor-the-rotary-switch-transitions","title":"Learning How to Monitor the Rotary Switch Transitions","text":"We will be using a low-cost ($1 USD) encoder that has five connectors, three for the direction and one for a momentary switch that is closed when you press the knob in. Here is the circuit that we will be using:
We hooked up the outer pins of the encoder to GPIO pins 16 and 17 in the lower right corner of the Pico.
Then we hooked the center pin to the 3.3 volt rail. The Pico likes to pull switches down from the 3.3 volt rail. This means that we will not be connecting any of the pins to GND.
We also hooked up the central press button to GPIO 22 and the 3.3 volt rail.
We then ran this code and turned the knob:
import time\nfrom machine import Pin\n\nrotaryA = Pin(16, Pin.IN, Pin.PULL_DOWN)\nrotaryB = Pin(17, Pin.IN, Pin.PULL_DOWN)\n\nwhile True:\n print(rotaryA.value(), end='')\n print(rotaryB.value())\n time.sleep(.1)\n
the results look like the following lines:
00\n00\n00\n10\n01\n00\n00\n00\n11\n00\n00\n00\n01\n11\n00\n
Note that the bit values the encoders switches (on or off as 0 and 1) are place next to each other on the same line. We did this by making the end of the first print statement be the null string not the default newline character.
This program prints out a LONG stream of numbers, mostly of the value 00
. The values are printed 10 times each second. Now let's take a closer look at only the values that change.
What we would like to do is now only print numbers if there is a change. To do this we will \"pack\" binary values into a two bit number by shifting the A pin value to the left:
import time\nfrom machine import Pin\n\nrotaryA = Pin(16, Pin.IN, Pin.PULL_DOWN)\nrotaryB = Pin(17, Pin.IN, Pin.PULL_DOWN)\n\n# we set the old value to zero for both bits being off\nold_combined = 0\nwhile True:\n A_val = rotaryA.value()\n B_val = rotaryB.value()\n # a sifts by one bit and then is ORed with the B calue\n new_combined = (A_val << 1) | B_val\n if new_combined != old_combined:\n print(A_val, end='')\n print(B_val)\n old_combined = new_combined\n time.sleep(.1)\n
Now we get values that look like this:
01\n11\n00\n01\n11\n00\n01\n11\n10\n00\n10\n00\n01\n11\n00\n10\n
Turning the knob clockwise we see the 01
before the 11 frequently Turning the know counterclockwise:
00\n10\n11\n00\n10\n11\n00\n11\n00\n10\n00\n10\n01\n00\n
Here we see the reverse 10
pattern occur more frequently. But there is noise in the switches as they open and close due to small variations in the contacts as they move.
import time\nfrom machine import Pin\n\nrotaryA = Pin(16, Pin.IN, Pin.PULL_DOWN)\nrotaryB = Pin(17, Pin.IN, Pin.PULL_DOWN)\n\n# we set the old value to zero for both bits being off\nold_combined = 0\nwhile True:\n A_val = rotaryA.value()\n B_val = rotaryB.value()\n # a sifts by one bit and then is ORed with the B calue\n new_combined = (A_val << 1) | B_val\n if new_combined != old_combined:\n #print(A_val, end='')\n #print(B_val)\n old_combined = new_combined\n if A_val == 0 and B_val == 1:\n print('clock')\n elif A_val == 1 and B_val == 0:\n print('counter clock')\n time.sleep(.1)\n
"},{"location":"sensors/10-rotary-encoder/#the-rotary-class","title":"The Rotary Class","text":"Here is one rotary class:
Mike Teachman Rotary Class. This is preferred since it does not call a slow scheduler within an interrupt.
"},{"location":"sensors/10-rotary-encoder/#using-a-scheduler","title":"Using a Scheduler","text":"There is another class Gurgle Apps Rotary Encoder that uses a scheduler within an interrupt which is not a best practice. However, we can show how this does work work with the one she created. The numbers incremented, but they didn't decrement. I had to change the pins to use the PULL_DOWN settings in the init method.
import machine\nimport utime as time\nfrom machine import Pin\nimport micropython\n\nclass Rotary:\n\n ROT_CW = 1\n ROT_CCW = 2\n SW_PRESS = 4\n SW_RELEASE = 8\n\n def __init__(self,dt,clk,sw):\n self.dt_pin = Pin(dt, Pin.IN, Pin.PULL_DOWN)\n self.clk_pin = Pin(clk, Pin.IN, Pin.PULL_DOWN)\n self.sw_pin = Pin(sw, Pin.IN, Pin.PULL_DOWN)\n self.last_status = (self.dt_pin.value() << 1) | self.clk_pin.value()\n self.dt_pin.irq(handler=self.rotary_change, trigger=Pin.IRQ_FALLING | Pin.IRQ_RISING )\n self.clk_pin.irq(handler=self.rotary_change, trigger=Pin.IRQ_FALLING | Pin.IRQ_RISING )\n self.sw_pin.irq(handler=self.switch_detect, trigger=Pin.IRQ_FALLING | Pin.IRQ_RISING )\n self.handlers = []\n self.last_button_status = self.sw_pin.value()\n\n def rotary_change(self, pin):\n new_status = (self.dt_pin.value() << 1) | self.clk_pin.value()\n if new_status == self.last_status:\n return\n transition = (self.last_status << 2) | new_status\n if transition == 0b1110:\n micropython.schedule(self.call_handlers, Rotary.ROT_CW)\n elif transition == 0b1101:\n micropython.schedule(self.call_handlers, Rotary.ROT_CCW)\n self.last_status = new_status\n\n def switch_detect(self,pin):\n if self.last_button_status == self.sw_pin.value():\n return\n self.last_button_status = self.sw_pin.value()\n if self.sw_pin.value():\n micropython.schedule(self.call_handlers, Rotary.SW_RELEASE)\n else:\n micropython.schedule(self.call_handlers, Rotary.SW_PRESS)\n\n def add_handler(self, handler):\n self.handlers.append(handler)\n\n def call_handlers(self, type):\n for handler in self.handlers:\n handler(type)\n
The following were the lines that I changed:
self.dt_pin = Pin(dt, Pin.IN, Pin.PULL_DOWN)\n self.clk_pin = Pin(clk, Pin.IN, Pin.PULL_DOWN)\n self.sw_pin = Pin(sw, Pin.IN, Pin.PULL_DOWN)\n
"},{"location":"sensors/10-rotary-encoder/#testing-script","title":"Testing Script","text":"from rotary import Rotary\nimport utime as time\nfrom machine import Pin\n\n# GPIO Pins 16 and 17 are for the encoder pins. 22 is the button press switch.\nrotary = Rotary(16, 17, 22)\nval = 0\n\ndef rotary_changed(change):\n global val\n if change == Rotary.ROT_CW:\n val = val + 1\n print(val)\n elif change == Rotary.ROT_CCW:\n val = val - 1\n print(val)\n elif change == Rotary.SW_PRESS:\n print('PRESS')\n elif change == Rotary.SW_RELEASE:\n print('RELEASE')\n\nrotary.add_handler(rotary_changed)\n\nwhile True:\n time.sleep(0.1)\n
"},{"location":"sensors/10-rotary-encoder/#adding-plot","title":"Adding Plot","text":"Now I can move the knob back and forth and get consistent values that go up and down. You can turn on the plot function of Thonny to see the values consistently go up and down.
"},{"location":"sensors/10-rotary-encoder/#mysterious-runtime-error-on-scheduling-queue","title":"Mysterious Runtime Error on Scheduling Queue","text":"I did notice that the Shell output did register the following errors:
Traceback (most recent call last):\n File \"rotary.py\", line 30, in rotary_change\nRuntimeError: schedule queue full\n
The error also occurred on line 32.
The following lines generated this error:
micropython.schedule(self.call_handlers, Rotary.ROT_CW)\nmicropython.schedule(self.call_handlers, Rotary.ROT_CCW)\n
This error did not seem to impact the execution of the code. My suspicion is that this is a bug in the Micropython firmware.
As a fix, you can use the try/except block an catch any runtime error. The pass function is a no-op (no operation)
def rotary_change(self, pin):\n new_status = (self.dt_pin.value() << 1) | self.clk_pin.value()\n if new_status == self.last_status:\n return\n transition = (self.last_status << 2) | new_status\n try:\n if transition == 0b1110:\n micropython.schedule(self.call_handlers, Rotary.ROT_CW)\n elif transition == 0b1101:\n micropython.schedule(self.call_handlers, Rotary.ROT_CCW)\n except RuntimeError:\n pass\n self.last_status = new_status\n
"},{"location":"sensors/10-rotary-encoder/#references","title":"References","text":"Digital Compass Lab Video
This lab will use the popular HML5883L chip to show how we can sense a magnetic field and allow our robots to sense the direction they are going.
"},{"location":"sensors/11-compass/#about-the-hml5883l","title":"About the HML5883L","text":"The HMC5883L is a multi-chip module able to measure magnetic sensing. It can communicate with an I2C digital interface with other devices in order to let creating applications such as low-cost compass, being used in many electronic fields like mobile phones, netbooks, consumer electronics, auto navigation systems, and personal navigation devices.
The module works with a low voltage supply (from 2.16 to 3.6V) and also has a low power consumption (100 \u03bcA).
HMC5883L has a 1\u00b0 to 2\u00b0 degrees heading accuracy, which is enough for a wide number of applications. It also allows setting its internal gain to increase resolution with a 3-bit gain control register. But increasing gain also increases noise effects.
"},{"location":"sensors/11-compass/#pinout","title":"Pinout","text":"The HMC5883L pinout has 5 PINs:
You can use a standard i2c-scanner program to verify that the four pins are connected correctly:
import machine\n\n# Change this if you are using other ports\nI2C_SDA_PIN = 0\nI2C_SCL_PIN = 1\ni2c=machine.I2C(0,sda=machine.Pin(I2C_SDA_PIN), scl=machine.Pin(I2C_SCL_PIN), freq=400000)\n\nprint('Scanning I2C bus.')\ndevices = i2c.scan() # this returns a list of devices\n\ndevice_count = len(devices)\n\nif device_count == 0:\n print('No i2c device found.')\nelse:\n print(device_count, 'devices found.')\n\nfor device in devices:\n print('Decimal address:', device, \", Hex address: \", hex(device))\n
For my system this returns the following report:
Scanning I2C bus.\n1 devices found.\nDecimal address: 30 , Hex address: 0x1e\n
You will notice that the scanner found a single device at hex address 0x1e
.
Like many sensors, this sensor uses a \"memory map\" to transmit it's data. You can write a quick test to verify that the values are changing by doing a read from this i2c address like this:
data = array('B', [0] * 6)\nwhile True:\n i2c.readfrom_mem_into(0x1e, 0x03, data)\n
Here is a program that will display the raw values. You can rotate the sensor to verify the raw values are changing.
from array import array\n# create an incomming data array of 6 bytes\nI2C_SDA_PIN = 0\nI2C_SCL_PIN = 1\ni2c=machine.I2C(0,sda=machine.Pin(I2C_SDA_PIN), scl=machine.Pin(I2C_SCL_PIN), freq=400000)\ndata = array('B', [0] * 6)\nwhile True:\n i2c.readfrom_mem_into(0x1e, 0x03, data)\n
Note that these are \"raw\" values because they have not been converted to easy-to-understand values by the driver.
from hmc5883l import HMC5883L\nfrom utime import sleep\n\nI2C_SDA_PIN = 0\nI2C_SCL_PIN = 1\n\n# i2c=machine.I2C(0,sda=machine.Pin(I2C_SDA_PIN), scl=machine.Pin(I2C_SCL_PIN), freq=400000)\n\n# The default address is address=0x1e\n\nsensor = HMC5883L(scl=I2C_SDA_PIN, sda=I2C_SCL_PIN)\n\nwhile True:\n x, y, z = sensor.read()\n # print(sensor.format_result(x, y, z))\n if x <400:\n print(x)\n sleep(.1)\n
"},{"location":"sensors/11-compass/#references","title":"References","text":"The APDS-9960 is an I2C device with advanced Gesture detection, Proximity detection, Digital Ambient Light Sense (ALS) and Color Sense (RGBC). It incorporates an IR LED and factory-calibrated LED driver.
"},{"location":"sensors/13-gesture/#gesture-detection","title":"Gesture detection","text":"Gesture detection utilizes four directional photodiodes to sense reflected IR energy (sourced by the integrated LED) to convert physical motion information (i.e. velocity, direction and distance) to a digital information. The architecture of the gesture engine features automatic activation (based on Proximity engine results), ambient light subtraction, cross-talk cancelation, dual 8-bit data converters, power saving inter-conversion delay, 32-dataset FIFO, and interrupt driven I2C communication. The gesture engine accommodates a wide range of mobile device gesturing requirements: simple UP-DOWN-RIGHT-LEFT gestures or more complex gestures can be accurately sensed.
"},{"location":"sensors/13-gesture/#code","title":"Code","text":"import machine\nfrom time import sleep_ms\nfrom uPy_APDS9960.apds9960LITE import APDS9960LITE\n\n#Init I2C Buss on RP2040\nsda=machine.Pin(12)\nscl=machine.Pin(13)\ni2c = machine.I2C(0,scl=scl, sda=sda)\n\napds9960=APDS9960LITE(i2c) # Enable sensor\napds9960.prox.enableSensor() # Enable Proximit sensing\n\nwhile True:\n sleep_ms(25) # wait for readout to be ready\n print(apds9960.prox.proximityLevel) #Print the proximity value\n
Returns
0 1 1 1 1 1 0 1 1 2 2 1 1 1 1 1 0 \n
"},{"location":"sensors/13-gesture/#ambient-light","title":"Ambient Light","text":"# from https://github.com/rlangoy/uPy_APDS9960\nimport machine\nfrom time import sleep,sleep_ms\nfrom APDS9960LITE import APDS9960LITE\n\n#Init I2C Buss on RP2040\nsda=machine.Pin(12)\nscl=machine.Pin(13)\ni2c=machine.I2C(0, sda=sda, scl=scl, freq=400000)\n\nprint(i2c)\n# create the driver\napds9960=APDS9960LITE(i2c)\napds9960.als.enableSensor() # Enable Light sensor\nsleep_ms(25) # Wait for readout to be ready\n\n#apds9960.powerOn(True)\n#print(apds9960.statusRegister())\n\nwhile True:\n print(apds9960.als.ambientLightLevel, ' ', end='')\n sleep(.1)\n
returns
1 1 0 0 0 0 0 0 0 0 1 1 1 4 8 10 10 10 12 16 17 19 19 20 21 21 22 25 27 30 33 35 36 37 38 33 33 34 34 34 34 34 35 36 36 38 39 40 38 37 38 40 42 45 48 49 55 61 66 73 76 76 82 93 109 132 156 169 171 173 173 171 164 160 169 165 162 162 164 163 159 158 152 148 148 145 141 135 122 106 96 101 108 89 62 48 33 20 10 5 3 1 0 0 0 0 0 0 0 1 2 4 8 14 26 45 65 91 110 121 124 127 132 150 191 225 244 244 254 288 295 243 219 259 299 287 204 99 31 10 2 1 1 1 0\n
"},{"location":"sensors/13-gesture/#reading-rgb","title":"Reading RGB","text":"while True:\n print(apds9960.als.ambientLightLevel,'', end='')\n print(apds9960.als.redLightLevel,'', end='')\n print(apds9960.als.greenLightLevel,'', end='')\n print(apds9960.als.blueLightLevel)\n sleep(.1)\n
1 0 0 0\n2 0 0 1\n4 1 0 1\n8 3 2 2\n17 6 5 5\n28 14 10 8\n38 19 12 11\n37 27 15 12\n38 30 15 12\n43 29 15 14\n35 22 11 11\n26 17 9 7\n18 12 6 5\n13 7 4 3\n9 4 3 2\n
"},{"location":"sensors/13-gesture/#rgb-display-graph","title":"RGB Display Graph","text":"This program displays the red, green and blue light values as bars on an OLED display.
# from https://github.com/rlangoy/uPy_APDS9960\nfrom machine import Pin, I2C\nimport ssd1306\nfrom time import sleep,sleep_ms\nfrom APDS9960LITE import APDS9960LITE\n\n#Init I2C Buss on RP2040\nsda=Pin(12)\nscl=Pin(13)\ni2c=I2C(0, sda=sda, scl=scl, freq=400000)\n\nprint(i2c)\n# create the driver\napds9960=APDS9960LITE(i2c)\napds9960.als.enableSensor() # Enable Light sensor\nsleep_ms(25) # Wait for readout to be ready\n\n# display\nWIDTH = 128\nONE_THIRD_WIDTH = int(WIDTH/3)\nHEIGHT = 64\nclock=Pin(2)\ndata=Pin(3)\nRES = machine.Pin(4)\nDC = machine.Pin(5)\nCS = machine.Pin(6)\nspi=machine.SPI(0, sck=clock, mosi=data)\noled = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)\n\ndef update_display(red, green, blue):\n oled.fill(0)\n # scale red, green and blue to the height\n red = int(red*4)\n green = int(green*4)\n blue = int(blue*3)\n\n oled.fill(0)\n # rect_fill(x, y, width, height)\n oled.fill_rect(0, HEIGHT - red, ONE_THIRD_WIDTH, red, 1)\n oled.fill_rect(ONE_THIRD_WIDTH, HEIGHT - green, ONE_THIRD_WIDTH, green, 1)\n oled.fill_rect(ONE_THIRD_WIDTH * 2, HEIGHT - blue, ONE_THIRD_WIDTH, blue, 1)\n oled.text(str(red), 0, 56, 0)\n oled.text(str(green), ONE_THIRD_WIDTH, 56, 0)\n oled.text(str(blue), ONE_THIRD_WIDTH*2, 56, 0)\n oled.show()\n\nwhile True:\n # print(apds9960.als.ambientLightLevel,'', end='')\n red = apds9960.als.redLightLevel\n green = apds9960.als.greenLightLevel\n blue = apds9960.als.blueLightLevel\n update_display(red, green, blue)\n sleep(.05)\n
"},{"location":"sensors/13-gesture/#references","title":"References","text":""},{"location":"sensors/13-gesture/#using-tinyml","title":"Using TinyML","text":"https://www.hackster.io/mjrobot/tinyml-motion-recognition-using-raspberry-pi-pico-6b6071?f=1
"},{"location":"sensors/13-gesture/#datasheet","title":"Datasheet","text":"https://cdn.sparkfun.com/assets/learn_tutorials/3/2/1/Avago-APDS-9960-datasheet.pdf
"},{"location":"sensors/13-gesture/#other-references","title":"Other References","text":"https://robojax.com/learn/arduino/?vid=robojax-gesture-APDS9960
https://upy-apds9960.readthedocs.io/en/latest/#
https://www.youtube.com/watch?v=wH9HpP9bKwE
https://github.com/rlangoy/uPy_APDS9960/blob/master/uPy_APDS9960/apds9960LITE.py
"},{"location":"sensors/dht11-temp-humidity/","title":"The DHT22 (AM2302) Temperature and Humidity Sensor","text":"The DHT22 is a low-cost ($2) digital temperature and humidity sensor. It uses a capacitive humidity sensor and a thermistor to measure the surrounding air, and transmits a digital signal on the data pin.
These devices are easy to use since they have an internal analog to digital conversion. They are good for 20-80% humidity readings with 5% accuracy and 0-50\u00b0C temperature readings with \u00b12\u00b0C accuracy. They are designed to be polled about once every two seconds.
"},{"location":"sensors/dht11-temp-humidity/#circuit","title":"Circuit","text":""},{"location":"sensors/dht11-temp-humidity/#references","title":"References","text":"DHT22 Datasheet (PDF)
"},{"location":"sims/","title":"Simulators for Learning MicroPython","text":"Knowledge Graph
Wokwi
Breadboard Simulator Over Image - simulator written in P5.js
Vector Breadboard With Zoom
Vector Drawn Breadboard Simulator
"},{"location":"sims/knolwedge-graphs/","title":"Knowledge Graphs","text":"```linenums=\"0\" Raspberry Pi Pico, and solderless breadboards. In our labs, we also use many parts such as photosensors, potentiometers, LEDs, resistors, transistors, time-0f-flight sensors, WS2812B LED strips, 128x64 OLED displays, motors, and the Cytron Maker Pi RP2040, which has the same RP2040 microcontroller.
We have a lot of material and lesson plans right now, but I would like an overall list of the 200 key concepts we need to cover in this course.
For this course, please create a list of the core learning concepts that we need to present in this course and the order in which they should be taught.
Please create a list of the 200 most essential concepts in this course. Return the concept's preferred label in a numbered list for each learning concept. Make sure that the concept names are distinct and not synonyms. Don't repeat
Start with the most basic foundational concepts and then proceed to the most complex concepts. Return just one concept label per line. Avoid using acronyms or abbreviations.
"},{"location":"sims/knolwedge-graphs/concept-enumeration/#response","title":"Response","text":"ChatGPT o1-preview Thought for 22 seconds
I am very pleased with these results. Now, can you suggest a\ntaxonomy to classify each of these learning concepts? Perhaps\nabout 10 different classifications?\n
ChatGPT o1-min Thought for a couple of seconds
I'm glad to hear that you're pleased with the results so far! Creating a taxonomy to classify your 197 learning concepts will help organize the curriculum, ensure comprehensive coverage, and facilitate a logical progression of topics. Below is a suggested taxonomy with 10 classifications, each accompanied by a description and examples of relevant concepts from your list.
"},{"location":"sims/knolwedge-graphs/concept-taxonomy/#1-computational-thinking-foundations","title":"1. Computational Thinking Foundations","text":"Description: Covers the basic principles and strategies of computational thinking, essential for problem-solving and algorithm development.
Relevant Concepts:
Description: Introduces the core concepts of programming that are applicable across various programming languages and environments.
Relevant Concepts:
Description: Focuses on programming using MicroPython, including syntax, environment setup, and MicroPython-specific features.
Relevant Concepts:
Description: Explores the physical components used in projects and how to interface them with the Raspberry Pi Pico.
Relevant Concepts:
Description: Introduces fundamental electronics concepts necessary for building and understanding circuits.
Relevant Concepts:
Description: Covers various sensors, their functionalities, and how to integrate and interpret data from them.
Relevant Concepts:
Description: Focuses on controlling various actuators such as motors, servos, and relays to perform physical actions.
Relevant Concepts:
Description: Details various communication protocols used for data transmission between devices and components.
Relevant Concepts:
Description: Explores IoT concepts, connectivity, data transmission, and integration with cloud services.
Relevant Concepts:
Description: Includes advanced programming techniques, best practices, version control, and emerging technologies.
Relevant Concepts:
Cross-Cutting Themes: Some concepts may span multiple categories. For example, \"Pulse Width Modulation\" could relate to both \"Electronics Basics\" and \"Actuator Control.\"
Prerequisites: Ensure that foundational categories like \"Computational Thinking Foundations\" and \"Programming Fundamentals\" are taught first, as they underpin many other topics.
Modular Organization: Consider organizing your curriculum into modules based on these classifications to provide a structured learning path.
Scalability: This taxonomy allows for the addition of new concepts or subcategories as the course evolves or as new technologies emerge.
For better visualization, here's a hierarchical representation of the taxonomy:
Here's how some of the concepts map to the suggested taxonomy:
Concept ID Concept Name Classification 1 Intro to Computational Thinking Computational Thinking Foundations --- --- --- 7 Variables in Programming Programming Fundamentals 14 Introduction to MicroPython MicroPython Programming 26 Microcontroller Basics Hardware Components and Interfacing 31 Circuit Basics Electronics Basics 99 Introduction to Sensors Sensor Integration 107 Introduction to Actuators Actuator Control 122 Serial Communication Protocols Communication Protocols 128 Internet of Things Concepts Internet of Things (IoT) 136 Programming Best Practices Advanced Topics and Best Practices"},{"location":"sims/knolwedge-graphs/concept-taxonomy/#final-remarks","title":"Final Remarks","text":"This taxonomy provides a structured framework to classify and organize your 197 learning concepts effectively. It ensures that foundational knowledge is established before progressing to more advanced topics, facilitating a coherent and comprehensive learning experience for high school students. Feel free to adjust the classifications or add subcategories to better fit the specific needs and goals of your course.
o1-mini
requestAnimationFrame((function(){window.__oai_logTTI?window.__oai_logTTI():window.__oai_SSR_TTI=window.__oai_SSR_TTI??Date.now()}))
"},{"location":"sims/knolwedge-graphs/concpt-dependency/","title":"Concept Dependency","text":""},{"location":"sims/knolwedge-graphs/concpt-dependency/#prompt","title":"Prompt","text":"Our next step is to create a fully connected dependency graph.\nIn this step, for each line or concept on this list, \nplease create a new list of other concepts on which it depends.\nIf a concept depends on a specific concept that is not listed,\nthen we must add it as a new concept.\nThis will help us determine the teaching order of the concepts.\n\nReturn the new list as a CSV file using the following format:\n\nThe first column is a numeric concept ID you assigned, \nthe second column is the concept name,\nand the third column is a pipe-delimited list of the concept IDs\nthat the concept depends upon.\n\nCheck your results to make sure that all concepts are\nconnected to at least one other concept.\n
"},{"location":"sims/knolwedge-graphs/concpt-dependency/#response","title":"Response","text":"ChatGPT o1-mini Thought for 53 seconds
Here is the CSV file containing the concepts along with their dependencies:
ID,Concept Name,Dependencies (IDs)\n1,Intro to Computational Thinking,\n2,Problem Decomposition,1\n3,Pattern Recognition,1\n4,Abstraction,1\n5,Algorithm Design,1|2|3|4\n6,Basic Programming Concepts,1|5\n7,Variables in Programming,6\n8,Data Types in Programming,6|7\n9,Operators,6|7|8\n10,Control Structures,6|7|8|9\n11,Conditional Statements,10|9\n12,Loops,10\n13,General Functions in Programming,6|7|8|10\n14,Introduction to MicroPython,6\n15,Setting Up MicroPython Environment,14\n16,Syntax in MicroPython,14|15\n17,Functions in MicroPython,13|16\n18,Modules in MicroPython,16|17\n19,Input and Output,16|7|8\n20,Working with Strings,8|16\n21,Working with Numbers,8|16\n22,Lists,8|16\n23,Tuples,8|16\n24,Dictionaries,8|16\n25,Exception Handling,16|10\n26,Microcontroller Basics,\n27,The Raspberry Pi Pico,26\n28,Understanding GPIO Pins,26|27\n29,Solderless Breadboards,32\n30,Breadboard Usage,29|32\n31,Circuit Basics,\n32,Ohm's Law,31\n33,Resistors in Circuits,31|32\n34,LEDs in Circuits,31\n35,Using Resistors with LEDs,33|34|32\n36,Current Limiting Resistors,33|35\n37,Connecting LEDs,34|30\n38,Blinking an LED with MicroPython,37|16|28\n39,Digital Output Signals,28|16\n40,Digital Input Signals,28|16\n41,Reading Button Inputs,40|30\n42,Debouncing Switches,41|10\n43,Pull-up and Pull-down Resistors,33|41\n44,Controlling Multiple LEDs,37|38|16\n45,Using Breadboard Jumpers,30\n46,Potentiometers in Circuits,31|33\n47,Reading Analog Inputs,46|28\n48,Analog-to-Digital Conversion,47\n49,Using ADC in MicroPython,48|16\n50,Reading Potentiometer Values,49|46\n51,Pulse Width Modulation Fundamentals,6|10\n52,Implementing Pulse Width Modulation in MicroPython,51|16\n53,LED Brightness Control with Pulse Width Modulation,52|37\n54,Fading LEDs Using Pulse Width Modulation,53|12\n55,Advanced Pulse Width Modulation Techniques,54|52\n56,Servo Motors Overview,31\n57,Controlling Servo Motors,52|28|56\n58,DC Motors Overview,31\n59,Transistors as Switches,31|33\n60,Controlling Motors with Transistors,59|58\n61,Transistor Biasing Resistors,59|33\n62,Power Management with Transistors,59|61\n63,Photocells,31|33\n64,Making a Night Light,63|28|16\n65,Voltage Dividers,33|31\n66,Voltage Dividers and Sensors,65|63\n67,Time-of-Flight Sensors,31|28\n68,Interfacing Time-of-Flight Sensors,67|16\n69,I2C Protocol,70|16\n70,SPI Protocol,28|16\n71,Connecting I2C Devices,69|28\n72,LED Displays Overview,34|31\n73,Organic Light-Emitting Diode Displays,72\n74,Display Drawing,73|16\n75,Drawing Circles and Ellipses,74\n76,Drawing Polygons,74\n77,Changing Fonts,74\n78,Advanced I2C,69|16\n79,Advanced SPI,70|16\n80,WS2812B LED Strips,34|31\n81,LED Strip Code,80|16\n82,Moving Pixels on LED Strips,81|12\n83,LED Strip Patterns,81|82\n84,LED Strip Button Demos,83|41\n85,Sensors and Actuators,31|28\n86,Integrating Multiple Sensors,85|16\n87,Event-Driven Programming,6|10\n88,Interrupts,28|87\n89,Using Interrupts with GPIO Pins,88|28\n90,Debouncing with Interrupts,89|42\n91,Timers,6|16\n92,Delays and Timing Functions,91|16\n93,Using the Time Module,92|16\n94,Data Logging with MicroPython,16|97\n95,Storing Data on Raspberry Pi Pico,94|27\n96,File Handling,16|95\n97,Universal Serial Bus Communication,27|16\n98,Universal Asynchronous Receiver-Transmitter Communication,27|16\n99,Introduction to Sensors,31\n100,Temperature Sensors,99|28\n101,Interfacing Temperature Sensors,100|16\n102,Humidity Sensors,99|28\n103,Using DHT11 and DHT22 Sensors,101|102\n104,Reading Sensor Data,103|16\n105,Interpreting Sensor Data,104\n106,Calibrating Sensors,105\n107,Introduction to Actuators,31\n108,Controlling Relays,107|16\n109,Relays Overview,107\n110,Relays Safety,109\n111,Stepper Motors,31\n112,Controlling Stepper Motors,111|16\n113,Using Stepper Motor Drivers,112|28\n114,Generating Sound with Buzzers,31|28\n115,Using Piezoelectric Buzzers,114|16\n116,Generating Tones and Melodies,115|12\n117,Infrared Communication,28|16\n118,Using Infrared Sensors,117|31\n119,Infrared Remote Control,118|16\n120,Ultrasonic Sensors,99|28\n121,Serial Communication Protocols,27|16\n122,I2C vs SPI vs UART,69|70|98\n123,Data Communication Principles,121\n124,Wireless Communication Basics,123\n125,Bluetooth Low Energy,124|27\n126,Wi-Fi Connectivity,124|27\n127,Python Modules Overview,16\n128,Internet of Things Concepts,124|27\n129,Connecting to the Internet,126|16\n130,Sending Data to Web Servers,129|16\n131,Hypertext Transfer Protocol Basics,130\n132,Message Queuing Telemetry Transport Protocol,130\n133,IoT Applications,128|130\n134,Cloud Services Integration,133\n135,Data Visualization Tools,134\n136,Programming Best Practices,6\n137,Code Optimization Techniques,136\n138,Debugging Techniques,16|136\n139,Using Debugging Tools,138\n140,Version Control Systems,6\n141,Using Git for Code Management,140\n142,Collaborative Coding Practices,141\n143,Documentation Standards,136\n144,Commenting Code Effectively,136\n145,Advanced MicroPython Features,16\n146,Object-Oriented Programming,6|136\n147,Classes and Objects,146\n148,Inheritance and Polymorphism,147\n149,Encapsulation in Object-Oriented Programming,147\n150,Designing Reusable Code,146|149\n151,Memory Management,16|136\n152,Managing Resources Efficiently,151\n153,Energy Efficiency in Programming,152\n154,Sleep Modes in Microcontrollers,153|27\n155,Battery-Powered Projects,154\n156,Power Management Techniques,155\n157,Advanced Electronics Concepts,31\n158,Capacitors in Circuits,157\n159,Inductors and Coils,157\n160,Diodes and Rectification,157\n161,Voltage Regulation,160\n162,Using Voltage Regulators,161\n163,Building Power Supplies,162\n164,Analog Signals vs Digital Signals,31|157\n165,Signal Conditioning,164|157\n166,Filtering Noise in Circuits,165\n167,Interference and Shielding,166\n168,Electromagnetic Compatibility,167\n169,Circuit Design Principles,157\n170,Using Simulation Tools,169\n171,Prototyping Techniques,169\n172,Schematic Diagram Reading,169\n173,Understanding Datasheets,169\n174,Component Specifications,173\n175,Safety in Electronics,157\n176,Electrostatic Discharge Precautions,175\n177,Soldering Techniques,175\n178,Printed Circuit Board Design,169\n179,Using PCB Design Software,178\n180,Fabricating Custom PCBs,179\n181,Advanced Sensor Integration,85|157\n182,Gesture Recognition Sensors,181|28\n183,Touch Sensors,181|28\n184,Environmental Monitoring,181|105\n185,Robotics Basics,31|157\n186,Building Simple Robots,185|16\n187,Line-Following Robots,186|83\n188,Obstacle Avoidance Robots,186|120\n189,Control Systems Basics,185\n190,Feedback Mechanisms,189\n191,Proportional-Integral-Derivative Controllers,190\n192,Machine Learning Concepts,6|136\n193,Using MicroPython for Artificial Intelligence,192|16\n194,Edge Computing,193|128\n195,Ethics in Technology,6\n196,Future Trends in Computing,195\n
Notes:
|
) as per your instructions.Concept 1: Intro to Computational Thinking
Concept 26: Microcontroller Basics
Dependencies: None (introduces the hardware aspect).
Concept 27: The Raspberry Pi Pico
Dependencies: Depends on Microcontroller Basics (26) for understanding microcontrollers.
Concept 38: Blinking an LED with MicroPython
Dependencies: Connecting LEDs (37), Syntax in MicroPython (16), Understanding GPIO Pins (28).
Concept 57: Controlling Servo Motors
Dependencies: Implementing PWM in MicroPython (52), Understanding GPIO Pins (28), Servo Motors Overview (56).
Concept 95: Storing Data on Raspberry Pi Pico
Dependencies: Data Logging with MicroPython (94), The Raspberry Pi Pico (27).
Concept 128: Internet of Things Concepts
Dependencies: Wireless Communication Basics (124), The Raspberry Pi Pico (27).
Concept 193: Using MicroPython for Artificial Intelligence
Dependencies: Machine Learning Concepts (192), Syntax in MicroPython (16).
HTML Page for Dependency Graph
"},{"location":"sims/knolwedge-graphs/graph/#adjusting-visualization","title":"Adjusting Visualization","text":"In the JavaScript file you can change the display options. Here is the portion of the JavaScript that changes the options.
var options = {\n nodes: {\n shape: 'dot',\n size: 10,\n font: {\n size: 14,\n },\n },\n edges: {\n arrows: 'to',\n smooth: true,\n },\n physics: {\n stabilization: false,\n },\n};\n
"},{"location":"sims/knolwedge-graphs/graph/#left-to-right-layout","title":"Left to Right Layout","text":"HTML Page for Dependency Graph
var options = {\nlayout: {\n hierarchical: {\n direction: 'LR', // Left to right\n sortMethod: 'directed', // Sort nodes based on dependencies\n nodeSpacing: 200, // Adjust spacing if needed\n levelSeparation: 150 // Adjust for horizontal space between levels\n }\n }\n}\n
"},{"location":"sims/knolwedge-graphs/graph/#full-layout-options","title":"Full Layout Options","text":"layout: {\n randomSeed: undefined,\n improvedLayout:true,\n clusterThreshold: 150,\n hierarchical: {\n enabled:false,\n levelSeparation: 150,\n nodeSpacing: 100,\n treeSpacing: 200,\n blockShifting: true,\n edgeMinimization: true,\n parentCentralization: true,\n direction: 'LR', // UD, DU, LR, RL\n sortMethod: 'hubsize', // hubsize, directed\n shakeTowards: 'leaves' // roots, leaves\n }\n }\n ```\n\n## Hierarchical Layout User Defined\nhttps://visjs.github.io/vis-network/examples/network/layout/hierarchicalLayoutUserdefined.html\n\n```js\n var options = {\n edges: {\n smooth: {\n type: \"cubicBezier\",\n forceDirection:\n directionInput.value == \"UD\" || directionInput.value == \"DU\"\n ? \"vertical\"\n : \"horizontal\",\n roundness: 0.4,\n },\n },\n layout: {\n hierarchical: {\n direction: directionInput.value,\n },\n },\n physics: false,\n };\n
"},{"location":"sims/knolwedge-graphs/graph/#category-colors","title":"Category Colors","text":"We can use generative AI to categorize each concept. Here are 11 categories of the concepts
CategoryID Color CategoryLabel 1 red Fundamental Concepts 2 orange Passive Components 3 yellow Active Components and Semiconductor Devices 4 green Circuit Analysis Techniques 5 cyan AC Circuit Concepts 6 blue Transient Analysis 7 purple Signal Processing and Filters 8 pink Amplifiers and Analog Circuits 9 gray Power Electronics 10 olive Control Systems and Stability 11 brown Types of CircuitsCategory Colors Demo
"},{"location":"sims/knolwedge-graphs/graph/#reference","title":"Reference","text":"Vis.js Network Layout Methods
"},{"location":"sims/knolwedge-graphs/graph/concept-taxonomy/","title":"Concept Taxonomy","text":"Run the Category Colors MicroSim
"},{"location":"sims/knolwedge-graphs/graph/concept-taxonomy/#prompt","title":"Prompt","text":"I'm glad to hear that you're pleased with the results so far! Creating a taxonomy to classify your 197 learning concepts will help organize the curriculum, ensure comprehensive coverage, and facilitate a logical progression of topics. Below is a suggested taxonomy with **10 classifications**, each accompanied by a description and examples of relevant concepts from your list.\n
"},{"location":"sims/knolwedge-graphs/graph/concept-taxonomy/#1-computational-thinking-foundations","title":"1. Computational Thinking Foundations","text":"Description: Covers the basic principles and strategies of computational thinking, essential for problem-solving and algorithm development.
Relevant Concepts:
Description: Introduces the core concepts of programming that are applicable across various programming languages and environments.
Relevant Concepts:
Description: Focuses on programming using MicroPython, including syntax, environment setup, and MicroPython-specific features.
Relevant Concepts:
Description: Explores the physical components used in projects and how to interface them with the Raspberry Pi Pico.
Relevant Concepts:
Description: Introduces fundamental electronics concepts necessary for building and understanding circuits.
Relevant Concepts:
Description: Covers various sensors, their functionalities, and how to integrate and interpret data from them.
Relevant Concepts:
Description: Focuses on controlling various actuators such as motors, servos, and relays to perform physical actions.
Relevant Concepts:
Description: Details various communication protocols used for data transmission between devices and components.
Relevant Concepts:
Description: Explores IoT concepts, connectivity, data transmission, and integration with cloud services.
Relevant Concepts:
Description: Includes advanced programming techniques, best practices, version control, and emerging technologies.
Relevant Concepts:
Cross-Cutting Themes: Some concepts may span multiple categories. For example, \"Pulse Width Modulation\" could relate to both \"Electronics Basics\" and \"Actuator Control.\"
Prerequisites: Ensure that foundational categories like \"Computational Thinking Foundations\" and \"Programming Fundamentals\" are taught first, as they underpin many other topics.
Modular Organization: Consider organizing your curriculum into modules based on these classifications to provide a structured learning path.
Scalability: This taxonomy allows for the addition of new concepts or subcategories as the course evolves or as new technologies emerge.
For better visualization, here's a hierarchical representation of the taxonomy:
Here's how some of the concepts map to the suggested taxonomy:
Concept ID Concept Name Classification 1 Intro to Computational Thinking Computational Thinking Foundations --- --- --- 7 Variables in Programming Programming Fundamentals 14 Introduction to MicroPython MicroPython Programming 26 Microcontroller Basics Hardware Components and Interfacing 31 Circuit Basics Electronics Basics 99 Introduction to Sensors Sensor Integration 107 Introduction to Actuators Actuator Control 122 Serial Communication Protocols Communication Protocols 128 Internet of Things Concepts Internet of Things (IoT) 136 Programming Best Practices Advanced Topics and Best Practices"},{"location":"sims/knolwedge-graphs/graph/concept-taxonomy/#final-remarks","title":"Final Remarks","text":"This taxonomy provides a structured framework to classify and organize your 197 learning concepts effectively. It ensures that foundational knowledge is established before progressing to more advanced topics, facilitating a coherent and comprehensive learning experience for high school students. Feel free to adjust the classifications or add subcategories to better fit the specific needs and goals of your course.
o1-mini
"},{"location":"sims/knolwedge-graphs/graph/concept-taxonomy/#you-said","title":"You said:","text":"I love this taxonomy. For each of the ten classifiers, use the number as a group ID. Next, regenerate the csv file, but add one more column. That column is the group ID for that learning concept.
"},{"location":"sims/knolwedge-graphs/graph/concept-taxonomy/#notes-on-classification","title":"Notes on Classification","text":"Group 1: Computational Thinking Foundations
Group 2: Programming Fundamentals
Concepts 6-8, 12-13, 127
Group 3: MicroPython Programming
Concepts 14-19, 20-25, 38-42, 49-57, 61-65, 145-149
Group 4: Hardware Components and Interfacing
Concepts 26-28, 30-37, 39-44, 72-84, 85-89, 95-113, 185-188
Group 5: Electronics Basics
Concepts 31-36, 43-48, 59-61, 65-67, 157-177
Group 6: Sensor Integration
Concepts 47-50, 63-68, 99-106, 120-124, 181-184, 182-183
Group 7: Actuator Control
Concepts 51-57, 60-62, 112-116
Group 8: Communication Protocols
Concepts 69-70, 71-73, 78-80, 88-90, 117-119, 121-123
Group 9: Internet of Things (IoT)
Concepts 125-135, 128-134
Group 10: Advanced Topics and Best Practices
Concepts 136-156, 189-194, 195-196
Overlap: Some concepts naturally span multiple groups. For simplicity, each concept is assigned to the most relevant single group.
Dependencies: Ensure that prerequisite concepts (as per the dependencies) are taught before dependent concepts to maintain a coherent learning progression.
Modular Structure: Organizing the course into these groups can help structure modules or units, allowing for focused learning and easier curriculum management.
This taxonomy and classification will aid in structuring your course effectively, ensuring that foundational knowledge is established before advancing to more complex topics. It also facilitates identifying any gaps in the curriculum and ensuring a balanced coverage of computational thinking, programming, hardware interfacing, and advanced concepts.
"},{"location":"sound/","title":"Introduction to Sound and Music in MicroPython","text":""},{"location":"sound/#how-microcontrollers-generate-sound","title":"How Microcontrollers Generate Sound","text":"Microcontrollers are really great at generating digital outputs on their GPIO pins. These digital signals that quickly switch between zero and a positive voltage like 3.3 or 5 volts. However, they are not designed to create \"analog\" output of a continuous varying voltage. However, we can use a technique called \"Pulse Width Modulation\" to simulate the various frequencies of sound using digital only outputs.
Pulse Width Modulation is the process of changing not the height of a electrical signal, but the width between the pulses of digital signals. By changing the distance of the spaces between the digital signals we can generate a signal that will sound like it has a higher or lower frequency or pitch.
MicroPython provides a powerful library of tools for you to easily generate pulses of different shapes. This is called the PWM library. Will will use this in our sound and music programs. Here is a sample of how this is called in our code:
"},{"location":"sound/#duty-cycle","title":"Duty Cycle","text":"The Duty Cycle is what percent of time a pulse is high.
For working with sound, we want to generate smooth sound waves that are on 1/2 of the time and off 1/2 of the time. So our duty cycles will be set to be 50%. On the Raspberry Pi Pico we can achieve this by the following function:
speaker.duty_u16(1000)\n
When we are done playing a tone, we must always explicitly turn the duty cycle back to 0.
speaker.duty_u16(0)\n
If we forget to add this line, the tone will continue to play despite the main program stopping. This shows you that the part of the chip that generates the tone pulses is an independent processor that is not dependant on the main program running!
from machine import Pin, PWM\nfrom utime import sleep\n
Note that we will also need to pause between notes, so will use the sleep library to pause execution of our sound generation.
"},{"location":"sound/#connecting-a-sound-device","title":"Connecting a Sound Device","text":"There are several different ways that you can connect a sound device to you MicroController. Here are three options:
"},{"location":"sound/#references","title":"References","text":"
1, Juke Phone in C - convert an old land-line telephone to an MP3-player Jukebox. Although this is written in C, it could be converted into MicroPython.
"},{"location":"sound/02-play-tone/","title":"Play Tones Using the PWM","text":"Since we will be using the sleep function many times, we will import it by name from the Micropthon time library like this:
from utime import sleep\n
Now, instead of putting utime.sleep(.5)
we can just reference sleep directly like this:
sleep(.5)\n
This will pause for 1/2 a second. This is how long we wait for a tone to stay on or go off. The nice thing about this menthod is that our code is a little smaller. However, you can't run other functions in the utime library. So if you want to add them later you will need to import them also, just like we did for the sleep function.
"},{"location":"sound/02-play-tone/#lab-1-play-a-single-tone","title":"Lab 1: Play A Single Tone","text":"from machine import Pin, PWM\nfrom utime import sleep\n\n# lower right corner with USB connector on top\nSPEAKER_PIN = 16\n\n# create a Pulse Width Modulation Object on this pin\nspeaker = PWM(Pin(SPEAKER_PIN))\n# set the duty cycle to be 50%\nspeaker.duty_u16(1000)\nspeaker.freq(1000) # 50% on and off\nsleep(1) # wait a second\nspeaker.duty_u16(0)\n# turn off the PWM circuits off with a zero duty cycle\nspeaker.duty_u16(0)\n
Note
The tone will keep sounding until you turn the speaker duty to 0. This shows you that the circuitry that is generating the sound is independent of the main CPU.
"},{"location":"sound/02-play-tone/#experiments","title":"Experiments","text":"speaker.freq(1000)
and rerunning the program. Try using values from 10 to 10000. What values seem the loudest to your ear?In this lesson we will play three consecutive tones. Each tone will have a specific time on and we will put a time between the tones.
from machine import Pin, PWM\nfrom utime import sleep\n\n# lower right corner with USB connector on top\nSPEAKER_PIN = 16\n\n# create a Pulse Width Modulation Object on this pin\nspeaker = PWM(Pin(SPEAKER_PIN))\n\nspeaker.duty_u16(1000)\nspeaker.freq(300) # 1 Kilohertz\nsleep(.5) # wait a 1/4 second\nspeaker.duty_u16(0)\nsleep(.25)\n\nspeaker.duty_u16(1000)\nspeaker.freq(800)\nsleep(.5)\nspeaker.duty_u16(0)\nsleep(.25)\n\nspeaker.duty_u16(1000)\nspeaker.freq(400)\nsleep(.5)\n\n# turn off the PWM \nspeaker.duty_u16(0)\n
"},{"location":"sound/03-play-three-tones/#using-variables","title":"Using Variables","text":"We can also put the time each tone stays on and the space between the tones into variables so it is easier to modify the values in a single place.
# set the time each tone will be on\nONTIME = .5\n# the time between the tones\nOFFTIME = .100\n
"},{"location":"sound/03-play-three-tones/#three-tones-with-variables","title":"Three Tones With Variables","text":"from machine import Pin, PWM\nfrom utime import sleep\n\n# lower right corner with USB connector on top\nSPEAKER_PIN = 16\n\n# create a Pulse Width Modulation Object on this pin\nspeaker = PWM(Pin(SPEAKER_PIN))\n\n# the time each tone will be on\nON_TIME = .25\n# the time between the tones\nOFF_TIME = .1\n\n# Low tone\nspeaker.duty_u16(1000)\nspeaker.freq(300)\nsleep(ON_TIME)\nspeaker.duty_u16(0)\nsleep(OFF_TIME)\n\n# High tone\nspeaker.duty_u16(1000)\nspeaker.freq(800)\nsleep(ON_TIME)\nspeaker.duty_u16(0)\nsleep(OFF_TIME)\n\n# Medium tone\nspeaker.duty_u16(1000)\nspeaker.freq(400)\nsleep(ON_TIME)\n\n# turn off the PWM \nspeaker.duty_u16(0)\n
"},{"location":"sound/03-play-three-tones/#experiments","title":"Experiments","text":"In this lesson, we will learn about how to automatically generate various pitches. We will see that the spacing between lower pitches is different from the spacing between higher pitches.
Here is a program that plays a scale of notes from a starting frequency of 30 hertz to an upper frequency of around 10,000 hertz. Note that
from machine import Pin, PWM\nfrom utime import sleep\n\n# lower right corner with USB connector on top\nSPEAKER_PIN = 16\n\n# create a Pulse Width Modulation Object on this pin\nspeaker = PWM(Pin(SPEAKER_PIN))\n\ndef playtone(frequency):\n speaker.duty_u16(1000)\n speaker.freq(frequency)\n sleep(0.3)\n\ndef bequiet():\n speaker.duty_u16(0)\n\nfreq = 30\n\nfor i in range(64):\n print(freq)\n playtone(freq)\n freq = int(freq * 1.1)\n\n# Turn off the PWM\nspeaker.duty_u16(0)\n
"},{"location":"sound/04-play-scale/#new-frequency-spacing","title":"New Frequency Spacing","text":"When you run the prior example, note the frequencies printed to the console. Are they evenly spaced?
Take a close look at the line that creates a new frequency:
freq = int(freq * 1.1)\n
The effect of this line is to create a new frequency that is 10% higher than the prior frequency.
"},{"location":"sound/04-play-scale/#experiments","title":"Experiments","text":"freq = freq +100
This program will play the theme music from the Mario video game.
from machine import Pin, PWM\nfrom utime import sleep\nbuzzer = PWM(Pin(16))\n\ntones = {\n\"B0\": 31,\"C1\": 33,\"CS1\": 35,\"D1\": 37,\"DS1\": 39,\"E1\": 41,\"F1\": 44,\"FS1\": 46,\n\"G1\": 49,\"GS1\": 52,\"A1\": 55,\"AS1\": 58,\"B1\": 62,\"C2\": 65,\n\"CS2\": 69,\"D2\": 73,\"DS2\": 78,\"E2\": 82,\"F2\": 87,\"FS2\": 93,\"G2\": 98,\n\"GS2\": 104,\"A2\": 110,\"AS2\": 117,\"B2\": 123,\"C3\": 131,\"CS3\": 139,\n\"D3\": 147,\"DS3\": 156,\"E3\": 165,\"F3\": 175,\"FS3\": 185,\n\"G3\": 196,\"GS3\": 208,\"A3\": 220,\"AS3\": 233,\"B3\": 247,\"C4\": 262,\"CS4\": 277,\"D4\": 294,\"DS4\": 311,\n\"E4\": 330,\"F4\": 349,\"FS4\": 370,\"G4\": 392,\"GS4\": 415,\"A4\": 440,\"AS4\": 466,\"B4\": 494,\"C5\": 523,\"CS5\": 554,\"D5\": 587,\"DS5\": 622,\"E5\": 659,\"F5\": 698,\n\"FS5\": 740,\"G5\": 784,\"GS5\": 831,\"A5\": 880,\"AS5\": 932,\"B5\": 988,\"C6\": 1047,\"CS6\": 1109,\"D6\": 1175,\"DS6\": 1245,\"E6\": 1319,\"F6\": 1397,\"FS6\": 1480,\"G6\": 1568,\"GS6\": 1661,\n\"A6\": 1760,\"AS6\": 1865,\"B6\": 1976,\"C7\": 2093,\"CS7\": 2217,\"D7\": 2349,\"DS7\": 2489,\"E7\": 2637,\"F7\": 2794,\"FS7\": 2960,\"G7\": 3136,\"GS7\": 3322,\"A7\": 3520,\n\"AS7\": 3729,\"B7\": 3951,\"C8\": 4186,\"CS8\": 4435,\"D8\": 4699,\"DS8\": 4978\n}\n\nsong = [\"E5\",\"G5\",\"A5\",\"P\",\"E5\",\"G5\",\"B5\",\"A5\",\"P\",\"E5\",\"G5\",\"A5\",\"P\",\"G5\",\"E5\"]\nmario = [\"E7\", \"E7\", 0, \"E7\", 0, \"C7\", \"E7\", 0, \"G7\", 0, 0, 0, \"G6\", 0, 0, 0, \"C7\", 0, 0, \"G6\",\n 0, 0, \"E6\", 0, 0, \"A6\", 0, \"B6\", 0, \"AS6\", \"A6\", 0, \"G6\", \"E7\", 0, \"G7\", \"A7\", 0, \"F7\", \"G7\",\n 0, \"E7\", 0,\"C7\", \"D7\", \"B6\", 0, 0, \"C7\", 0, 0, \"G6\", 0, 0, \"E6\", 0, 0, \"A6\", 0, \"B6\", 0,\n \"AS6\", \"A6\", 0, \"G6\", \"E7\", 0, \"G7\", \"A7\", 0, \"F7\", \"G7\", 0, \"E7\", 0,\"C7\", \"D7\", \"B6\", 0, 0]\n\ndef playtone(frequency):\n buzzer.duty_u16(1000)\n buzzer.freq(frequency)\n\ndef bequiet():\n buzzer.duty_u16(0)\n\ndef playsong(mysong):\n for i in range(len(mysong)):\n if (mysong[i] == \"P\" or mysong[i] == 0 ):\n bequiet()\n else:\n playtone(tones[mysong[i]])\n sleep(0.3)\n bequiet()\nplaysong(mario)\n
"},{"location":"sound/06-eight-key-piano/","title":"Eight Key Piano","text":"In this lab we wire up eight momentary press buttons so that when each one is pressed it will play a different note.
To create this project, you will need the following parts:
Each \"key\" is a momentary press button that is wired up between a GPIO pin and the +3.3 volt rail on the breadboard connected to 3V3(OUT) pin. We do not need to use any resistors to pull the signals low since we configure the pins to be inputs with the PULL_DOWN resistor like this:
button_pin_1 = machine.Pin(10, machine.Pin.IN, machine.Pin.PULL_DOWN)\n
"},{"location":"sound/06-eight-key-piano/#the-play-tone-functions","title":"The Play Tone Functions","text":"We will use two Python functions, one for playing a tone of a given frequency and one for turning off the sound.
def playtone(frequency):\n speaker.duty_u16(1000) # turn the PWM duty to 50%\n speaker.freq(frequency)\n builtin_led.high() # turn builtin LED on\n\ndef bequiet():\n speaker.duty_u16(0) # turn off the speaker PWM\n builtin_led.low() # turn builtin LED off\n
We will also use the .value()
method on each pin to detect if it is HIGH (1) like this:
if button_pin_1.value() == 1:\n playtone(220) # A3\n
We will be playing \"notes\" generating square waves with various frequencies from our lowest note of A3 at 220 Hz up to A4 at 440 Hz.
"},{"location":"sound/06-eight-key-piano/#sample-code","title":"Sample Code","text":"# play a tone durning button down\nfrom machine import Pin, PWM\nfrom utime import sleep, ticks_ms\n\nSPEAKER_PIN = 22 # pass through a speaker and tie the other end to GND\nspeaker = PWM(Pin(SPEAKER_PIN))\n\nbuiltin_led = machine.Pin(25, Pin.OUT)\n\n# Connect these GP pins through a button to the +3.3 volt rail\nbutton_pin_1 = machine.Pin(10, machine.Pin.IN, machine.Pin.PULL_DOWN)\nbutton_pin_2 = machine.Pin(11, machine.Pin.IN, machine.Pin.PULL_DOWN)\nbutton_pin_3 = machine.Pin(12, machine.Pin.IN, machine.Pin.PULL_DOWN)\nbutton_pin_4 = machine.Pin(13, machine.Pin.IN, machine.Pin.PULL_DOWN)\nbutton_pin_5 = machine.Pin(14, machine.Pin.IN, machine.Pin.PULL_DOWN)\nbutton_pin_6 = machine.Pin(15, machine.Pin.IN, machine.Pin.PULL_DOWN)\nbutton_pin_7 = machine.Pin(16, machine.Pin.IN, machine.Pin.PULL_DOWN)\nbutton_pin_8 = machine.Pin(17, machine.Pin.IN, machine.Pin.PULL_DOWN)\n\ndef playtone(frequency):\n speaker.duty_u16(1000) # turn the PWM duty to 50%\n speaker.freq(frequency)\n builtin_led.high() # turn builtin LED on\n\ndef bequiet():\n speaker.duty_u16(0) # turn off the speaker PWM\n builtin_led.low() # turn builtin LED off\n\nwhile True:\n if button_pin_1.value() == 1:\n playtone(220) # A3\n elif button_pin_2.value() == 1:\n playtone(247) # B3\n elif button_pin_3.value() == 1:\n playtone(262) # C4\n elif button_pin_4.value() == 1:\n playtone(294) # D4\n elif button_pin_5.value() == 1:\n playtone(330) # E4\n elif button_pin_6.value() == 1:\n playtone(349) # F4\n elif button_pin_7.value() == 1:\n playtone(392) # G4\n elif button_pin_8.value() == 1:\n playtone(440) # A4\n else:\n bequiet()\n
"},{"location":"sound/06-eight-key-piano/#exercises","title":"Exercises","text":"Note
This lesson is still a work in progress. The wavePlayer is in an early draft form and has unpredictable interactions with other components. See the wav file test results section below. We also are having problems when different GPIO pins are used. If you stick to pins for GPIO 2 and 3 for the two channels the test run OK.
"},{"location":"sound/07-play-audio-file/#playing-sounds-on-the-rp2040-chip","title":"Playing Sounds on The RP2040 Chip","text":"Although we can play tones of various pitches on the PR2040 using the PMW to generate square waves, the quality of this sound is not close to high-fidelity sound like you would expect in a personal MP3 audio player.
In this lesson we will demonstrate how to play a high-quality audio files that are stored on the two megabytes of non-volatile static memory of the Pico. According to the specification of the Raspberry Pi Pico, the system comes with 2MB on-board QSPI Flash that we can use to store sound files. By combining our Pico with an SD card reader we can also play many sounds and even full-length music and full albums.
"},{"location":"sound/07-play-audio-file/#background-on-audio-encoding","title":"Background on Audio Encoding","text":"Audio files are encoded in many formats. For this lab there are two key concepts to understand:
For our labs, we will be using mono files (not stereo) a sampling rate of 8,000 samples per second (8K Hz) and a sampling rate of 16-bit depth. This is a good compromise between smaller size and sound fidelity in typical robots.
Our robots typically will play a short 1-second sound to tell us they are performing an action like stopping, backing up or turning. This one-second 8K Hz WAV file will be about 20K. Our flash budget for the Raspberry Pi Pico is 2M, so we can easily store 10 sound effects in 200K or 1/10 of our available flash memory. We can also add a SD card if we need more flash memory.
We will be using standard .WAV files with Pulse Code Modulation Encoding in .WAV files. WAV files do take more space than compressed MP3 files, but they are easier to play because the decoding steps are trivial for a microcontroller to perform.
"},{"location":"sound/07-play-audio-file/#overall-architecture","title":"Overall Architecture","text":"/sounds
import os\n\nwaveFolder= \"/sounds\"\n\ntotal = 0\n# get a list of .wav files and their sizes\nfor file in os.listdir(waveFolder):\n size = os.path.getsize(file)\n print(file, size)\n total += size\nprint('Total sound directory size:', total)\n
"},{"location":"sound/07-play-audio-file/#connections","title":"Connections","text":"Some of this documentation was take from Dan Perron's Pico Audio GitHub Repo.
In these tests we used GPIO pins 2 and 3 to drive the left and right channels of audio that are sent to a stereo amplifier. You can use both an amplifier or head phone with a 1K resistor in series on the pins to limit the current from the 3.3v output signals.
The myPWM subclass set the maximum count to 255 (8 bits) or 1023(10bits) at a frequency around 122.5KHz.
The PWM is now on 10 bits (0..1023)
The myDMA class allows to use direct memory access to transfer each frame at the current sample rate.
We will need to install the wave.py and chunk.py from Jokey GitHub Awesome MicroPython Repo on root file system or the /lib folder on the pico file system.
Don't forget to increase the SPI clock up to 3Mhz. (TODO not sure what this means)
The following documentation was take from Daniel Perron's Github page.
wave.py
which will set the sample rate and read the audio data by chunkgit clone https://github.com/danjperron/PicoAudioPWM\ncd PicoAudioPWM\n
"},{"location":"sound/07-play-audio-file/#download-some-test-robot-wav-files","title":"Download some test robot wav files","text":"The following GitHub location:
https://github.com/CoderDojoTC/robot-media/tree/master/wav-8k
contains a set of 8K Hz 16 bit robot sounds that you can use with your robot.
"},{"location":"sound/07-play-audio-file/#converting-mp3-to-wav-files","title":"Converting .MP3 to .WAV files","text":"This software only currently support playing .wav files since it is easy to convert these files into a format that can be played. WAV files store uncompressed audio, so they are larger than MP3 files. Wav files are simple ways to store sound patterns. MP3 files are much more complex and require complex algorithms to convert into sound outputs.
The usual bitstream encoding is the linear pulse-code modulation (LPCM) format.
If you have just a few MP3 files, can use the following web-site to convert MP3 files into wave files:
Cloud Convert Service that Converts MP3 to WAV files
The next lab shows you how to convert an entire folder of MP3 files to 8K Hz 16-bit WAV files using a shell script.
"},{"location":"sound/07-play-audio-file/#copy-sound-files-to-the-pico","title":"Copy Sound Files to the Pico","text":"Your pico has 2MB of static memory. A typical robot sound effect you want to play when you bump into and object will play for around 1 second. You can copy many sound effect files to the pico file system and play them. Some IDEs may allow you to do this or you can use the rshell program.
Here is an example of using the rshell program to copy a directory of wav files to the pico. Lines that start with pc$ are commands that you type into your PC or MAC's terminal. Lines that start with rs$ are commands that you type into the rshell.
# list the devices (only works on Mac and UNIX)\npc$ ls /dev/cu.modem*\n# start the rshell\npc$ rshell -p /dev/cu.modem*\n# change the name of the device to be \"pico\"\nrs$ echo 'name=\"pico\"' > /pyboard/board.py\n# exit from rshell - can also use exit\nCONTROL-C\n# reconnect with the rshell\n$pc rshell -p /dev/cu.modem*\n# go into the /pico file systems\n$rs cd /pico\n# create a directory for all our sound files\nmkdir sounds\n# copy files from hour PC's home ~/tmp/sounds dir to the pico\nrs$ cp ~/tmp/sounds/*.wav /pico/sounds\nrs$ ls /pico/sounds\n
"},{"location":"sound/07-play-audio-file/#listing-the-wave-files","title":"Listing the Wave Files","text":"After you have a list of Wave files loaded you can verify them by using the os listdir() function.
import os\n\nwaveFolder= \"/sounds\"\nwavelist = []\n\n# get a list of .wav files\nfor i in os.listdir(waveFolder):\n if i.find(\".wav\")>=0:\n wavelist.append(waveFolder+\"/\"+i)\n elif i.find(\".WAV\")>=0:\n wavelist.append(waveFolder+\"/\"+i)\n\nif not wavelist :\n print(\"Warning NO '.wav' files\")\nelse:\n for i in wavelist:\n print(i)\n
Sample console output
/sounds/cylon-attention.wav\n/sounds/cylon-by-your-command.wav\n/sounds/cylon-excellent.wav\n/sounds/cylon-eye-scanner.wav\n/sounds/cylon-see-that-the-humans.wav\n/sounds/cylon-those-are-not-the-sounds.wav\n
"},{"location":"sound/07-play-audio-file/#checking-the-wav-file-format","title":"Checking the WAV File Format","text":"There is a standard Python module called `wave.py
that reads the .wav files and shows the metadata for the file. Wave files come in many formats, single channel, stereo and different bit rates. The wave player can show us all this data that describes the wave file.
The report shows you how to use fixed-width formatting since the file names and data should fit in columns to make it easier to read.
import os\nimport wave\n\nwaveFolder= \"/sounds\"\nwavelist = []\n\n# get a list of .wav files\nfor i in os.listdir(waveFolder):\n if i.find(\".wav\")>=0:\n wavelist.append(waveFolder+\"/\"+i)\n elif i.find(\".WAV\")>=0:\n wavelist.append(waveFolder+\"/\"+i)\n\nif not wavelist :\n print(\"Warning NO '.wav' files\")\nelse:\n print(\"{0:<45}\".format('File Path'), 'Frame Rate Width Chans Frames')\n for filename in wavelist:\n f = wave.open(filename,'rb')\n # the format string \"{0:<50}\" says print left justified from chars 0 to 50 in a fixed with string\n print(\"{0:<50}\".format(filename),\n \"{0:>5}\".format(f.getframerate()),\n \"{0:>5}\".format(f.getsampwidth()),\n \"{0:>6}\".format(f.getnchannels()),\n \"{0:>6}\".format(f.getnframes())\n )\n
Sample Response
File Path Frame Rate Width Chans Frames\n/sounds/cylon-attention.wav 8000 1 1 6399\n/sounds/cylon-by-your-command.wav 11025 1 1 12583\n/sounds/cylon-excellent.wav 22050 1 1 48736\n/sounds/cylon-eye-scanner.wav 16000 2 2 24768\n/sounds/cylon-see-that-the-humans.wav 11025 1 1 30743\n/sounds/cylon-those-are-not-the-sounds.wav 22050 1 1 64137\n
"},{"location":"sound/07-play-audio-file/#adding-an-interrupt","title":"Adding an Interrupt","text":"If you halt the RP2040 while it is playing a sound, the independent PWM controllers will continue to generate sound. In order to shut the independent PWM controllers, an interrupt controller system must be used to cleanly disable all the sound. Here is an example of this using a try/except block of code.
import os as uos\nfrom wavePlayer import wavePlayer\nplayer = wavePlayer()\n\ntry:\n while True:\n # repeat this over and over until the keyboard shuts down the circuit\n player.play('/sounds/cylon-eye-scanner.wav')\nexcept KeyboardInterrupt:\n player.stop()\n print(\"wave player terminated\")\n
"},{"location":"sound/07-play-audio-file/#playing-the-same-sound-repeatedly","title":"Playing the Same Sound Repeatedly","text":"import os as uos\nfrom wavePlayer import wavePlayer\nplayer = wavePlayer()\n\ntry:\n while True:\n player.play('/sounds/cylon-eye-scanner.wav')\nexcept KeyboardInterrupt:\n player.stop()\n print(\"wave player terminated\")\n
"},{"location":"sound/07-play-audio-file/#downloading-the-audio-libraries","title":"Downloading the Audio Libraries","text":"Both the wave.py and the chunck.py files are here:
https://github.com/joeky888/awesome-micropython-lib/tree/master/Audio
"},{"location":"sound/07-play-audio-file/#references","title":"References","text":"In this lab, we will be testing stereo audio ports. We will be sending a 1 kilohertz square wave to the left and then right ports. This program allows you to test your stereo connections and make sure that both channels are working correctly.
from machine import Pin, PWM\nfrom utime import sleep\n\n# You will need to configure these two digital output ports\nAUDIO_LEFT_PIN = 18\nAUDIO_RIGHT_PIN = 19\n\n# create a Pulse Width Modulation Object on this pin\nleft_speaker = PWM(Pin(AUDIO_LEFT_PIN))\n# set the duty cycle to be 50%\nleft_speaker.duty_u16(1000)\nleft_speaker.freq(1000) # 50% on and off\nsleep(1) # wait a second\nleft_speaker.duty_u16(0)\n# turn off the PWM circuits off with a zero duty cycle\nleft_speaker.duty_u16(0)\nsleep(1)\n\n# create a Pulse Width Modulation Object on this pin\nright_speaker = PWM(Pin(AUDIO_RIGHT_PIN))\n# set the duty cycle to be 50%\nright_speaker.duty_u16(1000)\nright_speaker.freq(1000) # 50% on and off\nsleep(1) # wait a second\nright_speaker.duty_u16(0)\n# turn off the PWM circuits off with a zero duty cycle\nright_speaker.duty_u16(0)\n
"},{"location":"sound/07a-test-audio-ports/#references","title":"References","text":"https://en.wikipedia.org/wiki/Phone_connector_(audio)#Computer_sound
"},{"location":"sound/08-i2s-standard/","title":"I2S Standard","text":"I2S (Inter-IC Sound) pronounced \"eye-two-ess\", is an electrical serial bus interface standard used for connecting digital audio devices together. It is a general interface to communicate PCM audio data between integrated circuits in an electronic device.
Note that I2S is unrelated to the similar sounding I2C bus protocol.
Since MicroPython 1.17 we have a library that allows us to play
"},{"location":"sound/09-converting-mp3-to-wav/","title":"Converting MP3 to WAV formats","text":"In last lab we will learned how to play an audio file stored on the flash memory or SD card. We used an early version of a Python module that plays .WAV files in a fixed format: 8,000 samples per second using 16-bit encoding. Because there are hundreds of different formats of audio files, we need a consistent way to convert all of these formats to 8K samples/second 16-bit .WAV formats.
The audio data file conversions are done on your host PC, not the microcontroller. This allows us to use a rich library of tools that don't need to run on our microcontroller.
"},{"location":"sound/09-converting-mp3-to-wav/#method-1-use-the-ffmpeg-command-line-tool","title":"Method 1: Use the ffmpeg Command Line Tool","text":"One of the easiest ways to get started is to go to the web site ffmpeg.org and download the program that does the conversion using a command line. Note that on a Mac you will need to go into your System Preferences and indicate that the following programs are trusted:
Here are the direct links for MacOS
This will download a zip file which you will need to unzip and place in a location such as ~/bin
. After you do this make sure you add `~/bin
to your path by adding the following file to your .bash_profile:
PATH=$PATH:~/bin
After you source your .bash_profile type in the following:
which ffmpeg
This should return the location that it finds the ffmpeg shell script. You can then see the many file-format MPEG options:
ffmpeg --help
To get the format we need for the MicroPython wave player class we just specific -i
for the input file and use the -ar 8000
to specify the output bit rate of 8K samples per second. The final parameter is a file name that must in .wav
so the command knows to use WAV PCM encoding. The default value is 16 gits per sample.
ffmpeg -i r2d2-beeping.mp3 -ar 8000 r2d2-beeping-8k.wav\n
"},{"location":"sound/09-converting-mp3-to-wav/#bulk-conversions","title":"Bulk Conversions","text":"We can use unix shell commands to do a batch conversion of all the . The following is an example of using awk and sed to convert all the .mp3 files in a directory and convert them to 8K Hz WAV files and put them in a sibling directory.
ls -1 *.mp3 | awk '{print \"ffmpeg -i \" $1 \" -ar 8000 ../wav-8k/\"$1}' | sed s/mp3$/wav/ | sh\n
"},{"location":"sound/09-converting-mp3-to-wav/#inspect-the-files-using-the-unix-file-command","title":"Inspect the Files Using the UNIX file command:","text":"cd ../wav-8k\nfile *.wav\n
returns
r2d2-another-beep.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 8000 Hz\nr2d2-beeping-2.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 8000 Hz\nr2d2-beeping-4.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 8000 Hz\nr2d2-beeping-8k.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 8000 Hz\nr2d2-beeping-like-an-alarm.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 8000 Hz\nr2d2-beeping.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 8000 Hz\nr2d2-cheerful.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 8000 Hz\nr2d2-determined.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 8000 Hz\nr2d2-excited.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 8000 Hz\nr2d2-laughing.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 8000 Hz\nr2d2-more-chatter.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 8000 Hz\nr2d2-processing.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 8000 Hz\nr2d2-sad.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 8000 Hz\nr2d2-shocked.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 8000 Hz\nr2d2-surprised.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 8000 Hz\nr2d2-taking-to-himself.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 8000 Hz\nr2d2-unsure.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 8000 Hz\n
Note that these are WAVE audio, Pulse-code Modulated (PCM), 16 bit and mono at 8K Hz.
"},{"location":"sound/09-converting-mp3-to-wav/#method-2-use-the-pydub-python-module","title":"Method 2: Use the pydub Python Module","text":"Note
This section is only for experienced Python developers.
"},{"location":"sound/09-converting-mp3-to-wav/#install-conda-and-the-python-libraries","title":"Install Conda and the Python libraries","text":"conda create -n mp3-to-wav python=3\nconda activate mp3-to-wav\npip install pydub ffprobe ffmpeg\n
Check your versions:
pip freeze\n
returns:
ffmpeg==1.4\nffprobe==0.5\npydub==0.25.1\n
"},{"location":"sound/09-converting-mp3-to-wav/#running-pydub","title":"Running pydub","text":"from pydub import AudioSegment\n\nsound = AudioSegment.from_mp3(\"r2d2-beeping.mp3\")\nsound.export(\"r2d2-beeping.wav\", format=\"wav\", tags={'Robot name': 'R2D2'})\n
"},{"location":"sound/09-converting-mp3-to-wav/#transferring-the-files-with-rshell","title":"Transferring the Files with Rshell","text":"cd ../wav-8k\nrshell -p /dev/cu.usbmodem14101\ncp *.wav /pico/sounds\n
"},{"location":"sound/09-converting-mp3-to-wav/#references","title":"References","text":"Warnining
I am not an expert on MIDI and I don't have any MIDI gear. This is mostly a collection of material I have for students that want to use MicroPython to generate music.
"},{"location":"sound/10-midi/#sample-code","title":"Sample Code","text":""},{"location":"sound/10-midi/#ascending-notes","title":"Ascending Notes","text":"# Play Test MIDI Ascending Notes\nfrom machine import Pin, UART\nimport time\nuart = UART(1, baudrate=31250, tx=Pin(4), rx=Pin(5))\nuart.init(bits=8, parity=None, stop=1)\nled=Pin(\"LED\", Pin.OUT)\nnote = 24\n\nwhile True:\n midimessage = bytearray([0x90, note, 64])\n uart.write(midimessage)\n led.toggle()\n # change third parameter to be 0\n midimessage = bytearray([0x90, note, 0])\n # pause 1/8 of a second\n time.sleep(0.125)\n note += 2\n if note > 64:\n note=24\n
"},{"location":"sound/10-midi/#references","title":"References","text":"Raspberry Pi Pico MIDI Development Board Midimuso for $20 US. This board has all the MIDI connectors on it.
Simple DIY electromusic Project
DIY Electro Music SDEMP MicroPython Code on GitHub
Pico MIDI by Barb Arach
PicoMIDI manual v1.0
"},{"location":"sound/11-sound-parts/","title":"Sound Parts","text":"PAM8302A Miniature 2.5W Class D Mono Audio Power Amplifier Module Board
PAM8302A 10pcs
These lessons are designed to give our students an understanding of how wireless communications work with MicroPython.
"},{"location":"wireless/01-intro/#raspberry-pi-pico-w","title":"Raspberry Pi Pico W","text":"One June 30th, 2022 the Raspberry Pi Foundation announced the availability of the Raspberry Pi Pico W. This $6 microprocessor now supports WiFi and with a software upgrade it may soon support Bluetooth.
The Pico W supports 2.4 Ghz 802.11n wireless networking. For MicroPython, we can use a MicroPython library built around the lwip TCP/IP stack. This stack is accessible using the MicroPython network functions.
The WiFi chip used is the Infineon CYW43439 chip. This chip also uses an ARM architecture and has extensive support for Bluetooth wireless communication.
You can read more about the capabilities of the WiFi/Bluetooth chip by reading the Infineon CYW43439 Datasheet. I found it interesting that the CYW43439 chip has 512KB of SRAM - almost double what the RP2040 chip contains!
"},{"location":"wireless/01-intro/#esp32-wireless","title":"ESP32 Wireless","text":"We have not integrated the ESP32 into our labs. We suggest you try the following links:
ESP32 MicroPython: Connecting to a WiFi Network on Tech Tutorials SX
MicroPython: Wi-Fi Manager with ESP32 (ESP8266 compatible) on Random Nerd Tutorials
"},{"location":"wireless/02-connecting-to-wifi/","title":"Connecting to a WiFi Network","text":""},{"location":"wireless/02-connecting-to-wifi/#setting-up-your-wifi-secretspy","title":"Setting Up Your WIFI secrets.py","text":"By convention, we put both our SSID and password in a python file called \"secrets.py\". This file should never be checked into a public source code repository. We can add secrets.py
to the .gitignore file to make sure the secrets.py is never checked into GitHub and exposing your passwords to everyone.
SSID = \"MY_WIFI_NETWORK_NAME\"\nPASSWORD = \"MY_WIFI_PASSWORD\"\n
By importing the secrets.py file you can then reference your network name like this:
print('Connecting to WiFi Network Name:', secrets.SSID)\n
"},{"location":"wireless/02-connecting-to-wifi/#testing-your-wifi-access-point-connection","title":"Testing Your WiFi Access Point Connection","text":"Here is a very simple script to test see if your network name and password are correct. This script may work, but as we will see, it is both slow and potentially unreliable.
import network\nimport secrets\nfrom utime import sleep\n\nprint('Connecting to WiFi Network Name:', secrets.SSID)\nwlan = network.WLAN(network.STA_IF)\nwlan.active(True) # power up the WiFi chip\nprint('Waiting for wifi chip to power up...')\nsleep(3) # wait three seconds for the chip to power up and initialize\nwlan.connect(secrets.SSID, secrets.PASSWORD)\nprint('Waiting for access point to log us in.')\nsleep(2)\nif wlan.isconnected():\n print('Success! We have connected to your access point!')\n print('Try to ping the device at', wlan.ifconfig()[0])\nelse:\n print('Failure! We have not connected to your access point! Check your secrets.py file for errors.')\n
Returns:
Connecting to WiFi Network Name: MY_WIFI_NETWORK_NAME\nWaiting for wifi chip to power up...\nWaiting for access point to log us in...\nSuccess! We have connected to your access point!\nTry to ping the device at 10.0.0.70\n
If the result is a Failure
you should check the name of the network and the password and that you are getting a strong WiFi signal where you are testing.
Note that we are using the sleep()
function to insert delays into our code. However, the results may actually be faster or slower than our sleep times. Our next step is to add logic that will test to see if the networking device is ready and if our local access point allows us to login correctly.
Sometimes we want to keep checking if our access point is connected before we begin using our connection. To do this we can create a while loop and continue in the loop while we are not connected.
import network\nimport secrets\nfrom utime import sleep, ticks_ms, ticks_diff\n\nprint('Connecting to WiFi Network Name:', secrets.SSID)\nwlan = network.WLAN(network.STA_IF)\nwlan.active(True)\n\nstart = ticks_ms() # start a millisecond counter\n\nif not wlan.isconnected():\n wlan.connect(secrets.SSID, secrets.PASSWORD)\n print(\"Waiting for connection...\")\n counter = 0\n while not wlan.isconnected():\n sleep(1)\n print(counter, '.', sep='', end='', )\n counter += 1\n\ndelta = ticks_diff(ticks_ms(), start)\nprint(\"Connect Time:\", delta, 'milliseconds')\nprint('IP Address:', wlan.ifconfig()[0])\n
This code also supports a timer that will display the number of seconds for the access point to become valid in the console. The first time after you power on, this may take several seconds. After you are connected the connection will be cached and the time will be 0 milliseconds.
First run upon power on might take several seconds:
>>> %Run -c $EDITOR_CONTENT\nConnecting to WiFi Network Name: MY_NETWORK_NAME\nWaiting for connection...\n0.1.2.3.Connect Time: 4640\nIP Address: 10.0.0.70\n
The second and consecutive runs will use a cached connection.
>>> %Run -c $EDITOR_CONTENT\nConnecting to WiFi Network Name: MY_NETWORK_NAME\nConnect Time: 0 milliseconds\nIP Address: 10.0.0.70\n>>>\n
"},{"location":"wireless/02-connecting-to-wifi/#error-handling","title":"Error Handling","text":"lan = network.WLAN(network.STA_IF)\nwlan.active(True)\nwlan.connect(ssid, password)\n\n# Wait for connect or fail\nmax_wait = 10\nwhile max_wait > 0:\n if wlan.status() < 0 or wlan.status() >= 3:\n break\n max_wait -= 1\n print('waiting for connection...')\n time.sleep(1)\n\n# Handle connection error\nif wlan.status() != 3:\n raise RuntimeError('network connection failed')\nelse:\n print('connected')\n status = wlan.ifconfig()\n print( 'ip = ' + status[0] )\n
The full TCP/IP stack is running on your Pico W. You should be able to ping the pico using the IP address returned by the status[0] of the wlan.ifconfig() function above.
"},{"location":"wireless/03-http-get/","title":"HTTP GET","text":""},{"location":"wireless/03-http-get/#testing-http-get","title":"Testing HTTP GET","text":"The following example was taken from Tom's Hardware
import network\nimport secrets\nfrom utime import sleep, ticks_ms, ticks_diff\nimport urequests\n\nwlan = network.WLAN(network.STA_IF)\nwlan.active(True)\nwlan.connect(secrets.SSID, secrets.PASSWORD)\n\nstart = ticks_ms() # start a millisecond counter\n\nastronauts = urequests.get(\"http://api.open-notify.org/astros.json\").json()\n\ndelta = ticks_diff(ticks_ms(), start)\n\nnumber = astronauts['number']\nprint('There are', number, 'astronauts in space.')\nfor i in range(number):\n print(i+1, astronauts['people'][i]['name'])\n\nprint(\"HTTP GET Time in milliseconds:\", delta)\n
Returns:
There are 10 astronauts in space.\n1 Oleg Artemyev\n2 Denis Matveev\n3 Sergey Korsakov\n4 Kjell Lindgren\n5 Bob Hines\n6 Samantha Cristoforetti\n7 Jessica Watkins\n8 Cai Xuzhe\n9 Chen Dong\n10 Liu Yang\nHTTP GET Time in milliseconds: 786\n
"},{"location":"wireless/04-web-server/","title":"Web Server with MicroPython","text":"This program turns your Pico W into a small web server. The web page has two links on it. One link will turn the on-board LED on and the other link will turn the LED off.
Screen image of Pico W Web Server:
# Code taken from https://www.cnx-software.com/2022/07/03/getting-started-with-wifi-on-raspberry-pi-pico-w-board/\nimport network\nimport socket\nimport time\nimport secrets\n\nfrom machine import Pin\n\n# Select the onboard LED\nled = machine.Pin(\"LED\", machine.Pin.OUT)\n\nwlan = network.WLAN(network.STA_IF)\nwlan.active(True)\nwlan.connect(secrets.SSID, secrets.PASSWORD)\nstateis = \"LED is OFF\"\n\nhtml = \"\"\"<!DOCTYPE html>\n<html>\n <head>\n <title>Web Server On Pico W </title>\n </head>\n <body>\n <h1>Pico Wireless Web Server</h1>\n <p>%s</p>\n <a href=\"/light/on\">Turn On</a>\n <a href=\"/light/off\">Turn Off</a>\n </body>\n</html>\n\"\"\"\n\n# Wait for connect or fail\nmax_wait = 10\nwhile max_wait > 0:\n if wlan.status() < 0 or wlan.status() >= 3:\n break\n max_wait -= 1\n print('waiting for connection...')\n time.sleep(1)\n\n# Handle connection error\nif wlan.status() != 3:\n raise RuntimeError('network connection failed')\nelse:\n print('We are connected to WiFI access point:', secrets.SSID)\n status = wlan.ifconfig()\n print( 'The IP address of the pico W is:', status[0] )\n\n# Open socket\naddr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]\nprint('addr:', addr)\ns = socket.socket()\n#if not addr:\ns.bind(addr)\ns.listen(1)\n\nprint('listening on', addr)\n\n# Listen for connections\nwhile True:\n try:\n cl, addr = s.accept()\n print('client connected from', addr)\n request = cl.recv(1024)\n print(request)\n request = str(request)\n led_on = request.find('/light/on')\n led_off = request.find('/light/off')\n print( 'led on = ' + str(led_on))\n print( 'led off = ' + str(led_off))\n\n if led_on == 6:\n print(\"led on\")\n led.value(1)\n stateis = \"LED is ON\"\n\n if led_off == 6:\n print(\"led off\")\n led.value(0)\n stateis = \"LED is OFF\"\n # generate the we page with the stateis as a parameter\n response = html % stateis\n cl.send('HTTP/1.0 200 OK\\r\\nContent-type: text/html\\r\\n\\r\\n')\n cl.send(response)\n cl.close()\n\n except OSError as e:\n cl.close()\n print('connection closed')\n
"},{"location":"wireless/04-web-server/#references","title":"References","text":""},{"location":"wireless/05-mac-address/","title":"Getting Your MAC Address","text":""},{"location":"wireless/05-mac-address/#what-is-a-mac-address","title":"What is a MAC Address?","text":"Every device that works with Ethernet and WiFi must have a Wikipedia Page on MAC Address.
A MAC address (media access control address) is a unique identifier assigned to a network interface controller (NIC) for use as a network address in communications within a network segment. This use is common in most IEEE 802 networking technologies, including Ethernet, Wi-Fi, and Bluetooth.
Note
The MAC address has nothing to do with the Apple Macintosh or Mac OS. They just use the same name to mean different things.
Understanding how devices use MAC addresses is essential to understanding how networks work and debugging them.
The MAC address is six bytes or \"octets\". The first three octets are assigned to the organization that created the device. The second three octets are assigned by the organization that created the device. See the Wikipedia Page on MAC Address for more information. If you run this on your Pico W the first octets should be similar.
Here are the two MAC addresses for two different Pico W devices that were purchase together. They came on adjacent parts of a \"real\" of devices.
28:cd:c1:1:35:54\n28:cd:c1:1:35:58\n
Because they were purchased together, their MAC address are very similar and only differ in the last few bits.
This Pico W came from another distributer.
28:cd:c1:0:9e:19\n
You can see that the first three octets are the same. A MAC address often can give clues about the origins of packets on your network. They can also be used as \"serial numbers\" for each device that connects to WiFi or Ethernet since they are usually burned into ROM at device manufacturing time and are designed to be unique.
"},{"location":"wireless/05-mac-address/#getting-the-macethernet-access","title":"Getting the MAC/Ethernet Access","text":"You can get the device MAC/Ethernet address and test the roundtrip time between the RP2040 and the WiFi chip using the MAC address function.
import network\nfrom utime import sleep, ticks_us, ticks_diff\n\nprint('Getting MAC/Ethernet Address for this device.')\n\nstart = ticks_us() # start a millisecond counter\nwlan = network.WLAN(network.STA_IF)\nwlan.active(True) # this line powers up the chip - it takes about 2.5 seconds\n\n# This returns a byte array of hex numbers\nmac_addess = wlan.config('mac')\nprint('Time in microseconds:', ticks_diff(ticks_us(), start))\n# each MAC address is 6 bytes or 48 bits\nprint(\"Hex byte array:\", mac_addess, 'length:', len(mac_addess))\n\n# This should be in hex per the Notational Conventions\n# https://en.wikipedia.org/wiki/MAC_address#Notational_conventions\n# b'(\\xcd\\xc1\\x015X'\n# 28:cd:c1:1:35:58\n# format in MAC Notational Convention\nfor digit in range(0,5):\n print(str(hex(mac_addess[digit]))[2:4], ':', sep='', end = '')\nprint(str(hex(mac_addess[5]))[2:4] )\n
First Time After Power On Results:
Getting MAC/Ethernet Address for this device.\nTime in microseconds: 2584424\nHex byte array: b'(\\xcd\\xc1\\x015X' length: 6\n28:cd:c1:1:35:58\n
Note that it takes about 2.5 seconds just to power on the chip before we get the MAC address.
Subsequent Times
Getting MAC/Ethernet Address for this device.\nTime in microseconds: 211\nHex byte array: b'(\\xcd\\xc1\\x015X' length: 6\n28:cd:c1:1:35:58\n
Note
We must add the wlan.active(True)
line to this code. If we don't do this, the wifi device will not be powered up and we can't get the MAC address. The function will return all zeros.
I ran this program on my Pico W and I got times of between 214 and 222 microseconds. This shows you that it takes about 100 microseconds to send a request from the RP2040 to the CYW43439 WiFi chip and about 100 milliseconds to return the results. This time lag represents some of the key performance limitations in using the Pico W for high-performance networking.
"},{"location":"wireless/05-mac-address/#mac-addresses-and-privacy","title":"MAC Addresses and Privacy","text":"MAC addresses are never sent to web servers to protect a clinet's privacy, but if you are debugging networks and looking at tools such as WireShark, they become visible to a local LAN administrator. MAC addresses are also visible to WiFi access points, so your local coffee shop that provides free WiFi could keep track of every time repeat customers use their WiFi access points.
Some computers such as Apple generate a different MAC address for each different access point they connect to. This prevents multiple stores from connecting your profiles together. So Starbucks can't use your MAC address to offer you a free coffee every 10th visit to different stores, but if you visit a local coffee shop they can use their WiFi access logs to see their same-store repeat customers.
"},{"location":"wireless/06-upip/","title":"MicroPython PIP (UPIP)","text":"MicroPython also has a package manager called upip
that can be run directly on the microcontroller. This means that every time you get a new microcontroller, you can automatically download all the python packages you need directly from the Internet using a single python program after upip
has been installed.
This also means that you don't need copies of these libraries installed on your local PC.
"},{"location":"wireless/06-upip/#install-upip-from-thonny-package-manager","title":"Install UPIP From Thonny Package Manager","text":"Go to the Thonny Tools -> Manage Packages... menu. Type \"upip\" in the search field and click the Search on PyPL button:
The first search result will be for **micropython-upip\". Click on that link and you will see the details.
"},{"location":"wireless/06-upip/#install-a-package","title":"Install A Package","text":"The following program assumes that you have followed the steps in our Connecting to WiFi and have a secrets.py file that have your WiFi network name (SSID) and password (PASSWORD).
import upip\nimport network\nimport secrets\nfrom utime import sleep, ticks_ms, ticks_diff\n\nprint('Connecting to WiFi Network Name:', secrets.SSID)\nwlan = network.WLAN(network.STA_IF)\nwlan.active(True)\n\nstart = ticks_ms() # start a millisecond counter\n\nif not wlan.isconnected():\n wlan.connect(secrets.SSID, secrets.PASSWORD)\n print(\"Waiting for connection...\")\n counter = 0\n while not wlan.isconnected():\n sleep(1)\n counter += 1\n print(counter, '.', sep='', end='', )\n\ndelta = ticks_diff(ticks_ms(), start)\nprint(\"Connect Time:\", delta)\n\n# This is the line you must modify for each package you want to install\n# pystone is a CPU performance benchmarking tool that is easy to run to and test installation\n\nstart = ticks_ms()\n\nupip.install(\"micropython-pystone_lowmem\")\n\nprint(\"Download Time:\", ticks_diff(ticks_ms(), start), milliseconds)\n
Results:
Connecting to WiFi Network Name: MY_NETWORK_NAME\nWaiting for connection...\n1.2.3.4.Connect Time: 4641\nInstalling to: /lib/\nWarning: micropython.org SSL certificate is not validated\nInstalling micropython-pystone_lowmem 3.4.2.post4 from https://micropython.org/pi/pystone_lowmem/pystone_lowmem-3.4.2.post4.tar.gz\nDownload Time: 4918 milliseconds\n
"},{"location":"wireless/06-upip/#testing-your-newly-installed-library","title":"Testing Your Newly Installed Library","text":"import pystone_lowmem\npystone_lowmem.main()\n
Results:
Pystone(1.2) time for 500 passes = 410ms\nThis machine benchmarks at 1219 pystones/second\n
"},{"location":"wireless/06-upip/#getting-upip-help","title":"Getting UPIP Help","text":"import upip\nupip.help()\n
returns:
upip - Simple PyPI package manager for MicroPython\nUsage: micropython -m upip install [-p <path>] <package>... | -r <requirements.txt>\nimport upip; upip.install(package_or_list, [<path>])\n\nIf <path> isn't given, packages will be installed to sys.path[1], or\nsys.path[2] if the former is .frozen (path can be set from MICROPYPATH\nenvironment variable if supported).\nDefault install path: /lib\n\nNote: only MicroPython packages (usually, named micropython-*) are supported\nfor installation, upip does not support arbitrary code in setup.py.\n
"},{"location":"wireless/07-web-server-rgb-neopixels/","title":"Web Server NeoPixel RGB","text":""},{"location":"wireless/07-web-server-rgb-neopixels/#sample-code-with-css","title":"Sample Code with CSS","text":"# Code taken from https://www.cnx-software.com/2022/07/03/getting-started-with-wifi-on-raspberry-pi-pico-w-board/\nfrom machine import Pin\nfrom neopixel import NeoPixel\nimport network\nimport socket\nfrom time import sleep\nimport secrets\n\nNEOPIXEL_PIN = 0\nNUMBER_PIXELS = 30\nPERCENT_COLOR_WHEEL = round(255/NUMBER_PIXELS)\n\n# setup\nstrip = NeoPixel(machine.Pin(NEOPIXEL_PIN), NUMBER_PIXELS)\n\nred = (255,0,0)\ngreen = (0,255,0)\nblue = (0,0,255)\noff = (0,0,0)\n\ndef set_color(color):\n for i in range(0, NUMBER_PIXELS):\n strip[i] = color\n strip.write()\n\n# Select the onboard LED\nled = machine.Pin(\"LED\", machine.Pin.OUT)\n\nwlan = network.WLAN(network.STA_IF)\nwlan.active(True)\nwlan.connect(secrets.SSID, secrets.PASSWORD)\nstateis = \"LED is OFF\"\n\nhtml = \"\"\"<!DOCTYPE html>\n<html>\n <head>\n <title>Web Server On Pico W </title>\n <style>\n body {\n font-size:24px;\n font-family:'Helvetica'\n }\n\n .button-row {\n display: inline-block;\n padding: 2px 3px 6px 3px;\n margin: 5px;\n }\n\n a:visited {\n color: yellow;\n background-color: transparent;\n text-decoration: none;\n }\n\n a:hover {\n color: orange;\n background-color: transparent;\n text-decoration: none;\n }\n\n .button {\n font: bold 16px Arial;\n text-decoration: none;\n padding: 2px 6px 2px 6px;\n border-top: 2px solid #CCCCCC;\n border-right: 2px solid #333333;\n border-bottom: 2px solid #333333;\n border-left: 2px solid #CCCCCC;\n }\n </style>\n </head>\n <body>\n <h1>Pico Wireless Web Server</h1>\n <p>Onboard LED State: %s</p>\n\n\n <div class=\"buttons\">\n\n <span class=\"button-row\" style=\"background-color:gray\">\n <a href=\"/light/on\" class=\"button\">Turn Onboard LED On</a>\n <a href=\"/light/off\" class=\"button\">Turn Onboard LED Off</a>\n </span><br/>\n\n <span class=\"button-row\" style=\"background-color:red\">\n <a href=\"/led/red/on\" class=\"button\">Red LED On</a>\n <a href=\"/led/red/off\" class=\"button\">Red LED Off</a>\n </span><br/>\n\n <span class=\"button-row\" style=\"background-color:green\">\n <a href=\"/led/green/on\" class=\"button\">Green LED On</a>\n <a href=\"/led/green/off\" class=\"button\">Green LED Off</a>\n </span><br/>\n\n <span class=\"button-row\" style=\"background-color:blue\">\n <a href=\"/led/blue/on\" class=\"button\">Blue LED On</a>\n <a href=\"/led/blue/off\" class=\"button\">Blue LED Off</a>\n </span>\n\n </div>\n </body>\n</html>\n\"\"\"\n\n# Wait for connect or fail\nmax_wait = 10\nwhile max_wait > 0:\n if wlan.status() < 0 or wlan.status() >= 3:\n break\n max_wait -= 1\n print('waiting for connection...')\n sleep(1)\n led.toggle()\n\n# Handle connection error\nif wlan.status() != 3:\n raise RuntimeError('network connection failed')\nelse:\n print('We are connected to WiFI access point:', secrets.SSID)\n status = wlan.ifconfig()\n print( 'The IP address of the pico W is:', status[0] )\n\n# Open socket\naddr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]\n# print('addr:', addr)\n\ns = socket.socket()\n# this prevents the \"OSError: [Errno 98] EADDRINUSE\" error for repeated tests\ns.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n# print('Got a sockert. s:', s)\n\n#if not addr:\n\n# print('going to run bind to addr on the socket')\ns.bind(addr)\n\n# print('going to run listen(1) on socket')\ns.listen(1)\n\n# print('listening on', addr)\n\n# Listen for connections\nwhile True:\n try:\n cl, addr = s.accept()\n # print('client connected from', addr)\n request = cl.recv(1024)\n # print(request)\n request = str(request)\n led_on = request.find('/light/on')\n led_off = request.find('/light/off')\n\n red_on = request.find('/led/red/on')\n red_off = request.find('/led/red/off')\n\n green_on = request.find('/led/green/on')\n green_off = request.find('/led/green/off')\n\n blue_on = request.find('/led/blue/on')\n blue_off = request.find('/led/blue/off')\n\n # print( 'led on = ' + str(led_on))\n # print( 'led off = ' + str(led_off))\n\n if led_on == 6:\n print(\"led on\")\n led.on()\n stateis = \"LED is ON\"\n\n if led_off == 6:\n print(\"led off\")\n led.off()\n stateis = \"LED is OFF\"\n\n if red_on == 6:\n print(\"red on\")\n set_color(red)\n if red_off == 6:\n print(\"red off\")\n set_color(off)\n\n if green_on == 6:\n print(\"green on\")\n set_color(green)\n if green_off == 6:\n print(\"green off\")\n set_color(off)\n\n if blue_on == 6:\n print(\"blue on\")\n set_color(blue)\n if blue_off == 6:\n print(\"blue off\")\n set_color(off)\n\n # generate the we page with the stateis as a parameter\n response = html % stateis\n cl.send('HTTP/1.0 200 OK\\r\\nContent-type: text/html\\r\\n\\r\\n')\n cl.send(response)\n cl.close()\n\n except OSError as e:\n cl.close()\n s.close()\n print('connection closed')\n
"},{"location":"wireless/07-web-server-rgb-neopixels/#references","title":"References","text":"Clear Fix Floating Boxes Example
https://www.w3schools.com/css/tryit.asp?filename=trycss_float_boxes
<style>\n* {\n box-sizing: border-box;\n}\n\n.box {\n float: left;\n width: 33.33%;\n padding: 30px 20px;\n}\n\n.clearfix::after {\n content: \"\";\n clear: both;\n display: table;\n}\n</style>\n</head>\n<body>\n\n <h2>Grid of Boxes</h2>\n <p>Float boxes side by side:</p>\n\n <div class=\"clearfix\">\n <div class=\"box\" style=\"background-color:silver\">\n <p>Some text inside the box.</p>\n </div>\n <div class=\"box\" style=\"background-color:gray\">\n <p>Some text inside the box.</p>\n </div>\n </div>\n
"},{"location":"wireless/08-get-weather/","title":"Get the Weather Forecast","text":"This demo uses the free web service Open Weather Map.
The Open Weather Map service returns the predicted temperatures and conditions (sun, cloudy, rain etc.) for three-hour intervals for the next 40 intervals at your specified location.
You can see this using the UNIX curl command:
curl 'http://api.openweathermap.org/data/2.5/forecast?\\\n units=imperial&\\\n id=5037649&\\\n appid=f2b1...'\n
In this example, we are asking it to use US standard Fahrenheit (imperial units) for the city with id 5037649 which is Minneapolis, MN in the USA. You can use the Open Weather site to find the id for your city or specify the longitude and latitude of the point you want to get weather forecasts for. You can also use the GeoNames to find your city ID number.
"},{"location":"wireless/08-get-weather/#how-to-use-the-open-weather-map-api","title":"How to Use the Open Weather Map API","text":"To use this service, you must register and get an API key. You then put your key in the secrets.py file:
Content of secrets.py
appid='f2b1...'\n
The secrets.py file is then imported into your program. Make sure to put secrets.py into your .gitignore file so it will not be checked into your public GitHub repo.
The URL for the service is then created by concatenating the base URL, the city location ID and the application id:
base = 'http://api.openweathermap.org/data/2.5/forecast?units=imperial&'\nlocation = '5037649' # GeoNames ID for Minneapolis in Minnesota, USA\nurl = base + 'id=' + location + '&appid=' + secrets.appid\n
"},{"location":"wireless/08-get-weather/#parsing-the-json-file","title":"Parsing the JSON file","text":"The service returns a JSON file with the following format:
{\n \"cod\": \"200\",\n \"message\": 0,\n \"cnt\": 40,\n \"list\": [\n {\n \"dt\": 1660078800,\n \"main\": {\n \"temp\": 80.83,\n \"feels_like\": 81.23,\n \"temp_min\": 80.83,\n \"temp_max\": 84.67,\n \"pressure\": 1019,\n \"sea_level\": 1019,\n \"grnd_level\": 988,\n \"humidity\": 47,\n \"temp_kf\": -2.13\n },\n \"weather\": [\n {\n \"id\": 800,\n \"main\": \"Clear\",\n \"description\": \"clear sky\",\n \"icon\": \"01d\"\n }\n ],\n \"clouds\": {\n \"all\": 0\n },\n \"wind\": {\n \"speed\": 6.08,\n \"deg\": 226,\n \"gust\": 6.38\n },\n \"visibility\": 10000,\n \"pop\": 0,\n \"sys\": {\n \"pod\": \"d\"\n },\n \"dt_txt\": \"2022-08-09 21:00:00\"\n }, \n /* ...repeated 40 times */\n ,\n \"city\": {\n \"id\": 5037649,\n \"name\": \"Minneapolis\",\n \"coord\": {\n \"lat\": 44.98,\n \"lon\": -93.2638\n },\n \"country\": \"US\",\n \"population\": 0,\n \"timezone\": -18000,\n \"sunrise\": 1660043269,\n \"sunset\": 1660094963\n }\n}\n
The bulk of the data is in the list structure with 40 items with a small set of data before and after the list. The data about the HTTP status (200) and the count (40) is before the list and the data about the city is after the list.
Each block of data in the list JSON object contains data about the main data (temperature, min, max, pressure, humidity), cloud cover, wind speed and visibility. It is up to you to decide what data you would like to display within this data. Once you decide what data you want to access you can use JSON path statements to pull the right data out. For example, to get the main temperature for each time period you would run:
# get the temp and humidity for the next 40 3-hour intervals\nfor i in range(0, 39):\n print('temp:', weather['list'][i]['main']['temp'], end='')\n print('humidity:', weather['list'][i]['main']['humidity'])\n
"},{"location":"wireless/08-get-weather/#sample-output","title":"Sample Output","text":"Here is the output on the Thonny shell of the first 16 temperature values:
"},{"location":"wireless/08-get-weather/#plotting-the-forecast-with-thonny-plot","title":"Plotting the Forecast with Thonny Plot","text":"Here is a plot of the temp and the \"feels like\" temp using the Thonny Plotter panel.
Each line has:
for each value plotted. There can be multiple values per line.
Here is that format:
Temperature: 63 Feels Like: 63.52\nTemperature: 62 Feels Like: 62.56\nTemperature: 70 Feels Like: 69.69\n
"},{"location":"wireless/08-get-weather/#sample-code","title":"Sample Code","text":"import network\nimport secrets\nimport urequests\nfrom utime import sleep, ticks_ms, ticks_diff\n\nprint('Connecting to WiFi Network Name:', secrets.SSID)\nwlan = network.WLAN(network.STA_IF)\nwlan.active(True)\n\nstart = ticks_ms() # start a millisecond counter\n\nif not wlan.isconnected():\n wlan.connect(secrets.SSID, secrets.PASSWORD)\n print(\"Waiting for connection...\")\n counter = 0\n while not wlan.isconnected():\n sleep(1)\n print(counter, '.', sep='', end='', )\n counter += 1\n\ndelta = ticks_diff(ticks_ms(), start)\nprint(\"Connect Time:\", delta, 'milliseconds')\nprint(\"IP Address:\", wlan.ifconfig()[0])\n\nbase = 'http://api.openweathermap.org/data/2.5/forecast?units=imperial&'\nlocation = '5037649' # twin cities\nurl = base + 'id=' + location + '&appid=' + secrets.appid\n\nweather = urequests.get(url).json()\nprint('City:', weather['city']['name'])\nprint('Timezone:', weather['city']['timezone'])\n\nmax_times = 16\n# for i in range(0, 39):\nfor i in range(0, max_times):\n print(weather['list'][i]['dt_txt'][5:13], ' ', sep='', end='')\nprint()\nfor i in range(0, max_times): \n print(round(weather['list'][i]['main']['temp']), ' ', end='')\n # print('feels like:', weather['list'][i]['main']['feels_like'])\n # print(weather['list'][i]['weather'][0]['description'])\n # print(weather['list'][i]['dt_txt'])\n\n# print(weather)\n
"},{"location":"wireless/08-get-weather/#displaying-predicted-temperatures-in-a-thonny-plot","title":"Displaying Predicted Temperatures in a Thonny Plot","text":"Thonny has a Plot object that you can use to display the relative temperature for the next 40 3-hour cycles.
To do this, we only need to print out the temperatures each on a separate line:
print()\nfor i in range(0, max_times): \n print(round(weather['list'][i]['main']['temp']))\n
"},{"location":"wireless/09-get-weather-display/","title":"Get Weather on an OLED Display","text":"In this lesson we will use the Pico W to get a weather forecast from a web service and display the current and forecasted temperatures on a 128x64 OLED display. The display above shows the city and current temperature on the top row of the display and then a plot of the predicted temperatures for the next 120 hours. The max temperature is 87 degrees and the minimum is 60 degrees Fahrenheit.
"},{"location":"wireless/09-get-weather-display/#calling-the-weather-api","title":"Calling the Weather API","text":"We will use the same method as in the previous lesson to get the weather forecast. However, in this lesson we will not just plot the temperature on the Thonny plot screen, we will use a OLED screen.
To do this we will need to write a function that will display the temperature data. The function will display the current location city name, the current temperature, and then draw a plot of the next 40 3-hour intervals. This lesson is a bit harder because we have to manually do all the work of scaling and plotting our data. This is all done for us in the prior Thonny plotting lab.
To achieve this we will need to scale the data to fit the display grid. We will reserve the top 10 pixels for the city and current temp and then draw the plot on the remaining 54 pixel high and 128 pixel wide plot region.
To scale the data we will need to find the min and max temperatures. These will also be displayed on the screen. The scale is then the ratio of the graph height over the temperature range. For example if the range of temperatures is 27 degrees and the height of the display is 54 we will need to scale the temperature vertically by 2.
The horizontal axis will go from 0 to 120. Since we have 40 points, each point will occur every 3rd pixel. We can then look at the difference between the current point and the next point to interpolate the dots between the two points.
The math can be a little confusing since higher temperatures are closer to the top of the display, so they have a lower Y coordinate value. Note the y_delta is subtracted from the next value:
y_delta = -round((y - y_next)/3) # a small positive or negative number for interpolation\n
def display_weather():\n global weather, city, current_temp\n oled.fill(0)\n min = 120\n max = -50\n for i in range(0, 39):\n temp = round(weather['list'][i]['main']['temp'])\n if temp < min:\n min = temp\n if temp > max:\n max = temp\n min = round(min)\n max = round(max)\n temp_range_height = max - min\n graph_height = 54\n scale = graph_height/temp_range_height\n print('min/max/range/scale:', min, max, temp_range_height, scale)\n\n # display city name, current temp, min and max\n oled.text(city + ': ' + str(current_temp), 0, 0, 1)\n oled.text(str(min), 0, 57, 1) # bottom left corner\n oled.text(str(max), 0, 10, 1) # under top row\n\n max_points = 39\n\n # graph temps for the next n periods\n print('Date Tmp TNx Y Y2 Del')\n for i in range(0, max_points):\n temp = round(weather['list'][i]['main']['temp'])\n x = i * 3 # scaled x\n y = 63 - round((temp - min)*scale)\n oled.pixel(x, y, 1)\n\n # now draw the next two points\n if i < max_points:\n temp_next = round(weather['list'][i+1]['main']['temp'])\n y_next = 63 - round((temp_next - min)*scale)\n y_delta = -round((y - y_next)/3) # a small positive or negative number\n\n # for debugging - fixed with columns with leading spaces\n print(weather['list'][i]['dt_txt'][0:13],\n '{: 3.3d}'.format(temp),\n '{: 3.3d}'.format(temp_next),\n '{: 3.3d}'.format(y),\n '{: 3.3d}'.format(y_next),\n '{: 3.3d}'.format(y_delta))\n\n # should be 1/3 of the way to the next point\n oled.pixel(x+1, y + y_delta, 1)\n # should be 2/3 of the way to the next point\n oled.pixel(x+2, y + 2*y_delta, 1)\n oled.show()\n
"},{"location":"wireless/09-get-weather-display/#the-main-loop","title":"The Main Loop","text":"The main loop repeats forever, pausing every hour between updates. It gets first calls the rest service, extracts the city name and current temperature and then calls the display_weather() function using global variables for the JSON file, city and current temperature.
while True:\n # globals: weather, city, current_temp\n weather = urequests.get(url).json()\n # print(weather)\n city = weather['city']['name']\n current_temp = round(weather['list'][0]['main']['temp'])\n display_weather()\n print('Going to sleep for one hour')\n sleep(3600) # sleep one hour\n
"},{"location":"wireless/09-get-weather-display/#full-sample-code","title":"Full Sample Code","text":"import network\nimport ssd1306\nimport secrets\nimport urequests\nfrom utime import sleep, ticks_ms, ticks_diff\n\n# startup\n\nprint('Connecting to WiFi Network Name:', secrets.SSID)\nwlan = network.WLAN(network.STA_IF)\nwlan.active(True)\n\nWIDTH = 128\nHEIGHT = 64\nSCK=machine.Pin(2)\nSDL=machine.Pin(3)\nspi=machine.SPI(0,baudrate=100000,sck=SCK, mosi=SDL)\nCS = machine.Pin(0)\nDC = machine.Pin(1)\nRES = machine.Pin(4)\noled = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)\noled.poweron()\n\ndef display_startup(counter):\n oled.fill(0)\n oled.text('Running startup', 0, 10, 1)\n oled.text('Connecting to', 0, 20, 1)\n oled.text(secrets.SSID, 0, 30, 1)\n oled.text(str(counter), 0, 40, 1)\n oled.show()\n\ndef display_status(counter):\n oled.fill(0)\n # display the network name\n oled.text('n:' + secrets.SSID, 0, 0, 1)\n\n # display the connection time\n oled.text('t:', 0, 10, 1)\n oled.text(str(connection_time)+ ' ms', 15, 10, 1)\n oled.show()\n\ndef display_weather():\n global weather, city, current_temp\n oled.fill(0)\n min = 120\n max = -50\n for i in range(0, 39):\n temp = round(weather['list'][i]['main']['temp'])\n if temp < min:\n min = temp\n if temp > max:\n max = temp\n min = round(min)\n max = round(max)\n temp_range_height = max - min\n graph_height = 54\n scale = graph_height/temp_range_height\n print('min/max/range/scale:', min, max, temp_range_height, scale)\n\n # display city name, current temp, min and max\n oled.text(city + ': ' + str(current_temp), 0, 0, 1)\n oled.text(str(min), 0, 57, 1) # bottom left corner\n oled.text(str(max), 0, 10, 1) # under top row\n\n max_points = 39\n\n # graph temps for the next n periods\n print('Date Tmp TNx Y Y2 Del')\n for i in range(0, max_points):\n temp = round(weather['list'][i]['main']['temp'])\n x = i * 3 # scaled x\n y = 63 - round((temp - min)*scale)\n oled.pixel(x, y, 1)\n\n # now draw the next two points\n if i < max_points:\n temp_next = round(weather['list'][i+1]['main']['temp'])\n y_next = 63 - round((temp_next - min)*scale)\n y_delta = -round((y - y_next)/3) # a small positive or negative number\n\n print(weather['list'][i]['dt_txt'][0:13],\n '{: 3.3d}'.format(temp),\n '{: 3.3d}'.format(temp_next),\n '{: 3.3d}'.format(y),\n '{: 3.3d}'.format(y_next),\n '{: 3.3d}'.format(y_delta))\n\n # should be 1/3 of the way to the next point\n oled.pixel(x+1, y + y_delta, 1)\n # should be 2/3 of the way to the next point\n oled.pixel(x+2, y + 2*y_delta, 1)\n oled.show()\n\nstart = ticks_ms() # start a millisecond counter\n\nif not wlan.isconnected():\n wlan.connect(secrets.SSID, secrets.PASSWORD)\n print(\"Waiting for connection...\")\n counter = 0\n while not wlan.isconnected():\n sleep(1)\n print(counter, '.', sep='', end='', )\n counter += 1\n display_startup(counter)\n\ndelta = ticks_diff(ticks_ms(), start)\n#print(\"Connect Time:\", delta, 'milliseconds')\n#print(\"IP Address:\", wlan.ifconfig()[0])\n\nbase = 'http://api.openweathermap.org/data/2.5/forecast?units=imperial&'\nlocation = '5037649' # Minneapolis, MN USA\nurl = base + 'id=' + location + '&appid=' + secrets.appid\n#print(url)\n\nmax_times = 39\n#for i in range(0, max_times): \n #print(' Temp: ', weather['list'][i]['main']['temp'])\n\nwhile True:\n # globals: weather, city, current_temp\n weather = urequests.get(url).json()\n # print(weather)\n city = weather['city']['name']\n current_temp = round(weather['list'][0]['main']['temp'])\n display_weather()\n print('Going to sleep for one hour.')\n sleep(3600) # sleep one hour\n
"},{"location":"wireless/09-get-weather-display/#sample-debugging","title":"Sample Debugging","text":"To allow you to see the math for the plotting and interpolation we have added a print that prints the temperatures and y coordinates in fixed with format. Here is an example of this output:
Connecting to WiFi Network Name: anndan-2.4\nmin/max/range/scale: 60 87 27 2.0\nDate Tmp Tnx Y Y2 Del\n2022-08-13 03 68 68 47 47 0\n2022-08-13 06 68 68 47 47 0\n2022-08-13 09 68 65 47 53 2\n2022-08-13 12 65 72 53 39 -5\n2022-08-13 15 72 83 39 17 -7\n2022-08-13 18 83 87 17 9 -3\n2022-08-13 21 87 82 9 19 3\n2022-08-14 00 82 71 19 41 7\n2022-08-14 03 71 66 41 51 3\n2022-08-14 06 66 62 51 59 3\n2022-08-14 09 62 60 59 63 1\n2022-08-14 12 60 71 63 41 -7\n2022-08-14 15 71 82 41 19 -7\n2022-08-14 18 82 85 19 13 -2\n2022-08-14 21 85 82 13 19 2\n2022-08-15 00 82 73 19 37 6\n2022-08-15 03 73 69 37 45 3\n2022-08-15 06 69 66 45 51 2\n2022-08-15 09 66 64 51 55 1\n2022-08-15 12 64 74 55 35 -7\n2022-08-15 15 74 83 35 17 -6\n2022-08-15 18 83 85 17 13 -1\n2022-08-15 21 85 81 13 21 3\n2022-08-16 00 81 75 21 33 4\n2022-08-16 03 75 70 33 43 3\n2022-08-16 06 70 69 43 45 1\n2022-08-16 09 69 66 45 51 2\n2022-08-16 12 66 66 51 51 0\n2022-08-16 15 66 79 51 25 -9\n2022-08-16 18 79 80 25 23 -1\n2022-08-16 21 80 70 23 43 7\n2022-08-17 00 70 65 43 53 3\n2022-08-17 03 65 64 53 55 1\n2022-08-17 06 64 63 55 57 1\n2022-08-17 09 63 61 57 61 1\n2022-08-17 12 61 72 61 39 -7\n2022-08-17 15 72 82 39 19 -7\n2022-08-17 18 82 85 19 13 -2\n2022-08-17 21 85 81 13 21 3\nGoing to sleep for one hour\n
"},{"location":"wireless/10-wifi-clock/","title":"WiFi Clock","text":"In the US, we can access a service called the \"Network Time Protocol\" or NTP. This service allows you to get the precise time using your WiFi network.
Calling the NTP service in MicroPython is simple once you have connected to your local wireless access point:
import ntptime\nntptime.host = 'us.pool.ntp.org'\nntptime.timeout = 10\nntptime.settime()\n
After running this code (and checking for errors) your internal Real-time Clock (RTC) will be synced to the NTP server.
The rest of this code does the work of adjusting the clock to your local timezone and correcting for any changes in daylight savings time in your area.
"},{"location":"wireless/10-wifi-clock/#the-config-file","title":"The Config File","text":"We always put local credentials in a separate file so it does not get checked into GitHub.
wifi_ssid = 'mywifinetworkname'\nwifi_pass = 'mypassword'\n
"},{"location":"wireless/10-wifi-clock/#full-program","title":"Full Program","text":"import ntptime, network\nfrom machine import RTC\nfrom utime import sleep, sleep_ms, time, localtime, mktime\nimport config\n\n# US Central\ntimeZone = -6\n# try one of these\nntptime.host = 'us.pool.ntp.org' #'time.nist.gov' #'pool.ntp.org'\nntptime.timeout = 10\n\ndef wifiConnect():\n wifi = network.WLAN(network.STA_IF)\n wifi.active(True)\n wifi.config(pm = 0xa11140) # disables wifi sleep mode\n if not wifi.isconnected():\n wifi.connect(config.wifi_ssid, config.wifi_pass)\n print('Connecting..', end='')\n max_wait = 10\n while max_wait > 0:\n if wifi.status() < 0 or wifi.status() >= 3: break\n sleep_ms(1000)\n print('.', end='')\n max_wait -= 1\n print()\n if wifi.status() != 3: print('Could not connect to wifi!')\n # print('Connected: ',wifi.isconnected(),'\\nIP: ',wifi.ifconfig()[0])\n sleep_ms(100)\n return wifi\n\n# daylight savings time\ndef dst():\n year, weekday = localtime()[0], localtime()[6]\n dst_start = mktime((year, 3, (8 - weekday) % 7 + 8, 2, 0, 0, 0, 0))\n dst_end = mktime((year, 11, (1 - weekday) % 7 + 1, 2, 0, 0, 0, 0))\n return dst_start <= time() < dst_end\n\ndef setRTC():\n timeset = False\n timetries = 0\n maxtries = 5\n while not timeset and timetries < maxtries:\n timetries += 1\n try:\n ntptime.settime() # update time from ntp server\n timeset = True\n except:\n print(f'NTP update attempt # {timetries} of {maxtries} failed!', 'Retrying in 15 seconds..' if timetries < maxtries else 'Check connection/config.')\n if timetries < maxtries: sleep_ms(15000)\n if timeset:\n sleep_ms(200)\n rtc = RTC()\n tz_offset = (timeZone + 1) * 3600 if dst() else timeZone * 3600\n #tz_offset = timeZone * 3600 # without daylight savings\n myt = localtime(time() + tz_offset)\n rtc.datetime((myt[0], myt[1], myt[2], myt[6], myt[3], myt[4], myt[5], 0))\n sleep_ms(200)\n dtime = rtc.datetime()\n timestr = '%2d:%02d%s' %(12 if dtime[4] == 0 else dtime[4] if dtime[4] < 13 else dtime[4] - 12, dtime[5], 'am' if dtime[4] < 12 else 'pm')\n datestr = f'{dtime[1]}/{dtime[2]}/{dtime[0] % 100}'\n # print('Time set to:', timestr, datestr)\n print(timestr, datestr)\n return True\n print('ERROR! Unable to update time from server!')\n return False\n\ndef update():\n success = False\n wifi = wifiConnect()\n sleep_ms(100)\n if wifi.isconnected():\n success = setRTC()\n sleep_ms(100)\n return wifi, success\n\nif __name__ == '__main__':\n while True:\n update()\n sleep(60)\n
"},{"location":"wireless/10-wifi-clock/#sample-output","title":"Sample Output:","text":"The console will display the following:
Connecting.........\nConnected: True \nIP: 10.0.0.118\nNTP update attempt # 1 of 5 failed! Retrying in 15 seconds..\n...\n7:41pm 9/10/23\n7:42pm 9/10/23\n7:43pm 9/10/23\n
Note that connecting to the NTP server failed the first time but worked on the second attempt.
This program assumes you have a config.py file in the same folder that the clock.py program runs.
"},{"location":"wireless/10-wifi-clock/#references","title":"References","text":"NikoKun
"},{"location":"wireless/11-advanced-labs/","title":"Advanced Wireless Labs","text":""},{"location":"wireless/11-advanced-labs/#secure-communications-with-https","title":"Secure Communications with HTTPS","text":"In our documentation we frequently refer to secure communications as using a \"Secure Sockets Layer\". Although the term \"SSL\" is common, we are actually using a protocol called Transport Layer Security (TLS).
TLS replaces SSL. It is an Internet Engineering Task Force (IETF) standard protocol that provides authentication, privacy and data integrity between two communicating computer applications.
Note
The standard Python request library does not yet support HTTPS on urequest on the Pico W. This is because there are additional tools that require us to use keys and certificates to validate data on an encrypted SSL stream.
See the MicroPython SSL/TLS Library
"},{"location":"wireless/11-advanced-labs/#testing-ssltls-on-standard-python","title":"Testing SSL/TLS on Standard Python","text":"import socket\nimport ssl\n\nhostname = 'www.python.org'\ncontext = ssl.create_default_context()\n\nwith socket.create_connection((hostname, 443)) as sock:\n with context.wrap_socket(sock, server_hostname=hostname) as ssock:\n print(ssock.version())\n
returns: TLSv1.3
This tells you that the standard Python socket libraries use the TLS v1.3 protocol.
"},{"location":"wireless/11-advanced-labs/#performance-monitoring-with-uiperf3","title":"Performance Monitoring with uiperf3","text":"iperf3 is a standard Python program for internet performance testing. For micropython, we have our own stripped down version called uiperf3.
IPerf3 uses a client-server testing model and measures networking performance between two system using various protocoos such as
It can also be used to measure total wireless throughput.
"},{"location":"wireless/11-advanced-labs/#upip-install","title":"UPIP Install","text":"upip.install(\"uiperf3\")\n
"},{"location":"wireless/11-advanced-labs/#testing-client-performance","title":"Testing Client Performance","text":"import uiperf3\n uiperf3.client('MY_IP_ADDRESS')\n
"},{"location":"wireless/12-display-clock/","title":"Display Clock","text":"This is similar to the WiFi Clock but it uses an OLED display to show the time and date.
from machine import Pin\nimport network\nimport ntptime\nimport ssd1306\n# where we keep the WiFi password\nimport secrets\nfrom utime import sleep, ticks_ms, ticks_diff\n\nWIDTH = 128\nHEIGHT = 64\nSCK=machine.Pin(2)\nSDL=machine.Pin(3)\nspi=machine.SPI(0,baudrate=100000,sck=SCK, mosi=SDL)\nCS = machine.Pin(4)\nDC = machine.Pin(5)\nRES = machine.Pin(6)\noled = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)\noled.poweron()\n\n# US Central\ntimeZone = -6\n# try one of these\nntptime.host = 'us.pool.ntp.org' #'time.nist.gov' #'pool.ntp.org'\nntptime.timeout = 10\n\ndef wifiConnect():\n wifi = network.WLAN(network.STA_IF)\n wifi.active(True)\n wifi.config(pm = 0xa11140) # disables wifi sleep mode\n if not wifi.isconnected():\n wifi.connect(config.wifi_ssid, config.wifi_pass)\n print('Connecting..', end='')\n max_wait = 10\n while max_wait > 0:\n if wifi.status() < 0 or wifi.status() >= 3: break\n sleep_ms(1000)\n print('.', end='')\n max_wait -= 1\n print()\n if wifi.status() != 3: print('Could not connect to wifi!')\n # print('Connected: ',wifi.isconnected(),'\\nIP: ',wifi.ifconfig()[0])\n sleep_ms(100)\n return wifi\n\n# daylight savings time\ndef dst():\n year, weekday = localtime()[0], localtime()[6]\n dst_start = mktime((year, 3, (8 - weekday) % 7 + 8, 2, 0, 0, 0, 0))\n dst_end = mktime((year, 11, (1 - weekday) % 7 + 1, 2, 0, 0, 0, 0))\n return dst_start <= time() < dst_end\n\ntimestr = ''\ndatestr = ''\ndtime = []\ndef setRTC():\n global timestr, datestr, dtime\n timeset = False\n timetries = 0\n maxtries = 5\n while not timeset and timetries < maxtries:\n timetries += 1\n try:\n ntptime.settime() # update time from ntp server\n timeset = True\n except:\n print(f'NTP update attempt # {timetries} of {maxtries} failed!', 'Retrying in 15 seconds..' if timetries < maxtries else 'Check connection/config.')\n if timetries < maxtries: sleep_ms(15000)\n if timeset:\n sleep_ms(200)\n rtc = RTC()\n tz_offset = (timeZone + 1) * 3600 if dst() else timeZone * 3600\n #tz_offset = timeZone * 3600 # without daylight savings\n myt = localtime(time() + tz_offset)\n rtc.datetime((myt[0], myt[1], myt[2], myt[6], myt[3], myt[4], myt[5], 0))\n sleep_ms(200)\n dtime = rtc.datetime()\n # set globals\n # hh:mm in 12hr am/pm format\n timestr = '%2d:%02d%s' %(12 if dtime[4] == 0 else dtime[4] if dtime[4] < 13 else dtime[4] - 12, dtime[5], 'am' if dtime[4] < 12 else 'pm')\n # mm/dd/yy\n datestr = f'{dtime[1]}/{dtime[2]}/{dtime[0] % 100}'\n # print('Time set to:', timestr, datestr)\n print(timestr, datestr)\n return True\n print('ERROR! Unable to update time from server!')\n return False\n\ndef update():\n success = False\n wifi = wifiConnect()\n sleep_ms(100)\n if wifi.isconnected():\n success = setRTC()\n sleep_ms(100)\n return wifi, success\n\ndef update_display():\n global timestr, datestr,dtime\n oled.fill(0)\n oled.text(timestr + ' ' + datestr, 0, 10, 1)\n oled.show()\n\nwhile True:\n update()\n update_display()\n sleep(60)\n
"},{"location":"wireless/20-display/","title":"Wireless With Display","text":"In this lesson, we will add a 128x64 OLED display to our Pico \"W\" to display network information.
The display we are using is a 2.42\" Diymore OLED display as describe in the Display Graphics.
# make a number scroll to the right and increment the number and move down one pixel\n# this test patten shows the device is working and will not burn any single pixel if it runs a long time\nfrom machine import Pin\nimport network\nimport ssd1306\nimport secrets\nfrom utime import sleep, ticks_ms, ticks_diff\n\nWIDTH = 128\nHEIGHT = 64\nSCK=machine.Pin(2)\nSDL=machine.Pin(3)\nspi=machine.SPI(0,baudrate=100000,sck=SCK, mosi=SDL)\nCS = machine.Pin(0)\nDC = machine.Pin(1)\nRES = machine.Pin(4)\noled = ssd1306.SSD1306_SPI(WIDTH, HEIGHT, spi, DC, RES, CS)\noled.poweron()\n\ndef mac_address_fmt():\n mac_addess = wlan.config('mac')\n s=\"\"\n for digit in range(0,5):\n s+=str(hex(mac_addess[digit]))[2:4] + ':'\n s+= hex(mac_addess[5])[2:4]\n return s\n\ndef display_startup(counter):\n oled.fill(0)\n oled.text('Running startup', 0, 10, 1)\n oled.text('Connecting to', 0, 20, 1)\n oled.text(secrets.SSID, 0, 30, 1)\n oled.text(str(counter), 0, 40, 1)\n oled.show()\n\ndef display_status(counter):\n oled.fill(0)\n # display the network name\n oled.text('n:' + secrets.SSID, 0, 0, 1)\n\n # display the connection time\n oled.text('t:', 0, 10, 1)\n oled.text(str(connection_time)+ ' ms', 15, 10, 1)\n\n # display the MAC address\n oled.text(mac_address_fmt(), 0, 20, 1)\n\n # display the IP address\n oled.text('ip:' + wlan.ifconfig()[0], 0, 30, 1)\n oled.text('c:' + str(counter), 0, 40, 1)\n oled.show()\n# startup\nled = Pin(\"LED\", Pin.OUT)\nled.on()\n\nstart = ticks_ms() # start a millisecond counter\n\nprint('Connecting to WiFi Network Name:', secrets.SSID)\nwlan = network.WLAN(network.STA_IF)\nwlan.active(True)\n\nif not wlan.isconnected():\n # this should normally take 3-5 seconds...\n wlan.connect(secrets.SSID, secrets.PASSWORD)\n print(\"Waiting for connection...\")\n counter = 0\n display_startup(counter)\n while not wlan.isconnected():\n sleep(1)\n counter += 1\n led.toggle()\n display_startup(counter)\n print(counter, '.', sep='', end='')\n\nconnection_time = ticks_diff(ticks_ms(), start)\nmac_addess = wlan.config('mac')\nprint('Connected to', secrets.SSID)\nprint('Total connect milliseconds:', connection_time)\n\ncounter = 0\nwhile True:\n led.toggle()\n counter += 1\n display_status(counter)\n sleep(1)\n print(counter, 'listening on', wlan.ifconfig()[0])\n
"}]}
\ No newline at end of file
diff --git a/sims/breadboard/index.html b/sims/breadboard/index.html
index f1afbe75..645f1f69 100644
--- a/sims/breadboard/index.html
+++ b/sims/breadboard/index.html
@@ -1129,7 +1129,7 @@
-
+
diff --git a/sims/index.html b/sims/index.html
index 3bcaaae2..aff5e126 100644
--- a/sims/index.html
+++ b/sims/index.html
@@ -1129,7 +1129,7 @@
-
+
@@ -1316,7 +1316,7 @@
Run the Category Colors MicroSim
I'm glad to hear that you're pleased with the results so far! Creating a taxonomy to classify your 197 learning concepts will help organize the curriculum, ensure comprehensive coverage, and facilitate a logical progression of topics. Below is a suggested taxonomy with **10 classifications**, each accompanied by a description and examples of relevant concepts from your list.