3.3 RC522
3.3.1 底层SPI协议简介
SPI(Serial Peripheral Interface,串行外设接口)是一种高速、全双工、同步通信总线,常用于短距离通讯,主要应用于 EEPROM、FLASH、实时时钟、AD 转换器、还有数字信号处理器和数字信号解码器之间。SPI 一般使用 4 根线通信,如下图所示:
- MOSI –主机输出 / 从机输入数据线(SPI Bus Master Output/Slave Input)。
- MISO –主机输入 / 从机输出数据线(SPI Bus Master Input/Slave Output)。
- SCLK –串行时钟线(Serial Clock),主设备输出时钟信号至从设备。
- CS –从设备选择线 (Chip select)。也叫 SS、CSB、CSN、EN 等,主设备输出片选信号至从设备。
整体的传输大概可以分为以下几个过程:
(1)主机先将NSS
信号拉低,这样保证开始接收数据;
(2)当接收端检测到时钟的边沿信号时,它将立即读取数据线上的信号,这样就得到了一位数据(1bit
;由于时钟是随数据一起发送的,因此指定数据的传输速度并不重要,尽管设备将具有可以运行的最高速度。
(3)主机发送到从机时:主机产生相应的时钟信号,然后数据一位一位地将从MOSI
信号线上进行发送到从机;
(4)主机接收从机数据:如果从机需要将数据发送回主机,则主机将继续生成预定数量的时钟信号,并且从机会将数据通过MISO
信号线发送;
3.3.2 RC522读卡机制说明
首先来看下RC522与M1卡的通讯流程:
寻卡->防止卡片冲撞->选卡->休眠->发送0x40(7bit)->发送0x43->发送0xa0等4字节->发送0x00等18字节
- 复位应答(Request):M1卡的通信协议和通信波特率是定义好的,当有卡片进入读卡器的工作范围时,读卡器要以特定的协议与卡片通信,从而确定卡片的卡型。
- 防冲突机制(Anticollision Loop):当有多张卡片进入读写器操作范围时,会从中选择一张卡片进行操作,并返回选中卡片的序列号。
- 选择卡片(Select Tag):选择被选中的卡的序列号,并同时返回卡的容量代码。
- 三次相互确认(3 Pass Authentication):选定要处理的卡片后,读写器就要确定访问的扇区号,并且对扇区密码进行密码校验。在三次互相认证后就可以通过加密流进行通信。每次在选择扇区的时候都要进行扇区的密码校验。
- 对数据块的操作:
读(Read):读一个块的数据;
写(Write):在一个块中写数据;
加(Increment):对数据块中的数值进行加值;
减(Decrement):对数据块中的数值进行减值;
传输(Transfer):将数据寄存器中的内容写入数据块中;
中止(Halt):暂停卡片的工作;
3.3.3 RC522在RT-Thread的使用
首先打开settings,添加RC522软件包,并在硬件部分使能SPI1
打开瑞萨FSP,添加一个名为r_spi的新stack,并进行如下配置:
引脚接线:
引脚功能 | 引脚接线 |
MOSI | P411 |
MISO | P410 |
SCL | P412 |
SDA | P311 |
RST | P312 |
VCC | 3.3V |
GND | GND |
IRQ | 悬空 |
代码部分参考RC522sample
SPI初始化配置:
#include "mfrc522.h" static struct rt_spi_device mfrc522_spi_dev; struct rt_hw_spi_cs { rt_uint32_t pin; }; static struct rt_hw_spi_cs spi_cs; static int rt_hw_spi_rc522_init() { rt_err_t res = RT_EOK; // Attach Device spi_cs.pin = MFRC522_SS_PIN; rt_pin_mode(spi_cs.pin, PIN_MODE_OUTPUT); res = rt_spi_bus_attach_device(&mfrc522_spi_dev, MFRC522_SPI_DEVICE_NAME, MFRC522_SPI_BUS_NAME, (void*)&spi_cs); if (res != RT_EOK) { rt_kprintf("[RC522] Failed to attach device %s\n", MFRC522_SPI_DEVICE_NAME); return res; } // Set device SPI Mode struct rt_spi_configuration cfg = {0}; cfg.data_width = 8; cfg.mode = RT_SPI_MASTER | RT_SPI_MODE_0 | RT_SPI_MSB | RT_SPI_NO_CS; cfg.max_hz = MFRC522_SPICLOCK; rt_spi_configure(&mfrc522_spi_dev, &cfg); return RT_EOK; } /* 导出到自动初始化 */ INIT_COMPONENT_EXPORT(rt_hw_spi_rc522_init);
另外需要在完成一下配置,双击打开mfrc522.h,修改MFRC522_SS_PIN为0x3b,MFRC522_RST_PIN为0x3c,分别对应SDA和RST引脚
打开mfrc522.c,修改配置MFRC522_SS_PIN
及MFRC522_RST_PIN
打开rtconfig.h,找到以下两个引脚的定义,修改成如下:
注意:一旦在RT-Thread settings中做了相关操作并保存设置后,在rtconfig.h中的配置都会以settings中的配置为准而被全部刷新,所以需要保留一个备份,下次保存设置的时候记得重新修改配置
#define MFRC522_SS_PIN 0x3b #define MFRC522_RST_PIN 0x3c
至此,RC522的相关配置结束
3.4 SSD1306
3.4.1 底层I2C通信协议
(这里参考AHT10关于I2C通信协议的介绍,此处不再赘述)
3.4.2 SSD1306在RT-Thred的使用
接线示意:
引脚功能 | 引脚接线 |
SCL | P400 |
SDA | P401 |
VCC | 3.3V |
GND | GND |
RT-Thread Settings配置:
添加ssd1306软件包,然后跳转到配置界面修改i2c address为0x3c,bus name为i2c0
打开rtconfig.h,添加i2c代码,注意之前在rtconfig.h中进行的配置已经被刷新,需要重新添加配置代码
:
#define BSP_USING_I2C #define BSP_USING_I2C0 #define BSP_I2C0_SCL_PIN 0x400 #define BSP_I2C0_SDA_PIN 0x401
打开drv_soft_i2c.c文件,添加代码:
#ifdef BSP_USING_I2C0 #define I2C0_BUS_CONFIG \ { \ .scl = BSP_I2C0_SCL_PIN, \ .sda = BSP_I2C0_SDA_PIN, \ .bus_name = "i2c0", \ } #endif
打开瑞萨FSP,新建一个r_iic_master的new stack,完成以下配置:
生成配置之后添加用户代码:
#include "ssd1306.h" void oled_init() { ssd1306_Init(); ssd1306_Fill(Black); ssd1306_SetCursor(10, 25); ssd1306_WriteString("Hello RT-Thread!", Font_7x10, White); ssd1306_UpdateScreen(); } INIT_APP_EXPORT(oled_init);
实时时钟显示代码:
ssd1306_Fill(White); ssd1306_SetCursor(0, 5); ssd1306_WriteString("Now Time", Font_16x26, Black); ssd1306_SetCursor(40, 40); ssd1306_WriteString(mstr, Font_11x18, Black); ssd1306_SetCursor(50, 40); ssd1306_WriteString(":", Font_11x18, Black); ssd1306_SetCursor(60, 40); ssd1306_WriteString(hstr, Font_11x18, Black); ssd1306_UpdateScreen();
温湿度数据显示代码:
ssd1306_Fill(White); ssd1306_SetCursor(4, 2); ssd1306_WriteString("Humi_Temp_Detection!", Font_7x10, Black); ssd1306_UpdateScreen(); rt_thread_mdelay(1000); char buff[64]; snprintf(buff, sizeof(buff), "Temperature: %d.%d\n", (int)temperature, (int)(temperature * 10) % 10); ssd1306_SetCursor(15, 30); ssd1306_WriteString(buff, Font_6x8, Black); ssd1306_UpdateScreen(); rt_kprintf("Temperature_OLED : %d.%d\n", (int)temperature, (int)(temperature * 10) % 10); snprintf(buff, sizeof(buff), "Humidity:%d.%d\n", (int)humidity, (int)(humidity * 10) % 10); ssd1306_SetCursor(25, 47); ssd1306_WriteString(buff, Font_6x8, Black); ssd1306_UpdateScreen(); rt_kprintf("Humidity_OLED : %d.%d\n", (int)humidity, (int)(humidity * 10) % 10);
4、整体代码框架
4.1 多线程任务分配
本次细分作品功能,共分为四大模块:分别是AHT10温湿度读取、onenet上云、oled显示、rc522读卡。
所以共创建四个线程:
(1)RC522_thread:用于RC522读卡
(2)aht10_read_thread:用于aht10读取温湿度数值
(3)onenet_aht10_thread:云端数据上报
(4)oled_thread:OLED显示
4.2 线程间交互
本次在IPC方面的使用很不成熟,只是在每个线程的入口函数中进行互斥量的保护,并没有将RT-Thread内核机制灵活运用到代码中,是我此次学习的最大不足,其实也做过一些例如邮箱机制的使用,但是由于数据显示异常而没有进行下去,在工程源码的ITNG_Project2中包含了这种机制的使用,也就是说提供了两套方案,但是确实个人效率太低,第二种方案被搁置。
4.3 代码整合
在本次的程序设计中,我使用了一个while循环结合switch选择语句来保证整体代码的运行,在线程的入口程序使用互斥量来完成资源的保护,但是RT-Thread多线程机制的使用也是仍显不足。
都说程序设计也是艺术设计,要学会使用代码抽象人类社会的运行机制,程序设计方面,我设计的不合理,导致整个项目如同流水线般运行,亮点不大,值得反思。
5、踩坑指南
其实大部分踩坑说明在上面的教学指南中一般都有说明,这里简单说些:
(1)注意瑞萨FSP目前在RT-Thread中的支持包版本为v3.5.0
(2)由于瑞萨有自己完整的生态开发工具,所以RT-Thread与瑞萨合作时对于底层驱动的定义只有部分,还有一些需要在FSP中进行配置并生成配置。同时在HAL库中也需要添加相应的驱动代码,同时记得需要在settings中将相应的外设支持打开。
(3)对于每次的settings设置,其实都会生成相关的宏和定义在rtconfig.h文件中,所以每次更行settings时都会将用户在rtconfig.h中添加的代码删除,这时候需要重新添加,否则会生成一些宏未定义的错误。