Jul 16

Project 121

Project 121 is a rugged temperature logger for high heat environments. It uses the ATtiny4 microcontroller with 32 bytes of ram and 512 bytes of flash storage.




project121 rev1 pcbs

May 15

Arduino library for UT390B

There is now an Arduino library available written by erniejunior to interface with the Uni-T UT390B laser distance meter. It's available here: github.com/erniejunior/UT390B_Arduino_Library

Sep 14

UT390B error numbers

I've gotten a few emails asking for help about the Uni-T UT390B laser distance meter because the operating manual is written in Chinese. I've created a list of the unit's error messages in case it's useful for anyone developing with this device.

enum {
    ERR_BATTERY_LOW = 101,
    ERR_HIGH_TEMP = 152,
    ERR_LOW_TEMP = 153,
    ERR_OUT_OF_RANGE = 154,
    ERR_WEAK_SIGNAL = 155,

May 14

Eagle library for HLK-RM04 wifi module

I've created an eagle library for the $15 Hi-Link HLK-RM04 wifi module. All pins are named and numbered. The only thing you need to look out for is the 2mm pin spacing. Typical protoboard spacing is 2.54mm, so you will likely need to make a breakout board for this module. I'm preparing a follow-up post with details on how to init this module and connect to a secured WPA2 network.


Oct 13

Laser distance meter update: serial commands, timing measurements

In my first post about the laser distance meter I asked for help finding usable commands to control the unit, and within days reader speleomaniac had found a command format and several commands. Let's have a look! But first I'll explain some timing measurements that people have been emailing about.

Timing measurements

The unit is able to take just over 1 measurement per second using the single-shot *00004# command on a flat surface (see below). However there is also a rapid-fire mode which can be activated by holding the ON button for about 1000mS. So far I haven't found a serial command to begin rapid-fire mode (edit: the command is *00084553#, scroll down for details). In rapid-fire mode, the unit takes 100 measurements in quick bursts, and it's capable of scanning just over 3 measurements per second off a smooth, reflective surface. When the laser beam is scanned across soft or bumpy surfaces the measurements take longer, and when the laser beam is out of range or shaky, the unit will output an error and continue until it has attempted all 100 measurements. The output for each failed measurement is:

OUT_RAN dist = 18
OUT_RAN dist = 31
OUT_RAN dist = 30

I've uploaded a text file containing raw sensor measurements, with timestamps added at the beginning of each line to show the speed. It can be downloaded here: ut390b_laser_timing.txt.


Command *00001#

Outputs the message

pMsgWrite TRUE 
pInitDataWrite TRUE

Command *00002#

Takes 3 readings, screen shows "Er". Outputs the 3 measurements in array notation (distance is 103.7mm):

u32Dist[0]=1037  u32Dist[1] =1037 u32Dist[2] =1037
u32temp =0

See below for an explanation of the last line ending in 42#.

Command *00004#

Takes 1 measurement, screen shows the measurement. Outputs the measurement like this: (distance is 112.7mm)

Dist: 1127,curtemp =22 

nDist: 1127,tempDv=0


See below for an explanation of the last line ending in 84#.

Command *00005#

Memory dump. I'm not sure why, but some measurements are recorded to the unit's nonvolatile memory. Not all measurements are stored -- measurements that will be stored will display the following additional message:

Dist: 3122,curtemp =21 

nDist: 3122,tempDv=0

WriteTestData TRUE

The memory dump command outputs these recorded measurements in the following format:


These fields are, in order:

  1. Command type (memory dump)
  2. Counter (starts at 00001 and counts up)
  3. First measurement
  4. Second measurement
  5. Third measurement
  6. Fourth measurement
  7. Fifth measurement
  8. Checksum

Not all measurements will be recorded on a given line (I'm not sure why). The checksum format is as follows: interpret the data bytes between * and # as 2-digit base-10 integers, and add them together (skipping the checksum). Mod the resulting value by 100. For example:

     +00+00+00+00+00+00+00+00+00+00+00+00 = 161
161 % 100 = 61

Which gives the resulting checksum of 61. This can be used to verify the serial output of the unit.

Command *100515#

Turns on laser light (seems to freeze the screen, and the buttons no longer work).

Command *100111#

Some kind of memory dump. I've seen between 1-3 lines of output for this command, depending on how many times the laser has been used. Typical output looks like this:

curent ver:420411

Where the bold values constantly fluctuate up and down (the 97 sometimes becomes 96, etc).

Command *00084553#

Begin rapidfire measurement.

Command #

Repeat previous command.

Thanks to all the readers who have offered info about this laser unit. Good luck!

Aug 13

Parsing laser distance meter serial output

UPDATE: Serial commands and timing information are now available here.

This is a follow-up on my previous article about an arduino-compatible laser distance meter‎ with serial output. I've received several emails asking for example code to parse the serial output. displaying a laser distance measurement on an lcd

int strstart_P(const char *s1, const char * PROGMEM s2)
    return strncmp_P(s1, s2, strlen_P(s2)) == 0;

int getdist(void)
    char buf[64];
    char *comma;
    int dist;
    int rc;

    for (;;) {
        rc = Serial.readBytesUntil('\n', buf, sizeof(buf));
        buf[rc] = '\0';

        if (!strstart_P(buf, PSTR("Dist: ")))

        comma = strchr(buf, ',');
        if (comma == NULL)

        *comma = '\0';

        dist = atoi(buf + strlen_P(PSTR("Dist: ")));
        return dist;

void setup(void)

void loop(void)
    int dist_mm;
    int dist_m;
    char buf[128];

    dist_mm = getdist();
    dist_m = dist_mm / 1000;

    snprintf_P(buf, sizeof(buf),
        PSTR("Laser distance: %d.%dm"),
        dist_m, dist_mm % 1000);


screen shot of laser distance measurements

Jul 13

Translink magnetic ticket format

Translink, the public transit provider for Vancouver, BC, will soon be switching to a stored-value RFID card (the Compass Card), so I figure there's no harm in publishing this now. In short, it's trivial to create Translink tickets which expire many years in the future.

A PDF copy of the report is available at http://qartis.com/translink.pdf.

Some of the code is available at http://github.com/qartis/translink/.

Jul 13

Arduino laser distance meter

UPDATE 2: Serial commands and timing information are now available here.

UPDATE: I've posted code to parse the serial datastream here.

This is a tutorial on how to get serial data out of a cheap handheld laser distance meter, which can be fed into an Arduino or a PC. You can use the distance measurements in all sorts of projects like robots and quadcopters. There are vendors who sell conversion kits that sniff the distance measurement off the LCD and output it over serial. The problem with those kits is that they cost more than the laser meter itself. Luckily I managed to find a laser distance meter that outputs distance measurements over serial directly: the UT390B from Uni-T which sells for about $56 from online retailers (that is an affiliate link). Laser distance meters use precise electronics to measure the length of time it takes a laser beam to reflect back from a target. Handheld units like this one have a range of about 0.2m - 40m with a precision of 2mm. They're a great replacement for HC-SR04 ultrasonic sensors if you need long range measurements.

Preparing the unit

You can see the unit's debugging port underneath the battery cover. To take it apart, remove the 2 visible screws.

ut390b device with battery cover removed showing debug pins

Now you can pry off the cover.

ut390b with front cover open

This ribbon cable connects the unit's keypad. Scrape off the glue and unplug it for now.

the ribbon connecting the two halves of the ut390b device

Next, remove the 2 screws with washers that hold the rubber piece at the front. If you want to remove the screen, it's connected by 4 clips to the circuit board. It's probably a better idea to leave the screen connected, so you can verify that the unit is working properly while you're testing your serial parsing code.
removing the screen from ut390b device DON'T remove these 3 screws. They connect the laser diode to the circuit board, and if you loosen these screws you risk breaking your laser. 3 screws on circuit board which should not be removed The easiest way to access the debug port is by desoldering the battery connector as shown below. Note: this photo shows the 3 screws removed. DO NOT do this, as it's unnecessary and could cause the unit to break in two.removing the ut390b circuit board (don't do this) Finally you can solder some thin wires to the debug port. The pinout is as follows, seen from below the board.pinout of ut390b debug pins. from left to right: GND, RX, TX, BOOT0, VCC (2.7V) We can replace the original keypad with a microcontroller which can simulate button presses to control the unit. The keypad pinout is below. The ribbon connector is 8 pin, 1mm pitch. If there's enough interest, I will assemble and sell a small interface board to break out the necessary signals. pinout of ut390b keypad ribbon connector. from left to right: Unit, n/c, Func, On, GND, Mode, n/c, Off

Decoding the serial output

This unit's serial port runs at 115200 baud (8N1). On bootup, it outputs the following text with \r\n line endings:

curent ver:420411
Year:Jan 21 2013 Time:13:53:10
Iint OK

Notice "APDMIN" and "APDMAX", which are likely calibration values for the avalanche photodiode used to detect the reflected laser beam. A few sloppy printfs as well.. To turn the unit on, connect the ON signal to GND for about 300ms. Once it's on, the ON signal is also used to take a measurement. After a measurement is taken, the unit outputs the following:

Dist: 2827,curtemp =21
nDist: 2827,tempDv=0

The Dist and nDist values are in millimeters. As far as I can tell, the two measurements are always identical. If there's a measurement error (Error 154, out of range or sensor error) the unit will output:

OUT_RAN dist = 30

If the unit can't determine the average distance (Error 160, sensor shaking too much or Error 155, signal too weak) the unit will output one of the following lines:


When you turn it off, it outputs:

WriteTestData TRUE

Serial input?

I'd like to find a way to control the unit over serial. So far all I've found is that sending 0x23 ('#') or 0x73 ('r') will cause the unit to power down. It seems to ignore every other byte, as well as some common english words and modbus commands. If you manage to find any other byte sequences, email me or leave a comment below. If you liked this article, you might also like DORI, my robot project. DORI uses a slightly different laser distance measurement strategy. You can learn more on the project homepage and in my thesis.

Jul 13

Problems with 23K256 and AVR/Arduino

23K256 IC pinout

The Microchip 23K256 is a great little device with 32 KiB of fast static ram over an SPI interface. I had some trouble getting this chip to work, and after searching online it looks like many people have some common problems. I'll show you a brief checklist you can use if you're having trouble like I was.

  1. The HOLD pin. This pin can be used to pause a sequential transfer and resume it immediately without having to re-send the address. If you don't care about this feature, tie the HOLD pin to Vcc (3.3v).
  2. The NC pin. This pin should not be connected to anything, including Vcc or GND. Just leave it floating.
  3. The HOLD bit. Read the datasheet carefully: writing a 1 bit disables the HOLD feature. Make sure you're setting this bit HIGH.
  4. Vcc. If you're using short wires and you're not sharing the SPI bus among several devices, the 23K256 can be powered from 2.7V. But this chip seems to need strong SPI signals with low capacitance. If you're running into problems using the chip on a shared SPI bus, try giving the chip a higher voltage supply -- it can tolerate up to 4.5V. Just don't permanently run the chip above 3.6V or it may become unreliable.
  5. The voltage levels. This chip runs from 2.7V - 3.6V. If you're interfacing with a 5V Arduino, you'll need to convert the Arduino's 5V signals to around 3V using a level converter circuit. This is simple with SPI because all of the data lines are unidirectional -- they're either microcontroller-to-sram or sram-to-microcontroller. The 23K256's 3V logic output should be high enough to be read directly by modern microcontrollers like the AVR in an Arduino. The microcontroller's 5V signals must be dropped to around 3V before they reach the 23K256. A simple voltage divider for these signals (MOSI, SCK, CS) will work at low SPI speeds, and can be built from 2 resistors per signal.
  6. MOSI/MISO. The chip's datasheet labels these pins SI/SO. There are several sources online (including Wikipedia) that say SO means Serial Output. Nope, when we're dealing with SPI, SO always means Slave Out. As in, the data stream going out from the SPI slave device (the 23K256) to the microcontroller.
  7. SS/CS. This is a big gotcha with Arduinos and all Atmel AVR microcontrollers. You can use any gpio pin as a Slave Select (aka Chip Select) line. However, the microcontroller's primary SS pin must be set as an output during SPI operation, even if you're not using it. If the SS pin isn't explicitly configured as an output pin, SPI transactions will mysteriously fail (usually by timing out because the SPIF bit never goes high), because the SPI hardware will be in slave mode. This is an easy mistake to make!
  8. The init sequence. The 23K256 essentially has no init, but we can write a simple routine to make sure communication is working:
#define SRAM_DESELECT() PORTD |= (1 << PORTD7);
#define SRAM_SELECT() PORTD &= ~(1 << PORTD7);

enum {

#define SRAM_MODE 0x41 /* sequential access, hold pin disabled */

uint8_t sram_read_status(void)
    uint8_t val;

    val = spi_write(0xff);

    return val;

uint8_t sram_init(void)
    uint8_t retry;

    DDRD |= (1 << PORTD7); /* set CS pin as output */


    retry = 255;
    while (sram_read_status() != SRAM_MODE && --retry) {

    return retry != 0;

Feb 13

AVR .initN sections and GCC -flto

While programming with avr-gcc I recently ran into a problem. Code placed into the .init3 section with __attribute__((section(".init3"))) wasn't being included in GCC's output. The culprit ended up being GCC's -flto optimization setting. GCC's link-time optimization (LTO) feature aggressively performs garbage collection on unused bits of code (among other things). The AVR linker scripts included with binutils know to always include the .initN and .finiN sections, but by that time LTO has already discarded them. So the moral of the story is you must add __attribute__((used)) to your function:

void func(void) __attribute__((section(".init3"), naked, used));

void func(void)
    /* code */