ATTiny10 Dual Channel, Precision RC Servo Controller

This ATTiny10 contains a 16 bit counter/timer unit that is capable of generating precise RC timing signals using its Fast PWM mode.  This article shows how to create a dual channel RC servo controller based on the ATTiny10 that receives asynchronous serial command bytes sent at 9600 baud and uses these commands to program and create a pulse stream that can control one, or two RC servos.  A command to set a servo to a particular position only needs to be sent once and the controller will then continue to generate a pulse stream with that setting until a new command is received that changes it.  Separate command codes are provided for individually programming each of the two channels, which operate independently of each other.  This is an all software implementation, so nothing more than an ATTiny10 (or ATTiny9) is needed, just connect the controller into your system, like this:

Two bytes must be sent to change the setting of one of the servo channels.  Each byte sends 6 bits of a 12 bit value that defines the width of the servo pulse created by the ATTiny10.  The first byte sends the least significant six bits and the 2nd sends the most significant six bits.  The bytes must be sent in this order because the reception of the 2nd byte triggers the change to the servo setting.  In addition, the upper two bits of each byte is used as an address to indicate which byte (MS, or LS) is being sent and which servo channel (A, or B) to change.  The command bytes are coded like this:

After a RESET, the code it set to output a 1.5 ms pulse at a 60 Hz repetition rate on both channels.  This corresponds to the middle, or neutral position of most servos.  However, if you want to change this, the code that controls this is:

; Set OCR0A to 6000 Dec
    ldi r16, (6000 >> 8)
    out OCR0AH, r16
    ldi r16, (6000 & 0xFF)
    out OCR0AL, r16
; Set OCR0B to 6000 Dec
    ldi r16, (6000 >> 8)
    out OCR0BH, r16
    ldi r16, (6000 & 0xFF)
    out OCR0BL, r16

The "6000" value set in both OCR0A and OCR0B registers controls the after RESET pulse width, so you can change this value for one, or both channels to define a different start up pulse width.  The value to program is defined by this formula:

value = (pulse width in milliseconds) * 4000

Using the Controller

To better understand how to use the controller, here's a simple Arduino sketch that uses SoftwareSerial to send commands to channel A that slowly ramps a servo connected to that channel back and forth between two positions near the extreme range of that most servos can handle.  Note: you can increase the values of LOW_LIMIT, and/or decrease HIGH_LIMIT to limit the servo's travel to a smaller range.  You can also increase STEP_SIZE to make the servo move faster.

#include <SoftwareSerial.h>

 * Test Code for ATTiny10 Servo Controller
#define LOW_LIMIT  0
#define HIGH_LIMIT 4080
#define STEP_SIZE  1
SoftwareSerial mySerial(3, 2);

void setup() {

void loop() {
  for (int ii = LOW_LIMIT; ii <= HIGH_LIMIT; ii += STEP_SIZE) {
  for (int ii = HIGH_LIMIT; ii >= LOW_LIMIT; ii -= STEP_SIZE) {

void setPWMA (int val) {
  byte sl = (byte) (val & 0x3F);
  byte sh = (byte) (val >> 6);
  mySerial.write(0x00 + sl);
  mySerial.write(0x40 + sh);

When you send a position value to either channel, the internal code multiplies the 12 bit value it receives by 2 and then adds 2000 to it before using this value to program the OCR0A and OCR0B registers.  This means that the minimum pulse width you can send to a servo is 0.5 ms.  Likewise, this means the maximum pulse width you can send to a servo is 4095 * 2 + 2000, which works out to 2.55 ms.  You can use the following formula to calculate the pulse width in milliseconds (ms) for a given 12 bit value:

width (ms) = (value * 2 + 2000) / 4000

If You Want to Make One

The code to needed to program an ATTiny10 as a dual channel servo controller is available at the bottom of the page and you can use my ATTiny10 Assembly IDE and Device Programmer to program a blank ATTiny10 with this code.  However, there are a few details you need to know in order to reproduce my results.  First, the code uses the ATTiny10's internal clock to drive the timing.  Since the serial reception is done with software emulation using timing loops, it's important that the master clock that drives the timing be as accurate as possible.  ATMEL does some calibration of the internal clock oscillator, but it's only guaranteed to an accuracy of +/- 10%, which may not be good enough to reliably receive serial data at 9600 baud.  However, it's possible to tweak the clock in software and, in fact, the code attached below has done this for the particular ATTiny9 chip I used.  The code that does this is, as follows:

; Calibrate Oscillator
  ldi r16, 0x70
  out OSCCAL, r16

In effect, this code writes a "tweak" value to a special register that adjust the internal clock frequency up, or down, depending on the value written to it.  I recommend that, at first, you should comment out this code and tryi using the factory calibration, as it may work fine for you.  If it does, that's great.  If not, you'll need to figure out a proper calibration value for your ATTiny and use it in place of the 0x70 value I used.  For details on how to calibrate your ATTiny chip's oscillator, see my writeup on ATTiny10 Clock Calibration.

Note: the latest version of the IDE and Programmer software adds a way to automatically get the 8 MHz calibration value for your chip.
Wayne Holder,
May 16, 2012, 9:10 PM