While looking into Halloween themed projects for this year, I came up with an idea for a project implementing electromagnets driven via the digital I/O of an FPGA. A solenoid peripheral such as an electromagnet is a common peripheral for hobby projects (other examples of solenoids include DC motors, transducers, servos, etc.). Solenoids stand apart from other peripherals since they are a coil, they need a driver circuit with kick-back diodes to protect against the flyback voltage spike as the generated magnetic field collapses when the driving current/voltage is removed. This project tutorial will cover the hardware setup and implementation of driving electromagnets via the Arduino header GPIO on the Zynq-based MiniZed FPGA development board.
A commonly used kick-back protection circuit for solenoids is a high-current darlington driver. Luckily this circuit is easily implemented due to the fact that it comes packaged in IC chips with varying numbers of channels.
A darlington driver circuit gets its name from the darlington transistor circuit it is comprised of. Consisting of two bipolar transistors, the darlington transistor circuit is capable of outputting a high gain in current compared to its input given the second transistor is amplifying the output of the first transistor. This effectively doubles the gain of the circuit, making it ideal for amplifying the digital I/O output voltage/current of an FPGA or microcontroller to power a solenoid peripheral such as an electromagnet. A darlington driver circuit adds a diode to the output of the collector of the darlington transistor to create the flyback voltage spike protection necessary.
I chose to use a ULN2803A 8 Channel Darlington Driver IC chip comprised of eight NPN darlington pairs to drive 5V electromagnets with 2.5 kg of holding force I found on Adafruit.
I chose these particular electromagnets due to their 5V reference voltage requirement since the Arduino header of the MiniZed provides a 5V reference (important note: the I/O on the MiniZed are not 5V tolerant they are only 3.3V, there is just the 5V reference on the JX1 header).
Using Fritzing, I created a circuit diagram for the darlington driver circuit showing how to connect the terminals of the electromagnets to the darlington IC and then to the respective digital I/O pin.
Neither the MiniZed nor electromagnets were in the Fritzing parts library and I just wanted a clear schematic and block diagram for this tutorial. I didn't think it was worth the time to create the parts in the Fritzing library so I made a couple of substitutions in the schematic that didn't make any functional difference.
I substituted in the Arduino rev3 for the MiniZed since that's the pinout the MiniZed's I/O is based on. I also substituted DC motors for the electromagnets since the hookup is the exact same.
To supply the 5V on the Arduino shield header pins of the MiniZed, a power supply must be provided via the aux port micro-USB port, J6. Overall the final circuit fully hooked up looked like this:
The hardware design in the BSP for the MiniZed has the pinout of the Arduino GPIO header specified in the Vivado constraints file (.xdc) but it doesn't actually have them connected to anything in the hardware block design. I documented how to enable the Arduino header I/O in the MiniZed's hardware design here.
Before writing the actual script to drive the electromagnets, I tested one of the electromagnets by manually driving the GPIO line it was connected to in Linux Sysfs. The GPIO controllers are visible in the /sys/class/gpio directory, where each gpiochip[number] controls a specified bank of GPIOs. Based on the physical address assigned to the AXI GPIO block in Vivado, gpiochip999 is the sysfs controller for the 14 GPIO of the Arduino header on the MiniZed.
Each GPIO signal in a controller must be exported to create its own directory in /sys/class/gpio with direction and value files that can be read and written.
Change directories into /sys/class/gpio and list the directory contents to see the available controllers.
root@minized-emmc-enhanced-2019-2:~# cd /sys/class/gpio/
root@minized-emmc-enhanced-2019-2:/sys/class/gpio# ls
export gpiochip1013 gpiochip1021 gpiochip1022 gpiochip1023 gpiochip881 gpiochip999 unexport
Export the IO0 pin on the GPIO header, which is 999 in the gpiochip999 controller. Then set the direction of the pin to 'high' to simultaneously set the direction of the pin as an output and the value logic level high.
root@minized-emmc-enhanced-2019-2:/sys/class/gpio# echo 999 > ./export
root@minized-emmc-enhanced-2019-2:/sys/class/gpio# echo high > ./gpio999/direction
Set the GPIO output low again:
root@minized-emmc-enhanced-2019-2:/sys/class/gpio# echo 0 > ./gpio999/value
To create the script drive the GPIOs, change directories into the eMMC and use the VI text editor to create and edit the files. I decided to create two separate scripts: one to set up each of the GPIO pins on the Arduino header, and another to actually step through and toggle each of the GPIO on and off at a specified time interval.
For the GPIO set up script, it is simply treating each GPIO instance as a file object to open the GPIO number, export the pin, then set it the pin's direction to output. Since I know the GPIO chip base number and width won't change, I've hardcoded them but it would be fairly straightforward to read them from the system at runtime.
root@minized-emmc-enhanced-2019-2:~# cd /run/media/mmcblk1p1/gameboard
root@minized-emmc-enhanced-2019-2:/run/media/mmcblk1p1/gameboard# vi gpio_setup.py
Code for the gpio_setup.py script:
import os
import re
import sys
import time, struct
import subprocess
print('Setting up GPIO')
for x in range(999, 1012):
GPIOnum = str(x)
gpioPath = '/sys/class/gpio/export'
gpioFile = open(gpioPath,'w')
gpioFile.write(GPIOnum)
gpioFile.close()
outPath = '/sys/class/gpio/gpio' + GPIOnum + '/direction'
outFile = open(outPath,'w')
outFile.write('out')
outFile.close()
To verify the set up script ran properly, list the contents of the sysfs GPIO directory:
root@minized-emmc-enhanced-2019-2:/run/media/mmcblk1p1# ls /sys/class/gpio/
export gpio1002 gpio1005 gpio1008 gpio1011 gpiochip1021 gpiochip881 gpio1000 gpio1003 gpio1006 gpio1009 gpio999 gpiochip1022 gpiochip999 gpio1001 gpio1004 gpio1007 gpio1010 gpiochip1013 gpiochip1023 unexport
For the actual driver script, it simply enables the GPIO for each electromagnet to turn it on for 3 seconds then turn it off in series pattern. Again, use the VI editor to create and edit the script:
root@minized-emmc-enhanced-2019-2:/run/media/mmcblk1p1/gameboard# vi gpio_driver.py
Code for the gpio_driver.py script:
import os
import re
import sys
import time, struct
import subprocess
print('Running GPIO script')
emag0 = '/sys/class/gpio/gpio1005/'
emag1 = '/sys/class/gpio/gpio1006/'
emag2 = '/sys/class/gpio/gpio1007/'
emag3 = '/sys/class/gpio/gpio1008/'
emag4 = '/sys/class/gpio/gpio1009/'
print('Electromagnet 0 ON...')
emag0Path = emag0 + 'value'
emag0File = open(emag0Path,'w')
emag0File.write('1')
emag0File.close()
time.sleep(3)
print('Electromagnet 0 OFF')
emag0File = open(emag0Path,'w')
emag0File.write('0')
time.sleep(0.5)
emag0File.close()
print('Electromagnet 1 ON...')
emag1Path = emag1 + 'value'
emag1File = open(emag1Path,'w')
emag1File.write('1')
emag1File.close()
time.sleep(3)
print('Electromagnet 1 OFF')
emag1File = open(emag1Path,'w')
emag1File.write('0')
time.sleep(0.5)
emag1File.close()
print('Electromagnet 2 ON...')
emag2Path = emag2 + 'value'
emag2File = open(emag2Path,'w')
emag2File.write('1')
emag2File.close()
time.sleep(3)
print('Electromagnet 2 OFF')
emag2File = open(emag2Path,'w')
emag2File.write('0')
time.sleep(0.5)
emag2File.close()
print('Electromagnet 3 ON...')
emag3Path = emag3 + 'value'
emag3File = open(emag3Path,'w')
emag3File.write('1')
time.sleep(3)
emag3File.close()
print('Electromagnet 3 OFF')
emag3File = open(emag3Path,'w')
emag3File.write('0')
time.sleep(0.5)
emag3File.close()
print('Electromagnet 4 ON...')
emag4Path = emag4 + 'value'
emag4File = open(emag4Path,'w')
emag4File.write('1')
time.sleep(3)
emag4File.close()
print('Electromagnet 4 OFF')
emag4File = open(emag4Path,'w')
emag4File.write('0')
time.sleep(0.5)
emag4File.close()
print('Done!')
As you can probably image, this tutorial can easily be adapted to fit many different peripherals/applications using the MiniZed. Hopefully this is a helpful starting point!
Comments