Shatin College, 2007
Cornell University, BA 2011
Le Cordon Bleu, Cuisine de Base
Capital One, Digital Analyst
Harvard Law School, JD 2016
Fenwick & West, Corporate/Tax
Allen & Overy, International Capital Markets
Boston Consulting Group
Companion Cubes
Introduction
To bring everything I learned together into a final project, I wanted to challenge myself by making use of the radio module that I still cannot figure out how to use effectively. After brainstorming with some friends, I decided to make a companion cube. We thought that at a big schools like Harvard Law, it is easy to feel lonely even when there are many people around. People resort to texting to feel connected to their friends and loved ones, but we wanted something more tangible and tactile. Something that cannot be ignored.
The idea is pretty simple: to have two or more cubes that would talk to each other by radio. When one person squeezes one cube, the other cubes to buzz and glow. And vice versa. Finally, when two cubes are squeezed at the same time, the cubes glows and buzzes in psychedelic happy colors.
Materials
I started the project by ordering some components of my project online that is not be carried by the shop. Below is a summary of the materials that I used to make a pair of companion cubes.
Item | Price | Vendor | Quantity |
---|---|---|---|
LiPo 3.7V 500mAh Battery | $7.95 | Adafruit | 2 |
JST-PH 2-Pin SMT Right Angle Connector | $0.75 | Adafruit | 2 |
Mini-Motor, Vibrator (DCM-371) | $1.75 | All Electronics | 2 |
ATMEGA328P-AU-ND | $2.87 | Inventory | 2 |
nRF24L01+ 2.4GHz Wireless Transceiver | $6.85 | Inventory | 1 |
620-1021-1-ND Hall Effect | $1.39 | Inventory | 2 |
NDS355ANCT-ND N-CH 30V 1.7A Mosfet | $0.26 | Inventory | 2 |
Mold Star 20T Platinum-cure Silicone Rubber | $30.11 | Amazon | 0.5 |
TOTAL | $51.85 |
Although it was my first time ordering these parts, it was surprisingly easy to figure out. A few notes: make sure you have everything you can think of before ordering. Often the shipping costs are higher than the cost of the component when ordering in small units. Also, ask around to see if anyone has used the component before (or has the component to lend), and you might also learn about good alternatives.
How to Make: a Squeezable Cube
I wanted a squidgy feel for the encasing of the electronics so that the user can squeeze to transmit their signals. I also wanted to use something that is trasparent or translucent so that light can shine through and glow from within. I asked Rob and it turned out that we had these two gallons of expired Mold Star 20T that would be perfect. Although he warned me that it was expired, I tried them out anyway, and they turned out great!
The was exactly the right squidiness for my project. I experimented with using the vacuum to reduce the number of bubbles, and the difference was significant. Even though it looks worse after using the vacuum, the bubbles rise to the top quickly when curing since they're bigger.
Happy with the material, I started designing my mold. I thought about a variety of ways to make the cube. The important thing was to make the components easily accessible, since I wanted to be able to reprogram, add components and charge the batteries. I decided to go with a semi-press-fit model of two halves. I also decided to make it more of a cuboid than a cube, since It would have been a little large. Molding this mold was pretty straight forward since it was only one-sided. At first I intended to drill the ups and downs of the press-fit corners, but because of the cut-depth of the toolbit, I had to just drill the downs (and make them bigger than the tool width), since the tool would not have been big enough to get between the protrusion and the edge of the cube. [Antimony file]
Then to milling! This time I used the table-top shopbot, which was way faster than using the Modella to mill the wax. The table-top shop-bot also had longer bits, which I needed for my 3cm cut-depth. After some intial projects realizing the auto-Z was broken from a ripped wire, and some manual Z-setting, it cut perfectly.
I then went to the Ultimaker and made some small cylinders to fit into the holes to make the ups of the press-fit. I had to adjust the diameter to slightly below a perfect fit. Since Rob fixed the nozzle-head fan, the Ultimaker printed beautifully, and it took 6 minutes to print each of these cylinders.
Afterwards it was smooth sailing. I poured in the Mold Star 20T and let it set. I made two versions: one with the vacuum and one without. I'm not sure which one I liked more, since the tiny bubbles looked like stars which was pretty neat. I might keep that one. There was some post-processing involved with cutting off wisps at the edges, but I liked how they came out! And they fit together perfectly.
How to Make: a Tiny Radio/LED/Motor Board
Next part was the hardest and took a lot of experimentation. I wanted to combine all the input/output of my project onto a tiny board that would fit into the cube. I started by looking into double sided PCB printing to fit in more components, experimenting with a coin-cell circuit connected to two RGB LEDs on either side.
It was a lot of time and effort to figure out how to flip over the board and invert the traces to make everything cut at the right place. And even afterwards, it was difficult to get the mill to cut the holes correctly since they had to be the right diamater (which was often too big). Rob suggested manually cutting the holes which was probably the easiest solution. Unsatisfied, I then looked to experimenting with geometric PCBs. I thought maybe I could make a cube and stuff some of the components on the inside. It took me a while to design the traces on EAGLE and required some photoshopping of the exported images to crop them into individual sides and add borders to make things cut out. In the end, I was very happy with what I was able to produce. [Cube Board Eagle zip]
I thought about putting the traces on the inside, which would have been easier to solder, but then difficult to debug and attach pins to. So I had them on the outside instead. I hotglued the structure together on the inside and it was pretty sturdy. Everything was looking good, until I tried to solder the traces across the edges. This was way more finnicky that In thought. I tried a variety of different wires (all very thin, since these traces were at 0.12mm). After an hour of struggle, I just ended up ripping a lot of the copper traces off the board. Since this was not a replicable or reliable process, I decided to give up and go back to a two-sided board.
I first started the put everything together by just loading everything onto the same board and making sure it worked.
After some issues with programming the boards, Dan helped me with rethinking the board and adding a 0.1uF capacitor between the RST pin and the RESET pin on the 328p to make it programmable via FDTI. I also removed the connection to AREF, which could create shorts. To save space, I also took out the 20Mhz clock, after determining that the radio and all the functions would run fine on the 8mhz internal oscillator. The result was my final board. [Final Board Eagle zip]
Post-production of the board was also a process. I had to drill holes to make a pseudo double-sided board. This was difficult to master and I made some mistakes along the way, including crushing my nearly 2x5 pins. Lessons learned: drill them holes before loading your components. Also, instead of punching it slightly to mark the center of the pad (which ended up destroying some of the traces) just start drilling in at full speed.
Threading a wire through the hole was also diffcult, since I didn't have any traces on the other side. I ended up ripping some copper traces, but mastering the skill of holding the two wires in places while soldering the motor legs onto them on the other side.
Once I got the motor soldered on, I hotglued it to fasten it to the board.
I also decided to make a little house for the motor head, since I didn't want it to get jammed while rotating on the interior of the cube. I made this house in Antimony and 3D printed it. I was really getting the hang of this workflow and was able to produce these casing and iterate for the right size in about 30 minutes. [Motor Housing Antimony File]
After hotgluing the house onto the board, the final product looked great!
Finally, I made some peripheral boards. First I replaced the 2x5 pin headers with a female socket to plug the RF module into. This meant that I had to replace the old connector I was using and mirror the pins horizontally.
After some mishaps, including fishing the milled board out of the vacuum, I was able to get this connector loaded and working with my board.
Next I had to make an adapter to connect to my LiPo battery from Adafruit. It used a header that I didn't wnat to rip up, since this was what it used for recharging. So a made a small adapter and also added a switch to it.
I then soldered some male headers to connect it to my board. I think I'll come back to this design later, since it wasn't the most efficient use of space.
In the end, I was pretty happy with how the board turned out. It was small and contained all the features I needed. If I were to redesign it, I would probably add a header instead of building the LED onto the board, that way I could connect multiple RGB LEDs instead of relying on the one.
How to Make: a Working Program
I spent probably 80% of my hours trying to get the radio modules to work. I first started with making sure that the output components were looking good. I did this with a simple fade-in fade-out code for both the RGB LED and the motor, loading everything through Arduino IDE. For extra measure, I also threw in a sensor read to make sure the magnetic sensor was working.
// Define RGB LED pins
#define red 5 // Red LED
#define green 6 // Green LED
#define blue 9 // Blue LED
#define anode 10 // Common Anode
#define sensor 0 // Sensor
// Define motor pin
#define motor 3
void setup() {
pinMode(red, OUTPUT);
pinMode(green, OUTPUT);
pinMode(blue, OUTPUT);
pinMode(anode, OUTPUT);
digitalWrite(anode, HIGH);
analogWrite(red, 255);
analogWrite(green, 255);
analogWrite(blue, 0);
analogWrite(motor, 0);
pinMode(motor, OUTPUT);
pinMode(sensor, INPUT);
Serial.begin(9600);
}
void loop() {
for(int i = 0; i<360; i++){
//convert 0-360 angle to radian (needed for sin function)
float rad = DEG_TO_RAD * i;
//calculate sin of angle as number between 0 and 255
int sinOut = constrain((sin(rad) * 128) + 128, 0, 255);
analogWrite(red, sinOut);
analogWrite(green, sinOut);
analogWrite(blue, sinOut);
analogWrite(motor, sinOut);
int value = analogRead(sensor);
Serial.println(value);
delay(15);
}
}
After determining that the input/outputs were working well on the board, I turned to Dan Chen for help with the Radio Module. He pointed me to his page. I used a modified version of his transmit/receive code (removing code on servo motor and wind sensor).
#include "nRF24L01.h"
#include "RF24.h"
#include "printf.h"
#include "SPI.h"
// Hardware configurati on: Set up nRF24L01 radio on SPI bus plus pins 7 & 8
RF24 radio(7,8);
int sensorPin = 0; // select the input pin for the potentiometer
byte sensorValue;
byte seninbyte;
// Topology
byte addresses[][6] = {"8Node","8Node"}; // Radio pipe addresses for the 2 nodes to communicate.
// Role management: Set up role. This sketch uses the same software for all the nodes
// in this system. Doing so greatly simplifies testing.
typedef enum { role_ping_out = 1, role_pong_back } role_e; // The various roles supported by this sketch
const char* role_friendly_name[] = { "invalid", "Ping out", "Pong back"}; // The debug-friendly names of those roles
role_e role = role_pong_back; // The role of the current running sketch
byte counter = 1; // A single byte to keep track of the data being sent back and forth
void setup(){
Serial.begin(57600);
printf_begin();
printf("\n\rRF24/examples/GettingStarted/\n\r");
printf("ROLE: %s\n\r",role_friendly_name[role]);
//printf("*** PRESS 'T' to begin transmitting to the other node\n\r");
// Setup and configure radio
radio.begin();
radio.setAutoAck(1); // Ensure autoACK is enabled
radio.enableAckPayload(); // Allow optional ack payloads
radio.setRetries(0,15); // Smallest time between retries, max no. of retries
radio.setPayloadSize(1); // Here we are sending 1-byte payloads to test the call-response speed
radio.openWritingPipe(addresses[1]); // Both radios listen on the same pipes by default, and switch when writing
radio.openReadingPipe(1,addresses[0]); // Open a reading pipe on address 0, pipe 1
radio.startListening(); // Start listening
radio.powerUp();
radio.printDetails(); // Dump the configuration of the rf unit for debugging
role=role_ping_out;
}
void loop(void) {
/****************** Ping Out Role ***************************/
int raw = analogRead(sensorPin);
Serial.print("Raw reading: ");
Serial.println(raw);
int mapped = raw / 3;
Serial.print("Mapped reading: ");
Serial.println(mapped);
if (role == role_ping_out){ // Radio is in ping mode
byte gotByte; // Initialize a variable for the incoming response
radio.stopListening(); // First, stop listening so we can talk.
printf("Now sending %d as payload\n\r",mapped); // Use a simple byte counter as payload
unsigned long time = micros(); // Record the current microsecond count
if ( radio.write(&seninbyte,1 ) ){ // Send the counter variable to the other radio
if(!radio.available()){ // If nothing in the buffer, we got an ack but it is blank
}else{
while(radio.available() ){ // If an ack with payload was received
radio.read( &gotByte, 1 ); // Read it, and display the response time
printf("Got response %d, round-trip delay: %lu microseconds\n\r",gotByte,micros()-time);
}
}
}else{ printf("Sending failed.\n\r"); } // If no ack response, sending failed
delay(500); // Try again later
}
}
#include "SPI.h"
#include "nRF24L01.h"
#include "RF24.h"
#include "printf.h"
// Hardware configuration: Set up nRF24L01 radio on SPI bus plus pins 7 & 8
RF24 radio(7,8);
// Topology
byte addresses[][6] = {"8Node","8Node"}; // Radio pipe addresses for the 2 nodes to communicate.
// Role management: Set up role. This sketch uses the same software for all the nodes
// in this system. Doing so greatly simplifies testing.
typedef enum { role_ping_out = 1, role_pong_back } role_e; // The various roles supported by this sketch
const char* role_friendly_name[] = { "invalid", "Ping out", "Pong back"}; // The debug-friendly names of those roles
role_e role = role_pong_back; // The role of the current running sketch
byte counter = 1; // A single byte to keep track of the data being sent back and forth
void setup(){
Serial.begin(57600);
printf_begin();
printf("\n\rRF24/examples/GettingStarted/\n\r");
printf("ROLE: %s\n\r",role_friendly_name[role]);
//printf("*** PRESS 'T' to begin transmitting to the other node\n\r");
// Setup and configure radio
radio.begin();
radio.setAutoAck(1); // Ensure autoACK is enabled
radio.enableAckPayload(); // Allow optional ack payloads
radio.setRetries(0,15); // Smallest time between retries, max no. of retries
radio.setPayloadSize(1); // Here we are sending 1-byte payloads to test the call-response speed
radio.openWritingPipe(addresses[1]); // Both radios listen on the same pipes by default, and switch when writing
radio.openReadingPipe(1,addresses[0]); // Open a reading pipe on address 0, pipe 1
radio.startListening(); // Start listening
radio.powerUp();
radio.printDetails(); // Dump the configuration of the rf unit for debugging
role=role_pong_back;
printf("Setup Complete! \n\r");
}
void loop(void) {
/****************** Pong Back Role ***************************/
printf("Loop starting \n\r");
if ( role == role_pong_back ) {
byte pipeNo, gotByte; // Declare variables for the pipe and the byte received
while( radio.available(&pipeNo)){ // Read all available payloads
radio.read( &gotByte, 1 );
printf("I am getting %d \n\r", gotByte);
if (gotByte>0){
}
delay(500);
}
}
}
To test whether this worked, I hooked the two boards up to my laptop and opened up separate instances of Arduino with two Serial Port readings. This was not a very reliable module/code, since it often didn't work. After giving up and going up and trying it again, it worked. It might be due to the ambient radio noise, but either way, it wasn't very stable.
After fiddling around with the addresses for a while, I looked up the documentation for the RF module library. It looked like Dan's code should work, but was outdated. I used the new library example for Getting Started, and it worked!
I changed the code slightly to get one of the radios to send data from my sensor to change the output of the LED on the receiver to see if this would work. And it did!
/*
* Getting Started example sketch for nRF24L01+ radios
* This is a very basic example of how to send data from one node to another
* Updated: Dec 2014 by TMRh20
*/
#include <SPI.h>
#include "RF24.h"
/****************** User Config ***************************/
/*** Set this radio as radio number 0 or 1 ***/
bool radioNumber = 0;
/* Hardware configuration: Set up nRF24L01 radio on SPI bus plus pins 7 & 8 */
RF24 radio(7,8);
/**********************************************************/
byte addresses[][6] = {"1Node","2Node"};
// Used to control whether this node is sending or receiving
bool role = 0;
void setup() {
Serial.begin(115200);
Serial.println(F("RF24/examples/GettingStarted"));
Serial.println(F("*** PRESS 'T' to begin transmitting to the other node"));
radio.begin();
// Set the PA Level low to prevent power supply related issues since this is a
// getting_started sketch, and the likelihood of close proximity of the devices. RF24_PA_MAX is default.
radio.setPALevel(RF24_PA_LOW);
// Open a writing and reading pipe on each radio, with opposite addresses
if(radioNumber){
radio.openWritingPipe(addresses[1]);
radio.openReadingPipe(1,addresses[0]);
}else{
radio.openWritingPipe(addresses[0]);
radio.openReadingPipe(1,addresses[1]);
}
// Start the radio listening for data
radio.startListening();
// Initialize digital pins for led as an output
pinMode(5, OUTPUT); // red
pinMode(6, OUTPUT); // green
pinMode(9, OUTPUT); // blue
pinMode(10, OUTPUT); // anode
}
void loop() {
/****************** Ping Out Role ***************************/
if (role == 1) {
radio.stopListening(); // First, stop listening so we can talk.
digitalWrite(5, HIGH); // Turn the LED on (HIGH is the voltage level)
int magval = analogRead(0); // Read magnet sensor pin
Serial.println(F("Now sending"));
//unsigned long time = micros(); // Take the time, and send it. This will block until complete
if (!radio.write( &magval, sizeof(int) )){
Serial.println(F("failed"));
}
radio.startListening(); // Now, continue listening
unsigned long started_waiting_at = micros(); // Set up a timeout period, get the current microseconds
boolean timeout = false; // Set up a variable to indicate if a response was received or not
while ( ! radio.available() ){ // While nothing is received
if (micros() - started_waiting_at > 200000 ){ // If waited longer than 200ms, indicate timeout and exit while loop
timeout = true;
break;
}
}
if ( timeout ){ // Describe the results
Serial.println(F("Failed, response timed out."));
}else{
unsigned int got_magval; // Grab the response, compare, and send to debugging spew
radio.read( &got_magval, sizeof(unsigned long) );
//unsigned long time = micros();
// Spew it
Serial.print(F("Sent "));
Serial.print(magval);
Serial.print(F(", Got response "));
Serial.print(got_magval);
// Serial.print(F(", Round-trip delay "));
// Serial.print(time-got_time);
// Serial.println(F(" microseconds"));
}
digitalWrite(5, LOW); // turn the LED off by making the voltage LOW
// Try again 1s later
delay(1000);
}
/****************** Pong Back Role ***************************/
if ( role == 0 )
{
int got_magval;
if( radio.available()){
// Variable for the received timestamp
while (radio.available()) { // While there is data ready
radio.read( &got_magval, sizeof(int) ); // Get the payload
}
// turn on LED based on mag val
if (got_magval < 100) {
digitalWrite(5, HIGH);
digitalWrite(6, HIGH);
digitalWrite(9, LOW);
}
else if (got_magval < 200) {
digitalWrite(5, LOW);
digitalWrite(6, HIGH);
digitalWrite(9, LOW);
}
else if (got_magval < 300) {
digitalWrite(5, LOW);
digitalWrite(6, HIGH);
digitalWrite(9, HIGH);
}
else if (got_magval < 400) {
digitalWrite(5, LOW);
digitalWrite(6, LOW);
digitalWrite(9, HIGH);
}
else if (got_magval < 500) {
digitalWrite(5, HIGH);
digitalWrite(6, LOW);
digitalWrite(9, HIGH);
}
else if (got_magval < 600) {
digitalWrite(5, HIGH);
digitalWrite(6, LOW);
digitalWrite(9, LOW);
}
else if (got_magval < 1000) {
digitalWrite(5, HIGH);
digitalWrite(6, HIGH);
digitalWrite(9, HIGH);
}
radio.stopListening(); // First, stop listening so we can talk
radio.write( &got_magval, sizeof(int) ); // Send the final one back.
radio.startListening(); // Now, resume listening so we catch the next packets.
Serial.print(F("Sent response "));
Serial.println(got_magval);
}
}
/****************** Change Roles via Serial Commands ***************************/
if ( Serial.available() )
{
char c = toupper(Serial.read());
if ( c == 'T' && role == 0 ){
Serial.println(F("*** CHANGING TO TRANSMIT ROLE -- PRESS 'R' TO SWITCH BACK"));
role = 1; // Become the primary transmitter (ping out)
}else
if ( c == 'R' && role == 1 ){
Serial.println(F("*** CHANGING TO RECEIVE ROLE -- PRESS 'T' TO SWITCH BACK"));
role = 0; // Become the primary receiver (pong back)
radio.startListening();
}
}
} // Loop
The next step was much trickier. I had to get both of these modules to talk to each other - essentially loading them with the same code and selecting different roles for them. They had to both tell each other when they were each getting squeezed. At first I tried a program where they would switch roles dynamically, but then decided this was too complicated. Instead I kept one node as the transmitter and one as the receiver, but they would talk to each other based on the transmitter's initiation. After many days of trying to get this to work, I finally got it working, albeit not in a stable manner.
// set up libraries for radio
#include <SPI.h>
#include "RF24.h"
// define pins
#define red 5 // red LED
#define green 6 // green LED
#define blue 9 // blue LED
#define anode 10 // common anode
#define sensor 0 // magnetic sensor
#define motor 3 // vibration motor
// set trigger value
#define trigger 20 // triggers when delta > 20
/****************** User Config ***************************/
/*** Set this radio as radio number 0 or 1 ***/
bool radioNumber = 0;
byte addresses[][6] = {"1Node","2Node"};
/* Hardware configuration: Set up nRF24L01 radio on SPI bus plus pins 7 & 8 */
RF24 radio(7,8);
/**********************************************************/
// Used to control whether this node is sending or receiving
bool role = radioNumber;
// Create a data structure for transmitting and receiving data
struct dataStruct{
unsigned long _micros;
bool squeeze;
}myData;
// Set up global variable baselines
int myBaseline;
int myValue;
bool mySqueeze;
bool yourSqueeze;
void setup() {
Serial.begin(115200);
Serial.println(F("Starting program!"));
radio.begin();
// Set the PA Level low to prevent power supply related issues since this is a
// getting_started sketch, and the likelihood of close proximity of the devices. RF24_PA_MAX is default.
radio.setPALevel(RF24_PA_LOW);
// Open a writing and reading pipe on each radio, with opposite addresses
if(radioNumber){
radio.openWritingPipe(addresses[1]);
radio.openReadingPipe(1,addresses[0]);
}else{
radio.openWritingPipe(addresses[0]);
radio.openReadingPipe(1,addresses[1]);
}
// Set up pins for I/O
pinMode(red, OUTPUT);
pinMode(green, OUTPUT);
pinMode(blue, OUTPUT);
pinMode(anode, OUTPUT);
digitalWrite(anode, HIGH);
pinMode(motor, OUTPUT);
pinMode(sensor, INPUT);
// Set baseline for magnetic sensor value
myBaseline = analogRead(sensor);
Serial.print(F("Baseline: "));
Serial.println(myBaseline);
mySqueeze = 0;
yourSqueeze = 0;
// Start the radio listening for data
radio.startListening();
}
void loop() {
myValue = analogRead(sensor); // Read value from sensor pin
Serial.print(F("Magnetic sensor value: ")); // Spew to serial
Serial.println(myValue);
mySqueeze = ( abs(myValue - myBaseline) > trigger ); // Squeezed if delta > trigger
Serial.print(F("My squeeze: ")); // Spew to serial
Serial.println(mySqueeze);
// Light it up
if (!mySqueeze && !yourSqueeze){
analogWrite(red, 255);
analogWrite(green, 255);
analogWrite(blue, 255);
analogWrite(motor, 0);
}else if (mySqueeze && !yourSqueeze){
analogWrite(red, 255);
analogWrite(green, 0);
analogWrite(blue, 255);
analogWrite(motor, 0);
}else if (!mySqueeze && yourSqueeze){
analogWrite(red, 0);
analogWrite(green, 255);
analogWrite(blue, 255);
analogWrite(motor, 255);
}else if (mySqueeze && yourSqueeze){
for(int i = 0; i<720; i++){
//convert 0-360 angle to radian and map sin(angle) to 0-255
float rad = DEG_TO_RAD * i;
int sinOut = constrain((sin(rad) * 128) + 128, 0, 255);
//write out color and motor
analogWrite(red, sinOut);
analogWrite(green, sinOut);
analogWrite(blue, sinOut);
analogWrite(motor, sinOut);
}
}
/****************** Ping Out Role ***************************/
if (role == 1) {
radio.stopListening(); // First, stop listening so we can talk.
Serial.println(F("Now sending")); // Update values in payload
myData._micros = micros();
myData.squeeze = mySqueeze;
if (!radio.write( &myData, sizeof(myData) )){
Serial.println(F("failed"));
}
radio.startListening(); // Now, continue listening
unsigned long started_waiting_at = micros(); // Set up a timeout period, get the current microseconds
boolean timeout = false; // Set up a variable to indicate if a response was received or not
while ( ! radio.available() ){ // While nothing is received
if (micros() - started_waiting_at > 200000 ){ // If waited longer than 200ms, indicate timeout and exit while loop
timeout = true;
break;
}
}
if ( timeout ){ // Describe the results
Serial.println(F("Failed, response timed out."));
}else{
// Grab the response, compare, and send to debugging spew
radio.read( &myData, sizeof(myData) );
unsigned long time = micros();
yourSqueeze = myData.squeeze; // Update yourSqueeze
// Spew it
Serial.print(F("Sent "));
Serial.print(time);
Serial.print(F(", Got response "));
Serial.print(myData._micros);
Serial.print(F(", Round-trip delay "));
Serial.print(time-myData._micros);
Serial.print(F(" microseconds, Squeeze "));
Serial.println(myData.squeeze);
}
// Try again 1s later
delay(1000);
}
/****************** Pong Back Role ***************************/
if ( role == 0 )
{
if( radio.available()){
// Variable for the received timestamp
while (radio.available()) { // While there is data ready
radio.read( &myData, sizeof(myData) ); // Get the payload
}
radio.stopListening(); // First, stop listening so we can talk
yourSqueeze = myData.squeeze; // Save your squeeze
Serial.print(F("Your squeeze: ")); // Spew to serial
Serial.println(yourSqueeze);
myData.squeeze = mySqueeze; // Update myData to mySqueeze instead
radio.write( &myData, sizeof(myData) ); // Send the final one back.
radio.startListening(); // Now, resume listening so we catch the next packets.
Serial.print(F("Sent response "));
Serial.print(myData._micros);
Serial.print(F(" : "));
Serial.println(myData.squeeze);
}
}
} // Loop
The next step of getting it to stay on and talking was the difficult part. Right now the code worked sporadically. There were many things that could be going on, so I went through them one at a time. First, I looked to the board. Dan suggested adding bigger capacitors to reduce noise and prevent brown-outs, so we added a 10uF to the Radio module between Ground and VCC, as well as stacked a 10uF on top of the existing 1uF between Ground and VCC on the board.
But that didn't help. The program stopped running sporadically, and I just could not figure out what was wrong. Fast forward 3 days and countless hours of trying to debug with different people, Dan finally suggested that I should try using my 3.7V battery. And it worked! It appeared that the 5V from the FDTI cable was overclocking the microchip, so that even though it worked, it was not 100% happy. Lesson learned: don't be surprised when weird things happen if you don't follow the optimal specs.
I then tested the final board and all looked good!
How to Make: a Complete Product
The last step was putting all the components together. I started by drilling some holes (to house the magnet) into the side of my encasing. I only drilled one, but more would probably have been better in terms of squeeze responsiveness. I learned that you need a bigger end mill to drill through this elastic material, since it bounces back in after drilling. After doing some test drills, I went ahead and drilled my final cases.
While stuffing the components into the case, I realized that I should have made my case slightly bigger. Luckily, the Mold Star is pretty elastic so there was a lot of leeway. I was originally going to 3D print a small shelf to hold things in place, but after inserting the components I realized that they are pretty stuck and would not move much anyway. So I left it at that. Given more time, I would have designed the case better so that it would hold the electronics perfectly in place and have sufficient room to be squeezed from all angles.
Finally, I touched up my code so that the transitions between each stage was smooth. This took a few tries, mainly because the common anode LED was less intuitive to PWM since analogWrite(pin,0)
was HIGH. I also tried a non-blocking version of the fade/light sequence by using micros()
to update the PWM, rather than a blocking for-loop. This worked, but I did not like the lag from running the radio parts of the code, which gave a staggered stepped look. I tried out a few external libraries for LED fading, but eventually just settled on keeping it simple and writing the PWM fade code myself. Below is the final code, with debugging-friendly Serial.print
lines commented out.
// set up libraries for radio
#include <SPI.h>
#include "RF24.h"
// define pins
#define red 5 // red LED
#define green 6 // green LED
#define blue 9 // blue LED
#define anode 10 // common anode
#define sensor 0 // magnetic sensor
#define motor 3 // vibration motor
// set trigger value
#define trigger 20 // triggers when delta > 20
/****************** User Config ***************************/
/*** Set this radio as radio number 0 or 1 ***/
bool radioNumber = 0;
byte addresses[][6] = {"1Node","2Node"};
/* Hardware configuration: Set up nRF24L01 radio on SPI bus plus pins 7 & 8 */
RF24 radio(7,8);
/**********************************************************/
// Used to control whether this node is sending or receiving
bool role = radioNumber;
// Create a data structure for transmitting and receiving data
struct dataStruct{
unsigned long _micros;
bool squeeze;
}myData;
// Set up global variable baselines
int myBaseline;
int myValue;
bool mySqueeze;
bool yourSqueeze;
int lastColor;
void setup() {
// Comment back in Serial.print for debugging
radio.begin();
Serial.begin(9600);
// Set the PA Level low to prevent power supply related issues since this is a
// getting_started sketch, and the likelihood of close proximity of the devices. RF24_PA_MAX is default.
radio.setPALevel(RF24_PA_LOW);
// Open a writing and reading pipe on each radio, with opposite addresses
if(radioNumber){
radio.openWritingPipe(addresses[1]);
radio.openReadingPipe(1,addresses[0]);
}else{
radio.openWritingPipe(addresses[0]);
radio.openReadingPipe(1,addresses[1]);
}
// Set up pins for I/O
pinMode(red, OUTPUT);
pinMode(green, OUTPUT);
pinMode(blue, OUTPUT);
pinMode(anode, OUTPUT);
digitalWrite(anode, HIGH);
pinMode(motor, OUTPUT);
pinMode(sensor, INPUT);
// Set baseline for magnetic sensor value
myBaseline = analogRead(sensor);
Serial.print(F("Baseline: "));
Serial.println(myBaseline);
mySqueeze = 0;
yourSqueeze = 0;
// Initialization color sequence
setColourRgb(0,255,255);
delay(500);
setColourRgb(255,0,255);
delay(500);
setColourRgb(255,255,0);
delay(500);
// Set last color as off
lastColor = 0;
setColourRgb(255,255,255);
analogWrite(motor,0);
// Start the radio listening for data
radio.startListening();
}
void loop() {
myValue = analogRead(sensor); // Read value from sensor pin
// Serial.print(F("Magnetic sensor value: ")); // Spew to serial
// Serial.println(myValue);
mySqueeze = ( abs(myValue - myBaseline) > trigger ); // Squeezed if delta > trigger
// Serial.print(F("My squeeze: ")); // Spew to serial
// Serial.println(mySqueeze);
// Change color based on conditions, using lastColor to track last state
if (!mySqueeze && !yourSqueeze){
if (lastColor == 0){ // do nothing
} else if (lastColor == 1){ // fade from green
lastColor = 0;
analogWrite(motor,0);
for (int fadeValue = 0 ; fadeValue <= 255; fadeValue += 1) {
setColourRgb(255,fadeValue,255);
delay(5);
Serial.println(fadeValue);
}
} else if (lastColor == 2){ // fade from red
lastColor = 0;
analogWrite(motor,0);
for (int fadeValue = 0 ; fadeValue <= 255; fadeValue += 1) {
setColourRgb(fadeValue,255,255);
delay(5);
}
} else if (lastColor == 3){ // fade from white
lastColor = 0;
analogWrite(motor,0);
for (int fadeValue = 0 ; fadeValue <= 255; fadeValue += 1) {
setColourRgb(fadeValue,fadeValue,fadeValue);
delay(5);
}
}
}else if (mySqueeze && !yourSqueeze){
if (lastColor != 1){
lastColor = 1;
analogWrite(motor,0);
for (int fadeValue = 255 ; fadeValue > 0; fadeValue -= 1) {
setColourRgb(255,fadeValue,255);
delay(5);
}
}
}else if (!mySqueeze && yourSqueeze){
if (lastColor != 2){
lastColor = 2;
analogWrite(motor,255);
for (int fadeValue = 255 ; fadeValue > 0; fadeValue -= 1) {
setColourRgb(fadeValue,255,255);
delay(5);
}
}
}else if (mySqueeze && yourSqueeze){
lastColor = 3;
// off -> red
for (int count = 0; count < 255; ++count) {
setColourRgb(255 - count,255,255);
// pulse motor
if (count < 64){
analogWrite(motor,255);
} else if (count < 128){
analogWrite(motor,0);
} else if (count < 192){
analogWrite(motor,128);
} else{
analogWrite(motor,0);
}
delay(2);
}
// red -> green
for (int count = 0; count < 255; ++count) {
setColourRgb(0 + count,255 - count,255);
// pulse motor
if (count < 64){
analogWrite(motor,255);
} else if (count < 128){
analogWrite(motor,0);
} else if (count < 192){
analogWrite(motor,255);
} else{
analogWrite(motor,0);
}
delay(2);
}
// green -> blue
for (int count = 0; count < 255; ++count) {
setColourRgb(255,0 + count,255 - count);
// pulse motor
if (count < 64){
analogWrite(motor,255);
} else if (count < 128){
analogWrite(motor,0);
} else if (count < 192){
analogWrite(motor,255);
} else{
analogWrite(motor,0);
}
delay(2);
}
// blue -> red
for (int count = 0; count < 255; ++count) {
setColourRgb(255 - count,255,0 + count);
// pulse motor
if (count < 64){
analogWrite(motor,255);
} else if (count < 128){
analogWrite(motor,0);
} else if (count < 192){
analogWrite(motor,255);
} else{
analogWrite(motor,0);
}
delay(2);
}
// red -> off
for (int count = 0; count < 255; ++count) {
setColourRgb(0 + count,255,255);
// pulse motor
if (count < 64){
analogWrite(motor,255);
} else if (count < 128){
analogWrite(motor,0);
} else if (count < 192){
analogWrite(motor,255);
} else{
analogWrite(motor,0);
}
delay(2);
}
}
/****************** Ping Out Role ***************************/
if (role == 1) {
radio.stopListening(); // First, stop listening so we can talk.
// Serial.println(F("Now sending")); // Update values in payload
myData._micros = micros();
myData.squeeze = mySqueeze;
if (!radio.write( &myData, sizeof(myData) )){
// Serial.println(F("failed"));
}
radio.startListening(); // Now, continue listening
unsigned long started_waiting_at = micros(); // Set up a timeout period, get the current microseconds
boolean timeout = false; // Set up a variable to indicate if a response was received or not
while ( ! radio.available() ){ // While nothing is received
if (micros() - started_waiting_at > 200000 ){ // If waited longer than 200ms, indicate timeout and exit while loop
timeout = true;
break;
}
}
if ( timeout ){ // Describe the results
// Serial.println(F("Failed, response timed out."));
}else{
// Grab the response, compare, and send to debugging spew
radio.read( &myData, sizeof(myData) );
unsigned long time = micros();
yourSqueeze = myData.squeeze; // Update yourSqueeze
// // Spew it
// Serial.print(F("Sent "));
// Serial.print(time);
// Serial.print(F(", Got response "));
// Serial.print(myData._micros);
// Serial.print(F(", Round-trip delay "));
// Serial.print(time-myData._micros);
// Serial.print(F(" microseconds, Squeeze "));
// Serial.println(myData.squeeze);
}
// Try again 1s later
//delay(100);
}
/****************** Pong Back Role ***************************/
if ( role == 0 )
{
if( radio.available()){
// Variable for the received timestamp
while (radio.available()) { // While there is data ready
radio.read( &myData, sizeof(myData) ); // Get the payload
}
radio.stopListening(); // First, stop listening so we can talk
yourSqueeze = myData.squeeze; // Save your squeeze
// Serial.print(F("Your squeeze: ")); // Spew to serial
// Serial.println(yourSqueeze);
myData.squeeze = mySqueeze; // Update myData to mySqueeze instead
radio.write( &myData, sizeof(myData) ); // Send the final one back.
radio.startListening(); // Now, resume listening so we catch the next packets.
// Serial.print(F("Sent response "));
// Serial.print(myData._micros);
// Serial.print(F(" : "));
// Serial.println(myData.squeeze);
}
}
} // Loop
// function to set RGB LED to the right color
void setColourRgb(unsigned int r, unsigned int g, unsigned int b) {
analogWrite(red, r);
analogWrite(green, g);
analogWrite(blue, b);
}
I was pleased with the final code, since the responses looked very natural.
Overall, through the many ups and downs of this final project, I learned a lot about how to spiral develop, how to test concepts, and how to quickly make small parts that fit the needs of my project. These skills are crucial in rapid prototyping. Although I was not able to complete the entire vision of my product, developing these skills were far more valuable that a finished product. I hope to complete this product next semester by having a better designed board, more LEDs, and have multiple cubes talk to each other!
Week 0
I did not have any experience with CAD programs coming in, so it was a very steep learning curve in multiple softwares this week. I decided to first create my mesh in Antimony the render more details in Blender.
Antimony was rather intuitive and I was able to piece together my vision of a final product: a smart bookstand. [Antimony file]
Blender was a struggle. I had no idea how to control and use it so it took me several hours of watching youtube tutorials to figure out the basics. First I tried to create a realistic wooden look to the object. I experimented on a plane:
But ultimately when I tried to transfer this onto the model, it failed to render and came out as brown. I gave up after several tries and decided to go with a simple render. I started by importing the .stl file and fiddling with it until the view was right.
Finally I added some lighting and materials to the stand, the floor, and the surroundings.
Given more time, I would love to investigate how to use Blender properly – perhaps for the final project. For now, this will have to do.
Learning to use Git
Git was also a struggle since I've never dealt with project management or even webpage development. After following Neil's steps and running through all the config commands, I was able to figure out a weekly step-by-step for publishing that worked:
cd .ssh/section.Harvard/people
open -R Chen
git status
to verify what changedgit add .
git commit - "[short update notes]"
git pull
git push
Week 1
This week was a struggle. I designed my press-fit construction kit in Antimony. Antimony was pretty intuitive to use. [Wishbone Antimony file]
After gaining some confidence and learning some shortcuts and tricks, I moved to a more complicated design. [Drumstick Antimony file]
After 3 hours of messing around, I found out that I could not export the file since Antimony crashed every time I tried. But at least I learned a lot about Antimony!
I switched to using Inkscape, also a new program to me. It was way easier to use for making the organic lines. What took me 3 hours in Antimony took me 30 minutes in Inkscape. [Bones svg file] [Bones outline svg file]
The laser cutting process itself was very straightforward. I worked with Ye, a student from GSE. We figured out the width of the gap was 0.14 inches through two test cuts and then we were well on our way.
I was pleased with my final product. It is a deconstructed dinosaur made from chicken bones.
Week 2
The milling of my board was a trial and error learning process.
My first board was a disaster. As soon as I saw the volume of dust and the sound it made, I knew I had it on the wrong setting. Sure enough, I set it to the edge cutting setting, rather than trace.
Next I decided to tinker with the overlap, changing from 50% to 25% to see if I would be able to get rid of more of the random blocks. Unfortunately, I was left with tiny thin strips between the paths.
While sandpaper did not remove the thin strips, I was able to remove the thin strips with light nudging by a tweezer.
Next, the soldering process was a lot more fun than I thought. Although my first impression was that it was all skill and manipulation, I soon realized that it was all about the heat and the chemical reaction. Once I got the hang of timing and tapping the solder on the metal rather than the soldering iron, it went a lot faster. The flux paste was quite helpful, as was the brass for removing the solder in hard to reach places.
My first solder! For the fine teeth, I used the brass wire trick of soldering a blob on and remove the excess by pulling the brass wire over them. Worked like magic.
Pretty proud of my first PCB!
Then I made the connection chord and programmed the board with Rob’s help. We had a hitch and couldn’t figure out why. Finally after trying several debugging (testing the programmer, testing the USB, testing the wire) we used the voltmeter and found that the USB supplying power to my board was not providing the right voltage.
But once we figured that out, it worked! I removed the solder jumpers and plugged it into the computer and it ran! Looking forward to using it to program my next board!
Didn’t have too much time to work on my project due to an interview in LA, but I was surprised with what I was able to make. I wanted to print a globe of waves containing a paper ship (a 3D printed twist on the ship in the bottle). I decided to design on Antimony to see how far I can take the program.
I used the Schwartz diamond as my basic shape to cut a spherical shell for the waves. The boat took a while longer and comprised of a series of pyramids.
I then created a horizontal platform for the waves within the sphere. And put the boat into the sphere. And that was my design! Took maybe a solid hour of trial and error to figure it out. [Antimony file]
I thought about adding supports by looking at Meshmixer and fiddling with all the different settings to see what would be the best orientation and where it would need support. But in the end it looked like it would require more support than it would be worth (to extract). So I decided to take a gamble and fast print on Ultimaker and just see what happens without the support.
The time estimate was around 30 minutes, which seemed long. But then once it started, it made sense why it would take that long. Very soon into the process, I realized that 80 voxel that I set was useless – the resolution when printing it at the size of two inches is simply not sufficient to see all the details. For a structure with no support, the fast print was making it surprisingly well! I was surprised it was able to make somewhat of a lattice for the horizontal piece. It looks more like spider webs, but it was more than I expected.
I then crossed my finger and hoped for the best as the boat started printing. There was no support structure other than the one thin layer of web…
But it worked! Not perfect, but definitely looks like a boat to me. Next, I redesigned the structure so that it was simpler and would print with support. I also thickened the walls so that there would be more to print. [Antimony file]
I first tried printing with the Makerbot. The print looked great, but unfortunately the reel at the back kept getting jammed so it cannot be left alone for too long. After a while a rigging the reel at the back to move more smoothly, I gave up when the wire itself got tangled. I did like the look of the see-through plastic, so I might go back to using it!
Next I went to the Lulzbot, the last machine in the Harvard Shop I haven't tried yet. I've heard that it also gives very stringy output, so I tried to preempt this by oozing a long string to clean out the nozzle before using it. The interface of the Lulzbot was less intuitive than the Makerbot and Ultimaker, but eventually we figured it out. Then it went to work!
The output was much better than my original design. The supports were surprisingly easy to snap off. I then tried to clean up some of the strings by using the soldering iron. This worked for most of the thin strings. I tried to use a hot air gun, but unfortunately this melted some of the plastic. I also dropped it by accident - and it was more fragile than I thought, and a piece broke off. Soldering it back together helped, but it still looked wonky.
I sent a copy of the file to Shapeways, an online professional shop that made 3D prints. I wanted to compare what that would look like - presumeably using different technology. The results were fantastic:
Finally, the scanning assignment. I started off by trying the 123D app on my iPhone. I'm not sure whether it was a problem with my phone or a problem with the app, but it took forever and always froze and stopped working. I gave up. I scanned myself using Sense with help from Rupal. She gave many good tips, such as setting the depth to around 1.7m and starting with my back facing the camera, since the overlap is weird. After a few duds, we were able to get it to scan properly. Creepy, but pretty cool.
Week 4
This week I decided to experiment with Fusion 360 since I wanted to move beyond Antimony. It was actually really intuitive and I enjoyed going through the tutorials to learn how to use the sketches and build models from them. I designed my book/magazine shelf using the program, carefully measuring out each length and angle. [STL file]
I ran into issues trying to figure out how to export this file into something that VCarvePro would be able to read. After trying with stl files, I eventually figured out that it was possible to export from sketch a vector file. I then imported the vector file into VCarve Pro. I had to do some post-processing once this was imported since the vector file included some lines that are unnecessary in the cut. Fortunately it was super easy to do this in VCarvePro using Trim the “cut” out these additional lines. I copied the design to fit 4 into a 2×4 foam block.
I decided to use foam for this assignment because I hate splinters. Also, I wanted to test the strength of the high density foam since I would be putting pretty heavy books on top, so this was more of an experiment than anything. The foam involved significant prep work. We had to put on a sacrificial wood layer, then glue gun the edge of the foam. Moreover, the foam was 2.05 inches thick, so we had to use the super long and thick end mill. This reduced the fidelity of the inner edges, but fortunately for this design it was not too big a deal.
The cutting went without a hitch. However, the drill did not hit the wood, which meant I was left with a 1mm thin layer of foam that I had to use a knife to cut out. In the future, I think it would be beneficial to drill down slightly more than the 2.06 value that I used, since the platform is not completely flat.
I was pleased with how clean the cut came out. I did some minor dusting with a tissue paper to get rid of the blue fluff. To stick the pieces together, I thought about using gorilla glue, but I didn’t want such a messy process. Since not a lot of force would be pulling the pieces apart, I decided to just use some double-sided sticky tape, which worked great.
The final test – would it hold? I put on some of my heavy law textbooks and it showed no sign of deforming or breaking – it was a success!
I was happy with the simplicity and utility of the output. If I have time, I would like to use the machine to make something bigger using wood!
Week 5
So much to learn, so little time. The recitation this week was a necessity – I was starting from scratch and knew nothing about electronics, let alone electronics design. Fortunately there were a lot of resources for dummys online, and the recitation was very clear and easy to follow. After following the example on the recitation I was confident enough to move to the actual design. Eagle was not always the most intuitive to use, but it was simple enough. [Schematics file] [Board file]
The main problem I ran into was not being able to fit all three wires that I wanted to fit under the ATTINY44. But they I fiddled around with the grid settings and made them really small, which allowed me to move the lines at smaller increments, and they fit!
Then came the milling. Fortunately we had a cheat sheet printed out from last time that I could follow, otherwise I would not have remembered all the steps. But when it came out…
You might be able to see in the image that some of the lines got joined. Looking back at the trace generated by fabmodule, it did not detect the gap in two spots. So I went back to Eagle to increase the DPI from 600 to 1000. No change. I figured I probably had to do some minute shifts in the design (either shifting some parts slightly more apart or making some wires thinner). But given that I was crunched for time, I just cut out the pieces using a sharp edge. It was not pretty, but it worked! I found this website helpful in learning the commands. A few useful commands I learned on the way:
add
to add parts move
to move parts label
to add a label name
to add a name (usually to update the label) value
to change the value (and update the label value) wire
to add a wire rip
to rip up a wire in the Board display none top
to show only the traces display none dimension
to show only the outline Soldering was fairly straight forward. I felt a lot more comfortable with figuring out all the pieces and manipulating the solder. But we haven’t had a chance to test if it works – so my fingers are crossed!
Week 6
This week’s assignment was by far the most complex and frustrating so far. The design process was very difficult – requiring some spatial visualization and maths to figure out how to design the cut in a way that, when fit together, would look right. I decided to make the mushroom from Mario. To make the two piece mold, I used the same solid shape and duplicated it and flipped it. This is where the math got tricky. I had to keep track of where the plane of reflection intersects with the mushroom and tracking how that affects the various transformations afterwards. I also couldn’t figure out how to exist a particular shape from a curved surface. But in the end I was pretty satisfied with my final mushroom. [Antimony file]
The next step was milling. Here was where I made a bunch of mistakes. First, the roland mill is really small and so the cut depth was limited. I start a job and quickly realized that it would go too deep. I had to cancel the job, flip my wax over, and shrink the file down so that the cut would only go as deep as the cut depth could handle.
But then came the second error. For the some reason, when I did the finish cut, the x-y was off and the end mill jabbed into the side of the wax. I could not figure out what was wrong. I double checked to make sure it was zeroed at the same place as before and then tried reloading the file. But to no avail.
I gave up and just decided to use the mold without the finish cut. Besides, the topogaphical lines looks kind of cool – like a pixelated version of the mushroom.
Mixing the Oomoo took a surprising long time and patience to get a smooth look without striations. it was also harder to pour the Oomoo “high and with a thin thread” as demonstrated. It just came out however it wanted. But in the end, I didn’t have too many bubbles to start off with, so it was okay. I went to bed since it was 11pm. The next day, I poked the Oomoo out and it look surprisingly successful. It captured all the details in the wax mold.
I then proceeded to pour the drystone in. A few difficulties here. My dowel holes did not work since I scaled my image down, so I had to guess where the match between the two pieces was. The liquid was super viscous and hard to use, even though we followed the proportions exactly by weight. But we managed to force some of it down the small hole I had. And then it was full! Or so I thought…
Turns out having a really small hole makes it really hard to fill with the cast. I got an Xcto knife and cut out a bigger hole. I then decided to try using metal. It was surprisingly much easier to use the metal – just heat and pour. It was also less viscous than the cast.
The results were great. The metal is pretty dense so even though it was the size of a fingernail, it felt very substantial. Overall I felt pretty happy with my mushroom, despite all the things that went wrong!
Week 7
This week we programmed our boards. So many mistakes but so many learnings.
Learning from Rob about how the C program works for the tiny44 was great. I kept some notes below:
PORT
: port you write toDDR
: direction (0 for in, 1 for out)PIN
: pin you read (high or low)avrdude -p attiny44 -c usbtiny
to check if alivecd
to where you store the .make and .c filesmake -f ________.make
make -f ________.make program-usbtiny-fuses
to set fuse to 5E per the data sheet for 20Mhz external clockmake -f ________.make program-usbtiny
I successfully programmed my hello world board to blink. This was all going very smooth until...
I stepped on my board and ripped off the 2x3 header. :( I tried salvaging the wires to no avail. So I decided to make new boards. I wanted to experiment with the wireless transmitters. I settled on Radio, since this seemed simple, had long range, and was low powered. Designing the board took me hours since I was still not great at Eagle and it was really hard to organize the board in a way that connected everything that needed to be connected. I ended up using 4 jumpers. [Eagle schematics] [Eagle board] [Trace png] [Outline png]
I milled the boards successfully, with only one spot (that I should have checked) where the wires were connected. With Dan Chen’s help, I cut it off using a knife and used a voltmeter to test if it was successfully cut. It was! But also I cut off another wire accidentally. Oops. So I went back to patch it back up.
I found that I was a lot better at soldering (although it still took a long time to do the two boards).
They were looking good. Next step - programming! Since I had experience with using the c work flow, I wanted to try out the Arduino IDE. It was very intuitive and easy to use. After selecting the right chip to burn the bootloader on I crossed my fingers and... it failed. But I could have know it would fail because my computer was showing a power usage error on the USB, indicating I should pull out the device and that there is a short circuit somewhere. It took me a long time to search for the short circuit. Finally I realized the mini USB connector had a metal underside and I was running both the ground and VCC wires under it. Oops.
I pulled it off, put some tape underneath, and stuck it back on. It worked. But weird – the red LED turned on without programming. I thought this might be another short circuit. I spent some time looking for another one but everything seemed fine. Arduino was still giving me an error when I tried to burn the bootloader. Finally I decided to replace the tiny44. Success!
So first – testing that the board works without the radio component. After testing out Arduino with a few examples provided, I decided to go back to coding in C since I was uncomfortable with having all the header files and not quite understanding what they did.
I tried loading two simple programs. One where it flashes between red and white. The other where red is lit until the button is pressed, then the blue is lit instead. Both worked!
// LED1 PA7 (pin 6).
// LED2 PB2 (pin 5).
#include <avr/io.h>
#define output(directions,pin) (directions |= pin) // set port direction for output
#define input(directions,pin) (directions &= (~pin)) // set port direction for input
#define set(port,pin) (port |= pin) // set port pin
#define clear(port,pin) (port &= (~pin)) // clear port pin
#define pin_test(pins,pin) (pins & pin) // test for port pin
#define bit_test(byte,bit) (byte & (1 << bit)) // test for bit set
#define output_port_1 PORTA // port A will be used for LED1
#define output_direction_1 DDRA // DDRA defines input/output for port B
#define output_pin_1 (1 << PA7) // The LED1 is on pin 7 of port A
#define output_port_2 PORTB // port B will be used for LED2
#define output_direction_2 DDRB // DDRB defines input/output for port B
#define output_pin_2 (1 << PB2) // The LED2 is on pin 2 of port B
#define DELAY 100 // blink delay
int main(void) {
//
// initialize pins
//
output(output_direction_1, output_pin_1);
output(output_direction_2, output_pin_2);
//
// main loop
//
while (1) {
set(output_port_1, output_pin_1);
clear(output_port_2, output_pin_2);
_delay_ms(DELAY);
clear(output_port_1, output_pin_1);
set(output_port_2, output_pin_2);
_delay_ms(DELAY);
}
}
Show .make code for alternate blinking
PROJECT=button_44_test
SOURCES=$(PROJECT).c
MMCU=attiny44
F_CPU = 20000000
CFLAGS=-mmcu=$(MMCU) -Wall -Os -DF_CPU=$(F_CPU)
$(PROJECT).hex: $(PROJECT).out
avr-objcopy -O ihex $(PROJECT).out $(PROJECT).c.hex;\
avr-size --mcu=$(MMCU) --format=avr $(PROJECT).out
$(PROJECT).out: $(SOURCES)
avr-gcc $(CFLAGS) -I./ -o $(PROJECT).out $(SOURCES)
program-usbtiny: $(PROJECT).hex
avrdude -p t44 -P usb -c usbtiny -U flash:w:$(PROJECT).c.hex
program-usbtiny-fuses: $(PROJECT).hex
avrdude -p t44 -P usb -c usbtiny -U lfuse:w:0x5E:m
Show C code for button
// Button PA3 (pin 10).
// LED1 PA7 (pin 6).
// LED2 PB2 (pin 5).
// CE PA1 (pin 12).
// CSN PA2 (pin 11).
#include <avr/io.h>
#define output(directions,pin) (directions |= pin) // set port direction for output
#define input(directions,pin) (directions &= (~pin)) // set port direction for input
#define set(port,pin) (port |= pin) // set port pin
#define clear(port,pin) (port &= (~pin)) // clear port pin
#define pin_test(pins,pin) (pins & pin) // test for port pin
#define bit_test(byte,bit) (byte & (1 << bit)) // test for bit set
#define button_port PORTA // The button is on port A
#define button_direction DDRA // DDRA defines input/output for port A
#define button_pin (1 << PA3) // The button is on pin 3 of port A
#define input_pins PINA // PINA is the register that is read to detect input high or low.
#define output_port PORTB // port B will be used for LED2
#define output_direction DDRB // DDRB defines input/output for port B
#define output_pin (1 << PB2) // The LED2 is on pin 2 of port B
int main(void) {
//
// initialize pins
//
output(output_direction, output_pin);
set(button_port, button_pin); // turn on pull-up
input(button_direction, button_pin);
//
// main loop
//
while (1) {
//
//
//
if (pin_test(input_pins,button_pin))
clear(output_port,output_pin);
else
set(output_port,output_pin);
//
// wait for button up
//
}
}
Show .make code for button
PROJECT=button_44_test2
SOURCES=$(PROJECT).c
MMCU=attiny44
F_CPU = 20000000
CFLAGS=-mmcu=$(MMCU) -Wall -Os -DF_CPU=$(F_CPU)
$(PROJECT).hex: $(PROJECT).out
avr-objcopy -O ihex $(PROJECT).out $(PROJECT).c.hex;\
avr-size --mcu=$(MMCU) --format=avr $(PROJECT).out
$(PROJECT).out: $(SOURCES)
avr-gcc $(CFLAGS) -I./ -o $(PROJECT).out $(SOURCES)
program-usbtiny: $(PROJECT).hex
avrdude -p t44 -P usb -c usbtiny -U flash:w:$(PROJECT).c.hex
program-usbtiny-fuses: $(PROJECT).hex
avrdude -p t44 -P usb -c usbtiny -U lfuse:w:0x5E:m
I then tried to tackle the radio component. Unfortunately, after multiple hours of research and significant help from Dan, we were unable to figure it out. The programming of a tiny44 to work with the radio (NRF24l01) turned out to be much more complicated than I thought. Since the tiny44 didn’t have hardware serial, some kind of software serial needed to be included in order for it to work. But also tiny44 had memory space constraints. I didn’t have time to tackle this programming problem this week, so I had to leave it be.
I’ll definitely be coming back to this, since I would like to include a wireless component! For my next project, I will try to make the same board with the MEGA and using a button battery.
Week 8
I learned a lot this week, i.e., so much went wrong this week. I wanted to make a motor work, in preparation for my new final project idea. I started by following Neil’s example on the class site for the DC motor, recreating his board on Eagle. [Eagle schematics] [Eagle board] [Trace png] [Outline png]
Although it took me a while, I do feel that I’m getting much faster at this design process. Learning how to use the command words were also key, so that I didn’t have to go back to click on the right icon each time I switched tools. In the end, I didn’t have too much trouble with the designing and went on the milling. And then disaster struck.
For some reason the bit went off the tool path went cutting the border and cut into the traces. Our working theory is that the screw was not tightened sufficiently. Either way, I tried milling it again, but this time…
The double sided sticky tape peeled off the sacrificial layer and the board started moving around. Well that’s no good. I replaced the cardboard sacrificial layer with the epoxy looking one and tried again…
Much better. But I stopped it before it cut all the way through, since it was making a weird noise. People in the lab suggested that it might be due to the fact that the tool does not like making tight turns, so the circle design was creating excess force on some parts of the tool. Anyhow, I decided to just stop it and cut out the rest of the circle manually. Soldering the components on was pretty straightforward – I definitely am getting the hang of it (although I might start wearing glasses since the fumes seem to irritate my contacts).
The next step was testing. I had no problem loading the program (which was again borrowed from Neil’s example). Here is the c code and the make file. But when it came to getting the motor to work… Nope. I checked everything in the code, and it all looked good. So I started checking each wire on the board, and found that even though the tiny44 was working (so the in wires to the H-bridge were showing the right voltages) the out wires were not showing anything. So I replaced the H-bridge with a new one – and it worked!
I didn’t realize until Rob told me afterwards that the 12V refers to the optimal voltage for the motor to work. So much for gingerly increasing the voltage so that it didn’t go too much above 5V.
Next I wanted to make the same board, but using a Mega328p. I knew I would have to move to this bigger chip (based on my experiences with the networking module) so I just wanted to experience designing the board and coding with this new chip. I also decided to add some LED lights, just because I had extra pins. [Eagle schematics] [Eagle board] [Trace png] [Outline png]
It took me forever to lay out the board since there were so many wires to consider. Also I made a bunch of mistakes. Things I learned: 1. Don’t put a resistor right above the 6-pin FTDI cable connected. 2. Try to put every pin that connects to the 6-pin header onto the same side. 3. You can put two wires underneath a 0-resistor jumper. If need be.
Programming the board turned out to be difficult. For the most part, I tried to follow Neil’s example of the Mega 328p. Here is the c code and the make file. It seemed to work and was running fine until after it successfully fused the low fuse. Then it refused to recognize the the board.
After much testing, I couldn’t find anything else wrong with the board, so I replaced the Mega 328p, and it worked again! This time, I talked to Rob and we tried to fuse it to a different setting. After looking at the data sheet, it looks like the setting we were trying should have worked (56) since it called for an external crystal. After taking a look at Rob’s board with the same external crystal setup, I tried using his setting (FF):
program-usbtiny-fuses: $(PROJECT).hex
avrdude -p atmega328p -P usb -c usbtiny -U lfuse:w:0xFF:m
avrdude -p atmega328p -P usb -c usbtiny -U hfuse:w:0xD9:m
avrdude -p atmega328p -P usb -c usbtiny -U efuse:w:0x07:m
And… it stopped working again. So I gave up. I replaced the Mega 328p one last time and just skipped the fuse step, going string to flashing the program. Although I got the lights to flash, I could not get the motor to work. I wonder if it has something to do with the fuse settings? I guess we’ll find out next week when I revisit this board!
Week 11
For our group project, we decided to make an cake decorator.
I was on the mechanical design: chassis team. I worked on laser-cutting the support columns for the structure, putting it all together with lots of hot glue, and documenting our team progress.
Week 9
This week was long, but fruitful.
I started this week by recreating Neil’s transmit-receive step response board. I hope to use this for my final project, by making a force sensor based on the proximity of the two copper plates. This went very smoothly and I had no problem with getting everything to work.
To document the extra steps: I had to first download pyserial onto my mac, then figure out the USB serial number connected to the board by looking into System Profiler. Afterwards, I ran python hello.txrx.45.py /dev/tty.usbserial-[USBSERIALNUMBER]
and it worked! [Neil's C code] [Neil's .make file] [Neil's Python code]
Next I wanted to change the chip to a Mega328. Mainly because I will have to use this chip for my final project, and I still have not had any luck with it. So I went ahead and milled a board using the hello arduino as a base and adding the two 1M ohm resistors for the step response.
This reference guide was super helpful in figuring out which pins to use, since the tx/rx of the sensor needed a A/D pin:
I had a lot of trouble with soldering the Mega328. I first tried the copper braid method, but for some reason it wasn’t working very well. I think that my soldering iron was probably not hot enough. Once I switched to soldering each leg on individual, it was a lot easier. Although I did put the chip on the wrong way the first time, so I had to hot air gun to remove the chip and resolder it. But it all came together in the end!
But not so fast. Again I had difficulties trying to program the board. I tried to use Arduino this time to try out the work flow. First I had to download this library so that it can talk to the 328p with a 20mhz resonator. But when I tried to burn the bootloader onto the chip, it didn’t work! I tried the same workflow with another board that I knew was functional from before, and the same thing happened.
I took my problems to Dan Chen who was super helpful and helped debug my board. There were quite a few issues with it…
Ultimately it still didn’t work, despite his best efforts. I went home defeated.
The next day, I milled a new board with all the fixes. I even set all the trace width to slightly thinner and moved all the traces around until it all got toolpathed properly in fabmodule. [Eagle board] [Eagle schematics] [Trace png] [Outline png]
It came out beautifully this time round – no need to fiddle around with the toolwidth in fabmodule, and no sketchy edges! I loaded the components on and programmed it, starting with Neil's code as the base. After finally reading through the data sheet understand what the different MUX settings meant, I was able to adjust the code to fit the 328p – success!
#include
#include
#define output(directions,pin) (directions |= pin) // set port direction for output
#define set(port,pin) (port |= pin) // set port pin
#define clear(port,pin) (port &= (~pin)) // clear port pin
#define pin_test(pins,pin) (pins & pin) // test for port pin
#define bit_test(byte,bit) (byte & (1 << bit)) // test for bit set
#define bit_delay_time 102 // bit delay for 9600 with overhead
#define bit_delay() _delay_us(bit_delay_time) // RS232 bit delay
#define half_bit_delay() _delay_us(bit_delay_time/2) // RS232 half bit delay
#define settle_delay() _delay_us(100) // settle delay
#define char_delay() _delay_ms(10) // char delay
#define nloop 100 // loops to accumulate
#define serial_port PORTD
#define serial_direction DDRD
#define serial_pin_out (1 << PD1) // Rx pin for FTDI
#define transmit_port PORTC
#define transmit_direction DDRC
#define transmit_pin (1 << PC0) // Tx pin for sensor
void put_char(volatile unsigned char *port, unsigned char pin, char txchar) {
//
// send character in txchar on port pin
// assumes line driver (inverts bits)
//
// start bit
//
clear(*port,pin);
bit_delay();
//
// unrolled loop to write data bits
//
if bit_test(txchar,0)
set(*port,pin);
else
clear(*port,pin);
bit_delay();
if bit_test(txchar,1)
set(*port,pin);
else
clear(*port,pin);
bit_delay();
if bit_test(txchar,2)
set(*port,pin);
else
clear(*port,pin);
bit_delay();
if bit_test(txchar,3)
set(*port,pin);
else
clear(*port,pin);
bit_delay();
if bit_test(txchar,4)
set(*port,pin);
else
clear(*port,pin);
bit_delay();
if bit_test(txchar,5)
set(*port,pin);
else
clear(*port,pin);
bit_delay();
if bit_test(txchar,6)
set(*port,pin);
else
clear(*port,pin);
bit_delay();
if bit_test(txchar,7)
set(*port,pin);
else
clear(*port,pin);
bit_delay();
//
// stop bit
//
set(*port,pin);
bit_delay();
//
// char delay
//
bit_delay();
}
int main(void) {
//
// main
//
static unsigned char count;
static uint16_t up,down;
//
// set clock divider to /1
//
CLKPR = (1 << CLKPCE);
CLKPR = (0 << CLKPS3) | (0 << CLKPS2) | (0 << CLKPS1) | (0 << CLKPS0);
//
// initialize output pins
//
set(serial_port, serial_pin_out);
output(serial_direction, serial_pin_out);
clear(transmit_port, transmit_pin);
output(transmit_direction, transmit_pin);
//
// init A/D
//
ADMUX = (1 << REFS0) // Vcc ref = AVcc
| (0 << ADLAR) // right adjust
| (0 << MUX3) | (0 << MUX2) | (0 << MUX1) | (1 << MUX0); // Rx on sensor ADC1 (See Input Channel Selections on datasheet)
ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0); // prescaler /128
//
// main loop
//
while (1) {
//
// accumulate
//
up = 0;
down = 0;
for (count = 0; count < nloop; ++count) {
//
// settle, charge
//
settle_delay();
set(transmit_port, transmit_pin);
//
// initiate conversion
//
ADCSRA |= (1 << ADSC);
//
// wait for completion
//
while (ADCSRA & (1 << ADSC))
;
//
// save result
//
up += ADC;
//
// settle, discharge
//
settle_delay();
clear(transmit_port, transmit_pin);
//
// initiate conversion
//
ADCSRA |= (1 << ADSC);
//
// wait for completion
//
while (ADCSRA & (1 << ADSC))
;
//
// save result
//
down += ADC;
}
//
// send framing
//
put_char(&serial_port, serial_pin_out, 1);
char_delay();
put_char(&serial_port, serial_pin_out, 2);
char_delay();
put_char(&serial_port, serial_pin_out, 3);
char_delay();
put_char(&serial_port, serial_pin_out, 4);
//
// send result
//
put_char(&serial_port, serial_pin_out, (up & 255));
char_delay();
put_char(&serial_port, serial_pin_out, ((up >> 8) & 255));
char_delay();
put_char(&serial_port, serial_pin_out, (down & 255));
char_delay();
put_char(&serial_port, serial_pin_out, ((down >> 8) & 255));
char_delay();
}
}
Next I wanted to see how to communicate with an output device so that it would not need to go through python/my computer. I decided to make a modular small LED board that can be attached to the existing input board. On the new board I put an RGB led (since I haven’t tried that out yet) and just a regular white led for testing. [Eagle board] [Trace png] [Outline png]
I tried making a ribbon connector using the pushing 2×3 header. And failed, twice. For some reason the connection just was not great. Giving up, I just connected it by individual wires.
I got the RGB led to work using the hello RGB example. The next part was the difficult one: coding it to get input from the pressure sensor I made by putting foam between the two pieces of copper.
Jacob and I were both trying this same thing, so we worked together. We started by studying Neil’s code for transmit-receive. After an hour of Googling and deciphering we realized that we just needed to grab his up/down values and feed the difference to an if-statement mapping different values to different states.I first tested it with the white LED, so that it turned off when it was in a certain mid range of value.
#include <avr/io.h>
#include <util/delay.h>
#define output(directions,pin) (directions |= pin) // set port direction for output
#define set(port,pin) (port |= pin) // set port pin
#define clear(port,pin) (port &= (~pin)) // clear port pin
#define pin_test(pins,pin) (pins & pin) // test for port pin
#define bit_test(byte,bit) (byte & (1 << bit)) // test for bit set
#define bit_delay_time 102 // bit delay for 9600 with overhead
#define bit_delay() _delay_us(bit_delay_time) // RS232 bit delay
#define half_bit_delay() _delay_us(bit_delay_time/2) // RS232 half bit delay
#define settle_delay() _delay_us(100) // settle delay
#define char_delay() _delay_ms(10) // char delay
#define nloop 100 // loops to accumulate
#define led_delay() _delay_ms(100) // LED delay
#define serial_port PORTD
#define serial_direction DDRD
#define serial_pin_out (1 << PD1) // Rx pin for FTDI
#define transmit_port PORTC
#define transmit_direction DDRC
#define transmit_pin (1 << PC0) // Tx pin for sensor
#define white_port PORTB
#define white_direction DDRB
#define white_pin (1 << PB0) // Pin for white LED
#define whiteg_port PORTD
#define whiteg_direction DDRD
#define whiteg_pin (1 << PD6) // Pin for white LED ground
int main(void) {
//
// main
//
static unsigned char count;
static uint16_t up,down, value;
Serial.begin(9600); // start serial
//
// set clock divider to /1
//
CLKPR = (1 << CLKPCE);
CLKPR = (0 << CLKPS3) | (0 << CLKPS2) | (0 << CLKPS1) | (0 << CLKPS0);
//
// initialize output pins
//
set(serial_port, serial_pin_out);
output(serial_direction, serial_pin_out);
clear(transmit_port, transmit_pin);
output(transmit_direction, transmit_pin);
//
// initialize white LED pin
//
clear(white_port, white_pin);
output(white_direction, white_pin);
clear(whiteg_port, whiteg_pin);
output(whiteg_direction, whiteg_pin);
//
//
// init A/D
//
ADMUX = (1 << REFS0) // Vcc ref = AVcc
| (0 << ADLAR) // right adjust
| (0 << MUX3) | (0 << MUX2) | (0 << MUX1) | (1 << MUX0); // Rx on sensor ADC1 (See Input Channel Selections on datasheet)
ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0); // prescaler /128
//
// main loop
//
while (1) {
//
// accumulate
//
up = 0;
down = 0;
for (count = 0; count < nloop; ++count) {
//
// settle, charge
//
settle_delay();
set(transmit_port, transmit_pin);
//
// initiate conversion
//
ADCSRA |= (1 << ADSC);
//
// wait for completion
//
while (ADCSRA & (1 << ADSC))
;
//
// save result
//
up += ADC;
//
// settle, discharge
//
settle_delay();
clear(transmit_port, transmit_pin);
//
// initiate conversion
//
ADCSRA |= (1 << ADSC);
//
// wait for completion
//
while (ADCSRA & (1 << ADSC))
;
//
// save result
//
down += ADC;
}
//
// save difference in up/down
//
value = up - down;
//
// set boundaries for when the LED should turn on/off
//
if (value < 3000){
set(white_port,white_pin);
}
else if (value < 4000){
clear(white_port,white_pin);
}
else {
set(white_port,white_pin);
}
}
}
I then moved onto the RGB, setting different values to different colors. At first I wanted to use a program to map values to a point in the RGB spectrum. But it was getting late, so instead I just created set bands of colors.
#include <avr/io.h>
#include <util/delay.h>
#define output(directions,pin) (directions |= pin) // set port direction for output
#define set(port,pin) (port |= pin) // set port pin
#define clear(port,pin) (port &= (~pin)) // clear port pin
#define pin_test(pins,pin) (pins & pin) // test for port pin
#define bit_test(byte,bit) (byte & (1 << bit)) // test for bit set
#define bit_delay_time 102 // bit delay for 9600 with overhead
#define bit_delay() _delay_us(bit_delay_time) // RS232 bit delay
#define half_bit_delay() _delay_us(bit_delay_time/2) // RS232 half bit delay
#define settle_delay() _delay_us(100) // settle delay
#define char_delay() _delay_ms(10) // char delay
#define nloop 100 // loops to accumulate
#define led_delay() _delay_ms(100) // LED delay
#define serial_port PORTD
#define serial_direction DDRD
#define serial_pin_out (1 << PD1) // Rx pin for FTDI
#define transmit_port PORTC
#define transmit_direction DDRC
#define transmit_pin (1 << PC0) // Tx pin for sensor
#define ledr_port PORTB
#define ledr_direction DDRB
#define ledg_port PORTD
#define ledg_direction DDRD
#define ledb_port PORTD
#define ledb_direction DDRD
#define red (1 << PB1)
#define green (1 << PD5)
#define blue (1 << PD6)
#define leda_port PORTB
#define leda_direction DDRB
#define anode (1 << PB2)
int main(void) {
//
// main
//
static unsigned char count;
static uint16_t up,down, value;
Serial.begin(9600); // start serial
//
// set clock divider to /1
//
CLKPR = (1 << CLKPCE);
CLKPR = (0 << CLKPS3) | (0 << CLKPS2) | (0 << CLKPS1) | (0 << CLKPS0);
//
// initialize output pins
//
set(serial_port, serial_pin_out);
output(serial_direction, serial_pin_out);
clear(transmit_port, transmit_pin);
output(transmit_direction, transmit_pin);
//
// initialize LED pins
//
set(ledr_port, red);
output(ledr_direction, red);
set(ledg_port, green);
output(ledg_direction, green);
set(ledb_port, blue);
output(ledb_direction, blue);
set(leda_port, anode);
output(leda_direction, anode);
//
// init A/D
//
ADMUX = (1 << REFS0) // Vcc ref = AVcc
| (0 << ADLAR) // right adjust
| (0 << MUX3) | (0 << MUX2) | (0 << MUX1) | (1 << MUX0); // Rx on sensor ADC1 (See Input Channel Selections on datasheet)
ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0); // prescaler /128
//
// main loop
//
while (1) {
//
// accumulate
//
up = 0;
down = 0;
for (count = 0; count < nloop; ++count) {
//
// settle, charge
//
settle_delay();
set(transmit_port, transmit_pin);
//
// initiate conversion
//
ADCSRA |= (1 << ADSC);
//
// wait for completion
//
while (ADCSRA & (1 << ADSC))
;
//
// save result
//
up += ADC;
//
// settle, discharge
//
settle_delay();
clear(transmit_port, transmit_pin);
//
// initiate conversion
//
ADCSRA |= (1 << ADSC);
//
// wait for completion
//
while (ADCSRA & (1 << ADSC))
;
//
// save result
//
down += ADC;
}
//
// save difference in up/down
//
value = up - down;
//
// set boundaries for when the different LEDs should turn on/off
//
if (value < 3000)
{
set(ledr_port,red);
clear(ledg_port,green);
clear(ledb_port,blue);
}
else if (value < 4000)
{
set(ledr_port,red);
set(ledg_port,green);
clear(ledb_port,blue);
}
else if (value < 5000)
{
clear(ledr_port,red);
set(ledg_port,green);
clear(ledb_port,blue);
}
else if (value < 6000)
{
clear(ledr_port,red);
set(ledg_port,green);
set(ledb_port,blue);
}
else if (value < 7000)
{
clear(ledr_port,red);
clear(ledg_port,green);
set(ledb_port,blue);
}
else
{
set(ledr_port,red);
clear(ledg_port,green);
set(ledb_port,blue);
}
}
}
It worked! This was past midnight so I went home happy. Unfortunately it was very difficult to predict what range of values would be spit out, since the sensor was super sensitive and was impacted by too many variables. I will have to figure this out next time, or try a pressure sensor for my final project. But that can wait until next week.
Week 10
I spent half of this week working on the assignment, and half of the week working on my final project. In terms of the assignment, I wanted to try out Node.js and speaking to a browser. Since I was taking CS50, I thought it might be nice to build on some of the skills and knowledge from that course. I was also interested in making a web-based application since this would likely be more accessible as an interface for my final project.
I started with looking at Neil's hello.mag example. I wanted to use the transmit-receive that I built from last week. Following Neil's advice, I made an sandwich so that the transmit would be wrapped around the receive plate. Theoretically, this would reduce the effect of my hand's capacitance on the readings.
The result was slightly better than before. My hand hovering over it did not affect it quite as much. But it was still so sensitive that really small changes like shifting it's orientation or the wires would trigger a big change. And the readings were also quite erratic in terms of the starting values.
In any case, I decided to go ahead and try to use Node.js to display the readings. I tried to run his example javascript but it didn't work. I then realized (sequentially, based on the error message in Terminal) that I had to install a bunch of stuff to make it run, including: Node.js, Serialport, WS, and XCode. After clearing out my hard drive to create space to install XCode... it worked! Well, no. It did not work. But at least it stopped giving me error messages about missing modules right off the bat!
Next I had to fiddle with the .js file from the Hello.Mag example to make it work with my sensor. First I tried to change Neil's code from hello.txrx.45.py into Javascript. To do this I used the Serialport documentation to understand how data was coming through. But for some reason, even after converting all the code over to Javascript, nothing was happening on my HTML page.
So then I looked to some debugging tools. After some Googling, I found this site on debugging. I tried node debug
out, but found it unwieldy and unfruitful. Unfortunately there were a ton of modules that get invoked, so there was a lot of function step-ins and step-outs. So I switched to the trusty old console.log()
method instead to figure out what was happening. This was what I was getting from my sensor:
<Buffer 36 0d 0a 36 30 32>
<Buffer 39 0d 0a 36 30 33>
<Buffer 34 0d 0a 36 30 34>
<Buffer 32 0d 0a 36 30 33>
<Buffer 37 0d 0a 36 30 32>
<Buffer 39 0d 0a 36 30 34>
<Buffer 32 0d 0a 36 30 35>
Serial.println
in my Arduino code uploaded and so the data coming through were not bytes that Neil's code can read. I figured this out after searching for 0d 0a
and realizing that this was CRLF
in hex. After switching the code over to a simple parseInt()
to convert the string coming through, it worked!
var server_port = '1234'
var client_address = '127.0.0.1'
var baud = 9600
// serial port initialization:
var serialport = require('serialport'), // include the serialport library
SerialPort = serialport.SerialPort, // make a local instance of serial
portName = process.argv[2]; // get the port name from the command line
portConfig = {
baudRate: baud,
// call myPort.on('data') when a CRLF is received Serial.println:
parser: serialport.parsers.readline('\r\n')
};
// open the serial port:
var myPort = new SerialPort(portName, portConfig);
// initialize value to send over to browser
var value = 0
myPort.open(function(error) {
if (error) {
console.log('can not open '+portName)
}
else {
console.log('opened '+portName)
myPort.on('data',function(data) {
// convert data (string of numbers) into int
value = parseInt(data)
})
}
})
//
// wait for socket request and then send field value
//
console.log("listening for connections from "+client_address+" on "+server_port)
var Server = require('ws').Server
wss = new Server({port:server_port})
wss.on('connection', function(ws) {
if (ws._socket.remoteAddress != client_address) {
console.log("error: client address doesn't match")
return
}
console.log("connected to "+client_address+" on port "+server_port)
ws.on('message', function(data) {
ws.send(JSON.stringify(value.toFixed(1)))
})
})
After speaking to Rob about the issues I had with the transmit-receive and how it might not work for my final project, he gave me the idea of using a magnet sensor instead. I've not tried that yet, so I wanted to test it out. I made the Hello.Mag board with Neil's trace and outline in record time - in maybe 15 minutes. Soldering was like second-nature, now that I've made so many boards that don't work.
I then loaded Neil's C code using Arduino and tested it out with his Python program. Side note: For the longest time I couldn't figure out why it was having issues with burning bootloader. Then someone told me I had to select "No" for the "Use Bootloader?" setting, and it worked! Anyway, the Python code worked exactly as Neil demonstrated! Unfortunately I did not have the same luck for the Node.js example. [Javascript file] [HTML file] For some reason, the values on the HTML would not update, even though I knew the values were coming through properly from the Python example. It would sometimes jump in value, but erratically:
I went back and used console.log
to figure out what was going on. It looked like Niel's Javascript code was reading in the data as if one byte was coming into the data array at a time. But in reality, this was happening:
I added an if-statement to update the data array in a different way when two bytes were coming in with the same buffer.
//
// new.hello.mag.45.js based on Neil's example
//
var server_port = '1234'
var client_address = '127.0.0.1'
var serial_port = "/dev/tty.usbserial-FTHKH0N6"
var baud = 9600
var samples = 100
//
// open serial port
//
var SerialPort = require("serialport").SerialPort
var sp = new SerialPort(serial_port,{baudrate:baud})
//
// look for framing and then update field value
//
var byte2 = 0
var byte3 = 0
var byte4 = 0
var byte5 = 0
var byte6 = 0
var byte7 = 0
var value = 0
sp.open(function(error) {
if (error) {
console.log('can not open '+serial_port)
}
else {
console.log('opened '+serial_port)
sp.on('data',function(data) {
if (data.length == 1) {
byte1 = byte2
byte2 = byte3
byte3 = byte4
byte4 = byte5
byte5 = byte6
byte6 = byte7
byte7 = data[0]
if ((byte1 == 1) & (byte2 == 2) & (byte3 == 3) & (byte4 == 4)) {
value = (byte5 + 256*byte6 + 256*256*byte7)/samples
}
}
else {
byte1 = byte3
byte2 = byte4
byte3 = byte5
byte4 = byte6
byte5 = byte7
byte6 = data[0]
byte7 = data[1]
if ((byte1 == 1) & (byte2 == 2) &(byte3 == 3) & (byte4 == 4)) {
value = (byte5 + 256*byte6 + 256*256*byte7)/samples
}
}
//console.log(byte1+','+byte2+','+byte3+','+byte4+','+byte5+','+byte6+','+byte7)
})
}
})
//
// wait for socket request and then send field value
//
console.log("listening for connections from "+client_address+" on "+server_port)
var Server = require('ws').Server
wss = new Server({port:server_port})
wss.on('connection', function(ws) {
if (ws._socket.remoteAddress != client_address) {
console.log("error: client address doesn't match")
return
}
console.log("connected to "+client_address+" on port "+server_port)
ws.on('message', function(data) {
ws.send(JSON.stringify(value.toFixed(1)))
})
})
The result was a data array that looked more like what Neil's parser was expecting:
AND IT WORKED! No more odd lags. Happy with figuring that out finally, I decided that I wanted to display the data in a time series so that I could see how the value changes over time. I looked up D3 and found a another javascript library called Rickshaw, which specialized in displaying time series. After some trial and error, and learning that the data coming through WS was type string
, I was able to display the results. [HTML/CSS/JS files]
Happy with what I had, I stopped there. If I had more time, I would like to investigate how to transmit data from the browser to the device and how to interact with a non-local server. Maybe I will build that as a functionality in my final project if I have time!