www.angelfire.com/dragon/letstry
cwave04 at yahoo dot com

Synchronisation

Microcontrollers depend on electronics, and like all manmade gadgets are based on simplified assumptions about nature. Despite one's best efforts, discrepancies exist between the expected and the actual behaviour of a microcontroller. Their effects appear to be random, or at least too complex to control directly. Sometimes these may appear like manufacturing defects, and so datasheets always try to downplay these. The datasheets try to present a microcontroller as a perfect instrument, and are silent about techniques to handle potential departures from the ideal, documented behaviour. One has to find these by trial-and-error.

In my experience there are two major ways microcontrollers can behave badly during programming. Both are forms of synchronisation problem.
  1. Sometimes bits will randomly go missing during communication.
  2. Program enabling is like waking the microcontroller from its slumber. Now microcontrollers are rather sleepy creatures, and tend to doze off again whenever they get a chance. This is especially true for the less expensive ones.

General strategy

The worst feature of synchronisation problem is that it is random. So each attempt is somewhat like a coin toss: you either succeed (HEAD) or you mysteriously fail (TAIL).

But if you keep on tossing the same coin again and again, then the chance of getting at least one HEAD is large. This is the crucial idea: keep on trying until you get the first success.

Of course, this strategy won't work for programming bugs, which are not random. But this works for synchronisation problems, which are random in nature.

Solution to problem 1: knock, knock, knock...

As we have already warned you, you may not able to wake the microcontroller up with a single call because of bits going missing mysteriously. Then we just have to keep on calling. So we shall make multiple attempts to wake them up. It will help to have a little function that make multiple attempts to wake them up. does this chore for you. It sends at most 20 wake up calls, and gives up after that.

int progKick(void) {
  int nTry;
  for(nTry=0;nTry<20;nTry++) {
    if(startProg()) break;
  }
  
  if(nTry<20) {
    printf("Programming enabled after %d attempt(s).\n",nTry);
    return 1;
  }

  printf("Programming couldn't be enabled even after %d tries!\n", 
         nTry);
  return 0;
}

Solution to problem 2: reset whenever you can

Microcontrollers are rather sleepy creatures. They tend to doze off again whenever they get a chance. This is especially true for the less expensive ones (AT89S52 will, for example, give more trouble than ATMEGA8515).

So every now and then you'll have to wake them up again. This means calling the progKick function repeatedly. There are a couple of points that you must bear in mind:
  • Every time you wake up the microcontroller it forgets some of things you had told it earlier. So you must repeat these again. This is true even if the microcontroller was actually awake when you sent it the "wake up" signal. So you must be careful about timing your "wake up" calls. It would help to consider the entire activity of programing as consisting of critical chunks that must not be interrupted by any "wake up" call. Then it is always a good idea to issue a "wake up" call between chunks.
  • Sometimes it is good to be able to detect if the microcontroller is awake or asleep. Though the datasheet does not mention any way to do this, there seems to be one dirty trick that works. Look at the lock bits. These become 1's whenever the microcontroller falls asleep.
We shall now ilustrate how to use these ideas.

Erasing, writing and reading

To program a microcontroller you have to first erase its memory, then write your program bytes into it, and finally you should read back what you've written (to verify that everything is OK). Page 20 of the datahseet gives you the bit sequencees to achieve these. We present three simple functions that implement these.

Erasing


void chipErase(void) {
  statMsg("Chiperase starts"); indent++;
  unlock();
  statMsg("Byte 1");
  sendByte2("10101100");
  statMsg("Byte 2");
  sendByte2("10011111");
  statMsg("Byte 3");
  sendByte2("11111111");
  statMsg("Byte 4");
  sendByte2("11111111");
  indent--; statMsg("Chiperase ends");
}

If you have carefully read page 20 of the datasheet, then the above function should not be hard to fathom, except for the call to the unlock function. The manufacturers have put a little locking feature in the microcontroller to avoid accidental erasing. The unlock function opens this lock. Comparing the following code with page 20 of the datasheet should explain its working.

void unlock(void) {
  sendByte("10101100");
  sendByte("11100000");
  sendByte("11111111");
  sendByte("11111111");
}

Writing

Of course, before we can write to the microcontroller we must have something to write! Our final aim will be to write the contents of a HEX file. But for now we shall content ourselves by writing some random bytes. These bytes will be stoed in a global array called mem. This is the right moment to talk about the memory organisation inside the microcontroller. The information is given in a cryptically concise form in page 9 of the datasheet. We shall try to present it in a more detailed way.

The microcontroller program memory is of size 4K. So it can store 4*1024 = 4096 bytes. Do not confuse this memory with the RAM, which is volatile (gets garbled on power-down). The 4K memory is the FLASH memory (a kind of EPROM, if that helps). This is where the microcontroller keeps its program and all permanent data (like fonts, strings etc).

To address a single byte in a 4K = 212 byte space you need an address consisting of 12 bits. It will help to group these 12 bits into two groups of 4 and 8:

The 4 MS bits comprise what we shall call the page number. The remaining 8 bits give the address of the byte within the page. Clearly, there are 24 = 16 pages each of size 28 = 256 bytes.

The AT89S52 microcontroller allows one to write a byte both in byte-mode (where we use the full 12 bit address for each byte to be written) or in page-mode (where first we specify the page number and then send the 256 bytes in the page). The page-mode writing is faster. So we shall go for that.

int sendPage(int whichPage) {
  int i,k;
  int pageStart, result;

  statMsg("sendPage starts"); indent++;
  pageStart = whichPage;
  pageStart <<= 8;

  for(i=0,k=pageStart;i<256;i++,k++) {
    sendByte("01000000");
    sendByte1(whichPage);
    sendByte1(i);
    sendByte1(mem[k]);
  }
  Sleep(100);

  indent--;  statMsg("sendPage ends");
  return 1;
}

Reading

The following two functions should be self-explanatory.

int readByte(int whence) {
  statMsg("readByte starts"); indent++;
  if(verbose & VERBOSE_MSG) 
    printf("whence = %d\n",whence);

  statMsg("Byte 1");
  sendByte2("00100000");
  statMsg("page MSB 4");  
  sendByte1(whence>>8);
  statMsg("page LSB 8");  
  sendByte1(whence&0xFF);
  statMsg("Dummy");  
  sendByte("11111111");
  indent--;   statMsg("readByte ends");

  return inpByte;
}


void verify(void) {
  int pg, byt, bad, k;

  for(pg=0,k=0;pg<16;pg++) {
    bad = 0;
    for(byt=0;byt<256;byt++,k++) {
      startProg();
      if(mem[k] != readByte(k)) bad =1;
    }
    printf(bad? "x" : ".");
  }
}


PrevNext
© Arnab Chakraborty (2008)