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:
parent
fc124c14d4
commit
e8299deb86
184
serial-arm.c
184
serial-arm.c
|
|
@ -16,6 +16,7 @@
|
||||||
#include "arduino.h"
|
#include "arduino.h"
|
||||||
#include "mbed-LPC11xx.h"
|
#include "mbed-LPC11xx.h"
|
||||||
#include "delay.h"
|
#include "delay.h"
|
||||||
|
#include "sersendf.h"
|
||||||
|
|
||||||
#ifdef XONXOFF
|
#ifdef XONXOFF
|
||||||
#error XON/XOFF protocol not yet implemented for ARM. \
|
#error XON/XOFF protocol not yet implemented for ARM. \
|
||||||
|
|
@ -44,19 +45,58 @@ void serial_init() {
|
||||||
| 0 << 1 // Tx Fifo empty irq enable.
|
| 0 << 1 // Tx Fifo empty irq enable.
|
||||||
| 0 << 2; // Rx Line Status irq enable.
|
| 0 << 2; // Rx Line Status irq enable.
|
||||||
|
|
||||||
// Baud rate calculation - - - TO BE REFINED, we can calculate all this
|
LPC_SYSCON->UARTCLKDIV = 0x1;
|
||||||
// in the preprocessor or even hardcode it, because baud rate never changes.
|
|
||||||
{
|
|
||||||
uint32_t baudrate = BAUD;
|
|
||||||
|
|
||||||
LPC_SYSCON->UARTCLKDIV = 0x1;
|
/**
|
||||||
uint32_t PCLK = SystemCoreClock;
|
Calculating the neccessary values for a proper baud rate is pretty complex
|
||||||
// First we check to see if the basic divide with no DivAddVal/MulVal
|
and, as system clock and baudrate are known at compile time and never
|
||||||
// ratio gives us an integer result. If it does, we set DivAddVal = 0,
|
changed at runtime, unneccessary.
|
||||||
// MulVal = 1. Otherwise, we search the valid ratio value range to find
|
|
||||||
// the closest match. This could be more elegant, using search methods
|
However, how to get these values? Well, we do kind of an easter-egg. If
|
||||||
// and/or lookup tables, but the brute force method is not that much
|
parameters are not known, we calculate them at runtime anyways, and also
|
||||||
// slower, and is more maintainable.
|
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);
|
uint16_t DL = PCLK / (16 * baudrate);
|
||||||
|
|
||||||
uint8_t DivAddVal = 0;
|
uint8_t DivAddVal = 0;
|
||||||
|
|
@ -64,68 +104,72 @@ void serial_init() {
|
||||||
int hit = 0;
|
int hit = 0;
|
||||||
uint16_t dlv;
|
uint16_t dlv;
|
||||||
uint8_t mv, dav;
|
uint8_t mv, dav;
|
||||||
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
|
if ((PCLK % (16 * baudrate)) != 0) { // Checking for zero remainder.
|
||||||
dlv = ((((2 * mv * PCLK) / (baudrate * (dav + mv))) / 16) + 1) / 2;
|
int err_best = baudrate, b;
|
||||||
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
|
for (mv = 1; mv < 16 && ! hit; mv++) {
|
||||||
if (dlv == 0)
|
for (dav = 0; dav < mv; dav++) {
|
||||||
dlv = 1;
|
/**
|
||||||
|
baudrate = PCLK / (16 * dlv * (1 + (DivAdd / Mul))
|
||||||
|
|
||||||
// datasheet says if dav > 0 then DL must be >= 2
|
solving for dlv, we get
|
||||||
if ((dav > 0) && (dlv < 2))
|
|
||||||
dlv = 2;
|
|
||||||
|
|
||||||
// integer rearrangement of the baudrate equation (with rounding)
|
dlv = mul * PCLK / (16 * baudrate * (divadd + mul))
|
||||||
b = ((PCLK * mv / (dlv * (dav + mv) * 8)) + 1) / 2;
|
|
||||||
|
|
||||||
// check to see how we went
|
mul has 4 bits, PCLK has 27 so we have 1 bit headroom which can be
|
||||||
b = b - baudrate;
|
used for rounding. For many values of mul and PCLK we have 2 or
|
||||||
if (b < 0) b = -b;
|
more bits of headroom which can be used to improve precision.
|
||||||
if (b < err_best)
|
|
||||||
{
|
|
||||||
err_best = b;
|
|
||||||
|
|
||||||
DL = dlv;
|
Note: X / 32 doesn't round correctly. Instead, we use
|
||||||
MulVal = mv;
|
((X / 16) + 1) / 2 for correct rounding.
|
||||||
DivAddVal = dav;
|
*/
|
||||||
|
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;
|
||||||
|
|
||||||
if (b == baudrate)
|
// Datasheet says, if DLL == DLM == 0, then 1 is used instead,
|
||||||
{
|
// since divide by zero is ungood.
|
||||||
hit = 1;
|
if (dlv == 0)
|
||||||
break;
|
dlv = 1;
|
||||||
}
|
|
||||||
}
|
// 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).
|
||||||
|
b = ((PCLK * mv / (dlv * (dav + mv) * 8)) + 1) / 2;
|
||||||
|
|
||||||
|
// Check to see how we went.
|
||||||
|
b = b - baudrate;
|
||||||
|
if (b < 0) b = -b;
|
||||||
|
if (b < err_best) {
|
||||||
|
err_best = b;
|
||||||
|
|
||||||
|
DL = dlv;
|
||||||
|
MulVal = mv;
|
||||||
|
DivAddVal = dav;
|
||||||
|
|
||||||
|
if (b == baudrate) {
|
||||||
|
hit = 1;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// set LCR[DLAB] to enable writing to divider registers
|
// Set results like above.
|
||||||
LPC_UART->LCR |= (1 << 7);
|
LPC_UART->LCR |= (1 << 7);
|
||||||
|
|
||||||
// set divider values
|
|
||||||
LPC_UART->DLM = (DL >> 8) & 0xFF;
|
LPC_UART->DLM = (DL >> 8) & 0xFF;
|
||||||
LPC_UART->DLL = (DL >> 0) & 0xFF;
|
LPC_UART->DLL = (DL >> 0) & 0xFF;
|
||||||
LPC_UART->FDR = (uint32_t) DivAddVal << 0
|
LPC_UART->FDR = (uint32_t) DivAddVal << 0
|
||||||
| (uint32_t) MulVal << 4;
|
| (uint32_t) MulVal << 4;
|
||||||
|
|
||||||
// clear LCR[DLAB]
|
|
||||||
LPC_UART->LCR &= ~(1 << 7);
|
LPC_UART->LCR &= ~(1 << 7);
|
||||||
|
#endif /* UART_DLM, ! UART_DLM */
|
||||||
} /* End of baud rate calculation. */
|
|
||||||
|
|
||||||
// Serial format.
|
// Serial format.
|
||||||
LPC_UART->LCR = (8 - 5) << 0 // 8 data bits.
|
LPC_UART->LCR = (8 - 5) << 0 // 8 data bits.
|
||||||
|
|
@ -138,6 +182,28 @@ void serial_init() {
|
||||||
| 0x02 << 3; // Pullup enabled.
|
| 0x02 << 3; // Pullup enabled.
|
||||||
LPC_IOCON->TXD_CMSIS = 0x01 << 0 // Function TXD.
|
LPC_IOCON->TXD_CMSIS = 0x01 << 0 // Function TXD.
|
||||||
| 0x00 << 3; // Pullup inactive.
|
| 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.
|
/** Check wether characters can be read.
|
||||||
|
|
@ -171,6 +237,10 @@ void serial_writechar(uint8_t data) {
|
||||||
if ( ! (LPC_UART->LSR & (0x01 << 5))) // Queue full?
|
if ( ! (LPC_UART->LSR & (0x01 << 5))) // Queue full?
|
||||||
delay_us((1000000 / BAUD * 10) + 1);
|
delay_us((1000000 / BAUD * 10) + 1);
|
||||||
|
|
||||||
|
#ifndef UART_DLM // Longer delays for fresh hardware.
|
||||||
|
delay_ms(100);
|
||||||
|
#endif
|
||||||
|
|
||||||
LPC_UART->THR = data;
|
LPC_UART->THR = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue