SeeedFusion PCB Service

Seeed were kind enough to offer me a coupon towards their Seeed-Fusion PCB service. I had a design ready which I had recently converted from Eagle to KiCad so it seemed like an excellent opportunity to try them out.

Board pricing structure

I am a maker, building things in my spare time, so I tend to make small boards in low quantities. The SeeedFusion pricing structure is optimized for larger boards at larger volumes. However, they usually have a offer in place to encourage prototypers and makers  to use their service for smaller volumes. When I ordered the offer was $9.90 for 10 boards up to 100mm by 100mm, at the time of writing the price has been reduced to $4.90.

This is an excellent deal if you want ten 100mm by 100mm boards, but it isn’t great if you just want a few small boards. 100mm by 100mm is larger than a Raspberry Pi or an Arduino. The board I had in mind was 0.8″ by 0.5″ (20.32mm x 12.7mm). The result of the special offer is that introduces a discontinuity in the pricing structure. I made the chart below to illustrate how the pricing in US dollars per square inch changes with board size and quantity by plugging different parameters into the order form.

In my case ten boards would have cost $9.90 whereas 30 boards cost $11.78 so I decided I might as well go for 30 boards.

Shipping costs

Free shipping is a wonderful thing because you don’t have to factor shipping costs into your purchase decision. However SeeedFusion charges shipping even on small orders. When I order components from AliExpress I usually get free or less than $2 China Post ePacket shipping to the US for small items. It would be great if SeeedFusion could offer a similar service on small orders.

The cheapest shipping option on my one ounce order was $11.52. So even at 30 boards this doubles the price of the board. In my case this added up to $0.78 per board.

I expect the shipping costs increase as the order size increases, but you don’t get to see the shipping cost until you add an order to your cart. So making the overly simplistic assumption that shipping on all order sizes is $11.52 we get the following price chart.

Stencils

Seeed-Fusion stencils start at $19.90 which is a reasonable price. However when I added a stencil to my cart it added $32.85 to my shipping costs. The reason is that the smallest stencil is 28cm by 38cm which is larger than a letter size sheet of paper. I like using a stencil for SMD boards, but I want a nice small stencil for prototyping as I am only making one board at a time.

HASL or ENIG finish

Exposed copper pads tarnish quickly, so they need to be finished to protect them. The prices above are for HASL which is basically a layer of solder on the exposed copper. Another option is ENIG which is an electroless nickel plating covered with a thin layer of immersion gold.

ENIG is flatter than HASL and doesn’t migrate during reflow so it is a better choice for fine pitched SMD parts and strongly recommended for BGAs.

The prices above jump dramatically (e.g. from $9.90 to $29.90 for 10 boards) when you check the box for ENIG. For my board with 0603 SMD components and 0.5mm pitch IC leads HASL is fine.

DRC Rules

When ordering from a PCB fabricator, especially for the first time, it’s important to understand what the limitations are. The SeeedFusion PCB Specification gives a good summary of their manufacturing capabilities. Their capabilities are in line with or better than other prototype PCBs services I have used with 6 mil minimum track width/spacing, 6 mil minimum annular ring and 0.3mm (12mil) minimum drill size.

I particularly liked that they specified their minimum silk screen size (23 mil) as I have made my text too small before now.

Uploading and reviewing design

SeeedFusion only accepts Gerber files. Some other prototype PCB fabs will accept Eagle and KiCad files directly now. This is acceptable as it is a good idea to learn how to generate Gerber files and verify them with a Gerber viewer before sending to a fab anyway. But this is a additional step in the process that might put off people producing their first boards.

They provide a link to EasyEDA’s Gerber Viewer so you can verify your Gerber upload before you submit your order. However I found the size of the rendering a little too small, which makes verification more painful. You can zoom in using the scroll wheel on your mouse, if you have one, but the rendering resolution doesn’t increase, you just get a pixelated image.

Once you have submitted your order you cannot go back and download or view the Gerbers. The ability to download your original Gerbers is especially useful if your boards don’t turn out right. Then there is no wondering which files you actually sent to the fab.

Fabrication time

I submitted my order to SeeedFusion on a Saturday, received notification that it had being sent to manufacturing on Tuesday and that it had been sent to their shipping agent on Friday. Five business days is a fast turnaround time for a non-express order.

Shipping time

Shipping time obviously isn’t entirely within Seeed’s control, but international shipping is still an important consideration when ordering prototype boards from China.

Seeed offers three shipping options, Registered Post (12 to 22 days), DHL (3 to 5 work days) and FedEx (4 to 8 work days).

There are a lots of variables involved such as the shipping option you choose, local customs processing and local delivery time. US duty should not be required on orders under $800 in value.

I chose Registered Post in order to keep the cost as low as possible. My order showed up on MyUSPS as “shipping label created”, 11 days after being shipped and arrived 4 days later. My second shipment showed up on MyUSPS 13 days after being shipped, but wasn’t marked as in transit for another 15 days and arrived 2 days later. So in my case the international and local shipping times were pretty consistent at around 15 days total, but there was an inconsistent delay at the US boarder, presumably for US customs.

Customer Service

I was unlucky in that my first order had fabrication issues. The silk screen was misaligned and solder mask was missing from the tented vias on one side of the board.

When I contacted customer service they were very responsive. They looked at the original Gerbers, confirmed that it was a mistake on their end and agreed to re-spin and re-ship the boards at no additional cost.

Manufacturing Quality

The copper, solder mask and drill accuracy were all very good and the vias were nicely tented.

Every board had a neatly routed outline, with no support tabs to remove, which is very nice.

The HASL surface isn’t as nice as ENIG, but it was perfectly adequate for my circuit.

The silkscreen registration was approximately half a mil too high and to the left on the top of the board. Also small text and graphics are noticeably pixelated. I’ve seen higher resolution silkscreen from other fabs.

An annoying feature is the tracking numbers that get added to every board (10220A-2/400958 above). Seeed decide where on your board to place this number.

Board Testing

I applied solder paste to a PCB with a stencil, populated the components, reflowed on a board heater and hand soldered the header pins.

Everything went smoothly and the circuit (a buck regulator) performed well at max current.

 

 

Making a buck converter

In my previous post I explained how to decide when you need a buck converter rather than a linear regulator. In this post I am going to explain how to build a buck converter into your project.

Drop in replacement

The simplest option, if you are used to using a linear regulator in a TO-220 package, is to buy a drop in replacement. For example Adafruit sell 1A 5V and 1A 3.3V drop in buck converters for just under $15 at the time of writing. Or for around $4.30 you could get a muRata OKI-78SR 1.5A 5V or 1.5A 3.3V module from DigiKey or Mouser.

From China

You can get small buck converters from China for a dollar or less, for an example search for Mini-360 buck converter. These are usually small PCBs with connection holes suitable for a wire or a header pin. The hole spacing is rarely breadboard compatible, but you can attach wires or carefully bend long header pins. They typically don’t come with datasheets, so you might want to test them before depending on the maximum advertised specifications. Shipping is slow, but you can’t beat the price.

Make your own

Finally you can make you own buck converter. Some reasons for building one yourself:

  • It is a great learning experience
  • You have complete control over and knowledge of the specifications
  • You can design the buck converter into the same PCB as the rest of your project

Picking components

There are thousands of buck converter ICs available. Your voltage and current requirements will help to narrow the field, but there will still be a large number to choose from. Other considerations are efficiency, frequency, circuit size, the number of external components, features, availability and price.

Small, simple and cheap

A common requirement is for a circuit that takes up a relatively small amount of board space at a reasonable cost. Unless you need a large amount of power a buck converter with built in MOSFETs will be ideal. While all in one modules do exist, they are currently much more expensive, so a few external components such as an inductor, capacitors and resistors are to be expected.

Efficiency, frequency and package

Improved efficiency means less heat is generated, requiring less space for thermal management, for this reason prefer synchronous designs that substitute a less efficient flyback diode for a second MOSFET. Higher frequency switching allows for smaller inductors and capacitors leading to a smaller design. Choose a package that matches you skills, avoid small packages like BGA unless you are used to working with them.

A handy search tool

Texas Instruments have a large number of buck converters, and nice tools for picking designs, you can try entering your criteria into the Power Quick Search tool.

And the winner is

My criteria for a recent project was a 12V input, 5V output and <500mA. I chose a Texas Instruments TPS560200 with the following specs:

  • 4.5V to 17V input
  • 0.8V to 6.5V output
  • 500mA maximum current
  • Synchronous
  • 80% efficiency
  • 600 KHz switching frequency
  • SOT-23 package
  • Light load efficiency, thermal protection and over current protection
  • Price about $0.89 for one, about $0.36 each for a real of 3000

This is a five pin SOT-23 surface mount component with about 0.04″/1mm lead pitch which should be manageable as a first surface mount design.

Picking passive components

A buck controller will generally need to be paired with the following components:

  • Input capacitor
  • Inductor
  • Output capacitor
  • Voltage divider resistors

The datasheet will provide guidance on how to pick the components. If you are using a TI buck controller then you can also use their WeBench tool to help you pick the right components for your design.

I don’t recommend the schematic and layout export from WeBench. My experience was that none of the wires were connected in the schematic and the board layout wouldn’t import into the free version of Eagle CAD.

Equivalent series resistance

In power circuits the capacitors are normally moving large amounts of current, so the equivalent series resistance, ESR, becomes an important consideration. For instance a typical 22uF electrolytic capacitor might have an ESR of 10 ohms whereas a multilayer ceramic capacitor might have an ESR of 10 mOhms (1/1000th).

In general:

  • Use ceramic multilayer XR5 or XR7 capacitors as they have very low equivalent series resistance. Otherwise look for low-ESR capacitors
  • You will generally pair a high frequency input bypass capacitor, usually 0.1uF, with a larger bulk capacitor, e.g. 10uF
  • You can use two capacitors in parallel to lower the ESR

Layout guidelines

There a several things to consider when laying out your circuit:

The current loop has high current slew rate so needs to be kept as small as possible to reduce parasitic inductance

    • Place the input bypass capacitor (small, high frequency) close to the IC
    • The input bulk capacitor should be reasonably close to the IC
    • Place the output capacitor close to the inductor
    • Connect directly to the ground plane

Removing heat

    • Use copper pours to improve heat dissipation
    • Use thermal vias to move heat to the bottom of the board

Watch out for noise from switching

    • Place the inductor close to the IC
    • Don’t expand copper to the inductor beyond what’s required for current
    • Keep the feedback return path away from the inductor and input capacitor

To improve efficiency

    • Don’t run ground plane under the inductor

Putting the rules into practice

Here is an example PCB layout for 5V 500mA Buck converter with breadboard friendly headers. U1 is a Texas Instruments TPS560200 buck controller IC. C1 and C2 are 10uF output capacitors. C3 is a 0.1uF input bypass capacitor and C4 is a 10uF input bulk capacitor. The R1 and R2 resistors act as a voltage divider on the feedback from the output to the IC. The ratio of the voltage divider determines the output voltage.

Buck Converter Layout

To minimize parasitic inductance in the current loop, the inductor and input capacitors are right next to the IC and the output capacitors are placed right next to the inductor. These current loop components are connected directly to a top layer ground pour which is connected to the bottom layer ground plane with lots of vias.

To maximize heat dissipation there are thermal vias underneath the IC and in the ground pour close to the IC to help move heat to the large ground plane on the bottom of the board. As this package doesn’t have a thermal pad around 40% of the heat will dissipate through the pins, so there are large pours connected to the ground pin and Vin pin.

To minimize switching noise the inductor is close to the IC and the connection to the IC hasn’t been expanded into a large copper pour. The traces and voltage divider resistors for the feedback signal have been routed away from the inductor.

Efficiency is optimized by adding a cutout in the ground plane under the inductor to avoid inductor efficiency being reduced by eddy currents forming in the ground plane.

Tented vias

Some of the thermal vias (smaller green circles) are very close to the SMD pads. This introduces some risk of an accidental connection to ground if the solder from the pads were to stray to the exposed copper of the via. To avoid this risk we can arrange for each via to be covered in solder mask, the term for this is a tented via.

OshPark PCB with Tented Vias

In the board above, ordered from OSH Park, you can see the 16mil tented vias. The annular rings of the small tented vias are covered in purple solder mask, whereas the annular rings for the large header pin vias are exposed.

Eagle CAD

To make small enough vias to fit under the package for your IC you may need to specify a new drill size if one of the default drill sizes isn’t appropriate. In Eagle CAD you can select a new 16mil drill size by selecting:

Change –> Drill –> … (New Drill)

Entering 0.016 (or 16mil) and then clicking on any existing vias you want to resize or using the “Draw a via” tool to create new vias.

The Eagle DRC (Design Rule Check) rules in Eagle typically specify that vias over a certain size should always have a stop mask. That is the copper annular ring should not be covered in solder mask.

Eagle Tented Via DRC

To ensure the thermal vias are tented (covered in solder mask) you should change the DRC rules so the stop mask is only automatically applied to vias larger than your selected tented via size, e.g. 16mil

Tools –> DRC –> Masks –> Limit –> 16mil

Then for vias 16mil or smaller you will be able to turn the stop mask on or off in the via properties.

KiCad

Creating thermal vias, also referred to as via stitching, can be confusing in the current version of KiCad as lone vias created by the Tracks And Vias tool will become disconnected from the GND net when you refill the filled zones or run DRC. There are plans to address this in version 5.0 of KiCad, but until then there are a couple of workarounds.

The simplest, if not the prettiest, is to make sure that your vias are connected to a GND pad with a track. The tracks will be absorbed by the copper pour (filled zone) so they don’t affect the final PBC.

The other solution is to create a new “via” footprint in Footprint Editor and add this footprint directly to your board in PcbNew.

To create a via footprint:

  • Launch Footprint Editor
  • Click New Component
  • Make the Name and Ref text Invisible
  • Add a pad at the origin (x=0, y=0)
  • Edit the pad
  • Set Copper to All Copper Layers
  • Remove F.Mask and B.Mask from Technical Layers to make the via tented
  • Set the hole size (e.g. 0.016″) and pad size (e.g. 0.03″)
  • Set Copper Zones Pad Connection to Solid
  • Save to an existing or new library with a name like via-16mil

To add the via footprint to you board

  • Click Add Footprint in Pcbnew
  • Click on the board and select your new via footprint (e.g. via-16mil)
  • Edit the pad (not the footprint)
  • Set the Net Name to GND

You can do this again to add another via or use Duplicate Footprint to copy an existing via footprint.

A finished example

Here is an example of a completed breadboard friendly 5V buck converter.

500ma DC-DC Buck Converter on a Breadboard

Powering a circuit with linear regulators and buck converters

Most electronic projects need a stable voltage source, typically 5V or 3.3V DC. Here we discuss how to choose when to use a linear voltage regulator and when to use a buck converter.

What’s the difference?

The standard advice for beginners is to use a linear voltage regulator, for example an LM7805. Linear voltage regulators are great for low current draws and low voltage drops, but become inefficient and surprisingly hot when used for higher current draws or larger voltage drops. The reason for this is that they turn the excess voltage into heat.

Buck converters turn the input voltage on and off quickly (PWM) to generate a lower average voltage and smooth the output to generate a consistent output voltage. This makes them far more efficient: capable of dropping a large voltage at a higher current while dissipating less heat.

Which one should you use?

Which is best for your project depends on:

  • Cost
    Buck converters typically cost more, although that difference is diminishing as buck converters become more common
  • Efficiency
    How much of the power is available for your circuit and how much is wasted might be a concern especially if you are powering your circuit from a battery.
  • Heat
    Excess heat can be an issue especially if your circuit is in an enclosure. It is essential to understand how much heat you need to dissipate and have a strategy for getting rid of it.
  • Size
    For small loads linear regulators are usually smaller, but for larger loads buck converters can be smaller.

Voltage and current requirements

Before picking components you need to understand the voltage and current requirements of your circuit. You will need to match voltage specifications closely, but you don’t have to match current requirements exactly.

You will want sufficient current to power your circuit during its peak current draw. Having more current available than your circuit requires will give you a safety margin and will also enable your power supply to run cooler. However, you might be adding unnecessary cost and size to your project if you over specify too much.

As an example I want to power a circuit with 5V and 3.3V logic from a 12V input supply. I don’t expect to draw more than 50mA from the 3.3V supply or more than 200mA from the 5V supply. So I have a maximum current requirement of 250mA. To add a safety margin and ensure my circuit runs cool I would ideally be looking for something than can supply 500mA.

Dissipating heat in linear regulators

To get the maximum power (in Watts) that a linear regulator would need to dissipate we multiply the voltage drop by the maximum current.

Power Dissipated = Voltage Drop * Current

In my example to get to 5V from a 12V supply the linear regulator needs to drop 7V volts. If we assume that the 3.3V will be sourced from the 5V output then the we have a maximum current of 250mA.

Power Dissipated = 7 V * 0.250 A = 1.75 W

A linear regulator is limited by its maximum junction temperature, which is normally 125 or 150 °C. You can use the thermal information in the datasheet to estimate what this temperature would be in your application.

Thermal resistance measures how heat flow is resisted, similar to the way electrical resistance resists current flow. For a linear regulator standing in still air without a heat sink the junction to ambient thermal resistance measures how heat flow from the junction to the surrounding air is resisted.

To estimate the junction temperature rise above the ambient temperature you multiply the power that the linear regulator needs to dissipate by the junction to ambient thermal resistance specified in the datasheet.

Temperature Rise = Power * Junction to Ambient Thermal Resistance

We add the temperature rise to ambient temperature to get the junction temperature. It’s typical to use a value of 25°C (77°F), but the actual operating environment of your circuit could be a 21°C air conditioned home or a 105°C engine compartment.

Junction Temperature = Ambient Temperature + Temperature Rise

Check the datasheet of your component for the junction to ambient thermal resistance as this number varies between devices even within the same package. For example, just considering the ubiquitous LM7805 TO-220 1.5A linear regulator. Texas Instruments specifies 23.9 °C/W whereas ST specifies 50 °C/W and I have seen values as high as 78 °C/W in a TO-220 package.

The ST LM7805 datasheet specifies a maximum current of 1.5A and a junction-to-ambient thermal resistance of 50 degrees C/W in a TO-220 package. So we just multiply this by the number of watts we need to dissipate to get a temperature rise of 87.5°C.

Temperature Rise = 1.75 W * 50 °C/W = 87.5 °C

Assuming an ambient temperature of 25 degrees C we get a junction temperature of 112.5 °C, which is just below typical maximum gate temperature of 125 degrees C.

Junction Temperature = 25°C + 87.5°C = 112.5°C

So this TO-220 linear regulator would work fine as long as the ambient temperature is less than 37.5 degrees C (100 degrees F), but it would run too hot to touch without a heat sink.

Adding a heat sink

To dissipate more heat you can add a heat sink. This adds some additional cost and takes up more space. Also as you start dissipating more heat you might need to consider how heat is managed within your circuit enclosure.

To see the impact of a heat sink you need to calculate the new junction to ambient thermal resistance of the linear regulator plus the heat sink. To do this look up the junction to case thermal resistance of the linear regulator and the thermal resistance of the heat sink. Then add these two numbers together.

Junction to Ambient Thermal Resistance = 
    Junction To Case Thermal Resistance + Heat Sink Thermal Resistance

The ST LM7805 TO-220 has a junction to case thermal resistance of 5 °C/W, so if we attach a 23 °C/W heat sink from Adafruit we get a total junction to ambient thermal resistance of 28 °C/W.

Junction to Ambient Thermal Resistance = 5°C/W + 23°C/W = 28°C/w

Which would give us a junction temperature rise of 49 °C.

Temperature Rise = 1.75 W * 28°C/W = 49°C/W

Assuming an ambient temperature of 25 °C would give us a junction temperature of 74 °C. About the temperature of a hot beverage from your favorite coffee shop.

Junction Temperature = 25°C + 49°C = 74°C

Buck converters

Buck converters are not perfectly efficient and will generate some heat. How much heat can be determined by the efficiency specified in the datasheet.

To determine how much power is dissipated as heat we need to know the difference between the input power and the output power.

Power Dissipated = Output Power - Input Power

To get the input power we can divide the output load by the efficiency of the buck converter.

Output Power = Input Power * Efficiency

Input Power = Output Power / Efficiency

For instance the TI TPS560200 buck controller is about 80% efficient when converting from 12V to 1.05V at 250mA. It will likely be slightly more efficient when converting 12V to 5V. At 80% efficient the buck converter will dissipate 20% of its input power as heat.

In our example we have a 250mA load at 5V which is 1.25 W.

Output Power = 0.25 A * 5 V = 1.25 W

So the input power is 1.5625 W.

Input Power = 1.25 W / 0.80 = 1.5625 W

And the power dissipated as heat is 0.3125 W.

Power Dissipated = 1.5625 W - 1.25 W = 0.3125 W

The MOSFETs are contained within this IC and the MOSFETs account for most of the losses in a buck converter, so we are going to ignore that some of the losses are in the external components.

The datasheet specifies a junction to ambient thermal resistance of 166.8 °C/W. The junction to ambient thermal resistance isn’t a perfect metric as PCB design will affect the actual thermal resistance, but it’s good enough for us to work with as an estimate.

With 0.3125 W to dissipate the temperature rise would be 52.125 °C.

Temperature Rise = 0.3125 W * 166.8°C/W = 52.125°C

So with an ambient temperature of 25 °C the junction temperature would be approximately 77.125 °C.

Junction Temperature = 25°C + 52.125°C = 77.125°C

Additional voltages

To get 3.3V we could we could use the 12V supply directly (dropping 8.7V), but seeing as we already have a 5V output, we can drop 1.7V from 5V. At 50mA this would be just 0.085W for a linear regulator to dissipate.

Power Dissipated = 1.7 V * 0.050 A = 0.085 W

The Microchip Technology MCP1700T-3302E/TT is a 3.3V linear regulator in a 3 pin SOT-23 surface mount package. The datasheet quotes the junction to ambient thermal resistance of 336 °C/W. So the gate temperature with a 25 °C ambient temperature would be 53.56 °C.

Temperature Rise = 0.085 W * 336°C/W = 28.56°C

Junction Temperature = 25°C + 28.56°C = 53.56°C

So in this case a linear regulator is sufficient and a SOT-23 takes up very little board space (about 3mm by 3mm).

So for our example circuit a good choice might be to use a buck converter to drop from 12V to 5V and a linear regulator to drop from 5V to 3.3V.

In my next post Making a buck converter we look how to incorporate a buck converter into your circuit.

USI Serial UART Send on ATtiny

 

In my previous post, USI Serial UART Receive on ATtiny, we discussed how to implement a Serial UART receiver on an Atmel ATtiny using the USI module. This post describes how to implement a simple UART transmitter using the USI module.

UART Signal

The transmitter will be simpler than the receiver as we just need to generate the signal with the correct timing.

serialtiming

Our transmitter will send 8 data bits with one or two stop bits.

#define STOPBITS           1

Calculating the width of a bit

We define the clock speed, F_CPU, and the baud rate. Note F_CPU is already defined in Arduino and may already be defined in your development environment, it’s normally defined as a build symbol.

#define F_CPU              8000000
#define BAUDRATE           9600

We can use these to calculate the number of CPU clock cycles per bit width.

#define CYCLES_PER_BIT     ( F_CPU / BAUDRATE )

If this number is 255 or less then we set the clock source to be the CPU clock, otherwise we will use the prescaler to divide the clock input to Timer/Counter0 by 8.

We use the bottom three Clock Select bits of Timer/Counter0 Control Register B, TCCR0B, to configure the prescaler. A Clock Select value of 1 is for the CPU clock and a value of 2 is for CPU clock divided by 8.

#if (CYCLES_PER_BIT > 255)
#define DIVISOR             8
#define PRESCALE            2
#else
#define DIVISOR             1
#define PRESCALE            1
#endif
#define FULL_BIT_TICKS      ( CYCLES_PER_BIT / DIVISOR )

Choosing the Tx pin

We can configure the USI module to output data on PB0 (SDA), used in Two Wire I2C mode or PB1 (DO), used in Three Wire SPI mode. But it can only read data from PB0 (DI/SDA).

A serial UART has separate transmit, Tx, and receive, Rx, lines. So if we want to enable both transmit and receive on the same device then we will need to use DO/PB1 for output.

ATtiny85 pinout

Sending the first buffer

Finally we need to fill the USI Data Register with some data and start the clock countdown. Our packet is going to be longer than the 8 bit USI data register once we have added start and stop bits. So will need to split our packet across two buffer sends. The first buffer from our send function and will contain the start bit and 7 bits of data. The second buffer will be sent from the USI overflow interrupt and contain 1 bit of data and one or two stop bits.

usi_send_buffers

Because the USI overflow interrupt will be called twice, once after sending the first buffer and again after sending the second buffer, we need a way to tell it which buffer it has just sent, so it can decide what to do next. We will store this state in a volatile variable as it will be accessed from an interrupt.

enum USISERIAL_SEND_STATE { AVAILABLE, FIRST, SECOND };
static volatile enum USISERIAL_SEND_STATE usiserial_send_state = AVAILABLE;

The USI sends the most significant bit (bit 7) and shifts left on each clock cycle, but the UART standard requires that the least significant bit (bit 0) be sent first. So the data needs to be loaded into the USI register in reverse bit order.

We can use the following function to efficiently reverse the bits in a byte:

static uint8_t reverse_byte (uint8_t x) {
   x = ((x >> 1) & 0x55) | ((x << 1) & 0xaa);
   x = ((x >> 2) & 0x33) | ((x << 2) & 0xcc);
   x = ((x >> 4) & 0x0f) | ((x << 4) & 0xf0);
   return x;
}

We will use a volatile variable to store the reversed data as it will be accessed from an interrupt.

static volatile uint8_t usiserial_tx_data;

The send routine

The first thing our serial send routine does is set the usiserial_send_state variable to indicate that we are sending the FIRST buffer.

void usiserial_send_byte(uint8_t data)
{
  usiserial_send_state = FIRST;

Then we save the reversed data in our volatile usiserial_tx_data variable.

  usiserial_tx_data = reverse_byte(data);

Configuring the timer

We want to configure Timer/Counter0 to Clear Timer on Compare Match (CTC) mode. We do this by setting the three bits of the Waveform Generation Mode, WGM0, flag to 2 (binary 010). Just for fun bit 0 and bit 1 are in the TCCR0A register and bit 2 is in the TCCR0B register. The other bits of TCCR0A are set to zero to indicate normal port operation. The least significant three bits of TCCR0B are used for the Clock Select mode and the rest of the bits (including WGM0 bit 2) are set to zero.

  TCCR0A = 2<<WGM00;           // CTC mode
  TCCR0B = CLOCKSELECT;        // Set prescaler to clk or clk /8

Then we reset the prescaler to indicate that we changed its configuration and start Timer/Counter0 at zero.

  GTCCR |= 1 << PSR0;           // Reset prescaler
  TCNT0 = 0;                    // Count up from 0

We store the number of bits to count in Output Compare Register A, OCR0A.

  OCR0A = FULL_BIT_TICKS;       // Trigger every full bit width

 Configuring the USI module

To use PB1 (DO) for USI output we select Three Wire SPI mode by setting Wire Mode, USIWM[1:0], to 01 in the USI Control Register USICR and setting the PB1 (DO) data direction to output. We also use the USI Control Register to enable the USI Counter overflow interrupt, USIOIE=1 and select Timer0 Compare Match as the input source, USICS[1:0]=01 and USICLK=0.

  USICR = (0<<USIWM1)|(1<<USIWM0)|       // Select three wire mode so USI output on PB1
    (1<<USIOIE)|                         // Enable USI Counter OVF interrupt.
    (0<<USICS1)|(1<<USICS0)|(0<<USICLK); // Timer0 Compare Match as USI clock source
  DDRB |= (1<<PB1);                      // Configure USI DO, PB1 as output

Now we can load up the USI data register with the start bit (low) and the first 7 bits of reversed data.

  USIDR = 0x00 |                     // Start bit (low)
          usiserial_tx_data >> 1;    // followed by first 7 bits of serial data

Finally we ensure that the USI Overflow Interrupt Flag is cleared and set the counter to increment eight times before overflowing. The 4 bit USI counter can hold a value from 0 to 15 and overflows as it attempts to increment to 16, so we set the starting value of the counter to 16 – 8 = 8.

  USISR = 1<<USIOIF |               // Clear USI overflow interrupt flag
          (16 - 8);                 // and set USI counter to count 8 bits
}

The USI interrupt vector

After the first buffer has been sent by the USI module, the USI overflow interrupt vector will be called with our state variable, usiserial_send_state, set to FIRST. Now we set usiserial_send_state to SECOND to indicate we are sending the second buffer.

ISR (USI_OVF_vect)
{
  if (usiserial_send_state == FIRST)
  {
    usiserial_send_state = SECOND;

We load up the USI Data Register with our last bit of data and the stop bits (high). There should only be one or two stop bits, but we need to set the rest of the bits to something, so we set them high. Interestingly, while one and two are the UART supported number of stop bits, values higher than two would work as the serial UART standard supports periods of line idle (high) between packets.

    USIDR = usiserial_tx_data << 7         // Send last 1 bit of data
            | 0x7F;                        // and stop bits (high)

The number of bits to send is the number of stop bits + 1, so we set the counter to 16 minus this value. The USI Overflow Interrupt Flag is cleared to indicate the interrupt has been processed. If we did not do this the microcontroller would immediately re-invoke the interrupt vector rather than when the counter overflowed.

    USISR = 1<<USIOIF |                 // Clear USI overflow interrupt flag
            (16 - (1 + (STOPBITS)));    // Set USI counter for 1 data bit and stop bits
  }

After the second buffer

After the second buffer has been sent by the USI module, the USI overflow interrupt vector will be called with our state variable, usiserial_send_state, set to SECOND.

Our packet has been sent, so we just need to turn off the USI module returning the Tx pin to an idle high state. Just to be safe we make sure PB1 is still configured as a high output.

  else // usiserial_send_state == SECOND
  {
    PORTB |= 1 << PB1;                  // Ensure output is high
    DDRB |= (1<<PB1);                   // Configure USI_DO as output.
    USICR = 0;                          // Disable USI.

Finally we clear the USI Overflow Interrupt Flag and set our state variable to AVAILABLE, to indicate that our routine is available to send another packet.

    USISR |= 1<<USIOIF;                  // clear interrupt flag
    usiserial_send_state = AVAILABLE;
  }
}

Sending multiple bytes

To keep this tutorial simple our routine only sends one byte at a time. To send multiple bytes we need to wait for our state variable to become AVAILABLE before calling our send routine with the next byte.

   char message[] = "USI Serial\r\n";
   uint8_t len = sizeof(message)-1;
   for (uint8_t i = 0; i<len; i++)
   {
       while (!(usiserial_send_state==AVAILABLE))
       {
           // Wait for last send to complete
       }
       usiserial_send_byte(message[i]);
   }

For safety and convenience we could also wait for usiserial_send_state to become AVAILABLE at the very beginning of our send routine, usiserial_send_byte.

Enhancing for multiple bytes

When sending multiple bytes we stop the USI module after sending the second buffer and reinitialize the timer when we start sending the next packet. This introduces a small gap of idle time between packets, this isn’t a problem as the UART standard supports idle (line high) time between packets.

However if we knew that we were going to send another packet we could just load the USI Data Register with the first buffer of the next packet and set the state variable back to FIRST.

Arduino Timer

When running this code on Arduino we need to borrow the timer used for millis. To avoid disrupting the Arduino timer we should save the state of the timer registers in volatile variables.

#ifdef ARDUINO
volatile static uint8_t oldTCCR0A;
volatile static uint8_t oldTCCR0B;
volatile static uint8_t oldTCNT0;
#endif

We can save these registers in our send routine after setting the usiserial_send_state to FIRST.

#ifdef ARDUINO  
   oldTCCR0B = TCCR0B;
   oldTCCR0A = TCCR0A;
   oldTCNT0 = TCNT0;
#endif

Then we can restore the registers it in the USI Overflow Interrupt vector before setting usiserial_send_state to AVAILABLE.

#ifdef ARDUINO
       TCCR0A = oldTCCR0A;
       TCCR0B = oldTCCR0B;
       TCNT0 = oldTCNT0;
#endif

Arduino will lose the time it takes us to send each byte, approximately 1ms at 9600 baud, which will not be an issue for most programs. However, if you need to maintain Arduino time with more accuracy, see my post on Borrowing an Arduino Timer.

Get the example code

I have uploaded an Arduino sketch to GitHub at:

https://github.com/MarkOsborne/becomingmaker/tree/master/USISerialSend

This example sends the message “USI Serial” every second. Connect your favorite serial device’s Rx pin to PB1/DO.

USI Serial UART Receive on ATtiny

Atmel USI Block Diagram

Many ATtiny microprocessors don’t include a hardware UART, but do include a Universal Serial Interface, USI. The USI module can be used to implement SPI, TWI (also known as I2C) and UART serial interfaces. This post describes how to implement a simple UART receiver using the USI module.

Arduino Software Serial Library

The Arduino core for ATtiny includes a Software Serial library which implements a serial UART interface. This does not take advantage of the USI module and requires 2K of flash. This is probably your best option if you are using Arduino and have plenty of flash available. Using the USI allows you to use faster baud rates and less microcontroller resources, the approach below uses less than 1K of flash.

Atmel app note AVR307

Atmel describe how to use the USI to implement a serial UART for an ATtiny26 in app note AVR307. This provides great information and is worth a read. They also provide the source code at www.atmel.com/images/AVR307.zip.

We will be targeting the ATtiny25, ATtiny45 and ATtiny85 in the code below.

Borrowing Timer/Counter0

The USI module uses Timer/Counter0 which is also used by the hilowtech Arduino core to keep track of time. So if you are using Arduino you will need to borrow Timer/Counter0 and give it back. I’ve written a separate post describing how to do this: Borrowing an Arduino timer.

Calibrating the internal oscillator

You can use an external oscillator or the internal oscillator. In my experience the internal oscillator is factory calibrated accurately enough that you probably won’t need to do any user tuning.

But if you need to tune your internal oscillator, then take a look the following post: Tuning ATtiny internal oscillator.

UART input signal

A serial UART packet consists of a start bit, 5 to 9 bits of data, an optional parity bit and one or two stop bits. But we will just consider the typical configuration of 8 data bits and no parity bit.

When the input line is idle and no data is being transmitted it is held high. You will want to include a pull up resistor in you circuit or enable pull up on the pin so that a floating input doesn’t trigger a false start.

The start of a byte is indicated by pulling the input line low for one bit width. This is immediately followed by each data bit with low representing zero and high representing one with the least significant bit transmitted first. This is then followed by one or two stop bits which are high.

uart trace
UART Oscilloscope trace

Note how the bits appear in the reverse order in the oscilloscope trace above.

Timing a bit width

We are going to use Timer/Counter0 to time a bit width. This is an 8 bit timer, so the its maximum value is 255. We will need to use the Prescaler to adjust the clock input to Timer/Counter0 so that the number of ticks is 255 or less.

We need to choose the right Prescaler value for the regular ATtiny clock speeds of 1MHz, 8MHz and 16Mz and a range of common UART baud rates of such as 9600, 14400, 28800, 57600, 115200 and 230400. For example 9600 baud is 1666 CPU cycles at 16MHz, which is 208.25 when divided by 8, 208 whole CPU cycles with a 0.12% drift.

When setting the baud rate and CPU clock speed you will want the timer to be as accurate as possible, keeping drift below 5%. So higher baud rates won’t be available at lower CPU speeds. For example 230400 baud with a 1MHz clock is 4.34 CPU cycles (1000000/230400) which is just 4 whole CPU cycles with a 34% drift. And that’s before we take code execution time into account.

We define the clock speed, F_CPU, and the baud rate. Note F_CPU is already defined in Arduino and may already be defined in your development environment, it’s normally defined as a build symbol.

#define F_CPU               8000000
#define BAUDRATE            9600

We can use these to calculate the number of CPU clock cycles per bit width.

#define CYCLES_PER_BIT      ( F_CPU / BAUDRATE )

If this number is 255 or less then we set the clock source to be the CPU clock, otherwise we will use the prescaler to divide the clock input to Timer/Counter0 by 8.

We use the bottom three Clock Select bits of Timer/Counter0 Control Register B, TCCR0B, to configure the prescaler. A Clock Select value of 1 is for the CPU clock and a value of 2 is for CPU clock divided by 8.

#if (CYCLES_PER_BIT > 255)
#define DIVISOR             8
#define PRESCALE            2
#else
#define DIVISOR             1
#define PRESCALE            1
#endif
#define FULL_BIT_TICKS      ( CYCLES_PER_BIT / DIVISOR )

Using the USI

The USI module is optimized for use in either Two Wire I2C or Three Wire SPI mode, it doesn’t have a dedicated mode for UART. However the USI module is flexible enough that we can use an internal clock to trigger the USI to left shift the data bits into the USI data register.

We can use a pin change interrupt to detect the beginning of the start bit and then configure the USI to sample the value of the input pin in the center of each of the eight bits. We can then read the back the eight bit values that the USI shifted into the USI register and reverse them to get the original byte value.

Getting setup

ATtiny85 pinoutThe first thing we need to do is ensure that the USI data input pin, DI, is enabled for input. This pin 0 of port B, PB0 in the diagram above.

DDRB &= ~(1 << DDB0);          // Set pin PB0 to input
PORTB |= 1 << PB0;             // Enable internal pull-up on pin PB0

This is equivalent to the following Arduino function call:

pinMode(0, INPUT_PULLUP);

We will also ensure that the USI is disabled at this point.

USICR = 0;                     // Disable USI

Finally we want to enable pin change interrupts on pin PB0 so we can trigger at the beginning of the start bit.

GIMSK |= 1<<PCIE;               // Enable pin change interrupts
PCMSK |= 1<<PCINT0;             // Enable pin change on pin PB0

UART start bit

The pin change interrupt, PCINT0_vect, will fire when any pin on port B with pin change enabled in PCMSK changes from low to high or high to low. We are only interested on a high to low, falling edge, on pin PB0. We will disable pin change on the data input pin while we are reading the byte, so we just need to check whether PB0 is low.

ISR (PCINT0_vect)
{
  uint8_t pinbVal = PINB;
  if (!(pinbVal & 1<<PINB0))   // Trigger if DI is Low
  {
    onSerialPinChange();
  }
}

Move to the middle

To be resilient to any clock drift we will want the USI to take a sample in the middle of each bit. So we need to delay for half a bit width before starting the USI module.

#define HALF_BIT_TICKS      ( FULL_BIT_TICKS / 2 )

We should also account for the number of CPU cycles the interrupt vector and our code introduce between the beginning of the start bit and us starting the USI. I used the Atmel Studio simulator to get numbers for my code.

#define START_DELAY         ( 65 + 42 )
#define TIMER_START_DELAY   ( START_DELAY  / DIVISOR )

First we need to the disable pin change interrupts as we don’t want to trigger our start bit interrupt vector while we are reading data bits.

void onSerialPinChange() {
  GIMSK &= ~(1<<PCIE);            // Disable pin change interrupts

We configure Timer/Counter0 to Clear Timer on Compare Match (CTC) mode. We do this by setting the three bits of the Waveform Generation Mode, WGM0, flag to 2 (binary 010). Just for fun bit 0 and bit 1 are in the TCCR0A and bit 2 is in TCCR0B, but as bit 2 is being set zero you won’t see it explicitly set in the code. The other bits of TCCR0A should be set to zero to indicate normal port operation. The least significant three bits of TCCR0B are used for the Clock Select mode and the rest of the bits (including WGM0 bit 2) should be set to zero.

  TCCR0A = 2<<WGM00;              // CTC mode
  TCCR0B = CLOCKSELECT;           // Set prescaler to cpu clk or clk/8

Then we can reset the prescaler to indicate that we changed its configuration and start Timer/Counter0 at zero.

  GTCCR |= 1 << PSR0;             // Reset prescaler
  TCNT0 = 0;                      // Count up from 0

We store the number of bits to count in Output Compare Register A, OCR0A. There is no code here to check that HALF_BIT_TICKS is actually greater than TIMER_START_DELAY, so be careful with choosing values for F_CPU, BAUDRATE and TIMER_START_DELAY.

  OCR0A = HALF_BIT_TICKS - TIMER_START_DELAY;

Finally we enable the output compare interrupt.

  TIFR = 1 << OCF0A;              // Clear output compare interrupt flag
  TIMSK |= 1<<OCIE0A;             // Enable output compare interrupt
}

Start the USI

When we reach the middle of the start bit, the Timer/Counter0 Compare Match A interrupt vector will fire.

ISR (TIMER0_COMPA_vect) {

First we disable the Compare Match A interrupt.

  TIMSK &= ~(1<<OCIE0A);          // Disable COMPA interrupt

We are going to configure the USI to sample every time Timer/Counter0 has counted up from zero to the value in Output Compare Register A, so we initialize the counter to zero and OCR0A to the full bit width.

  TCNT0 = 0;                      // Count up from 0
  OCR0A = FULL_BIT_TICKS;         // Shift every bit width

We configure the USI using the USI Control Register, USICR.

We want be notified when a byte has been read, so we set Counter Overflow Interrupt Enable, USIOIE, so that the USI_OVF interrupt will be called when the 8 bits have been shifted into the USI register.

We are not using either of the wire modes directly so we set the Wire Mode, USIWM, to zero indicating the automatic wire mode functionality is disabled.

To indicate we are using Timer/Counter0 Compare Match as the clock source we set Clock Source Select, USICS to 1.

  USICR = 1<<USIOIE | 0<<USIWM0 | 1<<USICS0;

Finally we start the USI using the USI Status Register, USISR.

The bottom four bits hold the USI Counter Value (0 to 15) and we want the overflow interrupt to be called after 8 bits have been read. The USI will still shift data into the USI Data Register on the Timer/Counter0 Compare Match trigger that results in an overflow, so we preset USICNT to 8.

We write 1 to the USI Overflow Interrupt Flag, USIOIF,  to clear the flag and ensure the interrupt doesn’t trigger immediately.

  USISR = 1<<USIOIF | 8;
}

Reversing a byte

The bits are going to be in the USI Data Register in reverse order, so we are going to need some code to reverse the bits. Needing to reverse a byte is a common problem so people have spent time producing the most efficient code to do this, so rather than write our own, we just borrow a common approach:

uint8_t ReverseByte (uint8_t x) {
    x = ((x >> 1) & 0x55) | ((x << 1) & 0xaa);
    x = ((x >> 2) & 0x33) | ((x << 2) & 0xcc);
    x = ((x >> 4) & 0x0f) | ((x << 4) & 0xf0);
    return x;
}

Reading the result

When USI has sampled 8 bits the USI counter will overflow and the USI Overflow interrupt USI_OVF will be called. The first thing we do is grab the received byte from the USI Data Register, USIDR and disable the USI.

ISR (USI_OVF_vect) {
  uint8_t usiByte = USIDR;
  USICR  =  0;                    // Disable USI

Then you will need to do something with the result, as we are still in an interrupt you will probably want to store the result in a volatile variable and then read and act on it in your main loop. Below I just call serialReceived function with the received data.

  serialReceived(ReverseByte(temp));

Now we are done we clear the Pin Change Interrupt Flag, by writing 1 to PCIF in the General Interrupt Flag register, GIFR, so the interrupt doesn’t trigger immediately. And we re-enable pin change interrupts so we are ready to receive the next byte by setting the Pin Change Interrupt Enable bit in the General Interrupt Mask Register, GIMSK.

  GIFR = 1<<PCIF;                 // Clear pin change interrupt flag
  GIMSK |= 1<<PCIE;               // Enable pin change interrupts again
}

We are actually in the middle of the last bit and if this is zero then a pin change interrupt will be called at the beginning of the stop bit which is high. However we only call onSerialChanged() if the state of the pin is low, so it will be ignored.

A one byte buffer

A really simple implementation of serialReceived() is to store a single byte, which is read by the main loop. If we receive a new byte before the main loop has read the last one then we just overwrite the value. A simple implementation like this is might be fine if we were only using the serial input to set the brightness of an LED say.

volatile bool serialDataReady = false;
volatile uint8_t serialInput;

void serialReceived(uint8_t data)
{
    serialDataReady = true;
    serialInput = data;
}

bool readSerialData(uint8_t* pData)
{
    if (serialDataReady)
    {
        *pData =serialInput;
        serialDataReady = false;
        return true;
    }
    return false;
}

The main loop calls readSerialData() to whenever it is ready to receive data. If the return value is true then new data is available in the location passed in the pData argument. For example:

unsigned char serialInput;
if (readSerialData(&serialInput))
{
  ledUpdate(serialInput);
}

Get the example code

I have uploaded an Arduino sketch to GitHub at:

https://github.com/MarkOsborne/becomingmaker/tree/master/USISerial

This example sets the PWM duty load of PB4 to the value of the byte read from the UART input SDA. Connect your favorite serial devices Tx pin to SDA and an LED (plus current limiting resistor) to PB4 and you can set the brightness of the LED by sending bytes of serial.

USI Serial Send

If you want to transmit serial data from an ATtiny using the USI then take a look at my blog post USI Serial UART Send on ATtiny.

Sources of inspiration:

Thanks to @Atmel for publishing app note AVR307 and the source code at www.atmel.com/images/AVR307.zip.

Thanks to @technoblogy for the post Simple ATtiny USI UART which inspired me to write my own based on AVR307.

And of course the ATTiny24/45/85 Datasheet is an essential resource.

Tuning ATtiny internal oscillator

square wavesThe internal oscillator of an ATtiny can be inaccurate and might require tuning.

This post discusses how to do that if you have access to an oscilloscope or a frequency counter.

An accurate clock speed is important if you are doing timing critical operations such as serial UART communication.

The internal oscillator

ATtiny microprocessors can use an internal RC oscillator or an external crystal oscillator. External crystal oscillators are more accurate, but require two pins. For these low pin count devices it can be beneficial to use the internal oscillator.

Atmel claim the internal oscillator is factory calibrated to +/-10% at 25 degrees centigrade and three volts. In my experience they are actually calibrated to +/- 1%.

Higher temperatures will increase the clock rate and higher voltages will decrease the clock rate. My ATtiny85 microcontrollers run about 1.1% slower at 5V.

For serial UART you’ll probably need to be within 5% (less than half a bit width drift over one start and 8 data bits), so even a factory calibrated ATtiny running at 9600 baud will work fine even at 5V. However there are times when you might want the internal oscillator more finely tuned.

The oscillator calibration register, OSCCAL

Tuning of the internal oscillator is controlled by the oscillator calibration register, OSCCAL. When the microcontroller starts the factory calibration value is automatically loaded into the OSCCAL register. You cannot modify the factory calibration value, but your program can change the OSCCAL register at runtime.

So tuning the oscillator is as simple as adjusting OSCCAL at the beginning of your program. For example I added the following to my code for an ATtiny85 running at 5V.

OSCCAL += 3;

Using a delta adjustment of the factory calibrated value can be better than baking in the actual OSCCAL value if you don’t want to tune each chip individually.

For example I know that Atmel did a good job calibrating the oscillator at 3V, but I will be running at 5V so the oscillator will run slightly slower at that voltage. If adding 3 to OSCCAL works for one chip, it will probably work reasonably well for every chip.

Using EEPROM

For most hobbyists it is sufficient to add an OSCCAL adjustment to our code. Even if you have to change that line of code from chip to chip, you are only working with a handful of chips.

However this would be impractical in a production environment where we could be working with hundreds or thousands of chips.

For a production run, we would want to write the value into EEPROM during calibration and copy it from EEPROM to the OSCCAL register at the beginning of the program.

In Arduino we can use the EEPROM library:

#include <EEPROM.h>
OSCCAL = EEPROM.read(0);

In avr-libc we can use the avr/eeprom.h routines:

#include <avr/eeprom.h>
OSCCAL = eeprom_read_byte((uint8_t*)0x00);

Measuring oscillator accuracy

XMEGA XprotolabThere are approaches that involve comparing the clock speed to an external clock source, but if you have access to an oscilloscope or a frequency counter then its simpler to just measure the clock speed directly.

I have a tiny 1″ $49 Gabotronic XMEGA Xprotolab oscilloscope that does the job nicely.

So then we just need to generate a specific frequency on one of the pins, measure it, adjust OSCCAL up or down accordingly and try again.

Using the CKOUT fuse

If you are comfortable setting fuses, then the simplest approach is to set the CKOUT fuse (bit 6 of the LOW fuse register) which will send the clock signal to PB4. Then you can use your oscilloscope or frequency counter to see how close you are to the selected frequency.

I use Atmel Studio and an Atmel-ICE Programmer/Debugger to set this fuse.

Atmel Stdion CKOUT fuse

WARNING: You can “brick” your ATtiny by setting the fuses incorrectly, for example by selecting an external crystal when you don’t have one connected. In the worst case you could make it impossible to reprogram your ATtiny without a special high voltage programmer.

A safer approach

If you don’t want to mess with fuses, then an alternative is to load a small program that oscillates a pin at a measureable frequency. I like to use 10Khz as it’s a nice round number and fast enough for calibration.

We can do this with a PWM output or with a Timer/Counter compare match. The PWM approach is slightly less code, but I am going to use the Timer/Counter approach as I think the code is slightly easier to understand.

The following program uses Timer/Counter0 Comparator A to generate a 10KHz 50% load on pin PB4 for an ATtiny25/45/85. It generates 10KHz whether the ATtiny is running directly off the internal oscillator at 8Mhz or is running at 1MHz because the CKDIV8 fuse is set.

Using Arduino:

// Timer/Counter0 Compare Match A interrupt handler 
ISR (TIMER0_COMPA_vect) {
   PORTB ^= 1 << PINB4;        // Invert pin PB4
}
 
void setup() {
    OSCCAL += 3;                // User calibration
    pinMode(4,OUTPUT);          // Set PB4 to output
    TCNT0 = 0;                  // Count up from 0
    TCCR0A = 2 << WGM00;        // CTC mode
    if (CLKPR == 3)             // If clock set to 1MHz
        TCCR0B = (1<<CS00);     // Set prescaler to /1 (1uS at 1Mhz)
    else                        // Otherwise clock set to 8MHz
        TCCR0B = (2<<CS00);     // Set prescaler to /8 (1uS at 8Mhz)
    GTCCR |= 1 << PSR0;         // Reset prescaler
    OCR0A = 49;                 // 49 + 1 = 50 microseconds (10KHz)
    TIFR = 1 << OCF0A;          // Clear output compare interrupt flag
    TIMSK |= 1 << OCIE0A;       // Enable output compare interrupt
}
 
void loop() {}

Using avr-libc:

 #include <avr/interrupt.h>
 
 // Timer/Counter0 Comparator A interrupt vector
 ISR (TIMER0_COMPA_vect) {
     PORTB ^= 1<< PINB4;         // Invert pin PB4
 }
 
 int main(void)
 {
     OSCCAL += 3;                // User calibration
     DDRB = 1 << PINB4;          // Set PB4 to output
     TCNT0 = 0;                  // Count up from 0
     TCCR0A = 2<<WGM00;          // CTC mode
     if (CLKPR == 3)             // If clock set to 1MHz
         TCCR0B = (1<<CS00);     // Set prescaler to /1 (1uS at 1Mhz)
     else                        // Otherwise clock set to 8MHz
         TCCR0B = (2<<CS00);     // Set prescaler to /8 (1uS at 8Mhz)
     GTCCR |= 1 << PSR0;         // Reset prescaler
     OCR0A = 49;                 // 49 + 1 = 50 microseconds (10KHz)
     TIFR = 1 << OCF0A;          // Clear output compare interrupt flag
     TIMSK |= 1<<OCIE0A;         // Enable output compare interrupt
     sei();                      // Enable global interrupts
     
     while (1) {}
}

 

Borrowing an Arduino Timer

TimerIf you want to use a microcontroller timer for your own purposes and you are using Arduino you might find that you need to borrow a timer from the Arduino core and then give it back.

The ATtiny85 (and lower memory ATtiny45, ATtiny25 variants) only have two timers and one is used by Arduino to keep track of time. This post explains how you can borrow that timer from Arduino and give it back.

Microcontroller timer counters

Microcontrollers contain a fixed number of timer-counter modules which can be used for various tasks such as driving a PWM output on a pin, driving a Universal Serial Interface (USI), or calling an interrupt to keep track of time passing. The ATTiny85 has two such timers Timer/Counter0 and Timer/Counter1.

The ATtiny85 Timer/Count0 has two Timer Counter Control Registers called TCCR0A and TCCR0B. These registers control the clock source, the prescaler, the output compare units and the PWM waveform generator.

The counter value for Timer/Counter0 is stored in the Timer Counter Register TCNT0.

Timer counter interrupts

The ATtiny Arduino core is using Timer/Counter0 and the Timer/Counter0 Overflow interrupt, TIMER0_OVF, to keep track of time for millis() and micros(). You can verify this by looking at the wiring.c source code in your Arduino core.

Timer/Counter0 can trigger two other interrupts, Compare Match A and Compare Match B (TIMER0_COMPA and TIMER0_COMPB) which are still available to use for your purposes. These trigger when Timer/Counter0 hits the value specified in Output Compare Registers OCR0A or OCR0B.

You can avoid triggering the TIMER0_OVF interrupt, used by Arduino, from being inadvertently called by disabling the overflow interrupt vector.

TIMSK &= ~(1<<TOIE0);

And enabling it when you are finished.

TIMSK |= 1<<TOIE0;

Or by using a Compare Match and never letting Timer/Counter0 overflow.

When we borrow Timer/Count0 for a little while, say to do accurate timing for a serial or IR input, we will probably need to change the timer counter control registers TCCR0A and TCCR0B.

So we can save these values along with the current counter value, TCNT0, before we borrow Timer/Counter0:

// Save Timer/Counter0 values
oldTCCR0B = TCCR0B;
oldTCCR0A = TCCR0A;
oldTCNT0 = TCNT0;

And restore them when we are finished:

//Restore old Timer/Counter0 values
TCCR0A = oldTCCR0A;
TCCR0B = oldTCCR0B;
TCNT0 = oldTCNT0;

Lost Time

This simple approach is perfectly acceptable, but there will be a small amount of time lost. For example to receive a byte of serial at 9600 baud takes approximately one millisecond, so every time we borrowed Timer/Counter0 to receive a byte, Arduino millis() would lose a millisecond.

If you were using millis() for something like button debouncing, where you want to ensure a button stays down for 50 milliseconds before registering a click, then losing a millisecond here and there isn’t going to make much difference to your application.

However if you really want Arduino millis() and micros() to remain accurate then we will need to fix up Arduino’s internal record of time before returning control to Arduino.

Arduino millis and micros

Looking at wiring.c there are two variables used to record the passage of time that we can gain access to using extern.

extern volatile unsigned long timer0_millis;
extern volatile unsigned long timer0_overflow_count;

The first variable, timer0_millis is the number that millis() returns, it is incremented by MILLIS_INC every time Timer/Counter0 overflows.

MILLIS_INC is defined in wiring.c as follows, you will need to redefine these in your code.

#define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(64 * 256))
#define MILLIS_INC (MICROSECONDS_PER_TIMER0_OVERFLOW / 1000)

The second variable, timer0_overflow_count, is used in combination with the Timer/Counter0 counter value, TCNT0, to calculate the value returned by micros(), which we can summarize as:

((timer0_overflow_count<<8) + TCNT0) * (64 / clockCyclesPerMicrosecond())

Here timer0_overflow_count*256 + TCNT0 gives us the recorded number of Timer/Counter0 increments.

The Arduino core sets the prescaler to 64 which causes Timer/Counter0 to increment once every 64 clock cycles, so this is multiplied by 64 to get the number of clock cycles and divided by clockCyclesPerMicrosecond() to get the number of microseconds.

Recovering lost time

To adjust the Arduino time we need to adjust Arduino’s record of the number of Timer/Counter increments represented by timer0_overflow_count*256 + TCNT0. We need to increment this by the number of microseconds borrowed, delta, multiplied by clockCyclesPerMicrosecond(), to get the number of clock cycles, divided by 64, to get the number of timer increments.

unsigned int newCounter = oldTCNT0 +
    delta / (64 / clockCyclesPerMicrosecond() );
timer0_overflow_count += (newCounter>>8);
TCNT0 = (byte)newCounter;

And for each time that Timer/Counter0 would have overflowed, we need to increment timer0_millis by MILLIS_INC.

unsigned long m = timer0_millis;
m += MILLIS_INC * (newCounter>>8);
timer0_millis = m;

This approach will allow you to fix up Ardunio time to the nearest Timer/Counter0 tick which is 8 microseconds for an ATTiny85 running at 8Mhz.

Arduino PWM MOSFET Gate Resistor

N-channel_mosfet

If you are driving a logic level MOSFET directly from an Arduino, or another Atmel AVR such as an ATTiny85, you may have wondered what value resistor should be placed between the output pin and the MOSFET Gate.

In the tutorials referenced by Adafruit and Sparkfun they connect an Arduino output pin directly to the MOSFET gate. But others insist that you need a current limiting resistor to protect the microcontroller output pin.

Voltage controlled device

A MOSFET is normally thought of as a voltage controlled device, sufficient voltage at the gate opens the MOSFET and allows a large current to flow through the drain/source. In this model very little current is required at the gate. So with this simplistic model of MOSFET operation a current limiting resistor is not required.

Charging the gate

However during switching a MOSFET behaves more like a capacitor that needs to be charged in order to open and discharged in order to close. The datasheet for a STP16NF06L 16A 60V logic level  MOSFET shows a 345nF input capacitance and a 7.3 nC total gate charge. Coulombs are a measure of capacity, current time in units of amp seconds. The datasheet quotes a 47nS on/rise time with a 4.7 ohm gate resistor, so it took an average of 7.3nC/47nS = 155mA average current to charge the gate – that’s far more than the 40mA maximum current for an Atmel AVR output pin. So with this in mind it’s important to protect the microcontroller with a current limiting resistor.

Charging speed

A MOSFET is an efficient switch when completely on or off,  but it is inefficient when switching and dissipates those losses as heat. The more resistance we put between the microcontroller output pin and the MOSFET gate, the more slowly the gate will charge and the hotter the MOSFET will run. This becomes particularly important if you are switching the MOSFET on and off quickly with a PWM output.

PWM stands for Pulse Width Modulation and is a method of controlling the average voltage output of a microcontroller pin by turning the output on (5V) and off (0V) very quickly. The duty load on an Arduino PWM pin can be set with analogWrite and ranges from zero, always off, to 255, always on. Set to 63 the pin would be on a quarter of the time so the average voltage would be 5V/4 = 1.25V.

LEDs have very fast switching times, approximately 20 nanoseconds for white LEDs, so when a PWM output is used to control the brightness of a LED the duty load determines what percentage of time the LED is turned on. As long the PWM frequency is greater than 200Hz the human eye cannot detect any flicker.

Microcontroller source resistance

If you look at an Atmel datasheet you will find a graph showing IO PIN OUPUT VOLTAGE vs. SOURCE CURRENT.

At 25 Celsius this this shows a nice straight line from 5V at 0mA to 4.5V at 20mA.

IOPinOutputVoltage

What this tells us is that the output pins have an internal resistance within the microcontroller,  (5V – 4.5V)/20 mA, which is 25 ohms. So the maximum theoretical current we can attempt to source with the microcontroller running at 5V is 5V/25ohms which is 200mA. This is considerably higher than the 40mA absolute maximum in the data sheet.

To get down to 40mA we need to add a 100 ohm resistor, 5V/125ohms = 40mA. To get down to the 20mA level, recommended by Arduino, would require 225 ohms, 5V/250ohms = 20mA, the nearest standard resistor value of 220 ohms is close enough.

A real world test

To see what really happens I hooked an ATTiny85 8kHz PWM output pin to STP16NF06L logic level MOSFET through a 100ohm resistor. I used an oscilloscope to measure the voltage with respect to ground on both sides of the resistor.

An oscilloscope can only measure voltage directly, but by measuring the voltage drop across the resistor we can calculate the current flowing. I used the math function of the oscilloscope to display the difference between the two signals.

scope_diff

This showed a maximum 2.5V voltage drop across the resistor during turn on and a maximum 3V voltage drop during turn off. So the current flowing was 2.5V/100ohms = 25mA during turn on and 3V/100ohms = 30mA during turn off, both under the 40mA absolute maximum. It’s a good idea to drive components below their absolute maximum ratings, so it’s nice to see that we are below 40mA.

So in summary a 100 ohm resistor between an Arduino or similar Atmel AVR output pin and a logic level MOSFET looks like a good balance between switching speed and protecting the output pin from damage.

3D Printed Enclosure

WP_20151011_003

I designed an enclosure for my LED lighting controller.

It houses the circuit board, has two buttons and cutouts for the wires and IR receiver.

I printed it in white PLA and installed it with a small strip of servo tape.

3D Design Software

I have tried a number of 3D design applications from SketchUp to Solidworks. For me the most important feature of the software is that it be parametric. This allows you to make changes to the dimensions of your design and have the 3D model update without having to recreate the design from scratch.

Professional packages like Solidworks provide this capability but a license will set you back around $5,000. You can get a student license for Solidworks, but they don’t provide a Maker license.

Another interesting option is Autodesk Fusion 360 which is also parametric but is free for students, enthusiasts, hobbyists, and startups.

I am a software engineer by trade, so I normally end up using OpenSCAD. Which instead of having a rich UI to manipulate 3D objects, allows you to write code which renders a 3D model. Because everything is in code, you can parameterize any dimension or design attribute you want with variables in the code.

Open SCAD

OpenSCAD has a set of primitive shape commands like cube and cylinder which can be moved around using commands like translate and rotate and combined using commands like union, difference and intersection.

Enclosure

My enclosure is almost all cubes and cylinders except for the beveled edge. The triangular buttons are actually cylinders with three sides, the $fn variable allows you to define how many fragments a circle short be drawn with. Curved surfaces in 3D models are rendered as mesh of triangles.

The bevel was made using the polyhedron command that allows you to define a 3D object as a mesh of triangles.

Enclosure Design

The first thing I did was get the exact dimensions of the board and enter these as variables into my OpenSCAD design. I got the board, mounting hole and button dimensions from my Eagle board design. I looked up the board thickness on the OshPark website and the button height in the datasheet.

Then I used these dimensions to create a simple model of my board with the mounting holes and buttons. I didn’t render the board in the final design, but it was useful to check my design against.

The enclosure consists of a base and a lid, the lid has a lip which slips into the base and provides a simple friction fit. There is a variable that controls the gap between the lid lip and the box.

Dimensional tolerance of 3D printed parts is never perfect, so some prototype printing and adjustment is required. I was printing on a Makerbot Replicator 2 at 200 micron resolution and a 200 micron gap in the design worked well here. The bumpy nature of the extruded print and the slight flexibility of the PLA makes for a very nice friction fit.

The board is secured on posts in the base with pegs that go through the mounting holes and I also have posts coming down from the lid to hold the board down onto the pegs.

The buttons on the circuit board are pushed by square plungers that sit in a guide. Here a greater gap of 400 micros is required so the plungers can move freely in the guide. The triangular top fits through a matching hole in the lid and is smaller than the peg so the peg cannot escape through the hole.

Thingiverse

Thingiverse is a popular website hosted by Makerbot for sharing 3D designs that can be downloaded and printed on 3D printers.

I have shared my LED light controller enclosure design files on  thingiverse. The STL files are rendered 3D meshes exported from OpenSCAD and ready for printing. The scad file is the OpenSCAD design file.

PCB populated and tested

Populated PCBI found some time last week to populate my OshPark board and test it. You can see it here connected to 12V power (bottom) and a 12V LED strip load (top).

Soldering the through the hole components didn’t take too long. I didn’t have any problem with the connections where I had omitted thermal relief, but they did take a little longer to heat up.

 

Thermal relief

If a component is connected directly to a large area of copper on a PCB such as a ground plane, the copper will conduct the heat away from the soldering iron making it more difficult to get everything hot enough to solder.

thermalreliefA thermal relief reduces this effect by adding short traces between the connection point and the copper area as shown in this illustration from Eagle.

I omitted the thermal relief on the 12V power connections to ensure the traces could carry plenty of current without heating up.

Having large traces and direct ground plane connections for the high current part of the circuit is necessary as thin traces will heat up if too much current is passed through them, wider traces can carry more current.

My LED strip is only drawing 1.25A, so this might be overkill as I only need 16.5 mil of trace to carry a 1.25A load with a 10 degrees centigrade temperature rise. But it seemed like a good idea to design in some extra current carrying capacity.

The MOSFET I used can switch 16A, but in its TO-220 package it already has a thermal rise of 6.84 degrees centigrade at 1.25A without a heat sink. So I would probably want to add a heat sink to switch more than 3A.

Programming

I tuned the adjustable DC/DC buck converter (green daughter board) to supply the board with a 5V and everything looked good electrically. So I inserted the XBee and the ATTiny85 microcontroller.

I attached a SparkFun ATTiny programmer to the board using a 6 pin ISP cable and uploaded my software to the ATTiny85.

The buttons worked as expected, but I was disappointed to discover that the IR and XBee serial input were not working correctly.

Debugging

The SparkFun ATTiny programmer is a great $20 programmer, but it doesn’t provide any debugging functionality so I resorted to the age old technique of modifying the code and flashing an LED to indicate what was going on.

I took the time to fix a bug, which I already knew about, that would cause the IR input to malfunction if a serial input occurred at the same time, but things were still not working correctly. When using interrupts you have to think carefully about what would happen if an interrupt triggered at an inopportune moment.

Then I noticed that the serial input was low when it should have be high. The XBee serial out should always be high unless it is sending data to the ATTiny85. When I checked the XBee serial output was indeed high, but the signal wasn’t triggering a high on the ATTiny digital input.

In Circuit Programmers

An ATTiny85 only has eight pins, for power, programming, and input/output. Programming the ATTiny requires six pins: GND, 5V, Reset, SCK, MOSI and MISO. My circuit requires 7 pins: GND, 5V, Up Button, Down Button, Serial input, IR input, PWM output. To accommodate these eleven different functions on 8 pins requires that the programming and I/O functions share the same pins.

For the programmer to work effectively while the ATTiny is in-circuit it is important that the circuit doesn’t interfere with the programmer. For things like buttons, this isn’t a huge problem – use large value pull-up resistors and don’t press a button in the middle of programming, but for the serial input pin this is a problem as the line is being held high even when there is no serial activity.

So I put a 1K resistor between the XBee serial output and the ATTiny85 serial input so it would not interfere with use of the pin as MOSI during programming. The XBee voltage is only 3.3V, but the trigger voltage of the ATTiny85 is 2.5V (with 5V power) so it can still trigger a high digital input. Adding the resistor directly between the XBee serial output and the ATTiny85 input doesn’t cause any noticeable voltage drop, so this approach works well.

Blinking LEDs

However, the SparkFun ATTiny programmer has a yellow LED connected to this same pin via a 330 ohm resistor. This is nice as you have an LED you can blink for testing and debugging on pin 0. Pin 0 can be a digital output in addition to a serial input and MOSI.

However this feature of the programmer caused the voltage to the ATTiny serial input to be dropped too low as the 330 ohm resistor and the 1K resistor act as a voltage divider with the ATTiny serial input pin in the middle.

Voltage Divider Calculation With An LED

The addition of the LED makes the voltage divider calculation a little more interesting. The voltage divider needs to be applied to the voltage between 3.3V and the LED, which means we must subtract the voltage drop of the LED first.

The yellow LED will drop the voltage by its forward voltage. Yellow LEDs are quoted as having a forward voltage of 2.1V, but that is at their brightest – it is a useful value to calculate the value of a resistor that will not cause the LED to burn out. However, with the 1K and 330 ohm resistors between the LED and 3.3V the LED is only drawing 1 to 2 mA of current and will only drop the voltage by about 1.7V. The datasheet show a graph of forward voltage by current.

So the voltage divider needs to be applied to 3.3V – 1.7V = 1.6V. The voltage divider equation is Vout = Vin*R2/(R2+R1). Where R2 is 330 ohms and R1 is 1K. See Wikipedia for details.

Vout = 1.6 * 330/(1000+330) = 0.4 V

Then we just add back the forward voltage of the LED to get the full voltage for that point in the circuit.

1.7 + 0.4 = 2.1 V

Which is below the 2.5V trigger threshold of the ATTiny85. So the ATTiny85 reads this as a digital low.

The Fix

As far as my code was concerned the serial input was continuously setting a zero dimming level. The simple fix was to unplug the programmer, upon which everything started working perfectly!

A workaround for testing the circuit with the programmer attached is to use a strong enough pull up resistor to push the serial input over 2.5V. I chose to add a 1K pull up resister between MOSI and 5V on the programmer which will add 0.6V. This is a similar voltage divider to above, but with a 5V input rather than a 3.3V input. I attached this to the header pins on the programmer, but I don’t want to make this a permanent addition to my PCB as 1K is rather a strong pull up which would draw unnecessary quiescent current when the programmer isn’t attached.

 

The next step is to design and 3D print an enclosure.