Map
Grand Prize Winner
(Dec 2012)
Second Prize Winner
(July 2012)

Electronic OBDII Gauge on Luminardo

Let’s make something more complex using Luminardo, a Bluetooth dongle, a OBDII translator ELM327 and a 128×64 pixels graphical monochrome OLED display SSD1306. The plan would be to utilise USB host functionality to communicate to ELM327 via SPP interface and represent such parameters as engine RPM, vehicle speed, battery voltage, remaining fuel on a display. In a way it will be like the well known OBDuino, however, we are going to take full advantage of a graphical display and in our case we are going to have a wireless communication interface. For simplicity sake we won’t implement trip computer to calculate distance traveled, fuel consumption per 100 km or tank distance, instead, we will be focusing on user interface and modular firmware architecture which would allow to integrate additional functionality later and with minimum development effort.

The information displayed on a display in mostly numeric, some of them more significant, some of them less, so most probably we will need to have several font sizes (or fonts). We strongly believe that it this application numeric data would be looked more professional if seven segment font is used. And if so, we would need to add more seven segment fonts as we have only 32×50 sevenSegNumBigFont. To add more options we will introduce 25×40 sevenSegNumMediumFont and 10×16 sevenSegNumSmallFont fonts and define them in glcdfont.h.

Next step is to prepare icons, the plan would be to display a number next to icon so that it would be intuitively clear and obvious what kind of information a number represents. For example, a battery icon for voltage readings, a fuel dispenser icon for tank level, a thermometer icon for temperature readings, well, you’ve got the idea. The will be two icons of the same type – one which fills the whole display for warnings in case of critical levels and one is of a regular size, 18 pixels maximum in height to roughly match with the small seven segment font. We would need the following icons: a car battery, a bluetooth, a fuel dispenser, a thermometer, a ‘car doors open’, a clock. The last two are ‘just in case’. All these icons will be defined in Icons.h.

Let’s also specify the most important architectural considerations. First of all, we will be heavily relying on Bluetooth communication between our Luminardo board and ELM327 OBDII adapter. Therefore, we will have to indicate explicitly connection status and upon boot up don’t progress to any screen with numbers until the connection is successfully established. It would be also necessary to distinguish two different states of connection: successful pairing/connection with ELM327 and successful recognition of the OBDII protocol with ability to communicate to a vehicle. That way to will definitely detect situations when Luminardo can communicate to ELM327 but ELM327 can’t communicate to a vehicle. It would be also highly desirable to display additional information about ELM327 upon connection – for example, its firmware revision number.

Next, we won’t be using delays in our code as it is very wasteful style of programming, especially for embedded devices with their limited computational power and battery power sources. Given that we have only single thread process (which is a limitation of most 8-bit microprocessor systems including Arduino) required delays between subsequent operations will be achieved by introduction states and counters, holding number of milliseconds passed since the previous operation. That way in the main loop we will be checking each counter whether it is time to progress to the next state, execute next operation and update counter with a new value or simply move on to check next counter. The code execution flow in this case will be non-blocking as this is exactly what we want.

Some parameter values will be refreshed less frequent than others. For example, engine RPM and vehicle speed will be requested from the vehicle every time when display is refreshed whereas tank level, battery voltage and temperature values change much slower and therefore they need to be retrieved and updated on the screen less frequent. Given, that from our previous experiments with display we discovered that full frame refresh takes longer than the actual transfer of block of data from microcontroller to OLED, we will be updating only those areas on the screen which are changing. In other words, if we retrieved just new values for PRM and speed we will be updating only appropriate numbers on the screen and nothing else.

With modularity and simplicity in mind, the sketch will be subdivided into several logical parts: the main part Luminardo_OBDII_BT_128x64_i2c.ino with top level logic, settings, definitions and global variables in Luminardo_OBDII_BT_128x64_i2c.h, string constants in Messages.h, auxiliary/utility functions in Utils.h and graphics in Icons.h.

There are two things to be configured before your Luminardo board will be able to find and connect to your ELM327. It is ELM327 Bluetooth address and pin code. Note, that Bluetooth address is specified in reversed order as shown in the code below:

//40:22:11:00:69:58 ELM327 OBDII
uint8_t remote_addr[6] = {0x58, 0x69, 0x00, 0x11, 0x22, 0x40};
SPPi SerialBT(&Btd, "Luminardo", "1234", true, (uint8_t*)&remote_addr);

It is also possible to adjust warning thresholds, these are: battery low voltage level (in volts multiplied by 10) and tank level (percentage remain) as shown in the code below:

#define BATTERY_VOLTAGE_WARN_THRESHOLD 110 //Multiplied by 10
#define TANK_LEVEL_WARN_THRESHOLD 30 //Percentage remain

Luminardo was specifically designed with slim form factor in mind and more small but useful features if compare with Arduino boards. However, given that Luminardo is an Arduino clone, it is possible to build the same functionality using an Arduino board. The final result will be rather bulky but nevertheless, it is still very much possible.

The final source code is freely available below.

Downloads:

1. Luminardo_OBDII_BT_SSD1306_2015_04_29.zip Luminardo OBDII sketch with customised SSD1306 OLED library;

2. USB_Host_Shield_2_SPP_Client_Support_2015_05_05.zip Customised USB Host 2.0 library with SPP client support;

3. Github repository with Luminardo core for Arduino environment;

Toyota Camry: Fixing Sagging Roof & Fitting Car Cameras

As the headlining in our Toyota Camry 1998 was deteriorating we decided to do something about it. But instead of rushing to the nearest auto upholstery/motor trimmers we tempted to do it ourselves. What does it have to do with electronics, an attentive reader would ask. Well, if we were going to deal with roof repair anyway then it would be a great opportunity to fit a dashcamera at the same time hiding all the wiring behind the roof and/or plastic panels. Knowing that it would make us spent quite some time we didn’t even suspect what we were getting ourselves into but nevertheless we still think that it was worth the effort.

A little bit of theory. Sagging roof is usually caused by time and temperature which disintegrate fabric’s foam layer. As a result, fabric is slowly getting loose. The more fabric gets loose, the faster the process goes. What makes it particularly disappointing, there is no quick solution. It is absolutely pointless to inject some glue or adhesive between foam panel and fabric as the panel is covered with disintegrated stuff and has to be thoroughly cleaned first.

Before doing anything with the old fabric we need to get some materials and tools. For a passenger car like our Toyota Camry we would need 2 meters of new gray fabric and adhesive. Not saying that it was the cheapest option but this was what we ended up with after a little bit of shopping around in Sydney. As per usual, here is a our comparison between ‘doing yourself’ and ‘asking someone to do it for you':

Variant I – done by automotive upholstery/motor trimmers
Item Price
Sagging roof fabric replacement Starts from 300.00
Variant II – do it yourself
Item Price You save
2m x LIGHT GREY Foam Backed Brush Nylon “Headlining” 58.00
BOND-IT™ Contact Adhesive “Hard Surfaces” 22.95
Total 80.95 219.05

No doubts, each option has its own pros and cons. Letting professional to do the job is obviously faster but more expensive. We wouldn’t gain any new skills too. When doing ourselves, it is more time consuming, challenging, requires some tools but cheaper, more fun and more experience in the end. It also allows us to deviate from boring standard approach and try to do something else – for example, install video recording system. And finally, when doing ourselves we can control the quality of our work and as a result, may have even better outcome, however, doing something for the first time usually means that inevitably there will be mistakes. In this post will be try to highlight typical pitfalls of this process and help others to avoid our misjudgments and miscalculations.

First step is to dismantle foam panel with old fabric altogether. It requires removal of car’s interior elements including plastic panels running along the windows. If possible, do this with someone’s help, it is very difficult to remove the panel without breaking it with just one pair of hands – this is what happened to us, without knowing in advance we broke the foam in many ways. Also, when removing any plastic part, it is worth remembering that if we are applying too much force then it is a good indication that we are doing something wrong – stop, have some rest and examine the part one more time to understand better how it is fitted and what holds it in place.

Sagging Roof

Sagging Roof

Bring the foam panel to a dry place (preferable outdoors as we will be using toxic substance during our work). Get rid of the old fabric.

Foam Panel With Old Fabric

Foam Panel With Old Fabric

Carefully clean the foam panel of the spongy layer which used to be a foam layer of the fabric. Without doing this all subsequent activities would be absolutely useless as adhesive wouldn’t hold.

Toyota Camry 1998 Cleaning Foam Panel

Toyota Camry 1998 Cleaning Foam Panel

Restore the structure of broken foam panel (if it is broken as it was in our case). We used a two-component epoxy that cost about 2 dollars per pack. Keep in mind that epoxy is very toxic when in liquid state, take all the precautions as directed on epoxy’s label.

Toyota Camry 1998 Foam Panel_Cleaned And Restored

Toyota Camry 1998 Foam Panel_Cleaned And Restored

Prepare new fabric and adhesive. Make sure that foam panel’s surface is clean as even small imperfections will be visible later through new fabric. Get ready the adhesive. Make sure there is no rain coming if you do it outdoors.

Toyota Camry 1998 Ready To Glue New Fabric

Toyota Camry 1998 Ready To Glue New Fabric

Before applying adhesive do the final try on to avoid unpleasant surprises.

Toyota Camry 1998 Fabric Final Try On

Toyota Camry 1998 Fabric Final Try On

Start from the most difficult surface (usually it is the front one). Apply adhesive to the first quarter of the foam panel and to fabric, carefully glue and move on to the next quarter. At all cost avoid overlapping of the fabric with itself after adhesive is applied – it is very difficult to fix such issues. When done with gluing trim the excess of the fabric and let it dry for a few hours until the drying glue doesn’t smell anymore.

Toyota Camry 1998 Fabric Glued

Toyota Camry 1998 Fabric Glued

Before putting the panel back decide how you are going to install video cameras and how to hide wiring. We picked two channel Blackvue, one of the best video recorders currently on the market. It has two, front and rear cameras and the rear one is supposed to be connected with the front one via a coaxial cable. It would be reasonable to hide the cable behind the foam panel.

Again, it would be highly desirable to do this part of job with more than just one pair of hands. This step is even more critical than panel removal – we can’t afford to make even one crack when installing the panel back. When the panel is finally back in its place adjust its location by installing grab-handles.

Plastic Panels Not Secured Yet

Plastic Panels Not Secured Yet

Next step is preparation for sun visors fitment.

Preparation For Sun Visors Fitment

Preparation For Sun Visors Fitment

… and then comes the actual visor fitment.

Sun Visors Fitment

Sun Visors Fitment

Next step is to install front interior lights.

Fitting Front Interior Lights

Fitting Front Interior Lights

Finally installing back primary interior lights…

Fitting Interior Lights

Fitting Interior Lights

And firmly securing the assembly in place.

Fitting Interior Lights

Fitting Interior Lights

Just doing the final touch to the elements of interior…

Final Touches

Final Touches

… and the only remaining parts is plastic panels. Before fitting panels make sure that black metallic brackets are on the panel, not inside slots on a car body otherwise you won’t be able to click panels back in place.

Securing Plastic Panels

Securing Plastic Panels

Put power cable going from the front camera under the right front plastic panel as shown on the picture below. The cable then will go under the dashboard to a cigarette lighter socket where it will be connected with a SCA 3 way quick connect housing.

Camera Power Supply Cable

Camera Power Supply Cable

The final result of the fitted front camera is shown on the picture below. It is practically unnoticeable to the driver as it is positioned behind the mirror.

Front Camera Fitment

Front Camera Fitment

From another angle it is clearly seen how two cables (coaxial and power) disappear behind the panel that we just put back after repair.

Front Camera Fitment

Front Camera Fitment

The rear camera is also neatly mounted on the back with coaxial cable hidden under the panel.

Rear Camera Fitment

Rear Camera Fitment

The two cameras in action can be seen in the following video:

Interfacing SSD1306 OLED Display with Luminardo

Some time ago we began looking for alternative displays for our Luminardo project and got inspired by a DIY OLED interface board. The author found cheap monochrome OLED displays on Ebay, designed an interface board and wrote a C library. In addition to that some more information on driving such display type was found at DGK Electronics. The displays looked bright and thin, the board was compact and simple, the display was controlled via standard either I2C or SPI interface so we decided to give it a try. After doing a bit of search it was discovered that there were big variety of such displays including some which were already fitted on small PCBs yet the price tag was still below $10. Finally we ended up ordering couple of 128×32 displays without PCB and one 128×64 mounted on a PCB and controlled via I2C. When they eventually arrived, we realised that we actually misjudged their size – in fact they were really small, much smaller than our Luminardo so there was probably no point in designing yet another ‘Luminardo display shield’. Nevertheless, let’s get started our experiments.

First of all, we need physically connect OLED display and Luminardo board. Luminardo conveniently has a designated connector for interfacing with I2C devices, it is P4. The wiring between two boards is given in the table below:

Net name Luminardo P4 con. pin num OLED pin name
+3.3V 1 VCC
SDA 2 SDA
SCL 3 SCL
GND 4 GND
Wiring Luminardo and SSD1306 OLED Display via I2C

Wiring Luminardo and SSD1306 OLED Display via I2C

Now we need a library to control the display. There are plenty of different flavors out there but the most comprehensive and well known is the one designed by Adafruit company which is called Adafruit_SSD1306. Well, at least the library seemed the best to us before we began using it, what is wrong with it you we learn pretty soon in this article.

The installation of the library is described in great detail at Adafruit SSD1306 Tutorial. As we are experimenting with I2C version of 128×64 pixels display we will need to run SSD1306_128x64_i2c Arduino sketch. Compile it for Luminardo platform and try to run. If the display doesn’t work, try to initialise I2C interface with 0x3C instead of 0x3D, it worked for us:

display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // used to be 0x3D

Also, note that our display doesn’t have reset pin so we defined a dummy GPIO:

#define OLED_RESET 0
Adafruit_SSD1306 display(OLED_RESET);

Once the display becomes alive and Luminardo happily runs Adafruit demo it is time to create our own picture. Let’s say, we want to design a ‘Magictale’ logo. First, find or draw a picture of icosahedron as the example below:

An Icosahedron

An Icosahedron

The picture is then resized and cropped to fit 128×64 pixels using GIMP and then converted to monochrome using ImageMagick with the following command:

convert  icosa.gif  -monochrome monochrome_icosa.gif

… and resulting in the following picture:

Icosahedron Monochrome

Icosahedron Monochrome

Next step then is to convert monochrome picture into C byte array with help of Picture To C Hex Online converter:

Picture To Byte Array Online Converter

Picture To Byte Array Online Converter

We almost ready to send our picture to the display. However, there is one inconvenient moment: Adafruit’s library has a method drawBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h, uint16_t color) which expects a pointer to a picture byte array and picture width, height being specified separately. It makes things look ugly as those values must be in essence hardcoded every time when we want to draw a picture. Why not to make ‘width’ and ‘height’ part of picture’s byte array? If so, each byte array, apart from picture itself, would have a two byte descriptor which specifies picture format:

static const unsigned char PROGMEM
  Clock[] =
  {
    62, 63, //62x63 pixels
    0x00,0x00,0x01,0xff,0xfc,0x00,0x00,0x03
    ...
    ,0x00,0x00,0x00,0x0f,0xc0,0x00,0x00,0x03
  };

And the generic function-wrapper around drawBitmap will be defined as follows (it also automatically applies adjustment to the screen center):

void drawBitmapCentred(const uint8_t *bitmap, uint16_t color)
{
    uint8_t b_width = pgm_read_byte(bitmap++);
    uint8_t b_height = pgm_read_byte(bitmap++);
    display.drawBitmap((display.width() - b_width) / 2, (display.height() - b_height) / 2, bitmap, b_width, b_height, color);
}

Next step would be to replace standard Adafruit splashscreen with something else. You won’t find splashscreen bitmap explicitly defined anywhere. The trick is in framebuffer which is defined as static uint8_t buffer[SSD1306_LCDHEIGHT * SSD1306_LCDWIDTH / 8] in Adafruit_SSD1306.cpp. The framebuffer is used to keep ‘a snapshot’ of the display content, it is physically located in ATMega’s RAM. So upon application boot the content of the framebuffer is filled with default values which represent Adafruit logo. Obviously, it could be changed if you are annoyed to see Adafruit logo every time when you switch your device on.

The pixels in framebuffer don’t go in the same order as we have it in our bitmaps – from left to right and from top to bottom, so we can’t just replace the content of the buffer with our new picture as it will result in chaotically scattered pixels. But the good thing is that we don’t need to know the format of the framebuffer – all we need to do is to draw a new logo using drawBitmap, drawChar or by drawing any of geometrical primitives and then dump the content of the whole framebuffer as byte array via Serial port. So Adafruit_SSD1306 will need the following method to be defined:

void Adafruit_SSD1306::bufToSerial()
{
    for (uint16_t i = 0; i < SSD1306_LCDHEIGHT * SSD1306_LCDWIDTH / 8; i++)
    {
        Serial.print("0x");    
        Serial.print(buffer[i], HEX);
        Serial.print(", ");  
    }
}

Now, when we get framebuffer dump, we just copy-and-paste it to static uint8_t buffer in Adafruit_SSD1306.cpp. This is what we got as a logo in our case:

Luminardo and SSD1306 OLED Display Custom Splashscreen

Luminardo and SSD1306 OLED Display Custom Splashscreen

Now we came to a point when it would be extremely useful to define more than one font. The standard one embedded into the library is only 5×7 pixels and while there is a functionality to specify font size everything bigger than size one looks really ugly. It would be very handy to have more than just one font at our disposal, for example, a higher definition font which simulates seven segment digits. We are not going to draw new font and just try to reuse the one from UTFT library defined as 2Kb SevenSegNumFont in DefaultFonts.c. The font already has 4 byte descriptor specifying character width and height in pixels, first character defined in font and total number of characters defined (as the is obviously no need to waste space for all 255 characters if we are going to define only 10 digits). We will also add one more byte to font descriptor which would tell a bit more how a character is defined in byte array. The newly added seven segment font will be defined as follows:

// SevenSegNumFont
// Font Size	: 32x50
// Memory usage	: 2005 bytes
// # characters	: 10
static const unsigned char sevenSegNumFont[2005] PROGMEM={
	0x20,0x32,0x30,0x0A, 1 << FONT_OPT_BIT_DIR,

        //the font actually starts here
        0x00,0x00,0x00 ...
};

Let’s have a closer look at the fifth byte in font’s descriptor. It has two options, FONT_OPT_BIT_DIR and FONT_OPT_COLUMNS. The first option tells how character bits are defined in byte array – normally, from left to right/top to bottom or in reversed order. The second option tells whether character is specified column by column or row by row. All these options really depend on font resolution and an attempt to specify all fonts the same way would result in bigger byte arrays and therefore inefficient memory usage.

In order to support multiple fonts, Adafruit_GFX class has two more members: const uint8_t* _currfont; and uint8_t _fontPitch;, implementation of drawChar is significantly redesigned (as there were too many hardcoded things and shortcuts), write method is changed and setFont method is introduced:

#if ARDUINO >= 100
size_t Adafruit_GFX::write(uint8_t c) {
#else
void Adafruit_GFX::write(uint8_t c) {
#endif
  const uint8_t* fontpntr = _currfont;
  uint8_t c_width = pgm_read_byte(fontpntr++);
  uint8_t c_height = pgm_read_byte(fontpntr++);

  if (c_width > c_height)
  {
      uint8_t tmp_width = c_width;
      c_width = c_height;
      c_height = tmp_width;
  }

  if (c == '\n') {
    cursor_y += textsize * c_height + _fontPitch;
    cursor_x  = 0;
  } else if (c == '\r') {
    // skip em
  } else {
    drawChar(cursor_x, cursor_y, c, textcolor, textbgcolor, textsize);
    cursor_x += textsize * c_width + _fontPitch;
    if (wrap && (cursor_x > (_width - textsize * c_width + _fontPitch))) {
      cursor_y += textsize * c_height + _fontPitch;
      cursor_x = 0;
    }
  }
#if ARDUINO >= 100
  return 1;
#endif
}

// Draw a character
void Adafruit_GFX::drawChar(int16_t x, int16_t y, unsigned char c,
			    uint16_t color, uint16_t bg, uint8_t size) 
{
  const uint8_t* fontpntr;

  fontpntr = _currfont;
  uint8_t c_width = pgm_read_byte(fontpntr++);
  uint8_t c_height = pgm_read_byte(fontpntr++);
  uint8_t start_chr = pgm_read_byte(fontpntr++);
  uint8_t end_chr = start_chr + pgm_read_byte(fontpntr++);
  uint8_t fnt_options = pgm_read_byte(fontpntr++);

  uint8_t bt_msk = 0x1;
  if ((fnt_options >> FONT_OPT_BIT_DIR) & 0x1 == 1) bt_msk = 0x80;
  bool column_first = false;
  if ((fnt_options >> FONT_OPT_COLUMNS) & 0x1 == 1) column_first = true;


  if (c < start_chr || c >= end_chr) return;

  c -= start_chr;

  if((x >= _width)            || // Clip right
     (y >= _height)           || // Clip bottom
     ((x + (c_width + 1) * size - 1) < 0) || // Clip left
     ((y + (c_height + 1) * size - 1) < 0))   // Clip top
    return;


  uint8_t line;
  uint8_t x_resolution = c_width / 8;
  if (c_width % 8 != 0) x_resolution++;

  for (uint8_t i = 0; i < c_height; i++)
  { 
      for (uint8_t j = 0; j < x_resolution; j++)
      {
        uint16_t offset = c * x_resolution * c_height + i * x_resolution + j;
        line = pgm_read_byte(fontpntr + offset);


        for (int8_t k = 0; k < 8; k++) 
        {
          if (line & bt_msk) 
          {
            if (size == 1) // default size
            {
              if (column_first)
                drawPixel(x + i, y + j * 8 + k, color);
              else
                drawPixel(x + j * 8 + k, y + i, color);
            }else 
            {  // big size
              if (column_first)
                fillRect(x + ((i * 8 + k) * size), y + (j * size), size, size, color);
              else 
                fillRect(x + ((j * 8 + k) * size), y + (i * size), size, size, color);
            } 
          } 
          else if (bg != color) 
          {
            if (size == 1) // default size
            {
              
              if (column_first)
                  drawPixel(x + i, y + j * 8 + k, bg);
              else
                  drawPixel(x + j * 8 + k, y + i, bg);
            }else 
            {  // big size
              if (column_first)
                fillRect(x + ((i * 8 + k) * size), y + (j * size), size, size, bg);
              else
                fillRect(x + ((j * 8 + k) * size), y + (i * size), size, size, bg);
            }
          }

          if (bt_msk == 1) line >>= 1;  
          else line <<= 1;  

        }
     }
  }
}

void Adafruit_GFX::setFont(const uint8_t* fnt, uint8_t fontPitch)
{
  _currfont = fnt;
  _fontPitch = fontPitch;
}

The final library support three full-blown fonts: 5×7 (smallFont), 8×12 (meduimFont) and 16×16 (bigFont). There is also a way to specify pitch – a distance in pixels between adjacent characters so in real life there is more than 3 differently looking fonts. In addition, there are 10 32×50 digits in seven segment style which make numbers much more neat:

Luminardo  and SSD1306 OLED Display - 7 Segment Large Digits

Luminardo and SSD1306 OLED Display – 7 Segment Large Digits

If you create a counter from 0 to 999 you will notice that it takes visibly longer time to render three big seven segment digits that just two or one. Given, that in order to commit changes to the display same about of data (whole frame buffer) is sent via I2C interface it means that our rendering mechanism in RAM is even slower than I2C bus (which is relatively slow thing). This is not good obviously and there should be some optimisations to the code to be done. But let’s leave it for the next exercise.

Downloads:

1. Adafruit_Luminardo_SSD1306_2015_04_06.zip – supports multiple fonts;

2. Adafruit_Luminardo_SSD1306_Blink_2015_04_08.zip – supports multiple fonts and blinking but uses 2Kb for primary and secondary frame buffers rather than just 1K for a single buffer;

Keyless Entry Remote Battery Replacement

Just recently we faced a problem when in order to lock/unlock doors of our Toyota Camry 1998 with a keyless entry remote we virtually had to be… inside the car as the remote’s effective radius shrank down to about 200 mm and even less! To make it worse, every subsequent button press led to even weaker signal up to a point when the remote was turning into an absolutely useless accessory. The cause for this was undoubtedly a battery in the 17-year-old remote. Quick Internet research suggested that a replacement for ‘such an old car’ is put in a ‘rarity’ category if judge by absolutely mindblowing price tag which normally starts from $80 on ebay! What is even more interesting, such things are claimed to be ‘genuine Toyota remotes’ but there is no a single word about their manufacturing date so we had some doubts about their longevity. And given that we needed to replace two remotes the prospect of paying $160 looked really daunting. So we decided to chose an alternative path.

First challenge was to open the shell to get access to the PCBA and battery. Two parts were fused together so chances of damaging plastic were relatively high. That is why before opening the thing we googled for a replacement and came out with 2 x TOYOTA Remote 1B shells for just $19.50 and free shipment on ebay. Going a bit further, two Lithium batteries cost us $0.99 each so the total typical expenses would look like this:

Variant I – full replacement
Item Price
2 x Keyless Entry Remote 160.00
Total 160.00
Variant II – battery and shell replacement
Item Price You save
2 x Remote 1B Shell 19.50
2 x CR2032 Lithium battery, 2 pins 01.98
Total 28.48 131.52

To crack open the shell we used a couple of screwdrivers, one was thinner and used to make the seal weaker, the second one was used as a leverage. While doing it, keep in mind two things: 1) never mind the damage to the shell and 2) remember, that there is PCBA inside the shell which must be in good condition after the operation. The picture below shows an opened remote:

Keyless Entry Remote Cracked Open

Keyless Entry Remote Cracked Open

On the next picture remote’s PCBA successfully extracted from the shell:

Toyota Camry 1998 Keyless Entry Remote PCBA

Toyota Camry 1998 Keyless Entry Remote PCBA

… and the opposite side of the PCBA reveals that there is no battery holder so we would need to use a soldering iron in order to do the replacement:

Toyota Camry 1998 Keyless Entry Remote PCBA Battery

Toyota Camry 1998 Keyless Entry Remote PCBA Battery

The next picture below shows PCBA and battery for replacement. In our case it was CR2032 with pins. Be careful when ordering such batteries – make sure that the distance between two pins and battery’s height is what you really need. When soldering, try to minimise battery’s overheating. And of course ensure that the polarity is correct:

Toyota Camry 1998 Keyless Entry Remote Before Battery Replacement

Toyota Camry 1998 Keyless Entry Remote Before Battery Replacement

The actual soldering is anything but difficult – all that needs to be done is to desolder and solder two pins:

Soldering New Battery

Soldering New Battery

When soldering is done, trim both pins:

Trimming Battery Pins

Trimming Battery Pins

The battery is finally replaced. Before putting into a new shell it is a good idea to go to your car and check that it works indeed:

Toyota Camry 1998 Keyless Entry Remote After Battery Replacement

Toyota Camry 1998 Keyless Entry Remote After Battery Replacement

Final step: putting the PCBA in a shell. It is a good idea to make sure that there are no chances for short circuit when two plastic parts hold your PCBA in place. In our case we also needed to trim rubber button as our microswitch on the PCBA remained always on.

Trimming Rubber Button

Trimming Rubber Button

The result of our effort is shown below:

Toyota Camry 1998 Keyless EntryRemote In New Case

Toyota Camry 1998 Keyless EntryRemote In New Case

Although in this post we described set of actions for a specific make and model, we believe that the idea is applicable to the majority of modern (and not very modern) cars. The same basic principle remains: quite often it is more cheaper to replace just a few parts rather than replacing the whole module or accessory. In addition, you will gain more skills and dump less which is also beneficial for our planet.

Site has been moved

Our site has been moved recently from http://atmega.magictale.com to http://magictale.com. All old style URLs are still valid as now there is a permanent redirect in place. Everything seems to be fine, however, there is always a chance that something has been overlooked. So if you notice any non-working or incorrectly working link, an image not being properly shown or something like that – please let us know!

Hacking Vodafone K4606 3G HSDPA USB Modem

The prospect of having high speed communication channel between an embedded project and outer world is very tempting. The challenge can be addressed by using a WiFi module in those areas with WiFi coverage being available but what if it is not an option? Then there is no other choice but GSM (also referred as 2G) module which substantially elevates the cost of overall solution and yet it is incapable of providing communication speed high enough for many nowdays needs. For example, prices for GSM Arduino shields currently available at Australian Little Bird Electronics are ranging between $71 and $118! The actual numbers for data exchange rate, however, are not that high and typically are in the vicinity of 9.6 Kbps for 2G networks. On the other hand, there are mobile broadband options like Vodafone USB Extreme 3G+ which can deliver data at up to 42 Mbps (download) and up to 5.76 Mbps (upload). Add to this virtually symbolic price tag for modem – Vodafone 3G+ USB stick would cost you $9(!) and it starts sounding like a fairy tale. The table below represents dramatic difference between the two solutions:

Modem type Communication speed Price, AU$
GSM Arduino Shield 9.6Kbps 71-118
Vodafone K4606 3G HSDPA USB Modem 42Mbps/5.76Mbps 9

Impressed by the inequality we were wondering whether it would be possible to use one of those USB broadband modems for embedded projects and eventually decided to pick Vodafone 3G+ modem to give it a try.

Vodafone K4606

Vodafone K4606

Acknowledging the fact that USB modems are more demanding to host controller’s resources, we were aware that it would be highly unlikely to make even top end Arduino board talk to the Vodafone dongle. So we turned our attention to ARM based solutions like one of mbed boards and finally chose the one with USB host functionality.

mbed LPC1768

mbed LPC1768

The mbed source code repository even had a fully functional library for interfacing with Vodafone mode; however, we didn’t have high hopes (and this turned to be the case shortly after) that it would immediately work with our modem. But before using USB port a few extra components including USB connector need to be wired. This is described in detail at VodafoneUSMModem Cookbook. Given that we were working on a prototype we took the risk of not using two pulldown resistors and to be honest haven’t noticed any detrimental effects during our experiments. Ignoring the advice to power USB modem separately resulted in spurious resets of mbed board so eventually we derived +5V from the second USB port. The final prototype ready for experiments is given below.

Vodafone K4606 Under Test

Vodafone K4606 Under Test

As a next step, VodafoneUSBModemHTTPClientTest - main.cpp along with the VodafoneUSBLibrary has been imported into mbed development environment. Subsequently going through all project’s folders and subfolders the underlying set of libraries has been updated to accommodate the latest changes made since the release of VodafoneUSBLibrary as show on the picture below.

VodafoneUSBModemHTTPClientTest After Project Import

VodafoneUSBModemHTTPClientTest After Project Import. Green arrow indicates that there is update avaialble

An interesting observation: updating the parent folder doesn’t cause children folders to be updated as well – it has to be done manually as shown on the picture below.

VodafoneUSBModemHTTPClientTest Updates Available

VodafoneUSBModemHTTPClientTest Updates Available. Update of the parent folder doesn’t cause children folder to be updated.

Eventually the code got into compilable state. It is fair to say that USB modem requires quite substantial bit of functionality on host controller. It includes USB Host and TCP stacks, PPP client along with CHAP/PAP authentications, DHCP client, TCP/UDP socket implementation, network streams and so on.

VodafoneUSBModemHTTPClientTest Successful Compilation

VodafoneUSBModemHTTPClientTest Successful Compilation

Before running the code APN was replaced with Australian Vodafone settings in Main.cpp

int ret = modem.connect("live.vodafone.com");

The compiled code then was uploaded to mbed board for the first time. As expected, nothing worked. There was nothing happening in terminal application so we added one more line in VodafoneUSBModem/core/dbg.h:

...
#ifdef __cplusplus
extern "C" {
#endif

#define __DEBUG__ 4

void debug_init(void);
...

… and immediately got plenty of useful information:

The most interesting output is given below. The driver was trying to detect modem type by discovered device’s VID and PID and obviously it didn’t know anything about our modem model. What has also become obvious after quick analysis of the output and source code was that the driver was designed to retrieve information about USB interfaces and endpoints slightly different depending on the modem type. In addition, upon being powered up, each modem initially works as USB mass storage device allowing to install device drivers and then it is switched to modem mode by a unique byte sequence. So as a bare minimum for our dongle it was necessary to implement its own initialiser and find out its unique combination of bytes to make it act as a modem.

[START]
...
[WARN] Module USBHALHost.cpp - Line 351: Device connected!!
...
[DBG] Module USBHost.cpp - Line 621: CLASS: 00   VID: 12D1       PID: 1F19
[DBG] Module WANDongle.cpp - Line 195: *initializer=100005e0
[DBG] Module WANDongle.cpp - Line 196: (*initializer)->getSerialVid()=12d1
[DBG] Module WANDongle.cpp - Line 197: (*initializer)->getSerialPid()=14c9
[DBG] Module WANDongle.cpp - Line 195: *initializer=100005f4
[DBG] Module WANDongle.cpp - Line 196: (*initializer)->getSerialVid()=19d2
[DBG] Module WANDongle.cpp - Line 197: (*initializer)->getSerialPid()=1181
[DBG] Module WANDongle.cpp - Line 195: *initializer=10000608
[DBG] Module WANDongle.cpp - Line 196: (*initializer)->getSerialVid()=12d1
[DBG] Module WANDongle.cpp - Line 197: (*initializer)->getSerialPid()=14cf
[DBG] Module WANDongle.cpp - Line 195: *initializer=1000061c
[DBG] Module WANDongle.cpp - Line 196: (*initializer)->getSerialVid()=12d1
[DBG] Module WANDongle.cpp - Line 197: (*initializer)->getSerialPid()=1506
[DBG] Module WANDongle.cpp - Line 195: *initializer=10000630
[DBG] Module WANDongle.cpp - Line 196: (*initializer)->getSerialVid()=12d1
[DBG] Module WANDongle.cpp - Line 197: (*initializer)->getSerialPid()=1001
[DBG] Module WANDongle.cpp - Line 195: *initializer=10000640
[DBG] Module WANDongle.cpp - Line 196: (*initializer)->getSerialVid()=1546
[DBG] Module WANDongle.cpp - Line 197: (*initializer)->getSerialPid()=1102
...
[DBG] Module USBHost.cpp - Line 645: device enumerated!!!!
[DBG] Module WANDongle.cpp - Line 80: Device has VID:12d1 PID:1f19
[DBG] Module VodafoneUSBModem.cpp - Line 527: Trying to connect the dongle
...

Searching through the internet we found that people had already discovered what the magic byte sequence was. They also provided dongles’s PID and VID for modem mode:

daxzor:

“The K4606 can also be switched to a regular modem by sending it:

55534243123456780000000000000011060000000000000000000000000000

The new id will be 12d1:1001 so the TargetProduct should be 0x1001″

Then we enhanced WANDongleInitializer.cpp with a new VodafoneK4606Initializer class taking K3773 as a base. Apart from just adding new initialiser there was one more problem to mitigate – a Huawei MU509 modem which had the same Vid:Pid combination in serial mode (12d1:1001) so after switching to serial mode the driver didn’t keep track on what was discovered before and simply kept picking up wrong initialiser. The issue was addressed by adding m_lastDongle in WANDongle.cpp, the modified files are available here. With the two enhancements the output became as shown on the video below:

Newly generated debug output (shown below) suggested that this time the modem was successfully switched to serial mode, correctly detected as K4606 so that its Vid:Pid pair become 12D1:1001 as expected, then the driver discovered three interfaces but skipped all endpoints that is why when it tried to connect serial ports both endpoints were returned as ‘Ep 00000000′ – means NULL pointers or, in other words, not initialised.

[START]
...
[DBG] Module USBHost.cpp - Line 621: CLASS: 00   VID: 12D1       PID: 1F19
[DBG] Module WANDongle.cpp - Line 204: *initializer=100021f4
[DBG] Module WANDongle.cpp - Line 205: (*initializer)->getSerialVid()=12d1
[DBG] Module WANDongle.cpp - Line 206: (*initializer)->getSerialPid()=14c9
[DBG] Module WANDongle.cpp - Line 204: *initializer=10002208
[DBG] Module WANDongle.cpp - Line 205: (*initializer)->getSerialVid()=19d2
[DBG] Module WANDongle.cpp - Line 206: (*initializer)->getSerialPid()=1181
[DBG] Module WANDongle.cpp - Line 204: *initializer=1000221c
[DBG] Module WANDongle.cpp - Line 205: (*initializer)->getSerialVid()=12d1
[DBG] Module WANDongle.cpp - Line 206: (*initializer)->getSerialPid()=14cf
[DBG] Module WANDongle.cpp - Line 204: *initializer=10002230
[DBG] Module WANDongle.cpp - Line 205: (*initializer)->getSerialVid()=12d1
[DBG] Module WANDongle.cpp - Line 206: (*initializer)->getSerialPid()=1506
[DBG] Module WANDongle.cpp - Line 204: *initializer=10002244
[DBG] Module WANDongle.cpp - Line 205: (*initializer)->getSerialVid()=12d1
[DBG] Module WANDongle.cpp - Line 206: (*initializer)->getSerialPid()=1001
[DBG] Module WANDongle.cpp - Line 204: *initializer=10002254
[DBG] Module WANDongle.cpp - Line 205: (*initializer)->getSerialVid()=1546
[DBG] Module WANDongle.cpp - Line 206: (*initializer)->getSerialPid()=1102
[DBG] Module WANDongle.cpp - Line 204: *initializer=10002268
[DBG] Module WANDongle.cpp - Line 205: (*initializer)->getSerialVid()=12d1
[DBG] Module WANDongle.cpp - Line 206: (*initializer)->getSerialPid()=1001
...
[DBG] Module USBHost.cpp - Line 776: !!!!!!!!!!!!! bulkwrite finished
[DBG] Module WANDongle.cpp - Line 120: Switched OK
[DBG] Module VodafoneUSBModem.cpp - Line 527: Trying to connect the dongle
[DBG] Module WANDongle.cpp - Line 48: Trying to connect device
[WARN] Module USBHALHost.cpp - Line 351: Device connected!!
...
[DBG] Module USBHost.cpp - Line 621: CLASS: 00   VID: 12D1       PID: 1001
[DBG] Module WANDongle.cpp - Line 198: Initializer has been already detected in previous step
...
[DBG] Module WANDongleInitializer.cpp - Line 755: *K4606 parsing intf 0, intf_class 2, m_currentSerialIntf 0
[DBG] Module USBHost.cpp - Line 687: Ep DESC
[DBG] Module USBHost.cpp - Line 687: Ep DESC
[DBG] Module USBHost.cpp - Line 687: Ep DESC
[DBG] Module WANDongleInitializer.cpp - Line 755: *K4606 parsing intf 0, intf_class 2, m_currentSerialIntf 0
[DBG] Module USBHost.cpp - Line 687: Ep DESC
[DBG] Module USBHost.cpp - Line 687: Ep DESC
[DBG] Module WANDongleInitializer.cpp - Line 755: *K4606 parsing intf 0, intf_class 2, m_currentSerialIntf 0
[DBG] Module USBHost.cpp - Line 687: Ep DESC
[DBG] Module USBHost.cpp - Line 687: Ep DESC
...
[DBG] Module WANDongle.cpp - Line 81: Device has VID:12d1 PID:1001
[DBG] Module WANDongle.cpp - Line 85: m_pInitializer=10002268
[DBG] Module WANDongle.cpp - Line 86: m_pInitializer->getSerialVid()=12d1
[DBG] Module WANDongle.cpp - Line 87: m_pInitializer->getSerialPid()=1001
[DBG] Module WANDongle.cpp - Line 90: The dongle is in virtual serial mode
[DBG] Module WANDongle.cpp - Line 93: Num serial ports: 2
[DBG] Module WANDongle.cpp - Line 101: Connecting serial port #1
[DBG] Module WANDongle.cpp - Line 102: Ep 00000000
[DBG] Module WANDongle.cpp - Line 103: Ep 00000000

At this stage it is, perhaps, worthwhile to give a bit of theory. Normally, a generic modem has just one serial port. However, this modem had two serial ports, each one was represented by one interface, each interface, in its own turn, had two endpoints, inbound and outbound datastreams. One serial port was intended for use by AT commands and the other one was for PPP transport. As we already noticed, this modem had three interfaces, not just two, and most probably the third one was for uploading firmware updates or something like that, the good news was that we wouldn’t need it. So our next task was to decide which two interfaces out of three to pick, which endpoints are for writing data and which are for reading data. Eventually we found (as we thought) the right combination, the changes are highlighted below:

bool VodafoneK4606Initializer::parseInterface(uint8_t intf_nb, uint8_t intf_class, uint8_t intf_subclass, uint8_t intf_protocol) //Must return true if the interface should be parsed
{
  if( m_hasSwitched )
  {
    DBG("*K4606 parsing intf %d, intf_class %d, m_currentSerialIntf %d", intf_nb, intf_class, m_currentSerialIntf);    
    
    if( intf_class == 0xFF )
    {
      if( (m_currentSerialIntf == 0) || (m_currentSerialIntf == 4) )
      {
        m_currentSerialIntf++;
        return true;
      }
      m_currentSerialIntf++;
    }
  }
  else
  {
    if( (intf_nb == 0) && (intf_class == MSD_CLASS) )
    {
      return true;
    }
  }
  return false;
}

It also has been noticed that with debug output enabled application kept stopping working at some arbitrary point in time displaying ‘blue lights of death’. With the baudrate ramped up from 9600bps to 460800bps the problem went away and never occured again. Here are the changes than needed to be done in order to charge baudrate:

#include "mbed.h"
#include "VodafoneUSBModem.h"
#include "HTTPClient.h"

#define DBG_PORT_BAUD 460800
Serial pc(USBTX, USBRX);

void test(void const*) 
{
...
}


int main()
{
  pc.baud (DBG_PORT_BAUD);  
  Thread testTask(test, NULL, osPriorityNormal, 1024 * 4);
  DigitalOut led(LED1);
  while(1)
  {
    led=!led;
    Thread::wait(1000);  
  }

  return 0;
}

Resulted debug output of the third experiment with correctly parsed and used interfaces and endpoints is given below:

After filtering important lines from the third experiment debug we got the following list. It is clearly seen that interfaces and their endpoints were parsed, both serial ports were successfully opened, modem was initialised, APN was set but for some reasons PPP failed to open. Given that the host controller didn’t receive a single byte in reply to the very first PPP packet there were really two potential reasons for that: either nobody was listening on a remote side or communication channel just didn’t work.

[START]
...
[DBG] Module WANDongleInitializer.cpp - Line 717: Vodafone K4606 MSD descriptor found on device 100039b0, intf 0, will now try to switch into serial mode
...
[DBG] Module WANDongle.cpp - Line 120: Switched OK
...
[DBG] Module WANDongleInitializer.cpp - Line 755: *K4606 parsing intf 0, intf_class 2, m_currentSerialIntf 0
[DBG] Module USBHost.cpp - Line 675: ADD INTF 0 on device 100039b0: class: 2, subclass: 2, proto: 255
...
[DBG] Module USBHost.cpp - Line 311: USBEndpoint created (10003870): type: 2, dir: 2, size: 64, addr: 2
[DBG] Module USBHost.cpp - Line 698: ADD USBEndpoint 10003870, on interf 0 on device 100039b0
[DBG] Module USBHost.cpp - Line 337: New ep 10003870
...
[DBG] Module USBHost.cpp - Line 311: USBEndpoint created (100038b8): type: 2, dir: 1, size: 64, addr: 1
[DBG] Module USBHost.cpp - Line 698: ADD USBEndpoint 100038b8, on interf 0 on device 100039b0
[DBG] Module USBHost.cpp - Line 337: New ep 100038b8
[DBG] Module WANDongleInitializer.cpp - Line 755: *K4606 parsing intf 1, intf_class 2, m_currentSerialIntf 1
...
[DBG] Module WANDongleInitializer.cpp - Line 755: *K4606 parsing intf 1, intf_class 2, m_currentSerialIntf 2
[DBG] Module USBHost.cpp - Line 675: ADD INTF 1 on device 100039b0: class: 2, subclass: 2, proto: 255
...
[DBG] Module USBHost.cpp - Line 311: USBEndpoint created (10003900): type: 2, dir: 2, size: 64, addr: 4
[DBG] Module USBHost.cpp - Line 698: ADD USBEndpoint 10003900, on interf 1 on device 100039b0
[DBG] Module USBHost.cpp - Line 337: New ep 10003900
...
[DBG] Module USBHost.cpp - Line 311: USBEndpoint created (10003948): type: 2, dir: 1, size: 64, addr: 3
[DBG] Module USBHost.cpp - Line 698: ADD USBEndpoint 10003948, on interf 1 on device 100039b0
[DBG] Module USBHost.cpp - Line 337: New ep 10003948
...
[DBG] Module USBHost.cpp - Line 645: device enumerated!!!!
[DBG] Module WANDongle.cpp - Line 81: Device has VID:12d1 PID:1001
...
[DBG] Module WANDongle.cpp - Line 90: The dongle is in virtual serial mode
[DBG] Module WANDongle.cpp - Line 93: Num serial ports: 2
[DBG] Module WANDongle.cpp - Line 101: Connecting serial port #1
[DBG] Module WANDongle.cpp - Line 102: Ep 10003900
[DBG] Module WANDongle.cpp - Line 103: Ep 10003948
...
[DBG] Module USBHost.cpp - Line 480: Now do queue transfer on ep 10003900
...
[DBG] Module WANDongle.cpp - Line 101: Connecting serial port #2
[DBG] Module WANDongle.cpp - Line 102: Ep 10003870
[DBG] Module WANDongle.cpp - Line 103: Ep 100038b8
...
[DBG] Module USBHost.cpp - Line 480: Now do queue transfer on ep 10003870
...
[DBG] Module VodafoneUSBModem.cpp - Line 531: Great the dongle is connected - I've tried 5 times to connect
...
[DBG] Module ATCommandsInterface.cpp - Line 69: AT interface opened
[DBG] Module VodafoneUSBModem.cpp - Line 572: Sending initialisation commands
[DBG] Module ATCommandsInterface.cpp - Line 77: Sending ATZ E1 V1
...
[DBG] Module ATCommandsInterface.cpp - Line 625: Processing read line [OK]
[DBG] Module SMSInterface.cpp - Line 327: AT code is OK
...
[DBG] Module ATCommandsInterface.cpp - Line 224: Executing command AT+CNMI=2,1,0,0,0
[DBG] Module ATCommandsInterface.cpp - Line 625: Processing read line [OK]
...
[DBG] Module ATCommandsInterface.cpp - Line 224: Executing command AT+CREG?
[DBG] Module ATCommandsInterface.cpp - Line 625: Processing read line [+CREG: 2,1,"00F7","04C7AFF2"]
[DBG] Module ATCommandsInterface.cpp - Line 677: OK result received
...
[DBG] Module ATCommandsInterface.cpp - Line 224: Executing command AT+CGDCONT=1,"IP","live.vodafone.com"
[DBG] Module ATCommandsInterface.cpp - Line 625: Processing read line [AT+CGDCONT=1,"IP","live.vodafone.com"]
[DBG] Module ATCommandsInterface.cpp - Line 677: OK result received
...
[DBG] Module VodafoneUSBModem.cpp - Line 245: APN set to live.vodafone.com
[DBG] Module VodafoneUSBModem.cpp - Line 251: Connecting
[DBG] Module VodafoneUSBModem.cpp - Line 270: Connecting PPP
...
[DBG] Module PPPIPInterface.cpp - Line 239: Sending ATH
[DBG] Module PPPIPInterface.cpp - Line 273: Got ATH
[DBG] Module PPPIPInterface.cpp - Line 287: Got
OK
...
[DBG] Module PPPIPInterface.cpp - Line 108: Sending ATD *99#
[DBG] Module PPPIPInterface.cpp - Line 134: Got ATD *99#

CONNECT
...
[DBG] Module PPPIPInterface.cpp - Line 145: Transport link open
...
[DBG] Module USBSerialStream.cpp - Line 140: Trying to write 46 chars
[DBG] Module USBSerialStream.cpp - Line 50: Trying to read at most 1504 chars
...
[DBG] Module USBSerialStream.cpp - Line 140: Trying to write 46 chars
...
[DBG] Module USBSerialStream.cpp - Line 140: Trying to write 46 chars
...
[DBG] Module USBSerialStream.cpp - Line 140: Trying to write 46 chars
...
[DBG] Module USBSerialStream.cpp - Line 140: Trying to write 46 chars
...
[DBG] Module USBSerialStream.cpp - Line 140: Trying to write 46 chars
...
[DBG] Module USBSerialStream.cpp - Line 140: Trying to write 46 chars
...
[DBG] Module USBSerialStream.cpp - Line 140: Trying to write 46 chars
...
[DBG] Module USBSerialStream.cpp - Line 151: Writing 46 chars
[DBG] Module USBSerialStream.cpp - Line 99: Aborted
...
[DBG] Module VodafoneUSBModem.cpp - Line 273: Result of connect: Err code=15
Could not connect
[DBG] Module USBSerialStream.cpp - Line 94: Timeout
[DBG] Module ATCommandsInterface.cpp - Line 344: Nothing read
...

At that point it was really unclear why PPP was unable to start so we decided to plug the modem into Linux machine and using Wireshark sniff USB traffic in order to find what we were doing differently. We didn’t realise how much there was yet to be discovered and how simple the final change would be.

USB traffic captured by Wireshark is shown below. The dongle was plugged into USB port shortly after the start of the capturing application. The modem successfully connected to the Internet and Kubuntu recognised the hardware without need to install drivers or doing something manually. However, two strange things have been discovered. First of all, from OS point of view, the hardware was not a modem but rather a wired Enternet adapter. Secondly, after filtering and analysing USB packets it turned out that the very last packet when Kubuntu was talking to the dongle as to a Mass Storage Device (MSD) was a byte sequence… slightly different from the one we used in order to switch the dongle to modem mode. By the look of things, the dongle could pretend to be not only Mass Storage Device or modem but also as an Ethernet adapter. And if so, we needed to use the other byte sequence in order to force the dongle to switch to modem mode and only then try to establish Internet connection again.

What was happening on a Linux machine after plugging in Vodafone modem with default settings is shown in video below. lsusb -t command indicated that the dongle was recognised as 'cdc_ether'. Shortly after ifconfig displayed one more additional Ethernet interface eth1. dmesg suggested that after mode switch the dongle had Vid:Pid combination set to 12d1:157b.

By forcing the dongle to switch to modem mode result was quite different. lsusb -t command indicated that the dongle was recognised but no appropriate driver was loaded. No additional interface appeared in the list generated by ifconfig. dmesg suggested that after mode switch the dongle had Vid:Pid combination set to 12d1:1001 (just what we had during our experiments with mbed board!)

Then it took us some time to understand how to load correct driver for modem mode. We were stuck until we found this discussion and an original way of solving the problem:

bmork:
which is good in the sense that it confirms that no driver has captured any of the interfaces. If the modem really has vid:pid = 12d1:1c05, then something *should* happen when you type:

Code:
echo 12d1 1c05 >/sys/bus/usb-serial/drivers/option1/new_id

If you have Linux v3.5 or later, then you can verify the list of dynamically added IDs by doing

Code:
cat /sys/bus/usb-serial/drivers/option1/new_id

Finally Linux loaded the driver and assigned three interfaces to ttyUSB0, ttyUSB1 and ttyUSB2. After that the only remaining part was to configuire wvdial and run it watching how PPP interface was being established. Then by opening new terminal it became obvious that ifconfig reported one additional ppp0 network interface and the Internet connection was wokring again, this time via the dongle in modem mode.

Remarkably, that wvdial used only one ttyUSB0 port for AT commands and PPP transport. Suddenly we were stunned – what if iterfaces for AT and PPP are not interchangeable and we inadvertently swapped them, could it explain why our mbed board kept failing to open PPP? All we needed to do was to replace just one line with the following content:

USBEndpoint* VodafoneK4606Initializer::getEp(USBDeviceConnected* pDev, int serialPortNumber, bool tx)
{
  return pDev->getEndpoint(serialPortNumber, BULK_ENDPOINT, tx?OUT:IN, 0);
}

Before compiling the code we disabled all debug output but from ATCommandsInterface.cpp to make it less extensive…and modem successfully connected to the Internet! The was one more thing to look at as HTTPClient failed to get expected results from both GET and POST HTTP requests but regardless the link was up!

After disabling debug output from ATCommandsInterface.cpp and enabling it from HTTPClient.cpp we got the following result:

There were actually two issues with HTTPClient.cpp – first of all, it didn’t process correctly HTTP redirects and in our case http://mbed.org/media/uploads/donatien/hello.txt doesn’t exist anymore instead our request gets redirected to http:///developer.mbed.org/media/uploads/donatien/hello.txt so there would be a trivial change in main.cpp:

void test(void const*) 
{
...    
    //GET data
    printf("Trying to fetch page...\n");
    ret = http.get("http://developer.mbed.org/media/uploads/donatien/hello.txt", str, 128);
...
}

And secondly, there was a limit for maximum HTTP header length in HTTPClient.cpp which was set to 32 while some header values received in our experiment were much longer. Strictly speaking the logic should be modified to avoid using intermediate buffer for parsing header’s values and therefore become bullet proof should we encounter super long headers. In fact, the whole HTTPClient had to be redesigned. But in our case for simplicity sake we just added extra definition which replaced all the hardcoded values for buffer length:

#define KEY_VAL_BUF_SIZE 60

…and run the code again:

… and finally, it worked! At that stage we had broadband connectivity ready for real projects.

Downloads:

1. VodafoneUSBModem Libarary with K4606 support;

Conclusion. This article was intended to serve as a guide to help in solving similar challenges with interfacing embedded projects and broadband modems. We hope that someone may find this information useful and worth reading.

Arducam Rev.C – First Encounter

This article was prepared back in October 2013 but remained unpublished all these months. At first, when we got Arducam Rev.C in possession its rich set of features and performance inspired us greatly. However, just within a few days the interested moved on, became dim and then quickly faded away. It simply turned out that despite all advantages there were as much limitations. Flicking through the list of unpublished posts we decided to review the article and give it another go. But enough of foreword, here is the article itself.

Necessity of taking pictures by means of an embedded platform is not new. The problem existed for a while as it was (and still is) very resource demanding in spite of tremendous advances in embedded computing last years. The problem is aggravated by the fact that people are getting used to better picture quality as technology evolves so solutions that had reasonable performance and image resolution couple of years ago are not acceptable anymore. It has been always a trade off between picture quality and hardware non-overcomplication. In other words, for cheap and inexpensive 8-bit Arduino-like host controller during last few years there was not that much to choose from: a serial enabled VGA camera module C328 replaced with uCam and LinkSprite. Both have been recently discontinued as they couldn’t offer resolution greater than VGA while having a price tag at around $50. The only alternative to the abovementioned VGA camera modules was an opensource project called Hacrocam. With a single advantage of being opensource Hacrocam had the same set of flows: same low resolution (VGA), only one image sensor type supported and similar price tag (around $40). No wonder that the project couldn’t live long life – and as for today, October 2013, the item is marked as ‘sold out’ at Hacrocam’s site.

Unlike camera modules ArduCam didn’t have much of public attention or appreciation and was silently evolving for a while. It’s first revision used LCD’s RAM as a frame buffer and therefore was limited in resolution even more than the modules. But ArduCam engineers kept working and finally Rev. B and then Rev.C came to existence. Even from a first glance it becomes obvious that this solution is superior in comparison with the abovementioned solutions. ArduCam Rev.C didn’t use any particular image sensor, instead, it supported concept of interchangeable sensors and at that time was capable of working with sensors up to 5MP. The quality of pictures and resolution was much better when comparing with products from the same ‘lightweight’ class. The Arducam board was controlled by I2C interface making it simple and easy to use. However, despite all reassurances from Arducam developers, it is NOT opensource project and it can’t be. The source code for FPGA chip is kept private and as for the design files, only Rev. B schematics is available, nothing for Rev. C and nothing for PCB design. This puts this project in the same category with countless proprietary things, as simple as that. All our questions to developers as to why this project is considered as ‘open hardware’ when there is no public access to design files – all these questions have been just silently ignored.

Nevertheless, let’s have a look at the Arducam board and do a test driver. As it turned out, the board didn’t quite mate with Arduino Mega that has been in our lab – it was a bit old and didn’t have I2C pins at right location. We did a workaround as it is shown on the picture below.

Arducam Rev. C and Arduino Mega

Arducam Rev. C and Arduino Mega

The final assembly looks bulky and it is not possible for a sensor to revolve or tilt unless the whole assembly is repositioned. And even being so bulky, the solution still lacks transport module of some kind to be able to upload data wirelessly to a server not to mention that it would be a big question on how to connect such a module as there is no convenient USB host interface or similar available.

Arducam Rev. C assembled

Arducam Rev. C assembled

Arducam Rev. C assembled Rear View

Arducam Rev. C assembled Rear View

A typical picture taken in daylight conditions without tripod is given below. The picture is a bit blurry but still reasonably good for a custom made camera with fixed lens and iris.

ArduCam 3MP Sensor Test

ArduCam 3MP Sensor Test

However, the picture is visibly noisy in dark regions and it has a little bit of fish eye distortion. On the other hand, the the size is only 107 Kbytes which is pretty good as it is easy to handle by basic embedded systems like Arduino.

To sum up, let’s give a synopsis on what we discovered:

– The idea and functional implementation are great but PCB’s form factor and closed source code along with design files kill both the idea and functionality making it impossible to adapt the project to something different from Arduino;

– Without design files available to the community there won’t be support for new image sensors so the project will decease as soon as Arducam guys shift their focus to something else when they won’t have enough resources to maintain this project;

– It is a big challenge to communicate with Arducam developers, those guys rather prefer to ignore your requests and comments unless they are in ‘guys_you_are_great’ style.

Revisiting VFD PSU – Part I

The need in inexpensive, simple and compact power supply unit (PSU) for VFD projects has always been topical for us. MVFD 16S8D Panel was designed to address the challenge, however, as it always the case for first revision, there are some things which require improvements or rethinking. And MVFD Panel was not an exception in this sense. Let’s quickly highlight requirements for PSU:

– Single (5V) input voltage with filament and segment/grid voltages being generated internally;
– No transformers or custom winded ferrites;
– Pulse filament drive to avoid brightness gradient;
– Feedback for segment/grid high voltage to break dependency between brightness and number of illuminated segments;
– Shutdown mode;
– Overcurrent protection is highly desirable;
– Filament/segment voltages to be adjustable in reasonable range to make the solution as generic as possible in order to support more than just one type of VFD;
– Reasonable number of components, compact dimensions;
– High availability of components with no ‘end of life’ or ‘not recommended for new design’ things;
– All components must be handsolderable;
– The total cost of components should not be greater than $5-6;

On the other hand, the previous design had the following issues:
– LM9022 filament driver became obsolete;
– Segment/grid voltage was generated by a charge pump using pulse filament drive and making segment and filament voltages interdependent on each other leaving without possibility of fine tuning;
– Absence of feedback for segment voltage caused less segments to shine brighter and more segments to be dimmed;
– Five stages of charge pump require substantial area on PCB;

So in our next PSU revision we will aim to address most (if not all) issues of the prior design. After countless hours of search we finally found what we think would be a good candidate for high voltage generator. It is MC34063, buck/boost/inverting regulator which comes at a price of 56 cents from Digikey! However, it also has disadvantages, the biggest one is its maximum switching frequency which is just 100kHz. It automatically imposes much bigger inductance if compare with switching voltage regulators operating in 1-2Mhz. Values of 80…200uH are very typical for circuits with MC34063. In addition to that, due to low frequencies inductors must be capable of passing through currents 10 and even 15 times higher than the actual load requires. To some degree the price migrates from switching converter to passive components and it is important to be aware that in some circumstances it is just not worth using MC34063.

Another challenge is to calculator values of passive components, the quick approach would be to use already existing online calculators and there are few of them available, the problem is that they produce quite different results. We ended up using MC34063 Boost Converter Calculator as it looked more professional due to a mere fact that is distinguishes numerous flavors of MC34063 from different manufacturers. Filling in required input parameters we got the following results below.

MC34063 Calculations

MC34063 Calculations

There are few things worth mentioning though. First of all, the calculator does not give anything if negative Vout is specified. Looks like it is unable to calculate for inverting mode (well, not a big deal, it should not fundamentally change anything if we provide positive value for Vout). Secondly, the calculator doesn’t give any result if we specify Vout = 24V. Well, we specified 23V and going to adjust value either of R1 or R2 to get 24V. If the prototype works then we keep 24V otherwise will stick to 23V as it is still within the optimum range of VFD. And finally, the calculated inductance is so high that it becomes unpractical, it also seems too high when looking at the application examples in the datasheet. There is a good article about a PSU based on MC34063, the author also got extremely high (389uH) inductance as a result of calculation and decided to use 100uH. We will also reduce calculated value down to 180uH and see what happens during our experiments. The first revision of our circuit is given on the picture below.

MC34063 Inverting -24V

MC34063 Inverting -24V

After getting all the required components we finally were ready to begin experiments in real life. The first prototype didn’t give any magic smoke but even without load applied there was current consumption of 70mA @ 5VDC input voltage! MC34063 was unpleasantly warm. High current in idle mode must be caused by pulses going back and forth through big inductor and frequency of pulses is in essence switching frequency. Then by ramping up switching frequency pulses of current through inductor should be less and therefore loses should be less… To check our assumption we then measured power consumption when using 1200pF and 600pF timing capacitor. In first case current consumption was 70mA, the second experiment gave 60mA. So we managed to bring down consumption by 10mA – not ideal but we were definitely going in the right direction. Using oscilloscope we confirmed that switching frequency increased from 40kHz to about 73kHz as it is shown on the picture below. Potentially we still had some space for improvement as we could increase the frequency up to 100kHz.

MC34063 1200pF @ 40kHz

MC34063 1200pF @ 40kHz

MC34063 600pF @ 73kHz

MC34063 600pF @ 73kHz

The picture below shows that the PSU indeed generates -24VDC out of +5VDC, however, it still consumes too much when idling.

MC34063 5V to -23V PSU Idling

MC34063 5V to -23V PSU Idling

We are going to continue our experiments with the prototype to make it more efficient. It would also be quite interesting to know how good it is when it works with real load – all this will be done soon, stay tuned.

VFD Color Filter Made of Plastic Glass

Stylish and professionally looking enclosures for DIY electronics is a known challenge for both beginners and advanced hobbysts. Custom made cases are either too expensive, too difficult/time consuming to build or the resulted forms are too primitive/ugly. Besides, electronics engineers usually are not exceptionally good at 3D designing and they tend to consider assembled and tested PCBAs as virtually finished products. As a result, the importance of putting everything in a box is often underestimated and even neglected. After all, who cares if the project ‘is working fine’? :) However, unappealing enclosure design may easily ruin the whole project even though its electronic part works like charm. That is why we decided to build something unusual as a case for our Luminardo board and today our goal would be to make a VFD filter. The problem with filter is that it has to be transparent which is almost impossible to achieve when using 3D printing. On the other hand, laser cutting on acrylic blanks could give us transparent part of virtually any shape but it would be flat. An alternative approach would be to find something transparent of a peculiar shape and use it as a blank for our filter. We looked around and found in one of Chinese shops a… plastic glass for just 3 Australian dollars. The glass is shown on the picture below.

Plastic Glass To Be As Filter Blank

Plastic Glass To Be As Filter Blank

The next step would be to cut a few elements off the plastic glass in the shape of visor. This was done by means of a rotary tool (Dremel) and finished with a double-cut flat file. The result is shown on the picture below, as an experiment we cut out three elements with variations in shape in size.

VFD Color Filters

VFD Color Filters

To make sure that the filter fits the remaining parts of our enclosure (which we are going to design in Sketchup later) we need to create its 3D model. At a first glance it looks like a difficult job because of surface’s complexity but in reality it is fairly simple. The key is to start over with modelling of glass surface created by lofting technique (glass’ section and ‘follow me’ tool). Then cut off the excess of material just like it has been already done in the physical world by means of dremel and files. As a final touch apply material to adjust color and transparency accordingly to make it look like real. Here is what we got as a result of our effort:

Downloads:

1. VFD Filter 3D Model in Sketchup;

Spark IO – First Encounter

Searching for a compact, flexible and cost effective WiFi solution for our new project we came across Spark IO This tiny board met all our requirements and looked ideal for our application, however, it was ‘out of stock’ in many if not all online electronic stores. Best we could do was to ‘express interest’ which is really a totally useless thing. The only possible place to get it from was Spark’s own online store so after doing some considerations and failing to find something similar in functionality and price tag we decided to take the risk and order. The parcel arrived in less than two weeks and contained a neat box with Spark IO itself, USB cable and protoboard which was a pleasant surprise. Spark IO visually looked even more compact than we expected. After unpacking and quick visual inspection the next logical step was to try the board in action. The Spark’s website contains comprehensive documentation which describes setup process as quick and painless and it by the way turned out not to be the case. That is a typical ‘chicken and egg’ problem whereby in order to be able to control the board it has to be configured and in order to configure it there is a need to get control. The idea of using a mobile phone as a tool of Spark’s initial configuration is great on the surface but hopeless when something goes wrong or when there are more than one Spark core awaiting configuration. Ironically, the fallback plan that is intended to help with configuration hurdles is just not good enough. Let’s have a closer look at potential difficulties with board’s first connection.

In order to get configured, Spark core a) has to be programmed with valid WiFi settings to get Internet access and b) has to be ‘claimed’ – in other words, associated with our developer’s account so that we (and no-one else) can access the board remotely virtually from any place on Earth (provided that the place has Internet connection). Unfortunately, programming a device with correct WiFi setting is not enough. Nowdays many WiFi networks have MAC address filtering in place which basically means that MAC address must be manually specified on WiFi access point. But the thing is that there is no easy way to get Spark core’s MAC address! It is labeled nether on the kit’s box, nor on the board or WiFi module. Then the next logical step would be to connect the board to a PC via USB cable and interrogate it to get MAC address, right? No! The command line interface is really ascetic – it only allows to set WiFi SSID and password and read core’s unique identifier (also known as core id), nothing more. Well… maybe core id contains MAC address then? To answer this question we downloaded source code for spark’s firmware and after analysing it discovered that core id is actually a unique microprocessor id and has nothing to do with WiFi MAC address which brought us back to square one leaving with the same question: how to get Spark core’s MAC address?

Turning our attention to the Internet we realised that people were suffering from the same problem. Here is a discussion about extracting MAC address by running a sketch. The funny part is that in order to upload sketch to the core, it has to be already claimed. And in order to be claimed it must have an Internet access already! So really it all comes down to disabling MAC address filtering on your WiFi access point, claiming the core, programming it with the sketch that extracts the MAC address and after that adding the newly discovered MAC address to the list of permitted devices on WiFi access point and only then enabling MAC address filtering again. Why it should be done in such a clumsy way – it is really a question to Spark core developers, all this could be easily simplified by just adding one extra command to Spark’s command line interface which would retrieve MAC address. As simple as that.

Spark IO Unpacked

Spark IO Unpacked

But in the end the problem with configuring was the only thing that we really didn’t like. The core has been eventually claimed and we felt tremendous freedom and flexibility provided by the solution.

Spark Core Successfully Configured

Spark Core Successfully Configured

Of course, as any product is has its own pros and cons and here is the list below that we came up with:

Advantages:
– It is Arduino compatible. Sketches and libraries designed for Adruino can be used for Sketch IO designs with zero or minimum modifications;
– It is fast. Ridiculously fast comparing to Arduino;
– It has really small form factor;
– It comes with API for cloud, iPhone and Android OS integration. All cloud-Spark core, cloud-userapp communications are secure;
– It is probably the cheapest WiFi-powered kit of that kind at the moment;
– Online development environment, zero effort to install and configure (it is also a disadvantage by the way :) );

Disadvantages:
– There is obviously no backward compatibility with Arduino;
– Number of GPIOs is less than, let’s say’ in case of Arduino Uno;
– Difficulties with getting MAC address which complicates initial configuring process;
– Not widely available at the moment. The only place to buy is SparkIO itself;
– Online development environment, your source code is kept somewhere is the cloud out of your control and potentially available for everyone if anything;