淘宝客服给的历程是32的F103的基于库函数写的IO口模拟SPI,首先自己用F407的开发板用寄存器写法顺利地实现了功能,然后就是直接用F103C8T6的最小系统的寄存器写法来实现功能,遇到了一些问题。经过不断地调试修改,最后还是成功实现了读卡的功能。
错误原由之一是将SCK管脚,连接错误;其二是由于设置Master的波特率时将波特率设置为了Fpclk/2,而我在通信中使用的是SPI2,SPI2的时钟源为APB1:36MHZ,因此波特率设置成了18MHZ,而RC522使用SPI进行通信时,最高支持的波特率为10Mbit/s,因此造成了寻卡函数一直返回ERR(254),将这些问题解决了也就成功了。
在调试的过程中一直怀疑是SPI配置的问题,因此,便再细看了32的硬件SPI的手册内容,查漏补缺,自己之前对SPI是比较模糊,仅仅是使用过便不管不顾,这次,增加了对SPI的了解和注意到了以前没注意的点。
在此过程中还出现了一点意外,我在SPI的读写函数中加入了printf来观察接收到的数据,然后主函数中却将Uart的初始化放在Spi后面,导致了串口助手没有数据出来,程序也没有正常运行,调整了顺序便好了。
***SPi端口初始化***
/*
***********************************************************
* 函数功能:SPI端口初始化
* 函数返回值:None
* 函数形参:None
* 备注:sck -- PB_13
miso -- PB_14
mosi -- PB_15
* 修改作者:None
* 修改时间:None
***********************************************************
*/
static void SPI_GPIO_Init(void)
{
//IO功能配置
RCC->APB2ENR |=0x01<<3;
GPIOB->CRH &=~((unsigned int)0xFFF<<20);
GPIOB->CRH |=((unsigned int)0xB8B<<20);
}
此处将SCK和MOSI管脚均设置为复用模式推挽输出,MISO管脚设置为上下拉输入。之前在调试的时候怀疑过是MISO管脚的问题,网上看有的程序将MISO设置为复用推挽输出,查了下
***
恐怕大家对MISO端口的设置就会产生疑惑了,MISO不是应该设置成为输入端口(GPIO_Mode_IN_FLOATING)才行的吗?
答题是肯定的,对于STM32的这一类管脚来说(如USART_RX)即可以设置成为输入模式,也可以设置成为复用的推挽输出。其工作都是正常的,不过建议大家还是设置成为输入端口的好,容易理解。
具体产生这一问题的原因是:从功能上来说,MISO应该配置为输入模式才对,但为什么也可以配置为GPIO_Mode_AF_PP?请看下面的GPIO复用功能配置框图。当一个GPIO端口配置为GPIO_Mode_AF_PP是,这个端口的内部结构框图如下:图中可以看到,片上外设的复用功能输出信号会连接到输出控制电路,然后在端口上产生输出信号。但是在芯片内部,MISO是SPI模块的输入引脚,而不是输出引脚,也就是说图中的"复用功能输出信号"根本不存在,因此"输出控制电路"不能对外产生输出信号。而另一方面看,即使在GPIO_Mode_AF_PP模式下,复用功能输入信号却与外部引脚之间相互连接,既MISO得到了外部信号的电平,实现了输入的功能。***
原子哥的回答是MISO的方向由硬件SPi控制,所以设置为复用推挽输出也是可以的。不过还是建议设置成输入模式,便于理解。
至于代码中在oxFFF和0xB8B前面加入(unsigned int)是因为一开始没加的话会报编译警告,百度了下
***编译器默认signed int即32位有符号整数类型,而1<<31实际为0x80000000,这样就有可能改写了符号位(最高位) ***在前面加(unsigned int)就解决了警告。
SPI初始化
/*
***********************************************************
* 函数功能:SPI初始化
* 函数返回值:None
* 函数形参:None
* 备注:PB_13 PB_14 PB_15
* 修改作者:None
* 修改时间:None
***********************************************************
*/
void SPI_Init(void)
{
SPI_GPIO_Init();
RCC->APB1ENR |=1<<14; //SPI2时钟使能
SPI2->CR1 &=~(1<<11); //8位数据帧
SPI2->CR1 |=1<<9; //启用软件从设备管理
SPI2->CR1 |=1<<8; //NSS引脚电平为高
SPI2->CR1 &=~(1<<7); //先发高位
SPI2->CR1 &=~(0x7<<3);
SPI2->CR1 |=0x01<<3; //波特率:36/4
SPI2->CR1 |=1<<1; //时钟空闲状态为高电平
SPI2->CR1 |=1<<0; //在时钟第二个电平发送数据
SPI2->CR1 |=1<<2; //主SPI
SPI2->CR1 |=1<<6; //使能SPI
SPI_Read_Write(0xff);
}
波特率要与从器件匹配。数据采集时序也要与从器件一致,rc522在上升沿采集数据。
SPI读写数据
/*
***********************************************************
* 函数功能:SPI读写数据
* 函数返回值:None
* 函数形参:None
* 备注:PB_13 PB_14 PB_15
* 修改作者:None
* 修改时间:None
***********************************************************
*/
u8 SPI_Read_Write(u8 data)
{
u8 ret;
while(!(SPI2->SR&(0x01<<1)))
{
}
SPI2->DR=data;
while(!(SPI2->SR&(0x01<<0)))
{
}
ret = SPI2->DR;
// printf("%d\r\n", ret);
return ret;
}
SPI读写同时进行。写入DR的数据被并行传输到移位寄存器,然后与从机串行传输数据,主句接收在移位寄存器中的数据将被并行传输到DR中,接收到的数据即使不使用到也到读取掉。
/////////////////////////////////////////////////////////////////////
//功 能:读RC632寄存器
//参数说明:Address[IN]:寄存器地址
//返 回:读出的值
/////////////////////////////////////////////////////////////////////
unsigned char ReadRawRC(unsigned char Address)
{
u8 ucAddr;
u8 ucResult=0;
RFID_CSL;
ucAddr = ((Address<<1)&0x7E)|0x80;
SPI_Read_Write(ucAddr);
ucResult=SPI_Read_Write(0);
RFID_CSH;
return ucResult;
}
需要得到的数据在RC522接收到寄存器地址后由从机发送,所以主机仍需发送一个数据才能接收到。
/////////////////////////////////////////////////////////////////////
//功 能:写RC632寄存器
//参数说明:Address[IN]:寄存器地址
// value[IN]:写入的值
/////////////////////////////////////////////////////////////////////
void WriteRawRC(unsigned char Address, unsigned char value)
{
u8 ucAddr;
// u8 tmp;
RFID_CSL;
ucAddr = ((Address<<1)&0x7E);
SPI_Read_Write(ucAddr);
SPI_Read_Write(value);
RFID_CSH;
}
先写入寄存器地址,再写数据。