An Arduino-based programmer for the AT89C2051 chip

The Atmel AT89C2051 is a low cost microcontroller in a 20-pin DIL package.  It runs MCS-51 (commonly termed ‘8051’) code.  It works from 2.7V to 6V at anything from 0 Hz up to 24 MHz.  It has 2K bytes of Flash memory to hold the program and 128 bytes of RAM.  It has 15 I/O lines, a UART, an analogue comparator and two 16-bit timer/counters.

I came across the chip as it’s often used in cheap 7-segment clock kits such as this one from BangGood (only £2.71 at the time of writing).

I wanted to reprogram the chip so I could use the kit as a stopwatch/timer instead of a regular clock.  Of course I could have bought a programmer to do the job, but reading the chip’s data sheet it seemed straightforward to do the programming with an Arduino – and I thought it would be a fun project to do that.

The chip is programmed a byte at a time by setting up each byte on 8 of the chip’s I/O lines and then pulsing some of the other I/O lines to ‘burn’ the byte to flash memory and move on to the next byte to be programmed.  You can also read the existing program out of a chip (unless a read-protect bit has been set) and there are special ways of pulsing the I/O lines to erase the whole chip and so on.

The only tricky thing is that one pin has to be raised from the nominal operating voltage of five volts up to twelve volts during programming – the challenge was working out the easiest way to do this using an Arduino.

So I decided to use an Arduino Mega 2560 for this project.  A Uno doesn’t have quite enough I/O to do the job properly, and the Mega 2560’s double row of I/O pins makes routing the connections to the chip simple as the chip can sit directly over the double-row connector.

I decided to use a charge pump (voltage multiplier) running off the Arduino’s five volts to generate the programming voltage – that seemed cleaner than needing a separate twelve volt supply.  It just uses a few diodes and capacitors and relies on the Arduino pulsing some of its I/O lines to drive the voltage multiplier.  A couple of zener diodes clip the voltage down to exactly 5V or 12V and a couple of transistors, also switched by the Arduino, select between either of those voltages or 0V to drive the pin on the chip.

I designed a PCB using the free KiCad package.  Here’s a .pdf of the circuit diagram, and here is what KiCad produces as a picture of the design.  In the picture it looks like the chip to be programmed is soldered straight into the board, but of course in reality a ZIF socket is fitted in that position so that the chip(s) you are programming can be quickly swapped.

programmer3DThat picture wrongly shows the tracks on the top of the board – I design them that way for home production as the transfer process mirror-images the tracks so that they’re correct for the back of the board.  If you fancy making one of your own, it would be quite straightforward to  do it on strip board – like I say most of the pins of the chip just connect direct to the Arduino pins that the chip sits over.  If you want to etch your own PCB, here is a .pdf of the mask.

Here are a couple of snaps of the prototype board.  You can see I didn’t bother to crop back the board edges!

 

topbackAnd this is what it looks like when docked on top of the Arduino Mega 2560.

topDockedbackDockedSo that’s about it for the hardware.  I’ll make a separate post about the Arduino sketch that does the work of programming the chip, and the PC program that talks to the Arduino to send and receive hex files.

Fast, small, and simple Arduino SPI RAM chip routines.

The 8-pin chip, 23LC1024, is a good candidate for adding extra RAM to simple Arduinos such as the Uno, Nano, Mini.  It provides 128K of RAM (the ATmega328 chip that these Arduinos use only has 2K of internal RAM).

The chip works at 5V (anything from 2.5V to 5.5V) and uses SPI so it only uses 4 pins of the Arduino to talk to it.  You can buy the chips on eBay for less than £5 each.

I wanted some fast simple routines to copy any arbitrary number of bytes between ‘normal’ RAM and the SPI RAM.  I didn’t want to link in any huge libraries so I wrote these small routines that hit the 328 chip’s SPI registers direct.

There are only three functions:

spiRam::start(); // sets up the SPI interface – call once from your setup() routine

spiRam::writeSpi(*ptr, address, length); // copies to SPI RAM

spiRam::readSpi(*ptr, address, length); // copies from SPI RAM

*ptr is an unsigned byte (uint8_t) pointer – but you can cast any other data type address to that easily – see example.

address is an unsigned long (uint32_t) as addresses in the SPI RAM range from 0 to 131071 (2^17 – 1).

length is an unsigned int (uint16_t) – but will never be bigger than 2048 as the 328 chip only has 2K of RAM.

Standard connections (you can use a different pin than D10 for the chip select – but there’s not much point unless you want to use more than one RAM chip to give more than 128K RAM – see notes in the .h file).

Chip pin Arduino pin
1 D10
2 D12
3 5V
4 Gnd
5 D11
6 D13
7 5V
8 5V

Here’s the sketch: SpiRam23LC1024.zip Unzip it inside your Arduino folder. You want to end up with the SpiRam23LC1024 folder in your Arduino folder with the SpiRam23LC1024.ino and the two other files inside.  Then it should compile and upload okay.  To use it with your own code just move the .h and .cpp files inside your folder where your .ino file is and add the line

#include “spiRam.h”

up at the top of your sketch.

Because the routines are interrupt driven your code could be doing something else while the transfers are taking place – but be careful if you’re not sure – it’s easy to start destroying or changing contents before they’ve finished being transferred if you’re careless. The routines are very fast anyhow (9 milliseconds to transfer 1024 bytes in either direction), so as standard there is a ‘busy wait loop’ until the transfers are complete so you don’t need to worry about checking from inside your code.

 

ATmega328P-PU 12MHz bootloader

It took me an embarrassingly long time to get a 328 with a 12MHz crystal working so that it would allow a normal (serial / FTDI) upload from the Arduino IDE.

There are a few older guides around but I didn’t find one that used the newer optiboot bootloader.

If you don’t want to compile the bootloader yourself, you can download the compiled optiboot_at328mega_12.hex file from this link (right-click and and Save (link) as…) just copy it to your optiboot folder before editing your “boards.txt” file as described below.

If you want to compile the hex file yourself here’s how:

Edit the Makefile in your optiboot folder. On my (Windows 7 64bit) machine using Arduino 1.0.5-r2 this folder was C:\Program Files (x86)\Arduino\hardware\arduino\bootloaders\optiboot

Add this section. I put it in after the existing atmega328 sections and before the Sanguino section  (note that the -W1, … shouldn’t be on a separate line – it’s a continuation of the LDSECTIONS line, but this blogging software seems to want to wrap it onto a new line):

atmega328_12: TARGET = atmega328
atmega328_12: MCU_TARGET = atmega328p
atmega328_12: CFLAGS += '-DLED_START_FLASHES=3' '-DBAUD_RATE=115200'
atmega328_12: AVR_FREQ = 12000000L
atmega328_12: LDSECTIONS  = -Wl,--section-start=.text=0x7e00 -Wl,--section-start=.version=0x7ffe
atmega328_12: $(PROGRAM)_atmega328_12.hex
atmega328_12: $(PROGRAM)_atmega328_12.lst

atmega328_12_isp: atmega328
atmega328_12_isp: TARGET = atmega328
atmega328_12_isp: MCU_TARGET = atmega328p
# 512 byte boot, SPIEN
atmega328_12_isp: HFUSE = DE
# Low power xtal (12MHz) 16KCK/14CK+65ms
atmega328_12_isp: LFUSE = FF
# 2.7V brownout
atmega328_12_isp: EFUSE = 05
atmega328_12_isp: isp

This is just a copy of the atmega328. section with:

  • the name changed from atmega328. to atmega328_12.
  • the f_cpu parameter changed from 16000000L to 12000000L
  • the two (PROGRAM) lines changed so that the .hex and .lst filenames also have the “_12” addition
  • the comment about the 16MHz crystal frequency changed to 12MHz

Run a command shell as administrator and navigate to the optiboot folder.  Enter the following command to compile the new 12MHz optiboot bootloader:

omake atmega328_12

Now edit your boards.txt file.  On my machine this was located at C:\Program Files (x86)\Arduino\hardware\arduino  Add this new section at the end – again this is just a copy of the ‘uno’ section with the few obvious edits for frequency and bootloader file.

##############################################################

atmega328bb12.name=ATmega328 12MHz crystal
atmega328bb12.upload.protocol=arduino
atmega328bb12.upload.maximum_size=32256
atmega328bb12.upload.speed=115200
atmega328bb12.bootloader.low_fuses=0xff
atmega328bb12.bootloader.high_fuses=0xde
atmega328bb12.bootloader.extended_fuses=0x05
atmega328bb12.bootloader.path=optiboot
atmega328bb12.bootloader.file=optiboot_atmega328_12.hex
atmega328bb12.bootloader.unlock_bits=0x3F
atmega328bb12.bootloader.lock_bits=0x0F
atmega328bb12.build.mcu=atmega328p
atmega328bb12.build.f_cpu=12000000L
atmega328bb12.build.core=arduino
atmega328bb12.build.variant=standard

Now launch Arduino, select “ATmega328 12MHz crystal” as your board and “Burn Bootloader” from the Tools menu.

That’s it, and you should be up and running with the normal hardware serial port working correctly with the ‘Serial’ commands and serial uploading from the Arduino IDE also working correctly.  I’ve seen comments that not all of the timing functions (microsecond delays etc.) work accurately when using a 12MHz crystal and the standard Arduino libraries.  I’ve not investigated that yet; and so far I’ve not tried using software serial with a 12MHz crystal – that also may require some tweaking…

 

 

Decoding 6 servo channel inputs with an Arduino UNO

Standard radio control receivers drive their connected servos by outputting a train of pulses.  The pulses usually repeat at a 50 Hz rate but the pulse width is the important thing – the standard is a 1.5 ms pulse width for a servo at its center position varying from about 1.0 ms up to 2.0 ms for servo’s nominal travel range. The pulses are positive going, usually about 5V in amplitude.

Searching on the web finds lots of examples of code to read the pulse widths for several servo channels, but for my application the Arduino is looking after several sensors besides the servo signals and also running software serial ports.  The examples I found all gave too much jitter and inaccuracy.

I wired the six channels to the Arduino digital pins 2 to 7.  Pins 0 and 1 are used by the UART (serial communication to PC or other device) so it’s best to avoid those.  Conveniently this means that all six channels are contained on a single Arduino input port, (Port D) so this makes the code to capture the pulse widths very clean.

We enable an interrupt that occurs when any of the 6 pins change state. To do this we set one bit for each of the 6 channels in the PCMSK register.  Then to allow changing input states on Port D to generate interrupts there is just one bit in the PCICR register to set:

PCMSK2 |= 0xFC;
PCICR |= 0x04;

The interrupt handler, declared as ISR(PCINT2_vect) checks each of the six pins. If a pin has changed state since the previous interrupt, we do one of two things:  If the pin has gone high we just remember the current time; if the pin has gone low we store the pulse width by subtracting the remembered time from the current one.  The Arduino has a function that returns microseconds (with a resolution of 4 microseconds on the UNO) convenient for this task.  The microsecond timer wraps around back to zero roughly every 70 minutes but this doesn’t cause any problems – by doing the subtractions using unsigned long integers the pulse widths are still correct even when a ‘wrap around’ occurs.  Here’s the complete program including interrupt handler and simple test output that just prints the current pulse widths to the serial port (PC).

volatile uint8_t prev; // remembers state of input bits from previous interrupt
volatile uint32_t risingEdge[6]; // time of last rising edge for each channel
volatile uint32_t uSec[6]; // the latest measured pulse width for each channel

ISR(PCINT2_vect) { // one or more of pins 2~7 have changed state
  uint32_t now = micros();
  uint8_t curr = PIND; // current state of the 6 input bits
  uint8_t changed = curr ^ prev;
  int channel = 0;
  for (uint8_t mask = 0x04; mask; mask <<= 1) {
    if (changed & mask) { // this pin has changed state
      if (curr & mask) { // +ve edge so remember time
        risingEdge[channel] = now;
      }
      else { // -ve edge so store pulse width
        uSec[channel] = now - risingEdge[channel];
      }
    }
    channel++;
  }
  prev = curr;
}

void setup() {
  Serial.begin(9600);

  for (int pin = 2; pin <= 7; pin++) { // enable pins 2 to 7 as our 6 input bits
    pinMode(pin, INPUT);
  }

  PCMSK2 |= 0xFC; // set the mask to allow those 6 pins to generate interrupts
  PCICR |= 0x04;  // enable interupt for port D
}

void loop() {
  Serial.flush();
  for (int channel = 0; channel < 6; channel++) {
    Serial.print(uSec[channel]);
    Serial.print("\t");
  }
  Serial.println();
}