一、模块简短介绍

有关RC522模块的背景知识以及工作原理,本次实验暂时不做分享,这里贴上两位写得很好的博主,大家可自行查看。

(5条消息) RC522(RFID)读写驱动_桃成蹊2.0的博客-CSDN博客https://blog.csdn.net/m0_51220742/article/details/123683745 ; (5条消息) STM32–RFID无线射频技术(RC522刷卡模块)_rc522射频模块详细资料_y黎好好的博客-CSDN博客https://blog.csdn.net/weixin_45771489/article/details/124079134 ;

该模块价格普遍实惠,所以我买了一个自己玩玩。经过一段时间的摸爬滚打目前能够正常读写,后续会继续完善该模块的使用,并加入到其他嵌入式设计中。

二、开发资料的使用及经验分享

该模块的API函数淘宝有很多资源,数据手册也有,不过是英文版的。建议大家有时间还是要过一数据手册的重要部分,比如寄存器和指令集,通信协议等,可以使用知云文献查看,我习惯用它来看论文,适合翻译长段大段。也可以用WPS的翻译,小部分翻译,看个人使用习惯。要是有能力直接撸原文那就更好,节约时间。

找过很多资料,提供的API无非就是一套没有章法,毫无可读性的杂乱代码,十分影响学习效果。这里建议大家不要去死磕,可以看本次实验的代码逻辑和上文提到的两位博主,每一个底层函数都有注释,在一些底层函数的逻辑上可以结合这些API函数进行反推或者校验,可以帮助读者理解实现原理。

一开始是通过数据手册,硬着头皮啃无良店铺给的代码,没有注释,没有逻辑,没有章法。虽然没报错,但是能不能用完全不知道,就是这一步就花了好几天时间,所以建议大家不要像我一样埋头苦读,可以先看每一个函数的中文注释,整理出一个框架。

三、源码解析

代码总体逻辑没有变化,参考了两位博主的思路,我用官方的API函数进行修改。两位博主一位使用的软件实现SPI协议,一位使用板载SPI。两种方法我都试过,但是软件SPI协议总是通信失败,不知道是我协议写的不对还是通信速度和模块不匹配,暂时还未解决。

软件模拟SPI

1
2
3
4
5
6
7
8
9
10
11
/*
* 函数名:SOFT_SPI_RC522_SendByte
* 描述 :向RC522发送1 Byte 数据
* 输入 :write_dat,要发送的数据
* 返回 : RC522返回的数据
*/
void SOFT_SPI_RC522_SendByte(uint8_t write_dat)
{
uint8_t i;

for(i=0;i

有大佬如果发现问题,还请给我留言,也不知道是什么原因导致通信失败。

板载SPI通信

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
/*
* 函数名:SPI_WriteNBytes
* 描述 :向RC522发送n Byte 数据
* 输入 :SPIx : 要发送数据的SPI
* 输入 :p_TxData : 要发送的数据
* 输入 :sendDataNum : 要发送的数据量(Byte)
* 返回 : 0
*/
int SPI_WriteNBytes(SPI_TypeDef* SPIx, uint8_t *p_TxData,uint32_t sendDataNum)
{
int retry=0;
while(sendDataNum--){
while((SPIx->SR&SPI_FLAG_TXE)==0)//等待发送区空
{
retry++;
if(retry>20000)return -1;
}
SPIx->DR=*p_TxData++;//发送一个byte
retry=0;
while((SPIx->SR&SPI_FLAG_RXNE)==0)//等待接收完一个byte
{
SPIx->SR = SPIx->SR;
retry++;
if(retry>20000)return -1;
}
SPIx->DR;
}
return 0;
}

/*
* 函数名:SPI_ReadNBytes
* 描述 :读取RC522 n Byte 数据
* 输入 :SPIx : 要读取数据的SPI
* 输入 :p_RxData : 要读取的数据
* 输入 :readDataNum : 要读取的数据量(Byte)
* 返回 : 0
*/
int SPI_ReadNBytes(SPI_TypeDef* SPIx, uint8_t *p_RxData,uint32_t readDataNum)
{
int retry=0;
while(readDataNum--){
SPIx->DR = 0xFF;
while(!(SPIx->SR&SPI_FLAG_TXE)){
retry++;
if(retry>20000)return -1;
}
retry = 0;
while(!(SPIx->SR&SPI_FLAG_RXNE)){
retry++;
if(retry>20000)return -1;
}
*p_RxData++ = SPIx->DR;
}
return 0;
}

本实验使用读取寄存器状态来完成SPI通信,未使用HAL_SPI_TransmitReceive库函数。

源码部分

主函数比较简略,调用一个操作函数,用按键触发的方式启动该函数实现读写。main.c 中,加入以下代码:

main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
   RC522_Init();
uint8_t key;

uint8_t Write_Card_Data[16]={0};//要写入的数据

while(1)
{
key = key_scan();//按键扫描函数,自己定义修改
if(key==1)
{
RC522_Start(5,readID,Write_Card_Data);//readID 读命令
}
else if(key==2)
{
RC522_Start(5,writeID,Write_Card_Data);//writeID 写命令
}
delay_ms(100);
LED0=!LED0;
}

RC522部分

RC522.c

其中RC522_Start是自定义函数,只要操作流程正确,可自行编写想要的操作方式,本实验只完成简单的单块读写操作。蓝卡和白卡的卡号根据自己的修改,可以通过手机NFC功能查看卡号,同时也能查看扇区数据,APP可以使用上文提到的博主推荐的NFC Writer。

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
#include "RC522.h"
#include "stdio.h"
#include "delay.h"
#include "spi.h"
#include "oled.h"
#include "string.h"

// M1卡分为16个扇区,每个扇区由四个块(块0、块1、块2、块3)组成
// 将16个扇区的64个块按绝对地址编号为:0~63,每块16个字节
// 第0个扇区的块0(即绝对地址0块),用于存放厂商代码,已经固化不可更改
// 每个扇区的块0、块1、块2为数据块,可用于存放数据
// 每个扇区的块3为控制块(绝对地址为:块3、块7、块11.....)包括密码A,存取控制、密码B等
void RC522_Init(void)
{

RC522_SPI_GPIO_Init();//初始化RC522的复位引脚和片选引脚

SPI1_Init();//初始化SPI

delay_ms(50);
PcdReset();//复位RC522读卡器

delay_ms(10);
PcdAntennaOff();//关闭天线发射

delay_ms(10);
PcdAntennaOn();//开启天线发射

printf("RFID-MFRC522 初始化完成\nPress KEY0 Or KEY1 To Start...\r\n"); //初始化完成
}

uint8_t IC_UID[4]; //UID卡片序列号,4字节
uint8_t IC_Type[2];//卡类型代码,2字节,0x0400,Mifare_One(S50)
uint8_t card_1[4]={0xC3,0xB2,0x37,0xC5};//蓝卡1卡号
uint8_t card_2[4]={0xA3,0x09,0x3C,0xFB};//白卡2卡号
uint8_t ID_num=0;//当前操作的序号
uint8_t Card_KEY[6]={0xff,0xff,0xff,0xff,0xff,0xff};//验证密码
uint8_t Card_Data[16];//读取出的块数据
/*
* 函数名:RC522_Start
* 输入 block:要操作的块地址
* 输入 option:读操作或写操作
* readID 1//读
* writeID 2//写
* 输入 block:要操作的块地址
* 描述 :RC522操作主函数,可完成对卡的读写操作
* * 返回 : 状态值
* = 1,成功
*/
void RC522_Start(uint8_t block,uint8_t option,uint8_t *Write_Card_Data)
{

if(PcdRequest(PICC_REQALL,IC_Type) == MI_OK)//寻卡
{
uint16_t cardType = (IC_Type[0] << 8) | IC_Type[1];
switch (cardType)
{
case 0x4400:
printf("\r\nMifare UltraLight\r\n");
break;
case 0x0400:
printf("\r\nMifare One(S50)\r\n");
break;
case 0x0200:
printf("\r\nMifare One(S70)\r\n");
break;
case 0x0800:
printf("\r\nMifare Pro(X)\r\n");
break;
case 0x4403:
printf("\r\nMifare DESFire\r\n");
break;
default:
printf("\r\nUnknown Card\r\n");
break;
}
if(PcdAnticoll(IC_UID)==MI_OK)//防冲撞
{

if((IC_UID[0]==card_1[0])&&(IC_UID[1]==card_1[1])&&(IC_UID[2]==card_1[2])&&(IC_UID[3]==card_1[3]))
{
ID_num=1;
printf("The User is: %d, Blue card",ID_num);
}
else if((IC_UID[0]==card_2[0])&&(IC_UID[1]==card_2[1])&&(IC_UID[2]==card_2[2])&&(IC_UID[3]==card_2[3]))
{
ID_num=2;
printf("The User is: %d, White card",ID_num);
}
printf("\r\ncard_ID: %02X:%02X:%02X:%02X\r\n",IC_UID[0],IC_UID[1],IC_UID[2],IC_UID[3]); //打印卡的UID号

if(PcdSelect(IC_UID)==MI_OK)//选卡
{
if(PcdAuthState(PICC_AUTHENT1A,block,Card_KEY,IC_UID)==MI_OK)//验证A密钥,对应块
{
// memset(Card_Data,1,16);
if(option==readID)//读操作
{
if(PcdRead(block,Card_Data)==MI_OK)
{
printf("\n读取结果: \n");
printf("block %d date:\r\n",block);
categories:
- stm32
- stm32hal库开发
tags:
- stm32
- 外设
for(int i=0;iSR&SPI_FLAG_TXE)==0)//等待发送区空
{
retry++;
if(retry>20000)return -1;
}
SPIx->DR=*p_TxData++;//发送一个byte
retry=0;
while((SPIx->SR&SPI_FLAG_RXNE)==0)//等待接收完一个byte
{
SPIx->SR = SPIx->SR;
retry++;
if(retry>20000)return -1;
}
SPIx->DR;
}
return 0;
}

/*
* 函数名:SPI_ReadNBytes
* 描述 :读取RC522 n Byte 数据
* 输入 :SPIx : 要读取数据的SPI
* 输入 :p_RxData : 要读取的数据
* 输入 :readDataNum : 要读取的数据量(Byte)
* 返回 : 0
*/
int SPI_ReadNBytes(SPI_TypeDef* SPIx, uint8_t *p_RxData,uint32_t readDataNum)
{
int retry=0;
while(readDataNum--){
SPIx->DR = 0xFF;
while(!(SPIx->SR&SPI_FLAG_TXE)){
retry++;
if(retry>20000)return -1;
}
retry = 0;
while(!(SPIx->SR&SPI_FLAG_RXNE)){
retry++;
if(retry>20000)return -1;
}
*p_RxData++ = SPIx->DR;
}
return 0;
}

/*
* 函数名:PcdComMF522
* 描述 :通过RC522和ISO14443卡通讯
* 输入 :ucCommand,RC522命令字
* pInData,通过RC522发送到卡片的数据
* ucInLenByte,发送数据的字节长度
* pOutData,接收到的卡片返回数据
* pOutLenBit,返回数据的位长度
* 返回 : 状态值
* = MI_OK,成功
* 调用 :内部调用
*/
char PcdComMF522(uint8_t Command,uint8_t *pInData,uint8_t InLenByte,uint8_t *pOutData,uint32_t *pOutLenBit)
{
char status = MI_ERR;
uint8_t irqEn = 0x00;
uint8_t waitFor = 0x00;
uint8_t lastBits;
uint8_t n;
uint32_t i;
switch (Command)
{
case PCD_AUTHENT: //Mifare认证
irqEn = 0x12; //允许错误中断请求ErrIEn 允许空闲中断IdleIEn
waitFor = 0x10; //认证寻卡等待时候 查询空闲中断标志位
break;
case PCD_TRANSCEIVE://接收发送 发送接收
irqEn = 0x77; //允许TxIEn RxIEn IdleIEn LoAlertIEn ErrIEn TimerIEn
waitFor = 0x30; //寻卡等待时候 查询接收中断标志位与 空闲中断标志位
break;
default:
break;
}
WriteRawRC(ComIEnReg, irqEn | 0x80); //IRqInv置位管脚IRQ与Status1Reg的IRq位的值相反
ClearBitMask(ComIrqReg, 0x80); //Set1该位清零时,CommIRqReg的屏蔽位清零
WriteRawRC(CommandReg, PCD_IDLE); //写空闲命令
SetBitMask(FIFOLevelReg, 0x80); //置位FlushBuffer清除内部FIFO的读和写指针以及ErrReg的BufferOvfl标志位被清除
for (i = 0; i < InLenByte; i++)
{
WriteRawRC(FIFODataReg, pInData[i]);//写数据进FIFOdata
}
WriteRawRC(CommandReg, Command);//写命令
if (Command == PCD_TRANSCEIVE)
{
SetBitMask(BitFramingReg, 0x80);//StartSend置位启动数据发送 该位与收发命令使用时才有效
}
i = 800;//根据时钟频率调整,操作M1卡最大等待时间25ms
do
{
n = ReadRawRC(ComIrqReg);//查询事件中断
i--;
} while ((i != 0) && !(n & 0x01) && !(n & waitFor));//退出条件i=0,定时器中断,与写空闲命令
ClearBitMask(BitFramingReg, 0x80);//清理允许StartSend位
if (i != 0)
{
if (!(ReadRawRC(ErrorReg) & 0x1B))//读错误标志寄存器BufferOfI CollErr ParityErr ProtocolErr
{
status = MI_OK;
if (n & irqEn & 0x01)//是否发生定时器中断
{
status = MI_NOTAGERR;
}
if (Command == PCD_TRANSCEIVE)
{
n = ReadRawRC(FIFOLevelReg);//读FIFO中保存的字节数
lastBits = ReadRawRC(ControlReg) & 0x07;//最后接收到得字节的有效位数
if (lastBits)
{
*pOutLenBit = (n - 1) * 8 + lastBits;//N个字节数减去1(最后一个字节)+最后一位的位数 读取到的数据总位数
}
else
{
*pOutLenBit = n * 8;//最后接收到的字节整个字节有效
}
if (n == 0)
{
n = 1;
}
if (n > MAXRLEN)
{
n = MAXRLEN;
}
for (i = 0; i < n; i++)
{
pOutData[i] = ReadRawRC(FIFODataReg);
}
}
}
else
{
status = MI_ERR;
}
}
SetBitMask(ControlReg, 0x80); // stop timer now
WriteRawRC(CommandReg, PCD_IDLE);
return status;
}

/*
* 函数名:PcdRequest
* 描述 :寻卡
* 输入 :ucReq_code,寻卡方式
* = 0x52,寻感应区内所有符合14443A标准的卡
* = 0x26,寻未进入休眠状态的卡
* pTagType,卡片类型代码
* = 0x4400,Mifare_UltraLight
* = 0x0400,Mifare_One(S50)
* = 0x0200,Mifare_One(S70)
* = 0x0800,Mifare_Pro(X))
* = 0x4403,Mifare_DESFire
* 返回 : 状态值
* = MI_OK,成功
*/
char PcdRequest(uint8_t req_code, uint8_t *pTagType)
{
char status;
uint32_t unLen;
uint8_t ucComMF522Buf[MAXRLEN];

ClearBitMask(Status2Reg, 0x08); //清除RC522寄存位,清理指示MIFARECyptol单元接通以及所有卡的数据通信被加密的情况
WriteRawRC(BitFramingReg, 0x07); //写RC522寄存器,发送的最后一个字节的七位
SetBitMask(TxControlReg, 0x03); 写RC522寄存位,TX1,TX2管脚的输出信号传递经发送调制的13.56的能量载波信号

ucComMF522Buf[0] = req_code;//存入寻卡方式

status = PcdComMF522(PCD_TRANSCEIVE, ucComMF522Buf, 1, ucComMF522Buf, &unLen);
if ((status == MI_OK) && (unLen == 0x10))//寻卡成功返回卡类型
{
*pTagType = ucComMF522Buf[0];
*(pTagType + 1) = ucComMF522Buf[1];
}
else
{
status = MI_ERR;
}
return status;
}

/*
* 函数名:PcdAnticoll
* 描述 :防冲撞
* 输入 :pSnr,卡片序列号,4字节
* 返回 : 状态值
* = MI_OK,成功
*/
char PcdAnticoll(uint8_t *pSnr)
{
char status;
uint8_t i, snr_check = 0;
uint32_t unLen;
uint8_t ucComMF522Buf[MAXRLEN];
ClearBitMask(Status2Reg, 0x08);//清MFCryptol On位,只有成功执行MFAuthent命令后,该位才能置位
WriteRawRC(BitFramingReg, 0x00);//清理寄存器,停止收发
ClearBitMask(CollReg, 0x80);//清ValuesAfterColl所有接收的位在冲突后被清除
ucComMF522Buf[0] = PICC_ANTICOLL1;//卡片防冲突命令
ucComMF522Buf[1] = 0x20;

status = PcdComMF522(PCD_TRANSCEIVE, ucComMF522Buf, 2, ucComMF522Buf, &unLen);
if (status == MI_OK)
{
for (i = 0; i < 4; i++)
{
*(pSnr + i) = ucComMF522Buf[i];
snr_check ^= ucComMF522Buf[i];
}
if (snr_check != ucComMF522Buf[i])
{
status = MI_ERR;
}
}
SetBitMask(CollReg, 0x80);
return status;
}

/*
* 函数名:PcdSelect
* 描述 :选定卡片
* 输入 :pSnr,卡片序列号,4字节
* 返回 : 状态值
* = MI_OK,成功
*/
char PcdSelect(uint8_t *pSnr)
{
char status;
uint8_t i;
uint32_t unLen;
uint8_t ucComMF522Buf[MAXRLEN];
ucComMF522Buf[0] = PICC_ANTICOLL1;//防冲撞
ucComMF522Buf[1] = 0x70;
ucComMF522Buf[6] = 0;
for (i = 0; i < 4; i++)
{
ucComMF522Buf[i + 2] = *(pSnr + i);
ucComMF522Buf[6] ^= *(pSnr + i);
}
CalulateCRC(ucComMF522Buf, 7, &ucComMF522Buf[7]);
ClearBitMask(Status2Reg, 0x08);
status = PcdComMF522(PCD_TRANSCEIVE, ucComMF522Buf, 9, ucComMF522Buf, &unLen);
if ((status == MI_OK) && (unLen == 0x18))
{
status = MI_OK;
}
else
{
status = MI_ERR;
}
return status;
}

/*
* 函数名:PcdAuthState
* 描述 :验证卡片密码
* 输入 :ucAuth_mode,密码验证模式
* = 0x60,验证A密钥
* = 0x61,验证B密钥
* uint8_t ucAddr,块地址
* pKey,密码
* pSnr,卡片序列号,4字节
* 返回 : 状态值
* = MI_OK,成功
*/
char PcdAuthState(uint8_t auth_mode, uint8_t addr, uint8_t *pKey, uint8_t *pSnr)
{
char status;
uint32_t unLen;
uint8_t i, ucComMF522Buf[MAXRLEN];
ucComMF522Buf[0] = auth_mode;
ucComMF522Buf[1] = addr;
for (i = 0; i < 6; i++)
{
ucComMF522Buf[i + 2] = *(pKey + i);
}
for (i = 0; i < 6; i++)
{
ucComMF522Buf[i + 8] = *(pSnr + i);
}
status = PcdComMF522(PCD_AUTHENT, ucComMF522Buf, 12, ucComMF522Buf, &unLen);
if ((status != MI_OK) || (!(ReadRawRC(Status2Reg) & 0x08)))
{
status = MI_ERR;
}
return status;
}

/*
* 函数名:PcdRead
* 描述 :读取M1卡一块数据
* 输入 :uint8_t ucAddr,块地址
* pData,读出的数据,16字节
* 返回 : 状态值
* = MI_OK,成功
* 调用 :外部调用
*/
char PcdRead(uint8_t addr, uint8_t *pData)
{
char status;
uint32_t unLen;
uint8_t i, ucComMF522Buf[MAXRLEN];
ucComMF522Buf[0] = PICC_READ;
ucComMF522Buf[1] = addr;
CalulateCRC(ucComMF522Buf, 2, &ucComMF522Buf[2]);

status = PcdComMF522(PCD_TRANSCEIVE, ucComMF522Buf, 4, ucComMF522Buf, &unLen);
if ((status == MI_OK) && (unLen == 0x90))
{
for (i = 0; i < 16; i++)
{
*(pData + i) = ucComMF522Buf[i];
}
}
else
{
status = MI_ERR;
}
return status;
}

/*
* 函数名:PcdWrite
* 描述 :写数据到M1卡一块
* 输入 :uint8_t ucAddr,块地址
* pData,写入的数据,16字节
* 返回 : 状态值
* = MI_OK,成功
* 调用 :外部调用
*/
char PcdWrite(uint8_t addr, uint8_t *pData)
{
char status;
uint32_t unLen;
uint8_t i, ucComMF522Buf[MAXRLEN];
ucComMF522Buf[0] = PICC_WRITE;
ucComMF522Buf[1] = addr;
CalulateCRC(ucComMF522Buf, 2, &ucComMF522Buf[2]);
status = PcdComMF522(PCD_TRANSCEIVE, ucComMF522Buf, 4, ucComMF522Buf, &unLen);
if ((status != MI_OK) || (unLen != 4) || ((ucComMF522Buf[0] & 0x0F) != 0x0A))
{
status = MI_ERR;
}
if (status == MI_OK)
{
for (i = 0; i < 16; i++)
{
ucComMF522Buf[i] = *(pData + i);
}
CalulateCRC(ucComMF522Buf, 16, &ucComMF522Buf[16]);

status = PcdComMF522(PCD_TRANSCEIVE, ucComMF522Buf, 18, ucComMF522Buf, &unLen);
if ((status != MI_OK) || (unLen != 4) || ((ucComMF522Buf[0] & 0x0F) != 0x0A))
{
status = MI_ERR;
}
}
return status;
}

/*
* 函数名:PcdValue
* 描述 :扣款和充值
* 输入 :dd_mode[IN]:命令字
* 0xC0 = 扣款
* 0xC1 = 充值
* addr[IN]:钱包地址
* pValue[IN]:4字节增(减)值,低位在前
* 返回 : 状态值
* = MI_OK,成功
*/
char PcdValue(uint8_t dd_mode, uint8_t addr, uint8_t *pValue)
{
char status;
uint32_t unLen;
uint8_t i, ucComMF522Buf[MAXRLEN];
ucComMF522Buf[0] = dd_mode;
ucComMF522Buf[1] = addr;
CalulateCRC(ucComMF522Buf, 2, &ucComMF522Buf[2]);
status = PcdComMF522(PCD_TRANSCEIVE, ucComMF522Buf, 4, ucComMF522Buf, &unLen);

if ((status != MI_OK) || (unLen != 4) || ((ucComMF522Buf[0] & 0x0F) != 0x0A))
{
status = MI_ERR;
}
if (status == MI_OK)
{
for (i = 0; i < 16; i++)
{
ucComMF522Buf[i] = *(pValue + i);
}
CalulateCRC(ucComMF522Buf, 4, &ucComMF522Buf[4]);
unLen = 0;
status = PcdComMF522(PCD_TRANSCEIVE, ucComMF522Buf, 6, ucComMF522Buf, &unLen);
if (status != MI_ERR)
{
status = MI_OK;
}
}
if (status == MI_OK)
{
ucComMF522Buf[0] = PICC_TRANSFER;
ucComMF522Buf[1] = addr;
CalulateCRC(ucComMF522Buf, 2, &ucComMF522Buf[2]);

status = PcdComMF522(PCD_TRANSCEIVE, ucComMF522Buf, 4, ucComMF522Buf, &unLen);

if ((status != MI_OK) || (unLen != 4) || ((ucComMF522Buf[0] & 0x0F) != 0x0A))
{
status = MI_ERR;
}
}
return status;
}

/*
* 函数名:PcdBakValue
* 描述 :备份钱包
* 输入 :sourceaddr[IN]:源地址
* goaladdr[IN]:目标地址
* 返回 : 状态值
* = MI_OK,成功
*/
char PcdBakValue(uint8_t sourceaddr, uint8_t goaladdr)
{
char status;
uint32_t unLen;
uint8_t ucComMF522Buf[MAXRLEN];
ucComMF522Buf[0] = PICC_RESTORE;
ucComMF522Buf[1] = sourceaddr;
CalulateCRC(ucComMF522Buf, 2, &ucComMF522Buf[2]);
status = PcdComMF522(PCD_TRANSCEIVE, ucComMF522Buf, 4, ucComMF522Buf, &unLen);
if ((status != MI_OK) || (unLen != 4) || ((ucComMF522Buf[0] & 0x0F) != 0x0A))
{
status = MI_ERR;
}
if (status == MI_OK)
{
ucComMF522Buf[0] = 0;
ucComMF522Buf[1] = 0;
ucComMF522Buf[2] = 0;
ucComMF522Buf[3] = 0;
CalulateCRC(ucComMF522Buf, 4, &ucComMF522Buf[4]);
status = PcdComMF522(PCD_TRANSCEIVE, ucComMF522Buf, 6, ucComMF522Buf, &unLen);
if (status != MI_ERR)
{
status = MI_OK;
}
}
if (status != MI_OK)
{
return MI_ERR;
}
ucComMF522Buf[0] = PICC_TRANSFER;
ucComMF522Buf[1] = goaladdr;
CalulateCRC(ucComMF522Buf, 2, &ucComMF522Buf[2]);
status = PcdComMF522(PCD_TRANSCEIVE, ucComMF522Buf, 4, ucComMF522Buf, &unLen);
if ((status != MI_OK) || (unLen != 4) || ((ucComMF522Buf[0] & 0x0F) != 0x0A))
{
status = MI_ERR;
}
return status;
}

/*
* 函数名:PcdHalt
* 描述 :命令卡片进入休眠状态
* 返回 : 状态值
* = MI_OK,成功
*/
char PcdHalt(void)
{
uint32_t unLen;
uint8_t ucComMF522Buf[MAXRLEN];

ucComMF522Buf[0] = PICC_HALT;
ucComMF522Buf[1] = 0;
CalulateCRC(ucComMF522Buf, 2, &ucComMF522Buf[2]);

PcdComMF522(PCD_TRANSCEIVE, ucComMF522Buf, 4, ucComMF522Buf, &unLen);

return MI_OK;
}

/*
* 函数名:CalulateCRC
* 描述 :用RC522计算CRC16
* 输入 :pIndata,计算CRC16的数组
* ucLen,计算CRC16的数组字节长度
* pOutData,存放计算结果存放的首地址
* 返回 : 无
* 调用 :内部调用
*/
void CalulateCRC(uint8_t *pIndata, uint8_t len, uint8_t *pOutData)
{
uint8_t i, n;
ClearBitMask(DivIrqReg, 0x04);
WriteRawRC(CommandReg, PCD_IDLE);
SetBitMask(FIFOLevelReg, 0x80);
for (i = 0; i < len; i++)
{
WriteRawRC(FIFODataReg, *(pIndata + i));
}
WriteRawRC(CommandReg, PCD_CALCCRC);
i = 0xFF;
do
{
n = ReadRawRC(DivIrqReg);
i--;
} while ((i != 0) && !(n & 0x04));
pOutData[0] = ReadRawRC(CRCResultRegL);
pOutData[1] = ReadRawRC(CRCResultRegM);
}

/*
* 函数名:PcdRese
* 描述 :复位RC522
* 返回 : 状态值
* = MI_OK,成功
*/
char PcdReset(void)
{
RC522_Reset_Disable();
delay_ms(10);
RC522_Reset_Enable();
delay_ms(60);
RC522_Reset_Disable();
delay_ms(500);
WriteRawRC(CommandReg, PCD_RESETPHASE);//复位指令
delay_ms(2);

WriteRawRC(ModeReg, 0x3D);//定义发送和接收常用模式,和Mifare卡通讯,CRC初始值0x6363
WriteRawRC(TReloadRegL, 30);//16位定时器低位
WriteRawRC(TReloadRegH, 0);//16位定时器高位
WriteRawRC(TModeReg, 0x8D);//定义内部定时器的设置
WriteRawRC(TPrescalerReg, 0x3E);//设置定时器分频系数
WriteRawRC(TxAutoReg, 0x40);//调制发送信号为100%ASK

ClearBitMask(TestPinEnReg, 0x80);
WriteRawRC(TxAutoReg, 0x40);

return MI_OK;
}

/*
* 函数名:ReadRawRC
* 描述 :读RC522寄存器
* 输入 :ucAddress,寄存器地址
* 返回 : 寄存器的当前值
*/
uint8_t ReadRawRC(uint8_t Address)
{
uint8_t ucAddr;
uint8_t ucResult = 0;
ucAddr = ((Address << 1) & 0x7E) | 0x80;//最高位置1,读操作;最低为默认0
delay_ms(1);
RC522_CS_Enable();
SPI_WriteNBytes(SPI1_SPI, &ucAddr, 1); //向总线写多个数据
SPI_ReadNBytes(SPI1_SPI, &ucResult, 1); //向总线读多个数据
RC522_CS_Disable();
return ucResult;
}

/*
* 函数名:WriteRawRC
* 描述 :写RC522寄存器
* 输入 :ucAddress,寄存器地址
* ucValue,写入寄存器的值
*/
void WriteRawRC(uint8_t Address, uint8_t value)
{
uint8_t ucAddr;
uint8_t write_buffer[2] = {0};
ucAddr = ((Address << 1) & 0x7E);//最高位置0,写操作;最低为默认0
write_buffer[0] = ucAddr;
write_buffer[1] = value;
delay_ms(1);
RC522_CS_Enable();
SPI_WriteNBytes(SPI1_SPI, write_buffer, 2);
RC522_CS_Disable();
}

/*
* 函数名:SetBitMask
* 描述 :对RC522寄存器置位,可对多个位同时操作
* 输入 :ucReg,寄存器地址
* ucMask,置位值,例0x13,0001 0011
*/
void SetBitMask(uint8_t reg, uint8_t mask)
{
uint8_t temp = 0x00;

temp = ReadRawRC(reg); //读寄存器
WriteRawRC(reg, temp | mask); //将目标位置1
}

/*
* 函数名:ClearBitMask
* 描述 :对RC522寄存器清位
* 输入 :ucReg,寄存器地址
* ucMask,清位值
*/
void ClearBitMask(uint8_t reg, uint8_t mask)
{
uint8_t temp = 0x00;

temp = ReadRawRC(reg);
WriteRawRC(reg, temp & ~mask);
}

/*
* 函数名:PcdAntennaOn
* 描述 :开启天线
* TxControlReg寄存器位0置1,引脚TX1上的输出信号将传输由传输数据调制的13.56 MHz能量载波。
* TxControlReg寄存器位1置1,引脚TX2上的输出信号将发送由传输数据调制的13.56 MHz能量载波。
*/
void PcdAntennaOn(void)
{
uint8_t i;
i = ReadRawRC(TxControlReg);
if (!(i & 0x03))
{
SetBitMask(TxControlReg, 0x03);
}
}

/*
* 函数名:PcdAntennaOff
* 描述 :关闭天线
*/
void PcdAntennaOff(void)
{
ClearBitMask(TxControlReg, 0x03);
}

/*
* 函数名:RC522_PcdConfig_Type
* 描述 :设置RC522的工作方式
* 工作方式ISO14443_A
* 复位中已经设置过,该函数暂时未调用
*/
void RC522_Config(uint8_t Card_Type)
{
ClearBitMask(Status2Reg, 0x08);
WriteRawRC(ModeReg, 0x3D);
WriteRawRC(RxSelReg, 0x86);
WriteRawRC(RFCfgReg, 0x7F);
WriteRawRC(TReloadRegL, 30);
WriteRawRC(TReloadRegH, 0);
WriteRawRC(TModeReg, 0x8D);
WriteRawRC(TPrescalerReg, 0x3E);
delay_ms(5);
PcdAntennaOn();
}

RC522.h

头文件内容与API大致相同,定义了指令集和寄存器。

这里的sys.h是正点原子的F1系列头文件。

加入了引脚定义和自定义函数。

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
#ifndef __RC522_H
#define __RC522_H
#include "sys.h"

/***************************引脚定义****************************/
/*
RC522SPI引脚,RST和CS可随意更改
RST-PC5
CS-PA4
SCK-PA5
MISO-PA6
MOSI-PA7
*/

//Reset
#define RC522_RST_Pin GPIO_PIN_5
#define RC522_RST_GPIO_Port GPIOC
#define RC522_GPIO_Reset_CLK_ENABLE() \
do { __HAL_RCC_GPIOA_CLK_ENABLE(); } while(0)

//CS片选,模块SDA引脚,SPI通信时用作NSS
#define RC522_GPIO_CS_PIN GPIO_PIN_4
#define RC522_GPIO_CS_PORT GPIOA
#define RC522_GPIO_CS_CLK_ENABLE() \
do { __HAL_RCC_GPIOA_CLK_ENABLE(); } while(0)

/***********************RC522 函数宏定义**********************/

#define RC522_Reset_Disable() HAL_GPIO_WritePin(RC522_RST_GPIO_Port, RC522_RST_Pin, GPIO_PIN_SET);
#define RC522_Reset_Enable() HAL_GPIO_WritePin(RC522_RST_GPIO_Port, RC522_RST_Pin, GPIO_PIN_RESET);

#define RC522_CS_Enable() HAL_GPIO_WritePin(RC522_GPIO_CS_PORT, RC522_GPIO_CS_PIN, GPIO_PIN_RESET);
#define RC522_CS_Disable() HAL_GPIO_WritePin(RC522_GPIO_CS_PORT, RC522_GPIO_CS_PIN, GPIO_PIN_SET);

#define readID 1//读
#define writeID 2//写

/***************************自定义函数****************************/
void RC522_Init(void);
void RC522_Start(uint8_t block,uint8_t option,uint8_t *Write_Card_Data);
void RC522_SPI_GPIO_Init(void);

/***************************RC522指令集****************************/
//MF522命令字
#define PCD_IDLE 0x00 //取消当前命令
#define PCD_AUTHENT 0x0E //验证密钥
#define PCD_RECEIVE 0x08 //接收数据
#define PCD_TRANSMIT 0x04 //发送数据
#define PCD_TRANSCEIVE 0x0C //发送并接收数据
#define PCD_RESETPHASE 0x0F //复位
#define PCD_CALCCRC 0x03 //CRC计算

//Mifare_One卡片命令字
#define PICC_REQIDL 0x26 //寻天线区内未进入休眠状态
#define PICC_REQALL 0x52 //寻天线区内全部卡
#define PICC_ANTICOLL1 0x93 //防冲撞
#define PICC_ANTICOLL2 0x95 //防冲撞
#define PICC_AUTHENT1A 0x60 //验证A密钥
#define PICC_AUTHENT1B 0x61 //验证B密钥
#define PICC_READ 0x30 //读块
#define PICC_WRITE 0xA0 //写块
#define PICC_DECREMENT 0xC0 //扣款
#define PICC_INCREMENT 0xC1 //充值
#define PICC_RESTORE 0xC2 //调块数据到缓冲区
#define PICC_TRANSFER 0xB0 //保存缓冲区中数据
#define PICC_HALT 0x50 //休眠

//MF522 FIFO长度定义
#define DEF_FIFO_LENGTH 64 //FIFO size=64byte

//MF522寄存器定义
// PAGE 0
#define RFU00 0x00
#define CommandReg 0x01
#define ComIEnReg 0x02
#define DivlEnReg 0x03
#define ComIrqReg 0x04
#define DivIrqReg 0x05
#define ErrorReg 0x06
#define Status1Reg 0x07
#define Status2Reg 0x08
#define FIFODataReg 0x09
#define FIFOLevelReg 0x0A
#define WaterLevelReg 0x0B
#define ControlReg 0x0C
#define BitFramingReg 0x0D
#define CollReg 0x0E
#define RFU0F 0x0F
// PAGE 1
#define RFU10 0x10
#define ModeReg 0x11
#define TxModeReg 0x12
#define RxModeReg 0x13
#define TxControlReg 0x14
#define TxAutoReg 0x15
#define TxSelReg 0x16
#define RxSelReg 0x17
#define RxThresholdReg 0x18
#define DemodReg 0x19
#define RFU1A 0x1A
#define RFU1B 0x1B
#define MifareReg 0x1C
#define RFU1D 0x1D
#define RFU1E 0x1E
#define SerialSpeedReg 0x1F
// PAGE 2
#define RFU20 0x20
#define CRCResultRegM 0x21
#define CRCResultRegL 0x22
#define RFU23 0x23
#define ModWidthReg 0x24
#define RFU25 0x25
#define RFCfgReg 0x26
#define GsNReg 0x27
#define CWGsCfgReg 0x28
#define ModGsCfgReg 0x29
#define TModeReg 0x2A
#define TPrescalerReg 0x2B
#define TReloadRegH 0x2C
#define TReloadRegL 0x2D
#define TCounterValueRegH 0x2E
#define TCounterValueRegL 0x2F
// PAGE 3
#define RFU30 0x30
#define TestSel1Reg 0x31
#define TestSel2Reg 0x32
#define TestPinEnReg 0x33
#define TestPinValueReg 0x34
#define TestBusReg 0x35
#define AutoTestReg 0x36
#define VersionReg 0x37
#define AnalogTestReg 0x38
#define TestDAC1Reg 0x39
#define TestDAC2Reg 0x3A
#define TestADCReg 0x3B
#define RFU3C 0x3C
#define RFU3D 0x3D
#define RFU3E 0x3E
#define RFU3F 0x3F

//和MF522通讯时返回的错误代码
#define MI_OK 0
#define MI_NOTAGERR 1
#define MI_ERR 2

//缓冲数组大小
#define MAXRLEN 18

/*******************************内部调用函数**************************************/
char PcdRequest(uint8_t req_code,uint8_t *pTagType);
char PcdAnticoll(uint8_t *pSnr);
char PcdSelect(uint8_t *pSnr);
char PcdAuthState(uint8_t auth_mode,uint8_t addr,uint8_t *pKey,uint8_t *pSnr);
char PcdRead(uint8_t addr,uint8_t *pData);
char PcdWrite(uint8_t addr,uint8_t *pData);
char PcdValue(uint8_t dd_mode,uint8_t addr,uint8_t *pValue);
char PcdBakValue(uint8_t sourceaddr, uint8_t goaladdr);
char PcdHalt(void);
void CalulateCRC(uint8_t *pIndata,uint8_t len,uint8_t *pOutData);
char PcdReset(void);
uint8_t ReadRawRC(uint8_t Address);
void WriteRawRC(uint8_t Address, uint8_t value);
void SetBitMask(uint8_t reg,uint8_t mask);
void ClearBitMask(uint8_t reg,uint8_t mask);
char PcdComMF522(uint8_t Command,uint8_t *pInData,uint8_t InLenByte,uint8_t *pOutData,unsigned int *pOutLenBit);
void PcdAntennaOn(void);
void PcdAntennaOff(void);
void RC522_Config(uint8_t Card_Type);

#endif

SPI定义

spi的初始化默认即可,注意以下两项的设置,才能保持和模块的正常通信:

CLKPolarity=SPI_POLARITY_LOW; //串行同步时钟的空闲状态为低电平

CLKPhase=SPI_PHASE_1EDGE; //串行同步时钟的第1个跳变沿(上升或下降)数据被采样

片选引脚改为软件管理,这样方便更改引脚,

NSS=SPI_NSS_SOFT; //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理

SPI.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
#include "spi.h"

SPI_HandleTypeDef SPI1_Handler; //SPI句柄

//以下是SPI模块的初始化代码,配置成主机模式
//SPI口初始化
//这里针是对SPI1的初始化
void SPI1_Init(void)
{
SPI1_Handler.Instance=SPI1_SPI; //SPI1
SPI1_Handler.Init.Mode=SPI_MODE_MASTER; //设置SPI工作模式,设置为主模式
SPI1_Handler.Init.Direction=SPI_DIRECTION_2LINES; //设置SPI单向或者双向的数据模式:SPI设置为双线模式
SPI1_Handler.Init.DataSize=SPI_DATASIZE_8BIT; //设置SPI的数据大小:SPI发送接收8位帧结构
SPI1_Handler.Init.CLKPolarity=SPI_POLARITY_LOW; //串行同步时钟的空闲状态为低电平
SPI1_Handler.Init.CLKPhase=SPI_PHASE_1EDGE; //串行同步时钟的第1个跳变沿(上升或下降)数据被采样
SPI1_Handler.Init.NSS=SPI_NSS_SOFT; //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
SPI1_Handler.Init.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_256;//定义波特率预分频的值:波特率预分频值为256
SPI1_Handler.Init.FirstBit=SPI_FIRSTBIT_MSB; //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
SPI1_Handler.Init.TIMode=SPI_TIMODE_DISABLE; //关闭TI模式
SPI1_Handler.Init.CRCCalculation=SPI_CRCCALCULATION_DISABLE;//关闭硬件CRC校验
SPI1_Handler.Init.CRCPolynomial=7; //CRC值计算的多项式,默认值为7
HAL_SPI_Init(&SPI1_Handler);//初始化

__HAL_SPI_ENABLE(&SPI1_Handler); //使能SPI1

SPI1_ReadWriteByte(0Xff); //启动传输,产生8个时钟脉冲, 达到清空DR的作用, 非必需
}

//SPI底层驱动,时钟使能,引脚配置
//此函数会被HAL_SPI_Init()调用
//hspi:SPI句柄
void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)
{
GPIO_InitTypeDef GPIO_Initure;

SPI1_SPI_CLK_ENABLE();
SPI1_SCK_GPIO_CLK_ENABLE();
SPI1_MISO_GPIO_CLK_ENABLE();
SPI1_MOSI_GPIO_CLK_ENABLE();

GPIO_Initure.Pin=SPI1_SCK_GPIO_PIN;
GPIO_Initure.Mode=GPIO_MODE_AF_PP;
GPIO_Initure.Pull=GPIO_PULLDOWN;
GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(SPI1_SCK_GPIO_PORT,&GPIO_Initure);

GPIO_Initure.Pin=SPI1_MISO_GPIO_PIN;
HAL_GPIO_Init(SPI1_MISO_GPIO_PORT,&GPIO_Initure);

GPIO_Initure.Pin=SPI1_MOSI_GPIO_PIN;
HAL_GPIO_Init(SPI1_MOSI_GPIO_PORT,&GPIO_Initure);
}

SPI.h

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
#ifndef __SPI_H
#define __SPI_H
#include "sys.h"

extern SPI_HandleTypeDef SPI1_Handler; //SPI句柄

/* SPI1 引脚 定义 */

#define SPI1_SCK_GPIO_PORT GPIOA
#define SPI1_SCK_GPIO_PIN GPIO_PIN_5
#define SPI1_SCK_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)

#define SPI1_MISO_GPIO_PORT GPIOA
#define SPI1_MISO_GPIO_PIN GPIO_PIN_6
#define SPI1_MISO_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)

#define SPI1_MOSI_GPIO_PORT GPIOA
#define SPI1_MOSI_GPIO_PIN GPIO_PIN_7
#define SPI1_MOSI_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)

/* SPI1相关定义 */
#define SPI1_SPI SPI1
#define SPI1_SPI_CLK_ENABLE() do{ __HAL_RCC_SPI1_CLK_ENABLE(); }while(0)

void SPI1_Init(void);

#endif

四、实验结果

本实验在于经验分享和学习记录,有不正确的地方请读者指正。

五、更新源码:

源码已上传至gitee:

stm32: 一些stm32模块使用经验记录 - Gitee.com https://gitee.com/lrf1125962926/stm32/tree/RC522_SPI/;