-
Notifications
You must be signed in to change notification settings - Fork 0
/
sup_usart.cpp
401 lines (376 loc) · 19.7 KB
/
sup_usart.cpp
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
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
//******************************************************************************************************
//
// file: sup_usart.cpp
// purpose: Support file for the RS-bus library. Select and initialise the USART for
// transmission, and return a pointer to the USART data register.
// Note that for reception of RS-bus messages the USART is not used.
//
// history: 2019-01-30 ap V0.1 Initial version
// 2021-07-16 ap V0.2 Changed the way the USART is selected
// 2021-07-19 ap V0.3 Added support for the 4809 and AVR-Dx controllers
//
// Based on the `usartNumber` parameter, this code allows selection of which USART will be used
// for RS-Bus transmission. After the USART is selected, the USART will be initialised as follows:
// 8 data bits, no parity, 1 stop bit, asynchronous mode and 4800 baud transmission.
//
// After the USART is initialised, the `dataRegister` variable will point to the USART's data register
// and the user sketch can send data bytes as follows:
// (*rsUSART.dataRegister) = data_byte;
//
// Number of USARTs
// ================
// Many of the `old` ATMega processors support a single USART only. Examples of such
// processors are the 8535, 16 and the 328 (the latter is used in the Arduino UNO and Nano).
// More powerful processors, such as the 2560 (used in the Arduino Mega) and newer
// processors such as the 4809 (used in the Arduino Every) support 4 or even more USARTS.
// Below an overview of (some) processors that support multiple USARTs.
// - 2 USARTS: 64, 128, 162, 164, 324, 328PB, 644, 1281, 1284, 2561
// - 4 USARTS: 640, 1280, 2560, 4808, 4809
// - 6 USARTS: AVR-DA, AVR-DB
// Note that the first USART on Arduino boards may also be used for the serial monitor.
// On such boards combined use of both RS-Bus and serial monitor may cause problems.
//
// USART Data Register names
// =========================
// For old ATMega processors, such as the 8535 and 16A, the data register is called UDR.
// For later ATMega processors, such as the 328 and 2560, the data register are called UDR0, UDR1, UDR2 and UDR3.
// For the newest ATMegaX processors, such as the 4808, 4809, DA and DB, the data register are called
// USART0.TXDATAL, USART1.TXDATAL, USART2.TXDATAL etc. Note that there are also TXDATAH registers, which
// are used for sending a ninth data bit, if needed.
//
// New __AVR_MEGAX__ processors
// ============================
// All of the AVR processors are known to the compiler as __AVR_MEGA__ processors,
// but the newer processors have the additional __AVR_MEGAX__ compiler directive.
// In literature these newer processors are also known as the megaAVR®-0, tinyAVR®-0,
// tinyAVR®-1, AVR-DA or AVR-DB devices.
//
// With these new __AVR_MEGAX__ processors, Microchip introduced a novel C-like register
// naming structure and coding style. The consequence of introducing novel register names is
// that old code must be modified to work with these newer processors. In this .cpp file
// the code is therefore split into 2 parts: one for the newer processors (identified by the
// __AVR_MEGAX__ compiler directive) and the other for the traditional __AVR_MEGA__ processors.
//
// A good introduction to this novel C-like naming structure can be found in TB3262:
// - AVR1000b: Getting Started with Writing C-Code for AVR® MCUs
// A good introduction for writing USART code for the new processors is TB3216:
// - Getting Started with Universal Synchronous and Asynchronous Receiver and Transmitter (USART)
//
// Alternative Pins
// ================
// An interesting feature of the __AVR_MEGAX__ processors, is that the USARTs need not always be
// connected to their default pins, but may alternatively be connected to other pins.
// In this .cpp code, this alternative pin assignment will be performed if the (optional) `defaultPins`
// parameter in the init() call is set to false.
//
// To perform this alternative pin assignment, the Port Multiplexer (PORTMUX) should be used. For the
// USARTs 0..3 we should use PORTMUX.USARTROUTEA; for the USARTs 4..5 it is PORTMUX.USARTROUTEB.
//
// Processors within a certain family but with a lower number of pins will not implement all USARTS.
// In the code below #ifdef xxx constructs are therefore used to prevent the compiler from generating
// calls to non-existent USART registers. If a (non-AVR) processor is selected that has no or different
// type of USARTS, a "human understandable" error message will be displayed during compile time.
//
// Although the compiler will not generate calls to non-supported USART registers, the compiler does
// not check if the user sketch selects non-existing registers.
// If the user sketch provides an unsupported usartNumber, "dataRegister" will point to a dummy variable
// and the boolean variable `noUsart` will be set.
//
// As opposed to the `old` AVR processors, for the new __AVR_MEGAX__ processors the USART TX pin
// must explicitly be defined as output, which implies that this .cpp code needs to know to which pins
// the USART will be connected.
//
// Ports to (alternative) pin mapping for the 4808 and 4809
// ========================================================
// Depending on the number of pins, upto 4 USARTS are supported, named USART0 till USART4.
// The mapping between USARTs and standard pins is as follows (between brackets the alternative pins):
// USART0 TX => PA0 (PA4) - On all versions (28, 32, 40 and 48 pin)
// USART1 TX => PC0 (PC4) - Alternative pins only on the 40 and 48 pin versions
// USART2 TX => PF0 (PF4) - Alternative pins not on the 28 pin version
// USART3 TX => PB0 (PB4) - Normal and alternative pins only on the 48 pin version
// Ports to (alternative) pin mapping for the DA core
// ==================================================
// Depending on the number of pins, upto 6 USARTS are supported.
// The mapping between USARTs and standard pins is as follows (between brackets the alternative pins):
// USART0 TX => PA0 (PA4) - On all versions (28, 32, 48 and 64 pin)
// USART1 TX => PC0 (PC4) - Alternative pins only on the 48 and 64 pin versions
// USART2 TX => PF0 (PF4) - Alternative pins not on the 28 pin version
// USART3 TX => PB0 (PB4) - Normal and alternative pins only on the 48 and 64 pin versions
// USART4 TX => PE0 (PE4) - Normal pins only on the 48 and 64 pin versions, alternative pins only on 64
// USART5 TX => PG0 (PG4) - Normal and alternative pins only on the 64 pin version
//
// Mapping between Arduino digital pin numbers and TX Pins
// =======================================================
// - 1 => Serial (TXD0) - UNO, NANO
// - 1 => Serial1 (TXD1) - NANO Every
// - 2 => Serial2 (TXD2) - NANO Every
// - 6 => Serial3 (TXD3) - NANO Every
// - 2 => Serial (TXD0) - Thinary Every
// - 0 => Serial2 (TXD2) - Thinary Every
// - 1 => Serial (TXD0) - Mega / MegaCore Mega layout
// - 18 => Serial1 (TXD1) - Mega / MegaCore Mega layout
// - 16 => Serial2 (TXD2) - Mega / MegaCore Mega layout
// - 14 => Serial3 (TXD3) - Mega / MegaCore Mega layout
// - 9 => Serial (TXD) - Mightycore standard layout - ATmega 8535/16/32
// - 9 => Serial (TXD0) - Mightycore standard layout - ATmega 164/324/644/1284
// - 11 => Serial1 (TXD1) - Mightycore standard layout - ATmega 164/324/644/1284
// - 0 => Serial (TXD0) - MegaCoreX (48pin) - ATMega 4809
// - 14 => Serial1 (TXD1) - MegaCoreX (48pin) - ATMega 4809
// - 34 => Serial2 (TXD2) - MegaCoreX (48pin) - ATMega 4809
// - 8 => Serial3 (TXD3) - MegaCoreX (48pin) - ATMega 4809
// Testing
// =======
// This source has been tested on:
// 1) Standard `old` Arduino AVR boards, such as the Arduino UNO, Arduino Nano, Arduino Mega.
// 2) New Arduino megaAVR boards, such as the Arduino Nano Every
// 3) MegaCore boards with the ATMEGA 2560
// 4) MightyCore boards with the ATMega 16A
// 5) MegaCoreX boards with the ATMega 4809
// 6) DxCore boards with the AVR128DA48 (Curiosity Nano)
//
// Compile errors??
// ================
// If compiled using an "Arduino megaAVR Board" with Register Emulation: 'ATMEGA328',
// the following error will be shown: 'class PORTCClass' has no member named 'DIR'
// Make sure to select Register Emulation: 'NONE'
//
// License
// =======
// This source file is subject of the GNU general public license 3,
// that is available at the world-wide-web at http://www.gnu.org/licenses/gpl.txt
//
//******************************************************************************************************
#include <Arduino.h>
#include "sup_usart.h"
// Instantiate the rsUSART object from the USART class
USART rsUSART;
// Define the constructor
USART::USART(void) {
// Initialise the data register to a place that can be written even if init() fails.
dataRegister = &dummy_byte;
}
void USART::init(uint8_t usartNumber, bool defaultPins) {
// Messages should be: 8 bit, no parity, 1 stop bit, asynchronous mode, 4800 baud.
//
// The code has two parts:
// 1) For newer __AVR_XMEGA__ processors, that support a C-like interface to access the USART
// 2) For traditional __AVR_MEGA__ processors.
//****************************************************************************************************
// Part 1: __AVR_XMEGA__ processors (such as 4808, 4809, AVR DA, AVR DB, ...)
//****************************************************************************************************
#if defined(__AVR_XMEGA__)
// Baudrate formula for the `new` Xmega controllers
// See: TB3216-Getting-Started-with-USART-DS90003216.pdf
#define USART_BAUD_RATE(BAUD_RATE) ((float)(F_CPU * 64 / (16 * (float)BAUD_RATE)) + 0.5)
switch (usartNumber) {
case 0:
#ifdef USART0
#define HAS_USART
// Determine which pin should be used for TX and set this pin as output
// USART0 uses as default TX pin PA0, and as alternative PA4
// Initialise at the default port, by clearing the PORTMUX USART0 fields
PORTMUX.USARTROUTEA &= ~(PORTMUX_USART0_gm);
if (defaultPins) {
PORTA.DIR |= PIN0_bm; // Set the default pin as output
}
else {
// Set the PORTMUX USART0 fields to the alternative pin
PORTMUX.USARTROUTEA |= PORTMUX_USART00_bm;
PORTA.DIR |= PIN4_bm; // Set the alternative pin as output
}
// Set the USART registers and the dataRegister pointer
USART0.BAUD = (uint16_t)USART_BAUD_RATE(4800);
USART0.CTRLB |= USART_TXEN_bm; // Turn on transmission (but not reception!) circuitry
dataRegister = &USART0.TXDATAL; // The low 8-bit USART data register
#endif
break;
case 1:
#ifdef USART1
#define HAS_USART
// Determine which pin should be used for TX and set this pin as output
// USART1 uses as default TX pin PC0, and as alternative PC4
// Initialise at the default port, by clearing the PORTMUX USART1 fields
PORTMUX.USARTROUTEA &= ~(PORTMUX_USART1_gm);
if (defaultPins) {
PORTC.DIR |= PIN0_bm; // Set the default pin as output
}
else {
// Set the PORTMUX USART0 fields to the alternative pin
PORTMUX.USARTROUTEA |= PORTMUX_USART10_bm;
PORTC.DIR |= PIN4_bm; // Set the alternative pin as output
}
// Set the USART registers and the dataRegister pointer
USART1.BAUD = (uint16_t)USART_BAUD_RATE(4800);
USART1.CTRLB |= USART_TXEN_bm; // Turn on transmission (but not reception!) circuitry
dataRegister = &USART1.TXDATAL; // The low 8-bit USART data register
#endif
break;
case 2:
#ifdef USART2
#define HAS_USART
// Determine which pin should be used for TX and set this pin as output
// USART2 uses as default TX pin PF0, and as alternative PF4
// Initialise at the default port, by clearing the PORTMUX USART2 fields
PORTMUX.USARTROUTEA &= ~(PORTMUX_USART2_gm);
if (defaultPins) {
PORTF.DIR |= PIN0_bm; // Set the default pin as output
}
else {
// Set the PORTMUX USART2 fields to the alternative pin
PORTMUX.USARTROUTEA |= PORTMUX_USART20_bm;
PORTF.DIR |= PIN4_bm; // Set the alternative pin as output
}
// Set the USART registers and the dataRegister pointer
USART2.BAUD = (uint16_t)USART_BAUD_RATE(4800);
USART2.CTRLB |= USART_TXEN_bm; // Turn on transmission (but not reception!) circuitry
dataRegister = &USART2.TXDATAL; // The low 8-bit USART data register
#endif
break;
case 3:
#ifdef USART3
#define HAS_USART
// Determine which pin should be used for TX and set this pin as output
// USART3 uses as default TX pin PB0, and as alternative PB4
// Initialise at the default port, by clearing the PORTMUX USART3 fields
PORTMUX.USARTROUTEA &= ~(PORTMUX_USART3_gm);
if (defaultPins) {
PORTB.DIR |= PIN0_bm; // Set the default pin as output
}
else {
// Set the PORTMUX USART3 fields to the alternative pin
PORTMUX.USARTROUTEA |= PORTMUX_USART30_bm;
PORTB.DIR |= PIN4_bm; // Set the alternative pin as output
}
// Set the USART registers and the dataRegister pointer
USART3.BAUD = (uint16_t)USART_BAUD_RATE(4800);
USART3.CTRLB |= USART_TXEN_bm; // Turn on transmission (but not reception!) circuitry
dataRegister = &USART3.TXDATAL; // The low 8-bit USART data register
#endif
break;
case 4:
#ifdef USART4
#define HAS_USART
// Determine which pin should be used for TX and set this pin as output
// USART4 uses as default TX pin PE0, and as alternative PE4
// Initialise at the default port, by clearing the PORTMUX USART4 fields
PORTMUX.USARTROUTEB &= ~(PORTMUX_USART4_gm);
if (defaultPins) {
PORTE.DIR |= PIN0_bm; // Set the default pin as output
}
else {
// Set the PORTMUX USART4 fields to the alternative pin
PORTMUX.USARTROUTEA |= PORTMUX_USART40_bm;
PORTE.DIR |= PIN4_bm; // Set the alternative pin as output
}
// Set the USART registers and the dataRegister pointer
USART4.BAUD = (uint16_t)USART_BAUD_RATE(4800);
USART4.CTRLB |= USART_TXEN_bm; // Turn on transmission (but not reception!) circuitry
dataRegister = &USART4.TXDATAL; // The low 8-bit USART data register
#endif
break;
case 5:
#ifdef USART5
#define HAS_USART
// Determine which pin should be used for TX and set this pin as output
// USART5 uses as default TX pin PG0, and as alternative PG4
// Initialise at the default port, by clearing the PORTMUX USART5 fields
PORTMUX.USARTROUTEB &= ~(PORTMUX_USART5_gm);
if (defaultPins) {
PORTG.DIR |= PIN0_bm; // Set the default pin as output
}
else {
// Set the PORTMUX USART5 fields to the alternative pin
PORTMUX.USARTROUTEA |= PORTMUX_USART50_bm;
PORTG.DIR |= PIN4_bm; // Set the alternative pin as output
}
// Set the USART registers and the dataRegister pointer
USART5.BAUD = (uint16_t)USART_BAUD_RATE(4800);
USART5.CTRLB |= USART_TXEN_bm; // Turn on transmission (but not reception!) circuitry
dataRegister = &USART5.TXDATAL; // The low 8-bit USART data register
#endif
break;
}
//****************************************************************************************************
// Part 2: Other __AVR_MEGA__ processors (such as 8535, 16, 328, 2560, ...)
//****************************************************************************************************
#elif defined(__AVR_MEGA__) || defined(__AVR_ATmega8535__) // ATMega 328, 2560, 16, 8535, etc...
// Step 1: Define which baudrate will be used
// Step 2: Initialise USART Control and Status Register B
// This register is responsible for (amongst others) activating (receive and) transmission circuitry
// Step 3: Initialise USART Control and Status Register C
// This register determines (amongst others) what type of serial format is being used
// We use 8 bit (no parity, 1 stop bit, asynchronous mode)
// Step 4: Set the baudrate register to 4800 baud
// See: http://www.github.com/abcminiuser/avr-tutorials/blob/master/USART/Output/USART.pdf?raw=true
// Baudrate and Prescaler for the standard `old` ATmega controllers
#define BAUD_RATE 4800
#define BAUD_PRESCALE ((((F_CPU / 16) + (BAUD_RATE / 2)) / (BAUD_RATE)) - 1)
switch (usartNumber) {
case 0:
#ifdef UDR // ATmega 8535/16/32
#define HAS_USART
dataRegister = &UDR;
UCSRB |= (1 << TXEN); // Control and Status Register B
UCSRC |= (1 << URSEL) // Register C. Use URSEL bit!
| (1 << UCSZ0) // Character size bit 0
| (1 << UCSZ1); // Character size bit 1
UBRRL = BAUD_PRESCALE; // Load lower 8-bits of the baud rate value
UBRRH = (BAUD_PRESCALE >> 8); // Load upper 8-bits of the baud rate
//
#elif defined (UDR0) // The standard `old` ATmega boards
#define HAS_USART
dataRegister = &UDR0;
UCSR0B |= (1 << TXEN0); // Turn on transmission (but not reception!) circuitry
UCSR0C |= (1 << UCSZ00) // Use 8 bit
| (1 << UCSZ01); //
UBRR0L = BAUD_PRESCALE; // Load lower 8-bits of the baud rate value
UBRR0H = (BAUD_PRESCALE >> 8); // Load upper 8-bits of the baud rate
#endif
break;
case 1:
#ifdef UDR1
#define HAS_USART
dataRegister = &UDR1;
UCSR1B |= (1 << TXEN1); // Turn on transmission (but not reception!) circuitry
UCSR1C |= (1 << UCSZ10) // Use 8 bit
| (1 << UCSZ11); //
UBRR1L = BAUD_PRESCALE; // Load lower 8-bits of the baud rate value
UBRR1H = (BAUD_PRESCALE >> 8); // Load upper 8-bits of the baud rate
#endif
break;
case 2:
#ifdef UDR2
#define HAS_USART
dataRegister = &UDR2;
UCSR2B |= (1 << TXEN2); // Turn on transmission (but not reception!) circuitry
UCSR2C |= (1 << UCSZ20) // Use 8 bit
| (1 << UCSZ21); //
UBRR2L = BAUD_PRESCALE; // Load lower 8-bits of the baud rate value
UBRR2H = (BAUD_PRESCALE >> 8); // Load upper 8-bits of the baud rate
#endif
break;
case 3:
#ifdef UDR3
#define HAS_USART
dataRegister = &UDR3;
UCSR3B |= (1 << TXEN3); // Turn on transmission (but not reception!) circuitry
UCSR3C |= (1 << UCSZ30) // Use 8 bit
| (1 << UCSZ31); //
UBRR3L = BAUD_PRESCALE; // Load lower 8-bits of the baud rate value
UBRR3H = (BAUD_PRESCALE >> 8); // Load upper 8-bits of the baud rate
#endif
break;
}
if (defaultPins) {;} // Only included to avoid compiler warnings.
#endif // End of conditional code
//****************************************************************************************************
// Raise an error if no USART support is found
//****************************************************************************************************
#ifndef HAS_USART
#error No USART could be found that is supported by this processor
#endif
noUsart = false;
if (dataRegister == &dummy_byte) {
noUsart = true; // the selected usartNumber is too high: no USART at this number
}
}