forked from Traumflug/Teacup_Firmware
-
Notifications
You must be signed in to change notification settings - Fork 0
/
serial-arm.c
247 lines (198 loc) · 7.98 KB
/
serial-arm.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
/** \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"
#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(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.
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 __ARMEL__ */