/
UART.cpp
888 lines (831 loc) · 46.2 KB
/
UART.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
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
/* UART.cpp - Hardware serial library, main file.
* This library is free software released under LGPL 2.1.
* See License.md for more information.
* This file is part of megaTinyCore.
*
* Copyright (c) 2006 Nicholas Zambetti, Modified by
* 11/23/2006 David A. Mellis, 9/20/2010 Mark Sproul,
* 8/24/2012 Alarus, 12/3/2013 Matthijs Kooijman
* Others (unknown) 2013-2017, 2017-2021 Spence Konde
* and 2021 MX682X
*
* See UART.h for more of a record of changes.
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include <util/atomic.h>
//#include <avr/io.h>
#include "Arduino.h"
#include "UART.h"
#include "UART_private.h"
// this next line disables the entire UART.cpp if there's no hardware serial
#if defined(USART0) || defined(USART1) || defined(USART2) || defined(USART3) || defined(USART4) || defined(USART5)
#if defined(HAVE_HWSERIAL0) || defined(HAVE_HWSERIAL1) || defined(HAVE_HWSERIAL2) || defined(HAVE_HWSERIAL3) || defined(HAVE_HWSERIAL4) || defined(HAVE_HWSERIAL5)
// macro to guard critical sections when needed for large TX buffer sizes
#if (SERIAL_TX_BUFFER_SIZE > 256)
#define TX_BUFFER_ATOMIC ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
#else
#define TX_BUFFER_ATOMIC
#endif
/*## ### ####
# # # #
# ### ####
# # # #
### #### # */
/* There ain't no such thing as a free lunch. Every new feature makes these worse.
USE_ASM_TXC 2 takes an additional 2 words/2 clocks vs 1
USE_ASM_RXC 2 takes an additional 5 words/5 clocks vs 1
RXC takes another 3 words/4 clocks if SDF errata compensation is needed.
Both USE_ASM_RXC and USE_ASM_TXC must be mode 2 for half duplex to work
Transmit Complete for Half Duplex
This is a more efficient and scalable version of the TXC ISR, the original implementation is below:
#if defined(USART1)
ISR(USART1_TXC_vect) {
uint8_t ctrla;
while (USART1.STATUS & USART_RXCIF_bm) {
ctrla = USART1.RXDATAL;
}
ctrla = USART1.CTRLA;
ctrla |= USART_RXCIE_bm; // turn on receive complete
ctrla &= ~USART_TXCIE_bm; // turn off transmit complete
USART1.CTRLA = ctrla;
}
In the USARTn.cpp, there's something like this that puts the low byte of the address of the USART into r30
after saving the register. Then it just jumps to this routine, which loads the (always the same) high byte!
and finishes up clearing out the other register we will need and saving the SREG. The logic is very simple,
It's just ugly. It gets worse for the other interrupts because we have to work with the class, not just the
hardware. Crucially the only thing different betweren the USARTs here isthe addressthey're working with.
Much of the benefit comes from being able to get the benefits of functionsin terms of flash use without the
penalties that come with using a true CALL instruction in an ISR (50-80 byte prologue + epiloge), and also
being aware that the X register can't do displacement when planning what goes in which regiseser... which
is not avr-gcc's strong suite, and often ends up displacing from the X with adiw/sbiw spam. savings for one
copy of it is small. Savings for several is gets large fast! Performance is better, but not much.
Biggest advantage is for 2-series with the dual UARTs, but potentially as little as 4k of flash.
TXC isr starts from this:
ISR(USART1_TXC_vect, ISR_NAKED) {
__asm__ __volatile__(
"push r30" "\n\t" // 1
"ldi r30, 0x20" "\n\t" // 1
"rjmp do_txc" "\n\t" // 1
:::);
}
*/
#if USE_ASM_TXC == 2
void __attribute__((naked)) __attribute__((used)) __attribute__((noreturn)) _do_txc(void) {
__asm__ __volatile__(
"_do_txc:" "\n\t" // We start out 11-13 clocks after the interrupt
"push r24" "\n\t" // r30 and r31 pushed before this.
"in r24, 0x3f" "\n\t" // Save SREG
"push r24" "\n\t" //
"push r25" "\n\t" //
"push r28" "\n\t" //
"push r29" "\n\t" //
"ldd r28, Z + 8" "\n\t" // Load USART into Y pointer, low byte
"ldi r29, 0x08" "\n\t" // all USARTs are 0x08n0 where n is an even hex digit.
"ldd r25, Y + 5" "\n\t" // Y + 5 = USARTn.CTRLA read CTRLA
"_txc_flush_rx:" "\n\t" // start of rx flush loop.
"ld r24, Y" "\n\t" // Y + 0 = USARTn.RXDATAL rx data
"ldd r24, Y + 4" "\n\t" // Y + 4 = USARTn.STATUS
"sbrc r24, 7" "\n\t" // if RXC bit is clear...
"rjmp _txc_flush_rx" "\n\t" // .... skip this jump to remove more from the buffer.
"andi r25, 0xBF" "\n\t" // clear TXCIE
"ori r25, 0x80" "\n\t" // set RXCIE
"std Y + 5, r25" "\n\t" // store CTRLA
// "ldd r24, Z + 12" "\n\t"
// "ahha, always, true" "\n\t" // wait, if we're in TXC, We are in half duplex mode, duuuuh
// "sbrs r24, 2" "\n\t" // if we're in half duplex skip...
// "rjmp .+ 6" "\n\t" // a jump over the next three instructoins. Do do them iff in half duplex only
// "ori r24, 0x10" "\n\t" // add the "there's an echo in here" bit
// "std Z + 12, r24" "\n\t" // Store modified state
"pop r29" "\n\t"
"pop r28" "\n\t"
"pop r25" "\n\t"
"pop r24" "\n\t" // pop r24 to get old SREG back
"out 0x3F, r24" "\n\t" // restore sreg.
"pop r24" "\n\t" // pop r24 restore it
"pop r31" "\n\t" // and r31
"pop r30" "\n\t" // Pop the register the ISR did
"reti" "\n" // return from the interrupt.
::
);
__builtin_unreachable();
}
#elif USE_ASM_TXC == 1
void __attribute__((naked)) __attribute__((used)) __attribute__((noreturn)) _do_txc(void) {
__asm__ __volatile__(
"_do_txc:" "\n\t" //
"push r31" "\n\t" // push other half of Z register.
"push r24" "\n\t" // push r24
"in r24, 0x3f" "\n\t" // save sreg to r24
"push r24" "\n\t" // and push that. r30 pushed and loaded by ISR already.
"ldi r31, 0x08" "\n\t" // all USARTs are 0x08n0 where n is an even hex digit.
"_txc_flush_rx:" "\n\t" // start of rx flush loop.
"ld r24, Z" "\n\t" // Z + 0 = USARTn.RXDATAL rx data
"ldd r24, Z + 4" "\n\t" // Z + 4 = USARTn.STATUS
"sbrc r24, 7" "\n\t" // if RXC bit is set...
"rjmp _txc_flush_rx" "\n\t" // .... skip this jump to remove more from the buffer.
"ldd r24, Z + 5" "\n\t" // Z + 5 = USARTn.CTRLA read CTRLA
"andi r24, 0xBF" "\n\t" // clear TXCIE
"ori r24, 0x80" "\n\t" // set RXCIE
"std Z + 5, r24" "\n\t" // store CTRLA
"pop r24" "\n\t" // pop r24, xcontaining old sreg.
"out 0x3f, r24" "\n\t" // restore it
"pop r24" "\n\t" // pop r24 to get it's old value back
"pop r31" "\n\t" // and r31
"pop r30" "\n\t" // Pop the register the ISR pushed
"reti" "\n" // return from the interrupt.
::);
__builtin_unreachable();
}
#endif
/*
We are starting from this:
ISR(USART0_RXC_vect, ISR_NAKED) {
__asm__ __volatile__(
"push r30" "\n\t"
"push r31" "\n\t"
:::);
__asm__ __volatile__(
"rjmp do_rxc" "\n\t"
::"z"(&Serialn));
__builtin_unreachable();
}
*/
#if ((USE_ASM_RXC == 1) && (SERIAL_RX_BUFFER_SIZE == 256 || SERIAL_RX_BUFFER_SIZE == 128 || SERIAL_RX_BUFFER_SIZE == 64 || SERIAL_RX_BUFFER_SIZE == 32 || SERIAL_RX_BUFFER_SIZE == 16) )
void __attribute__((naked)) __attribute__((used)) __attribute__((noreturn)) _do_rxc(void) {
__asm__ __volatile__(
"_do_rxc:" "\n\t" // We start out 11-13 clocks after the interrupt
"push r18" "\n\t" // r30 and r31 pushed before this.
"in r18, 0x3f" "\n\t" // Save SREG
"push r18" "\n\t" //
"push r19" "\n\t" // Ugh we needed another register....
"push r24" "\n\t" //
"push r25" "\n\t" //
"push r28" "\n\t" //
"push r29" "\n\t" //
"ldd r28, Z + 8" "\n\t" // Load USART into Y pointer
// "ldd r29, Z + 9" "\n\t" // We interact with the USART only this once
"ldi r29, 0x08" "\n\t" // High byte always 0x08 for USART peripheral: Save-a-clock. 11 clocks to here
#if defined(ERRATA_USART_WAKE) // This bug appears to be near-universal, 4 clocks to workaround.
"ldd r18, Y + 6" "\n\t"
"andi r18, 0xEF" "\n\t"
"std Y + 6, r18" "\n\t" // turn off SFD interrupt before reading RXDATA so we don't corrupt the next character.
#endif
"ldd r24, Y + 1" "\n\t" // Y + 1 = USARTn.RXDATAH - load high byte first - 16 clocks from here to next
"ld r25, Y" "\n\t" // Y + 0 = USARTn.RXDATAL - then low byte of RXdata
"andi r24, 0x46" "\n\t" // extract framing, parity bits.
"lsl r24" "\n\t" // leftshift them one place
"ldd r19, Z + 12" "\n\t" // load _state
"or r19, r24" "\n\t" // bitwise or with errors extracted from _state
"sbrc r24, 2" "\n\t" // if there's a parity error, then do nothing more (note the leftshift).
"rjmp _end_rxc" "\n\t" // Copies the behavior of stock implementation - framing errors are ok, apparently...
//#if USE_ASM_TXC == 2 && USE_ASM_RXC == 2
//"sbic 0x1F, 0" "\n\t"
//"sbi 0x01, 2" "\n\t"
//"sbrs r19, 4" "\n\t" // Is there an echo in here?
//"rjmp storechar" "\n\t" // if not skip these next isns "\n\t"
//"sbic 0x1F, 0" "\n\t"
//"sbi 0x02, 4" "\n\t"
//"andi r19, 0xEF" "\n\t" // clear t
//"rjmp _end_rxc" "\n\t"
// "storechar:"
//#endif
"ldd r28, Z + 13" "\n\t" // load current head index
"ldi r24, 1" "\n\t" // Clear r24 and initialize it with 1
"add r24, r28" "\n\t" // add current head index to it
#if SERIAL_RX_BUFFER_SIZE == 256
// No additional action needed, head wraps naturally.
#elif SERIAL_RX_BUFFER_SIZE == 128
"andi r24, 0x7F" "\n\t" // Wrap the head around
#elif SERIAL_RX_BUFFER_SIZE == 64
"andi r24, 0x3F" "\n\t" // Wrap the head around
#elif SERIAL_RX_BUFFER_SIZE == 32
"andi r24, 0x1F" "\n\t" // Wrap the head around
#elif SERIAL_RX_BUFFER_SIZE == 16
"andi r24, 0x0F" "\n\t" // Wrap the head around
#endif
"ldd r18, Z + 14" "\n\t" // load tail index This to _end_rxc is 11 clocks unless the buffer was full, in which case it's 8.
"cp r18, r24" "\n\t" // See if head is at tail. If so, buffer full. The incoming data is discarded,
"breq _buff_full_rxc" "\n\t" // because there is noplace to put it, and we just restore state and leave.
"add r28, r30" "\n\t" // r28 has what would be the next index in it.
"mov r29, r31" "\n\t" // and this is the high byte of serial instance
"ldi r18, 0" "\n\t" // need a known zero to carry.
"adc r29, r18" "\n\t" // carry - Y is now pointing 17 bytes before head
"std Y + 17, r25" "\n\t" // store the new char in buffer
"std Z + 13, r24" "\n\t" // write that new head index.
"_end_rxc:" "\n\t"
"std Z + 12, r19" "\n\t" // record new state including new errors
// Epilogue: 9 pops + 1 out + 1 reti +1 std = 24 clocks
"pop r29" "\n\t" // Y Pointer was used for head and usart.
"pop r28" "\n\t" //
"pop r25" "\n\t" // r25 held the received character
"pop r24" "\n\t" // r24 held rxdatah, then the new head.
"pop r19" "\n\t" // restore r19 which held the value that State will have after this.
"pop r18" "\n\t" // Restore saved SREG
"out 0x3f, r18" "\n\t" // and write back
"pop r18" "\n\t" // used as tail offset, and then as known zero.
"pop r31" "\n\t" // end with Z which the isr pushed to make room for
"pop r30" "\n\t" // pointer to serial instance
"reti" "\n\t" // return
"_buff_full_rxc:" "\n\t" // potential improvement: move _buff_full_rxc to after the reti, and then rjmp back, saving 2 clocks for the common case
"ori r19, 0x40" "\n\t" // record that there was a ring buffer overflow. 1 clk
"rjmp _end_rxc" "\n\t" // and now jump back to end. That way we don't need to jump over this in the middle of the common case.
::); // total: 77 or 79 clocks, just barely squeaks by for cyclic RX of up to RX_BUFFER_SIZE characters.
__builtin_unreachable();
}
#elif defined(USE_ASM_RXC) && USE_ASM_RXC == 1
#warning "USE_ASM_RXC is defined and this has more than one serial port, but the buffer size is not supported, falling back to the classical RXC."
#else
#if defined(PERMIT_USART_WAKE)
#error "USART Wake is not supported by the non-ASM RXC interrupt handler"
#endif
void HardwareSerial::_rx_complete_irq(HardwareSerial& HardwareSerial) {
// if (bit_is_clear(*_rxdatah, USART_PERR_bp)) {
uint8_t rxDataH = HardwareSerial._hwserial_module->RXDATAH;
uint8_t c = HardwareSerial._hwserial_module->RXDATAL; // no need to read the data twice. read it, then decide what to do
rx_buffer_index_t rxHead = HardwareSerial._rx_buffer_head;
if (!(rxDataH & USART_PERR_bm)) {
// No Parity error, read byte and store it in the buffer if there is room
// unsigned char c = HardwareSerial._hwserial_module->RXDATAL;
#if SERIAL_RX_BUFFER_SIZE > 256
rx_buffer_index_t i = (uint16_t)(rxHead + 1) % SERIAL_RX_BUFFER_SIZE;
#else
rx_buffer_index_t i = (uint8_t)(rxHead + 1) % SERIAL_RX_BUFFER_SIZE;
#endif
// if we should be storing the received character into the location
// just before the tail (meaning that the head would advance to the
// current location of the tail), we're about to overflow the buffer
// and so we don't write the character or advance the head.
if (i != HardwareSerial._rx_buffer_tail) {
HardwareSerial._rx_buffer[rxHead] = c;
HardwareSerial._rx_buffer_head = i;
}
}
}
#endif
/*
DRE starts just like RXC
ISR(USART0_DRE_vect, ISR_NAKED) {
__asm__ __volatile__(
push r30
push r31 "\n\t"
:::);
__asm__ __volatile__(
"rjmp do_dre" "\n\t"
::"z"(&Serialn));
__builtin_unreachable();
*/
#if USE_ASM_DRE == 1 && (SERIAL_RX_BUFFER_SIZE == 256 || SERIAL_RX_BUFFER_SIZE == 128 || SERIAL_RX_BUFFER_SIZE == 64 || SERIAL_RX_BUFFER_SIZE == 32 || SERIAL_RX_BUFFER_SIZE == 16) && \
(SERIAL_TX_BUFFER_SIZE == 256 || SERIAL_TX_BUFFER_SIZE == 128 || SERIAL_TX_BUFFER_SIZE == 64 || SERIAL_TX_BUFFER_SIZE == 32 || SERIAL_TX_BUFFER_SIZE == 16)
void __attribute__((naked)) __attribute__((used)) __attribute__((noreturn)) _do_dre(void) {
__asm__ __volatile__(
"_do_dre:" "\n\t"
"push r18" "\n\t"
"in r18, 0x3F" "\n\t"
"push r18" "\n\t"
"push r24" "\n\t"
"push r25" "\n\t"
"push r26" "\n\t"
"push r27" "\n\t"
"set" "\n\t" // SEt the T flag - we use this to determine how we got here and hence whether to rjmp to end of poll or reti
"_poll_dre:" "\n\t"
"push r28" "\n\t"
"push r29" "\n\t"
"ldi r18, 0" "\n\t"
"ldd r28, Z + 8" "\n\t" // usart in Y
// "ldd r29, Z + 9" "\n\t" // usart in Y
"ldi r29, 0x08" "\n\t" // High byte always 0x08 for USART peripheral: Save-a-clock.
"ldd r25, Z + 16" "\n\t" // tx tail in r25
"movw r26, r30" "\n\t" // copy of serial in X
"add r26, r25" "\n\t" // SerialN + txtail
"adc r27, r18" "\n\t" // X = &Serial + txtail
#if SERIAL_RX_BUFFER_SIZE == 256 // RX buffer determines offset from start of class to TX buffer
"subi r26, 0xEF" "\n\t" // There's no addi/adci, so we instead subtract (65536-(offset we want to add))
"sbci r27, 0xFE" "\n\t" // +273
"ld r24, X" "\n\t" // grab the character
#elif SERIAL_RX_BUFFER_SIZE == 128
"subi r26, 0x6F" "\n\t" //
"sbci r27, 0xFF" "\n\t" // +145
"ld r24, X" "\n\t" // grab the character
#elif SERIAL_RX_BUFFER_SIZE == 64
"subi r26, 0xAF" "\n\t" //
"sbci r27, 0xFF" "\n\t" // +81
"ld r24, X" "\n\t" // grab the character
#elif SERIAL_RX_BUFFER_SIZE == 32
"adiw r26, 0x31" "\n\t" // +49
"ld r24, X" "\n\t" // grab the character
#elif SERIAL_RX_BUFFER_SIZE == 16
"adiw r26, 0x21" "\n\t" // +33
"ld r24, X" "\n\t" // grab the character
#endif
"ldi r18, 0x40" "\n\t"
"std Y + 4, r18" "\n\t" // Y + 4 = USART.STATUS - clear TXC
"std Y + 2, r24" "\n\t" // Y + 2 = USART.TXDATAL - write char
"subi r25, 0xFF" "\n\t" // txtail +1
#if SERIAL_TX_BUFFER_SIZE == 256
// // No action needed to wrap the tail around -
#elif SERIAL_TX_BUFFER_SIZE == 128
"andi r25, 0x7F" "\n\t" // Wrap the tail around
#elif SERIAL_TX_BUFFER_SIZE == 64
"andi r25, 0x3F" "\n\t" // Wrap the tail around
#elif SERIAL_TX_BUFFER_SIZE == 32
"andi r25, 0x1F" "\n\t" // Wrap the tail around
#elif SERIAL_TX_BUFFER_SIZE == 16
"andi r25, 0x0F" "\n\t" // Wrap the tail around
#endif
"ldd r24, Y + 5" "\n\t" // Y + 5 = USART.CTRLA - get CTRLA into r24
"ldd r18, Z + 15" "\n\t" // txhead into r18
"cpse r18, r25" "\n\t" // if they're the same
"rjmp _done_dre_irq" "\n\t"
"andi r24, 0xDF" "\n\t" // DREIE off
"std Y + 5, r24" "\n\t" // write new ctrla
"_done_dre_irq:" "\n\t" // Beginning of the end of DRE
"std Z + 16, r25" "\n\t" // store new tail
"pop r29" "\n\t" // pop Y
"pop r28" "\n\t" // finish popping Y
#if PROGMEM_SIZE > 8192
"brts .+4" "\n\t" // hop over the next insn if T bit set, means entered through do_dre, rather than poll_dre
"jmp _poll_dre_done" "\n\t" // >8k parts must us jmp, otherwise it will give PCREL error.
#else
"brts .+2" "\n\t" // hop over the next insn if T bit set, means entered through do_dre, rather than poll_dre
"rjmp _poll_dre_done" "\n\t" // 8k parts can use RJMP
#endif
"pop r27" "\n\t" // and continue with popping registers. 21 clocks left
"pop r26" "\n\t"
"pop r25" "\n\t"
"pop r24" "\n\t"
"pop r18" "\n\t" // pop SREG value from stack
"out 0x3f, r18" "\n\t" // restore SREG
"pop r18" "\n\t" // pop old r18
"pop r31" "\n\t" // pop the Z that the isr pushed.
"pop r30" "\n\t"
"reti" "\n" // and RETI!
::);
__builtin_unreachable();
}
#elif USE_ASM_DRE == 1
#warning "USE_ASM_DRE == 1, but the buffer sizes are not supported, falling back to the classical DRE."
#else
void HardwareSerial::_tx_data_empty_irq(HardwareSerial& HardwareSerial) {
USART_t* usartModule = (USART_t*)HardwareSerial._hwserial_module; // reduces size a little bit
tx_buffer_index_t txTail = HardwareSerial._tx_buffer_tail;
// Check if tx buffer already empty. when called by _poll_tx_data_empty()
// if (HardwareSerial._tx_buffer_head == txTail) {
// Buffer empty, so disable "data register empty" interrupt
// usartModule->CTRLA &= (~USART_DREIE_bm);
// return;
//} // moved to poll function to make ISR smaller and faster
// There must be more data in the output
// buffer. Send the next byte
uint8_t c = HardwareSerial._tx_buffer[txTail];
// clear the TXCIF flag -- "can be cleared by writing a one to its bit
// location". This makes sure flush() won't return until the bytes
// actually got written. It is critical to do this BEFORE we write the next byte
usartModule->STATUS = USART_TXCIF_bm;
usartModule->TXDATAL = c;
txTail = (txTail + 1) & (SERIAL_TX_BUFFER_SIZE - 1); //% SERIAL_TX_BUFFER_SIZE;
uint8_t ctrla = usartModule->CTRLA;
if (HardwareSerial._tx_buffer_head == txTail) {
// Buffer empty, so disable "data register empty" interrupt
ctrla &= ~(USART_DREIE_bm);
usartModule->CTRLA = ctrla;
}
HardwareSerial._tx_buffer_tail = txTail;
}
#endif
// To invoke data empty "interrupt" via a call, use this method
void HardwareSerial::_poll_tx_data_empty(void) {
if ((!(SREG & CPU_I_bm)) || CPUINT.STATUS) {
// We're here because we're waiting for space in the buffer *or* we're in flush
// and waiting for the last byte to leave, yet we're either in an ISR, or
// interrupts are disabled so the ISR can't fire on it's own.
//
// Interrupts are disabled either globally or for data register empty,
// or we are in another ISR. (It doesn't matter *which* ISR we are in
// whether it's another level 0, the priority one, or heaven help us
// the NMI, if the user code says to print something or flush the buffer
// we might as well do it. It is entirely plausible that an NMI might
// attempt to print out some sort of record of what happened.
//
// so we'll have to poll the "data register empty" flag ourselves.
// If it is set, pretend an interrupt has happened and call the handler
// to free up space for us.
// -Spence 10/23/20
// Invoke interrupt handler only if conditions data register is empty
if ((*_hwserial_module).STATUS & USART_DREIF_bm) {
if (_tx_buffer_head == _tx_buffer_tail) {
// Buffer empty, so disable "data register empty" interrupt
(*_hwserial_module).CTRLA &= (~USART_DREIE_bm);
return;
}
#if !(USE_ASM_DRE == 1 && (SERIAL_RX_BUFFER_SIZE == 256 || SERIAL_RX_BUFFER_SIZE == 128 || SERIAL_RX_BUFFER_SIZE == 64 || SERIAL_RX_BUFFER_SIZE == 32 || SERIAL_RX_BUFFER_SIZE == 16) && \
(SERIAL_TX_BUFFER_SIZE == 256 || SERIAL_TX_BUFFER_SIZE == 128 || SERIAL_TX_BUFFER_SIZE == 64 || SERIAL_TX_BUFFER_SIZE == 32 || SERIAL_TX_BUFFER_SIZE == 16))
_tx_data_empty_irq(*this);
#else // We're using ASM DRE
#ifdef USART1
void * thisSerial = this;
#endif
__asm__ __volatile__(
"clt" "\n\t" // Clear the T flag to signal to the ISR that we got there from here. This is safe per the ABI - The T-flag can be treated like R0
#if PROGMEM_SIZE > 8192
"jmp _poll_dre" "\n\t"
#else
"rjmp _poll_dre" "\n\t"
#endif
"_poll_dre_done:" "\n"
#ifdef USART1
::"z"((uint16_t)thisSerial)
#else
::"z"(&Serial0)
#endif
: "r18","r19","r24","r25","r26","r27"); // these got saved and restored in the ISR context, but here we don't need top and in many cases no action is needed.
// the Y pointer was already handled, because as a call-saved register, it would always need to be saved and restored, so we save 4 words of flash by doing that after
// jumps into the middle of the ISR, and before it jumps back here.
#endif
}
}
// In case interrupts are enabled, the interrupt routine will be invoked by itself
// Note that this currently does not handle cases where the DRE interruopt becomes
// disabled, yet you are actually attempting to send. I don't think it can happen.
}
/*### # # #### # ### ### # # #### ##### # # ### #### ###
# # # # # # # # # ## ## # # # # # # # # #
#### # # #### # # # # # # ### # ##### # # # # ###
# # # # # # # # # # # # # # # # # # #
# ### #### #### ### ### # # #### # # # ### #### ##*/
// Invoke this function before 'begin' to define the pins used
bool HardwareSerial::pins(uint8_t tx, uint8_t rx) {
uint8_t ret_val = _pins_to_swap(_module_number, tx, rx); // return 127 when correct swap number wasn't found
return swap(ret_val);
}
uint8_t HardwareSerial::_pins_to_swap(uint8_t port_num, uint8_t tx_pin, uint8_t rx_pin) {
if (tx_pin == NOT_A_PIN && rx_pin == NOT_A_PIN) {
return 128; // get MUX_NONE
} else {
const uint8_t * muxtab_ptr = _usart_pins[port_num];
if (*muxtab_ptr == tx_pin && (*(muxtab_ptr + 1) == rx_pin)) {
return 0;
}
#if !defined(__AVR_ATtinyx24__)
if ((*(muxtab_ptr + 4)) == tx_pin && (*(muxtab_ptr + 5) == rx_pin))
#else
if (port_num && (*(muxtab_ptr + 4)) == tx_pin && (*(muxtab_ptr + 5) == rx_pin))
#endif
{
return 1;
}
return NOT_A_MUX; // At this point, we have checked all group codes for this peripheral. It ain't there. Return NOT_A_MUX.
}
}
uint8_t HardwareSerial::getPin(uint8_t pin) {
if (pin >3) return NOT_A_PIN;
return (_usart_pins[_module_number + _pin_set][pin]);
}
bool HardwareSerial::swap(uint8_t newmux) {
#if !(MEGATINYCORE_SERIES == 2 && defined(__ATtinyxy4__))
// it's either a 0/1-series: They have options of 0 and 1.
// Or it's a 2-series with 20 or 24 pins. Both USARTS have option of 0 & 1.
if (newmux < 2) {
_pin_set = newmux;
return true;
}
#else
// means it is a 14-pin 2-series, whose second USART doesn't have an alternate location.
if (_module_number + newmux < 2) {
_pin_set = newmux;
return true;
}
#endif
#if MEGATINYCORE_SERIES == 2
else if (newmux == MUX_NONE) { // 128 codes for MUX_NONE
_pin_set = 3;
return true;
}
#endif
else {
_pin_set = 0;
}
return false;
}
void HardwareSerial::begin(unsigned long baud, uint16_t options) {
// Make sure no transmissions are ongoing and USART is disabled in case begin() is called by accident
// without first calling end()
if (_state & 1) {
this->end();
}
uint8_t ctrlc = (uint8_t) options;
if (ctrlc == 0) { // see if they passed anything in low byte or SERIAL_CONFIG_VALID.
ctrlc = (uint8_t)SERIAL_8N1; // low byte of 0 could mean they want SERIAL_5N1. Or that they thought they'd
}
ctrlc &= ~0x04; // Now unset that 0x04 bit if it's set, because none of the values with it set are supported. We use that to smuggle in a "this constant was specified" for 5N1
uint8_t ctrla = (uint8_t) (options >> 8);// CTRLA will get the remains of the options high byte.
uint16_t baud_setting = 0; // at this point it should be able to reuse those 2 registers that it received options in!
uint8_t ctrlb = (~ctrla & 0xC0); // Top two bits (TXEN RXEN), inverted so they match he sense in the registers.
if (baud > F_CPU / 16) { // if this baud is too fast for non-U2X
ctrlb |= USART_RXMODE0_bm; // set the U2X bit in what will become CTRLB
baud >>= 1; // And lower the baud rate by haldf
}
baud_setting = (((4 * F_CPU) / baud)); // And now the registers that baud was passed in are done.
if (baud_setting < 64) // so set to the maximum baud rate setting.
baud_setting= 64; // set the U2X bit in what will become CTRLB
//} else if (baud < (F_CPU / 16800)) { // Baud rate is too low
// baud_setting = 65535; // minimum baud rate.'
// Baud setting done now we do the other options not in CTRLC;
if (ctrla & 0x04) { // is ODME option set?
ctrlb |= USART_ODME_bm; // set the bit in what will become CTRLB
}
// Baud setting done now we do the other options.
// that aren't in CTRLC;
ctrla &= 0x2B; // Only LBME and RS485 (both of them); will get written to CTRLA, but we leave the event bit.
if (ctrlb & USART_RXEN_bm) { // if RX is to be enabled
ctrla |= USART_RXCIE_bm; // we will want to enable the ISR.
}
uint8_t setpinmask = ctrlb & 0xC8; // ODME in bit 3, TX and RX enabled in bit 6, 7
if ((ctrla & USART_LBME_bm) && (setpinmask == 0xC8)) { // if it's open-drain and loopback, need to set state bit 2.
_state |= 2; // since that changes some behavior (RXC disabled while sending) // Now we should be able to ST _state.
setpinmask |= 0x10; // this tells _set_pins not to disturb the configuration on the RX pin.
}
if (ctrla & USART_RS485_bm) { // RS485 mode recorded here too... because we need to set
setpinmask |= 0x01; // set pin output if we need to do that. Datasheet isn't clear
}
uint8_t oldSREG = SREG;
cli();
volatile USART_t* MyUSART = _hwserial_module;
(*MyUSART).CTRLB = 0; // gotta disable first - some things are enable-locked.
(*MyUSART).CTRLC = ctrlc; // No reason not to set first.
(*MyUSART).BAUD = baud_setting; // Wish I could have set it long ago
if (ctrla & 0x20) { // Now we have to do a bit of work
setpinmask &= 0x7F; // Remove the RX pin in this case because we get the input from elsewhere.
(*MyUSART).EVCTRL = 1; // enable event input - not clear from datasheet what's needed to
(*MyUSART).TXPLCTRL = 0xFF; // Disable pulse length encoding.
} else {
(*MyUSART).EVCTRL = 0; // This needs to be turned off when not in use.
} // finally strip out the SERIAL_EVENT_RX bit which is in the DREIE
(*MyUSART).CTRLA = ctrla & 0xDF; // position, which we never set in begin.
(*MyUSART).CTRLB = ctrlb; // Set the all important CTRLB...
_set_pins(_module_number, _pin_set, setpinmask); // set up the pin(s)
SREG = oldSREG; // re-enable interrupts, and we're done.
}
void HardwareSerial::_set_pins(uint8_t mod_nbr, uint8_t mux_set, uint8_t enmask) {
// Set the mux register
#if defined(PORTMUX_USARTROUTEA)
uint8_t muxregval = PORTMUX.USARTROUTEA;
muxregval &= ~(mod_nbr ? 0x0C : 0x03);
PORTMUX.USARTROUTEA = (muxregval) | (mux_set << (mod_nbr ? 2 : 0)); // shift muxset left if needed.
#else
if (mux_set) {
PORTMUX.CTRLB |= 0x01; // for 0/1-series this can only be zero or 1
} else {
PORTMUX.CTRLB &= 0xFE;
}
#endif
#if MEGATINYCORE_SERIES == 2
if (mux_set == 3) { // not connected to pins...
return; // so we are done!
}
#endif
const uint8_t* muxrow = &(_usart_pins[mod_nbr + mux_set][0]);
if ((enmask & 0x40 && !(enmask & 0x08))) {
pinMode(muxrow[0], OUTPUT); // If and only if TX is enabled and open drain isn't should the TX interrupt be used. .
} else if (enmask & 0x50) { // if it is enabled but is in open drain mode, or is disabled, but loopback is enabled
// TX should be INPUT_PULLUP.
pinMode(muxrow[0], INPUT_PULLUP);
}
if (enmask & 0x80 && !(enmask & 0x10)) {
// Likewise if RX is enabled, unless loopback mode is too (in which case we caught it above, it should be pulled up
pinMode(muxrow[1], INPUT_PULLUP);
}
if (enmask & 0x01) { // finally if RS485 mode is enabled, we make XDIR output, otherwise it can't drive the pin.
pinMode(muxrow[3], OUTPUT); // make XDIR output.
}
/*
uint8_t muxrow = mod_nbr + mux_set;
if ((enmask & 0x40 && !(enmask & 0x08))) {
pinMode(_usart_pins[muxrow][0], OUTPUT); // If any only if TX is enabled and open drain isn't should the TX pin be output.
} else if (enmask & 0x50) { // if it is enabled but is in open drain mode, or is disabled, but loopback is enabled
// TX should be INPUT_PULLUP.
pinMode(_usart_pins[muxrow][0], INPUT_PULLUP);
}
if (enmask & 0x80 && !(enmask & 0x10)) {
// Likewise if RX is enabled, unless loopback mode is too (in which case we caught it above, it should be pulled up
pinMode(_usart_pins[muxrow][1], INPUT_PULLUP);
}
if (enmask & 0x01) { // finally if RS485 mode is enabled, we make XDIR output, otherwise it can't drive the pin.
pinMode(_usart_pins[muxrow][3], OUTPUT); // make XDIR output.
}
// And it is up to the user to configure the XCK pin as required for their application if they are using that.
*/
/*
uint8_t muxrow = mod_nbr + mux_set;
if (enmask & 0x40) { // tx enabled
pinMode(_usart_pins[muxrow][0], (enmask & 0x08) ? INPUT_PULLUP : OUTPUT);
}
if (enmask & 0x80) {
if (!(enmask & 0x10)) {
pinMode(_usart_pins[muxrow][1], INPUT_PULLUP);
} else if (!(enmask & 0x40)) { // Loopback mode set, TX disabled, and RX enabled. Wacky configuration, but I guess that means TX should be INPUT_PULLUP.
pinMode(_usart_pins[muxrow][0], INPUT_PULLUP);
}
}
if (enmask & 0x01) { // RS485 enabled
pinMode(_usart_pins[muxrow][3], OUTPUT); // make XDIR output.
}
*/
}
void HardwareSerial::end() {
// wait for transmission of outgoing data
flush();
// Disable receiver and transmitter as well as the RX complete and the data register empty interrupts.
// TXCIE only used in half duplex - we can just turn the damned thing off yo!
volatile USART_t * temp = _hwserial_module; /* compiler does a slightly better job with this. */
temp -> CTRLB = 0; //~(USART_RXEN_bm | USART_TXEN_bm);
temp -> CTRLA = 0; //~(USART_RXCIE_bm | USART_DREIE_bm | USART_TXCIE_bm);
temp -> STATUS = USART_TXCIF_bm | USART_RXCIF_bm; // want to make sure no chance of that firing in error now that the USART is off. TXCIE only used in half duplex
// clear any received data
_rx_buffer_head = _rx_buffer_tail;
// Note: Does not change output pins
// though the datasheetsays turning the TX module off sets it to input.
_state = 0;
}
int HardwareSerial::available(void) {
return ((unsigned int)(SERIAL_RX_BUFFER_SIZE + _rx_buffer_head - _rx_buffer_tail)) & (SERIAL_RX_BUFFER_SIZE - 1); //% SERIAL_RX_BUFFER_SIZE;
}
int HardwareSerial::peek(void) {
if (_rx_buffer_head == _rx_buffer_tail) {
return -1;
} else {
return _rx_buffer[_rx_buffer_tail];
}
}
int HardwareSerial::read(void) {
// if the head isn't ahead of the tail, we don't have any characters
if (_rx_buffer_head == _rx_buffer_tail) {
return -1;
} else {
unsigned char c = _rx_buffer[_rx_buffer_tail];
_rx_buffer_tail = (rx_buffer_index_t)(_rx_buffer_tail + 1) & (SERIAL_RX_BUFFER_SIZE - 1); // % SERIAL_RX_BUFFER_SIZE;
return c;
}
}
int HardwareSerial::availableForWrite(void) {
tx_buffer_index_t head;
tx_buffer_index_t tail;
TX_BUFFER_ATOMIC {
head = _tx_buffer_head;
tail = _tx_buffer_tail;
}
if (head >= tail) {
return SERIAL_TX_BUFFER_SIZE - 1 - head + tail;
}
return tail - head - 1;
}
void HardwareSerial::flush() {
// If we have never written a byte, no need to flush. This special
// case is needed since there is no way to force the TXCIF (transmit
// complete) bit to 1 during initialization
if (!_state & 1) {
return;
}
// Check if we are inside an ISR already (e.g. connected to a different peripheral then UART), in which case the UART ISRs will not be called.
// Spence 10/23/20: Changed _poll_tx_data_empty() to instead call the ISR directly in this case too
// Why elevate the interrupt if we're going to go into a busywait loop checking if the interrupt is disabled and if so, check for the bit and
// manually call the ISR if the bit is set... *anyway*? Plus, in write(), this mode will be enabled upon a write of a single character from an ISR
// and will stay that way until the buffer is empty, which would mean that the fairly long and slow UART TX ISR would have priority over a
// potentially very fast interrupt that the user may have set to priority level 1. Just because a whizz-bang feature is there doesn't mean
// it's appropriate to use for applications where it has only very small benefits, and significant risk of surprising the user and causing
// breakage of code that would otherwise work. Finally, the previous implementation didn't check if it was called from the current lvl1 ISR
// and in that case flush(), and write() with full buffer would just straight up hang...
// Spin until the data-register-empty-interrupt is disabled and TX complete interrupt flag is raised
while (((*_hwserial_module).CTRLA & USART_DREIE_bm) || (!((*_hwserial_module).STATUS & USART_TXCIF_bm))) {
// If interrupts are globally disabled or the and DR empty interrupt is disabled,
// poll the "data register empty" interrupt flag to prevent deadlock
_poll_tx_data_empty();
}
// When we get here, nothing is queued anymore (DREIE is disabled) and
// the hardware finished transmission (TXCIF is set).
}
size_t HardwareSerial::write(uint8_t c) {
_state |= 1; // Record that we have written to serial since it was begun.
// If the buffer and the data register is empty, just write the byte
// to the data register and be done. This shortcut helps
// significantly improve the effective data rate at high (>
// 500kbit/s) bit rates, where interrupt overhead becomes a slowdown.
if ((_tx_buffer_head == _tx_buffer_tail) && ((*_hwserial_module).STATUS & USART_DREIF_bm)) {
if (_state & 2) { // in half duplex mode, we turn off RXC interrupt
uint8_t ctrla = (*_hwserial_module).CTRLA;
ctrla &= ~USART_RXCIE_bm;
ctrla |= USART_TXCIE_bm;
(*_hwserial_module).STATUS = USART_TXCIF_bm;
(*_hwserial_module).CTRLA = ctrla;
} else {
(*_hwserial_module).STATUS = USART_TXCIF_bm;
}
// MUST clear TXCIF **before** writing new char, otherwise ill-timed interrupt can cause it to erase the flag after the new charchter has been sent!
(*_hwserial_module).TXDATAL = c;
/* I cannot figure out *HOW* the DRE could be enabled at this point (buffer empty and DRE flag up)
* When the buffer was emptied, it would have turned off the DREI after it loaded the last byte.
* Thus, the only possible way this could happen is if an interrupt also tried to write to serial,
* *immediately* after we checked that the buffer was empty, before we made it not empty. And
* in that case, without this line it would lose one of the characters... with that line, it could
* stop servicing DRE until another serial write, AND lose a character. That's not better! -Spence 4/2021
* So this is to stop a race condition in which people are doing something that every guide everywhere says not to do
* (writing serial from within an ISR).
* I maintain that users SHOULD NOT WRITE TO SERIAL FROM AN ISR, and certainly not while the non-interrupt code is also writing!
* Original comments:
* // Make sure data register empty interrupt is disabled to avoid
* // that the interrupt handler is called in this situation
* (*_hwserial_module).CTRLA &= (~USART_DREIE_bm);
*/
return 1;
}
tx_buffer_index_t i = (_tx_buffer_head + 1) & (SERIAL_TX_BUFFER_SIZE - 1); // % SERIAL_TX_BUFFER_SIZE;
// If the output buffer is full, there's nothing we can do other than to
// wait for the interrupt handler to empty it a bit (or emulate interrupts)
while (i == _tx_buffer_tail) {
_poll_tx_data_empty();
}
_tx_buffer[_tx_buffer_head] = c;
_tx_buffer_head = i;
if (_state & 2) { // in half duplex mode, we turn off RXC interrupt
uint8_t ctrla = (*_hwserial_module).CTRLA;
ctrla &= ~USART_RXCIE_bm;
ctrla |= USART_TXCIE_bm | USART_DREIE_bm;
(*_hwserial_module).STATUS = USART_TXCIF_bm;
(*_hwserial_module).CTRLA = ctrla;
} else {
// Enable "data register empty interrupt"
(*_hwserial_module).CTRLA |= USART_DREIE_bm;
}
return 1;
}
void HardwareSerial::printHex(const uint8_t b) {
char x = (b >> 4) | '0';
if (x > '9')
x += 7;
write(x);
x = (b & 0x0F) | '0';
if (x > '9')
x += 7;
write(x);
}
void HardwareSerial::printHex(const uint16_t w, bool swaporder) {
uint8_t *ptr = (uint8_t *) &w;
if (swaporder) {
printHex(*(ptr++));
printHex(*(ptr));
} else {
printHex(*(ptr + 1));
printHex(*(ptr));
}
}
void HardwareSerial::printHex(const uint32_t l, bool swaporder) {
uint8_t *ptr = (uint8_t *) &l;
if (swaporder) {
printHex(*(ptr++));
printHex(*(ptr++));
printHex(*(ptr++));
printHex(*(ptr));
} else {
ptr+=3;
printHex(*(ptr--));
printHex(*(ptr--));
printHex(*(ptr--));
printHex(*(ptr));
}
}
uint8_t * HardwareSerial::printHex(uint8_t* p, uint8_t len, char sep) {
for (byte i = 0; i < len; i++) {
if (sep && i) write(sep);
printHex(*p++);
}
println();
return p;
}
uint16_t * HardwareSerial::printHex(uint16_t* p, uint8_t len, char sep, bool swaporder) {
for (byte i = 0; i < len; i++) {
if (sep && i) write(sep);
printHex(*p++, swaporder);
}
println();
return p;
}
volatile uint8_t * HardwareSerial::printHex(volatile uint8_t* p, uint8_t len, char sep) {
for (byte i = 0; i < len; i++) {
if (sep && i) write(sep);
uint8_t t = *p++;
printHex(t);
}
return p;
}
volatile uint16_t * HardwareSerial::printHex(volatile uint16_t* p, uint8_t len, char sep, bool swaporder) {
for (byte i = 0; i < len; i++) {
if (sep && i) write(sep);
uint16_t t = *p++;
printHex(t, swaporder);
}
return p;
}
#endif
#endif