ATtiny85 Breakout Board

Sunday, April 20 2014 @ 02:44 PM

Now that I have a bootloader for the ATtiny85 it's time to simplify the hardware side of things. To that purpose I've designed a small breakout board that makes it easy to set up projects on a breadboard (there are two versions of it available).

The bootloader re-purposes the RESET pin as a serial port so at a bare minimum the breadboard will need to include the circuitry required to split the single pin into Tx and Rx pins for the serial cable. Entering the bootloader requires a single pin to be held low when power is applied so I wanted to make that as simple as possible to do as well. Finally, I wanted to breakout board to have the same footprint as a standalone ATtiny so it would be as easy as possible to migrate a project from development to production.

Breakout Board (V1)

The first version of the breakout board is very simple - the pins of the ATtiny are taken straight to pin headers to insert into the breadboard and the serial port circuitry is exposed to a 6 pin header for an FTDI serial cable.

FTDI Friend

I use an 'FTDI Friend' like this one from Adafruit). You can find them on eBay as well, the pinout seems to be consistent. Many models (including the AdaFruit one) allow you to switch between voltage 3.3V and 5V as well which is useful if you work with different MCU's with different voltage level requirements. The pin header for the serial connection is designed to match the FTDI friend pinout:

Pin Description
1 Ground
2 CTS (Clear To Send)
3 Vcc
4 Tx
5 Rx
6 RTS (Ready To Send)

If you do have one with a different pinout and need to change the board layout I've made the Fritzing project files publicly available so you can redesign it to suit what you are using. In this version of the breakout board I'm only using the Tx, Rx and GND pins.

To enter the bootloader you need to ensure that pin 2 (PB3) is pulled low when power is applied. In this version I've just attached that pin to ground through a pin header - you can put a jumper across the header or connect it to a push button to switch between starting the application or starting the bootloader.

Using the Breakout Board

I've been using that version of the breakout board fairly solidly over the past week and it works well. There were a few things that popped up though:

  1. I tend to keep the board in bootloader mode all the time (so the jumper for that is pretty much always in place). The bootloader has a 'RESET' command that jumps straight to the application code so my workflow is - flash the new code, connect the serial program, type '!' and monitor the output.
  2. The board requires an external power supply so I always have to have it plugged in to a breadboard regardless of what (if any) hardware I have connected. It would be nice if I could avoid that - it would mean more development time in the lounge room while watching TV.
  3. The other downside of having an external power supply is providing power control. I use a simple regulator board that provides 3.3V and 5V output from a 6V input and it has individual switches on each output so that is easy enough. Powering from another source would mean adding some way to easily switch the power on and off.

With those things in mind I did a minor redesign of the breakout board an came up with a second version that is a bit easier to use.

Breakout Board (V2)

The format of the new board is much the same, although it is slightly larger. The footprint for the breadboard connectors still matches the footprint of a standalone ATtiny and the serial hardware is still in place and connected to the same pins.

It now (optionally) uses power from the serial connector and I've added a pin header to enable that - if your project is using less than 200mA the power from the FTDI cable should be enough. If you remove the jumper from the pin header you can still provide power externally.

The header for bootloader entry has been replaced with a small switch, I used a two position DIP switch for this. The second switch provides power control on the Vcc connector from the FTDI cable (if you are using external power you will still need to provide some sort of power control yourself). During development you will most likely leave bootloader entry enabled and simply use the power switch - the application can be launched manually with the RESET command.

The new design makes it very easy to connect simple hardware directly to the pin headers that map to the ATtiny pins - you can develop, test and debug parts of your project on a coffee table with nothing more required than your laptop. The image to the right shows a small keypad I made connected directly to the device (the keypad uses a single analog pin and different voltage levels to indicate which key is pressed - I used it to test the ADC inputs on the breakout board).

A Simple Project Template

As I mentioned earlier the Fritzing project files for these boards are publicly available. There is a bit of a bonus as well - I've created a repository that contains (the beginnings of) a complete template for ATtiny85 projects that includes the following:

  1. The Fritzing project files for the breakout boards so you can make your own or use them as the basis for customised ATtiny85 based circuits.
  2. The latest HEX file for the ATtiny85 version of the Microboot bootloader and all the tools needed to interact with it.
  3. A library of utility functions for the ATtiny85 including the single pin serial implementation, 'printf()' like formatted output, digital and analog pin access helpers and a range of other helper functions.
  4. A Makefile that uses avr-gcc to compile and link your project as well as targets to flash your code to the ATtiny over the serial port.

All of this is released under a Creative Commons Attribution-ShareAlike 4.0 International License so you are welcome to use it for both commercial and non-commercial purposes as long as you provide attribution and share any changes that you make.

My goal in developing this template was to make it as easy as possible to start an ATtiny based project - I wanted to have all the common things in place so that I could simply concentrate on the project specific code. I hope you find it useful for your own projects.

You can find the full repository on GitHub, be warned that it is a work in progress (and I expect to be adding features and new functionality well into the future). If you have anything you want added or would like new features added please go ahead and raise an issue. If you would like to contribute to the library send me a pull request or just email a patch.

Using the Template Project

The idea behind the template is that you can quickly start your own project while keeping up to date with any bug fixes or features that get added to the original. The easiest way to do this is through git by forking the repository and then bringing in changes from the original as needed. To have more control over what updates you bring in it would help to have a branch. Once you have a copy of the cloned repository you can do the following:

cd my-project
# Add the main template repository as a remote
git remote add template https://github.com/thegaragelab/tinytemplate.git
# Create a branch to track changes to the template and put it in your repo
git branch template
git checkout template
git push -u origin template
# To get any changes from the template into the 'template' branch ...
git checkout template
git pull template master
# Now you can merge or cherry-pick these changes to your master branch

I will be writing more posts about this template project in future so as I start integrating it into more of my own projects I'll be able to give more specific examples of how to use it in various scenarios.

Next Steps

Now that I have the tools finalised and in place I'm going to be moving ahead with a few project ideas I have that will use an ATtiny at the heart of them (including revisiting the safety light project to fix a few annoying issues that have cropped up). Along the way I'll be updating the template library as I find more things that would benefit from having a generic solution available as shared code.

I've been documenting the code in the library as I write it and you can generate that documentation for yourself by running make docs in the 'firmware' directory (you need to have doxygen installed). I'm looking for a way to push that documentation to the Wiki on GitHub - the alternative is hosting it here but I'd like to keep it all in one place. I'll post an update here once I have found a solution to that.

UPDATE: I am using the GitHub 'pages' facility to host the generated documentation - you can find it here. I need to do some cleanup of the formatting over time but it's a much nicer way to browse the code and get an idea of the functionality available.

I'm looking forward to hearing from others using the library, please download it and have a look for yourself - I'm sure you'll find it useful.


Microboot on the ATtiny85

Thursday, April 17 2014 @ 06:16 PM

The last post gave an overview of the protocol and tools used with the bootloader I've developed - this post describes the implementation details for the version I have running on the ATtiny85. All the code is available on GitHub and released under a Creative Commons Attribution-ShareAlike 4.0 International License.

Hardware Requirements

The bootloader requires two pins, one to determine if the bootloader should enter programming mode and the other to implement serial communications. These pins are only used when the bootloader code is running, the application is still free to use them for whatever purpose it wants (with some minor restrictions). The details on the serial port will be described a little later on in the post, first I'll explain the purpose of the bootloader entry pin and how it is used.

When the chip is first powered on the bootloader code will start running, at this point it needs to decide if it should enter bootloading mode or simply hand over control to the application code. To make this decision it inspects the state of an input pin, if it is held low then it initialises the serial port and waits for commands, if it is not low the the pin is restored to it's default state and the application code is started.

To minimise the external hardware required I use the internal pull up resistor on the input pin so all that is required is a push button or jumper switch to tie the pin to ground before powering up the device. In the default configuration I use pin B3 (physical pin 2 on the 8 pin DIP version of the chip) for this purpose but you can change that to whatever you like.

If you are using this pin in your application you need to make sure that it is not held low when power is applied and that having it raise high (through the pull-up) is not going to trigger whatever circuitry you have attached to it.

Single Pin UART

One Pin UART

The UART used for communication is implemented in software (it's the same design I used in the Safety Light project) and uses a single pin for both Tx and Rx with a small amount of external circuitry (shown to the right). The design and code came from this site and was originally developed by Ralph Doncaster.

The default configuration uses pin B5 (physical pin 1 on the 8 pin DIP version of the chip) which is usually the RESET pin. Having support for a serial bootloader negates the need for SPI programming so the RESET pin is simply consuming a valuable resource to no real benefit. Disabling the RESET function and using it as the serial interface means that you can use an extra IO pin in your application (and given that the serial circuitry is already attached to it you may as well use it as a serial port).

Pin Assignments and Fuses

The pin assignments are done in the file 'hardware.h' in the source directory, you can simply change the assignments there if you want to use different pins to those that I have selected. If you do use the pins I have selected you will need to ensure the RSTDISBL fuse is programmed so the RESET pin can be used as general purpose IO.

To allow writing to the flash from the bootloader code you must have the SELFPRGEN fuse programmed as well, this is not optional if you want the bootloader to work at all. The fuse bytes I use are:

High Fuse     = 0x5D
Low Fuse      = 0xE2
Extended Fuse = 0xFE

This set of fuses runs the chip at 8MHz using the internal RC oscillator, sets the brown out detection level to 2.7V, enables self programming and disables the RESET pin. Here is the sequence of commands I use to build the bootloader, burn it to the chip and set the fuses:

./makeall.sh
make MCU=attiny85 flash
make MCU=attiny85 fuses

WARNING: Using this combination of fuses means that you will not be able to re-program the chip using SPI - you will need a high voltage programmer (or similar tool) to reset the chip to factory defaults. The sequence of command is important as well - if you change the fuses first you will not be able to flash the code (the fuse settings will disable RESET and prevent you from entering SPI programming mode).

The targets in the Makefile are set up to use USBasp programmer or one of its many clones, if you have a different programmer simply edit the avrdude command in the Makefile and change the command line parameters appropriately.

Reading Flash Pages

Reading the flash memory is done with the lpm instruction on the AVR. The standard C library that is part of avr-gcc includes the pgmspace.h utility library so I'm just using that for simplicity. To process the read command I simply use the 'pgm_read_byte_near()' function to fill a buffer with data from the specified address in flash and send it back.

bool readFlash() {
  uint16_t address = ((uint16_t)g_buffer[0] << 8) | g_buffer[1];
  for(uint8_t index = 0; index<DATA_SIZE; index++, address++)
    g_buffer[index + 2] = pgm_read_byte_near(address);
  return true;
  }

It's not the most efficient way of doing things but it's simple, easy to understand and it works.

Writing Flash Pages

Writing to the flash is a bit more complicated. The entire process is described in detail in the datasheet, I'll just give a run down of the process here.

The program flash is accessed in pages (on the ATtiny85 each page is 32 words or 64 bytes in size) and the write operation must update an entire page at a time - you can't simply write byte by byte the way I handle the read operation.

The boot loader protocol is currently using 16 byte data packets so we only have that much data to write, we need to keep the rest of the flash contents the same. The sequence I use is:

  1. Copy the appropriate page from flash to a memory buffer.
  2. Update a portion of the memory buffer with the data provided to the bootloader.
  3. Erase the flash page in question
  4. Write the memory buffer (a combination of the original contents with the new data) into the flash page.

Apart from preparing the memory buffer the code I use to do this is essentially the sample provided in the datasheet.

bool writeFlash() {
  uint16_t page_address, address = (((uint16_t)g_buffer[0] << 8) & 0xFF00) | g_buffer[1];
  uint8_t page_hi, page_lo, index, written = 0;
  while(written<DATA_SIZE) {
    page_address = ((address / SPM_PAGESIZE) * SPM_PAGESIZE);
    page_hi = (page_address >> 8) & 0xFF;
    page_lo = page_address & 0xFF;
    // Read the page into the buffer
    for(index=0; index<SPM_PAGESIZE; index++)
      g_pagecache[index] = pgm_read_byte_near(page_address + index);
    // Add in our data
    uint8_t offset = (uint8_t)(address - page_address);
    for(index=0;(written<DATA_SIZE)&&((offset + index)<SPM_PAGESIZE);written++,index++)
      g_pagecache[offset + index] = g_buffer[written + 2];
    // Write the page
    asm volatile(
      // Y points to memory buffer, Z points to flash page
      "  mov   r30, %[page_lo]                     \n\t"
      "  mov   r31, %[page_hi]                     \n\t"
      "  ldi   r28, lo8(g_pagecache)               \n\t"
      "  ldi   r29, hi8(g_pagecache)               \n\t"
      // Wait for previous SPM to complete
      "  rcall wait_spm                            \n\t"
      // Erase the selected page
      "  ldi   r16, (1<<%[pgers]) | (1<<%[spmen])  \n\t"
      "  out %[spm_reg], r16                       \n\t"
      "  spm                                       \n\t"
      // Transfer data from RAM to Flash page buffer
      "  ldi   r20, %[spm_pagesize]                \n\t"
      "write_loop:                                 \n\t"
      // Wait for previous SPM to complete
      "  rcall wait_spm                            \n\t"
      "  ld    r0, Y+                              \n\t"
      "  ld    r1, Y+                              \n\t"
      "  ldi   r16, (1<<%[spmen])                  \n\t"
      "  out %[spm_reg], r16                       \n\t"
      "  spm                                       \n\t"
      "  adiw  r30, 2                              \n\t"
      "  subi  r20, 2                              \n\t"
      "  brne  write_loop                          \n\t"
      // Wait for previous SPM to complete
      "  rcall wait_spm                            \n\t"
      // Execute page write
      "  mov   r30, %[page_lo]                     \n\t"
      "  mov   r31, %[page_hi]                     \n\t"
      "  ldi   r16, (1<<%[pgwrt]) | (1<<%[spmen])  \n\t"
      "  out %[spm_reg], r16                       \n\t"
      "  spm                                       \n\t"
      // Exit the routine
      "  rjmp   page_done                          \n\t"
      // Wait for SPM to complete
      "wait_spm:                                   \n\t"
      "  lds    r17, %[spm_reg]                    \n\t"
      "  andi   r17, 1                             \n\t"
      "  cpi    r17, 1                             \n\t"
      "  breq   wait_spm                           \n\t"
      "  ret                                       \n\t"
      "page_done:                                  \n\t"
      "  clr    __zero_reg__                       \n\t"
      :
      : [spm_pagesize] "M" (SPM_PAGESIZE),
        [spm_reg] "I" (_SFR_IO_ADDR(__SPM_REG)),
        [spmen] "I" (SPMEN),
        [pgers] "I" (PGERS),
        [pgwrt] "I" (PGWRT),
        [page_hi] "r" (page_hi),
        [page_lo] "r" (page_lo)
      : "r0","r16","r17","r20","r28","r29","r30","r31");
    // Update addresses
    address = address + written;
    }
  return true;
  }

It's a fairly straight forward process. The downside in using a smaller data size is that each flash page is written four times to fill the page. Increasing the size of the data block in the protocol would help with this. In future revisions I might increase the size to alleviate this issue.

Bootloader and Application Startup

The ATmega series of chips have direct support for bootloaders - programming the appropriate fuse bits will cause execution to start at the bootloader start address instead of using the code at the RESET vector to start the application. Unfortunately this feature is not available on the ATtiny85 so we have to resort to a little bit of trickery to get the same result.

The boot loader is positioned in the upper 1K of memory, starting at address 0x1C00 (unless otherwise specified I will be using the byte address rather than the word address). When the ATtiny starts up it starts execution at 0x0000 which is the RESET vector. When you load a new program into the flash it will insert an 'jsr' (jump relative) instruction to jump to the start address of the application. We want the CPU to go directly to the bootloader rather than the application so we make a few changes to the contents of the data being loaded into the flash before actually transferring it.

The byte code for the 'jsr' instruction is 0xCnnn where nnn is one less than the word address of the application entry point - a code of 0xC00F would indicate that the start of the application is at 0x0010 (word address, the byte address would be 0x0020). The 'mbflash.py' utility I wrote handles this when the device type is an ATtiny85 - it looks for the signature of the 'jsr' instruction at address 0x0000 and, if found, replaces it with a different 'jsr' instruction that will jump to the bootloader program. The start address of the application program is stored at the top of flash memory in the two bytes just before the bootloader code itself. When it's time to launch the application the bootloader loads that information in the Z register and uses the 'ijmp' instruction to call it.

addr_hi = pgm_read_byte_near(TOP_ADDRESS);
addr_lo = pgm_read_byte_near(TOP_ADDRESS - 1);
asm volatile(
  // Z points to the application start address
  "  mov   r30, %[addr_lo]                     \n\t"
  "  mov   r31, %[addr_hi]                     \n\t"
  "  ijmp                                      \n\t"
  :
  : [addr_hi] "r" (addr_hi),
    [addr_lo] "r" (addr_lo)
  : "r30","r31");

The flashing utility will refuse to transfer the code if it can't find the 'jsr' instruction or if the code to be loaded would overwrite the storage location of the actual application start address.

Next Steps

The next post will detail the small breakout board I made for the ATtiny that works well with this bootloader for very rapid development and testing. I've made a few minor changes to the design based on how I found myself using - once I finish those off I'll document the whole thing.

I'm hoping the bootloader will get some use externally (even if only as sample code for writing your own) - if there are any questions about it please go ahead and bring them up in the comments. If you find any bugs or have feature requests just raise them on the GitHub project page.


Microboot - A Simple Bootloader

Monday, April 14 2014 @ 08:13 PM

In my previous post I alluded to some work I was doing on a bootloader for the ATtiny (and other chips). This has been progressing well so it seemed like a good time to start providing more details. This project is still a work in progress so nothing is completely finalised yet but any changes are going to be fairly minimal.

Overview

This work came out of two sets of requirements that wound up aligning with each other - the first was my desire for a bootloader to ease the development of some more ATtiny based projects, the second was to develop a bootloader for the prototyping boards I've been working on.

The Microboard system is designed to support a range of different MCU's - some of which have built in bootloaders (like the LPC1114), some that have well defined bootloaders and support tools available for them (like the ATmega series) and others that have no standardised bootloader protocol (like the PIC16F1827 and the PIC32MX series). I had already decided to write my own bootloader for Microchip PIC devices and, after some thought, it seemed like a good idea to implement this on the AVR chips as well rather than wind up with a different bootloader and set of tools for each chip. The end result will be a common bootloader protocol and tools for all chips except the LPC1114 (the built in bootloader is fine - implementing my own would just be redundant).

Because the AVR chips are so widely used there is no shortage of example code to refer to - it was the best target to start with. With a bit of work the same bootloader could be made to work on the ATtiny chips as well so it solves two problems at the same time. This is the route I have been heading down over the past few weeks.

One major goal I have to keep the bootloader as simple and as straight forward as possible - it's not meant to be the fastest or smallest available, just easy to use and easy to understand. The implementation comes in at less than 1K on any target platform and will communicate at 57.6 KBaud - fast enough for daily use without trying to break any speed records. It is not required to modify the EEPROM or fuse bytes on chips that have them, it's only purpose is to modify the program flash.

The remainder of this post documents the behaviour of the bootloader, the protocol used for communication and describes some of the utilities I've developed to work with it. Implementation details for specific processors will be described in future posts.

Entering the Bootloader

The bootloader is run whenever the MCU resets, at this stage it determines if it should enter bootloader mode or continue to the application program currently on the flash. The first versions I worked on initialised the serial port of the MCU and waiting for a certain period for any communications, if nothing arrived the application was started. This approach turned out to be very annoying - it caused an unnecessary delay on each power cycle and when you did want to transfer code it could be very difficult to start the communications within the window of opportunity available. It also put restrictions on what hardware you could attach to the serial pins - if it generated any input to the MCU the bootloader mode was entered - leaving you wondering why the application wasn't doing what you expected.

The current model is based on the behaviour of the LPC bootloader - a bootloader entry pin is checked, and if it is low the serial port is initialised and the bootloader code started. If it is not low the application is started immediately. Where possible the implementation uses an IO pin that has an internal weak pull-up resistor - eliminating the need for any external circuitry. There are still some restrictions on the use of that pin from the application code (it can't be low at reset for example) but they can be worked around very easily.

Once the bootloader is started it will configure the serial port for a baud rate of 57.6 KBaud, 8 bits per character, 1 stop bit and no parity (8N1). It will then listen on the serial port for incoming commands.

Bootloader Protocol

Debugging the bootloader

I've deliberately kept the protocol very simple to make it easier to process on the chip and easy to write host side tools to work with it. All information is transferred in printable ASCII characters with binary data in hexadecimal, this means that more data is transferred but makes it a lot simpler to debug. It also means that you can interact with the boot loader manually, dynamically writing data to arbitrary locations in memory.

Each packet starts with a command (when sent from the PC to the MCU) or a status indicator (from the MCU to the PC) which is optionally followed by a sequence of data bytes and finally terminated with the 'line feed' character. When data is sent it is a sequence of byte values in hexadecimal format. The data is verified with a 16 bit checksum which is the sum of all the individual bytes added to the seed value 0x5050 with overflow ignored. The Python code to generate the checksum for a list of values looks like this:

checksum = 0x5050
for val in data:
  checksum = (checksum + (val & 0xFF)) & 0xFFFF

There are four supported commands sent from the PC to the MCU. Commands can be sent in any order, no command depends on the result of another. Responses are always prefixed with the '+' character (to indicate success) or '-' to indicate failure. The description of each command is as follows ...

Query Command

This is generally the first command sent, it reports various information about the bootloader and can be used to determine that you are in fact talking to the bootloader. The format is simply the '?' character immediately followed by the new line character. The response is a list of byte values followed by a checksum. A typical transaction looks like:

> ?
< +101001015072

The response consists of four data bytes and a two byte checksum. The data bytes are:

Index Description
0 Protocol version. The current version is 1.0 and represented as 0x10 hex.
1 The size of each data line. This tells the host how many bytes of data should be placed in each write command (and how many bytes will be returned for a read command).
2 This value indicates the type of CPU - AVR, PIC or PIC32
3 The specific model of the CPU - ATmega8, ATtiny85, etc

Read Command

The read command is used to read the contents of the flash memory on the MCU and is expressed as the letter 'R', a 16 bit address in 4 hex digits and a 16 bit checksum. If the read is successful the return value will be the '+' character, a 16 bit address as 4 hex digits, a sequence of bytes where each byte is a two character hex value, the 16 bit checksum and finally the terminating new line character.

A typical sequence looks like the following:

> R00105060
< +00100EC00DC00CC00BC00AC009C008C011245622

The number of data bytes returned will be the value indicated by the query command.

Write Command

The write command is used to change the contents of the flash memory on the MCU and is expressed as the letter 'W', a 16 bit address in 4 hex digits, a sequence of bytes where each byte is a two character hex value, the 16 bit checksum and finally the terminating new line character. The number of data bytes in the command must be equal to the value indicated by the query command. If there is not enough data or too little the command will fail.

A typical sequence looks like the following:

> W0000FFCD15C014C013C012C011C010C00FC057DA
< +

The response to this command is always a simple success ('+') or fail ('-') acknowledgement followed by the new line character with no additional data.

Execute Command

The execute command causes the application program to be started. This can be used directly after uploading to start running the new application.

The command is represented by the exclamation mark character ('!') followed by a new line character. There is no response to this command as the bootloader will transfer control to the application and not return.

Utilities

A bootloader by itself is not very useful without the corresponding utilities to run on the PC to use it. For this bootloader I have written some basic utilities in Python to transfer data to and from the device. A basic description of these tools follows.

Reading the Flash - mbdump.py

The mbdump.py utility will read the entire contents of the flash memory from the chip and save it to an Intel HEX format file. The usage information for this utility is ...

mbdump.py - Microboot/Microboard Firmware Dump Utility
Copyright (c) 2014, The Garage Lab. All Rights Reserved.

Usage:

  mbdump.py options [filename]

Options:
  -d,--device name  Specify the expected device, eg: attiny85,atmega8. This
                    is required.
  -p,--port name    Specify the name of the serial port to use for communication.
                    If not specified the port /dev/ttyUSB0 will be used.
  --log             Log all communications to the file 'transfer.log'

If a filename is not specified the output will be saved in the file 'device'.hex,
eg atmega8.hex if the device is an atmega8.

Writing Code - mbflash.py

The mbflash.py utility will read an Intel HEX file and program the flash on the MCU with it's contents. The usage information for this utility is ...

mbflash.py - Microboot/Microboard System Flashing Utility
Copyright (c) 2014, The Garage Lab. All Rights Reserved.

Usage:

  mbflash.py options filename

Options:
  -d,--device name  Specify the expected device, eg: attiny85,atmega8. This
                    is required.
  -p,--port name    Specify the name of the serial port to use for communication.
                    If not specified the port /dev/ttyUSB0 will be used.
  --log             Log all communications to the file 'transfer.log'

A simple example would be ...

:::shell $ ./mbflash.py -d attiny85 blinky.hex

This code would program an ATtiny85 connected on port /dev/ttyUSB0 with the contents of the file blinky.hex.

The Support Library - microboot.py

Both of the utilities above use a common support library contained in the file microboot.py. I've designed this to be easily integrated into other applications if custom firmware updating is required (for example - combining a fixed firmware blob with some use generated binary data).

What's Next?

Breakout Boards

I've just completed the first version of the bootloader running on the ATtiny85 using the same single pin serial interface I used in the safety light project and I've built a small breakout board with the additional circuitry required for the serial interface. I'm going to spend a few more days testing the implementation before I push the repository to GitHub for public access. There will be a few more posts over the next few days giving more details of that specific implementation.


Bootloaders and Bricked AVRs

Wednesday, April 02 2014 @ 07:45 PM

The last project based around an ATtiny85 was pretty successful, I'm impressed with what you can squeeze out of the chips and I have a few more smaller projects that they would be perfect for as well.

One of the more frustrating aspects was having to physically move the chip from the circuit to the programmer every time I wanted to update the firmware - by the fourth iteration I was wishing very hard for some sort of serial bootloader. The ATtiny doesn't have a UART on board but the functionality can be implemented in software (and, with the help of a little bit of hardware, can be done on a single pin).

I thought that taking that idea, and going over the source code for some existing bootloaders for the ATtiny and ATmega, would give me enough information to write my own. Although it would probably be smarter to use an existing bootloader (or at least implement and existing bootloader protocol) I wanted to use the task as an opportunity to get more in depth with the AVR architecture as well as learn more about the boot-loading process. To make things a little more tricky I would like to implement the bootloader protocol I come up with on the PIC16F1872 and PIC32MX series of chips - all CPUs used in my Microboard form factor.

While I'm busy mucking around with bootloaders and fuse bits I run a fairly high risk of bricking the AVR - putting into a state that you can only get out of by using High Voltage Programming or HVP - something that my little USBasp programmer can't do. Luckily, I found this tool which will automatically fix the fuses on a range of AVR chips to allow ISP programming to work again . I'm going to build one up over the next few days before I get too heavily into testing the bootloader- an image of what the finished product should look like is on the left, I doubt mine is going to look that neat though.

I'll post an update on how well the device works once I have one built and running (I intend to deliberately brick some chips just to make sure it can clear them so I'm really hoping it works as advertised). The bootloader project will get it's own write up as well once I've tested it enough to be confident of it's reliability.


An ATtiny85 Based Safety Light

Sunday, March 30 2014 @ 05:32 PM

Finished Product

This project is a simple presence sensing night light for doors, stairs and other areas which could be dangerous or difficult to navigate in low lighting conditions. It is built around an ATtiny85 microcontroller and uses the head from a cheap LED torch as the lighting element. The ambient lighting is detected with a simple LDR and presence with a PIR motion sensor.

Using a microcontroller is probably overkill for this project and the firmware may seem a bit large for what it does (around 1.5K). I wanted to use the project to experiment with some other features as well as making a useful utility for the lab so it has been a little over-engineered as a result.

Some of the more interesting features of the project include:

  • Combining the motion and light sensing inputs on a single pin.
  • A single pin half duplex serial interface to communicate with MCU.
  • A very simple communications protocol which allows changing configuration values without needing to re-flash the chip.

Schematic

As well as those items it was a good way to teach myself to work with the timers, PWM outputs, analog inputs and EEPROM on the ATtiny directly from C or assembly code. All the files for the project (firmware source, schematic and 3D models for the casing) are available on GitHub under a Creative Commons Attribution-ShareAlike 4.0 International License, feel free to use and abuse it as you see fit.

The remainder of this article details the various features of the project.

Power Supply

To power the device I'm using a set of 4 AAA batteries and a 3.3V MCP1700 LDO linear regulator to provide power for the digital circuitry. The voltage level of the battery pack is monitoring through a voltage divider consisting of two 10K resistors. The idea is to provide some sort of indication as the batteries go flat so you know when to change them before they fall below a suitable operating voltage.

It would probably be possible to remove the regulator completely, the ATtiny will run with anything from 2.7V to 6.0V without problems. It would make calculating the current limiting resistor for the output LED a little more problematic (it would simply fade as the voltage, and therefore the driving current, dropped) and you would need a simple 3.3V zener diode on the TX pin of the serial interface to keep that voltage level down as well.

Motion and Light Sensing

To detect motion I'm using a fairly standard PIR (passive infrared) module that I picked up from eBay. This particular one does not work at 3.3V unfortunately and the voltage level of the pulse output is the same as the power voltage. I wound up driving it from the battery rather than from the 3.3V regulator and added a diode so the forward voltage drop will bring the voltage closer to 5V for a fully charged battery pack.

For sensing the light intensity I'm using a simple LDR (I got some from Jaycar) which forms one half of a voltage divider. This model has a resistance range of 48K to 148K in light with a 10M resistance in total darkness.

I put the LDR in parallel with a 10K resistor to limit the range of resistance and configured a voltage divider with another 10K resistor such that the voltage will range from 0 to a maximum of half the input voltage. Higher voltages represent darker conditions.

Motion Sensor

Because we are not concerned about the light conditions if there is no warm body present I use the output of the PIR as the input voltage for the divider. This means I can detect both conditions with a single analog input and simply check for a value greater than a certain threshold to determine whether to turn the light on or not.

On problem with this approach is that the measured values will trend downwards as the battery discharges. This can be handled in software though, because we also measure the battery voltage we know what level it is at and can adjust the reading from the motion sensor accordingly. The corresponding code in the firmware that does that is as follows:

/** Base value for full voltage
 *
 * This is the value expected for a fully charged 6V battery pack.
 */
#define BASE_LEVEL 0xE8

/** Read the analog inputs
 *
 * This function reads the analog inputs (battery voltage and motion sensor).
 * Because the PIR and LDR are being driven directly from the batter we adjust
 * that input to the current battery voltage (so readings remain consistent).
 */
static void readSensors() {
  uint8_t power = adcVoltage();
  uint8_t motion = adcMotion();
  // Adjust motion value according to current power level
  if(power<BASE_LEVEL)
    motion = motion + (BASE_LEVEL - power);
  else if(power>BASE_LEVEL)
    motion = motion - (power - BASE_LEVEL);
  // Update sensor values
  configWrite(STATE_POWER, power);
  configWrite(STATE_MOTION, motion);
  }

We now know if someone is sneaking around in the dark, the next step is to cast some light on them.

Illumination Control

For illumination I'm using the head assembly of a cheap LED torch. These have the current limiting resistors built in and give you a concave reflective mirror and protective lens as well - much easier than assembly your own array of super bright LED's. The one I'm using is available in most supermarkets in Australia for around $AU 5 - a price well worth paying for the benefits you get above building up something yourself.

This module is driven direct from the battery (through the same diode used for the PIR) and controlled through an NPN transistor. I measured the current flowing through the LED assembly while driven directly from it's original 4.5V battery pack at 60mA so it fits neatly under the 100mA limit of a BC547 signal transistor.

Half Duplex Serial (Single Pin)

This is one of the more interesting aspects of the design. I came across this implementation of a software UART running at half-duplex that only uses a single IO pin. The implementation is in assembly and only consumes 62 bytes of flash (and no RAM at all). Speeds of up to 115200 baud are supported.

Serial Interface

There are limitations of course - there is no buffering and you cannot receive data while you are sending (and vice-versa). Some supporting external circuitry is required which uses an NPN transistor to ensure data being sent only goes to the Tx line. Anything that comes in the Rx line will be echoed on the Tx though so client software will have to take this into account.

In this project I'm running the serial line at 57600 baud and I've exposed the Tx and Rx lines to a 6 pin header that can be used with a 3.3V FTDI cable to communicate with the device.

I've found that transmits (from the ATtiny to the host) are very reliable but receives (from the host to the ATtiny) can be a little unreliable. Part of this is due to the way the firmware checks for activity on the serial port which can miss part of the initial start bit for a character. A longer term solution might be to use an 'on-change' interrupt on the pin so the device can start processing the incoming byte as soon as the start bit is detected. In the meantime this can easily be worked around in software on the client side of the connection.

Configuration Settings

Apart from providing a useful debugging tool during development the ability to communicate over a serial port allows the device to be configured without having to modify and re-flash the firmware. To achieve this I moved all of the values that control the behaviour into an array of bytes and treat it as a virtual bank of registers.

This includes all the configuration values (trigger levels for low battery and light activation, flash rates for the LED and other properties) as well as state information (current readings for the voltage and motion sensor for example). The configuration values can be saved to EEPROM and will be loaded when the device powers up. If there are no values in the EEPROM a set of suitable defaults will be used instead.

The full set of available registers are described in the following table.

Register Index Description
CONFIG_FIRMWARE 0 Firmware version (read only).
CONFIG_TRIGGER 1 Trigger value for motion/light sensor.
CONFIG_COUNT 2 Number of sequential trigger readings required.
CONFIG_LOW_POWER 3 Low power detection level.
CONFIG_LIGHT_ON 4 Time to keep the light on (in seconds).
CONFIG_LIGHT_START 5 Starting PWM value for turning on light.
CONFIG_LIGHT_STEP 6 Step value for turning on/off light.
CONFIG_LED_ON 7 Duration (in 1/10th sec) to keep LED on.
CONFIG_LED_OFF 8 Duration (in 1/10th sec) to keep LED off.
CONFIG_LED_ON_LOW 9 Duration (in 1/10th sec) to keep LED on (LP).
CONFIG_LED_OFF_LOW 10 Duration (in 1/10th sec) to keep LED off (LP).
STATE_POWER 11 Current battery power reading.
STATE_MOTION 12 Current motion/brightness reading.
STATE_LIGHT 13 Seconds remaining before the light goes off.
STATE_COUNT 14 Current trigger level count.

Each of these registers can be read over the serial connection and configuration values can be set. This allows the behaviour of the device to be easily customised for the environment it is in and provides some basic monitoring. All that is needed is a simple communications protocol to support this and a client side tool to provide monitoring and configuration capabilities.

Communications Protocol

The protocol used to communicate with the device is deliberately simple, each packet contains a single 16 bit value which is used to describe a command (or response) with parameters. The packet format consists of a start character (!), a sequence of 4 printable hex characters for the value, a single printable hex character as a checksum and is terminated by a newline character. On the wire this comes to a total of 7 ASCII characters and is very easy to verify on the ATtiny without needing a lot of memory or code to do so.

The protocol is implemented in a ping/pong method - for each request sent to the device a single response will be returned. Failure to receive a response indicates a communication error or an invalid request. Here is what the code looks like to send a packet to the device and read a response:

def __send(self, value):
  """ Send a value as a packet
  """
  if self.serial is None:
    raise Exception("Attempting to send on an unopen port")
  # Retry until we get a response
  retries = RETRY_COUNT
  while retries <> 0:
    packet = "%c%04X%1X%c" % (
      CHAR_START,
      value,
      self.__checksum(value),
      CHAR_END
      )
    self.serial.write(packet)
    # Read back what we just sent (side effect of the half-duplex UART)
    self.serial.read(PACKET_LENGTH)
    # Read the return value and convert it
    response = self.serial.read(PACKET_LENGTH)
    if len(response) == PACKET_LENGTH:
      value = int(response[1:5], 16)
      return value
    # Wait and try again
    sleep(0.1)
    retries = retries - 1
  # Failed, raise an exception
  raise Exception("No response from device.")

As I mentioned earlier anything sent to the device will be echoed back to the client so the code above immediately reads back the command it just sent and discards it before looking for any response from the ATtiny. There is no error checking or validation of the response in the sample above, that needs to be added sometime in the future.

The supported commands that can be sent to the device are:

Command Hex Description
CMD_GET 1R00 Get the current value of register R.
CMD_SET 2RNN Set the value of register R to NN.
CMD_SAVE 3000 Save the configuration registers to EEPROM.

The response codes that the device can return are:

Command Hex Description
STATUS_OK 0000 The operation was successful.
STATUS_INF 1RNN The value of register R is NN.
STATUS_ERR FFFF An error occurred during the operation.

The STATUS_INF response is sent in reply to CMD_GET and CMD_SET commands, the STATUS_OK and STATUS_ERR commands will be sent in response to CMD_SAVE.

Configuration Tool

I wrote a small Python class to handle communication with the device using an FTDI serial cable. This allows me to quickly develop useful tools to help with debugging and configuration. One of the first tools I wrote was a simple monitoring program that dumps the sensor data to a comma-delimited text file for later analysis with a spreadsheet.

Configuration

I wrote a small GUI in Python (using GTK) to allow you to change the configuration values and keep an eye on the current state. I've found that the LDR's vary in behaviour so it is necessary to adjust the trigger level to match the behaviour of the component you are using.

Board Layout and Casing

The circuit is very simple so a simple small home made PCB will do the trick. I managed to come up with a single sided layout that only uses a single jumper wire. I only need three devices in total so it doesn't seem worth doing a two layer board and having it made up at a PCB fabrication service - etching that many boards by hand is not too onerous a task.

Casing

The casing was a little more problematic - I wanted to utilise my 3D printer to create a custom case rather than jury rig a standard project box for the purpose, it's one of the more complex objects I've designed in OpenSCAD. It was a bit fiddly to design but I came up with a working design after only a single prototype print. The design files are in the GitHub repository as well so you can have a look at them for yourself.

Summary

This has been an interesting project to work on (and immediately useful as well as being fun to do). The ATtiny chips are very capable devices and make for very simple, small circuit designs. Keeping the IO requirements down by sharing pin functionality or looking for hardware and software tricks to multiplex them is a great learning experience. An additional bonus is that it's very easy to move up to an ATmega chip if you do run out of IO.

The development cycle isn't as quick as using the Arduino environment (without a bootloader you have to manually pull the chip out of the circuit and into the programmer for each code update) but it's not overly complex either. I managed to put everything I need in the Makefile so it was a painless process to compile and flash the code when I made changes.

I have a few other projects in mind that would work well with an ATtiny as the main CPU so you'll definitely see some more articles on the site about them.

I hope you enjoyed this project write up, if you wind up building one for yourself or using the design for other purposes I'd love to hear about it. Once again all the design files for this project are available on GitHub under a Creative Commons Attribution-ShareAlike 4.0 International License so you are welcome to take them and use them as you see fit.


Recommended Sites

  • EEWeb

    Electrical Engineering News, Resources, and Community.

  • Sprites Mods

    A collection of projects from a very talented engineer.

  • Blondihacks

    A great collection of hacks and projects by Quinn Dunki.

  • Embedded Projects

    Embedded Projects from Around the Web.

Donate Doge

Like the site? Be kind and send a few Dogecoin my way.
DAqGZFkVj9meRgHMve9W5KQshdvZ3UCtGF