249 lines
8.1 KiB
C
249 lines
8.1 KiB
C
|
|
/** \file
|
|
\brief Serial subsystem, ARM specific part.
|
|
|
|
To be included from serial.c, for more details see there.
|
|
|
|
Other than AVRs, ARMs feature a serial buffer in hardware, so we can get
|
|
away without a software buffer and also without(!) interrupts.
|
|
|
|
Code here is heavily inspired by serial_api.c of MBED, found in
|
|
mbed/libraries/mbed/targets/hal/TARGET_NXP/TARGET_LPC11XX_11CXX/.
|
|
*/
|
|
|
|
#if defined TEACUP_C_INCLUDE && defined __ARM_LPC1114__
|
|
|
|
#include "arduino.h"
|
|
#include "cmsis-lpc11xx.h"
|
|
#include "delay.h"
|
|
#include "sersendf.h"
|
|
|
|
#ifdef XONXOFF
|
|
#error XON/XOFF protocol not yet implemented for ARM. \
|
|
See serial-avr.c for inspiration.
|
|
#endif
|
|
|
|
|
|
/** Initialise serial subsystem.
|
|
|
|
Set up baud generator and interrupts, clear buffers.
|
|
*/
|
|
void serial_init() {
|
|
|
|
// Turn on UART power.
|
|
LPC_SYSCON->SYSAHBCLKCTRL |= (1 << 12);
|
|
|
|
// Enable fifos and default RX trigger level.
|
|
LPC_UART->FCR = 1 << 0 // FIFO Enable - 0 = Disabled, 1 = Enabled.
|
|
| 0 << 1 // Rx Fifo Reset.
|
|
| 0 << 2 // Tx Fifo Reset.
|
|
| 0 << 6; // Rx irq trigger level.
|
|
// 0 = 1 char, 1 = 4 chars, 2 = 8 chars, 3 = 14 chars.
|
|
|
|
// Disable IRQs.
|
|
LPC_UART->IER = 0 << 0 // Rx Data available irq enable.
|
|
| 0 << 1 // Tx Fifo empty irq enable.
|
|
| 0 << 2; // Rx Line Status irq enable.
|
|
|
|
LPC_SYSCON->UARTCLKDIV = 0x1;
|
|
|
|
/**
|
|
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;
|
|
uint8_t MulVal = 1;
|
|
int hit = 0;
|
|
uint16_t dlv;
|
|
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.
|
|
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.
|
|
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 results like above.
|
|
LPC_UART->LCR |= (1 << 7);
|
|
LPC_UART->DLM = (DL >> 8) & 0xFF;
|
|
LPC_UART->DLL = (DL >> 0) & 0xFF;
|
|
LPC_UART->FDR = (uint32_t) DivAddVal << 0
|
|
| (uint32_t) MulVal << 4;
|
|
LPC_UART->LCR &= ~(1 << 7);
|
|
#endif /* UART_DLM, ! UART_DLM */
|
|
|
|
// Serial format.
|
|
LPC_UART->LCR = (8 - 5) << 0 // 8 data bits.
|
|
| (1 - 1) << 2 // 1 stop bit.
|
|
| 0 << 3 // Parity disabled.
|
|
| 0 << 4; // 0 = odd parity, 1 = even parity.
|
|
|
|
// Pinout the UART. No need to set GPIO stuff, like data direction.
|
|
LPC_IOCON->RXD_CMSIS = 0x01 << 0 // Function RXD.
|
|
| 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(serial_writechar, PSTR(" UART_DLM %sx\n"), (DL >> 8) & 0xFF);
|
|
sersendf_P(serial_writechar, PSTR(" UART_DLL %sx\n"), (DL >> 0) & 0xFF);
|
|
sersendf_P(serial_writechar, 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.
|
|
|
|
Other than the AVR implementation this returns not the number of characters
|
|
in the line, but only wether there is at least one or not.
|
|
*/
|
|
uint8_t serial_rxchars(void) {
|
|
return LPC_UART->LSR & 0x01;
|
|
}
|
|
|
|
/** Read one character.
|
|
*/
|
|
uint8_t serial_popchar(void) {
|
|
uint8_t c = 0;
|
|
|
|
if (serial_rxchars())
|
|
c = LPC_UART->RBR;
|
|
|
|
return c;
|
|
}
|
|
|
|
/** Send one character.
|
|
|
|
If the queue is full, we wait as long as sending a character takes
|
|
(87 microseconds at 115200 baud) and send then blindly. This way we can
|
|
send arbitrarily long messages without slowing down short messages or even
|
|
blocking.
|
|
*/
|
|
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;
|
|
}
|
|
|
|
#endif /* defined TEACUP_C_INCLUDE && defined __ARM_LPC1114__ */
|