In this instructable, the Intel Edison MCU drives a circuit to dim AC voltage. It is controlled via touch sensor, android app, web app or another socket.io application. The circuit is a variation of diy_bloke's awesome instructable here. The part list links are all mouser.com, which ships to USA. This circuit should also work with 240VAC in theory but not tested. The core components are the circuit, the edison and two pieces of code:
1) MCU program to drive the circuit
2) C/C++ program that runs on the edison CPU and communicates with the MCU, monitors the touch switch and (optionally) communicates with a nodejs program for external control over the LAN.
The circuit is relatively simple, the schematic is shown above. I wont go into great detail about how to build the circuit since it's pretty straightforward, though a parts list with links will be provided.
WARNING: Do not attempt this unless you know exactly what you're doing. This deals with mains voltage which can kill you and start fires amongst other disastrous events. Never work near live wires or circuits. Always remove power to wires and circuits before moving, touching or working on them. Properly house circuits and terminate connections to avoid calamity.
With that out of the way, I'll talk about how this all works. The Intel Edison arduino breakout board has 14 GPIO pins, 0-13. They can be driven by the CPU using the MRAA library or by the MCU. In addition to the Intel® Atom™ dual-core x86 32bit processor, the Edison includes an MCU (Microcontroller Unit) which consists of a host CPU and the MCU itself. The host CPU is an Intel Atom processor running Linux, and the MCU is a Minute Intel® architecture CPU running Viper*, a Wind River real-time operating system (RTOS) that provides basic OS function support, including thread scheduling, memory management, interrupt dispatch, and more. In this application, the MCU is driving the circuit. Why use the MCU instead of a C program using MRAA running on the main cpu under linux? In a nutshell, because it's too slow and unpredictable for this application. Dimming lights requires precise timings and linux isn't a realtime OS so it's difficult to get the timing accurate enough. Specifically, the program must set a pin high at a very specific time after zero-cross. For more information regarding timing, see this. Perhaps another option might have been writing a kernel module but this remains unexplored.
The circuit high volt portions are electrically isolated from the low volt parts because the components use light to drive the input and output. When the AC wave crosses zero, the output pin is set high for a brief moment. This is the zero-cross point. The BT136 triac turns off the current flow at each zero-cross. Since the wave crosses zero twice each cycle, there are 2 * 60Hz = 120 zero crosses per second. Since there are 1,000,000 microseconds in one second, zero cross happens every 1000000 / 120 = 8333 microseconds. Somewhere between 0 and 8333 microseconds after zero cross, the output pin must be set high to allow current to flow to the load (a light in this case), otherwise the triac will remain off. Because this is happening so quickly and the human eye is sensitive, timing is critical, and any discrepancy can cause flicker. Fortunately, the code that runs on the MCU doesn't have to share processing time with other applications.
The MCU is in an infinite loop, continually firing the triac after a specific delay to maintain the dim level. This is fine but to control the dim level, we need to pass a value to the MCU program. After trying a few things that were unworkable solutions, I remembered reading something in the MCU 'Known limitations' section - "There is no access coordination between the Intel Atom processor and the MCU. Both can configure the same I/O at once. This can potentially cause conflicts and must be user-managed." Exploiting this, pins 6-13 are used to communicate the dim value in bits, from the MRAA program to the MCU. Reading a pin in the MCU program still takes time so pin 13 is used as a trigger. When there is a new value, pins 6-12 are set by the MRAA program to the corresponding data bits and then sets 13 high, which the MCU program is also polling. When it's high, the MCU program reads pins 6-12 as data bits and continues on. This avoids having to read all the pins every loop, reducing the potential for flicker.
There might be a better way to communicate the dim value to the MCU that doesn't hog 8 pins but the solution used here is the least latent among the methods tested. The entropy for the data could be lowered, freeing up a few pins for other uses if needed.
The MRAA program creates a unix domain socket for the nodejs relay server and handles the connections in the main thread. It also polls the touch switch in a different thread. Touching it cycles through levels of LOW, MEDIUM, HIGH and OFF, picking the next level above the current setting in case it has been set by another client to a value in between levels. Holding it switches to the next dim level but then slowly dims until releasing. The nodejs relay server connects to the MRAA program on the unix socket and listens for datagrams and socket.io connections on the LAN. It manages clients and keeps them in sync. The clients use udp so the ip(v4) address is found and connected to automatically so the only requirement there is the client and edison are on the same LAN. Multiple edisons can run the same code and will dynamically be added to the clients' device lists. The server code can run anywhere nodejs does and other implementations could exist using the same protocol, so this infrastructure could be expanded to control all sorts of devices. This could also be used outside of LAN remotely but not considered here.
The web client is a nodejs program that runs on desktop and serves a webpage, a socket.io connection to the browser and another to the edison relay server program. The android client is an app that handles all of these tasks in one program. These are optional; the touch sensor works even if the edison is not connected to the LAN or the relay server program is not running.
After trying some unreliable methods for making and coding a capacitive touch sensor, I ended up using the touch sensor included with the Grove Starter Kit Plus. The headers and code to use it is C++ which is the only reason the program is compiled with g++. All other code is extern "C". Connecting the touch sensor to a wire that is connected to some aluminum foil, it is actually a lot more sensitive. Touching is not required and requires moving further away to detect release but at least there are no false positives that could turn on the light randomly at any time. The touch sensor code is a relatively small portion and another type of switch could be used. Pins 4 and 5 are available with this setup.
The lamp used must support dimming. Most incandescent work well, especially ones that sort of fade out when turned off because they can absorb subtle flicker better. LED bulbs can work too but again, must support dimming. If an LED doesn't advertise dimmable, is most likely doesn't support dimming. Most florescent or CFL bulbs aren't dimmable.
Enough rambling, time for some instructions!
Step 1: Gather Materials
For this project you will need:
- Arduino breakout board - included with Grove Starter Kit Plus
- Base Shield - included with Grove Starter Kit Plus
- Power supply for Edison - included with Grove Starter Kit Plus
- TTP223 touch sensor - included with Grove Starter Kit Plus
- Zero Cross detection dimmer circuit materials:
- 2x 6-pin IC sockets
- Project box or in-wall electric box with blank cover
- Smaller gauge wire (nothing special, can use cat-5/6 or bell wire)
- Lamp extension cord (to power the circuit and provide a plug for the lamp)
- Header for low volt pins (optional, can solder wires directly to board instead)
- Android phone (optional)
- Laptop or desktop computer
- A lot of patience and determination
Step 2: Build Circuit
Build the circuit following the schematic diagrams. The mains/load connector from the link in the parts list doesn't fit the holes on the pad-per-hole board, unless placed diagonally. I used a drimmel tool to make it fit. It's probably a good idea to add an additional piece of 14 gauge copper wire on both traces running between the triac and mains/load connector. Solder a loose wire to a piece of aluminum foil or other material to be used as the touch sensor extender. Cut the lamp extension cord in half and connect the male end to the mains input of the circuit and the other two wires to the load output, minding polarity. Typically, extension cords will have some type of raised polarity striping on one of the wires if both are the same color. Test everything with an multimeter to verify no shorts and everything is connected as expected, before plugging it in. House the circuit in a project box. I used an extra-deep wall box and put an outlet for the Edison power supply.
Step 3: Connect Everything
Here you can find the pin layout. The pin numbers referenced are as labeled on the arduino breakout board. Pins 0 and 1 are reserved for serial communication. Pins 2 and 3 are connected to the output and input of the circuit, respectively. Pins 4 and 5 are connected to the touch sensor via D4. Pins 6-13 are reserved for communication with the MCU.
Arduino Pin # | Route
0 | Reserved / Serial
1 | Reserved / Serial
2 | Dimmer IN
3 | Dimmer OUT
4 | Reserved / Touch Sensor
5 | Reserved / Touch Sensor
6 | Reserved / MCU
7 | Reserved / MCU
8 | Reserved / MCU
9 | Reserved / MCU
10 | Reserved / MCU
11 | Reserved / MCU
12 | Reserved / MCU
13 | Reserved / MCU
Connect pins 2 and 3 on the edison to the output and input of the circuit, respectively. Connect ground and +5V from the edison to the circuit. Plug the touch sensor cable into D4 on the breakout board. Install the touch sensor somewhere preferably accessible yet hidden. Secure the wire coming from the custom sensor to the grove touch sensor pad with a loose alligator clip. A paper clip might be fashioned into something that can do the same job.
Step 4: Install Software
Get a bunch of code and install it everywhere! :-)
Here's the communication overview:
clients <--> LAN <--> edison relay-server.js <--> core-server.cpp --> MCU --> circuit
The client implementations are web app and android app. The android app is standalone while the web app is nodejs running ui-server.js and the system browser that accesses it. Multiple clients can connect to the relay-server. The touch sensor is also handled by core-server.cpp which updates the MCU and relay-server with new data. The relay server then updates the clients with data manipulated by the touch sensor. We'll start by getting the MCU code installed.
To install Eclipse for Intel Edison MCU, refer to https://software.intel.com/en-us/node/545143. Also see the bottom of the page to add "/home/root/scripts/enable-mcu-pins.sh" (without quotes) to /etc/intel_mcu/mcu_fw_loader.sh on the Edison.
Start Eclipse and navigate to 'MCU' > 'New MCU Project' and type 'AC_Dimmer_Controller', select 'Use default location' and 'Empty project'. Click 'Finish' and expand the project files in the left panel. Double click on MCU.xml in the left panel. Change the ip address to the edison ip address. Right click on src folder in left panel and select 'New' > 'File' and name it main.c then click 'Finish'. Copy the contents from https://github.com/soreau/edison-iot-light-dimmer/blob/master/server/mcu/main.c and paste them into the blank file. Save the file with Ctrl+S. Click 'MCU' > 'Build Project' followed by 'MCU' > 'Download'. Select Yes when prompted to reboot. This creates $project_dir/AC_Dimmer_Controller/Release/intel_mcu.bin and copies it to /lib/firmware/ on the Edison, which is loaded as the MCU program at boot.
Next we'll setup the web client. These instructions assume a system running Xubuntu Linux 16.04.
$ sudo apt install git npm nodejs $ git clone https://github.com/soreau/edison-iot-light-dimmer $ cd edison-iot-light-dimmer/clients/web $ npm install $ nodejs ui-server.js
Navigate to localhost:8080 in your browser. The interface should load with a 'connecting' gif. You can optionally add 'nodejs /full/path/to/ui-server.js' to your startup so it starts with the system. Just make sure the working directory is set to the path where ui-server.js and node_modules directory lives.
Setup the Edison. To install Yocto Linux, see https://software.intel.com/en-us/iot/library/edison-getting-started
Put the following contents into /etc/opkg/base-feeds.conf
src/gz all http://repo.opkg.net/edison/repo/all src/gz edison http://repo.opkg.net/edison/repo/edison src/gz core2-32 http://repo.opkg.net/edison/repo/core2-32
and the following in /etc/opkg/intel-iotdk.conf
src intel-iotdk http://iotdk.intel.com/repos/3.0/intelgalactic/opkg/i586
Run the following to install the core-server and relay-server:
# opkg update # opkg upgrade # opkg install mraa nodejs nodejs-npm # mkdir ~/src/light-controller ~/scripts # cd /tmp # git clone https://github.com/soreau/edison-iot-light-dimmer # cd /tmp/edison-iot-light-dimmer/server # cp src/* nodejs/* ~/src/light-controller/ # cd /tmp/edison-iot-light-dimmer/server/scripts # cp *.service /etc/systemd/system/ # cp *.sh ~/scripts/ # cd ~/src/light-controller/ # make # npm install # systemctl enable light-controller.service # systemctl enable core-server.service # systemctl enable wifi-keep-alive.service # systemctl start core-server # systemctl start light-controller # systemctl start wifi-keep-alive
The browser should now display Edison Dimmer Switch. The service files ensure the programs are started at boot time. The wifi-keep-alive script makes sure it can ping the gateway or reconnects in case wifi fails and doesn't fix itself.
Android Client: (optional)
Install android studio and use the files in edison-iot-light-dimmer/clients/android/ to build the app.
Step 5: Test
Hook up a light of 200 watts or less and try it out!