Friday, May 31, 2013

BufferSerial: let Arduino Stream writes go in a buffer

When you have existing code that uses an Arduino Stream like "Serial" or "SoftwareSerial" data is written part by part on the serial device. Sometimes you want to collect different parts first, maybe adding something like a checksum and only then send everything on one go. By using the BufferSerial construct you can just keep the existing code, pass it the BufferSerial instead of your normal Serial and afterward use the content of the buffer, and send it as one big blob.

 A simple example:
uint8_t mybuffer[32];
BufferSerial bs(mybuffer, 32);
bs.print((int)42);
bs.write(' ');
bs.print(127, HEX);
bs.print("hello");

Serial.println(bs);

The code for BufferSerial is really simple and can be found at: https://github.com/andete/blog-stuff/tree/master/BUFSER1 .

Tuesday, May 28, 2013

NullSerial, a /dev/null Serial Stream for Arduino

Sometimes I use a (Software) Serial device for debugging in some AVR/Arduino code.

An easy way to disable this debugging without going through the entire code or using macros is using this simple dummy serial device instead of the real thing.

Usage example:

#ifdef WANT_DEBUG
#include <softwareserial.h>
SoftwareSerial Debug(4, 5); // RX. TX
#else
#include "NullSerial.h"
NullSerial Debug;
#endif
NullSerial itself is this:
class NullSerial : public Stream
{
private:

public:
  // public methods
  NullSerial() {}
  ~NullSerial() {}
  void begin(long speed) {}
  bool listen() { return true; }
  void end() {}
  bool isListening() { return true; }
  bool overflow() { return false; }
  int peek() { return -1; }

  virtual size_t write(uint8_t byte) { return 0; }
  virtual int read() { return -1; }
  virtual int available() { return 0; }
  virtual void flush() {}
  
  size_t write(const char *str) { return 0; }
  size_t write(const uint8_t *buffer, size_t size) { return 0; }
    
  size_t print(const __FlashStringHelper *) { return 0; }
  size_t print(const String &) { return 0; }
  size_t print(const char[]) { return 0; }
  size_t print(char) { return 0; }
  size_t print(unsigned char, int = DEC) { return 0; }
  size_t print(int, int = DEC) { return 0; }
  size_t print(unsigned int, int = DEC) { return 0; }
  size_t print(long, int = DEC) { return 0; }
  size_t print(unsigned long, int = DEC) { return 0; }
  size_t print(double, int = 2) { return 0; }
  size_t print(const Printable&) { return 0; }

  size_t println(const __FlashStringHelper *) { return 0; }
  size_t println(const String &s) { return 0; }
  size_t println(const char[]) { return 0; }
  size_t println(char) { return 0; }
  size_t println(unsigned char, int = DEC) { return 0; }
  size_t println(int, int = DEC) { return 0; }
  size_t println(unsigned int, int = DEC) { return 0; }
  size_t println(long, int = DEC) { return 0; }
  size_t println(unsigned long, int = DEC) { return 0; }
  size_t println(double, int = 2) { return 0; }
  size_t println(const Printable&) { return 0; }
  size_t println(void) { return 0; }

  // public only for easy access by interrupt handlers
  static inline void handle_interrupt();
};
All this assumes you're not using the return values of the print/println methods.
The full code can be downloaded at github.

Friday, May 24, 2013

madparts release 1.1

A new release of the madparts functional electronics footprint editor is available.

Important changes are:

  • py2exe simple Windows installation: just unzip the zip file and execute madparts.exe
  • linux packages for madparts and pyv8 for Debian and Ubuntu i386 and x86_64
  • a ton of bug-fixes
  • various internal code cleanups
  • simple built-in command line interface for listing of libraries and import and export of footprints
For downloads, and more details head over to the madparts.org website. As always, don't hesitate to contact me with questions or feedback at joost@madparts.org or use the github bugtracker.

The next release will normally contain support for KiCad, but I first I need to learn working with it :)

madparts running on macosx

Tuesday, May 14, 2013

high power LED retrofit (part 4)

So the lamp is working fine. Here it is suspended to the stairs for duration testing.


So dimming works, remote control works and temperature measurement all works. For some reason Lux measurement is not working, I think I may have fried that sensor by accident.

Cabling is still somewhat of a mess, but I was thinking of sorting that out when the lamp is moved to it's final place.

Then a day passed, and I got some time to think :)

Right now there are two locations where active circuitry is located: near the lamp for temperature measurement and fan control, and near the power supply for on/off (relay), wireless, dimming and lux measurement.

These two locations are connected via a whole bunch of cables. And I'm already doing cheap wireless at one point.  That is just silly!

So the next step will be creating a separate board with wireless for each of those two locations. The only wiring remaining between the two locations will be a 12V/GND cable, and of course the LED current cable.

To be continued, but somewhat further in the future as I'm working on some other things at the moment, and I want to design some proper PCBs for the next step that fit in a nice casing and such.

Sunday, May 5, 2013

high power LED retrofit (part 3)

Murphy always strikes multiple times...

fan speed control (PWM) fried

So Friday I accidentally hooked up the fan the wrong way and (as I found out afterwards) killed the fan PWM circuitry.

micro-controller fried

Turned out the fan speed control died "short". This also caused a power surge towards the micro-controller which also died. Strangely it still somewhat worked but when I tried to re-program that always failed. So I replaced the micro-controller for the second time, installed the boot-loader and firmware, and back to business.

Not so fast though, I first need to install a new fan.
So I open the lamp only to find out...

LED broken

I guess my crude mounting with the aluminium brackets put to much strain on the LED die and that combined with heat caused it to crack. Luckily I had a spare lying around.

It's not all bad though, while having the lamp open, I decided to improve some things.

Better LED mounting


 

With the help of some thick aluminium foil cut to the right size and bolted down, I get a much nicer mounting of the LED. While at it I also improved the location of the thermo-couple. It now sits all the way on top of the LED, right at the official temperature measurement point!

Speaking about temperature...

LED PWM

In my early testing with the LED, I found that when driving the LED at near or full blast (800mA-1000mA) the LED got warm really fast, easily reaching 60-70 degrees Celsius.

With my testing with the whole setup, temperature stayed nice and low, peaking at about 42 degrees Celsius. What is happening there?

First thing I did was measure with the thermo-couple of my multi-meter. Exactly the same temperature. Then I measured the current provided to the LED.
Turns out exactly 330mA is provided when PWM is fully on. And I'm using 3.3V PWM... That can't be a coincidence. 

The LED driver officially works on 10V PWM, but I assumed as it was dimming fine with 3.3V PWM that it also supports that. My guess is now they're just using an analog filter to convert the PWM signal to a voltage., and then interpret the Voltage on a scale from 0 to 10V.

Easily fixed though, I created a simple amplifier circuit like this:



Brief testing showed this works just fine! Brief testing also immediately showed the temperature rising again rapidly!

Other heatsink

While inside the lamp anyway, I decided to install another heat-sink:



Some more testing showed that even though the heat-sink does the job, some active (fan) cooling is still highly appropriate.

New fan


Here the new fan is spinning nicely during a test.

Cable mess

While working on the inside anyway, I got annoyed by all the cables easily blocking the fan, etc.. so I re-arranged and fixed them.

That's where the lamp is at now!

To be continued!

Click here for Part 4.

Thursday, May 2, 2013

high power LED retrofit (part2)

In this second part we're looking at the code that is going to control the LED lamp. Remark that the code discussed is the code as it is today (2/5/2013), for the latest code, go to github.
Also note that this is the code without the auto-dimming as that feature is still under test.

Ports & Plugs

static PortI2C bus1(1);
static LuxPlug lux_plug(bus1, 0x39);
static Port fan_port(2);
static Port temperature_port(3);
static Port relay_port(4);
Here the Jeenode ports are set up. We're using all 4 ports. Port 1 is connected to a software I2C bus, which is used by the lux_plug. Port 2 is a simple port, which we use for fan control. Port 3 is another simple port which we use for temperature control and port 4 is finally used to switch the relay.

global state

static uint8_t counter = 0;
#define DATA_MSG_SIZE 11
static uint8_t data_msg[DATA_MSG_SIZE];
Some global state. A counter which is inserted in responses to have an increasing number for testing, and a message buffer which is used for building up the response to the message that queries the system state.

LED PWM

namespace led_pwm {
  static const byte PIN = 9;
  static byte setting = 35;

  static inline void setup() {
    pinMode(PIN, OUTPUT);
    analogWrite(PIN, setting);
  }

  static inline void set_dim(const uint8_t dim) {
    setting = dim;
    analogWrite(PIN, setting);
  }
}
Pretty straightforward, using Arduino pin 9 for PWM, this is not a pin that is part of one of the Jeenode ports, but is instead available on a separate header.

fan

namespace fan {
  static const uint8_t default_pwm_setting = 35;
  static uint8_t pwm_setting = 0;
  volatile uint16_t pulse_count = 0;
  static uint16_t pulse_count_save = 0;
  static uint8_t pulse_per_second = 10;

  void pulse_interrupt() {
    ++pulse_count;
  }

  static inline void normal() {
    pwm_setting = default_pwm_setting;
    fan_port.anaWrite(pwm_setting);
  }

  static inline void setup() {
    fan_port.mode(OUTPUT);
    attachInterrupt(1, pulse_interrupt, FALLING);
    normal();
  }
  static inline void update_pulse_per_second() {
    const uint16_t l = pulse_count;
    pulse_per_second = l - pulse_count_save;
    pulse_count_save = l;
  }

  static inline void full_blast() {
    pwm_setting = 255;
    fan_port.anaWrite(pwm_setting);
  }

  static inline void off() {
    pwm_setting = 0;
    fan_port.anaWrite(pwm_setting);
  }
}
PWM handing is quite similar to the LED PWM, except here the fan_port Jeenode port is used to access the digital pin. In the setup method an interrupt handler is registered for the INT1 interrupt calling pulse_interrupt() on the falling edge. This causes pulse_count to be incremented for each pulse received from the fan. update_pulse_per_second() is then called every second to calculate a new pulse_per_second by simply looking at the difference.

temperature

namespace temperature {
  static int8_t value = 0;

  static inline void beep(const int16_t len = 100) {
    temperature_port.digiWrite(1);
    delay(len);
    temperature_port.digiWrite(0);
  }

  static inline void setup() {
    temperature_port.mode(OUTPUT);
    beep(500);
  }

  static inline void handle() {
    const int16_t t = temperature_port.anaRead();
    value = map(t, 0, 1023, 0, 330); // 10 mV/C
  }
}
Temperature is pretty straightforward. The temperature port digital pin is used to activate the buzzer on the temperature plug, the analog input pin is used to sample the temperature which is then converted from 0-1023 to 0-330 degrees Celsius.

lux

namespace lux {
  static uint16_t value = 0;

  static inline void setup() {
    lux_plug.begin();
  }

  static inline void handle() {
    lux_plug.getData();
    value = lux_plug.calcLux();
  }
}
Lux measurement is really simple, all the work is nicely hidden by the LuxPlug class.

indicator LED

namespace indicator {
  static const byte PIN = 8;

  static inline void on() {
    digitalWrite(PIN, LOW);
  }
  static inline void off() {
    digitalWrite(PIN, HIGH);
  }

  static inline void setup() {
    pinMode(PIN, OUTPUT);
    for (int8_t i = 0; i < 10; ++i) {
      on();
      delay(100);
      off();
      delay(100);
    }
  }
}
In indicator LED is connected to Arduino pin 8 as the Jeenode has no LED build-in and having some visual feedback is always handy while testing.

relay

namespace relay {

  static bool value = false;

  static inline void off() {
    relay_port.digiWrite(HIGH);
    delay(1000);
    relay_port.digiWrite(LOW);
    value = false;
  }

  static inline void on() {
    relay_port.digiWrite2(HIGH);
    delay(1000);
    relay_port.digiWrite2(LOW);
    value = true;
  }

  static inline void setup() {
    relay_port.mode(OUTPUT);
    relay_port.digiWrite(LOW);
    relay_port.mode2(OUTPUT);
    relay_port.digiWrite2(LOW);
    off();
  }
}
The relay is handled by the relay_port. Both the digital and the analog pin of that port are used as digital pins and control the on and off signals of the latching relay. The latching relay is driven by a simple breakout board connected to port 4 of the Jeenode, that just brings up the 3.3V digital signals to the 5V needed for the relay.

RF

namespace rf {
  static inline void setup() {
    // this is node 1 in net group 100 on the 868 MHz band
    rf12_initialize(1, RF12_868MHZ, 100);
  }

  static inline bool available() {
    return (rf12_recvDone() && rf12_crc == 0 && rf12_len >= 1);
  }

  static inline uint8_t command() {
    return rf12_data[0];
  }

  static inline void ack() {
    rf12_sendStart(RF12_ACK_REPLY, 0, 0);
    rf12_sendWait(1);
  }
}
The rf namespace provides just a thin wrapped around the Jeenode provided rf12 functionality. The first byte of a received packet is interpreted as a command. Packets with CRC checksum errors are not looked at.

commands

namespace commands {
  static inline void on() {
    rf::ack();
    relay::on();
  }

  static inline void off() {
    rf::ack();
    relay::off();
  }

  static inline void dim_value() {
    if (rf12_len >= 2) {
      led_pwm::set_dim(rf12_data[1]);
    }
    rf::ack();
  }

  static inline void beep() {
    rf::ack();
    temperature::beep();
  }

  static inline void query_data() {
    rf12_sendStart(RF12_ACK_REPLY, data_msg, DATA_MSG_SIZE);
    rf12_sendWait(1);
    ++counter;
  }


  static inline void handle() {
    indicator::on();
    switch (rf::command()) {
      // standard commands
      case 0: on(); break;
      case 1: off(); break;
      case 2: dim_value(); break;
      case 10: query_data(); break;
      case 11: beep(); break;
    }
    indicator::off();
  }
}
The handle() method here handles the possible commands received via RF. Just some simple dispatcher code.

taking measurements

static inline void take_measurements() {
  indicator::on();
  fan::update_pulse_per_second();
  lux::handle();
  temperature::handle();

  data_msg[0] = counter;
  data_msg[1] = relay::value;
  data_msg[2] = led_pwm::auto_mode;
  data_msg[3] = temperature::value;
  data_msg[4] = fan::pulse_per_second;
  data_msg[5] = led_pwm::setting;
  data_msg[6] = led_pwm::auto_setting;
  data_msg[7] = led_pwm::auto_calculated_pwm;
  data_msg[8] = fan::pwm_setting;
  memcpy(data_msg+9, &lux::value, 2);
  indicator::off();
}
This simple method called every second just gets new fan speed, lux and temperature measurements. They are also stored in the data_msg structure in case they are queried over RF later.

safety checks

static inline void safety_checks() {

  if (temperature::value > 50) {
    Serial.println(F("Danger, temp > 50, turning off lamp!"));
    relay::off();
    return;
  }

  if (!fan::forced) {
    if (temperature::value > 40) {
      Serial.println(F("temp > 40, fan full blast!"));
      fan::full_blast();
    } else {
       if (!relay::value && temperature::value < 30) {
         fan::off();
       } else {
         fan::normal();
       }
    }
  }

  if (fan::pulse_per_second < 20 && fan::pwm_setting > 0) {
    Serial.println(F("fan pulse not found, turning off lamp!"));
    relay::off();
    return;
  }

}
This method, also called every second, does some safety checks concerning temperature and fan speed. The lamp is turned off if the temperature raises above 50 degrees Celsius or when the fan is no longer running. When the temperature goes above 40 degrees Celsius the fan is turned fully on to provide extra cooling. Note that the thresholds are still subject to change.

setup

void setup () {
  Serial.begin(9600);

  indicator::setup();
  rf::setup();
  lux::setup();
  temperature::setup();
  fan::setup();
  led_pwm::setup();
  relay::setup();
}
Really simple, just setting up all individual functions.

loop

void loop () {
  static int8_t loop_count = 0;

  if (rf::available()) {
    commands::handle();
    return;
  }

  ++loop_count;

  // take measurements every second
  if (loop_count % 20 == 0) { 

    take_measurements(); 
    loop_count = 0;
    safety_checks();

  }
  delay(50);
}
Finally the main loop of the sketch. Checks for new RF messages every 50 milliseconds and updates measurements and does safety checks every 20 loops, hence every second. That's it for a close look at the software.

To be continued later with another look at the hardware!

Click here for the next post in this series.

Wednesday, May 1, 2013

high power LED retrofit (part1)

When I got hold on an old large industrial style lamp, I immediately knew I wanted to make something cool out of it.

This picture shows the lamp with the new LED already in place, hanging from a ladder, to give an impression of the size.



I decided to make it a lamp with the following specifications:

  • using a modern, very high power LED for serious brightness and yet good efficiency
  • fully dimmable
  • optional automatic dimming with ambient light sensing
  • temperature sensing and overheat protection
  • passive cooling or else using a low speed inaudible fan
  • remote controllable

Besides that I wanted to use mostly readily available breakout boards to minimize time spent researching sensors and writing drivers.

LED

After some research I decided to use the CREE XLamp CXA2530 which is a very modern LED with a very high brightness and great efficiency. I chose a warm-white color temperature variant.
It has some very impressive specs:
  • Size:  23.85mm x 23.85mm
  • Maximum drive current: 1.5A
  • Maximum power: 61W
  • Light output: 3000 – 5700lm
  • Typical forward voltage @ 0.8 A, 85°C:  37V
  • Viewing angle: 115 degrees
  • Thermal resistance: 0.8 °C/W
For more details and full specifications visit the CREE website. To give you a rough idea: at 30W this LED gives light equivalent to a more then 100W incandescent lamp.

Power supply

As the LED used needs some serious power a very good power supply will be needed. It should be able to provide up to 40V at 1A, be constant current based, and would ideally support dimming out of the box.

After some research I settled on the MeanWell HLM-40H-42B which is really quite expensive but has very high efficiency, can provide the requirement voltage and current, and has dimming built-in.

Dimming works by means of a PWM signal that is provided to the module. This signal will be provided by the micro-controller. The module also supports dimming via a sense resistor.

When no dimming signal is provided the power supply is fully on. This implies that in case the PWM signal from the micro-controller disappears, the LED will be fully on. By shorting the dim connector with a 10k resistor the lamp will fall-back to a ~10% dimmed state when the micro-controller fails to provide the dimming signal. The PWM signal is strong enough to take prescedence when active.

Micro-controller

As micro-controller breakout board I'm using a JeeNode v6



This is an Arduino compatible micro-controller board based on an Atmel atmega328p like in Arduino UNO. It comes with an on-board HopeRF RFM12B 868Mhz RF interface. The provided JeeLib software is installed as a library and provides access to 4 "ports" on the board in a convenient way.
Each port supports what is called a "plug" in the Jee-world.

I'm using two plugs and the other two ports are used directly for their pins.

Lux sensing

Lux sensing is done by means of the JeeLabs LuxPlug. This is based on a simple I2C bus lux measurement IC.



The result is a 16-bit light intensity number that can be used for automatic dimming of the lamp.

Temperature sensing

The temperature is measured near the LED die by means of a K-type thermocouple. The thermocouple signal is amplified by an AD597 IC provided by the JeeLabs Thermo Plug.



The temperature can be used by the micro-controller to shutdown the lamp in case over overheating or to control the fan speed depending on the temperature.

Fan control

Some initial testing showed that  the temperature of the LED rises really fast, even with a large heat-sink attached. Some more testing showed that doing active cooling with a large fan running at a low (inaudible) speed makes a large (>10°C) difference, and a cooler LED is a happier LED with a longer lifetime.

I settled on installing a 12cm PC fan. With the sides cut of it is a perfect fit inside if the top of the lamp, sucking out air upwards.



In order to allow cables to enter the lamp from above without blocking the fan, a simple spacer is needed. The 3D-printer to the rescue! This picture also shows part of the heat-sink at the top. The heat-sink is not really ideal and I plan to use other heat-sinks for future builds.


Finally the fan is mounted and running nicely. This picture also shows the LED mounted on the case with some pieces of aluminium, some screws and some thermal paste. If you look carefully you also see the tip of the thermocouple sitting under the aluminium very near to the LED.


The fan also provides a pulse per rotation signal on another wire. This signal is connected on the pin for the INT1 interrupt facility of the micro-controller. In the interrupt handler a counter is incremented for each activation. By them sampling this counter every second I get a rough value on the rotations/second speed of the fan. If the speed drops below a certain threshold the lamp can be turned of for safety reasons.

Relay

While just fully dimming turns the lamp off, this still means that the LED power supply is powered and in case of some failure the lamp might turn on again.

To be able to fully turn of the driver a relay is inserted on the AC line before the power supply. The specific relay I'm using is a dual-pole, dual coil, latching relay.
Dual-pole means that it can switch both wires of the AC, completely disconnecting the power supply when off.
Dual coil latching means that the relay has a separate coil for turning on and for turning off. To change state one of the coils is activated for a short time only. This means the relay uses no power when active, only when changing state.

Here is the relay wired up to be integrated in the lamp setup.


An added complexity is that the relay coils operate at 5V and the micro-controller only provides 3.3V (and limited current) on the digital outputs.

As the micro-controller gets 5V in this is easily remedied by introducing two low-side N-channel switching mosfets.

The next simple (untested!) schematic shows how this could be done.


[Update: the circuit above is flawed in that the IRF540 mosfets require at least 4V and I'm providing a 3V3 signal. I replaced them by BC935 NPN BJT transistors, and removed R3 and R4]

Remote control

In order to be able to control the lamp remotely I want to make a simple remote based on the Jeelabs Jeenode Micro, and more specific the variant with the build-in boost converter that can be easily run from only one 1.5V battery.

Jeenode Micro


This will be for later though. 

For now I'm testing by means of a Jeelabs Jeelink, which is a simple RFM12B USB dongle running a simple firmware that allows sending and receiving packages via the wireless chip.

Jeelink


High-level schematic

This picture gives a high-level overview of the whole setup.


To be continued

In further blog posts I will have a closer look at how it all fits together in practice, and have a look at the software aspect, and finally see the lamp fully working at is destined location. [ hopefully :) ]

Click here to go the the second part!