Teacup_Firmware/serial-arm.c

172 lines
5.5 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 __ARMEL__
#include "arduino.h"
#ifdef XONXOFF
#error XON/XOFF protocol not yet implemented for ARM. \
See serial-avr.c for inspiration.
#endif
LPC_UART_TypeDef *port = LPC_UART;
/** 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.
port->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.
port->IER = 0 << 0 // Rx Data available irq enable.
| 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.
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 LCR[DLAB] to enable writing to divider registers
port->LCR |= (1 << 7);
// set divider values
port->DLM = (DL >> 8) & 0xFF;
port->DLL = (DL >> 0) & 0xFF;
port->FDR = (uint32_t) DivAddVal << 0
| (uint32_t) MulVal << 4;
// clear LCR[DLAB]
port->LCR &= ~(1 << 7);
} /* End of baud rate calculation. */
// Serial format.
port->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.
}
/** 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 port->LSR & 0x01;
}
/** Read one character.
*/
uint8_t serial_popchar(void) {
uint8_t c = 0;
if (serial_rxchars())
c = port->RBR;
return c;
}
/** Send one character.
If the queue is full, too bad. Do NOT block.
*/
void serial_writechar(uint8_t data) {
port->THR = data;
}
#endif /* defined TEACUP_C_INCLUDE && defined __ARMEL__ */