智能家居助手以R7FA4M2AD3CFP为主控器,基于FreeRTOS开发,该系统主要用于监控室内环境信息,自动/远程控制室内设备,还有门禁功能,将门卡信息保存到手机,从此告别钥匙。
智能家居助手的核心功能如下:
智能家居助手以R7FA4M2AD3CFP主控芯片组为核心,由温湿度模块、光感模块、OLED显示模块、PN632、电机等模块构成。
门禁子系统由AT24C02和PN532构成,由于引脚比较多,这里使用一个RA4M2作为从设备来,通过485总线与主控通信。
这里选用PN532为NFC的读卡器,通信接口可选UART,SPI,I2C,笔者使用I2C作为通信接口,PN532的模块如下图所示。
PN532的硬件威廉希尔官方网站 图如下。
根据官方手册,我们了解到PN532可以使用UART,SPI,I2C与主控MCU进行通信,具体通信方式根据P16和P17引脚来决定。
值得注意的是,硬件通信接口只指定通信介质和时序,跟软件通信协议无关,无论何种通信接口,其传输数据时收包和发包的内容都是一致的,由软件层面的通信协议来决定。具体使用时不同的通信接口单双工模式稍有差别,SPI和UART在硬件层面是双工的,I2C则是单工的,软件层面的协议要仔细看手册,PN532所有通信行为都是单工的。
窗帘控制子模块由光敏电阻传感器和电机构成。
光敏电阻器一般用于光的测量、光的控制和光电转换(将光的变化转换为电的变化)。光敏电阻传感器模块威廉希尔官方网站 如下图所示:
DO:数字开关量输出(0 和 1),DO 输出端可以与单片机直接相连,通过单片机来检测高低电平,由此来检测环境的光线亮度改变
AO:模拟信号输出,最简单的模拟输出的威廉希尔官方网站 如下:
光敏电阻传感器模块一般有有三线和四线的接法,四线相比三线多了AO。AO的使用相对比较复杂,需要用到ADC,本文将讲解如何使用AO来控制LED。DO就不讲了,就一个检测高低电平,比较简单。
本文使用的光敏电阻模块如下所示:
光敏电阻模块接线图如下:
当然为了方便,这里使用数字开关量作为窗帘控制的中断信号。
温湿度子模块由HS3003和OLED构成。
HS3003是一款高精度的温湿度传感器,标准I2C格式。
温度湿度读取时序如下:
HS3003在睡眠模式下,传感器一直等待主机发送测量命令。CPU此时只执行温湿度测量指令,其余指令无法唤醒传感器进行数据采集。
主机发送采集命令后即可唤醒HS3003进行数据采集,启动采集只需要发送7位从机地址,第八位写0即可。
采集数据时,传感器内部的数字信号处理器会对采集到的温湿度数据进行计算,并且内部算法对数据进行校正运算:采集结束后,传感器的输出寄存器会将数据进行更新,
测量周期由湿度和温度转换后的数字信号处理器(DSP)校正计算。在测量周期结束时,数字电源关闭之前输出寄存器将被更新。测量数据以14位的输出,数据位以右对齐。
HS3003输出的真实的相对湿度(%)和温度(℃)数据通过以下公式进行计算。
相对湿度:
温度转换:
HS3003模块相关威廉希尔官方网站 如下图所示:
根据可知HS300x Datasheet,HS3003的地址为0x44。
HS3003接的RA4M2的I2C0,SCL和SDA分别接的是P400和P401引脚。
智能家居助手的软件基于FreeRTOS实现,其软件架构如下:
整个软件架构分为四层:Hardware、Driver、FreeRTOS Kernel、Application。
Hardware层:基础为外设有UART、I2C、GPIO,UART用于调试,I2C用于与外设通信。
Driver层:主要为上层应用提供驱动接口。
FreeRTOS Kernel层:FreeRTOS内容比较多,除了基础内核外,还包含了丰富的组件和第三方库,主
要包含以下组成部分:
Application层:Application部分包含系统任务和用户自定义任务,用户自定义任务包含通信、调试、控制、算法等模块。
整个软件的运行如下:
(1)硬件初始化
主要初始化按键、OLED、温湿度等资源。
(2)OS初始化
该阶段主要进行OS任务启动
(3)任务运行阶段
为了实现数据在模块之间的传递,方便参数和硬件资源标准化,从而可以采用相同的方法来访问和控制模块内部的资源,线面是本系统遵循的通信协议。
Table ‑ 通信协议
1.帧头
帧头为通信数据的开头,固定1个字节,为3A。
2.状态
状态为通信过程中接收端返回的操作信息。
3.功能码
这个选项对命令进一步补充。这里暂时只有读、写操作。
4.设备地址
设备地址通常是用于多种设备之间,为了方便区分不同的同类型设备。
5.数据类型
数据类型用于区分不同的设备操作。
6.数据长度
数据长度这个选项,可能有的协议会把该选项提到前面设备地址位置,把命令这些信息算在“长度”里面。
7.数据
数据就是传输的实实在在的数据,比如温度:25℃。
8.校验码
校验码是为CRC16。
9.帧尾
帧尾为通信数据的结尾,固定1个字节,为3F。
智能家居助手基于FreeRTOS开发,整个工程结构如下。
下面对通信协议的具体定义:
/* Exported macro ------------------------------------------------------------*/
#define SF_PACKAGE_LENGTH 34
#define SF_HEAD 0x3A //Head
typedef enum //Status
{
SF_SUCCESS = 0x00,
SF_FAIL = 0x01,
}EN_Status;
typedef enum //Function code
{
SF_WRITE = 0x01,
SF_READ = 0x02,
}EN_Fun_code;
typedef enum //Data type
{
SF_FDC_TYPE = 0x0101,
SF_ACC_TYPE = 0x0201,
SF_GRAY_TYPE = 0x0202,
SF_MAG_TYPE = 0x0203,
SF_EULER_TYPE = 0x0204,
}EN_Data_Type;
#define SF_END 0xFF //Head
/* Exported types ------------------------------------------------------------*/
//Communication protocol
#pragma pack(1)
typedef struct
{
uint8_t Head; //head
uint8_t Status; //satus
uint8_t FunCode; //function code
uint8_t Addr; //address
union dataType_un //data type
{
uint16_t DataType16;
uint8_t DataType8[2];
}UN_DataType;
uint8_t DataLen; //data length
union data_un //data
{
uint8_t Data8[24];
uint16_t Data16[12];
uint32_t Data32[4];
}UN_Data;
union crc_un
{
uint8_t CRC8[2];
uint16_t CRC16;
}UN_CRC;
uint8_t End; //end of frame
}ST_Data_Package;
在通信过程中,发送很简单,直接发送整个buff即可,接收一般采用中断,然后根据协议的内容一步步解析。
门禁子系统流程如下:
门禁子系统的核心就是对NFC的操作,相关的协议请参看PN532的数据手册,下面给出使用功能I2C读写NFC的相关代码。
据模块化的思维,我们在工程中新建一个pn532.c和pn532.h,以及一个pn532hw.c的文件,在pn532.c中尽量完成所有与PN532相关协议栈的处理,在pn532hw.c文件中完成与硬件相关的耦合。
【pn532hw.c】
void i2c_read(uint8_t* data, uint16_t count)
{
R_IIC_MASTER_Read (&g_i2c_master0_ctrl, data, count, false);
}
void i2c_write(uint8_t* data, uint16_t count)
{
R_IIC_MASTER_Write(&g_i2c_master0_ctrl, data, count, true);
}
int PN532_I2C_ReadData(uint8_t* data, uint16_t count)
{
uint8_t status[] = {0x00};
uint8_t frame[count + 1];
i2c_read(status, sizeof(status));
if (status[0] != PN532_I2C_READY)
{
return PN532_STATUS_ERROR;
}
i2c_read(frame, count + 1);
for (uint8_t i = 0; i < count; i++)
{
data[i] = frame[i + 1];
}
return PN532_STATUS_OK;
}
int PN532_I2C_WriteData(uint8_t *data, uint16_t count)
{
i2c_write(data, count);
return PN532_STATUS_OK;
}
bool PN532_I2C_WaitReady(uint32_t timeout)
{
uint8_t status[] = {0x00};
uint32_t tickstart = 1000;
while (tickstart < timeout)
{
i2c_read(status, sizeof(status));
if (status[0] == PN532_I2C_READY)
{
return true;
}
else
{
R_BSP_SoftwareDelay(100, BSP_DELAY_UNITS_MILLISECONDS);
}
}
return false;
}
int PN532_I2C_Wakeup(void)
{
// TODO
R_IOPORT_PinWrite(&g_ioport_ctrl, PN532_REQ_PORT, BSP_IO_LEVEL_HIGH);
R_BSP_SoftwareDelay(100, BSP_DELAY_UNITS_MILLISECONDS);
R_IOPORT_PinWrite(&g_ioport_ctrl, PN532_REQ_PORT, BSP_IO_LEVEL_LOW);
R_BSP_SoftwareDelay(100, BSP_DELAY_UNITS_MILLISECONDS);
R_IOPORT_PinWrite(&g_ioport_ctrl, PN532_REQ_PORT, BSP_IO_LEVEL_HIGH);
R_BSP_SoftwareDelay(100, BSP_DELAY_UNITS_MILLISECONDS);
return PN532_STATUS_OK;
}
void PN532_I2C_Init(PN532* pn532)
{
// init the pn532 functions
pn532->reset = PN532_Reset;
pn532->read_data = PN532_I2C_ReadData;
pn532->write_data = PN532_I2C_WriteData;
pn532->wait_ready = PN532_I2C_WaitReady;
pn532->wakeup = PN532_I2C_Wakeup;
pn532->log = PN532_Log;
// hardware wakeup
pn532->wakeup();
}
pn532hw.c实现了PN532与RA4M2的通信。
【pn532.c】
/**************************************************************************
* [url=home.php?mod=space&uid=1455510]@file[/url] pn532.c
* [url=home.php?mod=space&uid=40524]@author[/url] Yehui from Waveshare
* [url=home.php?mod=space&uid=285243]@license[/url] BSD
*
* This is a library for the Waveshare PN532 NFC modules
*
* Check out the links above for our tutorials and wiring diagrams
* These chips use SPI communicate.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documnetation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
**************************************************************************/
#include <stdio.h>
#include "pn532.h"
const uint8_t PN532_ACK[] = {0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00};
const uint8_t PN532_FRAME_START[] = {0x00, 0x00, 0xFF};
#define PN532_FRAME_MAX_LENGTH 255
#define PN532_DEFAULT_TIMEOUT 1000
/**
* @brief: Write a frame to the PN532 of at most length bytes in size.
* Note that less than length bytes might be returned!
* @retval: Returns -1 if there is an error parsing the frame.
*/
int PN532_WriteFrame(PN532* pn532, uint8_t* data, uint16_t length) {
if (length > PN532_FRAME_MAX_LENGTH || length < 1) {
return PN532_STATUS_ERROR; // Data must be array of 1 to 255 bytes.
}
// Build frame to send as:
// - Preamble (0x00)
// - Start code (0x00, 0xFF)
// - Command length (1 byte)
// - Command length checksum
// - Command bytes
// - Checksum
// - Postamble (0x00)
uint8_t frame[PN532_FRAME_MAX_LENGTH + 7];
uint8_t checksum = 0;
frame[0] = PN532_PREAMBLE;
frame[1] = PN532_STARTCODE1;
frame[2] = PN532_STARTCODE2;
for (uint8_t i = 0; i < 3; i++) {
checksum += frame[i];
}
frame[3] = length & 0xFF;
frame[4] = (~length + 1) & 0xFF;
for (uint8_t i = 0; i < length; i++) {
frame[5 + i] = data[i];
checksum += data[i];
}
frame[length + 5] = ~checksum & 0xFF;
frame[length + 6] = PN532_POSTAMBLE;
if (pn532->write_data(frame, length + 7) != PN532_STATUS_OK) {
return PN532_STATUS_ERROR;
}
return PN532_STATUS_OK;
}
/**
* @brief: Read a response frame from the PN532 of at most length bytes in size.
* Note that less than length bytes might be returned!
* @retval: Returns frame length or -1 if there is an error parsing the frame.
*/
int PN532_ReadFrame(PN532* pn532, uint8_t* response, uint16_t length) {
uint8_t buff[PN532_FRAME_MAX_LENGTH + 7];
uint8_t checksum = 0;
// Read frame with expected length of data.
pn532->read_data(buff, length + 7);
// Swallow all the 0x00 values that preceed 0xFF.
uint8_t offset = 0;
while (buff[offset] == 0x00) {
offset += 1;
if (offset >= length + 8){
pn532->log("Response frame preamble does not contain 0x00FF!");
return PN532_STATUS_ERROR;
}
}
if (buff[offset] != 0xFF) {
pn532->log("Response frame preamble does not contain 0x00FF!");
return PN532_STATUS_ERROR;
}
offset += 1;
if (offset >= length + 8) {
pn532->log("Response contains no data!");
return PN532_STATUS_ERROR;
}
// Check length & length checksum match.
uint8_t frame_len = buff[offset];
if (((frame_len + buff[offset+1]) & 0xFF) != 0) {
pn532->log("Response length checksum did not match length!");
return PN532_STATUS_ERROR;
}
// Check frame checksum value matches bytes.
for (uint8_t i = 0; i < frame_len + 1; i++) {
checksum += buff[offset + 2 + i];
}
checksum &= 0xFF;
if (checksum != 0) {
pn532->log("Response checksum did not match expected checksum");
return PN532_STATUS_ERROR;
}
// Return frame data.
for (uint8_t i = 0; i < frame_len; i++) {
response[i] = buff[offset + 2 + i];
}
return frame_len;
}
/**
* @brief: Send specified command to the PN532 and expect up to response_length.
* Will wait up to timeout seconds for a response and read a bytearray into
* response buffer.
* [url=home.php?mod=space&uid=3142012]@param[/url] pn532: PN532 handler
* @param command: command to send
* @param response: buffer returned
* @param response_length: expected response length
* @param params: can optionally specify an array of bytes to send as parameters
* to the function call, or NULL if there is no need to send parameters.
* @param params_length: length of the argument params
* @param timeout: timout of systick
* @retval: Returns the length of response or -1 if error.
*/
int PN532_CallFunction(
PN532* pn532,
uint8_t command,
uint8_t* response,
uint16_t response_length,
uint8_t* params,
uint16_t params_length,
uint32_t timeout
) {
// Build frame data with command and parameters.
uint8_t buff[PN532_FRAME_MAX_LENGTH];
buff[0] = PN532_HOSTTOPN532;
buff[1] = command & 0xFF;
for (uint8_t i = 0; i < params_length; i++) {
buff[2 + i] = params[i];
}
// Send frame and wait for response.
if (PN532_WriteFrame(pn532, buff, params_length + 2) != PN532_STATUS_OK) {
pn532->wakeup();
pn532->log("Trying to wakeup");
return PN532_STATUS_ERROR;
}
if (!pn532->wait_ready(timeout)) {
return PN532_STATUS_ERROR;
}
// Verify ACK response and wait to be ready for function response.
pn532->read_data(buff, sizeof(PN532_ACK));
for (uint8_t i = 0; i < sizeof(PN532_ACK); i++) {
if (PN532_ACK[i] != buff[i]) {
pn532->log("Did not receive expected ACK from PN532!");
return PN532_STATUS_ERROR;
}
}
if (!pn532->wait_ready(timeout)) {
return PN532_STATUS_ERROR;
}
// Read response bytes.
int frame_len = PN532_ReadFrame(pn532, buff, response_length + 2);
// Check that response is for the called function.
if (! ((buff[0] == PN532_PN532TOHOST) && (buff[1] == (command+1)))) {
pn532->log("Received unexpected command response!");
return PN532_STATUS_ERROR;
}
// Return response data.
for (uint8_t i = 0; i < response_length; i++) {
response[i] = buff[i + 2];
}
// The the number of bytes read
return frame_len - 2;
}
/**
* @brief: Call PN532 GetFirmwareVersion function and return a buff with the IC,
* Ver, Rev, and Support values.
*/
int PN532_GetFirmwareVersion(PN532* pn532, uint8_t* version) {
// length of version: 4
if (PN532_CallFunction(pn532, PN532_COMMAND_GETFIRMWAREVERSION,
version, 4, NULL, 0, 500) == PN532_STATUS_ERROR) {
pn532->log("Failed to detect the PN532");
return PN532_STATUS_ERROR;
}
return PN532_STATUS_OK;
}
/**
* @brief: Configure the PN532 to read MiFare cards.
*/
int PN532_SamConfiguration(PN532* pn532) {
// Send SAM configuration command with configuration for:
// - 0x01, normal mode
// - 0x14, timeout 50ms * 20 = 1 second
// - 0x01, use IRQ pin
// Note that no other verification is necessary as call_function will
// check the command was executed as expected.
uint8_t params[] = {0x01, 0x14, 0x01};
PN532_CallFunction(pn532, PN532_COMMAND_SAMCONFIGURATION,
NULL, 0, params, sizeof(params), PN532_DEFAULT_TIMEOUT);
return PN532_STATUS_OK;
}
/**
* @brief: Wait for a MiFare card to be available and return its UID when found.
* Will wait up to timeout seconds and return None if no card is found,
* otherwise a bytearray with the UID of the found card is returned.
* @retval: Length of UID, or -1 if error.
*/
int PN532_ReadPassiveTarget(
PN532* pn532,
uint8_t* response,
uint8_t card_baud,
uint32_t timeout
) {
// Send passive read command for 1 card. Expect at most a 7 byte UUID.
uint8_t params[] = {0x01, card_baud};
uint8_t buff[19];
int length = PN532_CallFunction(pn532, PN532_COMMAND_INLISTPASSIVETARGET,
buff, sizeof(buff), params, sizeof(params), timeout);
if (length < 0) {
return PN532_STATUS_ERROR; // No card found
}
// Check only 1 card with up to a 7 byte UID is present.
if (buff[0] != 0x01) {
pn532->log("More than one card detected!");
return PN532_STATUS_ERROR;
}
if (buff[5] > 7) {
pn532->log("Found card with unexpectedly long UID!");
return PN532_STATUS_ERROR;
}
for (uint8_t i = 0; i < buff[5]; i++) {
response[i] = buff[6 + i];
}
return buff[5];
}
/**
* @brief: Authenticate specified block number for a MiFare classic card.
* @param uid: A byte array with the UID of the card.
* @param uid_length: Length of the UID of the card.
* @param block_number: The block to authenticate.
* @param key_number: The key type (like MIFARE_CMD_AUTH_A or MIFARE_CMD_AUTH_B).
* @param key: A byte array with the key data.
* @retval: PN532 error code.
*/
int PN532_MifareClassicAuthenticateBlock(
PN532* pn532,
uint8_t* uid,
uint8_t uid_length,
uint16_t block_number,
uint16_t key_number,
uint8_t* key
) {
// Build parameters for InDataExchange command to authenticate MiFare card.
uint8_t response[1] = {0xFF};
uint8_t params[3 + MIFARE_UID_MAX_LENGTH + MIFARE_KEY_LENGTH];
params[0] = 0x01;
params[1] = key_number & 0xFF;
params[2] = block_number & 0xFF;
// params[3:3+keylen] = key
for (uint8_t i = 0; i < MIFARE_KEY_LENGTH; i++) {
params[3 + i] = key[i];
}
// params[3+keylen:] = uid
for (uint8_t i = 0; i < uid_length; i++) {
params[3 + MIFARE_KEY_LENGTH + i] = uid[i];
}
// Send InDataExchange request
PN532_CallFunction(pn532, PN532_COMMAND_INDATAEXCHANGE, response, sizeof(response),
params, 3 + MIFARE_KEY_LENGTH + uid_length, PN532_DEFAULT_TIMEOUT);
return response[0];
}
/**
* @brief: Read a block of data from the card. Block number should be the block
* to read.
* @param response: buffer of length 16 returned if the block is successfully read.
* @param block_number: specify a block to read.
* @retval: PN532 error code.
*/
int PN532_MifareClassicReadBlock(PN532* pn532, uint8_t* response, uint16_t block_number) {
uint8_t params[] = {0x01, MIFARE_CMD_READ, block_number & 0xFF};
uint8_t buff[MIFARE_BLOCK_LENGTH + 1];
// Send InDataExchange request to read block of MiFare data.
PN532_CallFunction(pn532, PN532_COMMAND_INDATAEXCHANGE, buff, sizeof(buff),
params, sizeof(params), PN532_DEFAULT_TIMEOUT);
// Check first response is 0x00 to show success.
if (buff[0] != PN532_ERROR_NONE) {
return buff[0];
}
for (uint8_t i = 0; i < MIFARE_BLOCK_LENGTH; i++) {
response[i] = buff[i + 1];
}
return buff[0];
}
/**
* @brief: Write a block of data to the card. Block number should be the block
* to write and data should be a byte array of length 16 with the data to
* write.
* @param data: data to write.
* @param block_number: specify a block to write.
* @retval: PN532 error code.
*/
int PN532_MifareClassicWriteBlock(PN532* pn532, uint8_t* data, uint16_t block_number) {
uint8_t params[MIFARE_BLOCK_LENGTH + 3];
uint8_t response[1];
params[0] = 0x01; // Max card numbers
params[1] = MIFARE_CMD_WRITE;
params[2] = block_number & 0xFF;
for (uint8_t i = 0; i < MIFARE_BLOCK_LENGTH; i++) {
params[3 + i] = data[i];
}
PN532_CallFunction(pn532, PN532_COMMAND_INDATAEXCHANGE, response,
sizeof(response), params, sizeof(params), PN532_DEFAULT_TIMEOUT);
return response[0];
}
/**
* @brief: Read a block of data from the card. Block number should be the block
* to read.
* @param response: buffer of length 4 returned if the block is successfully read.
* @param block_number: specify a block to read.
* @retval: PN532 error code.
*/
int PN532_Ntag2xxReadBlock(PN532* pn532, uint8_t* response, uint16_t block_number) {
uint8_t params[] = {0x01, MIFARE_CMD_READ, block_number & 0xFF};
// The response length of NTAG2xx is same as Mifare's
uint8_t buff[MIFARE_BLOCK_LENGTH + 1];
// Send InDataExchange request to read block of MiFare data.
PN532_CallFunction(pn532, PN532_COMMAND_INDATAEXCHANGE, buff, sizeof(buff),
params, sizeof(params), PN532_DEFAULT_TIMEOUT);
// Check first response is 0x00 to show success.
if (buff[0] != PN532_ERROR_NONE) {
return buff[0];
}
// Although the response length of NTAG2xx is same as Mifare's,
// only the first 4 bytes are available
for (uint8_t i = 0; i < NTAG2XX_BLOCK_LENGTH; i++) {
response[i] = buff[i + 1];
}
return buff[0];
}
/**
* @brief: Write a block of data to the card. Block number should be the block
* to write and data should be a byte array of length 4 with the data to
* write.
* @param data: data to write.
* @param block_number: specify a block to write.
* @retval: PN532 error code.
*/
int PN532_Ntag2xxWriteBlock(PN532* pn532, uint8_t* data, uint16_t block_number) {
uint8_t params[NTAG2XX_BLOCK_LENGTH + 3];
uint8_t response[1];
params[0] = 0x01; // Max card numbers
params[1] = MIFARE_ULTRALIGHT_CMD_WRITE;
params[2] = block_number & 0xFF;
for (uint8_t i = 0; i < NTAG2XX_BLOCK_LENGTH; i++) {
params[3 + i] = data[i];
}
PN532_CallFunction(pn532, PN532_COMMAND_INDATAEXCHANGE, response,
sizeof(response), params, sizeof(params), PN532_DEFAULT_TIMEOUT);
return response[0];
}
/**
* @brief: Read the GPIO states.
* @param pin_state: pin state buffer (3 bytes) returned.
* returns 3 bytes containing the pin state where:
* P3[0] = P30, P7[0] = 0, I[0] = I0,
* P3[1] = P31, P7[1] = P71, I[1] = I1,
* P3[2] = P32, P7[2] = P72, I[2] = 0,
* P3[3] = P33, P7[3] = 0, I[3] = 0,
* P3[4] = P34, P7[4] = 0, I[4] = 0,
* P3[5] = P35, P7[5] = 0, I[5] = 0,
* P3[6] = 0, P7[6] = 0, I[6] = 0,
* P3[7] = 0, P7[7] = 0, I[7] = 0,
* @retval: -1 if error
*/
int PN532_ReadGpio(PN532* pn532, uint8_t* pins_state) {
return PN532_CallFunction(pn532, PN532_COMMAND_READGPIO, pins_state, 3,
NULL, 0, PN532_DEFAULT_TIMEOUT);
}
/**
* @brief: Read the GPIO state of specified pins in (P30 ... P35).
* @param pin_number: specify the pin to read.
* @retval: true if HIGH, false if LOW
*/
bool PN532_ReadGpioP(PN532* pn532, uint8_t pin_number) {
uint8_t pins_state[3];
PN532_CallFunction(pn532, PN532_COMMAND_READGPIO, pins_state,
sizeof(pins_state), NULL, 0, PN532_DEFAULT_TIMEOUT);
if ((pin_number >= 30) && (pin_number <= 37)) {
return (pins_state[0] >> (pin_number - 30)) & 1 ? true : false;
}
if ((pin_number >= 70) && (pin_number <= 77)) {
return (pins_state[1] >> (pin_number - 70)) & 1 ? true : false;
}
return false;
}
/**
* @brief: Read the GPIO state of I0 or I1 pin.
* @param pin_number: specify the pin to read.
* @retval: true if HIGH, false if LOW
*/
bool PN532_ReadGpioI(PN532* pn532, uint8_t pin_number) {
uint8_t pins_state[3];
PN532_CallFunction(pn532, PN532_COMMAND_READGPIO, pins_state,
sizeof(pins_state), NULL, 0, PN532_DEFAULT_TIMEOUT);
if (pin_number <= 7) {
return (pins_state[2] >> pin_number) & 1 ? true : false;
}
return false;
}
/**
* @brief: Write the GPIO states.
* @param pins_state: pin state buffer (2 bytes) to write.
* no need to read pin states before write with the param pin_state
* P3 = pin_state[0], P7 = pin_state[1]
* bits:
* P3[0] = P30, P7[0] = 0,
* P3[1] = P31, P7[1] = P71,
* P3[2] = P32, P7[2] = P72,
* P3[3] = P33, P7[3] = nu,
* P3[4] = P34, P7[4] = nu,
* P3[5] = P35, P7[5] = nu,
* P3[6] = nu, P7[6] = nu,
* P3[7] = Val, P7[7] = Val,
* For each port that is validated (bit Val = 1), all the bits are applied
* simultaneously. It is not possible for example to modify the state of
* the port P32 without applying a value to the ports P30, P31, P33, P34
* and P35.
* @retval: -1 if error
*/
int PN532_WriteGpio(PN532* pn532, uint8_t* pins_state) {
uint8_t params[2];
// 0x80, the validation bit.
params[0] = 0x80 | pins_state[0];
params[1] = 0x80 | pins_state[1];
return PN532_CallFunction(pn532, PN532_COMMAND_WRITEGPIO, NULL, 0,
params, sizeof(params), PN532_DEFAULT_TIMEOUT);
}
/**
* @brief: Write the specified pin with given states.
* @param pin_number: specify the pin to write.
* @param pin_state: specify the pin state. true for HIGH, false for LOW.
* @retval: -1 if error
*/
int PN532_WriteGpioP(PN532* pn532, uint8_t pin_number, bool pin_state) {
uint8_t pins_state[2];
uint8_t params[2];
if (PN532_ReadGpio(pn532, pins_state) == PN532_STATUS_ERROR) {
return PN532_STATUS_ERROR;
}
if ((pin_number >= 30) && (pin_number <= 37)) {
if (pin_state) {
params[0] = 0x80 | pins_state[0] | 1 << (pin_number - 30);
} else {
params[0] = (0x80 | pins_state[0]) & ~(1 << (pin_number - 30));
}
params[1] = 0x00; // leave p7 unchanged
}
if ((pin_number >= 70) && (pin_number <= 77)) {
if (pin_state) {
params[1] = 0x80 | pins_state[1] | 1 << (pin_number - 70);
} else {
params[1] = (0x80 | pins_state[1]) & ~(1 << (pin_number - 70));
}
params[0] = 0x00; // leave p3 unchanged
}
return PN532_CallFunction(pn532, PN532_COMMAND_WRITEGPIO, NULL, 0,
params, sizeof(params), PN532_DEFAULT_TIMEOUT);
}
pn532.c就是对协议的封装,更换MCU仍然适用。
然后就是调用相关函数来解除门禁,笔者这里通过PN532寻卡,然后在EEPROM查询卡号,如果有卡牌呢ID,则通过打开门禁,这里通过继电器模拟门禁开关。
PN532_I2C_Init(&pn532);
PN532_GetFirmwareVersion(&pn532, buff);
if (PN532_GetFirmwareVersion(&pn532, buff) == PN532_STATUS_OK)
{
printf("Found PN532 with firmware version: %d.%d\r\n", buff[1], buff[2]);
}
PN532_SamConfiguration(&pn532);
while (1)
{
uid_len = PN532_ReadPassiveTarget(&pn532, uid, PN532_MIFARE_ISO14443A, 1000);
if (uid_len == PN532_STATUS_ERROR)
{
continue;
}
else
{
printf("Found card with UID: ");
if(Search_Card(uid))
{
for (uint8_t i = 0; i < uid_len; i++)
{
printf("%02x ", uid[i]);
}
printf("\r\nOpen the door");
Open_The_Door();
}
}
R_BSP_SoftwareDelay(200, BSP_DELAY_UNITS_MILLISECONDS);
}
窗帘子模块流程如下:
/* curtain task entry function */
/* pvParameters contains TaskHandle_t */
void curtain_task_entry(void * pvParameters)
{
FSP_PARAMETER_NOT_USED(pvParameters);
BaseType_t xReturn = pdPASS;
uint32_t r_queue; /* 定义一个接收消息的变量 */
/* 创建 Queue */
Moror_Queue = xQueueCreate((UBaseType_t ) QUEUE_LEN,/* 消息队列的长度 */
(UBaseType_t ) QUEUE_SIZE);/* 消息的大小 */
if (NULL != Moror_Queue)
{
printf("Create Moror Queue succeeded\r\n");
}
IRQ_Init();
/* TODO: add your own code here */
while(1)
{
QueueReceive( Moror_Queue, /* 消息队列的句柄 */
&r_queue, /* 发送的消息内容 */
portMAX_DELAY); /* 等待时间 一直等 */
if(MOTOR_FOREWARD == r_queue)
{
Motor_Foreward();
Motor_Stop();
}
else if(MOTOR_REVERSAL == r_queue)
{
Motor_Reversal();
Motor_Stop();
}
vTaskDelay(10);
}
}
HS3003读取流程如下:
HS3003接的是I2C0.
HS3003的温湿度读取很简单,下面是相关的函数。
R_IIC_MASTER_Open()函数为执行IIC初始化,开启配置如下所示。
/* Initialize the IIC module */
err =R_IIC_MASTER_Open(&g_i2c_master0_ctrl, &g_i2c_master0_cfg);
assert(FSP_SUCCESS == err);
R_IIC_MASTER_Write()函数是向IIC设备中写入数据,写入格式如下所示。
err = R_IIC_MASTER_Write(&g_i2c_device_ctrl_1, &g_i2c_tx_buffer[0], I2C_BUFFER_SIZE_BYTES, true);
assert(FSP_SUCCESS == err);
R_IIC_MASTER_Read()函数是向IIC设备中读数据,读数据的格式如下所示。
err = R_IIC_MASTER_Read(&g_i2c_device_ctrl_1, &g_i2c_rx_buffer[0], I2C_BUFFER_SIZE_BYTES, false);
assert(FSP_SUCCESS == err);
首先是HS3003的初始化,主要是打开I2C。
/**
* [url=home.php?mod=space&uid=2666770]@Brief[/url] HS3003 Init
* @param None
* @retval None
*/
void BSP_HS3003_Init(void)
{
fsp_err_t err;
err = R_IIC_MASTER_Open(&g_i2c_master0_ctrl, &g_i2c_master0_cfg);
assert(FSP_SUCCESS == err);
}
然后就是温湿度读取。
/**
* @brief HS3003 Get data
* @param HS3003_Data
* @retval fsp_err_t
*/
fsp_err_t BSP_HS3003_Get_data(ST_HS3003_Data *HS3003_Data)
{
fsp_err_t err;
uint8_t r_buf[4] = {0};
uint16_t humi, temp;
uint8_t data = 0x00;
err = R_IIC_MASTER_Write(&g_i2c_master0_ctrl, &data, 1, true);
R_BSP_SoftwareDelay(50, BSP_DELAY_UNITS_MILLISECONDS);
err = R_IIC_MASTER_Read(&g_i2c_master0_ctrl, r_buf,4 , false);
R_BSP_SoftwareDelay(50, BSP_DELAY_UNITS_MILLISECONDS);
err = R_IIC_MASTER_Write(&g_i2c_master0_ctrl, &data, 1, true);
R_BSP_SoftwareDelay(50, BSP_DELAY_UNITS_MILLISECONDS);
err = R_IIC_MASTER_Read(&g_i2c_master0_ctrl, r_buf,4 , false);
if(err == FSP_SUCCESS)
{
//printf("0x%X,0x%X,0x%X,0x%X\r\n", r_buf[0], r_buf[1], r_buf[2], r_buf[3]);
//printf("state:%x\n", r_buf[0] & RM_HS300X_MASK_STATUS_0XC0);
if ((r_buf[0] & RM_HS300X_MASK_STATUS_0XC0) != RM_HS300X_DATA_STATUS_VALID)
{
printf("The conversion time is short\r\n");
}
humi = (uint16_t)((r_buf[0] & RM_HS300X_MASK_HUMIDITY_UPPER_0X3F) << 8 | r_buf[1]);
temp = (uint16_t)(r_buf[2] << 8 | (r_buf[3] & RM_HS300X_MASK_TEMPERATURE_LOWER_0XFC)) >> 2;
HS3003_Data->humi = ((float)humi * RM_HS300X_CALC_HUMD_VALUE_100) / RM_HS300X_CALC_STATIC_VALUE;
HS3003_Data->temp = (((float)temp * RM_HS300X_CALC_TEMP_C_VALUE_165) / RM_HS300X_CALC_STATIC_VALUE) - RM_HS300X_CALC_TEMP_C_VALUE_40;
R_BSP_SoftwareDelay(100, BSP_DELAY_UNITS_MILLISECONDS);
}
return err;
}
温湿度子任务的核心代码如下:
/* hs3003 task entry function */
/* pvParameters contains TaskHandle_t */
void hs3003_task_entry(void * pvParameters)
{
fsp_err_t err;
char tempString[64] = {0};
FSP_PARAMETER_NOT_USED(pvParameters);
BSP_OLED_Init();
BSP_HS3003_Init();
BSP_OLED_CLS();
/* TODO: add your own code here */
while(1)
{
err = BSP_HS3003_Get_data(&HS3003_Data);
if(err == FSP_SUCCESS)
{
//HS3003_Data.humi = 50.23;
//HS3003_Data.temp = 21.25;
printf("Humidity: %.2f%%\r\n",HS3003_Data.humi);
printf("Temperature: %.2f°C\r\n", HS3003_Data.temp);
BSP_OLED_CLS();
sprintf((char *)tempString,"Humi: %.2f %%",HS3003_Data.humi);
BSP_OLED_ShowStr(2,3,(unsigned char*)tempString, 2);
sprintf((char *)tempString,"Temp: %.2f C",HS3003_Data.temp);
BSP_OLED_ShowStr(5,5,(unsigned char*)tempString, 2);
}
else
{
printf("Read hs3003 data error\r\n");
}
vTaskDelay(100);
}
}
人家交互主要以UART/BT来实现,当然也可使用功能WiFi来实现,BT/WIFI也通过UART透传,核心的通信都是UART的解析与传输,笔者在前面的帖子已经介绍过UART的使用,这里不在赘述了。
整体效果如下:
当检测NFC到门卡,PN532会读取门卡信息,然后在EEPROM查找是否由此卡片,如果有则打开门禁。
窗帘控制子模块很简单,当检测到光强太强的关闭窗帘。
当然BT也可直接控制窗帘的打开和关闭。
温湿度读取后,实时显示在OLED上,也会通过串口打印,当然BT也可获取温湿度信息。
当然也可使用串口查看打印信息。
当手机APP连接到BT后,BT的指示灯会来量,接下来就可以进行数据交互了。
感谢瑞萨电子和电子发烧友举办此次活动。
在开发过程,遇到了很多问题,大多在网上搜索答案,从而提高自己的解决问题的能力。
待完善项:
1.开发人机交互APP
2.增加ZigBee子系统,从而介入更多的设备
3.增加云服务功能
4.将光电传感器改为光强传感器,设置阈值来关闭或者打开传窗帘。
更多回帖