Showing posts with label Feather. Show all posts
Showing posts with label Feather. Show all posts

Friday, May 26, 2017

Head Move Donkey V02: progress


After this post on my donkey update I learned some things, much of it from Adafruit Support on the forum in this thread.

First I was concerned with a statement in the MusicMaker Learning Guide "Don't forget to make sure you have a good strong 5V power supply - especially if you're using the 3W 4 ohm speakers! " It turns out that I was able to do what I needed with the 3.7V LiPo--I noticed no difference in output using the 5V 2A  wall wart.  I was even able to run everything off the 4xAA battery pack (using NiMHs only give 1.2V each, 4.8V total).  However, when I added the second Servo. I needed 2 power supplies.

Second I had trouble getting decent volume from an 8Ohm .5W speaker. I was directed to use 4Ohm 3W speakers.  I tried an 8Ohm 2.5W speaker I took out of a CRT television amd that was fine, but it's too big for the donkey.  I got some 1.25" 4Ohm 3W speakers off ebay, but they did not produce enough sound.  The smaller Adafruit version did the trick.

Third, I cooked an SD card and a MusicMaker.  I think it was while I was experimenting with power options...I may have mis-connected something.  First symptom was that it could not initialize the SD card.  I replaced it, and it got past initialization and said it was playing music but it wasn't.  Back to Adafruit for replacement board.

The reason for the second Servo is that I decided to move the donkey's tail, too.  I move the Servos in opposite directions, just because for grins.

Here's my appearance with the project on Adafruit's Show and Tell. I'm on from about 11:30 to 14:00.




Monday, May 1, 2017

Head Move Donkey V02 in-process


Adafruit Feather M0, with MusicMaker and PWM/Servo FeatherWing stacked, running the Head Move Donkey code: plays
"Donkey Serenade" while moving the servo about 60 deg right and left to move the head.
As described in detail in this post, I recreated a wind-up toy that I bought for my daughter (an infant in 1978) with a Pro Trinket 3V and other components.  In the 2.5 years since, I've gotten a little smarter and the available technology has gotten a lot better, so I thought I'd try a version 2.  The biggest difference is that I needed fewer components. 
Donkey V01 
In the original, I needed an SD card reader, an amplifier, and a PWM/Servo Board. I still need those things, but the Adafruit Feather System makes it a lot simpler, eliminating most of the wiring and some of the code.  Also the Feather uses a ATSAMD21G18 @ 48MHz with 3.3V logic/power and 256KB of FLASH + 32KB of RAM while the Trinket uses the ATmega328P  at 12MHz with 28K of flash, and 2K of RAM...so, the M0 is much more powerful.

Power management is slightly different. I was able to power the Trinket from a 4xAA battery pack, using the 3V output to power the SD card and the 6V to power the Amp and servo driver.  Logic levels were not a problem.  I power the M0 from a 3.7V LiPo and the Servo Wing from the 4xAA--again, the Servo is forgiving with respect to logic levels.  I probably should have had separate power sources in V01. One twist is that I'll need a DPST switch to turn on both power sources together.


Donkey V02 Circuit Working


Next steps are to construct and enclose the completed circuit and re-stuff the donkey.  Since the enclosure will be smaller, I'm going to try to get the batteries inside the donkey this time.
Fritzing Diagram of Circuit

Wednesday, April 19, 2017

Fun with Robots, IR Decoding, LED Matrix, etc.

Fig 1: Three experiment--two with the samsung remote on the left, one with the Radio Shack remote on the right--to control a +Adafruit Industries 8x8 Bicolor LED Matrix
I haven't posted in a while because several interests have kept me very busy. This all started withe the +Parallax Board of Education (BOE) Robot kit. I asked for and received on for Christmas a couple of years ago and let it ripen until I was ready.  I decided that it would make a good project to do with my 7-year-old granddaughter when she visits this summer--but first, I should make sure that I can build it without a lot of fumbling. Fumbling is good, but watching me figure out what dumb thing I did is not always interesting to a 7yr-old. I followed the tutorial and learned quite a bit, The last project that I did used infrared for proximity sensing to avoid obstacles. At the same time, +Adafruit Industries, via The Desk of Lady Ada, provided some great information on IR. So, I decided to play and learn further.
Fig 2: +Parallax BOE Bot with 2 IR transmitters aand 2 IR receivers

IR Decoding the Hard Way

Following the +Adafruit Industries tutorial on IR Sensing, I was able to decode only some of the buttons on my Samsung TV Remote (see Figure 1). Using the remote is a 2-step process: first decode the signals send by the buttons, then use the decoded signals in a different sketch that takes action based on the buttons. Using this method (see the Raw IR Decoder and ircommander code on github), I was able to decode some of the buttons and use them in a sketch.  

Quick diversion:  I have had big plans to use a 32x32 LED Matrix that I bought in a parking assistant project that will display faces to convey the emotions evoked by a car getting increasingly closer. I decided to use the 8x8 bicolor matrix and backpack to prove the concept, and use the remote to signal which face to display. Figure 1 shows the initialized yellow face (looks orange to me). So, I needed to follow the tutoral for the matrix and backback library.

I was able to merge code from the bicolor8x8 example code in the Adafruit LED Backpack library with code from the ircommander sketch referenced above. After adding the decoded remote buttons to a newly created library (see the IR Sensor tutorial and the project video cited at the end of this post), I was able to change the face on the matrix based on the remote buttons.

I found both the coding aspect and the operation of the technique to be less than perfect.  Maintaining a separate library with very long sequences of numbers just to test what button  was pressed seems a bit much. In operation, the code seems to reset itself randomly, and only respond to buttons when it feels like. This may be me, and I will investigate, but read on to see a much neater technique.

IR Decoding an Easier Way

After working through the above, I watched Lady Ada work through Chris Young's IRLib2 in one of the Desk of Lady Ada epdisodes cited above.  Chris, aka cyborg5, is a frequent visitor to the Adafruit Show and Tell, and has done a lot of great work (see his blog).  Both the decoding and the processing techniques are simplifies greatly, and his library handles several IR protocols. The IRLIB2 tutorial describes it all very well.  You wind up with one simple code that you supply in a sketch so you can determine what button was pressed--straightforward, and I was able to decode all the buttons with no missed presses. From there it was a matter of simplifying the sketch I wrote earlier to display faces on the LED matrix.  Again, the project video shows all this.

Controlling Motors with IR

Since we started with robots, I thought it would be interesting to control motors with IR.  It was a simple matter to modify the IRLib2 sketch above to direct the continuous rotation servos.  I took code from the BOE-Bot project, and code from the Adafruit_PWMServoDriver library tutorial, and it went smoothly.  The servos in the BOE-Bot are different from the Adafruit ones, so I had to go to the data sheet to see what values to use to get them to move as I wanted. I was able to decode all the buttons on the Samsung remote, and used the arrow keys to simulate moving the robot forward, back, left, and right.

I first did this without the PWM Servo FeatherWing. Because the feather is 3V logic, I needed a level shifter to handle the signals from the board to the servos. That worked, but added a few connections and I used the Arduino servo library as opposed to the Adafruit library which allowed for simpler code.

Another thing I needed to worry about was timer conflicts. I had come across this before, in my Donkey Project, but Chris' tutorial gave a straightforward explanation and solution. The problem is that the servo library uses Timer1 and on the 32u4 Feather IRLib2 does too. It was necessary to go into one of the associated libraries and change one line of code to re-assign the timer.  You'll get better instruction in the tutorial than I can give here.  Again, the project video shows the results.
Controlling 2 FS90R continuous rotation servos from an Adafruit BLE Feather and PWM Servo FeatherWing

Trying a Different Remote

A while back, Radio Shack sold some MAKE project kits, including robotics. During an earlier RS bankruptcy, I acquired a RS remote at a big discount and still had it in inventory, so I decided to try it out. Using the IRLib2 approach to decoding, I found the the protocol was not one of supported ones. A look at the datasheet for the PT2248 chip in the remote revealed that ot used the Toshiba protocol. So, I had to go back to the hard way, and I got that to work.  See the video.

IR Affects the BOE-Bot

Just for grins, I took out the BOE-Bot, let it runon the floor, and chased it with a remote.  The bot's IR retrievers detected the transmissions, and the bot reacted accordingly (moving left or right as if it had detected an obstacle).

Project Video

See the project video on YouTube

Tuesday, October 11, 2016

Interrupts, Power Saving, the Zero, OLED display, and a compass


This is a story about bumbling into moderate success. The project objective, which I met, was to build a compass. Sounds simple, but the real objective to all of this is to learn--and I met that, too (as usual). In the sequence below, Steps 1-3 are basic setup, Step 4 is a straightforward programming implementation, The learning (for me) begins at Step5.

It all started a few months ago as explained in this post  on Arduino and an OLED display. As mentioned in that post,  I bought "Arduino for Ham Radio" by +Glen Popiel to see if I could spend time on two of my hobbies together. I'm neutral on the book, but I thought the compass project might be useful.

In attempt #1, partially pictured in the post mentioned above, I used an Uno clone and an OLED display and a magnetometer from eBay.  I can't sweat to it now, but at the time I was sure that the magnetometer readings were off.  Google revealed that some clones yield odd results.  The board works fine for everything else I've tried, so I'm not sure, but in any case I have since committed to buy only genuine Arduino boards (for official boards like the Uno--I will buy variants by +Adafruit Industries like the Feather in this example).

Next step was to try another board.  I got the Adafruit Feather M0 Basic Proto, Feather Wing OLED display, and HMC5883l Magnetometer (just in case the cheap one from eBay was faulty). With a breadboard and some jumper wires that's all I needed to build the prototype.

In this post, I will go through the learning and prototyping I did to build the compass.
  1. test the display
  2. add the magnetometer
  3. combine the examples
  4. add the text direction
  5. slow this down (this is where the fun starts)
  6. interrupt with and without sleeping
  7. battery status
  8. improving the display
  9. parts and code
  10. next steps
But first, here's a discussion of learning points and peculiarities (or at least things peculiar to me):

Resetting the Feather M0 for upload
Arduino and Arduino-ish boards have a variety of reset issues. For example, the Adafruit Trinket often requires that the user press upload in the IDE, wait a while, then press reset on the Trinket--otherwise the booloader is not active.  On the Feather M0, it is often necessary to "double-click" the reset button to force it into booloader mode, where it stays until after upload.

This is a nice feature, but at least on my system (Windows 10, IDE 1.6.12) the Feather sometimes switches ports (COM3 before double-click reset, COM4 after, or vice versa, in my case).

Coding  (stuff new to me)
I'm sure this is old news to some, but I learned some stuff in this exercise.  
In researching how to enter sleep mode, I found this:
   SCB->SCR |= 1<<2 div="">
SCB is System Control Block and SCR is System Control Register. -> is member assignment and << is left shift.  I knew most of that (not ->), but the instruction looked strange to me. In English (my paraphrase--others may object), it says "OR the SCR with a 1 shifted 2 bits to the left, and assign the result to the SCR member of teh SCB structure." SCR bit 2 is the sleep bit. 0 is idle, 1 is deepsleep (see next item), so ORing it with a 1 makes the bit 1.
I thought that syntasx was a little obtuse, and with the help of the Adafruit Forum I found:
    //  SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;  
which does the same thing, but (to me) makes more sense--it is closer to saying what you want to do instead of how you're twiddling the bits.
Now we can turn on the sleep bit--how to turn it off.  For 40 years, I've been turning bits on with OR's and off with ANDs. Here, we are addressing the whole SCR but manipulating only one bit, so in either case, you need to leave everything else as is. You undo that by ORing with 0s and ANDing with 1s.  I tried I few ways to code a binary in the operand, and thanks to the forum, this works (as in compiles--|= 0b000010 does work for deep sleep):
    SCB->SCR &|= 0b11111011;
A better answer is to AND with the inverse of what we ORed with, so
   SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk;    
turns the bit off. 

Another feature, described in Step 6 below is digitalPinToInterrupt(). I was reading old materials on interrupts, evidently before this feature was added. In the olden days (like 3 years ago), the attachInterrupt instruction required (interrupt#, ISR, mode)--more on operand 2 and 3 later. Interrupt number is hardware dependent and different from pin number in many cases.  To make it more readable and more portable, replace interrupt# with digitalPinToInterrupt(pin#). The function returns the interrupt number for that pin on the device being used. This is really important as more and more boards join the Arduino family.

Sleep mode
As I write this I am in a discussion on the Adafruit forum to determine how to get the Feather M0 into idle mode, showing how the Adafruit community helped me solvd the problem of getting to idle mode,
For deep sleep, I coded the instruction above in setup, and put a wait-for-interrupt (__wfi();) at the top of the loop. That works. The button press causes an interrupt, the loop does it's thing then waits again on iterating back to the top (see Step 6 below).  I could not get it to work for idle mode.
Looking through datasheets, programming guides, examples, searches and the above-referenced forum thread, the best answer is to code    SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk;    in setup.  However, according to what I read, this is not enough,  There are 3 idle modes, controlled by 2 bits in Power Management.  The masks for each value are defined (on my Windows system) in ~\AppData\Local\Arduino15\packages\tools\CMSIS\4.0.0-Atmel\Device\ATMEL\samd21\include\component\pm.h
So, PM->SLEEP.reg |= PM_SLEEP_IDLE_APB; sets the bits to 2 (b'10')
Even with the correct syntax, I struggled with this. It appeared that it was not waiting. As it turns out, in idle mode, other things could still wake it up, so I added an explicit test of the booleans set in the ISRs. If neither was set, I wait until one is. Very simple solution. Thanks Adafruit forum!

ISR modes and sleeping
As noted below, FALLING works for a pin going from HIGH to LOW if you're not sleeping. However, FALLING and RISING use clock, which is turned off.  So the attach should be coded:
     attachInterrupt(digitalPinToInterrupt(BUTTON_B), headingISR, LOW); 

Those are the high points.  What follows is how I went about building it.

Step 1 - test the display

This step was really just a matter of soldering stacking headers on the Feather M0, female headers on the Feather Wing, and following the Adafruit SSD1306 tutorial.
  1. solder the headers on the boards 
  2. mount the M0 on the breadboard
  3. mount the Feather Wing on the M0
  4. install the Adafruit_SSD1306 and Adafruit_GFX libraries (see SSD1306 tutorial)
  5. preparing the IDE for the M0 (see the M0 tutorial)
  6. run the SSD1306 example program ssd1306_128x32_i2c (see SSD1306 tutorial)--the Feather Wing is 128X32 and uses I2C

Step 2 - add the magnetometer

Note that the example uses serial, not the OLED display, so you could do this part with or without the Feather Wing.
  1. solder the header pins (if it's new like mine was)
  2. add to the breadboard
  3. it's I2C, so connect SDA to pin A4 on the M0, SCL to A5
  4. connect VIN to the 3V rail and GND to the ground rail
  5. follow the Adafruit HMC5883l tutorial to install the Adafruit_Sensor and Adafruit_HMC5883_U libraries
  6. run the Adafruit_HMC5883_U example sketch magsensor 

Step 3 - combine the examples

I was now working with 3 examples that I needed to combine: SSD1306, HMC5833_U, and the book.

The example in the "Arduino for Ham Radio" uses a variant of the magsensor example (from a different library) to display the heading in degrees and the corresponding text compass heading on an LCD display.  The main modifications were to used the OLED instructions instead of  LCD instructions and to use a function instead of a lengthy series of if constructs to determine the text heading (see Step 4).

To me, the most logical approach was to start  with the magsensor example and add to that, reasoning that the compass is the objective and this code already has display code that I just needed to modify. Also, since the book example used a similar sketch as a base, I could just make the corresponding modifications as I went.
  1. save the magsensor example to your sketchbook directory/folder and give it a meaningful name like "magsensor_OLED"
  2. add the OLED libraries (see step 1 above)
  3. add ssd1306 display code (I find the easiest way to do this is to leave the Serial instructions in place to use for testing and clone them as displays). Note that you will need to add clearDisplay, setCursor, and the all-important display.display() in addition to changing serial.print to display.print (etc.). So, this line:
        Serial.print("Heading Degrees: ");Serial.println(headingDegrees);
    will be copied and cloned as:
        Serial.print("Heading Degrees: ");Serial.println(headingDegrees);
        display.clearDisplay();
        display.setCursor(0,0);
        display.print("Heading Degrees: "); display.println(headingDegrees);
        display.display():
    So, we still use the serial monitor, but that scrolls continually. For the OLED, we need to clear whatever was there (ClearDisplay). position the cursor at the home position (setCursor--operands are column, row-relative to zero), move the text to the buffer (print, println--work just like Serial). and display the buffer contents (display).  Trust me, if you you skip the display.display(), you will not see what you expect.

Step 4 - add the text direction

The Ham Radio book uses what I consider to be an ugly series of if statements to determine which of the 16 compass points (N, NNE, NE, ENE, E and so on) corresponds to the heading in degrees. I replaced this with a 16 element string array, and a function to find the appropriate element.

String directionArray[] = {"N","NNE","NE","ENE","E","ESE", "SE","SSE","S","SSW","SW","WSW","W","WNW","NW","NNW" };   //array for map function 16 compass directions

The function (see the comment about how N is handled, also note that map only works on decimals, so I multiplied by 100 and worked in steps of 2250):

String mapDirection (float headDeg)
{
/*function to return direction in text based on heading in degrees. N is 11.25 degrees each side of 0, so that doesn't work too well with the map function. So if it's N, we say so. This is a nice way of taking the procedural stuff out of line, so the loop code can just ask for the direction
*/
    if (headDeg > 348.75 |  headDeg < 11.26)<11 .26="" 0="" around="" case="" degrees="" direction="North," div="" nbsp="" special="" wrapped="">
    {
        Dir = 0;
    } else {  
//not North, so we eliminate the decimals and map degrees to text in 22.5 degree increments      
        Dir = map(headDeg*100, 1126, 34875,1,15);  //map only works on whole #s so we eliminate 2 decimal places
    }
//and return the selected text compass direction    
    return directionArray[Dir];
}

So, we can use the function directly in the display statement from the last step, which now reads:
    display.print("Heading Degrees: "); display.print(headingDegrees); display.print(" ");
    display.println(mapDirection(headingDegrees));

Step 5 - slow this down (this is where the fun starts)

    Sitting at a desktop computer, it's fun to watch the heading info scroll by on the serial monitor or flash by on the OLED.  However, that'not great for a walk in the woods.  First of all, you probably won't have the project tied to a USB port, it would be powered by a battery. Further, continuous updates would use more battery than you want.  

Option 1: delay
    The first option, the one used in the Ham Radio book, is to code a delay, maybe for a second, to slow things down and reduce the number of times the Feather has to read the sensor. In this case, just code
    delay(1000); 
after displaying the heading.

Option 2: button
    Option 1 still probably yields more measurements than you need, wasting power.  Another possibility would be to only display the heading when the operator asks for it. The Feather Wing has 3 buttons, labeled A, B, and C.  So, in setup we could display "press button B" on the OLED, and in the loop test to see if button B was pressed.  (I'll explain later why I don't use button A).
    The buttons have pull-up resistors, so pressing them causes the pin to go LOW.  On the zero, the buttons are assigned to pins as follows:
    #define BUTTON_A 9
    #define BUTTON_B 6
    #define BUTTON_C 5
Then, in loop, test the button state:
      if (!digitalRead(BUTTON_B))   //means BUTTON_B is LOW, so it's pressed
      {
          [get the sensor data, display on OLED as above]
      } 
      delay(3000) //wait 3 seconds
      clear display
      display "press button" message

    This is all fine and good, but what we want is for the device to sleep unless we want to use it, not to be continually checking to see if the button was pressed,  That requires some additional programming. 

Step 6: interrupt with and without sleeping

I recently bought "Programming Arduino: Getting Started with Sketches" by +Simon Monk. It reintroduced me concepts I first met in Jeremy Blum's (+sciguy14) arduino tutorials (tutorial #10 was on interrupts).  I also did a C Course on Udemy that included interrupts on a TI device..

So, I thought this would be a good application.

Interrupt without sleep
First, I figured I'd replace testing for a button press with code to see if we'd been interrupted. This should have been straightforward. In setup, code an attachInterrupt of the type:
      attachInterrupt(interrupt#, ISR, mode); where
          interrupt# = number of the interrupt (hardware dependent, NOT the same as pin number)
          ISR = interrupt service routine--code that is executed on the interrupt, returning to the interrupt point
          mode = HIGH, LOW, RISING, FALLING, CHANGE depending on how you want the interrupt triggered--when the pin goes HIGH, LOW, LOW to HIGH, HIGH to LOW, or changes, respectively.

This drove me a little nuts.  I went through the Adafruit documentation for the device, and found the pinout diagram. Following what Simon and Jeremy said, I coded the statement for interrupt number 4 (button b is connected to pin 6, and in the pinout.it says EXTINT4 for that pin).  It didn't work.  I looked through the interwebs until I stumbled upon the statement definition on arduino.cc (when all else fails...).  There I learned 2 things. First, on the zero, the pin number = interrupt number (I don't know what EXTINT4 in the pinout means).  Second, for portability, you can use digitalPinToInterrupt(pin#), so if you change to a board that uses a different interrupt number for the same pin you don't have to change your code. Of course, if the second board does not allow interrupts on that pin, you still need to change it. So, I was able to code:
  attachInterrupt(digitalPinToInterrupt(BUTTON_B), headingISR, FALLING);
meaning that when the pin BUTTON_B goes from HIGH to LOW, invoke headingISR

All headingISR does is set a boolean.  The loop code can then check to see if the boolean is set and if so, reset it and go on to display the compass reading. Here are the relevant parts:

        volatile boolean headingRead = false;


        void headingISR() //ISR for button B  press

       {

            headingRead = true;

        }


        in loop:
        if (headingRead) {
            headingRead = false;
            [code to read compass and display heading]   
        } else {
            [delay/clear/display press button message, as above]
        }

Interrupt with sleep
This works nicely, but the device is still active all the time.  The next step was to figure out how to put the M0 into low-power mode (sleep) while it's waiting for the operator to ask for a button to be pushed.  The solution took some investigation to find, but it turns out to be very simple to implement.

What we want to do is to tell the M0 to wait for an interrupt, and to sleep while it's doing that. Fortunately there is a wait-for interrupt instruction:
       __wfi(); 
That's a double underscore, I did not notice that at first and chased a lot of documentation before I figured it out.
If you just use that instruction, it has no effect.  It must be preceded by setting a bit in the System Control Register (SCR).  Bit 2 is the sleep bit. 0 means sleep, 1 means deep sleep.  Not all processors support two sleep modes.  As best I can determine, the Feather M0 supports only deep sleep, because leaving the bit at 0, or setting it to zero, has no effect--just like coding the wfi with no preceding operations. As I write, I have a query on the Adafruit forum to verify this (see discussion under "Sleep Mode" above).
The sequence is:
  SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;  /set sleep bit to 1--do this in setup
then, in loop, code:
  __wfi():
I found the above in this post on the arduino.cc forum. I also found the bit-twiddling written this way:
    SCR->SCR |= 1<<2 1="" 2="" a="" bit="" bits="" div="" left="" nbsp="" or="" scr="" shifted="" the="" to="" with="">
and I figured out that it also works if coded SCR->SCR |= 0b00000100. I think the shifting is the least readable (as in self-documenting), the bits are a little better, and the mask (first example) is the best. I'm sure others disagree.

To implement, leave the attachInterrupt and ISR as above, add the bit setting in setup, and delete the if/else construct for headingRead, replacing it with:
            __wfi();
            [code to read compass and display heading]   
            [delay/clear/display press button message, as above]
The display now reads "press button B" until button B is pressed. On the interrupt, it gets the reading and displays it for the time of the delay (I used 3 seconds), then displays the button message again and goes to sleep.
To have the button message display initially, that code must also be included in setup.

Reminder: the FALLING mode on the attachInterrupt does not work in sleep mode since it involves the timer, so I changed the mode to LOW (since pressing the button sets the pin to LOW).

Step 7 - Battery status

Since the probable use of these device is in the field, on battery, it would be nice to track battery status. The Feather M0 has this ability to read battery voltage. See the Feather M0 Basic Proto tutorial.

A fully charged 3.7V LiPo battery will read ~4.2V.  When it drops below 3,7V, it should be recharged (nice that the Feather has a built-in charger).

The code in the tutorial works as-is.  Note that VBATPIN is A7. A7 is also D9, and D9 is connected to BUTTON_A.  That's why I don't use BUTTON_A for interrupts. The code sort of works, but it's flaky.  

In keeping with learning interrupts, I added on on BUTTON_C.  When the operator presses BUTTON_C, the battery status is displayed for 3 seconds and then the heading is displayed.

To implement, we add VBATPIN:
    #define VBATPIN A7
add a new attachInterrupt:

  attachInterrupt(digitalPinToInterrupt(BUTTON_C), batteryISR, LOW);
add the ISR:
    void batteryISR() //ISR for button A  press
    {
        batteryRead = true;
    }
and in loop, after the wfi, code:
if(batteryRead) //we woke up because Button C was pressed
{ batteryRead = false; //reset the battery flag
   [code to display battery status from tutorial]
   delay(3000); //hold for 3 seconds
 }
[rest of current loop: display compass heading ]

Step 8 - improving the display

The 128x32 dimensions of trhe display is in pixels. The library generates characters of 5x8 pixels, meaning we get 128/8 = ~ 25 columns X 32/8 = 4 rows of characters, using the setTextSize(1), where 1 is a whole number to scale the dimensions.The display is pretty small, and we don't have a lot of text, so we can make some characters larger.  For example, to enlarge just the "B" in the press button b message, we code:
  // Clear the buffer and display the press button message
  display.clearDisplay();
  display.setCursor(0,0);
  display.print("Press Button ");display.setTextSize(2); display.print("B");  
  display.display();
  display.setTextSize(1);  //reset size for next time
The "press button " is standard, but the B is double size (10x16 pixels)--we can only get 2 rows of about 12 characters at that size.

For further readability, I also made the entire battery status and heading messages double size. Another option that I did not choose would have been to setTextColor(INVERSE)--I used WHITE. 

Step 9 - parts and code

Parts List
Adafruit Feather M0 Basic Proto with stacking headers
Adafruit HMC5883l Magnetometer with standard male headers
Breadboard and jumper wires

Code
/********************************
2016-1011 VM
Code sample from Adafruit web site, FeatherWing OLED example
Combined with HMC5883L example as described below
Added power management, interrupts, and battery status display, plus a function
******************/
/***************************************************************************
  This is a library example for the HMC5883 magnentometer/compass

  Designed specifically to work with the Adafruit HMC5883 Breakout
  http://www.adafruit.com/products/1746

  *** You will also need to install the Adafruit_Sensor library! ***

  These displays use I2C to communicate, 2 pins are required to interface.

  Adafruit invests time and resources providing this open source code,
  please support Adafruit andopen-source hardware by purchasing products
  from Adafruit!

  Written by Kevin Townsend for Adafruit Industries with some heading example from
  Love Electronics (loveelectronics.co.uk)

 This program is free software: you can redistribute it and/or modify
 it under the terms of the version 3 GNU General Public License as
 published by the Free Software Foundation.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see .

 ***************************************************************************/
#include
#include
#include
#include
#include

Adafruit_SSD1306 display = Adafruit_SSD1306();

#if defined(ESP8266)
  #define BUTTON_A 0
  #define BUTTON_B 16
  #define BUTTON_C 2
  #define LED      0
#elif defined(ARDUINO_STM32F2_FEATHER)
  #define BUTTON_A PA15
  #define BUTTON_B PC7
  #define BUTTON_C PC5
  #define LED PB5
#elif defined(TEENSYDUINO)
  #define BUTTON_A 4
  #define BUTTON_B 3
  #define BUTTON_C 8
  #define LED 13
#else
//these are the values for the Feather M0
  #define BUTTON_A 9
  #define BUTTON_B 6
  #define BUTTON_C 5
  #define LED      13
#endif

#define VBATPIN A7 //aka D9, which BUTTON_A uses on the M0--don't use BUTTON_A for interrupts if you are also trying to display battery status

#if (SSD1306_LCDHEIGHT != 32)
 #error("Height incorrect, please fix Adafruit_SSD1306.h!");
#endif

/* Assign a unique ID to this sensor at the same time */
Adafruit_HMC5883_Unified mag = Adafruit_HMC5883_Unified(12345);

String directionArray[] = {"N","NNE","NE","ENE","E","ESE", "SE","SSE","S","SSW","SW","WSW","W","WNW","NW","NNW" };   //array for map function 16 compass directions
int Dir=0;  //index for directionArray

const float declinationAngle = 0.261799;  //angle for Orleans, MA
float heading = 0; //this and next for comparison to see if we've been interrupted
volatile boolean headingRead = false; //variables in ISR need to be volatile
volatile boolean batteryRead = false;

#if defined(ARDUINO_SAMD_ZERO) && defined(SERIAL_PORT_USBVIRTUAL)
  // Required for Serial on Zero based boards
  #define Serial SERIAL_PORT_USBVIRTUAL
#endif

void headingISR() //ISR for button B  press
{
    headingRead = true;
}
void batteryISR() //ISR for button A  press
{
    batteryRead = true;
}

String mapDirection (float headDeg)
{
//function to return direction in text based on heading in degrees
//N is 11.25 degrees each side of 0, so that doesn't work too well with the map function
//so if it's N, we say so
//this is a nice way of taking the procedural stuff out of line, so the loop code can just ask for the direction
    if (headDeg > 348.75 | headDeg <11 .26="" 0="" around="" case="" direction="North," nbsp="" p="" special="" wrapped="">    {
        Dir = 0;
    } else {
//otherwise we elimiate the decimals and map degrees to text in 22.5 degree increments    
        Dir = map(headDeg*100,1126,34875,1,15);
    }
//and return the selected text compass direction  
    return directionArray[Dir];
}

void setup() {

  // by default, we'll generate the high voltage from the 3.3v line internally! (neat!)
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // initialize with the I2C addr 0x3C (for the 128x32)
  // init done
  // Show image buffer on the display hardware.
  // Since the buffer is intialized with an Adafruit splashscreen
  // internally, this will display the splashscreen.
  display.display();
  delay(1000);


  pinMode(BUTTON_A, INPUT_PULLUP);
  pinMode(BUTTON_B, INPUT_PULLUP);
  pinMode(BUTTON_C, INPUT_PULLUP);

  // Clear the buffer and display the press button message
  display.clearDisplay();
  display.setCursor(0,0);
  display.print("Press Button ");display.setTextSize(2); display.print("B");
  display.display();
  display.setTextSize(1);  //reset size

  /* Initialise the sensor */
  if(!mag.begin())
  {
    /* There was a problem detecting the HMC5883 ... check your connections */
    display.println("Ooops, no HMC5883 detected ... Check your wiring!");  display.display();
    while(1);
  }

  interrupts(); //enable interrupts (should not need to do this, but just for drill...)
//on the zero (as in Feather M0), interrup#=pin#; we use digitalPintToInterrupt here to provide some portability
//if we change to a different board AND that board allows interrups on the same pins, we don't have to change anything to get the interrupt number
//if we're using the battery function, VBATPIN is A7, also D9, and button A uses D9, so we avoid conflict
  attachInterrupt(digitalPinToInterrupt(BUTTON_B), headingISR, LOW); // when button B is pressed display compass heading; use LOW because FALLING does not work in sleep mode--needs a timer
  attachInterrupt(digitalPinToInterrupt(BUTTON_C), batteryISR, LOW); // when button C is pressed display battery status
//set System Control Register (SCR) sleep bit to deep sleep (do once so wfi (wait for intyerrupt)  in loop waits)
//There are 2 sleep modes, idle and standby (deep) Sleep bit is SCR bit 2, 0 is idle, 1 is standby
// SCB->SCR |= 0b00000100; //just a test to see how to code binary--this works
//   SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;  // set to deep sleep (bit-1) by ORing with 100
 SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk;   //set to idle (bit=0) by ANDing with the inverse of above--leaves all bita alone except sleep, which ANDs to 0
//  SCB->SCR &= 0b11111011;  //another test
//There are 3 idle modes, 0 (CPU),1 (CPU,AHB clock),2 (CPU,AHB,APB). Set this if using IDLE
 PM->SLEEP.reg |= PM_SLEEP_IDLE_APB;  //idle turn off CPU, AFB, APB //Advanced Peripheral Bus (APB)   Advanced High-performance Bus (AHB)

  // display instructions at startup
  //set size and color of text, tell the user we're waiting 10s, then say to press the button
  display.setTextSize(1);       //parameter is scale of 5X8 pixels
  display.setTextColor(WHITE);  //options are BLACK | WHITE | INVERSE
  display.clearDisplay();
  display.setCursor(0,0);
  display.println("initializing for 10s");
  display.display();
//  delay(10000);  //see if sensor will settle down before taking reading
  display.setCursor(0,0);
  display.clearDisplay();
  display.print("Press Button ");display.setTextSize(2); display.print("B"); //button letter is double size
  display.display();
  display.setTextSize(1);  //reset size
}

void loop() {
//the wfi() means we only progress in loop on an interrupt, either button B or C pressed invoking headingISR or batteryISR
//which set the corresponding booleans
//If it was C, we display the battery status for 3 seconds and go on
//in either case, we display the compass heading, complete the loop, and wait for the next button press
 
//wait-for-interrupt has no effect unless the sleep bit is set in the
//System Control Register (SCR)(see setup, in the attachInterrupt area)
while (!(batteryRead || headingRead)) { //if an ISR has not set one of the booleans, wait (they're both initalized to false)
//if the sleep bit is set, we wait after this instruction for an interrupt
 __WFI();  //Double underscore!! (took me a few looks to see that)
}
 if (batteryRead)  //if we got here because operator wants battery info (button C pressed--see batteryISR)
  {
    batteryRead=!batteryRead;  //reset for next pass
    float measuredvbat = analogRead(VBATPIN);
    measuredvbat *= 2;    // we divided by 2, so multiply back
    measuredvbat *= 3.3;  // Multiply by 3.3V, our reference voltage
    measuredvbat /= 1024; // convert to voltage
    display.clearDisplay();
    display.setCursor(0,0);
    display.setTextSize(2);  //set size    
    display.print("VBat: " ); display.println(measuredvbat);
    display.setTextSize(1);  //reset size
    display.print("Press Button ");display.setTextSize(2); display.println("B");
    display.display();
    display.setTextSize(1);  //reset size
    delay(3000);       //hold for 3 sec, then go on and display heading
  }
//however we got here (BUTTON_B or BUTTON_C), read the compass and display
    /* Get a new sensor event */
    headingRead = false;  //set headingRead to false whether we need to or not
    sensors_event_t event;
    mag.getEvent(&event);
    float heading = atan2(event.magnetic.y, event.magnetic.x);
    heading+=declinationAngle;  //add declination for location--initialized as a constant
    // Correct for when signs are reversed.
    if(heading < 0) heading += 2*PI;
    // Check for wrap due to addition of declination.
    if(heading > 2*PI) heading -= 2*PI;
    // Convert radians to degrees for readability.
    float headingDegrees = heading * 180/M_PI;
    // use degrees to determine text Direction in map function
    display.clearDisplay();
    display.setCursor(0,0);
    display.setTextSize(2); display.print(headingDegrees);display.print("="); display.println(mapDirection(headingDegrees));
    display.setTextSize(1);display.print("Press Button ");display.setTextSize(2); display.print("B");
    display.display();
    display.setTextSize(1);  //reset size
//end of loop--back to top to wait for interrupt (next button press)
}

Step 10 - next steps

Now that it works on a breadboard the next step is to put it in an enclosure. I have a Radio Shack 3x2x1" box. I'll add a power LED, put the project on a perf board,make holes for the LED and the microUSB to reprogram and charge the battery, and make an opening for the display and buttons.

One problem is that the HMC588l is sensitive to magnetic and metal objects, including batteries. When I power it with a LiPo, the battery causes it to give erroneous headings.  I need to work on that.



         


  


Monday, August 15, 2016

Control AC Devices over the Internet



When I built the project described in my post on controlling devices from a smartphone, my brother-in-law said "yeah, but can you do that over the internet?"  The answer was yes, but I needed to wait until my +Adafruit Industries Feather Huzzah with ESP8266 WiFi arrived. I had already decided to use adafruit.io as the browser component.  It was super easy to set up a dashboard and two feeds-a button interface to turn the light on and off and a text feed (output from the Huzzah) so I could see what the code thinks it's doing (see the tutorials at the adafruit.io link provided above.

By following the tutorials and code examples, and cloning what I could from tmy bluetooth project (see above), I got it working very quickly.  However, I wanted it to be a useful device that I could give to my brother-in-law. As noted in other posts, he has helped with all kinds of projects.  So, I needed a project box, a circuit board and a few other things, and I needed to do some designing. My first thought was to find a 120V relay and make my own Power Switch Tail.  In studying the schematic, I found that there were all kinds of circuit protections included, so I decided to go with a commercial product.  The powerswitch was US$26, so I looked for an alternative.  I found this AC/DC Control Relay on Amazon. The price has gone up by $2 since I bought it, but it's still $9 less than the powerswitch and has more capability. For example, it has 4 receptacles, 2 normally off and 2 normally on, so it can control multiple devices.

Parts and supplies list, partially annotated

  • AC/DC Comtrol Relay (takes input from the Huzzah: a signal line and a ground line; different from powerswitch tail, which also takes a DC power input; in addition the logic is opposite--power swith tail closes the relay on LOW, this one closes it on HIGH) )
  • +Adafruit Industries Feather Huzzah with ESP8266 WiFi Board
  • 2-wire JST connectors (connect to the relay)
  • LEDs (I used 5mm, one green, one red)
  • resistors (current-limiting for LEDs--I used 330Ohm)
  • 22-gauge solid core hookup wire 
  • tinned copper bus wire
  • project box (this was tight, but I wanted it small)
  • circuit board (I started to use plain perf board, but it broke--these boards were not quite the right size, so I had to file them. The soldering pads were useful.)
  • 3/8" 4-40 machine screws (to attache the circuit board to the standoffs in the box)
  • Drill and bits
  • A set of files (to deburr the drilled holes, and to size the holes properly)
  • Normal project tools: soldering iron and accessories, multimeter, strippers, clippers, helping hands and/or Panavise Jr., and the like
  • Super Glue or the like to fix the LEDs and JST to the box
  • Heat shrink tubing

Concept

The circuit is very simple, maybe deceptively so.  As you will see in the code, it connects to wifi, then connects to adafruit.io, and turns everything off at the start (in setup), then waits for orders from adafruit.io.  The toggle button on the adafruit.io dashboard sends info to the Huzzah, and depending on the state of the lamp (or whatever device you have plugged into the relay), it toggles the state.

The LEDs are used as indicators.  The green LED turns on after successful connection to adafruit.io (if it's not on, there's a problem). The red LED turns on and off with the lamp or other device (also to help see what's going on).

Schematic


Assembly

I spent a lot of time planning where to put the LEDS and the JST connectot. I had some scrap ABS plastic left from my robot cart project, so I used that to test a few options:




I wanted the JST to be recessed, but that didn't work out. I liked the LED holders, but they took up too much room in the box.

I drilled a hole in one end of the box for the USB cable, then drilled 3 3/16" holes in one side for the LEDs and JST connector.  The holes needed to be enlarged, so I took time to get just enough room to get the components through, while still not allowing the whole component to pass.

I had to expand two of the mounting holes on the circuit board to get the screws through. The other two holes did not line up with the standoffs.  I planned to mount the board with the soldering pads down, so I could keep the minimal wiring out of the way.

I positioned the Huzzah on the board, and used hookup wire to fix the back end to the circuit board through the mounting holes.  I had to remove one later to allow access to one of the LED holes in the case.  I ran black hookup wire under the board from the HUZZAH GND pin to the Ground bus on the other side of the board.  I soldered the hookup wire to the GND pin and to the soldering pad in the corner of the opposite side.  I ran tinned copper bus wire from the hookup wire down that side of the board and soldered each corner.


Next, I conencted the 330Ohm resistors through the board to the Ground bus and soldered them to it, then cut the wires on the female end of the JST to length, positioned it in the hole in the box, and soldered the black wire to the Ground bus and the red wire to pin 2 (see schematic above).

Note: if I were to do this again, I would run the Ground bus down the opposite side of the board, solder one end of the resistors to it on the underside, and solder the other ends of the resistors to the ground leads of the LEDs. That would make it neater.

I then took the time to test the circuit to be sure the connections were OK.  Using the Arduino IDE serial monitor and the relay with a lamp plugged in, I found that all was good.  I then added the LEDs, connecting the proper leads to the resistors and to the correct pins, and soldering.  One more test showed the LEDs functioning properly, so I screwed the circuit board to the box.

The final touches were to trim the connections to the resistors, use some electrical tape for insulation, and to super glue the LEDs and JST connector to the box.  Finally, I spliced the male end of the JST to hookup wires leading to the corresponding connections on the relay (there is a terminal block on the relay).  After testing the connections for continuity with my multimeter, I applied heat shrink to each splice and then a long piece of heat shrink to the whole connection.
Enclosure before gluing LEDS and JST and adding screws

Project Video

On my youtube channel

Code

/***************************************************
  Adafruit MQTT Library ESP8266 Example

  Must use ESP8266 Arduino from:
    https://github.com/esp8266/Arduino

  Works great with Adafruit's Huzzah ESP board:
  ----> https://www.adafruit.com/product/2471
  products from Adafruit!

  Written by Tony DiCola for Adafruit Industries.
  Adafruit IO example additions by Todd Treece.
  MIT license, all text above must be included in any redistribution
 ****************************************************/
/*  Adafruit invests time and resources providing this open source code,
  please support Adafruit and open-source hardware by purchasing */

/*2015-08-15 Virgil Machine
  Light goes on, light goes off, internet style
  I used the adfruit.io tutorial and the adafruit esp8266
  tutorial and library examples to get the connection stuff.
  The logic is mainly cloned from my bluetooth example 
  (see virgilmachine.blogspot.com). Connects to WIFI, connects 
  to adafruit.io, turns on green LED to indicate connection,inbitializes 
  logic and devices to off, then waits for instructions from 
  adafruit.io--toggle switch to turn light on and off. Sets relay
  pin to HIGH, also turns on red LED and on-board LED, and vice versa.
  */
#include
#include "Adafruit_MQTT.h"
#include "Adafruit_MQTT_Client.h"

// function prototypes
void connect(void);

/****************************** Pins ******************************************/

#define LAMP           2  // AC/DC Control Relay 
#define LED            0  // on-board LED
#define POWER_LED      4  // lED to show power on
#define LAMP_LED       5  // lED to show LAMP on

/************************* WiFi Access Point *********************************/
//use your own values here

#define WLAN_SSID       "SSID"
#define WLAN_PASS       "PASSWORD"

/************************* Adafruit.io Setup *********************************/

#define AIO_SERVER      "io.adafruit.com"
#define AIO_SERVERPORT  1883
//use your own values here
#define AIO_USERNAME    "USERNAME"
#define AIO_KEY         "adafruit.io KEY"

/************ Global State (you don't need to change this!) ******************/

// Create an ESP8266 WiFiClient class to connect to the MQTT server.
WiFiClient client;

// Store the MQTT server, client ID, username, and password in flash memory.
// This is required for using the Adafruit MQTT library.
const char MQTT_SERVER[] PROGMEM    = AIO_SERVER;
// Set a unique MQTT client ID using the AIO key + the date and time the sketch
// was compiled (so this should be unique across multiple devices for a user,
// alternatively you can manually set this to a GUID or other random value).
const char MQTT_CLIENTID[] PROGMEM  = __TIME__ AIO_USERNAME;
const char MQTT_USERNAME[] PROGMEM  = AIO_USERNAME;
const char MQTT_PASSWORD[] PROGMEM  = AIO_KEY;
int current = 0;
char light_on[] = "Light Goes On";
char light_off[] = "Light Goes Off";
//2016-08-08 vm char stuff replaced by boolean
bool lamp_on = false;  //initalized to off to match the rest of the code

// Setup the MQTT client class by passing in the WiFi client and MQTT server and login details.
Adafruit_MQTT_Client mqtt(&client, MQTT_SERVER, AIO_SERVERPORT, MQTT_CLIENTID, MQTT_USERNAME, MQTT_PASSWORD);

/****************************** Feeds ***************************************/

// Setup a feed called 'lamp' for subscribing to changes.
// Notice MQTT paths for AIO follow the form: /feeds/
const char LAMP_FEED[] PROGMEM = AIO_USERNAME "/feeds/UncleJimThing";
Adafruit_MQTT_Subscribe lamp = Adafruit_MQTT_Subscribe(&mqtt, LAMP_FEED);
// Use feed UncleJimText as TEXT_FEED for publishing changes.
// Notice MQTT paths for AIO follow the form: /feeds/
const char TEXT_FEED[] PROGMEM = AIO_USERNAME "/feeds/UncleJimText";
Adafruit_MQTT_Publish text = Adafruit_MQTT_Publish(&mqtt, TEXT_FEED);
/*************************** Sketch Code ************************************/

void setup() {

  // setup relay and LED pins as outputs and start off as LOW
  pinMode(LAMP, OUTPUT);
  digitalWrite(LAMP, LOW);  //default to off at startup (had this backwards) 
  pinMode(LED, OUTPUT);  //for testing
  digitalWrite(LED, LOW);  //default to off at startup
  // setup pins for indicator LEDS 
  pinMode(LAMP_LED, OUTPUT);
  digitalWrite(LAMP_LED, LOW);  //default to off at startup
  // POWER_LED IS SET to HIGH in connect() routine, after successful connection
  //but default to off until then
  pinMode(POWER_LED, OUTPUT);  //to show if we're connected
  digitalWrite(POWER_LED, LOW);  //default to off at startup 
  
  Serial.begin(115200);

  Serial.println(F("Uncle Jim IO Example"));

  // Connect to WiFi access point.
  Serial.println(); Serial.println();
  delay(10);
  Serial.print(F("Connecting to "));
  Serial.println(WLAN_SSID);

  WiFi.begin(WLAN_SSID, WLAN_PASS);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(F("."));
  }
  Serial.println();

  Serial.println(F("WiFi connected"));
  Serial.println(F("IP address: "));
  Serial.println(WiFi.localIP());

  // listen for events on the lamp feed
  mqtt.subscribe(&lamp);

  // connect to adafruit io
  /* when we connect successfully, the LED is turned on
  so we enter loop with the POWER_LED indicator on
  but it's off until then
  */
  connect();
/****2016-07-17 VM 
     We want the test to say "light goes off at startup, since we set if off in setup
     I think this will work here--***experiment***
****/
     text.publish(light_off);
}

void loop() {

  Adafruit_MQTT_Subscribe *subscription;

  // ping adafruit io a few times to make sure we remain connected
  if(! mqtt.ping(3)) {
    // reconnect to adafruit io
    if(! mqtt.connected())
    { digitalWrite(POWER_LED, LOW);  //turn off LED to show we're not connected  
      connect();
    }  
  }


  // this is our 'wait for incoming subscription packets' busy subloop
  while (subscription = mqtt.readSubscription(1000)) {

    // we only care about the lamp events
    if (subscription == &lamp) {

      // convert mqtt ascii payload to int
      char *value = (char *)lamp.lastread;
      Serial.print(F("Received: "));
      Serial.println(value);
      Serial.print(F("\nlamp-state in: "));
      Serial.println(lamp_on);
      int current = atoi(value);
      if (lamp_on) {  
        Serial.println(light_off);
        text.publish(light_off);
        digitalWrite(LAMP, LOW);  //RELAY
        digitalWrite(LED, LOW);  //ON-BOARD
        digitalWrite(LAMP_LED, LOW); //INDICATOR
      } else {  
        Serial.println(light_on);
        text.publish(light_on);
        digitalWrite(LAMP, HIGH); 
        digitalWrite(LED, HIGH);
        digitalWrite(LAMP_LED, HIGH);          
      } 
      lamp_on = !lamp_on; //toggle lamp state         
      Serial.print(F("\nlamp-state out: "));
      Serial.println(lamp_on);
  
      Serial.print(F("\nSending button value: "));
      Serial.print(value);
      Serial.print("... ");

    }   //if subscription is lamp
  }     //while subscription
}       //loop

// connect to adafruit io via MQTT
void connect() {

  Serial.print(F("Connecting to Uncle Jim IO... "));

  int8_t ret;

  while ((ret = mqtt.connect()) != 0) {

    switch (ret) {
      case 1: Serial.println(F("Wrong protocol")); break;
      case 2: Serial.println(F("ID rejected")); break;
      case 3: Serial.println(F("Server unavail")); break;
      case 4: Serial.println(F("Bad user/pass")); break;
      case 5: Serial.println(F("Not authed")); break;
      case 6: Serial.println(F("Failed to subscribe")); break;
      default: Serial.println(F("Connection failed")); break;
    }

    if(ret >= 0)
      mqtt.disconnect();

    Serial.println(F("Retrying connection..."));
    delay(5000);

  }

  Serial.println(F("Uncle Jim IO Connected!"));
  digitalWrite(POWER_LED, HIGH);  //turn on LED to show we're connected  
}

Saturday, May 28, 2016

Control AC devices with a Smartphone

I found this tutorial and thought it was a cool idea. It describes how to control a light (or any AC device) from a Bluetooth app on a smartphone.

I thought it would be something that my granddaughter would enjoy when she comes this summer. The tutorial gives what I assume is a lot of useful information, but it gets into the weeds of bluetooth much more than I'm interested in at this point, and all I want is to allow a 6-year-old to turn a light on and off.

I followed the tutorial though steps 1 and 2.  Step one uses Blink to flash an LED. Step two adds a button with some interrupt handling. The authors do not mention that in order to get this code to work without a bluetooth nRF8001 module connected, you need to comment out the line "blePeripheral.begin();" in setup--otherwise it hangs there--I guess waiting for a bluetooth board. So I got steps 1 and 2 working.

For step 3, I need to add a board. I have the +Adafruit Industries Bluetooth LE module  (nRF8001 chip) that the authors suggest, but I found it simpler to use the Adafruit Feather Bluefruit LE board (nRF51822 chip) . Since the boards use different Nordic chips, I had to figure out how to get the code to work. The Make tutorial uses the BLEPeripheral library, which I did not get to work, so I used the Adafruit_BluefruitLE_nRF51 library, and the functions are very different.

The code in the Make tutorial looked overly complex for what I wanted,  Since this is my first Bluetooth project, so I wanted to start slowly.  Following the Adafruit tutorial, I installed the " Adafruit_BluefruitLE_nRF51" library and the Adafruit BLE connect app for Android and started working through the example programs. "bleuart_cmdmode" did what I needed: communicate between the module and the app. I set it up so that sending "ON"--all caps--turns the light on and "OFF"--caps again--turns it off,

With nothing but the Feather, a resistor, and an LED I was able to turn the LED on and off from my phone. After 40 years of programming I still get a kick out of seeing something work the first time--no matter how simple.

Next I needed to add the PowerSwitch Tail. It was very simple, except that I did not have a screwdriver small enough to tighten the terminal blocks. Fortunately, my brother-in-law has tools (in addition to skills) and we got it connected. I connected pin 1 to the 3.3V rail on the breadboard, pin 2 to pin 6 on the Feather (the LED pin), and pin 3 to the ground rail on the breadboard.  Then I plugged one end of the PowerSwitch to a 110VAC wall receptacle and plugged a light into the other end.

Power Switch Tail with jumper wires, ready for hookup
The PowerSwitch is a Normally Open relay, so when you hook it up the device (light in my case) is off.  When pin 2 on the PowerSwitch is set LOW, the relay closes and the device turns on.  This is the reverse of the way my code worked, so when the LED was on (Feather pin 6 HIGH), the light was off and vice versa.  This makes a little sense--it gives you some light when the AC light is off, but it's not intuitive for a 6-year-old to say "ON"and turn a light off.  So, I added a pin (5), connected pin 2 of the PowerSwitch to that, and set it LOW for ON and HIGH for OFF.

Project Video.

Now that I feel comfortable with the concepts (as far as I've gone), I can go back to the original Make tutorial and learn more. This is cool.

Here's the code that goes with the video (as mentioned above it's a modification of the "bleuart_cmdmode" example in the Adafruit Bluefruit nRF51 library):

/*********************************************************************
 This is an example for our nRF51822 based Bluefruit LE modules

 Pick one up today in the adafruit shop!

 Adafruit invests time and resources providing this open source code,
 please support Adafruit and open-source hardware by purchasing
 products from Adafruit!

 MIT license, check LICENSE for more information
 All text above, and the splash screen below must be included in
 any redistribution
*********************************************************************/

#include
#include
#if not defined (_VARIANT_ARDUINO_DUE_X_) && not defined (_VARIANT_ARDUINO_ZERO_)
  #include
#endif

#include "Adafruit_BLE.h"
#include "Adafruit_BluefruitLE_SPI.h"
#include "Adafruit_BluefruitLE_UART.h"

#include "BluefruitConfig.h"

/*=========================================================================
    APPLICATION SETTINGS

    FACTORYRESET_ENABLE       Perform a factory reset when running this sketch
   
                              Enabling this will put your Bluefruit LE module
                              in a 'known good' state and clear any config
                              data set in previous sketches or projects, so
                              running this at least once is a good idea.
   
                              When deploying your project, however, you will
                              want to disable factory reset by setting this
                              value to 0.  If you are making changes to your
                              Bluefruit LE device via AT commands, and those
                              changes aren't persisting across resets, this
                              is the reason why.  Factory reset will erase
                              the non-volatile memory where config data is
                              stored, setting it back to factory default
                              values.
       
                              Some sketches that require you to bond to a
                              central device (HID mouse, keyboard, etc.)
                              won't work at all with this feature enabled
                              since the factory reset will clear all of the
                              bonding data stored on the chip, meaning the
                              central device won't be able to reconnect.
    MINIMUM_FIRMWARE_VERSION  Minimum firmware version to have some new features
    MODE_LED_BEHAVIOUR        LED activity, valid options are
                              "DISABLE" or "MODE" or "BLEUART" or
                              "HWUART"  or "SPI"  or "MANUAL"
    -----------------------------------------------------------------------*/
    #define FACTORYRESET_ENABLE         1
    #define MINIMUM_FIRMWARE_VERSION    "0.6.6"
//    #define MODE_LED_BEHAVIOUR          "MODE"
    #define MODE_LED_BEHAVIOUR          "MANUAL" //2016-05-27 vm
    #define TAIL_PIN                    5
    #define LED_PIN                     6
    int LED_STATE;
    char* x;
    char y;
/*=========================================================================*/

// Create the bluefruit object, either software serial...uncomment these lines
/*
SoftwareSerial bluefruitSS = SoftwareSerial(BLUEFRUIT_SWUART_TXD_PIN, BLUEFRUIT_SWUART_RXD_PIN);

Adafruit_BluefruitLE_UART ble(bluefruitSS, BLUEFRUIT_UART_MODE_PIN,
                      BLUEFRUIT_UART_CTS_PIN, BLUEFRUIT_UART_RTS_PIN);
*/

/* ...or hardware serial, which does not need the RTS/CTS pins. Uncomment this line */
// Adafruit_BluefruitLE_UART ble(Serial1, BLUEFRUIT_UART_MODE_PIN);

/* ...hardware SPI, using SCK/MOSI/MISO hardware SPI pins and then user selected CS/IRQ/RST */
Adafruit_BluefruitLE_SPI ble(BLUEFRUIT_SPI_CS, BLUEFRUIT_SPI_IRQ, BLUEFRUIT_SPI_RST);

/* ...software SPI, using SCK/MOSI/MISO user-defined SPI pins and then user selected CS/IRQ/RST */
//Adafruit_BluefruitLE_SPI ble(BLUEFRUIT_SPI_SCK, BLUEFRUIT_SPI_MISO,
//                             BLUEFRUIT_SPI_MOSI, BLUEFRUIT_SPI_CS,
//                             BLUEFRUIT_SPI_IRQ, BLUEFRUIT_SPI_RST);


// A small helper
void error(const __FlashStringHelper*err) {
  Serial.println(err);
  while (1);
}

/**************************************************************************/
/*!
    @brief  Sets up the HW an the BLE module (this function is called
            automatically on startup)
*/
/**************************************************************************/
void setup(void)
{
//2016-05-27 define light pins as output and set them to off
//(relay on switch tail is reverse--flops closed for on when low)
  pinMode(LED_PIN, OUTPUT);
  pinMode(TAIL_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);
  digitalWrite(TAIL_PIN, HIGH);
  LED_STATE = 0;
//  while (!Serial);  // required for Flora & Micro--2016-05-28 vm causes feather to hang if not
//  connected via usb
//2016-05-27 end
delay(500);

  Serial.begin(115200);
  Serial.println(F("Adafruit Bluefruit Command Mode Example"));
  Serial.println(F("---------------------------------------"));

  /* Initialise the module */
  Serial.print(F("Initialising the Bluefruit LE module: "));

  if ( !ble.begin(VERBOSE_MODE) )
  {
    error(F("Couldn't find Bluefruit, make sure it's in CoMmanD mode & check wiring?"));
  }
  Serial.println( F("OK!") );

  if ( FACTORYRESET_ENABLE )
  {
    /* Perform a factory reset to make sure everything is in a known state */
    Serial.println(F("Performing a factory reset: "));
    if ( ! ble.factoryReset() ){
      error(F("Couldn't factory reset"));
    }
  }

  /* Disable command echo from Bluefruit */
  ble.echo(false);

  Serial.println("Requesting Bluefruit info:");
  /* Print Bluefruit information */
  ble.info();

  Serial.println(F("Please use Adafruit Bluefruit LE app to connect in UART mode"));
  Serial.println(F("Then Enter characters to send to Bluefruit"));
  Serial.println();

  ble.verbose(false);  // debug info is a little annoying after this point!

  /* Wait for connection */
  while (! ble.isConnected()) {
      delay(500);
  }
//Serial.println(
  // LED Activity command is only supported from 0.6.6
//  if ( ble.isVersionAtLeast(MINIMUM_FIRMWARE_VERSION) )
//  {
    // Change Mode LED Activity
    Serial.println(F("******************************"));
    Serial.println(F("Change LED activity to " MODE_LED_BEHAVIOUR));
    ble.sendCommandCheckOK("AT+HWModeLED=" MODE_LED_BEHAVIOUR);
    Serial.println(F("******************************"));
//  }
}

/**************************************************************************/
/*!
    @brief  Constantly poll for new command or response data
*/
/**************************************************************************/
void loop(void)
{
  // Check for user input
  char inputs[BUFSIZE+1];

  if ( getUserInput(inputs, BUFSIZE) )
  {
    // Send characters to Bluefruit
    Serial.print("[Send] ");
    Serial.println(inputs);

    ble.print("AT+BLEUARTTX=");
    ble.println(inputs);

    // check response stastus
    if (! ble.waitForOK() ) {
      Serial.println(F("Failed to send?"));
    }
  }

  // Check for incoming characters from Bluefruit
  ble.println("AT+BLEUARTRX");
  ble.readline();
  if (strcmp(ble.buffer, "OK") == 0) {
    // no data
    return;
  }
  // Some data was found, its in the buffer
  Serial.print(F("[Recv] ")); Serial.println(ble.buffer);
//2016-05-27 vm test buffer for ON or OFF, display results  on serial monitor and bluetooth device,
//turn LED and Light on or off (or do nothing)
  if (strcmp(ble.buffer,"ON")== 0) {
      Serial.println("Light ON");
      ble.print("AT+BLEUARTTX=");
      ble.println("Light ON");
      digitalWrite(LED_PIN, HIGH);
      digitalWrite(TAIL_PIN, LOW); //relay logic is reverse of LED
  } else if (strcmp(ble.buffer,"OFF")== 0) {
      Serial.println("Light OFF");
      ble.print("AT+BLEUARTTX=");
      ble.println("Light OFF");
      digitalWrite(LED_PIN, LOW);
      digitalWrite(TAIL_PIN, HIGH);    //relay logic is reverse of LED
  } else {
      Serial.println("Light not changed");
      ble.print("AT+BLEUARTTX=");
      ble.println("Light not changed");
  }
  ble.waitForOK();
}

/**************************************************************************/
/*!
    @brief  Checks for user input (via the Serial Monitor)
*/
/**************************************************************************/
bool getUserInput(char buffer[], uint8_t maxSize)
{
  // timeout in 100 milliseconds
  TimeoutTimer timeout(100);

  memset(buffer, 0, maxSize);
  while( (!Serial.available()) && !timeout.expired() ) { delay(1); }

  if ( timeout.expired() ) return false;

  delay(2);
  uint8_t count=0;
  do
  {
    count += Serial.readBytes(buffer+count, maxSize);
    delay(2);
  } while( (count < maxSize) && (Serial.available()) );

  return true;
}