基于STM32的MFRC522射频卡模块使用
本学期感测技术选修课需要做一个作品出来,用到了MFRC522射频卡模块,经历一个星期的调试,终于可以正常使用并寻卡成功了了。 成功的把C51的程序移植到了STM32上面。 现在分享一下调试过程
1、操作环境
我所使用的是STM32F407的开发板,使用STM32CubeMX配置初始代码。 MFRC522使用软件模拟SPI通信
2、 关于引脚的配置
淘宝买来的模块,店家都会送资料 ,也可以点下面连接保存至网盘
1、SPI通信引脚
NSS(SDA) --------->> 片选信号
SCK --------->> 时钟信号
MOSI --------->> 信号输出端(即单片机引脚设置为输入,MFRC522该引脚输出)
MISO --------->> 信号输入端
(上图截图于数据手册,移植别人的程序最好看一下所使用的芯片的数据手册,很有用,方便自己理解程序)
这里说明一下,在 MFRC522数据手册里面说了, MFRC522需要工作在从机模式下。
所以MFRC522这个模块就是从机(Slave),而所使用的单片机就是主机(Master)
这就是为什么上面的MOSI对应的单片机引脚要设置为输出,(Master Output Slave Input)
MISO信号输入端是指的输入给单片机了
2、 通信时序
这是数据手册里面的,一定要注意时序的正确性
片选信号在数据写入期间一定要保持低电平,而无数据时(即空闲状态)必须保持高电平
强调:时序很重要
时序出错,一切都白扯
3、 程序流程
下面我把我用STM32CubeMX的配置贴出来
一定要注意按照这样配置,因为数据手册里面的时序要求是NSS(SDA)引脚默认状态必须是高电平,即1,所以IO口设置必须为High, 且上拉,其他引脚同理,只是不需要上拉了
3、 下面先贴一下寻卡结果
S50的卡是0x04000, 所以打印的就是40了
主函数里面程序
int main(void)
{
/* USER CODE BEGIN 1 */
unsigned char status,i;
unsigned int temp;
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
printf("The USART Is Ok!!!\r\n");
// 下面进行的是初始化
PcdReset();
PcdAntennaOff(); //关闭天线
PcdAntennaOn(); //开启天线
M500PcdConfigISOType('A'); // 选择工作方式
printf("开始寻卡... ...\r\n");
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
status = PcdRequest(PICC_REQALL, g_ucTempbuf);//寻卡
if (status == MI_ERR) // 如果寻卡失败,则重新初始化 然后continue 继续寻卡
{
PcdReset();
PcdAntennaOff(); //关闭天线
PcdAntennaOn(); //开启天线
M500PcdConfigISOType('A');
continue;
}
// 如果寻卡成功 则LED1闪烁 然后串口打印出来卡的类型
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);
HAL_Delay(10);
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);
HAL_Delay(10);
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);
HAL_Delay(10);
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);
HAL_Delay(10);
printf("\r\n卡的类型:");
for (i = 0; i < 2; i++)
{
temp = g_ucTempbuf[i];
printf("%X", temp);
}
//PcdHalt();
}
/* USER CODE END 3 */
}
主程序前面还要定义一个变量,要不然会出现g_ucTempbuff未定义的情况:
unsigned char g_ucTempbuf[20];
下面是我移植的底层驱动程序,应该也是大部分人想要的吧,不过最好还是自己好好看看那手册改一下
我只贴出有关SPI通讯的程序,其他部分跟我上面给出的网盘资料里面的C51例程是差不多的,通用
/*******************************************************************
@func : ReadRawRC
@brief : 读RC632寄存器
@pram : Address[IN]:寄存器地址
@retval : 读出的值
@NOTE : MFRC522数据手册.pdf 10.2是关于SPI的详细说明 10.2.2 Read data
: unsigned char === uint8_t
@Call : 内部调用
*******************************************************************/
unsigned char ReadRawRC(unsigned char Address)
{
unsigned char i, ucAddr;
unsigned char ucResult=0;
HAL_GPIO_WritePin(NSS_GPIO_Port, NSS_Pin, GPIO_PIN_RESET);// MF522_NSS = 0;
HAL_GPIO_WritePin(SCK_GPIO_Port, SCK_Pin, GPIO_PIN_RESET);// MF522_SCK = 0;
// 地址左移一位是因为LSB是要保留 即RFU位(Reserved for Future Use)
// &0x7E 是把bit1~bit6 的地址(address)写入
// |0x80 是为了使最高位为1 1(Read) 0(Write) 即使能 '读'
ucAddr = ((Address<<1)&0x7E)|0x80;
for(i=8;i>0;i--)
{
if((ucAddr&0x80)==0x80)
{
HAL_GPIO_WritePin(MOSI_GPIO_Port, MOSI_Pin, GPIO_PIN_SET);
}
else
{
HAL_GPIO_WritePin(MOSI_GPIO_Port, MOSI_Pin, GPIO_PIN_RESET);
}
HAL_GPIO_WritePin(SCK_GPIO_Port, SCK_Pin, GPIO_PIN_SET);
ucAddr <<= 1;
HAL_GPIO_WritePin(SCK_GPIO_Port, SCK_Pin, GPIO_PIN_RESET);
}
for(i=8;i>0;i--)
{
HAL_GPIO_WritePin(SCK_GPIO_Port, SCK_Pin, GPIO_PIN_SET);
ucResult <<= 1;
ucResult |= HAL_GPIO_ReadPin(MISO_GPIO_Port, MISO_Pin);
HAL_GPIO_WritePin(SCK_GPIO_Port, SCK_Pin, GPIO_PIN_RESET);
// 有人说对于STM32这里需要加一句延时,这个是没必要的 这个我经过测试是可以使用的,不用延时
}
HAL_GPIO_WritePin(NSS_GPIO_Port, NSS_Pin, GPIO_PIN_SET);// MF522_NSS = 1;
HAL_GPIO_WritePin(SCK_GPIO_Port, SCK_Pin, GPIO_PIN_SET);// MF522_SCK = 1;
return ucResult;
}
/*******************************************************************
@func : WriteRawRC
@brief : 写RC632寄存器
@pram : Address[IN]:寄存器地址
: value[IN]:写入的值
@retval : None
@Call : 内部调用
*******************************************************************/
void WriteRawRC(unsigned char Address, unsigned char value)
{
unsigned char i, ucAddr;
HAL_GPIO_WritePin(SCK_GPIO_Port, SCK_Pin, GPIO_PIN_RESET);// MF522_SCK = 0;
HAL_GPIO_WritePin(NSS_GPIO_Port, NSS_Pin, GPIO_PIN_RESET);// MF522_NSS = 0;
ucAddr = ((Address << 1) & 0x7E);
for(i=8;i>0;i--)
{
if ((ucAddr&0x80)==0x80)
{
HAL_GPIO_WritePin(MOSI_GPIO_Port, MOSI_Pin, GPIO_PIN_SET);
}
else
{
HAL_GPIO_WritePin(MOSI_GPIO_Port, MOSI_Pin, GPIO_PIN_RESET);
}
HAL_GPIO_WritePin(SCK_GPIO_Port, SCK_Pin, GPIO_PIN_SET);
ucAddr <<= 1;
HAL_GPIO_WritePin(SCK_GPIO_Port, SCK_Pin, GPIO_PIN_RESET);
}
for(i=8;i>0;i--)
{
// MF522_SI = ((value&0x80)==0x80);
if ((value&0x80)==0x80)
{
HAL_GPIO_WritePin(MOSI_GPIO_Port, MOSI_Pin, GPIO_PIN_SET);
}
else
{
HAL_GPIO_WritePin(MOSI_GPIO_Port, MOSI_Pin, GPIO_PIN_RESET);
}
HAL_GPIO_WritePin(SCK_GPIO_Port, SCK_Pin, GPIO_PIN_SET);
value <<= 1;
HAL_GPIO_WritePin(SCK_GPIO_Port, SCK_Pin, GPIO_PIN_RESET);
}
HAL_GPIO_WritePin(NSS_GPIO_Port, NSS_Pin, GPIO_PIN_SET);// MF522_NSS = 1;
HAL_GPIO_WritePin(SCK_GPIO_Port, SCK_Pin, GPIO_PIN_SET);// MF522_SCK = 1;
}
复位函数
/*******************************************************************
@func : PcdReset
@brief : 复位RC522
@pram : None
@retval : 成功返回MI_OK
@NOTE : 外部调用
*******************************************************************/
char PcdReset(void)
{
/* MF522_RST=1; */
HAL_GPIO_WritePin(NSS_GPIO_Port, NSS_Pin, GPIO_PIN_SET);
HAL_Delay(10);
/* MF522_RST=0; */
HAL_GPIO_WritePin(NSS_GPIO_Port, NSS_Pin, GPIO_PIN_RESET);
HAL_Delay(10);
/* MF522_RST=1; */
HAL_GPIO_WritePin(NSS_GPIO_Port, NSS_Pin, GPIO_PIN_SET);
HAL_Delay(10);
WriteRawRC(CommandReg,PCD_RESETPHASE); // 复位
HAL_Delay(10);
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 调试的时候加上这一句试试
return MI_OK;
}
其他的底层驱动函数就不需要改了,由于总的代码量比较长,我就只贴出关键的,其他不需要改的直接参考资料里面的例程即可
我自己移植过来完整的有很多程序的注注释,有兴趣的可以下载一下,不过自己花时间看看数据手册打个注释是最好的
任何你的不足,在你成功地那一刻,都会被别人说成特色!! 加油吧