/
uartsw.c
413 lines (331 loc) · 10.9 KB
/
uartsw.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
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
/*
* $Id: uartsw.c,v 1.12 2010/12/02 19:11:04 clivewebster Exp $
*
* Revision History
* ================
* $Log: uartsw.c,v $
* Revision 1.12 2010/12/02 19:11:04 clivewebster
* Try to achieve higher and more reliable baud rates
*
* Revision 1.11 2010/07/01 23:56:37 clivewebster
* pin_make_output now specifies the initial output value
*
* Revision 1.10 2010/06/15 00:48:59 clivewebster
* Add copyright license info
*
* Revision 1.9 2010/02/21 19:54:21 clivewebster
* Allow software UART to use an 8 bit timer
*
* Revision 1.8 2010/02/17 23:57:58 clivewebster
* Specify timer number in MAKE command rather than a point to the TIMER
*
* Revision 1.7 2009/11/02 19:07:54 clivewebster
* Added revision log
*
* ===========
*
* Copyright (C) 2010 Clive Webster (webbot@webbot.org.uk)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* uartsw.c
*
* Created on: 30-Apr-2009
* Author: Clive Webster
*/
#include "timer.h"
#include "uartsw.h"
#include "core.h"
#include "errors.h"
// Program ROM constants
// Global variables
// functions
static void startXmitMode(UART* _uart);
static void endXmitMode(UART* _uart);
static void oneWireReceiveMode(const UART* _uart);
static void uartswTxBitService(const TimerCompare *channel, void* _uart)
{
SW_UART* uart = (SW_UART*) _uart;
if(uart->txBitNum)
{
// there are bits still waiting to be transmitted
if(uart->txBitNum > 1)
{
// transmit data bits (inverted, LSB first)
if( (uart->txData & 0x01) )
pin_high(uart->_uart_.tx_pin);
else
pin_low(uart->_uart_.tx_pin);
// shift bits down
uart->txData >>= 1;
}
else
{
// transmit stop bit
if(uart->inverted){
pin_low(uart->_uart_.tx_pin);
}else{
pin_high(uart->_uart_.tx_pin);
}
}
// schedule the next bit
uint16_t top = 1 + timerGetTOP(compareGetTimer(channel));
uint16_t next = (compareGetThreshold(channel) + uart->dataBitLength);
if(next >= top){
next -= top;
}
compareSetThreshold(channel, next );
// count down
uart->txBitNum--;
}
else
{
// We have finished sending the current byte - look for the next byte to send
__uartTransmitService(&uart->_uart_);
}
}
// Start receiving a new byte. Caused by a change on the input capture
static void uartswRxStartService(const Timer *timer, void* _uart){
SW_UART* uart = (SW_UART*)_uart;
// disable ICP interrupt - as remainder are done by OC1B
__portMaskClear(&timer->pgm_captureint);
// Get the timer value of when the interrupt happened
PORT icr = pgm_read_word(&timer->pgm_icr);
uint16_t capture = _SFR_MEM16(icr);
// attach RxBit service routine to OC1B for 1.5 bit from now
const TimerCompare* rx = timerGetCompare(timer,1);
compareSetThreshold(rx, (capture + uart->dataBitLength + uart->dataBitLength/2) );
compareClearInterruptPending(rx); // clear any interrupt pending
__portMaskSet(&rx->pgm_intenable); // turn interrupt on
// reset bit counter and data
uart->rxBitNum = uart->rxData = 0;
}
static void uartswRxBitService(const TimerCompare *channel, void* _uart){
SW_UART* uart = (SW_UART*)_uart;
// start bit has already been received
// we're in the data bits
// shift data byte to make room for new bit
uart->rxData >>= 1;
// sample the data line
if(uart->inverted){
if( pin_is_low(uart->_uart_.rx_pin) ){
uart->rxData |= 0x80;
}
}else{
if( pin_is_high(uart->_uart_.rx_pin) ){
uart->rxData |= 0x80;
}
}
// schedule next bit sample
compareSetThreshold(channel, (compareGetThreshold(channel) + uart->dataBitLength));
// check if we have a full byte
if(++uart->rxBitNum >= 8)
{
uint8_t c = uart->rxData;
// Detach the OC1B interrupt
// compareDetach(channel);
__portMaskClear(&channel->pgm_intenable); // turn interrupt off
// attach RxBit service routine to ICP
// trigger on rising edge if inverted, or falling if not
// timerCaptureAttach(compareGetTimer(channel), uartswRxStartService, uart, uart->inverted );
const Timer* timer = compareGetTimer(channel);
timerCaptureClearInterruptPending(timer); // Clear any pending capture interrupt flag
__portMaskSet(&timer->pgm_captureint); // Re-enable capture interrupt
// save data in receive buffer
__uartReceiveService(&uart->_uart_, c);
}
}
// Disable the uart
static void __uartswOff(UART * _uart){
SW_UART* uart = (SW_UART*) _uart;
const Timer* timer = &pgm_Timers[uart->timer];
// detach the service routines
compareDetach(timerGetCompare(timer,0));
compareDetach(timerGetCompare(timer,1));
timerCaptureDetach(timer);
}
static void setTxIdle(SW_UART* uart){
// set the output idle state of the transmitter
pin_make_output(uart->_uart_.tx_pin, (uart->inverted) ? FALSE : TRUE);
}
// enable and initialize the software uart - called with interrupts turned off
void __uartswInit(UART* _uart, BAUD_RATE baud){
SW_UART* uart = (SW_UART*) _uart;
if(uart->_uart_.rx_pin!=null || uart->_uart_.tx_pin!=null){
const Timer* timer = &pgm_Timers[uart->timer];
// initialize baud rate
uartSetBaudRate(uart, baud);
// setup the transmitter
if(uart->_uart_.tx_pin!=null){
uart->txBitNum=0;
// set the output idle state of the transmitter
if(!uartIsOneWire(uart)){
setTxIdle(uart);
}
CRITICAL_SECTION{
const TimerCompare* channel = timerGetCompare(timer,0);
// Mark the transmit channel as in use
compareAttach(channel,&uartswTxBitService,0,uart);
// But turn off interrupt for now
__portMaskClear(&channel->pgm_intenable);
}
}
// setup the receiver
if(uart->_uart_.rx_pin!=null){
// Locate the timer whose capture input is this pin
const IOPin* tCapture = timerGetCapturePin(timer);
if(tCapture!=uart->_uart_.rx_pin){
setError(UARTSW_NOT_CAPTURE);
}else{
// Make the Rx pin an input with no pullup
pin_make_input(uart->_uart_.rx_pin,FALSE);
// attach RxBit service routine to ICP
// trigger on rising edge if inverted, falling if not
timerCaptureAttach(timer, uartswRxStartService, uart, uart->inverted );
// Set up receive interrupt for bit handling - then immediately disable it
const TimerCompare* rx = timerGetCompare(timer,1);
compareAttach(rx, uartswRxBitService, compareGetThreshold(rx)-1, uart);
__portMaskClear(&rx->pgm_intenable); // turn interrupt off
compareClearInterruptPending(rx); // clear any interrupt pending
}
}
}
oneWireReceiveMode(_uart);
}
void __uartswSetBaudRate(UART* _uart, BAUD_RATE baudrate){
SW_UART* uart = (SW_UART*)_uart;
const Timer* timer = &pgm_Timers[uart->timer];
boolean isMyTimer = FALSE;
// Make sure the timer is in normal mode
if(!timerIsInUse(timer)){
timerSetMode(timer,TIMER_MODE_NORMAL);
// set timer prescaler
timerSetPrescaler(timer,1);
isMyTimer = TRUE;
}
uint16_t top = timerGetTOP(timer);
// Get the current prescaler
uint16_t prescale = timerGetPrescaler(timer);
// calculate division factor for requested baud rate, and set it
calcBaud:
uart->dataBitLength = (uint16_t)( (cpu_speed+(baudrate/2L)) / (prescale * baudrate * 1L) );
// Fudge factor compensates for interrupt processing overhead
// The larger this number then the shorter the start bit
//
uint16_t pre = prescale;
uint16_t fudge = 104U;
while(pre > 1){
fudge>>=1;
pre>>=1;
}
uart->startBitLength = uart->dataBitLength - fudge;
if(uart->dataBitLength > top){
uart->dataBitLength = top;
if(isMyTimer){
// Try increasing the prescaler
uint16_t nextpre = timerGetClosestPrescale(timer, prescale+1);
if(nextpre != prescale){
prescale = nextpre;
timerSetPrescaler(timer,prescale);
goto calcBaud;
}
}
}
}
// Start transmitting the given byte
void __uartswStartXmit(UART* _uart, uint8_t data){
SW_UART* uart = (SW_UART*)_uart;
// save data
uart->txData = (uart->inverted) ? ~data : data;
// set number of bits (+1 for stop bit)
uart->txBitNum = 9;
const Timer* timer = &pgm_Timers[uart->timer];
// Get the time when the start bit is set
uint16_t start;
start = timerGetCounter(timer);
// set the start bit
if(uart->inverted){
pin_high(uart->_uart_.tx_pin);
}else{
pin_low(uart->_uart_.tx_pin);
}
if(uart->_uart_.tx_pin){
const TimerCompare* channel = timerGetCompare(timer,0);
// schedule the next bit
uint16_t top = 1 + timerGetTOP(timer);
uint16_t next = (start + uart->startBitLength);
if(next >= top){
next -= top;
}
compareSetThreshold(channel, next);
// No interrupt pending
compareClearInterruptPending(channel);
// Allow compare interrupts
__portMaskSet(&channel->pgm_intenable);
}else{
// There is no iopin - so we have finished sending the current byte
__uartTransmitService(&uart->_uart_);
}
}
// We are about to start sending stuff
static void startXmitMode(UART* _uart){
if(_uartIsOneWire(_uart)){
SW_UART* uart = (SW_UART*)_uart;
// Place into transmit mode and turn receiver off
const Timer* timer = &pgm_Timers[uart->timer];
const TimerCompare* rx = timerGetCompare(timer,1);
CRITICAL_SECTION_START;
// Disable the input capture
__portMaskClear(&timer->pgm_captureint); // disable capture interrupt on timer
__portMaskClear(&rx->pgm_intenable); // disable timer compare interrupt for recv bits
// Turn the transmitter pin back into an output
setTxIdle(uart);
CRITICAL_SECTION_END;
}
}
// If one wire - then go into receive mode
static void oneWireReceiveMode(const UART* _uart){
if(_uartIsOneWire(_uart)){
const SW_UART* uart = (const SW_UART*)_uart;
// Place into receive mode and turn transmitter off
const Timer* timer = &pgm_Timers[uart->timer];
CRITICAL_SECTION_START;
// Turn the transmitter/receiver pin back into an output
// setTxIdle(uart);
// Turn the xmit into an input pin with pullup
pin_make_input(_uart->tx_pin,TRUE);
// Clear any pending capture interrupt flag
timerCaptureClearInterruptPending(timer);
// Re-enable the input capture to detect the start of a byte
__portMaskSet(&timer->pgm_captureint);
CRITICAL_SECTION_END;
}
}
// We have finished sending stuff
static void endXmitMode(UART* _uart){
const SW_UART* uart = (const SW_UART*)_uart;
// Turn timer interrupt off
const Timer* timer = &pgm_Timers[uart->timer];
const TimerCompare* channel = timerGetCompare(timer,0);
__portMaskClear(&channel->pgm_intenable);
oneWireReceiveMode(_uart);
}
// Create a class with the method overrides for this type of UART
UART_CLASS const c_sw_uart = MAKE_UART_CLASS( \
&__uartswSetBaudRate, \
&__uartswStartXmit, \
&__uartswInit, \
&__uartswOff, \
&startXmitMode, \
&endXmitMode );