-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathuart.c
194 lines (147 loc) · 4.18 KB
/
uart.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
#include "uart.h"
#include <stdint.h>
#include <stddef.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#define SET(reg, pos) (reg |= 1<<(pos))
#define FLP(reg, pos) (reg ^= 1<<(pos))
#define CLR(reg, pos) (reg &= ~(1<<(pos)))
#define GET(reg, pos) (reg & 1<<(pos))
/* TX queue ring buffer */
static volatile uint8_t txqueue[UART_BUFFER_SIZE];
static volatile size_t txqueue_r;
static volatile size_t txqueue_w;
static volatile size_t txqueue_len;
/* TX shift register */
static volatile uint16_t txframe;
/**
* Prepare c for uart transmission
*/
static inline void uart_set_txframe(const uint8_t c)
{
/* Calculate parity if needed */
#ifdef UART_PARITY
uint8_t parity = UART_PARITY;
for(uint8_t _c = c; _c; _c>>=1)
parity ^= _c&1;
#endif
/* MARK . STOP | (PARITY) | DATA | START */
#ifdef UART_PARITY
txframe = (0x03<10) | (parity<<9) | ((uint16_t) c<<1) | 0;
#else
txframe = (0x03<<9) | ((uint16_t) c<<1) | 0;
#endif
}
/**
* Timer1A interrupt at BAUDRATE
*/
ISR(TIM1_COMPA_vect)
{
if(txframe) {
/* Write current bit */
if(txframe & 1) SET(UART_PORT, UART_TX_BIT);
else CLR(UART_PORT, UART_TX_BIT);
txframe >>= 1;
if(! (txframe || txqueue_len)) {
/* Nothing to send (in the next ISR),
* disable interrupts */
CLR(TIMSK, OCIE1A);
}
}else if(txqueue_len) {
/* Prepare next frame */
uart_set_txframe(txqueue[txqueue_r++]);
txqueue_r %= UART_BUFFER_SIZE;
txqueue_len--;
}
}
/**
* Queue a character for uart transmission (does not start Timer1 ISR)
*/
static void uart_queue(const char c)
{
uint8_t sreg;
/* Wait until queue is empty enough */
while(txqueue_len == UART_BUFFER_SIZE);
sreg = SREG;
cli();
txqueue[txqueue_w++] = c;
txqueue_w %= UART_BUFFER_SIZE;
txqueue_len++;
SREG = sreg;
}
/**
* Try to queue a character for uart transmission (does not start Timer1 ISR)
* @return UART_BUSY when busy
*/
static uart_error_t uart_try_queue(const char c)
{
uint8_t sreg;
/* Check if queue is full */
if(txqueue_len == UART_BUFFER_SIZE) return UART_BUSY;
sreg = SREG;
cli();
txqueue[txqueue_w++] = c;
txqueue_w %= UART_BUFFER_SIZE;
txqueue_len++;
SREG = sreg;
return UART_OK;
}
void uart_putc(const char c)
{
uart_queue(c);
/* Enable timer interrupt and clear flag */
SET(TIMSK, OCIE1A);
SET(TIFR, OCF1A);
sei();
}
uart_error_t uart_try_putc(const char c)
{
uart_error_t ret;
ret = uart_try_queue(c);
if(ret) return ret;
/* Enable timer interrupt */
SET(TIMSK, OCIE1A);
sei();
return UART_OK;
}
void uart_puts(const char *s, size_t len)
{
size_t maxlen = UART_BUFFER_SIZE - len;
/* Wait until queue is empty enough */
while(txqueue_len > maxlen);
while(len--) uart_queue(*s++);
/* Enable timer interrupt and clear flag */
SET(TIMSK, OCIE1A);
SET(TIFR, OCF1A);
sei();
}
uart_error_t uart_try_puts(const char *s, size_t len)
{
size_t maxlen = UART_BUFFER_SIZE - len;
/* Check if queue is empty enough */
if(txqueue_len > maxlen) return UART_BUSY;
while(len--) uart_queue(*s++);
/* Enable timer interrupt */
SET(TIMSK, OCIE1A);
sei();
return UART_OK;
}
void uart_init()
{
uint8_t sreg = SREG;
cli();
/* Set timer1 (CK) to CTC with apropiate divisor */
TCCR1 = _BV(CTC1) | UART_TIMER1_PRESCALE_BITS;
/* Set BAUDRATE clock divisor */
OCR1A = (uint8_t) ((uint32_t) UART_TIMER1_CK/UART_BAUDRATE)-1;
OCR1C = OCR1A;
/* Enable and pull TX Pin to HIGH */
SET(UART_DDR, UART_TX_BIT);
SET(UART_PORT, UART_TX_BIT);
txframe = 0;
txqueue_r = 0;
txqueue_w = 0;
txqueue_len = 0;
SET(TIFR, OCF1A);
SREG = sreg;
}