serial-arm.c: don't calculate serial line parameters at runtime.

This is a pretty complex and, as system clock and baudrate are
known at compile time and never changed at runtime, unneccessary.
Replacing this calculation with fixed values makes the binary
a whopping 564 bytes smaller.

However, how to get these values? Well, we do kind of an
easter-egg. If parameters arenot known, we calculate them at
runtime anyways, and also report them to the user. So she can
insert them into the code and after doing so, whoops, serial
fast and binary small :-)

With known parameters:

    SIZES          ARM...     lpc1114
    FLASH  :  1092 bytes           4%
    RAM    :   132 bytes           4%
    EEPROM :     0 bytes           0%

Without (1428 bytes more):

    SIZES          ARM...     lpc1114
    FLASH  :  2520 bytes           8%
    RAM    :   132 bytes           4%
    EEPROM :     0 bytes           0%
This commit is contained in:
Markus Hitter 2015-07-27 14:19:58 +02:00
parent fc124c14d4
commit e8299deb86
1 changed files with 127 additions and 57 deletions

View File

@ -16,6 +16,7 @@
#include "arduino.h"
#include "mbed-LPC11xx.h"
#include "delay.h"
#include "sersendf.h"
#ifdef XONXOFF
#error XON/XOFF protocol not yet implemented for ARM. \
@ -44,19 +45,58 @@ void serial_init() {
| 0 << 1 // Tx Fifo empty irq enable.
| 0 << 2; // Rx Line Status irq enable.
// Baud rate calculation - - - TO BE REFINED, we can calculate all this
// in the preprocessor or even hardcode it, because baud rate never changes.
{
uint32_t baudrate = BAUD;
LPC_SYSCON->UARTCLKDIV = 0x1;
uint32_t PCLK = SystemCoreClock;
// First we check to see if the basic divide with no DivAddVal/MulVal
// ratio gives us an integer result. If it does, we set DivAddVal = 0,
// MulVal = 1. Otherwise, we search the valid ratio value range to find
// the closest match. This could be more elegant, using search methods
// and/or lookup tables, but the brute force method is not that much
// slower, and is more maintainable.
/**
Calculating the neccessary values for a proper baud rate is pretty complex
and, as system clock and baudrate are known at compile time and never
changed at runtime, unneccessary.
However, how to get these values? Well, we do kind of an easter-egg. If
parameters are not known, we calculate them at runtime anyways, and also
report them to the user. So she can insert them here and after doing so,
whoops, serial fast and binary small :-)
Replacing this calculation with fixed values makes the binary a whopping
564 bytes smaller.
*/
#if (__SYSTEM_CLOCK == 48000000UL) && (BAUD == 115200)
#define UART_DLM 0x00
#define UART_DLL 0x17
#define UART_FDR 0xF2
//#elif (__SYSTEM_CLOCK == xxx) && (BAUD == xxx)
// Define more combinations here, Teacup reports the neccessary values
// at startup time.
#endif
#ifdef UART_DLM
// Set LCR[DLAB] to enable writing to divider registers.
LPC_UART->LCR |= (1 << 7);
// Set divider values.
LPC_UART->DLM = UART_DLM;
LPC_UART->DLL = UART_DLL;
LPC_UART->FDR = UART_FDR;
// Clear LCR[DLAB].
LPC_UART->LCR &= ~(1 << 7);
#else
/**
Calculate baud rate at runtime and later report it to the user. This
code is taken as-is from MBEDs serial_api.c, just reformatted whitespace
and comments.
*/
uint32_t baudrate = BAUD;
uint32_t PCLK = __SYSTEM_CLOCK;
/**
First we check to see if the basic divide with no DivAddVal/MulVal
ratio gives us an integer result. If it does, we set DivAddVal = 0,
MulVal = 1. Otherwise, we search the valid ratio value range to find
the closest match. This could be more elegant, using search methods
and/or lookup tables, but the brute force method is not that much
slower, and is more maintainable.
*/
uint16_t DL = PCLK / (16 * baudrate);
uint8_t DivAddVal = 0;
@ -64,47 +104,56 @@ void serial_init() {
int hit = 0;
uint16_t dlv;
uint8_t mv, dav;
if ((PCLK % (16 * baudrate)) != 0) { // Checking for zero remainder
if ((PCLK % (16 * baudrate)) != 0) { // Checking for zero remainder.
int err_best = baudrate, b;
for (mv = 1; mv < 16 && !hit; mv++)
{
for (dav = 0; dav < mv; dav++)
{
// baudrate = PCLK / (16 * dlv * (1 + (DivAdd / Mul))
// solving for dlv, we get dlv = mul * PCLK / (16 * baudrate * (divadd + mul))
// mul has 4 bits, PCLK has 27 so we have 1 bit headroom which can be used for rounding
// for many values of mul and PCLK we have 2 or more bits of headroom which can be used to improve precision
// note: X / 32 doesn't round correctly. Instead, we use ((X / 16) + 1) / 2 for correct rounding
if ((mv * PCLK * 2) & 0x80000000) // 1 bit headroom
dlv = ((((2 * mv * PCLK) / (baudrate * (dav + mv))) / 16) + 1) / 2;
else // 2 bits headroom, use more precision
dlv = ((((4 * mv * PCLK) / (baudrate * (dav + mv))) / 32) + 1) / 2;
for (mv = 1; mv < 16 && ! hit; mv++) {
for (dav = 0; dav < mv; dav++) {
/**
baudrate = PCLK / (16 * dlv * (1 + (DivAdd / Mul))
// datasheet says if DLL==DLM==0, then 1 is used instead since divide by zero is ungood
solving for dlv, we get
dlv = mul * PCLK / (16 * baudrate * (divadd + mul))
mul has 4 bits, PCLK has 27 so we have 1 bit headroom which can be
used for rounding. For many values of mul and PCLK we have 2 or
more bits of headroom which can be used to improve precision.
Note: X / 32 doesn't round correctly. Instead, we use
((X / 16) + 1) / 2 for correct rounding.
*/
if ((mv * PCLK * 2) & 0x80000000) // 1 bit headroom.
dlv = ((((2 * mv * PCLK) /
(baudrate * (dav + mv))) / 16) + 1) / 2;
else // 2 bits headroom, use more precision.
dlv = ((((4 * mv * PCLK) /
(baudrate * (dav + mv))) / 32) + 1) / 2;
// Datasheet says, if DLL == DLM == 0, then 1 is used instead,
// since divide by zero is ungood.
if (dlv == 0)
dlv = 1;
// datasheet says if dav > 0 then DL must be >= 2
// Datasheet says if dav > 0 then DL must be >= 2.
if ((dav > 0) && (dlv < 2))
dlv = 2;
// integer rearrangement of the baudrate equation (with rounding)
// Integer rearrangement of the baudrate equation (with rounding).
b = ((PCLK * mv / (dlv * (dav + mv) * 8)) + 1) / 2;
// check to see how we went
// Check to see how we went.
b = b - baudrate;
if (b < 0) b = -b;
if (b < err_best)
{
if (b < err_best) {
err_best = b;
DL = dlv;
MulVal = mv;
DivAddVal = dav;
if (b == baudrate)
{
if (b == baudrate) {
hit = 1;
break;
}
@ -113,19 +162,14 @@ void serial_init() {
}
}
// set LCR[DLAB] to enable writing to divider registers
// Set results like above.
LPC_UART->LCR |= (1 << 7);
// set divider values
LPC_UART->DLM = (DL >> 8) & 0xFF;
LPC_UART->DLL = (DL >> 0) & 0xFF;
LPC_UART->FDR = (uint32_t) DivAddVal << 0
| (uint32_t) MulVal << 4;
// clear LCR[DLAB]
LPC_UART->LCR &= ~(1 << 7);
} /* End of baud rate calculation. */
#endif /* UART_DLM, ! UART_DLM */
// Serial format.
LPC_UART->LCR = (8 - 5) << 0 // 8 data bits.
@ -138,6 +182,28 @@ void serial_init() {
| 0x02 << 3; // Pullup enabled.
LPC_IOCON->TXD_CMSIS = 0x01 << 0 // Function TXD.
| 0x00 << 3; // Pullup inactive.
#ifndef UART_DLM
/**
Baud rate settings were calculated at runtime, report them to the user
to allow her to insert them above. This is possible, because we just
completed setting up the serial port. Be generous with delays, on new
hardware delays might be too quick as well.
Uhm, yes, lots of code. 1420 bytes binary size together with the baudrate
calculation above. But it's #ifdef'd out when parameters are set above
and doing the calculation always at runtime would always add 400 bytes
binary size.
*/
delay_ms(500);
serial_writestr_P(PSTR("\nSerial port parameters were calculated at "));
serial_writestr_P(PSTR("runtime.\nInsert these values to the list of "));
serial_writestr_P(PSTR("known settings in serial-arm.c:\n"));
sersendf_P(PSTR(" UART_DLM %sx\n"), (DL >> 8) & 0xFF);
sersendf_P(PSTR(" UART_DLL %sx\n"), (DL >> 0) & 0xFF);
sersendf_P(PSTR(" UART_FDR %sx\n"), (DivAddVal << 0) | (MulVal << 4));
serial_writestr_P(PSTR("Doing so will speed up serial considerably.\n\n"));
#endif
}
/** Check wether characters can be read.
@ -171,6 +237,10 @@ void serial_writechar(uint8_t data) {
if ( ! (LPC_UART->LSR & (0x01 << 5))) // Queue full?
delay_us((1000000 / BAUD * 10) + 1);
#ifndef UART_DLM // Longer delays for fresh hardware.
delay_ms(100);
#endif
LPC_UART->THR = data;
}