SPI介绍

什么是SPI

SPI 是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口,是一种用于芯片通信的同步串行通信接口规范,是Motorola首先在其MC68HCXX系列处理器上定义的。

SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便。

SPI 接口主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。(M4高达37.5Mbps,不过很多外围设备往往只支持高达10Mbps,极少部分的设备能够超过10Mbps,例如W25Q128)

SPI设备之间使用全双工(一个信号线输入,一根信号线输出)模式通信,是一个主机和一个或多个从机的主从模式。主机负责初始化帧,这个数据传输帧可以用于读与写两种操作,片选线路可以从多个从机选择一个来响应主机的请求。

SPI信号线

  1. MOSI –》SPI总线主机输出/ 从机输入(SPI Bus Master Output/Slave Input);
  2. MISO –》SPI总线主机输入/ 从机输出(SPI Bus Master Input/Slave Output);
  3. SCLK –》串行时钟信号,由主设备产生;
  4. CS –》从设备使能信号,由主设备控制(Chip select),有的IC此pin脚叫SS。低电平使能,选中这个芯片工作。

对于多个从设备,每个从设备都需要一个独立的SS信号。大多数从属设备具有三态逻辑的特性,所以当器件未被选中时,它们的MISO信号变为高阻抗(逻辑断开)。没有三态输出的器件不能与其他器件共享SPI总线段,但是可以使用外接的三态逻辑缓存来解决这个问题。

image-20200822202234453

SPI片选使能问题:spi片选使能问题???-CSDN社区

数据传输

为了开始通信,总线上的主设备需要使用从设备支持的频率来配置时钟,这个频率最高为几兆赫兹左右。然后主设备将某根连接到从设备SS的线上的信号置为0来选中这个从设备。如果等待时间是必要的话(例如进行模数转换),主设备必须等待满这段时间之后,才可以发出时钟周期信号。

在每个SPI时钟周期内,都会发生全双工数据传输。主设备在MOSI线上发送一个位,从设备读取它,同时从机在MISO线上发送一位数据,主机读取它。即使只有单向数据传输的目的,主从机之间的通信工作方式仍然是双工的。

传输通常会使用给定字长的两个移位寄存器(通常包含8位),一个在主设备中,一个在从设备中,它们之间的连接方式形成了一个虚拟的环形拓朴结构。数据通常先从最高位移出,在时钟信号边沿,主机和从机均移出一位,然后在传输线上输出给对方。在下一个时钟沿,每个接收器都从传输线接受对方发出的数据位,并且从移位寄存器的最低位推入。每完成这样一个移出推入的周期后,主机和从机就交换寄存器中的一位数据。当所有数据位都经过了这样的移出推入过程后,主机和从机就完成了寄存器上的数据交换。如果需要交换的数据比寄存器的位数还要长的话,则需要重新加载移位寄存器并重复该过程。传输可能会持续任意数量的时钟周期。传输完成后,主设备会停止发送时钟信号,并通常会取消选择从设备。

SPI接口框图

image-20200822204706092

数据帧格式

移出数据时 MSB 在前还是 LSB 在前取决于 SPI_CR1 寄存器中 LSBFIRST 位的值。每个数据帧的长度均为 8 位或 16 位,具体取决于使用 SPI_CR1 寄存器中的 DFF 位。

注:所选的数据帧格式适用于发送和/或接收。

image-20200822204746456

image-20200822205207927

时钟相位和时钟极性

通过 SPI_CR1 寄存器中的 CPOL 和 CPHA 位的排列组合,可以用软件选择四种可能的时序关系,用于选择数据捕获时钟边沿。其中,使用的最为广泛的是模式0(00)和模式3方式。

时钟极性

CPOL(时钟极性)位控制不传任何数据时的时钟电平状态,此位对主器件和从器件都有作用。如果复位 CPOL, SCK 引脚在空闲状态处于低电平;如果将 CPOL 置 1, SCK 引脚在空闲状态处于高电平。

image-20200822210149794

时钟相位

如果将 CPHA(时钟相位)位置 1,则 SCK 引脚上的第二个边沿对 MSBit 采样(如果将 CPOL 位复位,则为下降沿;如果将 CPOL 位置 1,则为上升沿)。即,在第二个时钟边沿锁存数据。

image-20200822210534709

如果将 CPHA 位复位,则 SCK 引脚上的第一个边沿对 MSBit 采样(如果将 CPOL 位置 1,则为下降沿;如果复位 CPOL 位,则为上升沿)。即,在第一个时钟边沿锁存数据。

image-20200822210653432

SPI工作原理总结

  • 硬件上为4根线。
  • 主机和从机都有一个串行移位寄存器,主机通过向它的SPI串行寄存器写入一个字节来发起一次传输。
  • 串行移位寄存器通过MOSI信号线将字节传送给从机,从机也将自己的串行移位寄存器中的内容通过MISO信号线返回给主机。这样,两个移位寄存器中的内容就被交换。
  • 外设的写操作和读操作是同步完成的。如果只进行写操作,主机只需忽略接收到的字节;反之,若主机要读取从机的一个字节,就必须发送一个空字节(任意字节)来引发从机的传输。

SPI配置流程

添加支持spi的库函数文件:stm32f4xx_spi.c

  1. 理解电路原理图

    1
    2
    3
    4
    CS -- PB14(普通输出功能)
    SCK -- PB3(复用SPI1)
    MOSI -- PB5(复用SPI1)
    MISO -- PB4(复用SPI1)
    image-20200822211224230
  2. 使能SPIx和IO口时钟

    RCC_AHBxPeriphClockCmd();

  3. 初始化IO口为复用功能

    void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);

  4. 设置引脚复用映射

    GPIO_PinAFConfig();

  5. 初始化SPIx,设置SPIx工作模式

    void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);

  6. 使能SPIx

    void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);

  7. SPI传输数据

    void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);

    uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);

  8. 查看SPI传输状态

    SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE);

    image-20200822212432868

库函数配置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
57
58
59
60
void SpiFlash_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
SPI_InitTypeDef SPI_InitStruct;
//1. 使能SPIx和IO口时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);

//2. 初始化IO口为复用功能
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4| GPIO_Pin_5; //GPIOB 3 4 5为复用
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF; //配置IO口复用功能
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; //速度 50MHz
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; //推挽复用输出
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOB,&GPIO_InitStruct);

GPIO_InitStruct.GPIO_Pin = GPIO_Pin_14; //配置CS为普通输出端口
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; //推挽输出
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; //速度 50MHz
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; //推挽复用输出
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOB,&GPIO_InitStruct);
//3. 设置引脚复用映射
GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1);

SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128; //分频
SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low; //时钟极性为低电平 ,低电平空闲
SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge; //时钟相位 第一边沿发送数据
SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b; //八位数据
SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //全双工
SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB; //高位在前
SPI_InitStruct.SPI_Mode = SPI_Mode_Master ; //主机模式
SPI_InitStruct.SPI_NSS = SPI_NSS_Soft; //软件控制CS
SPI_InitStruct.SPI_CRCPolynomial = 7; //校验位
//4. 初始化SPIx,设置SPIx工作模式
SPI_Init(SPI1, &SPI_InitStruct);

//5. 使能SPI1
SPI_Cmd(SPI1,ENABLE);

F_CS = 1; //不使能芯片
}

//发送数据即可接收数据
u8 Spi_Send_Byte(u8 data)
{
u8 rx_data; //定义变量接收数据

//判断发送缓冲是否为空
while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
SPI_I2S_SendData(SPI1, data); //发送数据

//判断接收缓冲是否为非空
while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
rx_data = SPI_I2S_ReceiveData(SPI1) ;

return rx_data;
}

FLASH

Flash存储器属于内存器件的一种,是一种非易失性( Non-Volatile )内存。可以对称为块的存储器单元块进行擦写和再编程。任何flash器件的写入操作只能在空或已擦除的单元内进行,所以大多数情况下,在进行写入操作之前必须先执行擦除。

快闪存储器(英语:flash memory),是一种电子式可清除程序化只读存储器的形式,允许在操作中被多次擦或写的存储器。这种科技主要用于一般性资料存储,以及在电脑与其他数字产品间交换传输资料,如储存卡与U盘。闪存是一种特殊的、以宏块抹写的EEPROM。早期的闪存进行一次抹除,就会清除掉整颗芯片上的资料。闪存的写入速度往往明显慢于读取速度。

闪存的一种限制在于即使它可以单一字节的方式读或写入,但是抹除一定是一整个区块。一般来说都是设置某一区中的所有比特为“1”,刚开始区块内的所有部分都可以写入,然而当有任何一个比特被设为“0”时,就只能借由清除整个区块来恢复“1”的状态。换句话说闪存(特别是NOR Flash)能提供随机读取与写入操作,却无法提供任意的随机改写。

不过闪存的区块可以写入与既存的“0”值一样长的消息。例如:有一小区块的值已抹除为1111,然后写入1110的消息。接下来这个区块还可以依序写入1010、0010,最后则是0000。可是实际上少有算法可以从这种连续写入兼容性得到好处,一般来说还是整块抹除再重写。

W25Q128

W25Q128将16M的容量分为256个块(Block),每个块大小为64K字节,每个块又分为16个扇区(Sector),每个扇区4K个字节。W25Q128 的最小擦除单位为一个扇区,也就是每次必须擦除4K个字节。这样我们需要给W25Q128开辟一个至少4K的缓存区,这样对SRAM要求比较高,要求芯片必须有4K以上SRAM才能很好的操作。

W25Q128的擦写周期多达10W次,具有20年的数据保存期限,支持电压为2.7~3.6V,W25Q128支持标准的SPI,还支持双输出/四输出的SPI,最大SPI时钟可以到80Mhz (双输出时相当于160Mhz, 四输出时相当于320M),更多的W25Q128的介绍,请参考W25Q128的datasheet。

STM32F407ZET6的RAM主要由内部SRAM1(112KB) + 辅助内部 SRAM2 (16 KB) + 辅助内部 SRAM3 (64 KB)(CCM数据RAM)三部分组成。注:CCM只可以CPU访问,SRAM的话CPU和DMA等外设都可以访问。

image-20200913225155113 image-20200822214330220 image-20200822214728768 image-20200822215846787

读取制造商/设备ID(90h)
“读取制造商/设备ID”指令是“掉电/设备ID释放”指令的替代方法,该指令同时提供JEDEC分配的制造商ID和特定的设备ID。

读取制造商/设备ID指令与关机/设备ID释放指令非常相似。通过将/CS引脚驱动为低电平并转移指令代码“ 90h”,后跟000000h的24位地址(A23-A0),可以启动指令。之后,制造商ID(EFh)和设备ID在CLK的下降沿移出,最高有效位(MSB)首先出现,如图29所示。

W25Q128的设备ID值在制造商和设备标识表中列出。如果24位地址最初设置为000001h,则将首先读取设备ID,然后再读取制造商ID。

可以连续读取制造商和设备ID,彼此交替读取。 最后,通过将/CS拉高来完成指令。

image-20200822220659066

写使能(06h)
写使能指令将状态寄存器中的写使能锁存器(WEL)位置1。必须在扇区擦除,块擦除,芯片擦除,写入状态寄存器和擦除/程序安全寄存器指令之前将WEL位置1。

通过将/CS驱动为低电平,在CLK的上升沿将指令代码“ 06h”移入数据输入(DI)引脚,然后将/CS驱动为高电平,即可输入WriteEnable指令。

image-20200822221300060

读取状态寄存器1(05h)和读取状态寄存器2(35h)
读取状态寄存器指令允许读取8位状态寄存器。通过将/CS驱动为低电平,并在CLK的上升沿将状态寄存器1的指令代码“ 05h”或状态寄存器2的指令“ 35h”移入DI引脚来输入指令。然后,状态寄存器位在CLK下降沿的DO引脚上移出,最高有效位(MSB)首先移出,如图所示。

读取状态寄存器指令可在任何时候使用,即使在擦除或写入状态寄存器周期正在进行。这样可以检查BUSY状态位,以确定周期何时完成,以及器件是否可以接受另一条指令。

状态寄存器可以连续读取。通过将/CS拉高来完成指令。

image-20200822221929951 image-20200822222944057

扇区擦除(20h)
扇区擦除指令将指定扇区(4K字节)内的所有内存设置为全1(FFh)的擦除状态。在器件接受扇区擦除指令之前,必须先执行写使能指令(状态寄存器位WEL必须等于1)。通过将/CS引脚驱动为低电平并将指令代码“ 20h”移至24位扇区地址(A23-A0),可以启动指令。

在最后一个字节的第八位被锁存后,必须将/CS引脚驱动为高电平。 如果不这样做,将不执行扇区擦除指令。/CS被驱动为高电平后,自定时扇区擦除指令将在tSE的持续时间内开始。

在扇区擦除周期进行期间,仍可以访问读取状态寄存器指令以检查BUSY位的状态。 在扇区擦除周期中,BUSY位为1;在周期结束且设备准备好再次接受其他指令时,BUSY位为0。扇区擦除周期结束后,状态寄存器中的写使能锁存(WEL)位被清除为0。

如果寻址的页面受块保护,则将不执行扇区擦除指令。

image-20200822222910408

Page Program (02h)

页面编程指令允许从一个字节到256字节(一页)的数据在先前擦除(FFh)的存储位置进行编程。在设备将接受页面编程指令(状态寄存器位WEL = 1)之前,必须执行写使能指令。通过将/CS引脚驱动为低电平,然后将指令代码“ 02h”紧随其后的是24位地址(A23-A0)和至少一个数据字节,移入DI引脚来启动指令。在将数据发送到设备时,/CS引脚在整个指令期间必须保持低电平。

如果要对整个256字节页面进行编程,则最后一个地址字节(8个最低有效地址位)应设置为0。如果最后一个地址字节不为零,并且时钟数超过了剩余的页面长度,则寻址将换行到页面的开头。在某些情况下,可以编程少于256个字节(部分页),而不会影响同一页中的其他字节。执行部分页面编程的条件之一是时钟数不能超过剩余页面长度。如果发送给设备的字节数超过256个,则寻址将环绕到页面的开头,并覆盖先前发送的数据。

与写和擦除指令一样,必须将最后一个字节的第八位锁存后,将/CS引脚驱动为高电平。如果不这样做,将不会执行页面编程指令。/CS驱动为高电平后,自定时页面编程指令将开始持续tpp时间。

在页面编程周期进行期间,仍可以访问读取状态寄存器指令以检查BUSY位的状态。在页面编程周期中,BUSY位为1;在周期结束且设备准备好再次接受其他指令时,BUSY位为0。页面编程周期完成后,状态寄存器中的写使能锁存(WEL)位被清除为0。

image-20200822223902567

读取数据(03h)
读取数据指令允许从存储器中顺序读取一个或多个数据字节。通过将/CS引脚驱动为低电平,然后将指令代码“ 03h”和随后的24位地址(A23-A0)移入DI引脚,可以启动指令。代码和地址位锁存在CLK引脚的上升沿。接收到地址后,寻址存储单元的数据字节将在CLK的下降沿的DO引脚上移出,最高有效位(MSB)在前。每个数据字节移出后,地址将自动递增到下一个更高的地址,从而允许连续的数据流。这意味着只要时钟继续,就可以用一条指令访问整个存储器。 通过将/CS拉高来完成指令。读数据指令序列如图所示。

如果在执行擦除,编程或写周期(BUSY = 1)时发出了读数据指令,则该指令将被忽略,并且不会对当前周期产生任何影响。

image-20200822225006898

模拟SPI示例代码

在有些情况下没有硬件SPI的支持,只能通过IO口来模拟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
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
#define F_CS     PBout(14)
#define SCK PBout(3)
#define MISO PBin(4)
#define MOSI PBout(5)

void SpiFlash_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
//使能IO口时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);

GPIO_InitStruct.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_14 | GPIO_Pin_5; //GPIOB 3 14 5
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; //配置IO口输出
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; //速度 50MHz
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; //推挽复用输出
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOB,&GPIO_InitStruct);

GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOB,&GPIO_InitStruct);

F_CS = 1; //不使能芯片
}

//发送数据即可接收数据
u8 Spi_Send_Byte(u8 data) //data = 0x87 1000 0111
{
u8 i, rx_data = 0x00; //定义变量接收数据 0 0 0 0 0 0 0 0

SCK = 0;

for(i=0; i<8; i++)
{
//准备数据
if(data & (1<<(7-i)))
{
MOSI = 1;
}
else
{
MOSI = 0;
}
delay_us(2);
SCK = 1;
delay_us(2);
//接受数据,SCK变为低电平之前
if(MISO == 1)
{
rx_data |= (1<<(7-i));
}
SCK = 0;
}
return rx_data;
}

u16 W25q128_Id(void)
{
u16 id;
F_CS = 0; //使能芯片

//发送读ID命令
Spi_Send_Byte(0x90);

//发地址
Spi_Send_Byte(0x00);
Spi_Send_Byte(0x00);
Spi_Send_Byte(0x00);

id |= Spi_Send_Byte(0xFF)<<8; //生产商ID
id |= Spi_Send_Byte(0xFF); //设备ID

F_CS = 1; //不使能芯片
return id;
}

void Write_Enable(void)
{
F_CS = 0; //使能芯片
//发送写使能命令
Spi_Send_Byte(0x06);
F_CS = 1; //不使能芯片
}

//读状态寄存器1
u8 W25Q128_Read_status1(void)
{
u8 status1;
F_CS = 0; //使能芯片

//发送命令
Spi_Send_Byte(0x05);

//发送任意字节
status1 = Spi_Send_Byte(0xFF);

F_CS = 1; //不使能芯片
return status1;
}

void W25Q128_Erase_Sector(u32 addr) //0x01BF01
{
//写使能
Write_Enable();

F_CS = 0; //使能芯片

//发送擦除命令
Spi_Send_Byte(0x20);

//拆分地址发送
Spi_Send_Byte((addr>>16)&0xFF);
Spi_Send_Byte((addr>>8)&0xFF);
Spi_Send_Byte(addr&0xFF);

F_CS = 1; //不使能芯片

//判断是否擦除完成
while(1)
{
//判断BUSY位是否为0
if((W25Q128_Read_status1() & 0x01) == 0 )
break;
}
}

void W25Q128_Writer_data(u32 addr,u8 *buf, u32 len)
{
Write_Enable();
//芯片使能
F_CS = 0;
//写页命令
Spi_Send_Byte(0x02);

Spi_Send_Byte((addr>>16) &0xFF);
Spi_Send_Byte((addr>>8) &0xFF);
Spi_Send_Byte(addr&0xFF);

//写数据
while(len--)
{
Spi_Send_Byte(*buf);
buf++;
}
F_CS = 1;

//判断是否写完成
while(1)
{
if((W25Q128_Read_status1() & 0x01) == 0 )
break;
}
}

void W25Q128_Read_data(u32 addr,u8 *buf, u32 len)
{
//芯片使能
F_CS = 0;

Spi_Send_Byte(0x03);

//读数据地址
Spi_Send_Byte((addr>>16) &0xFF);
Spi_Send_Byte((addr>>8) &0xFF);
Spi_Send_Byte(addr&0xFF);

//读数据
while(len--)
{
//发送任意数据
*buf= Spi_Send_Byte(0xff);
buf++;
}

F_CS = 1;
}

RFID

ISO14443协议

ISO14443协议是Contactless card standards (非接触式IC卡标准)协议。

REQA和WAKE-UP帧(P13,18)

请求和唤醒帧用来初始化通信并按以下次序组成:

通信开始。

  • 7个数据位发送

  • LSB首先发送

  • 标准REQA的数据内容是0x26,WAKE UP请求的数据内容是0x52

通信结束不加奇偶校验位。

NVB

整个数据包里的数据的个数。长度: 1字节。

  • 较高4位称为字节计数,指定所有有效数据位(包括被PCD发送的NVB和SEL,不包括CRC)的数目被8除后所得的整数。这样,字节计数的最小值是2而最大值是7。

  • 较低4位称为比特计数,指定所有有效数据位(包括被PCD发送的NVB和SEL)的数目被8除后所得的余数。

基于14443-A的操作帧格式

  • 请求卡: 0x26。返回两字节的卡类型
  • 唤醒所有卡: 0x52。返回两字节的卡类型
  • 防冲突: 0x93+0x20得到卡ID。返回四字节卡ID,一字节的校验(异或)
  • 选择卡片: 0x93+0x70+4字节卡ID+1字节校验+2字节CRC16校验。返回卡校验0x08

RFID中间件的概念

为解决分布异构问题,人们提出了中间件(middleware)的概念。中间件是位于平台(硬件和操作系统)和应用之间的通用服务(如:DNS,DHCP),这些服务具有标准的程序接口和协议。针对不同的操作系统和硬件平台,它们可以有符合接口和协议规范的多种实现。

RFID微波2.4GHz频段的无线技术标准

  • ZigBee/IEEE 802.15.4: ZigBee技术是一项新兴的短距离无线通信技术,主要面向的应用领域是低速率无线个人局域网(LRWPAN),典型特征是近距离、低功耗、低成本、低传输速率,主要适用于自动控制以及远程控制领域,目的是为了满足小型廉价设备的无线联网和控制。
  • Wi-Fi/IEEE 802.11b: Wi-Fi即无线局域网,工作在2.4GHz频段,用于学校、商业等办公区域的无线连接技术,传输速率可达11Mbit/s,工作距离100m,采用直接序列扩频(DSSS) 的方式。采用Wi-Fi的主要推动因素是数据吞吐量,Wi-Fi-般用来将计算机与本地局域网相连或直接与互联网相连。

RC522概述

MF RC522是应用于13.56MHz非接触式通信中,高集成度读写卡系列芯片中的一员。是NXP公司针对“三表”应用推出的一款低电压、低成本、体积小的非接触式读写卡芯片。

  • 读写器,支持ISO 14443A/ MIFARER
  • 可实现各种不同主机接口的功能:SPI接口;串行UART(类似RS232,电压电平取决于提供的管脚电压);I2C接口
  • 64字节的发送和接收FIFO缓冲区。
  • 内部振荡器,连接27.12MHz的晶体。

RC522寄存器

1
2
3
4
5
6
7
8
9
10
11
12
CommandReg启动和停止命令的执行。
ComIrqReg包含中断请求标志
ErrorReg错误标志,指示执行的上个命令的错误状态
Status2Reg包含接收器和发送器的状态标志
FIFODtataReg 64字节FIFO缓冲区的输入和输出
FIFOLevelReg指示FIFO中存储的字节数
ControlReg不同的控制寄存器
BitFramingReg面向位的帧的调节
CollReg RF接口上检测到的第一个位冲突的位的位置

//spi地址字节按下面的格式传输。
//第一个字节的MSB位设置使用的模式。MSB位为1时从MFRC522读出数据;MSB位为0时将数据写入MFRC522。第一个字节的位6-1定义地址, 最后一位应当设置为0

FIFO缓冲区

FIFO缓冲区的输入和输出数据总线连接到FIFODataReg寄存器。通过写FIFODataReg寄存器来将一个字节的数据存入FIFO缓冲区,之后内部FIFO缓冲区写指针加1。除了读写FIFO缓冲区外,FIFO 缓冲区指针还可通过置位寄存器FIFOLevelReg的FlushBuffer位来复位。从而,FIFOLevel 位被清零,寄存器ErrorReg的BufferOvfl位被清零,实际存储的字节不能再访问。已经存放在FIFO缓冲区中的字节数:可以查看寄存器FIFOLevelReg的FIFOLevel字段。

RFID原理分析

image-20200915141638442

M1卡

所谓的M1芯片,是指恩智浦出品的芯片缩写,全称为NXP Mifare1系列,常用的有S50及S70两种型号,属于非接触式IC卡。优点是可读可写的多功能卡,缺点是:价格稍贵,感应距离短,适合非定额消费系统、停车场系统、门禁考勤系统等。

工作原理

读写器向 M1 卡发一组固定频率的电磁波,卡片内有一个 LC 串联谐振电路,其频率与读写器发射的频率相同,在电磁波的激励下, LC 谐振电路产生共振,从而使电容内有了电荷,在这个电容的另一端,接有一个单向导通的电子泵,将电容内的电荷送到另一个电容内储存,当所积累的电荷达到 2V 时,此电容可做为电源为其它电路提供工作电压,将卡内数据发射出去或接取读写器的数据。

主要指标

  • 容量为 8K 位 EEPROM(1KB)
  • 分为 16 个扇区,每个扇区为 4 块,每块 16 个字节,以块为存取单位
  • 每个扇区有独立的一组密码及访问控制
  • 每张卡有唯一序列号,为 32 位
  • 具有防冲突机制,支持多卡操作(可以同时读取多个电子标签)
  • 无电源,自带天线(线圈),内含加密控制逻辑和通讯逻辑电路
  • 工作频率: 13.56MHZ(高频)
  • 通信速率: 106 KBPS

存储结构

M1卡分为16个扇区,每个扇区由4块(块 0、块 1、块 2、块 3)组成,我们也将16个扇区的64个块按绝对地址编号为0~63,存贮结构如下图所示。

image-20200829113220206 image-20200829113441226

M1 射频卡与读写器的通讯

image-20200829113609029

复位应答( Answer to request):M1 射频卡的通讯协议和通讯波特率是定义好的,当有卡片进入读写器的操作范围时,读写器以特定的协议与它通讯,从而确定该卡是否为 M1 射频卡,即验证卡片的卡型。

**防冲突机制 (Anticollision Loop)**:当有多张卡进入读写器操作范围时,防冲突机制会从其中选择一张进行操作,未选中的则处于空闲模式等待下一次选卡,该过程会返回被选卡的序列号。

**选择卡片(Select Tag)**:选择被选中的卡的序列号,并同时返回卡的容量代码。

**三次互相确认(3 Pass Authentication)**:选定要处理的卡片之后,读写器就确定要访问的扇区号,并对该扇区密码进行密码校验,在三次相互认证(硬件完成)之后就可以通过加密流进行通讯。(在选择另一扇区时,则必须进行另一扇区密码校验。)

加值和减值

image-20200915175932051

初始化想要的值段后,调用加值和减值的函数即可。

image-20200915180518781

面向比特的防冲突机制

卡片有一个全球唯一的序列号。 比如Mifare1卡, 每张卡片有一个全球唯一的32位二进制序列号。 显而易见,卡号的每一位上不是“1”就是“0”,而且由于是全世界唯一,所以任何两张卡片的序列号总有一位的值是不一样的,也就说总存在某一位,一张卡片上是“0”,而另一张卡片上是“1”。硬件自动完成。

S50读卡命令

  • 请求0x26
  • 防冲突0x93,0x70
  • A认证0x60, addr, keyA, ID
  • 读0x30,addr CRC16

硬件连接图

image-20200822230556168 image-20200822230515354
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
1. 主函数中的修改对应的头文件和LCD及Touch初始化全部改为串口初始化

2. main主函数只留下Delay_Init、串口及RMFRC522_Initializtion()初始化

3. 将MFRC522_Initializtion();//初始化MFRC522里面函数STM32_SPI3_Init()初始化程序改为模拟SPI引脚初始化

4. 在MFRC522.h中修改相关的头文件
将下面宏定义修改为对引脚的定义(CS:PD15 RST:PE15)
#define MFRC522_CS(x) x ? GPIO_SetBits(GPIOB,GPIO_Pin_2):GPIO_ResetBits(GPIOB,GPIO_Pin_2)
#define MFRC522_Rst(x) x ? GPIO_SetBits(GPIOB,GPIO_Pin_1):GPIO_ResetBits(GPIOB,GPIO_Pin_1)

5. 修改void SPI3_Send(u8 val) 及u8 SPI3_Receive(void)为模拟SPI发送接收数据

6. 修改MFRC522.c相关的延时函数

7. 修改主函数当中的MFRC522Test();获取RFID相关的卡ID 写入数据等
while (1)
{
MFRC522Test();
delay_s(1);
}
8. 屏幕显示全部改为串口打印即可
//命令有两种类型,一种是传给RC522的,对RC522进行操作;一种是先传给RC522,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 //寻天线区内未进入休眠状态 REQA请求
#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 //休眠

NFC

NFC (Near Field Communication)近场通信,这个技术由非接触式射频识别(RFID) 演变而来,由飞利浦半导体(现恩智浦半导体公司)、诺基亚和索尼共同研制开发,其基础是RFID及互连技术。

NFC是一种短距离高频的无线电技术,在13.56MHz频率运行于20cm距离内。其传输速度有106Kbit/s,212Kbit/s或者424Kbit/s三种。NFC采用主动和被动两种读取模式。

工作模式

  • 卡模式:这个模式其实就是相当于一张采用RFID技术的IC卡。可以替代大量的IC卡(包括信用卡)场合商场刷卡、公交卡、门禁管制,车票,门票等等。此种方式下,有一个极大的优点,那就是卡片通过非接触读卡器的RF域来供电,即便是寄主设备(如手机)没电也可以工作。
  • 读写器模式:这个模式可以模拟读卡器功能,读取MIFARE和FeliCa卡的信息
  • 点对点模式:这个模式和红外线差不多,可用于数据交换,只是传输距离较短,传输创建速度较快,传输速度可快些,功耗低(蓝牙也类似)。将两个具备NFC功能的设备链接,能实现数据点对点传输,如下载音乐、交换图片。

PN532

PN532,它是一款高度集成的非接触式通讯收发模块,基于8051单片机核心。它支持6个不同的操作模式(硬件集成的):

  • ISO/IEC14443A/MIFARE 读/写器

  • FeliCa 读/写器

  • ISO/IEC14443B读/写器

  • ISO/IEC14443A MIFARE卡模拟模式

  • FeliCa卡模拟模式

  • ISO/IEC 18092 ECMA 340点对点

这款芯片提供3中和主机通信的接口: SPI\|I2C\USART

image-20200915155342037 image-20200915155453638
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
struct Pn532Cmd{
uint8_t Preamble;//前序
uint8_t StartCode[2];//包头
uint8_t LEN;//包长 TFI+PD0... PDn
uint8_t LCS;//长度校验LEN + LCS = 0x00
uint8_t TFI;//命令帧识别位(0xD4: input) (0xD5: output)
uint8_t *data;//数据域
uint8_t DCS;//数据校验[TFI+PDO+PD1+...+PDn+DCS]=0x00
uint8_t Postamble;//后序
};

uint8_t AUTHOR[14] = {0x40, 0x01, 0x60, 0x02, 0xff, 0xff, 0xff, 0xff, Oxff, 0xff, 0x01, 0x02, 0x03, 0x04);
/*41 0应答,无措*/
uint8_t READ[4] = {0x40, 0x01, 0x30, 0x07};//交换数据,1号卡,读取块,2地址
/*41, 0,16bytes 应答,16个数据*/
uint8_t WRITE[20] = {0x40, 0x01, 0xa0, 0x02, 1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6};//交換数据,1号卡,写入块
/*41, 0应答,无错*/
uint8_t INCREMENT[8] = {0x40, 0x01, 0xc1, 0x02, 1,0,0,0};//交换数据, 1号卡,充值,2地址,数据
uint8_t DECREMENT[8] = {0x40, 0x01,0xc0,0x02, 1,0,0,0};//交换数据, 1号卡,扣款,2地址,数据
uint8_t TRANSFER[4] = {0x40, 0x01,0xB0,0x02};//交换数据,1号卡,保存,2地址 回写

uint8_t Wake_Card()
{
memset(rx_buffer, 0, sizeof(rx_buffer));//串口缓存器清零
SendCmd(WAKE, 24) ;
HAL_Delay(100) ;
if(!memcmp(rx_buffer.WAKEBACK, 9))
{
printf("wake ok\n");
return l;
}
else
{
printf("wake failed\n");
}
return 0;
}

uint8_t Scan_Card()
{
menset(rx_buffer, 0, 128):
GetCmd(SCAN, 3);
SendCmd(buf, len);
HAL_Delay(100);
if(rx_buffer[5]==0xd5 && rx_buffer[6]==0x4b)
{
printf("get card\n");
for(int i=0; i<4: i++)
{
ID[i]=rx_buffer[13+i];
printf("%x ",ID[i]);
}
printf("\n");
return 1;
}
return 0;
}
打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2021-2025 wrd
  • 访问人数: | 浏览次数:

      请我喝杯咖啡吧~

      支付宝
      微信