Harness the power of electromagnetism to talk to the spirits this Halloween with an FPGA-powered Ouija board with webserver running to allow for users to activate the planchet to move.
After binging my favorite horror movies and podcasts when the Halloween season began this year, I was stuck on the idea of Ouija boards. I wanted a way to use one hands free, which initially made me think of using an FPGA with wireless capabilities running a webserver for users to connect to. This paired with electromagnets activated at the right time to move the planchet, would allow for a user connected to the same Wi-Fi network as the FPGA to interface with a webpage to control the planchet's movements.
The Zynq-based MiniZed jumped out at me as the ideal FPGA board to use for this project since it is compact, has a built-in Murata "Type 1DX" wireless module with 802.11b/g/n Wi-Fi and Bluetooth 4.1, and can be powered via its USB ports. It also has an 8GB eMMC memory chip that can be reprogrammed over a Wi-Fi connection, so I feel like it's an ideal option for leaving in an enclosed space you may not want to access on a regular basis. Which is the exact use case for this project where the FPGA and accompanying circuitry will be enclosed in what is essentially a gameboard-shaped box.
To outline everything, there are four main steps to this project:
- The FPGA design on the MiniZed (the webserver and GPIO driver scripts)
- The build of the gameboard box with the Ouija board design on the top lid.
- The circuit layout of the electromagnets and mechanical parts inside the gameboard box.
- Updating the GPIO driver script with the timing of when to turn on the electromagnets and motors based on the user input on the webpage.
I'm starting the FPGA design part of this project with the PetaLinux project that I created from the Avnet BSP for Vivado/Vitis/PetaLinux version 2019.2 then modified to add the Arduino header GPIO. However, only 128MB of the eMMC is partitioned for the initramfs root filesystem as the default configuration of the embedded Linux image on the MiniZed out of the box. This is not enough space to install all of the Python packages necessary for this project, on top of storing all of the project files. Any changes to an initramfs root filesystem are also not persistent between power cycles. Thus, the PetaLinux project needs to be reconfigured to boot from an ext4 filesystem stored in a second partition that has access to the rest of the 8GB memory of the eMMC chip. This also means that the eMMC on the MiniZed needs to be repartitioned to have a boot partition formatted as FAT and a root filesystem partition formatted as ext4.
Before repartitioning and reformatting the eMMC, we need to add a few library files to the current embedded Linux version running in initramfs in order to format the eMMC, specifically the libraries to format a partition in ext4, which is in the e2fsprogs utility set. I tried adding these filesystem packages and just generating a new boot from QSPI flash only image, but it made the kernel too big to fit in the default partition of the MiniZed, so I had use this hack workaround:
- Build the project with e2fsprogs enabled in the rootfs to get the ext4 file system formatting files needed.
- Exact the rootfs.tar.gz to a temporary location on my computer.
- Copy the needed library files from /sbin and /lib to a flash drive to transfer to the MiniZed.
To start, change directories into the Petalinux project and open the root filesystem configuration editor:
cd ./minized/minized_emmc_enhanced_2019_2
petalinux-config -c rootfs
Navigate to Filesystem Packages > base > e2fsprogs and enable all of the package options:
Build the PetaLinux project:
petalinux-build
After the build completes, create a temporary directory on your PC to exact the root filesystem to so you can copy over the needed sbin & lib files to a flash drive (taking all of the library dependencies to avoid an Invalid ELF Header message).
mkdir -p <project path>/image/linux/tmp_rootfs
tar -xvf <project path>/image/linux/rootfs.tar.gz
cp <project path>/image/linux/tmp_rootfs/lib/libcom_err.so /dev/sda1
cp <project path>/image/linux/tmp_rootfs/lib/libcom_err.so.2 /dev/sda1
cp <project path>/image/linux/tmp_rootfs/lib/libcom_err.so.2.1 /dev/sda1
cp <project path>/image/linux/tmp_rootfs/lib/libe2p.so /dev/sda1
cp <project path>/image/linux/tmp_rootfs/lib/libe2p.so.2 /dev/sda1
cp <project path>/image/linux/tmp_rootfs/lib/libe2p.so.2.3 /dev/sda1
cp <project path>/image/linux/tmp_rootfs/lib/libext2fs.so /dev/sda1
cp <project path>/image/linux/tmp_rootfs/lib/libext2fs.so.2 /dev/sda1
cp <project path>/image/linux/tmp_rootfs/lib/libext2fs.so.2.4 /dev/sda1
cp <project path>/image/linux/tmp_rootfs/sbin/mke2fs /dev/sda1
cp <project path>/image/linux/tmp_rootfs/sbin/mkfs.ext4 /dev/sda1
Connect a second USB cable to AUX power port on the Minized, plug the USB flash drive into it, and copy the library (lib) files over to the /lib directory and the make filesystem files (mkfs) to the /sbin directory.
root@MiniZed:~# cp /dev/sda1/libcom_err.so /lib
root@MiniZed:~# cp /dev/sda1/libcom_err.so.2 /lib
root@MiniZed:~# cp /dev/sda1/libcom_err.so.2.1 /lib
root@MiniZed:~# cp /dev/sda1/libe2p.so /lib
root@MiniZed:~# cp /dev/sda1/libe2p.so.2 /lib
root@MiniZed:~# cp /dev/sda1/libe2p.so.2.3 /lib
root@MiniZed:~# cp /dev/sda1/libext2fs.so /lib
root@MiniZed:~# cp /dev/sda1/libext2fs.so.2 /lib
root@MiniZed:~# cp /dev/sda1/libext2fs.so.2.4 /lib
root@MiniZed:~# cp /dev/sda1/mke2fs /sbin
root@MiniZed:~# cp /dev/sda1/mkfs.ext4 /sbin
Once the files are copied onto the MiniZed, remove the flash drive and return to the PetaLinux project on your PC. Go back to the root filesystem editor, and disable the e2fsprogs packages, then rebuild the project.
petalinux-build
Moving on to reconfigurations of the Linux image to boot from an ext4 type external filesystem, open the PetaLinux hardware configuration editor:
petalinux-config
Navigate to the Image Packaging Configuration tab and start by changing the Root filesystem type to EXT (SD/eMMC/QSPI/SATA/USB). Then update the device node of the SD device to /dev/mmcblk1p2, which as you'll see, will match the name of the ext4 partition in the eMMC we will create in later steps.
Edit the Root filesystem formats to only include ext4. Then finally, disable the TFTP boot option.
Save & exit the PetaLinux hardware configuration editor.
I've covered in previous projects how to create a custom webserver to run on the MiniZed, as well as how to drive electromagnets with the MiniZed's Arduino header GPIO. I decided to take the source files from those projects and package them into a user package in the root filesystem by adding a custom recipe to the PetaLinux project. This way they will be always available in this new embedded Linux image on the MiniZed.
Create a new recipe directory in the <PetaLinux project path>/project-spec/meta-user/ directory:
mkdir -p <PetaLinux project path>/project-spec/meta-user/recipe-knitronics
Create the specific package directory within the recipe directory:
mkdir -p <PetaLinux project path>/project-spec/meta-user/recipe-knitronics/gameboard
Create a directory called 'files' and a bitbake file with the same name as the package directory:
mkdir -p <PetaLinux project path>/project-spec/meta-user/recipe-knitronics/gameboard/files
nano <PetaLinux project path>/project-spec/meta-user/recipe-knitronics/gameboard/gameboard.bb
A bitbake file serves the same role in the Yocto toolset (which is what PetaLinux is based on) as a Makefile does for general build automation on a PC. The bitbake specifies how the files in the recipe will be installed in the embedded Linux image. In this case, the bitbake is super simple since all's that needs to be done is copy the project source files for the webserver and controlling the GPIO into the root directory of the embedded Linux image.
For simplicity of keeping the code short in the bitbake, I compressed the webserver directory into a tarball and just specified that as what would be copied into the root directory of the embedded Linux image.
Add the following code to the bitbake to place all of the files specified in the 'files' folder in the root directory.
SUMMARY = "MiniZed Gameboard"
DESCRIPTION = "Files for MiniZed webserver-enabled gameboard"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
#FILESEXTRAPATHS_prepend := "${THISDIR}/files:"
SRC_URI += "file://gpio_driver.py \
file://gpio_setup.py \
file://gameboard-webserver.tar.xz;unpack=0 \
"
do_install() {
install -d ${D}/home/root/
cp -r ${WORKDIR}/gpio_driver.py ${D}/home/root/
cp -r ${WORKDIR}/gpio_setup.py ${D}/home/root/
install -m 0755 ${WORKDIR}/gameboard-webserver.tar.xz ${D}/home/root/
}
FILES_${PN} = " \
/home/root/gpio_driver.py \
/home/root/gpio_setup.py \
/home/root/gameboard-webserver.tar.xz \
"
After adding all of the source files to the files directory in the new recipe and completing the bitbake file, edit the user-rootfsconfig in <PetaLinux project path>/project-spec/meta-user/conf to add the following line:
CONFIG_gameboard
This makes the root filesystem editor aware that the new recipe is available as an option for a user defined package. To add it to the next build, open the root filesystem editor:
petalinux-config -c rootfs
Navigate to the user packages, and you'll see the new package as an option to enable.
Enable the gameboard package then save & close the root filesystem editor.
Edit the wpa_supplicant.conf file in <PetaLinux project path>/project-spec/meta-user/recipes-bsp/minized-misc/files directory with the Wi-Fi SSID and password of the network you want to connect the MiniZed to.
Finally, build the PetaLinux project:
petalinux-build
Once the build is completed, create the new boot binary image using the petalinux-package command:
petalinux-package --boot --fsbl <project directory>/images/linux/zynq_fsbl.elf --fpga . <project directory>/images/linux/system.bit --u-boot --force
Side note: PetaLinux 2020.1 has had a major upgrade in the way it packages the root filesystem and boot binary for an SD device so while I think this method is fairly backwards compatible with older versions of PetaLinux, I don't think it would work with 2020.1.
From the <PetaLinux project path>/image/linux directory, copy BOOT.BIN, image.ub, & rootfs.ext4 to a USB flash drive.
Again, connect a second USB cable to AUX power port on the MiniZed and plug the flash drive into it.
Fundamentally, I followed the same process as I would prepping an SD card for an FPGA to boot and run its filesystem from, treating the eMMC as the SD card.
Unmount the eMMC and run the fdisk utility on it:
root@MiniZed:~# umount /dev/mmcblk1p1
root@MiniZed:~# fdisk /dev/mmcblk1
Resize the default partition making mmcblk1p1 for the boot partition of the eMMC 512MB and mmcblk1p2 the remaining size of the 8GBs for the root filesystem.
Command (m for help): d
Selected partition 1
Command (m for help): n
Command action
e extended
p primary partition (1-4)
p
Partition number (1-4): 1
First cylinder (1-232448, default 1): 1
Last cylinder or +size or +sizeM or +sizeK (122-232448, default 232448): +512M
Command (m for help): a
Partition number (1-4): 1
Command (m for help): p
Disk /dev/mmcblk1: 7616 MB, 7616856064 bytes
4 heads, 16 sectors/track, 232448 cylinders
Units = cylinders of 64 * 512 = 32768 bytes
Device Boot Start End Blocks Id System
/dev/mmcblk1p1 * 1 15747 500032 83 Linux
Command (m for help): n
Command action
e extended
p primary partition (1-4)
p
Partition number (1-4): 2
First cylinder (1-232448, default 1): 15748
Last cylinder or +size or +sizeM or +sizeK (15748-232448, default 232448): Using default value 232448
Command (m for help): p
Disk /dev/mmcblk1: 7616 MB, 7616856064 bytes
4 heads, 16 sectors/track, 232448 cylinders
Units = cylinders of 64 * 512 = 32768 bytes
Device Boot Start End Blocks Id System
/dev/mmcblk1p1 * 1 15747 500032 83 Linux
/dev/mmcblk1p2 15748 232448 6934432 83 Linux
Command (m for help): w
Type 'w' to write the new partitions to the disk.
After exiting the fdisk utility, format the second partition (mmcblk1p2) as ext4:
root@MiniZed:~# mkfs.ext4 -L root /dev/mmcblk1p2
Create a directory to mount the partition to.
root@MiniZed:~# root@MiniZed:~# mkdir /mnt/emmc_rootfs
root@MiniZed:~# mount /dev/mmcblk1p2 /mnt/emmc_rootfs
Copy the compressed root filesystem image (rootfs.ext4) to the directory.
root@MiniZed:~# cp /run/media/sda1/linux_ver1/rootfs.ext4 /mnt/emmc_rootfs/
Create a temporary directory to mount to in case the extra space is needed then copy the root filesystem contents back to the first directory.
root@MiniZed:~# mkdir /mnt/ext4
root@MiniZed:~# mount /mnt/emmc_rootfs/rootfs.ext4 /mnt/ext4/ -o loop
root@MiniZed:~# cp -rf /mnt/ext4/* /mnt/emmc_rootfs/
Unmount the temporary directory and delete the compressed root filesystem image.
root@MiniZed:~# umount /mnt/ext4/
root@MiniZed:~# rm /mnt/emmc_rootfs/rootfs.ext4
Flash the QSPI with the new boot binary image:
root@MiniZed:~# flashcp /run/media/sda1/linux_ver1/BOOT.BIN /dev/mtd0
Format the first partition (mmcblk1p1) as FAT:
root@MiniZed:~# mkfs.vfat -F 32 -n BOOT /dev/mmcblk1p1
Create a directory to mount the partition to.
root@MiniZed:~# mkdir /mnt/emmc_BOOT
root@MiniZed:~# mount /dev/mmcblk1p1 /mnt/emmc_BOOT/
Copy the kernel image and boot binary image to the boot partition.
root@MiniZed:~# cp /run/media/sda1/linux_ver1/BOOT.BIN /mnt/emmc_BOOT/
root@MiniZed:~# cp /run/media/sda1/linux_ver1/image.ub /mnt/emmc_BOOT/
Fully shutdown the MiniZed then use the reset button to power it back on.
root@MiniZed:~# shutdown -h now
Once powered back on, run the Wi-Fi connection script to get the MiniZed on the wireless network specified in wpa_supplicant.conf:
root@MiniZed:~# wifi.sh
Run the GPIO setup Python script to export all of the pins on the Arduino header of the MiniZed so they are available for the GPIO driver script to use.
root@MiniZed:~# python3 ./gpio_setup.py
Unzip the webserver tarball to the root directory and start it by running the main Python script:
root@MiniZed:~# tar -xvf gameboard-webserver.tar.xz ./
root@MiniZed:~# python3 ./gameboard-webserver/webserver.py
Test the webserver by connecting to the MiniZed's assign IP in a web browser on your PC or phone.
Now, any device connected to the same Wi-Fi network can connect to the gameboard's homepage to key in the next phrase for the board.
Building the Gameboard BoxWhen I first had the idea for this project, building the enclosure was what I had the least experience with so I decided to just wander around my local art supply store to see what some of my options were. Since the entire project was based around the use of electromagnets, I knew I wanted something thin and non-ferrous, but also sturdy. I had been leaning towards wood, and the choice was solidified when I came across an entire section of craft wood in all shapes and sizes at the art supply store.
I wanted to leave enough room in the box to comfortably fit all of the gameboard's guts inside and be able to add future updates/fixes, but also have it still at least somewhat resemble the dimensions of a gameboard. So I decided about a 1" interior thickness with only the top and bottom plywood sheets adding to the exterior thickness would be the best middle ground. I also wanted to minimized the amount of cuts I'd have to make since woodworking is a new found skill for me, so I chose to keep the gameboard box the dimensions that the plywood sheets came in, which was 12"x24".
The wood & supplies I ended up going with were:
- 24" x 1" x 1/2" bass wood planks - (x2) to act as support struts for the lid of the box since that plywood sheet is so thin as to interfere the least with the electromagnets.
- 24" x 1" x 1" bass wood planks - (x4) for the sides of the gameboard box.
- 24" x 12" x 1/4" craft plywood sheet - (x1) as the bottom of the gameboard box.
- 24" x 12" x 1/32" birch plywood sheet - (x1) for the lid of the gameboard box with the Ouija board design.
- Mitre Box for cutting down the bass wood planks for the gameboard box sides and the supporting struts inside the box.
- Elmer's Carpenter's Wood Glue
- Metallic film tape to decorate the exterior of the box.
- Sandpaper block to smooth any edges and surfaces.
- Screws to attach the lid, but also have the option to remove in the future for updates/fixes.
I started by using the mitre box to cut the 24" x 1" x 1" bass wood planks to have 45-degree angles at the ends to create a frame for the gameboard box. I then used wood glue to glue the frame together. I then glued the frame to the 24" x 12" x 1/4" craft plywood sheet creating the bottom half of the box.
I wanted to get some letter stamps and paint to re-create the Ouija board design by hand on the 24" x 12" x 1/32" birch plywood sheet, but after brief trial and error, it was quickly apparent that I didn't have the artistic skill to make it look the way I wanted. So I ran to my local shipping store to print out an 11"x 17" tabloid sized Ouija board image to glue to the top sheet of the gameboard (the 1/32-inch thick plywood sheet).
To frame in the image, I used the awesome metallic gold film tape I found at the art supply store. I quickly learned though that my poor scissors were extremely dull though, this tape is pretty tough stuff.
I broke out my beloved Dymo label maker to add a "Knitronics" and "Halloween 2020" label.
I also wrapped the edges of the base of the gameboard in the same gold tape.
Circuit Layout in the Gameboard BoxFor the guts of the gameboard, I had intended for the electromagnets to be strong enough to be the only somewhat mechanical parts necessary. I purchased 1-inch diameter blank metal strike plates that were neodymium magnets (not permanent magnets, but were extremely ferrous) to attach to the feet of the planchet for the electromagnets to pull it with.
However I found that none of hobbyist sized (aka reasonably sized) electromagnets were strong enough to attract the planchet from more than half a centimeter away.
This lead me to implement a DC motor with belt & pulley system that pulled the electromagnet around instead. By using a bipolar motor with a belt, I attached the electromagnet to the bottom side of the belt and the motor was able to spin in either direction to pull the electromagnet back and forth.
I found some mini bi-polar stepper motors from Adafruit that were perfect for the job, and I ended up using 3D printer replacement parts for the pulley wheels and belt.
To drive the electromagnets, I used a simple Darlington driver circuit (using an ULN2803) to allow the GPIO of the MiniZed to turn them on and off while protecting the IO of the MiniZed from the reverse current that is generated when power is removed from an electromagnet. For the stepper motors, I used an L293N H-bridge IC.
The GPIO driver script that is called by the webserver (spell_phrase.py) is ultimately responsible for the logic of translating the input phrase in the form of a string from the webpage to the corresponding GPIO toggling to control the electromagnets and the motor.
A last minute short only left me with one row working, so that is the code I've included here. Ultimately the script consists of a case statement in the main function for the accessible characters in the first row of the Ouija board (A - M), with four other functions. The four other functions are the GPIO controller functions for the electromagnets and motor.
The function for the electromagnets treats each GPIO pin of the MiniZed as a file object to open the GPIO number, export the pin, then set it the pin's direction to output.
The motor forward direction and reverse direction functions also treat each GPIO pin as a file object to open the GPIO number, export the pin, then set it the pin's direction to output. Since a stepper motor divides its a full rotation into a number of equal steps, this translates to a specific sequence of applying current to one of the four phase inputs of the stepper at a time; which means a specific sequence of toggling the 4 GPIO inputs of the H-bridge.
Since I'm using a 4-pin stepper motor, the sequence to drive it one revolution forward is as follows:
Step 1:
- Pin0(1)
- Pin1(0)
- Pin2(1)
- Pin3(0)
Step 2:
- Pin0(0)
- Pin1(1)
- Pin2(1)
- Pin3(0)
Step 3:
- Pin0(0)
- Pin1(1)
- Pin2(0)
- Pin3(1)
Step 4:
- Pin0(1)
- Pin1(0)
- Pin2(0)
- Pin3(1)
Driving the stepper motor in the opposite direction is simply a matter of repeating this sequence backwards.
Ultimately I measured that it took about 381 revolutions of the stepper to move the electromagnet attached to the belt the entire length of the row of letters from A to M, with approximately 30 revolutions between letters. This means that repeating the above sequence 30 times will move the electromagnet (and ultimately the planchet that is attracted to it when it is turned on) from one letter to the next neighboring letter.
Overall the script takes the input phrase from the webpage front end and parses it into individual characters, which it then passes through a giant if else statement (the Python version of a case statement) where each character is assigned a numeric value. The value from the previous character is saved, and the difference between the two numeric values times the number of stepper revolutions needed to move between neighboring letters provides the distance and direction data needed.
Code for spell_phrase.py:
import os
import re
import sys
import time
import requests
import subprocess
emag0 = '/sys/class/gpio/gpio999/'
emag1 = '/sys/class/gpio/gpio1000/'
motor0a = '/sys/class/gpio/gpio1001/'
motor0c = '/sys/class/gpio/gpio1002/'
motor0b = '/sys/class/gpio/gpio1003/'
motor0d = '/sys/class/gpio/gpio1004/'
def emag_on(emag):
emagPath = emag + 'value'
emagFile = open(emagPath,'w')
emagFile.write('1')
emagFile.close()
def emag_off(emag):
emagPath = emag + 'value'
emagFile = open(emagPath,'w')
emagFile.write('0')
emagFile.close()
def motor0_forward(distance):
for x in range(1, distance):
motorPath0a = motor0a + 'value'
motorPath0c = motor0c + 'value'
motorPath0b = motor0b + 'value'
motorPath0d = motor0d + 'value'
motorFile0a = open(motorPath0a,'w')
motorFile0c = open(motorPath0c,'w')
motorFile0b = open(motorPath0b,'w')
motorFile0d = open(motorPath0d,'w')
motorFile0a.write('1')
motorFile0c.write('0')
motorFile0b.write('1')
motorFile0d.write('0')
motorFile0a.close()
motorFile0c.close()
motorFile0b.close()
motorFile0d.close()
motorFile0a = open(motorPath0a,'w')
motorFile0c = open(motorPath0c,'w')
motorFile0b = open(motorPath0b,'w')
motorFile0d = open(motorPath0d,'w')
motorFile0a.write('0')
motorFile0c.write('1')
motorFile0b.write('1')
motorFile0d.write('0')
motorFile0a.close()
motorFile0c.close()
motorFile0b.close()
motorFile0d.close()
motorFile0a = open(motorPath0a,'w')
motorFile0c = open(motorPath0c,'w')
motorFile0b = open(motorPath0b,'w')
motorFile0d = open(motorPath0d,'w')
motorFile0a.write('0')
motorFile0c.write('1')
motorFile0b.write('0')
motorFile0d.write('1')
motorFile0a.close()
motorFile0c.close()
motorFile0b.close()
motorFile0d.close()
motorFile0a = open(motorPath0a,'w')
motorFile0c = open(motorPath0c,'w')
motorFile0b = open(motorPath0b,'w')
motorFile0d = open(motorPath0d,'w')
motorFile0a.write('1')
motorFile0c.write('0')
motorFile0b.write('0')
motorFile0d.write('1')
motorFile0a.close()
motorFile0c.close()
motorFile0b.close()
motorFile0d.close()
def motor0_backward(distance):
for x in range(1, distance):
motorPath0a = motor0a + 'value'
motorPath0c = motor0c + 'value'
motorPath0b = motor0b + 'value'
motorPath0d = motor0d + 'value'
motorFile0a = open(motorPath0a,'w')
motorFile0c = open(motorPath0c,'w')
motorFile0b = open(motorPath0b,'w')
motorFile0d = open(motorPath0d,'w')
motorFile0a.write('1')
motorFile0c.write('0')
motorFile0b.write('0')
motorFile0d.write('1')
motorFile0a.close()
motorFile0c.close()
motorFile0b.close()
motorFile0d.close()
motorFile0a = open(motorPath0a,'w')
motorFile0c = open(motorPath0c,'w')
motorFile0b = open(motorPath0b,'w')
motorFile0d = open(motorPath0d,'w')
motorFile0a.write('0')
motorFile0c.write('1')
motorFile0b.write('0')
motorFile0d.write('1')
motorFile0a.close()
motorFile0c.close()
motorFile0b.close()
motorFile0d.close()
motorFile0a = open(motorPath0a,'w')
motorFile0c = open(motorPath0c,'w')
motorFile0b = open(motorPath0b,'w')
motorFile0d = open(motorPath0d,'w')
motorFile0a.write('0')
motorFile0c.write('1')
motorFile0b.write('1')
motorFile0d.write('0')
motorFile0a.close()
motorFile0c.close()
motorFile0b.close()
motorFile0d.close()
motorFile0a = open(motorPath0a,'w')
motorFile0c = open(motorPath0c,'w')
motorFile0b = open(motorPath0b,'w')
motorFile0d = open(motorPath0d,'w')
motorFile0a.write('1')
motorFile0c.write('0')
motorFile0b.write('1')
motorFile0d.write('0')
motorFile0a.close()
motorFile0c.close()
motorFile0b.close()
motorFile0d.close()
def spell_phrase(phrase):
char_num = 1
emag_on(emag0)
if(phrase!=None):
[char for char in phrase]:
if char == 'a' or char == 'A':
next_char_num = 1
distance = (char_num - next_char_num)*30
if distance == 0 or distance < 0:
# already at edge of board
else:
motor0_forward(distance)
elif char == 'b' or char == 'B':
next_char_num = 2
distance = (char_num - next_char_num)*30
if distance < 0:
motor0_backward(distance)
else:
motor0_forward(distance)
elif char == 'c' or char == 'C':
next_char_num = 3
distance = (char_num - next_char_num)*30
if distance < 0:
motor0_backward(distance)
else:
motor0_forward(distance)
elif char == 'd' or char == 'D':
next_char_num = 4
distance = (char_num - next_char_num)*30
if distance < 0:
motor0_backward(distance)
else:
motor0_forward(distance)
elif char == 'e' or char == 'E':
next_char_num = 5
distance = (char_num - next_char_num)*30
if distance < 0:
motor0_backward(distance)
else:
motor0_forward(distance)
elif char == 'f' or char == 'F':
next_char_num = 6
distance = (char_num - next_char_num)*30
if distance < 0:
motor0_backward(distance)
else:
motor0_forward(distance)
elif char == 'g' or char == 'G':
next_char_num = 7
distance = (char_num - next_char_num)*30
if distance < 0:
motor0_backward(distance)
else:
motor0_forward(distance)
elif char == 'h' or char == 'H':
next_char_num = 8
distance = (char_num - next_char_num)*30
if distance < 0:
motor0_backward(distance)
else:
motor0_forward(distance)
elif char == 'i' or char == 'I':
next_char_num = 9
distance = (char_num - next_char_num)*30
if distance < 0:
motor0_backward(distance)
else:
motor0_forward(distance)
elif char == 'j' or char == 'J':
next_char_num = 10
distance = (char_num - next_char_num)*30
if distance < 0:
motor0_backward(distance)
else:
motor0_forward(distance)
elif char == 'k' or char == 'K':
next_char_num = 11
distance = (char_num - next_char_num)*30
if distance < 0:
motor0_backward(distance)
else:
motor0_forward(distance)
elif char == 'l' or char == 'L':
next_char_num = 12
distance = (char_num - next_char_num)*30
if distance < 0:
motor0_backward(distance)
else:
motor0_forward(distance)
elif char == 'm' or char == 'M':
next_char_num = 13
distance = (char_num - next_char_num)*30
if distance == 0 or distance < 0:
motor0_backward(distance)
else:
# already at edge of baord
else:
motor0_backward(1)
motor0_forward(1)
char_num = next_char_num
else:
phrase = "noPhrase"
emag_off(emag0)
return phrase
def main(arg):
phraseOut = spell_phrase(arg)
if __name__ == "__main__":
main(sys.argv[1])
Project SummaryI hit a few roadblocks with this project before getting it to the exact point I wanted this Halloween, the current version relies on only taking input strings consisting of letters A - M. I would also like to find quieter stepper motors, but it's still a fun prop this year! I'm really happy with how the MiniZed fit into this project mechanically, and I think it will be my go-to board for future prop-building projects.
I've made a list and I'm going to keep upgrading the board! I think this will be a staple proper for me every Halloween now. I also learned so much from the mechanical design and enclosure fabrication that I already want upgrades I want to make. Keep an eye out for next year!
Future updates:
- Modify the webpage to launch at boot-up.
- Have the GPIO setup Python script either run at boot up or have the webserver run it at first launch before accepting the first input phrase.
- Fix the short to get the other 4 rows working.
Comments