h1

Xmega fractional baud-rate source code

2010/08/18

Earlier I posted a spreadsheet I created that calculated the BSEL and BSCALE for the Xmega’s fractional baud-rate generator.  This works well to determine what the potential is for getting your chip to run a viable baud-rate for a given clock, but isn’t so useful when you actually want to write a configurable piece of code.

Since then I’ve developed two methods for generating the appropriate register settings for a given baud rate.  The first method was designed around the original constraints I had, which were that the CPU frequency and baud-rate were set statically in the source code, and never changed or dealt with programmatically.  As such, it’s a set of macros that determine the best available BSEL and BSCALE:

#ifndef __XMEGA_BAUD_H__
#define __XMEGA_BAUD_H__

#define _BAUD_BSEL_FROM_BAUDSCALE(f_cpu,baud,bscale) (                \
((bscale) < 0) ?                                                      \
  (int)((((float)(f_cpu)/(8*(float)(baud)))-1)*(1<<-(bscale)))        \
: (int)((float)(f_cpu)/((1<<(bscale))*8*(float)(baud)))-1 )

#define _BSCALE(f_cpu,baud) (                                         \
(_BAUD_BSEL_FROM_BAUDSCALE(f_cpu,baud,-7) < 4096) ? -7 :              \
(_BAUD_BSEL_FROM_BAUDSCALE(f_cpu,baud,-6) < 4096) ? -6 :              \
(_BAUD_BSEL_FROM_BAUDSCALE(f_cpu,baud,-5) < 4096) ? -5 :              \
(_BAUD_BSEL_FROM_BAUDSCALE(f_cpu,baud,-4) < 4096) ? -4 :              \
(_BAUD_BSEL_FROM_BAUDSCALE(f_cpu,baud,-3) < 4096) ? -3 :              \
(_BAUD_BSEL_FROM_BAUDSCALE(f_cpu,baud,-2) < 4096) ? -2 :              \
(_BAUD_BSEL_FROM_BAUDSCALE(f_cpu,baud,-1) < 4096) ? -1 :              \
(_BAUD_BSEL_FROM_BAUDSCALE(f_cpu,baud,0) < 4096) ? 0 :                \
(_BAUD_BSEL_FROM_BAUDSCALE(f_cpu,baud,1) < 4096) ? 1 :                \
(_BAUD_BSEL_FROM_BAUDSCALE(f_cpu,baud,2) < 4096) ? 2 :                \
(_BAUD_BSEL_FROM_BAUDSCALE(f_cpu,baud,3) < 4096) ? 3 :                \
(_BAUD_BSEL_FROM_BAUDSCALE(f_cpu,baud,4) < 4096) ? 4 :                \
(_BAUD_BSEL_FROM_BAUDSCALE(f_cpu,baud,5) < 4096) ? 5 :                \
(_BAUD_BSEL_FROM_BAUDSCALE(f_cpu,baud,6) < 4096) ? 6 :                \
7 )

#define BSEL(f_cpu,baud)                                              \
  _BAUD_BSEL_FROM_BAUDSCALE(f_cpu,baud,_BSCALE(f_cpu,baud))

#define BSCALE(f_cpu,baud) ((_BSCALE(f_cpu,baud)<0) ? (16+_BSCALE(f_cpu,baud)) : _BSCALE(f_cpu,baud))

#endif /* __XMEGA_BAUD_H__ */

(beware the line continuations!) Basically, the BSCALE macro steps through the +-7 range hunting for the highest legal (12-bit) BSEL value, and the BSEL macro uses that to generate the right divider.  A typical usage would be something like this:

#define F_CPU 32000000
#define BAUDRATE 115200

USARTC0.BAUDCTRLA = BSEL(F_CPU,BAUDRATE) & 0xff;
USARTC0.BAUDCTRLB = (BSCALE(F_CPU,BAUDRATE) << USART_BSCALE0_bp) | (BSEL(F_CPU,BAUDRATE) >> 8);

More recently I’ve been developing a more “object oriented” set of routines that allow me to stack one thing on top of another (more about that later).  As a result, I needed to develop a form of the above code that would work at runtime.  As you can see from the first macro in the above code, a naive approach would bring a microcontroller to its knees in a matter of seconds (as in: it could take entire seconds to calculate…).  In order to solve this problem I took a look at the problem from a different perspective, and ended up with the following code:

#define F_CPU 32000000

uint8_t xmega_usart_setspeed (USART_t *usart, uint32_t baud) {
  uint32_t div1k;
  uint8_t bscale = 0;
  uint16_t bsel;

  if (baud > (F_CPU/16)) return 0;

  div1k = ((F_CPU*128) / baud) - 1024;
  while ((div1k < 2096640) && (bscale < 7)) {
    bscale++;
    div1k <<= 1;
  }

  bsel = div1k >> 10;

  usart->BAUDCTRLA = bsel&0xff;
  usart->BAUDCTRLB = (bsel>>8) | ((16-bscale) << 4);

  return 1;
}

The above code will result in the best available baud rate, calculated with 0.1% precision (but does not guarantee 0.1% baud-rate accuracy), using only a single 32-bit divide.  My current headache prevents me from properly explaining how it works, but the clever reader should be able to puzzle it out pretty quickly.  I’ll try to replace this excuse with an actual explanation at some point in the future.  If I haven’t yet, write a comment reminding me….

If you’re running on a system with a variable system clock (e.g. stepping the clock up for a burst of performance and back down for a long sleep), you could easily modify the function to take the F_CPU as a function parameter rather than a #define.  Replacing the (F_CPU/16) with (F_CPU>>4) and (F_CPU*128) with (F_CPU<<7) might be necessary to hint the compiler, but everything else should work the same.  You could then precalculate and store the BAUDCTRL values for each clock speed, and swap them in as needed, or if your clock is more variable than that, just run the calculation each time.

I haven’t profiled the runtime of the code yet, but I suspect it’s well under 1000 cycles, dominated by the 32-bit divide.

Advertisements

4 comments

  1. The algorithm assumes that CLK2X (Double Speed Operation) is set. Are there any guidelines when to use double speed and when not? The algorithm could be improved to make that decision or at least check if the CLK2X is set or not.


    • I’ve never found anything stating that CLK2X should or shouldn’t be used in a given circumstance. However, from the description of how the async serial sampler works in the Xmega datasheet, I would assume that CLK2X is less forgiving of both absolute bitrate differences (which the fractional generator can solve unless one side’s source clock isn’t up to snuff) and jitter.

      I can look at updating the algo to support CLK2X, should just be a simple switchout of a divider constant somewhere in there.


  2. Thanks dude! I’ve been trying to calculate the baud rate on the fly on my xmega, but allways made some math errors.
    Thanks! :D


  3. Works like a dream.

    Don’t forget to set USART_CLK2X_bm on CTRLB!



Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: