-
Notifications
You must be signed in to change notification settings - Fork 5
/
fpga.c
607 lines (538 loc) · 16 KB
/
fpga.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
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
/*
*
* Odyssey 2 MCU
*
* Copyright (C) 2020 Davide Gerhard IV3CVE
*
*
* 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 <https://www.gnu.org/licenses/>.
*
*/
/*
* This library implements software UART, aka bit-banging, to communicate with the FPGA.
* To avoid issue with delay drift, since you sum the error each time, we use interrupt timers
* for both RX and TX and in the RX path we use Interrupt On Change to get the
* start bit. It should also work in full duplex, at reasonable speed, but it is not tested.
*
*
* PROTOCOL: see README.org
*
*/
/*
* TODO:
* - for some strange reason the last data bit in TX is 3/4 usec shorter (at 9600)
* it doesn't hurt too much but it is strange :)
* - manage receiving error per each byte
* - manage overwriting of the RX circular queue (should be not needed for the moment)
*/
#include "fpga.h"
/**
* @brief initialize the UART channel used to communicate with the FPGA
*
* RB6 is used for the TX to FPGA (RX on this one)
* RB7 is used for the RX to FPGA (TX on this one)
*
* Timer4 is used during receive
* Timer6 is used during transmit
*/
void
fpga_init(void) {
// set the TX port
IO_RB6_FPGA_TX_SetDigitalMode();
IO_RB6_FPGA_TX_SetDigitalOutput();
IO_RB6_FPGA_TX_SetHigh();
IO_RB6_FPGA_TX_WPU = 1;
// set the RX port
IO_RB7_FPGA_RX_SetDigitalMode();
IO_RB7_FPGA_RX_SetDigitalInput();
IO_RB7_FPGA_RX_WPU = 1;
// enable Interrupt on Change bit
INTCONbits.IOCIE = 1;
// we use interrupt on negative edge change
// to detect the receiving start bit
IOCBNbits.IOCBN7 = 1;
// TIMER 4 for RX
// we use post-scaller sets at 1:8
T4CON |= 56;
// no pre-scaller
T4CONbits.T4CKPS1 = 0;
T4CONbits.T4CKPS0 = 0;
// Timer6 interrupt period calculated from baud rate
PR4 = (uint8_t) FPGA_UART_INTERRUPT_PERIOD;
// Enable Timer 6 interrupt
PIE3bits.TMR4IE = 1;
// Disable the Timer 6 by default
T4CONbits.TMR4ON = 0;
// TIMER 6 for TX
// we use post-scaller sets at 1:8
T6CON |= 56;
// no pre-scaller
T6CONbits.T6CKPS1 = 0;
T6CONbits.T6CKPS0 = 0;
// Timer6 interrupt period calculated from baud rate
PR6 = (uint8_t) FPGA_UART_INTERRUPT_PERIOD;
// Enable Timer 6 interrupt
PIE3bits.TMR6IE = 1;
// Disable the Timer 6 by default
T6CONbits.TMR6ON = 0;
// disable the power amplifier line RA2
#ifdef PA_BIAS_4096
DACCON0bits.DACLPS = 0;
DACCON1bits.DACR = 0x00;
#else
IO_RA2_1W_PA_SetLow();
#endif
// disable the audio amplifier
IO_RA6_AUDIO_AMPL_LAT = 1;
}
/**
* @brief de-initialize the FPGA UART
*/
void
fpga_deinit(void)
{
// disable Interrupt On Change
IOCBNbits.IOCBN7 = 0;
// disable Timers 4 and 6
T4CONbits.TMR4ON = 0;
T6CONbits.TMR6ON = 0;
// reset helper variables
fpga_boot_slot = 0;
fpga_status = 0;
fpga_stage = 0;
fpga_version[0] = 0;
fpga_ip_address[0] = 0;
fpga_swr[0] = 0;
fpga_stage_changed = false;
fpga_value_changed = false;
fpga_swr_changed = false;
fpga_request_status = false;
fpga_request_poweron = false;
// disable the power amplifier line RA2
#ifdef PA_BIAS_4096
DACCON0bits.DACLPS = 0;
DACCON1bits.DACR = 0x00;
#else
IO_RA2_1W_PA_SetLow();
#endif
// disable the audio amplifier
IO_RA6_AUDIO_AMPL_LAT = 1;
}
/**
* @brief receive a single bit from the UART
*
* this function is driven by Timer4 and at each interrupt
* its gets the bit near the middle of the period.
* At the end it re-enables the Interrupt On Change to eventually
* receive another byte.
*/
inline void
uart_rx_bit(void)
{
if (fpga_rx_pos < FPGA_UART_TRANSFER_BITS)
{
// store the bit
fpga_rx_queue[fpga_rx_write_pos] |= IO_RB7_FPGA_RX_GetValue() << fpga_rx_pos;
// go to the next bit
fpga_rx_pos++;
}
// we should receive a stop bit
else if (fpga_rx_pos == FPGA_UART_TRANSFER_BITS)
{
// if it is not a stop bit (high)
// we erase the data value received
if (!IO_RB7_FPGA_RX_GetValue())
fpga_rx_queue[fpga_rx_write_pos] = 0x00;
// move on (not used just to differentiate the state)
fpga_rx_pos++;
// stop the timer
T4CONbits.TMR4ON = 0;
// move to the next location in the circular queue
fpga_rx_write_pos = (fpga_rx_write_pos+1) % FPGA_UART_RX_QUEUE_MAX;
// re-enable the interrupt on change
IOCBNbits.IOCBN7 = 1;
}
}
/**
* @brief receive a single byte; driven by Interrupt On Change
*/
inline void
uart_rx(void)
{
// we have detected a start bit; from now on we use the
// Timer 4 interrupt to drive the read
IOCBNbits.IOCBN7 = 0;
// initialize the pointer
fpga_rx_pos = 0;
// initialize the data
fpga_rx_queue[fpga_rx_write_pos] = 0x00;
// since we have a range to receive we use a quick and dirty
// way to center in the middle of the bit
// minus 4 usec to compensate the instructions delay
__delay_us((FPGA_UART_INTERRUPT_PERIOD/2)-4);
// confirm that it is a start bit
if (IO_RB7_FPGA_RX_GetValue())
{
// re-enable the interrupt on change
IOCBNbits.IOCBN7 = 1;
return;
}
// then enable the interrupt that will read the bits
T4CONbits.TMR4ON = 1;
}
/**
* @brief transmit a single bit using Timer6
*
* this function transmit one data bit and the stop bit
* the data is got from fpga_tx_data and we use the
* counter fpga_tx_pos to understand where we hare.
* this should be called from uart_trasmit_start which
* sets the starting value, the start bit and the Timer6
*/
inline void
uart_tx_bit(void)
{
// we have bits to send
if (fpga_tx_pos < FPGA_UART_TRANSFER_BITS)
{
// optimize the change
if((fpga_tx_data>>fpga_tx_pos)&0x1)
IO_RB6_FPGA_TX_SetHigh();
else
IO_RB6_FPGA_TX_SetLow();
// got to the next bit
fpga_tx_pos++;
}
// we have sent all bits so send the stop bit
else if (fpga_tx_pos == FPGA_UART_TRANSFER_BITS)
{
// stop bit
IO_RB6_FPGA_TX_SetHigh();
// another delay to close the transmission
fpga_tx_pos++;
}
else
{
// we have done
T6CONbits.TMR6ON = 0;
}
}
/**
* @brief send a single byte to UART
*
* @param d byte to send
*/
void
uart_tx_byte(const char d)
{
// check if we are already transmitting
// so we BLOCK here the next transmitting data
// until the previous one is finished
while(T6CONbits.TMR6ON) {}
// set the starting value
fpga_tx_pos = 0;
fpga_tx_data = d;
// start bit to high
IO_RB6_FPGA_TX_SetLow();
// start the timer6
T6CONbits.TMR6ON = 1;
}
/**
* @brief send a stream of byte to UART
*
* @param array char array to send
* @param length length of the array to send; generally sizeof(array)
*/
void
uart_tx_bytes(const char *array,
const uint8_t length)
{
uint8_t i;
// cycle through the array
// the transmission is automatically blocked by
// while() in uart_transmit()
for(i=0; i < length; i++)
uart_tx_byte(*(array+i));
}
/**
* @brief read the received buffer and check for commands
*/
void
fpga_check_command(void)
{
// read byte until we have reached the write position
while (fpga_rx_read_pos != fpga_rx_write_pos)
{
// we have pending bytes?
if (fpga_rx_cmd_next_bytes != 0)
{
// check which command belongs these bytes
switch(fpga_rx_cmd_wait)
{
case FPGA_CMD_VERSION:
// the bytes arrive is this sense "000003.0"
// therefore we get only the good text
if (fpga_rx_queue[fpga_rx_read_pos] != 0x00) {
fpga_version[fpga_version_pos] = fpga_rx_queue[fpga_rx_read_pos];
fpga_version_pos++;
}
if (fpga_rx_cmd_next_bytes == 1)
fpga_value_changed = true;
break;
case FPGA_CMD_IP:
fpga_ip_address[FPGA_IP_ADDRESS_LENGTH-fpga_rx_cmd_next_bytes] = fpga_rx_queue[fpga_rx_read_pos];
if (fpga_rx_cmd_next_bytes == 1)
fpga_value_changed = true;
break;
case FPGA_CMD_SWR:
fpga_swr[FPGA_SWR_LENGTH-fpga_rx_cmd_next_bytes] = fpga_rx_queue[fpga_rx_read_pos];
if (fpga_rx_cmd_next_bytes == 1)
fpga_swr_changed = true;
break;
// if the bytes are not correlated to a command
// exit from this check
default:
fpga_rx_cmd_next_bytes = 1;
break;
}
fpga_rx_cmd_next_bytes--;
}
else {
// check the command
switch(fpga_rx_queue[fpga_rx_read_pos] >> 4)
{
// reserved command or not valid
case 0:
break;
// acknowledge command
case 1:
// check if there is a command to acknowledge
// and if the command is the same
if (!fpga_cmd_to_ack &&
(fpga_rx_queue[fpga_rx_read_pos] & 0x0F) == fpga_cmd_to_ack >> 4)
fpga_cmd_to_ack = 0x00;
break;
// stage command
case 2:
// check in which stage we are
// we use a switch to ensure the good data and eventually
// to implement other functions
switch(fpga_rx_queue[fpga_rx_read_pos] & 0x0F)
{
// reserved
case 0:
fpga_stage = FPGA_RESERVED;
break;
// booting
case 1:
fpga_stage = FPGA_BOOTING;
break;
// bootloader
case 2:
fpga_stage = FPGA_BOOTLOADER;
break;
// radio
case 3:
// disable the power amplifier line RA2
#ifdef PA_BIAS_4096
DACCON0bits.DACLPS = 0;
DACCON1bits.DACR = 0x00;
#else
IO_RA2_1W_PA_SetLow();
#endif
fpga_stage = FPGA_RADIO;
break;
// PTT
case 4:
// check if we need to enable RA2 1W power amplifier
if (bit_is_set(fpga_status, 1)) {
#ifdef PA_BIAS_4096
DACCON0bits.DACLPS = 1;
DACCON1bits.DACR = 0x1F;
#else
IO_RA2_1W_PA_SetHigh();
#endif
}
fpga_stage = FPGA_PTT;
break;
// firmware CRC ERROR
case 5:
fpga_stage = FPGA_CRC_ERROR;
break;
// default stage
default:
fpga_stage = FPGA_RESERVED;
break;
}
fpga_stage_changed = true;
break;
// fpga firmware version
case 3:
// we don't care about which type is at the moment
// only that it is not invalid or MCU
if ((fpga_rx_queue[fpga_rx_read_pos] & 0x0F) != 0 &&
(fpga_rx_queue[fpga_rx_read_pos] & 0x0F) != 1)
{
// clear version string
cla(fpga_version, sizeof(fpga_version));
// we need to get the following 8 bytes
fpga_rx_cmd_next_bytes = FPGA_VERSION_LENGTH;
// save the command to correlate the next bytes
fpga_rx_cmd_wait = FPGA_CMD_VERSION;
// reset the counter
fpga_version_pos = 0;
}
break;
// IP address
case 4:
// clear the IP array
cla(fpga_ip_address, sizeof(fpga_ip_address));
// we need to get the following 16 bytes
fpga_rx_cmd_next_bytes = FPGA_IP_ADDRESS_LENGTH;
// save the command to correlate the next bytes
fpga_rx_cmd_wait = FPGA_CMD_IP;
break;
// status
case 5:
// we have a request therefore send the status
// is blocking but fast enough to not care
if (fpga_rx_queue[fpga_rx_read_pos] == FPGA_CMD_STATUS)
{
fpga_request_status = true;
break;
}
// get a valid slot
if ((fpga_rx_queue[fpga_rx_read_pos] & 0x0C) != 0)
fpga_boot_slot = (fpga_rx_queue[fpga_rx_read_pos] & 0x0C) >> 2;
// get the 1W power amplifier status
if (rbi(fpga_rx_queue[fpga_rx_read_pos], 1))
sbi(fpga_status, 1);
else
cbi(fpga_status, 1);
// get the audio amplifier status
if (rbi(fpga_rx_queue[fpga_rx_read_pos], 0))
sbi(fpga_status, 0);
else
cbi(fpga_status, 0);
// write to eeprom the new values
fpga_write_eeprom();
// we have changed the values
fpga_value_changed = true;
break;
// auto power on
case 6:
if (fpga_rx_queue[fpga_rx_read_pos] == FPGA_CMD_POWERON)
{
fpga_request_poweron = true;
break;
}
if (fpga_rx_queue[fpga_rx_read_pos] == (FPGA_CMD_POWERON | 0x01))
sbi(fpga_status, 2);
else
cbi(fpga_status, 2);
// write the new value to eeprom
fpga_write_eeprom();
break;
case 7:
// clear the array
cla(fpga_swr, sizeof(fpga_swr));
// configure to receive the next bytes
fpga_rx_cmd_next_bytes = FPGA_SWR_LENGTH;
fpga_rx_cmd_wait = FPGA_CMD_SWR;
// if the command is invalid don't do anything
default:
break;
}
}
// move on in the circular queue
fpga_rx_read_pos = (fpga_rx_read_pos + 1) % FPGA_UART_RX_QUEUE_MAX;
}
}
/**
* @brief send the status to the FPGA
*
* before calling this function we need to check
* that fpga_cmd_to_ack must be 0 to ensure
* that there is not other commands on the fly
*/
void
fpga_send_status(void)
{
// send the status byte
uart_tx_byte(FPGA_CMD_STATUS | (fpga_boot_slot << 2) | (fpga_status & 0x03));
}
/**
* @brief send the MCU software version to FPGA
*/
void
fpga_send_mcu_version(void)
{
// we send the MCU version
uart_tx_byte(FPGA_CMD_VERSION | 1);
uart_tx_bytes(mcu_version, 8);
}
/**
* @brief send the power on value to FPGA
*/
void
fpga_send_poweron(void)
{
// send the auto power on byte
uart_tx_byte(FPGA_CMD_POWERON | (rbi(fpga_status,2) ? 0x01 : 0x02));
}
/**
* @brief write configuration to eeprom
*/
void
fpga_write_eeprom(void)
{
EEPROM_WRITE(FPGA_EEPROM_STATUS_ADDR, fpga_status);
EEPROM_WRITE(FPGA_EEPROM_SLOT_ADDR, fpga_boot_slot);
}
/**
* @brief read configuration from eeprom
*/
void
fpga_read_eeprom(void)
{
// wait for end-of-write before EEPROM_READ
while(WR) {}
// check the sentinel if it is different
// if yes we need to initialize the values
// we use this method since __EEPROM_DATA()/__eeprom
// and @0xF0 didn't work well for us
if (EEPROM_READ(FPGA_EEPROM_SENTINEL_ADDR) != FPGA_EEPROM_SENTINEL_VALUE) {
EEPROM_WRITE(FPGA_EEPROM_SENTINEL_ADDR, FPGA_EEPROM_SENTINEL_VALUE);
// everything disabled
EEPROM_WRITE(FPGA_EEPROM_STATUS_ADDR, 0x00);
// first slot
EEPROM_WRITE(FPGA_EEPROM_SLOT_ADDR, 0x01);
}
fpga_status = EEPROM_READ(FPGA_EEPROM_STATUS_ADDR);
fpga_boot_slot = EEPROM_READ(FPGA_EEPROM_SLOT_ADDR);
// check to be sure that a good value is generated
if (fpga_boot_slot < 1 || fpga_boot_slot > 3) {
fpga_boot_slot = 1;
fpga_write_eeprom();
}
}
/**
* @brief update the status to eeprom and to fpga
*/
void
fpga_update_status(void)
{
fpga_write_eeprom();
fpga_send_status();
}