]> cvs.zerfleddert.de Git - proxmark3-svn/blame - armsrc/iso14443.c
Implement version information storage and retrieval for the bootrom and the osimage.
[proxmark3-svn] / armsrc / iso14443.c
CommitLineData
6658905f 1//-----------------------------------------------------------------------------\r
2// Routines to support ISO 14443. This includes both the reader software and\r
3// the `fake tag' modes. At the moment only the Type B modulation is\r
4// supported.\r
5// Jonathan Westhues, split Nov 2006\r
6//-----------------------------------------------------------------------------\r
7#include <proxmark3.h>\r
8#include "apps.h"\r
30f2a7d3 9#include "../common/iso14443_crc.c"\r
6658905f 10\r
11\r
12//static void GetSamplesFor14443(BOOL weTx, int n);\r
13\r
14#define DMA_BUFFER_SIZE 256\r
15\r
16//=============================================================================\r
17// An ISO 14443 Type B tag. We listen for commands from the reader, using\r
18// a UART kind of thing that's implemented in software. When we get a\r
19// frame (i.e., a group of bytes between SOF and EOF), we check the CRC.\r
20// If it's good, then we can do something appropriate with it, and send\r
21// a response.\r
22//=============================================================================\r
23\r
24//-----------------------------------------------------------------------------\r
25// Code up a string of octets at layer 2 (including CRC, we don't generate\r
26// that here) so that they can be transmitted to the reader. Doesn't transmit\r
27// them yet, just leaves them ready to send in ToSend[].\r
28//-----------------------------------------------------------------------------\r
29static void CodeIso14443bAsTag(const BYTE *cmd, int len)\r
30{\r
31 int i;\r
32\r
33 ToSendReset();\r
34\r
35 // Transmit a burst of ones, as the initial thing that lets the\r
36 // reader get phase sync. This (TR1) must be > 80/fs, per spec,\r
37 // but tag that I've tried (a Paypass) exceeds that by a fair bit,\r
38 // so I will too.\r
39 for(i = 0; i < 20; i++) {\r
40 ToSendStuffBit(1);\r
41 ToSendStuffBit(1);\r
42 ToSendStuffBit(1);\r
43 ToSendStuffBit(1);\r
44 }\r
45\r
46 // Send SOF.\r
47 for(i = 0; i < 10; i++) {\r
48 ToSendStuffBit(0);\r
49 ToSendStuffBit(0);\r
50 ToSendStuffBit(0);\r
51 ToSendStuffBit(0);\r
52 }\r
53 for(i = 0; i < 2; i++) {\r
54 ToSendStuffBit(1);\r
55 ToSendStuffBit(1);\r
56 ToSendStuffBit(1);\r
57 ToSendStuffBit(1);\r
58 }\r
59\r
60 for(i = 0; i < len; i++) {\r
61 int j;\r
62 BYTE b = cmd[i];\r
63\r
64 // Start bit\r
65 ToSendStuffBit(0);\r
66 ToSendStuffBit(0);\r
67 ToSendStuffBit(0);\r
68 ToSendStuffBit(0);\r
69\r
70 // Data bits\r
71 for(j = 0; j < 8; j++) {\r
72 if(b & 1) {\r
73 ToSendStuffBit(1);\r
74 ToSendStuffBit(1);\r
75 ToSendStuffBit(1);\r
76 ToSendStuffBit(1);\r
77 } else {\r
78 ToSendStuffBit(0);\r
79 ToSendStuffBit(0);\r
80 ToSendStuffBit(0);\r
81 ToSendStuffBit(0);\r
82 }\r
83 b >>= 1;\r
84 }\r
85\r
86 // Stop bit\r
87 ToSendStuffBit(1);\r
88 ToSendStuffBit(1);\r
89 ToSendStuffBit(1);\r
90 ToSendStuffBit(1);\r
91 }\r
92\r
93 // Send SOF.\r
94 for(i = 0; i < 10; i++) {\r
95 ToSendStuffBit(0);\r
96 ToSendStuffBit(0);\r
97 ToSendStuffBit(0);\r
98 ToSendStuffBit(0);\r
99 }\r
100 for(i = 0; i < 10; i++) {\r
101 ToSendStuffBit(1);\r
102 ToSendStuffBit(1);\r
103 ToSendStuffBit(1);\r
104 ToSendStuffBit(1);\r
105 }\r
106\r
107 // Convert from last byte pos to length\r
108 ToSendMax++;\r
109\r
110 // Add a few more for slop\r
111 ToSendMax += 2;\r
112}\r
113\r
114//-----------------------------------------------------------------------------\r
115// The software UART that receives commands from the reader, and its state\r
116// variables.\r
117//-----------------------------------------------------------------------------\r
118static struct {\r
119 enum {\r
120 STATE_UNSYNCD,\r
121 STATE_GOT_FALLING_EDGE_OF_SOF,\r
122 STATE_AWAITING_START_BIT,\r
123 STATE_RECEIVING_DATA,\r
124 STATE_ERROR_WAIT\r
125 } state;\r
126 WORD shiftReg;\r
127 int bitCnt;\r
128 int byteCnt;\r
129 int byteCntMax;\r
130 int posCnt;\r
131 BYTE *output;\r
132} Uart;\r
133\r
0e25ae11 134/* Receive & handle a bit coming from the reader.\r
135 *\r
136 * LED handling:\r
137 * LED A -> ON once we have received the SOF and are expecting the rest.\r
138 * LED A -> OFF once we have received EOF or are in error state or unsynced\r
139 *\r
140 * Returns: true if we received a EOF\r
141 * false if we are still waiting for some more\r
142 */\r
6658905f 143static BOOL Handle14443UartBit(int bit)\r
144{\r
145 switch(Uart.state) {\r
146 case STATE_UNSYNCD:\r
0e25ae11 147 LED_A_OFF();\r
6658905f 148 if(!bit) {\r
149 // we went low, so this could be the beginning\r
150 // of an SOF\r
151 Uart.state = STATE_GOT_FALLING_EDGE_OF_SOF;\r
152 Uart.posCnt = 0;\r
153 Uart.bitCnt = 0;\r
154 }\r
155 break;\r
156\r
157 case STATE_GOT_FALLING_EDGE_OF_SOF:\r
158 Uart.posCnt++;\r
159 if(Uart.posCnt == 2) {\r
160 if(bit) {\r
161 if(Uart.bitCnt >= 10) {\r
162 // we've seen enough consecutive\r
163 // zeros that it's a valid SOF\r
164 Uart.posCnt = 0;\r
165 Uart.byteCnt = 0;\r
166 Uart.state = STATE_AWAITING_START_BIT;\r
0e25ae11 167 LED_A_ON(); // Indicate we got a valid SOF\r
6658905f 168 } else {\r
169 // didn't stay down long enough\r
170 // before going high, error\r
171 Uart.state = STATE_ERROR_WAIT;\r
172 }\r
173 } else {\r
174 // do nothing, keep waiting\r
175 }\r
176 Uart.bitCnt++;\r
177 }\r
178 if(Uart.posCnt >= 4) Uart.posCnt = 0;\r
179 if(Uart.bitCnt > 14) {\r
180 // Give up if we see too many zeros without\r
181 // a one, too.\r
182 Uart.state = STATE_ERROR_WAIT;\r
183 }\r
184 break;\r
185\r
186 case STATE_AWAITING_START_BIT:\r
187 Uart.posCnt++;\r
188 if(bit) {\r
189 if(Uart.posCnt > 25) {\r
190 // stayed high for too long between\r
191 // characters, error\r
192 Uart.state = STATE_ERROR_WAIT;\r
193 }\r
194 } else {\r
195 // falling edge, this starts the data byte\r
196 Uart.posCnt = 0;\r
197 Uart.bitCnt = 0;\r
198 Uart.shiftReg = 0;\r
199 Uart.state = STATE_RECEIVING_DATA;\r
0e25ae11 200 LED_A_ON(); // Indicate we're receiving\r
6658905f 201 }\r
202 break;\r
203\r
204 case STATE_RECEIVING_DATA:\r
205 Uart.posCnt++;\r
206 if(Uart.posCnt == 2) {\r
207 // time to sample a bit\r
208 Uart.shiftReg >>= 1;\r
209 if(bit) {\r
210 Uart.shiftReg |= 0x200;\r
211 }\r
212 Uart.bitCnt++;\r
213 }\r
214 if(Uart.posCnt >= 4) {\r
215 Uart.posCnt = 0;\r
216 }\r
217 if(Uart.bitCnt == 10) {\r
218 if((Uart.shiftReg & 0x200) && !(Uart.shiftReg & 0x001))\r
219 {\r
220 // this is a data byte, with correct\r
221 // start and stop bits\r
222 Uart.output[Uart.byteCnt] = (Uart.shiftReg >> 1) & 0xff;\r
223 Uart.byteCnt++;\r
224\r
225 if(Uart.byteCnt >= Uart.byteCntMax) {\r
226 // Buffer overflowed, give up\r
227 Uart.posCnt = 0;\r
228 Uart.state = STATE_ERROR_WAIT;\r
229 } else {\r
230 // so get the next byte now\r
231 Uart.posCnt = 0;\r
232 Uart.state = STATE_AWAITING_START_BIT;\r
233 }\r
234 } else if(Uart.shiftReg == 0x000) {\r
235 // this is an EOF byte\r
0e25ae11 236 LED_A_OFF(); // Finished receiving\r
6658905f 237 return TRUE;\r
238 } else {\r
239 // this is an error\r
240 Uart.posCnt = 0;\r
241 Uart.state = STATE_ERROR_WAIT;\r
242 }\r
243 }\r
244 break;\r
245\r
246 case STATE_ERROR_WAIT:\r
247 // We're all screwed up, so wait a little while\r
248 // for whatever went wrong to finish, and then\r
249 // start over.\r
250 Uart.posCnt++;\r
251 if(Uart.posCnt > 10) {\r
252 Uart.state = STATE_UNSYNCD;\r
253 }\r
254 break;\r
255\r
256 default:\r
257 Uart.state = STATE_UNSYNCD;\r
258 break;\r
259 }\r
260\r
0e25ae11 261 if (Uart.state == STATE_ERROR_WAIT) LED_A_OFF(); // Error\r
262\r
6658905f 263 return FALSE;\r
264}\r
265\r
266//-----------------------------------------------------------------------------\r
267// Receive a command (from the reader to us, where we are the simulated tag),\r
268// and store it in the given buffer, up to the given maximum length. Keeps\r
269// spinning, waiting for a well-framed command, until either we get one\r
270// (returns TRUE) or someone presses the pushbutton on the board (FALSE).\r
271//\r
272// Assume that we're called with the SSC (to the FPGA) and ADC path set\r
273// correctly.\r
274//-----------------------------------------------------------------------------\r
275static BOOL GetIso14443CommandFromReader(BYTE *received, int *len, int maxLen)\r
276{\r
277 BYTE mask;\r
278 int i, bit;\r
279\r
280 // Set FPGA mode to "simulated ISO 14443 tag", no modulation (listen\r
281 // only, since we are receiving, not transmitting).\r
0e25ae11 282 // Signal field is off with the appropriate LED\r
283 LED_D_OFF();\r
6658905f 284 FpgaWriteConfWord(\r
285 FPGA_MAJOR_MODE_HF_SIMULATOR | FPGA_HF_SIMULATOR_NO_MODULATION);\r
286\r
287\r
288 // Now run a `software UART' on the stream of incoming samples.\r
289 Uart.output = received;\r
290 Uart.byteCntMax = maxLen;\r
291 Uart.state = STATE_UNSYNCD;\r
292\r
293 for(;;) {\r
294 WDT_HIT();\r
295\r
296 if(BUTTON_PRESS()) return FALSE;\r
297\r
298 if(SSC_STATUS & (SSC_STATUS_TX_READY)) {\r
299 SSC_TRANSMIT_HOLDING = 0x00;\r
300 }\r
301 if(SSC_STATUS & (SSC_STATUS_RX_READY)) {\r
302 BYTE b = (BYTE)SSC_RECEIVE_HOLDING;\r
303\r
304 mask = 0x80;\r
305 for(i = 0; i < 8; i++, mask >>= 1) {\r
306 bit = (b & mask);\r
307 if(Handle14443UartBit(bit)) {\r
308 *len = Uart.byteCnt;\r
309 return TRUE;\r
310 }\r
311 }\r
312 }\r
313 }\r
314}\r
315\r
316//-----------------------------------------------------------------------------\r
317// Main loop of simulated tag: receive commands from reader, decide what\r
318// response to send, and send it.\r
319//-----------------------------------------------------------------------------\r
320void SimulateIso14443Tag(void)\r
321{\r
322 static const BYTE cmd1[] = { 0x05, 0x00, 0x08, 0x39, 0x73 };\r
323 static const BYTE response1[] = {\r
324 0x50, 0x82, 0x0d, 0xe1, 0x74, 0x20, 0x38, 0x19, 0x22,\r
325 0x00, 0x21, 0x85, 0x5e, 0xd7\r
326 };\r
327\r
328 BYTE *resp;\r
329 int respLen;\r
330\r
331 BYTE *resp1 = (((BYTE *)BigBuf) + 800);\r
332 int resp1Len;\r
333\r
334 BYTE *receivedCmd = (BYTE *)BigBuf;\r
335 int len;\r
336\r
337 int i;\r
338\r
339 int cmdsRecvd = 0;\r
340\r
341 memset(receivedCmd, 0x44, 400);\r
342\r
343 CodeIso14443bAsTag(response1, sizeof(response1));\r
344 memcpy(resp1, ToSend, ToSendMax); resp1Len = ToSendMax;\r
345\r
346 // We need to listen to the high-frequency, peak-detected path.\r
347 SetAdcMuxFor(GPIO_MUXSEL_HIPKD);\r
348 FpgaSetupSsc();\r
349\r
350 cmdsRecvd = 0;\r
351\r
352 for(;;) {\r
353 BYTE b1, b2;\r
354\r
355 if(!GetIso14443CommandFromReader(receivedCmd, &len, 100)) {\r
356 DbpIntegers(cmdsRecvd, 0, 0);\r
357 DbpString("button press");\r
358 break;\r
359 }\r
360\r
361 // Good, look at the command now.\r
362\r
363 if(len == sizeof(cmd1) && memcmp(receivedCmd, cmd1, len)==0) {\r
364 resp = resp1; respLen = resp1Len;\r
365 } else {\r
366 DbpString("new cmd from reader:");\r
367 DbpIntegers(len, 0x1234, cmdsRecvd);\r
368 // And print whether the CRC fails, just for good measure\r
369 ComputeCrc14443(CRC_14443_B, receivedCmd, len-2, &b1, &b2);\r
370 if(b1 != receivedCmd[len-2] || b2 != receivedCmd[len-1]) {\r
371 // Not so good, try again.\r
372 DbpString("+++CRC fail");\r
373 } else {\r
374 DbpString("CRC passes");\r
375 }\r
376 break;\r
377 }\r
378\r
379 memset(receivedCmd, 0x44, 32);\r
380\r
381 cmdsRecvd++;\r
382\r
383 if(cmdsRecvd > 0x30) {\r
384 DbpString("many commands later...");\r
385 break;\r
386 }\r
387\r
388 if(respLen <= 0) continue;\r
389\r
390 // Modulate BPSK\r
0e25ae11 391 // Signal field is off with the appropriate LED\r
392 LED_D_OFF();\r
6658905f 393 FpgaWriteConfWord(\r
394 FPGA_MAJOR_MODE_HF_SIMULATOR | FPGA_HF_SIMULATOR_MODULATE_BPSK);\r
395 SSC_TRANSMIT_HOLDING = 0xff;\r
396 FpgaSetupSsc();\r
397\r
398 // Transmit the response.\r
399 i = 0;\r
400 for(;;) {\r
401 if(SSC_STATUS & (SSC_STATUS_TX_READY)) {\r
402 BYTE b = resp[i];\r
403\r
404 SSC_TRANSMIT_HOLDING = b;\r
405\r
406 i++;\r
407 if(i > respLen) {\r
408 break;\r
409 }\r
410 }\r
411 if(SSC_STATUS & (SSC_STATUS_RX_READY)) {\r
412 volatile BYTE b = (BYTE)SSC_RECEIVE_HOLDING;\r
413 (void)b;\r
414 }\r
415 }\r
416 }\r
417}\r
418\r
419//=============================================================================\r
420// An ISO 14443 Type B reader. We take layer two commands, code them\r
421// appropriately, and then send them to the tag. We then listen for the\r
422// tag's response, which we leave in the buffer to be demodulated on the\r
423// PC side.\r
424//=============================================================================\r
425\r
426static struct {\r
427 enum {\r
428 DEMOD_UNSYNCD,\r
429 DEMOD_PHASE_REF_TRAINING,\r
430 DEMOD_AWAITING_FALLING_EDGE_OF_SOF,\r
431 DEMOD_GOT_FALLING_EDGE_OF_SOF,\r
432 DEMOD_AWAITING_START_BIT,\r
433 DEMOD_RECEIVING_DATA,\r
434 DEMOD_ERROR_WAIT\r
435 } state;\r
436 int bitCount;\r
437 int posCount;\r
438 int thisBit;\r
439 int metric;\r
440 int metricN;\r
441 WORD shiftReg;\r
442 BYTE *output;\r
443 int len;\r
444 int sumI;\r
445 int sumQ;\r
446} Demod;\r
447\r
0e25ae11 448/*\r
449 * Handles reception of a bit from the tag\r
450 *\r
451 * LED handling:\r
452 * LED C -> ON once we have received the SOF and are expecting the rest.\r
453 * LED C -> OFF once we have received EOF or are unsynced\r
454 *\r
455 * Returns: true if we received a EOF\r
456 * false if we are still waiting for some more\r
457 *
458 */\r
6658905f 459static BOOL Handle14443SamplesDemod(int ci, int cq)\r
460{\r
461 int v;\r
462\r
463 // The soft decision on the bit uses an estimate of just the\r
464 // quadrant of the reference angle, not the exact angle.\r
465#define MAKE_SOFT_DECISION() { \\r
466 if(Demod.sumI > 0) { \\r
467 v = ci; \\r
468 } else { \\r
469 v = -ci; \\r
470 } \\r
471 if(Demod.sumQ > 0) { \\r
472 v += cq; \\r
473 } else { \\r
474 v -= cq; \\r
475 } \\r
476 }\r
477\r
478 switch(Demod.state) {\r
479 case DEMOD_UNSYNCD:\r
480 v = ci;\r
481 if(v < 0) v = -v;\r
482 if(cq > 0) {\r
483 v += cq;\r
484 } else {\r
485 v -= cq;\r
486 }\r
487 if(v > 40) {\r
488 Demod.posCount = 0;\r
489 Demod.state = DEMOD_PHASE_REF_TRAINING;\r
490 Demod.sumI = 0;\r
491 Demod.sumQ = 0;\r
492 }\r
493 break;\r
494\r
495 case DEMOD_PHASE_REF_TRAINING:\r
496 if(Demod.posCount < 8) {\r
497 Demod.sumI += ci;\r
498 Demod.sumQ += cq;\r
499 } else if(Demod.posCount > 100) {\r
500 // error, waited too long\r
501 Demod.state = DEMOD_UNSYNCD;\r
502 } else {\r
503 MAKE_SOFT_DECISION();\r
504 if(v < 0) {\r
505 Demod.state = DEMOD_AWAITING_FALLING_EDGE_OF_SOF;\r
506 Demod.posCount = 0;\r
507 }\r
508 }\r
509 Demod.posCount++;\r
510 break;\r
511\r
512 case DEMOD_AWAITING_FALLING_EDGE_OF_SOF:\r
513 MAKE_SOFT_DECISION();\r
514 if(v < 0) {\r
515 Demod.state = DEMOD_GOT_FALLING_EDGE_OF_SOF;\r
516 Demod.posCount = 0;\r
517 } else {\r
518 if(Demod.posCount > 100) {\r
519 Demod.state = DEMOD_UNSYNCD;\r
520 }\r
521 }\r
522 Demod.posCount++;\r
523 break;\r
524\r
525 case DEMOD_GOT_FALLING_EDGE_OF_SOF:\r
526 MAKE_SOFT_DECISION();\r
527 if(v > 0) {\r
528 if(Demod.posCount < 12) {\r
529 Demod.state = DEMOD_UNSYNCD;\r
530 } else {\r
0e25ae11 531 LED_C_ON(); // Got SOF\r
6658905f 532 Demod.state = DEMOD_AWAITING_START_BIT;\r
533 Demod.posCount = 0;\r
534 Demod.len = 0;\r
535 Demod.metricN = 0;\r
536 Demod.metric = 0;\r
537 }\r
538 } else {\r
539 if(Demod.posCount > 100) {\r
540 Demod.state = DEMOD_UNSYNCD;\r
541 }\r
542 }\r
543 Demod.posCount++;\r
544 break;\r
545\r
546 case DEMOD_AWAITING_START_BIT:\r
547 MAKE_SOFT_DECISION();\r
548 if(v > 0) {\r
549 if(Demod.posCount > 10) {\r
550 Demod.state = DEMOD_UNSYNCD;\r
551 }\r
552 } else {\r
553 Demod.bitCount = 0;\r
554 Demod.posCount = 1;\r
555 Demod.thisBit = v;\r
556 Demod.shiftReg = 0;\r
557 Demod.state = DEMOD_RECEIVING_DATA;\r
558 }\r
559 break;\r
560\r
561 case DEMOD_RECEIVING_DATA:\r
562 MAKE_SOFT_DECISION();\r
563 if(Demod.posCount == 0) {\r
564 Demod.thisBit = v;\r
565 Demod.posCount = 1;\r
566 } else {\r
567 Demod.thisBit += v;\r
568\r
569 if(Demod.thisBit > 0) {\r
570 Demod.metric += Demod.thisBit;\r
571 } else {\r
572 Demod.metric -= Demod.thisBit;\r
573 }\r
574 (Demod.metricN)++;\r
575\r
576 Demod.shiftReg >>= 1;\r
577 if(Demod.thisBit > 0) {\r
578 Demod.shiftReg |= 0x200;\r
579 }\r
580\r
581 Demod.bitCount++;\r
582 if(Demod.bitCount == 10) {\r
583 WORD s = Demod.shiftReg;\r
584 if((s & 0x200) && !(s & 0x001)) {\r
585 BYTE b = (s >> 1);\r
586 Demod.output[Demod.len] = b;\r
587 Demod.len++;\r
588 Demod.state = DEMOD_AWAITING_START_BIT;\r
589 } else if(s == 0x000) {\r
590 // This is EOF\r
0e25ae11 591 LED_C_OFF();\r
6658905f 592 return TRUE;\r
593 Demod.state = DEMOD_UNSYNCD;\r
594 } else {\r
595 Demod.state = DEMOD_UNSYNCD;\r
596 }\r
597 }\r
598 Demod.posCount = 0;\r
599 }\r
600 break;\r
601\r
602 default:\r
603 Demod.state = DEMOD_UNSYNCD;\r
604 break;\r
605 }\r
606\r
0e25ae11 607 if (Demod.state == DEMOD_UNSYNCD) LED_C_OFF(); // Not synchronized...\r
6658905f 608 return FALSE;\r
609}\r
610\r
0e25ae11 611/*\r
612 * Demodulate the samples we received from the tag\r
613 * weTx: set to 'TRUE' if we behave like a reader\r
614 * set to 'FALSE' if we behave like a snooper\r
615 * quiet: set to 'TRUE' to disable debug output
616 */\r
fb25b483 617static void GetSamplesFor14443Demod(BOOL weTx, int n, BOOL quiet)\r
6658905f 618{\r
619 int max = 0;\r
620 BOOL gotFrame = FALSE;\r
621\r
622//# define DMA_BUFFER_SIZE 8\r
623 SBYTE *dmaBuf;\r
624\r
625 int lastRxCounter;\r
626 SBYTE *upTo;\r
627\r
628 int ci, cq;\r
629\r
630 int samples = 0;\r
631\r
632 // Clear out the state of the "UART" that receives from the tag.\r
633 memset(BigBuf, 0x44, 400);\r
634 Demod.output = (BYTE *)BigBuf;\r
635 Demod.len = 0;\r
636 Demod.state = DEMOD_UNSYNCD;\r
637\r
638 // And the UART that receives from the reader\r
639 Uart.output = (((BYTE *)BigBuf) + 1024);\r
640 Uart.byteCntMax = 100;\r
641 Uart.state = STATE_UNSYNCD;\r
642\r
643 // Setup for the DMA.\r
644 dmaBuf = (SBYTE *)(BigBuf + 32);\r
645 upTo = dmaBuf;\r
646 lastRxCounter = DMA_BUFFER_SIZE;\r
647 FpgaSetupSscDma((BYTE *)dmaBuf, DMA_BUFFER_SIZE);\r
648\r
0e25ae11 649 // Signal field is ON with the appropriate LED:\r
650 if (weTx) LED_D_ON(); else LED_D_OFF();\r
6658905f 651 // And put the FPGA in the appropriate mode\r
652 FpgaWriteConfWord(\r
653 FPGA_MAJOR_MODE_HF_READER_RX_XCORR | FPGA_HF_READER_RX_XCORR_848_KHZ |\r
654 (weTx ? 0 : FPGA_HF_READER_RX_XCORR_SNOOP));\r
655\r
656 for(;;) {\r
657 int behindBy = lastRxCounter - PDC_RX_COUNTER(SSC_BASE);\r
658 if(behindBy > max) max = behindBy;\r
659\r
6658905f 660 while(((lastRxCounter-PDC_RX_COUNTER(SSC_BASE)) & (DMA_BUFFER_SIZE-1))\r
661 > 2)\r
662 {\r
663 ci = upTo[0];\r
664 cq = upTo[1];\r
665 upTo += 2;\r
666 if(upTo - dmaBuf > DMA_BUFFER_SIZE) {\r
667 upTo -= DMA_BUFFER_SIZE;\r
668 PDC_RX_NEXT_POINTER(SSC_BASE) = (DWORD)upTo;\r
669 PDC_RX_NEXT_COUNTER(SSC_BASE) = DMA_BUFFER_SIZE;\r
670 }\r
671 lastRxCounter -= 2;\r
672 if(lastRxCounter <= 0) {\r
673 lastRxCounter += DMA_BUFFER_SIZE;\r
674 }\r
675\r
676 samples += 2;\r
677\r
678 Handle14443UartBit(1);\r
679 Handle14443UartBit(1);\r
680\r
681 if(Handle14443SamplesDemod(ci, cq)) {\r
682 gotFrame = 1;\r
683 }\r
684 }\r
6658905f 685\r
686 if(samples > 2000) {\r
687 break;\r
688 }\r
689 }\r
690 PDC_CONTROL(SSC_BASE) = PDC_RX_DISABLE;\r
fb25b483 691 if (!quiet) DbpIntegers(max, gotFrame, Demod.len);\r
6658905f 692}\r
693\r
694//-----------------------------------------------------------------------------\r
695// Read the tag's response. We just receive a stream of slightly-processed\r
696// samples from the FPGA, which we will later do some signal processing on,\r
697// to get the bits.\r
698//-----------------------------------------------------------------------------\r
699/*static void GetSamplesFor14443(BOOL weTx, int n)\r
700{\r
701 BYTE *dest = (BYTE *)BigBuf;\r
702 int c;\r
703\r
704 FpgaWriteConfWord(\r
705 FPGA_MAJOR_MODE_HF_READER_RX_XCORR | FPGA_HF_READER_RX_XCORR_848_KHZ |\r
706 (weTx ? 0 : FPGA_HF_READER_RX_XCORR_SNOOP));\r
707\r
708 c = 0;\r
709 for(;;) {\r
710 if(SSC_STATUS & (SSC_STATUS_TX_READY)) {\r
711 SSC_TRANSMIT_HOLDING = 0x43;\r
712 }\r
713 if(SSC_STATUS & (SSC_STATUS_RX_READY)) {\r
714 SBYTE b;\r
715 b = (SBYTE)SSC_RECEIVE_HOLDING;\r
716\r
717 dest[c++] = (BYTE)b;\r
718\r
719 if(c >= n) {\r
720 break;\r
721 }\r
722 }\r
723 }\r
724}*/\r
725\r
726//-----------------------------------------------------------------------------\r
727// Transmit the command (to the tag) that was placed in ToSend[].\r
728//-----------------------------------------------------------------------------\r
729static void TransmitFor14443(void)\r
730{\r
731 int c;\r
732\r
733 FpgaSetupSsc();\r
734\r
735 while(SSC_STATUS & (SSC_STATUS_TX_READY)) {\r
736 SSC_TRANSMIT_HOLDING = 0xff;\r
737 }\r
738\r
0e25ae11 739 // Signal field is ON with the appropriate Red LED\r
740 LED_D_ON();\r
741 // Signal we are transmitting with the Green LED\r
742 LED_B_ON();\r
743 FpgaWriteConfWord(\r
6658905f 744 FPGA_MAJOR_MODE_HF_READER_TX | FPGA_HF_READER_TX_SHALLOW_MOD);\r
745\r
746 for(c = 0; c < 10;) {\r
747 if(SSC_STATUS & (SSC_STATUS_TX_READY)) {\r
748 SSC_TRANSMIT_HOLDING = 0xff;\r
749 c++;\r
750 }\r
751 if(SSC_STATUS & (SSC_STATUS_RX_READY)) {\r
752 volatile DWORD r = SSC_RECEIVE_HOLDING;\r
753 (void)r;\r
754 }\r
755 WDT_HIT();\r
756 }\r
757\r
758 c = 0;\r
759 for(;;) {\r
760 if(SSC_STATUS & (SSC_STATUS_TX_READY)) {\r
761 SSC_TRANSMIT_HOLDING = ToSend[c];\r
762 c++;\r
763 if(c >= ToSendMax) {\r
764 break;\r
765 }\r
766 }\r
767 if(SSC_STATUS & (SSC_STATUS_RX_READY)) {\r
768 volatile DWORD r = SSC_RECEIVE_HOLDING;\r
769 (void)r;\r
770 }\r
771 WDT_HIT();\r
772 }\r
0e25ae11 773 LED_B_OFF(); // Finished sending\r
6658905f 774}\r
775\r
776//-----------------------------------------------------------------------------\r
777// Code a layer 2 command (string of octets, including CRC) into ToSend[],\r
778// so that it is ready to transmit to the tag using TransmitFor14443().\r
779//-----------------------------------------------------------------------------\r
780void CodeIso14443bAsReader(const BYTE *cmd, int len)\r
781{\r
782 int i, j;\r
783 BYTE b;\r
784\r
785 ToSendReset();\r
786\r
787 // Establish initial reference level\r
788 for(i = 0; i < 40; i++) {\r
789 ToSendStuffBit(1);\r
790 }\r
791 // Send SOF\r
792 for(i = 0; i < 10; i++) {\r
793 ToSendStuffBit(0);\r
794 }\r
795\r
796 for(i = 0; i < len; i++) {\r
797 // Stop bits/EGT\r
798 ToSendStuffBit(1);\r
799 ToSendStuffBit(1);\r
800 // Start bit\r
801 ToSendStuffBit(0);\r
802 // Data bits\r
803 b = cmd[i];\r
804 for(j = 0; j < 8; j++) {\r
805 if(b & 1) {\r
806 ToSendStuffBit(1);\r
807 } else {\r
808 ToSendStuffBit(0);\r
809 }\r
810 b >>= 1;\r
811 }\r
812 }\r
813 // Send EOF\r
814 ToSendStuffBit(1);\r
815 for(i = 0; i < 10; i++) {\r
816 ToSendStuffBit(0);\r
817 }\r
818 for(i = 0; i < 8; i++) {\r
819 ToSendStuffBit(1);\r
820 }\r
821\r
822 // And then a little more, to make sure that the last character makes\r
823 // it out before we switch to rx mode.\r
824 for(i = 0; i < 24; i++) {\r
825 ToSendStuffBit(1);\r
826 }\r
827\r
828 // Convert from last character reference to length\r
829 ToSendMax++;\r
830}\r
831\r
832//-----------------------------------------------------------------------------\r
833// Read an ISO 14443 tag. We send it some set of commands, and record the\r
fb25b483 834// responses.
835// The command name is misleading, it actually decodes the reponse in HEX
836// into the output buffer (read the result using hexsamples, not hisamples)\r
6658905f 837//-----------------------------------------------------------------------------\r
838void AcquireRawAdcSamplesIso14443(DWORD parameter)\r
839{\r
6658905f 840 BYTE cmd1[] = { 0x05, 0x00, 0x08, 0x39, 0x73 };\r
841\r
842 // Make sure that we start from off, since the tags are stateful;\r
843 // confusing things will happen if we don't reset them between reads.\r
6658905f 844 FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF);\r
0e25ae11 845 LED_D_OFF();\r
6658905f 846 SpinDelay(200);\r
847\r
848 SetAdcMuxFor(GPIO_MUXSEL_HIPKD);\r
849 FpgaSetupSsc();\r
850\r
851 // Now give it time to spin up.\r
0e25ae11 852 // Signal field is on with the appropriate LED\r
853 LED_D_ON();\r
6658905f 854 FpgaWriteConfWord(\r
855 FPGA_MAJOR_MODE_HF_READER_RX_XCORR | FPGA_HF_READER_RX_XCORR_848_KHZ);\r
856 SpinDelay(200);\r
857\r
858 CodeIso14443bAsReader(cmd1, sizeof(cmd1));\r
859 TransmitFor14443();\r
0e25ae11 860// LED_A_ON();\r
fb25b483 861 GetSamplesFor14443Demod(TRUE, 2000, FALSE);\r
0e25ae11 862// LED_A_OFF();\r
6658905f 863}\r
fb25b483 864
865//-----------------------------------------------------------------------------\r
866// Read a SRI512 ISO 14443 tag.\r
0e25ae11 867//
fb25b483 868// SRI512 tags are just simple memory tags, here we're looking at making a dump
869// of the contents of the memory. No anticollision algorithm is done, we assume
870// we have a single tag in the field.
871//
872// I tried to be systematic and check every answer of the tag, every CRC, etc...\r
873//-----------------------------------------------------------------------------\r
874void ReadSRI512Iso14443(DWORD parameter)\r
875{\r
876 BYTE i = 0x00;
877\r
878 // Make sure that we start from off, since the tags are stateful;\r
879 // confusing things will happen if we don't reset them between reads.\r
880 LED_D_OFF();\r
881 FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF);\r
882 SpinDelay(200);\r
883\r
884 SetAdcMuxFor(GPIO_MUXSEL_HIPKD);\r
885 FpgaSetupSsc();\r
886\r
887 // Now give it time to spin up.\r
0e25ae11 888 // Signal field is on with the appropriate LED\r
889 LED_D_ON();\r
fb25b483 890 FpgaWriteConfWord(\r
891 FPGA_MAJOR_MODE_HF_READER_RX_XCORR | FPGA_HF_READER_RX_XCORR_848_KHZ);\r
892 SpinDelay(200);\r
893
894 // First command: wake up the tag using the INITIATE command\r
895 BYTE cmd1[] = { 0x06, 0x00, 0x97, 0x5b};\r
896 CodeIso14443bAsReader(cmd1, sizeof(cmd1));\r
897 TransmitFor14443();\r
0e25ae11 898// LED_A_ON();\r
fb25b483 899 GetSamplesFor14443Demod(TRUE, 2000,TRUE);\r
0e25ae11 900// LED_A_OFF();\r
fb25b483 901
902 if (Demod.len == 0) {
903 DbpString("No response from tag");
904 return;
905 } else {
906 DbpString("Randomly generated UID from tag (+ 2 byte CRC):");
907 DbpIntegers(Demod.output[0], Demod.output[1],Demod.output[2]);
908 }
909 // There is a response, SELECT the uid
910 DbpString("Now SELECT tag:");
911 cmd1[0] = 0x0E; // 0x0E is SELECT
912 cmd1[1] = Demod.output[0];
913 ComputeCrc14443(CRC_14443_B, cmd1, 2, &cmd1[2], &cmd1[3]);\r
914 CodeIso14443bAsReader(cmd1, sizeof(cmd1));\r
915 TransmitFor14443();\r
0e25ae11 916// LED_A_ON();\r
fb25b483 917 GetSamplesFor14443Demod(TRUE, 2000,TRUE);\r
0e25ae11 918// LED_A_OFF();\r
fb25b483 919 if (Demod.len != 3) {
920 DbpString("Expected 3 bytes from tag, got:");
921 DbpIntegers(Demod.len,0x0,0x0);
922 return;
923 }
924 // Check the CRC of the answer:
925 ComputeCrc14443(CRC_14443_B, Demod.output, 1 , &cmd1[2], &cmd1[3]);\r
926 if(cmd1[2] != Demod.output[1] || cmd1[3] != Demod.output[2]) {\r
927 DbpString("CRC Error reading select response.");
928 return;
929 }
930 // Check response from the tag: should be the same UID as the command we just sent:
931 if (cmd1[1] != Demod.output[0]) {
932 DbpString("Bad response to SELECT from Tag, aborting:");
933 DbpIntegers(cmd1[1],Demod.output[0],0x0);
934 return;
935 }
936 // Tag is now selected,
1a093c19 937 // First get the tag's UID:
938 cmd1[0] = 0x0B;
939 ComputeCrc14443(CRC_14443_B, cmd1, 1 , &cmd1[1], &cmd1[2]);
940 CodeIso14443bAsReader(cmd1, 3); // Only first three bytes for this one\r
941 TransmitFor14443();\r
0e25ae11 942// LED_A_ON();\r
1a093c19 943 GetSamplesFor14443Demod(TRUE, 2000,TRUE);\r
0e25ae11 944// LED_A_OFF();\r
1a093c19 945 if (Demod.len != 10) {
946 DbpString("Expected 10 bytes from tag, got:");
947 DbpIntegers(Demod.len,0x0,0x0);
948 return;
949 }
950 // The check the CRC of the answer (use cmd1 as temporary variable):
951 ComputeCrc14443(CRC_14443_B, Demod.output, 8, &cmd1[2], &cmd1[3]);\r
952 if(cmd1[2] != Demod.output[8] || cmd1[3] != Demod.output[9]) {\r
953 DbpString("CRC Error reading block! - Below: expected, got");
954 DbpIntegers( (cmd1[2]<<8)+cmd1[3], (Demod.output[8]<<8)+Demod.output[9],0);
955 // Do not return;, let's go on... (we should retry, maybe ?)
956 }
957 DbpString("Tag UID (64 bits):");
958 DbpIntegers((Demod.output[7]<<24) + (Demod.output[6]<<16) + (Demod.output[5]<<8) + Demod.output[4], (Demod.output[3]<<24) + (Demod.output[2]<<16) + (Demod.output[1]<<8) + Demod.output[0], 0);
959
960 // Now loop to read all 16 blocks, address from 0 to 15
fb25b483 961 DbpString("Tag memory dump, block 0 to 15");
962 cmd1[0] = 0x08;
963 i = 0x00;
964 for (;;) {
965 if (i == 0x10) {
966 DbpString("System area block (0xff):");
967 i = 0xff;
968 }
969 cmd1[1] = i;
970 ComputeCrc14443(CRC_14443_B, cmd1, 2, &cmd1[2], &cmd1[3]);\r
971 CodeIso14443bAsReader(cmd1, sizeof(cmd1));\r
972 TransmitFor14443();\r
0e25ae11 973// LED_A_ON();\r
fb25b483 974 GetSamplesFor14443Demod(TRUE, 2000,TRUE);\r
0e25ae11 975// LED_A_OFF();
fb25b483 976 if (Demod.len != 6) { // Check if we got an answer from the tag
977 DbpString("Expected 6 bytes from tag, got less...");
978 return;
979 }
980 // The check the CRC of the answer (use cmd1 as temporary variable):
981 ComputeCrc14443(CRC_14443_B, Demod.output, 4, &cmd1[2], &cmd1[3]);\r
982 if(cmd1[2] != Demod.output[4] || cmd1[3] != Demod.output[5]) {\r
1a093c19 983 DbpString("CRC Error reading block! - Below: expected, got");
fb25b483 984 DbpIntegers( (cmd1[2]<<8)+cmd1[3], (Demod.output[4]<<8)+Demod.output[5],0);
985 // Do not return;, let's go on... (we should retry, maybe ?)
986 }
987 // Now print out the memory location:
988 DbpString("Address , Contents, CRC");
1a093c19 989 DbpIntegers(i, (Demod.output[3]<<24) + (Demod.output[2]<<16) + (Demod.output[1]<<8) + Demod.output[0], (Demod.output[4]<<8)+Demod.output[5]);
fb25b483 990 if (i == 0xff) {
991 break;
992 }
993 i++;
994 }
995}\r
996
6658905f 997\r
998//=============================================================================\r
999// Finally, the `sniffer' combines elements from both the reader and\r
1000// simulated tag, to show both sides of the conversation.\r
1001//=============================================================================\r
1002\r
1003//-----------------------------------------------------------------------------\r
1004// Record the sequence of commands sent by the reader to the tag, with\r
1005// triggering so that we start recording at the point that the tag is moved\r
1006// near the reader.\r
1007//-----------------------------------------------------------------------------\r
0e25ae11 1008/*\r
1009 * Memory usage for this function, (within BigBuf)\r
1010 * 0-1023 : Demodulated samples receive (1024 bytes)\r
1011 * 1024-1535 : Last Received command, 512 bytes (reader->tag)\r
1012 * 1536-2047 : Last Received command, 512 bytes(tag->reader)\r
1013 * 2048-2304 : DMA Buffer, 256 bytes (samples)
1014 */\r
6658905f 1015void SnoopIso14443(void)\r
1016{\r
1017 // We won't start recording the frames that we acquire until we trigger;\r
1018 // a good trigger condition to get started is probably when we see a\r
1019 // response from the tag.\r
1020 BOOL triggered = FALSE;\r
1021\r
1022 // The command (reader -> tag) that we're working on receiving.\r
0e25ae11 1023 BYTE *receivedCmd = (BYTE *)(BigBuf) + 1024;\r
6658905f 1024 // The response (tag -> reader) that we're working on receiving.\r
0e25ae11 1025 BYTE *receivedResponse = (BYTE *)(BigBuf) + 1536;\r
6658905f 1026\r
1027 // As we receive stuff, we copy it from receivedCmd or receivedResponse\r
1028 // into trace, along with its length and other annotations.\r
1029 BYTE *trace = (BYTE *)BigBuf;\r
1030 int traceLen = 0;\r
1031\r
1032 // The DMA buffer, used to stream samples from the FPGA.\r
0e25ae11 1033 SBYTE *dmaBuf = (SBYTE *)(BigBuf) + 2048;\r
6658905f 1034 int lastRxCounter;\r
1035 SBYTE *upTo;\r
1036 int ci, cq;\r
1037 int maxBehindBy = 0;\r
1038\r
1039 // Count of samples received so far, so that we can include timing\r
1040 // information in the trace buffer.\r
1041 int samples = 0;\r
1042\r
0e25ae11 1043 // Initialize the trace buffer\r
1044 memset(trace, 0x44, 1024);\r
6658905f 1045\r
1046 // Set up the demodulator for tag -> reader responses.\r
1047 Demod.output = receivedResponse;\r
1048 Demod.len = 0;\r
1049 Demod.state = DEMOD_UNSYNCD;\r
1050\r
1051 // And the reader -> tag commands\r
1052 memset(&Uart, 0, sizeof(Uart));\r
1053 Uart.output = receivedCmd;\r
1054 Uart.byteCntMax = 100;\r
1055 Uart.state = STATE_UNSYNCD;\r
1056\r
1057 // And put the FPGA in the appropriate mode\r
0e25ae11 1058 // Signal field is off with the appropriate LED\r
1059 LED_D_OFF();\r
6658905f 1060 FpgaWriteConfWord(\r
1061 FPGA_MAJOR_MODE_HF_READER_RX_XCORR | FPGA_HF_READER_RX_XCORR_848_KHZ |\r
1062 FPGA_HF_READER_RX_XCORR_SNOOP);\r
1063 SetAdcMuxFor(GPIO_MUXSEL_HIPKD);\r
1064\r
1065 // Setup for the DMA.\r
1066 FpgaSetupSsc();\r
1067 upTo = dmaBuf;\r
1068 lastRxCounter = DMA_BUFFER_SIZE;\r
1069 FpgaSetupSscDma((BYTE *)dmaBuf, DMA_BUFFER_SIZE);\r
6658905f 1070 // And now we loop, receiving samples.\r
1071 for(;;) {\r
0e25ae11 1072 int behindBy = (lastRxCounter - PDC_RX_COUNTER(SSC_BASE)) &\r
6658905f 1073 (DMA_BUFFER_SIZE-1);\r
1074 if(behindBy > maxBehindBy) {\r
1075 maxBehindBy = behindBy;\r
0e25ae11 1076 if(behindBy > (DMA_BUFFER_SIZE-2)) { // TODO: understand whether we can increase/decrease as we want or not?\r
6658905f 1077 DbpString("blew circular buffer!");\r
0e25ae11 1078 DbpIntegers(behindBy,0,0);\r
6658905f 1079 goto done;\r
1080 }\r
1081 }\r
1082 if(behindBy < 2) continue;\r
1083\r
1084 ci = upTo[0];\r
1085 cq = upTo[1];\r
1086 upTo += 2;\r
1087 lastRxCounter -= 2;\r
1088 if(upTo - dmaBuf > DMA_BUFFER_SIZE) {\r
1089 upTo -= DMA_BUFFER_SIZE;\r
1090 lastRxCounter += DMA_BUFFER_SIZE;\r
0e25ae11 1091 PDC_RX_NEXT_POINTER(SSC_BASE) = (DWORD) upTo;\r
6658905f 1092 PDC_RX_NEXT_COUNTER(SSC_BASE) = DMA_BUFFER_SIZE;\r
1093 }\r
1094\r
1095 samples += 2;\r
1096\r
1097#define HANDLE_BIT_IF_BODY \\r
1098 if(triggered) { \\r
1099 trace[traceLen++] = ((samples >> 0) & 0xff); \\r
1100 trace[traceLen++] = ((samples >> 8) & 0xff); \\r
1101 trace[traceLen++] = ((samples >> 16) & 0xff); \\r
1102 trace[traceLen++] = ((samples >> 24) & 0xff); \\r
1103 trace[traceLen++] = 0; \\r
1104 trace[traceLen++] = 0; \\r
1105 trace[traceLen++] = 0; \\r
1106 trace[traceLen++] = 0; \\r
1107 trace[traceLen++] = Uart.byteCnt; \\r
1108 memcpy(trace+traceLen, receivedCmd, Uart.byteCnt); \\r
1109 traceLen += Uart.byteCnt; \\r
1110 if(traceLen > 1000) break; \\r
1111 } \\r
1112 /* And ready to receive another command. */ \\r
1113 memset(&Uart, 0, sizeof(Uart)); \\r
1114 Uart.output = receivedCmd; \\r
1115 Uart.byteCntMax = 100; \\r
1116 Uart.state = STATE_UNSYNCD; \\r
1117 /* And also reset the demod code, which might have been */ \\r
1118 /* false-triggered by the commands from the reader. */ \\r
1119 memset(&Demod, 0, sizeof(Demod)); \\r
1120 Demod.output = receivedResponse; \\r
1121 Demod.state = DEMOD_UNSYNCD; \\r
1122\r
1123 if(Handle14443UartBit(ci & 1)) {\r
1124 HANDLE_BIT_IF_BODY\r
1125 }\r
1126 if(Handle14443UartBit(cq & 1)) {\r
1127 HANDLE_BIT_IF_BODY\r
1128 }\r
1129\r
1130 if(Handle14443SamplesDemod(ci, cq)) {\r
1131 // timestamp, as a count of samples\r
1132 trace[traceLen++] = ((samples >> 0) & 0xff);\r
1133 trace[traceLen++] = ((samples >> 8) & 0xff);\r
1134 trace[traceLen++] = ((samples >> 16) & 0xff);\r
1135 trace[traceLen++] = 0x80 | ((samples >> 24) & 0xff);\r
1136 // correlation metric (~signal strength estimate)\r
1137 if(Demod.metricN != 0) {\r
1138 Demod.metric /= Demod.metricN;\r
1139 }\r
1140 trace[traceLen++] = ((Demod.metric >> 0) & 0xff);\r
1141 trace[traceLen++] = ((Demod.metric >> 8) & 0xff);\r
1142 trace[traceLen++] = ((Demod.metric >> 16) & 0xff);\r
1143 trace[traceLen++] = ((Demod.metric >> 24) & 0xff);\r
1144 // length\r
1145 trace[traceLen++] = Demod.len;\r
1146 memcpy(trace+traceLen, receivedResponse, Demod.len);\r
1147 traceLen += Demod.len;\r
1148 if(traceLen > 1000) break;\r
1149\r
1150 triggered = TRUE;\r
6658905f 1151\r
1152 // And ready to receive another response.\r
1153 memset(&Demod, 0, sizeof(Demod));\r
1154 Demod.output = receivedResponse;\r
1155 Demod.state = DEMOD_UNSYNCD;\r
1156 }\r
0e25ae11 1157 WDT_HIT();\r
6658905f 1158\r
1159 if(BUTTON_PRESS()) {\r
1160 DbpString("cancelled");\r
1161 goto done;\r
1162 }\r
1163 }\r
1164\r
1165 DbpString("in done pt");\r
1166\r
1167 DbpIntegers(maxBehindBy, Uart.state, Uart.byteCnt);\r
1168 DbpIntegers(Uart.byteCntMax, traceLen, 0x23);\r
1169\r
1170done:\r
0e25ae11 1171 LED_D_OFF();\r
6658905f 1172 PDC_CONTROL(SSC_BASE) = PDC_RX_DISABLE;\r
6658905f 1173}\r
Impressum, Datenschutz