瑞萨单片机论坛
直播中

ouxiaolong

11年用户 470经验值
擅长:嵌入式技术 光电显示
私信 关注
[经验]

【RA4M2设计挑战赛】智能家居助手详细设计与实现

1 引言

智能家居助手以R7FA4M2AD3CFP为主控器,基于FreeRTOS开发,该系统主要用于监控室内环境信息,自动/远程控制室内设备,还有门禁功能,将门卡信息保存到手机,从此告别钥匙。

2 功能描述

智能家居助手的核心功能如下:

  • 实时监控室内环境信息,自动关窗帘,监控室内温湿度,基于NFC的门禁
  • 实时检测室内温湿度,并将温湿度显示在OLED上,也可通过BT/WIFI传至云端
  • 环境信息可通过WIFI上传至云端,也可通过BT传到手机APP

3 方案设计

3.1 系统硬件方案

智能家居助手以R7FA4M2AD3CFP主控芯片组为核心,由温湿度模块、光感模块、OLED显示模块、PN632、电机等模块构成。

1.png

3.3.1 门禁子系统

门禁子系统由AT24C02和PN532构成,由于引脚比较多,这里使用一个RA4M2作为从设备来,通过485总线与主控通信。

这里选用PN532为NFC的读卡器,通信接口可选UART,SPI,I2C,笔者使用I2C作为通信接口,PN532的模块如下图所示。

https://files.hexcode.cn/1555f1be-f14d-4825-a1c9-dc12cc0c4bbc.png

PN532的硬件电路图如下。

1677306588356icmw6ogq25

根据官方手册,我们了解到PN532可以使用UART,SPI,I2C与主控MCU进行通信,具体通信方式根据P16和P17引脚来决定。

值得注意的是,硬件通信接口只指定通信介质和时序,跟软件通信协议无关,无论何种通信接口,其传输数据时收包和发包的内容都是一致的,由软件层面的通信协议来决定。具体使用时不同的通信接口单双工模式稍有差别,SPI和UART在硬件层面是双工的,I2C则是单工的,软件层面的协议要仔细看手册,PN532所有通信行为都是单工的。

3.1.2 窗帘控制子模块

窗帘控制子模块由光敏电阻传感器和电机构成。

光敏电阻器一般用于光的测量、光的控制和光电转换(将光的变化转换为电的变化)。光敏电阻传感器模块电路如下图所示:

1677306588789ht1vq0qlyo

DO:数字开关量输出(0 和 1),DO 输出端可以与单片机直接相连,通过单片机来检测高低电平,由此来检测环境的光线亮度改变

AO:模拟信号输出,最简单的模拟输出的电路如下:

C:\Users\BruceOu\Desktop\QQ截图20210626155335.png

光敏电阻传感器模块一般有有三线和四线的接法,四线相比三线多了AO。AO的使用相对比较复杂,需要用到ADC,本文将讲解如何使用AO来控制LED。DO就不讲了,就一个检测高低电平,比较简单。

本文使用的光敏电阻模块如下所示:

1677306589424tls0l91xby

光敏电阻模块接线图如下:

1677306590040vbo3caurcg

当然为了方便,这里使用数字开关量作为窗帘控制的中断信号。

3.1.3 温湿度子模块

温湿度子模块由HS3003和OLED构成。

HS3003是一款高精度的温湿度传感器,标准I2C格式。

温度湿度读取时序如下:

167730659047150fvvz57o3

Figure ‑ HS3003读取时序

HS3003在睡眠模式下,传感器一直等待主机发送测量命令。CPU此时只执行温湿度测量指令,其余指令无法唤醒传感器进行数据采集。

主机发送采集命令后即可唤醒HS3003进行数据采集,启动采集只需要发送7位从机地址,第八位写0即可。

采集数据时,传感器内部的数字信号处理器会对采集到的温湿度数据进行计算,并且内部算法对数据进行校正运算:采集结束后,传感器的输出寄存器会将数据进行更新,

测量周期由湿度和温度转换后的数字信号处理器(DSP)校正计算。在测量周期结束时,数字电源关闭之前输出寄存器将被更新。测量数据以14位的输出,数据位以右对齐

HS3003输出的真实的相对湿度(%)和温度(℃)数据通过以下公式进行计算。

相对湿度:

16773065908126iy6jotdo5

温度转换:

1677306591115ntfcl1cxew

HS3003模块相关电路如下图所示:

167730659143032kewvs8vr

根据可知HS300x Datasheet,HS3003的地址为0x44。

HS3003接的RA4M2的I2C0,SCL和SDA分别接的是P400和P401引脚。

3.2 软件设计

智能家居助手的软件基于FreeRTOS实现,其软件架构如下:

2.png

整个软件架构分为四层:Hardware、Driver、FreeRTOS Kernel、Application。

Hardware层:基础为外设有UART、I2C、GPIO,UART用于调试,I2C用于与外设通信。

Driver层:主要为上层应用提供驱动接口。

FreeRTOS Kernel层:FreeRTOS内容比较多,除了基础内核外,还包含了丰富的组件和第三方库,主

要包含以下组成部分:

  • 基础内核:包括不可裁剪的最小内核和可裁剪模块。最小内核包含任务管理、内存管理、中断管理、异常管理和系统时钟。可裁剪模块包括队列、信号量、互斥量、软件定时器、任务通知、事件组。
  • 内核增强:在内核基础功能之上,进一步提供增强功能,包括低功耗模式、cu的使用率、Trce事件跟踪、TCP/IP、CLI、POSIX等。
  • 协议栈:提供的一系列独立于FreeRTOS内的库,只和标准C库相关。比如MQTT、JSON、HTTP等。
  • 第三方组件:一般和具体应用场景相关的组件或者第三方提供的组件,比如GUI、为AWS IOT工特定的增值系服务等。

Application层:Application部分包含系统任务和用户自定义任务,用户自定义任务包含通信、调试、控制、算法等模块。

整个软件的运行如下:

3.png

(1)硬件初始化

主要初始化按键、OLED、温湿度等资源。

(2)OS初始化

该阶段主要进行OS任务启动

(3)任务运行阶段

  • FreeRTOS和中断会配合运行各个任务,主要操作如下:+
  • 中断会由用户或FreeRTOS内核触发,比如event/signal。
  • 任务间信号量、互斥量等完成某项需求相互间需要同步等需求。:
  • 任务需要与硬件外设进行打交道,或入或出。

3.3 通信协议

为了实现数据在模块之间的传递,方便参数和硬件资源标准化,从而可以采用相同的方法来访问和控制模块内部的资源,线面是本系统遵循的通信协议。

Table ‑ 通信协议

1.帧头

帧头为通信数据的开头,固定1个字节,为3A。

2.状态

状态为通信过程中接收端返回的操作信息。

3.功能码

这个选项对命令进一步补充。这里暂时只有读、写操作。

4.设备地址

设备地址通常是用于多种设备之间,为了方便区分不同的同类型设备。

5.数据类型

数据类型用于区分不同的设备操作。

6.数据长度

数据长度这个选项,可能有的协议会把该选项提到前面设备地址位置,把命令这些信息算在“长度”里面。

7.数据

数据就是传输的实实在在的数据,比如温度:25℃。

8.校验码

校验码是为CRC16。

9.帧尾

帧尾为通信数据的结尾,固定1个字节,为3F。

4 智能家居助手实现

智能家居助手基于FreeRTOS开发,整个工程结构如下。

1677306592525a7spr7ddg1

4.1 通信协议实现

下面对通信协议的具体定义:

/* 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即可,接收一般采用中断,然后根据协议的内容一步步解析。

4.2 门禁子系统实现

门禁子系统流程如下:

4.png

门禁子系统的核心就是对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);
}

4.3 窗帘控制子模块实现

窗帘子模块流程如下:

5.png

/* 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);
    }
}

4.4 温湿度子模块实现

HS3003读取流程如下:

167730659341076j6er9t5z

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);
    }
}

4.5 人机交互实现

人家交互主要以UART/BT来实现,当然也可使用功能WiFi来实现,BT/WIFI也通过UART透传,核心的通信都是UART的解析与传输,笔者在前面的帖子已经介绍过UART的使用,这里不在赘述了。

5 功能演示

整体效果如下:

C:\Users\BruceOu\Documents\Tencent Files\270139773\FileRecv\MobileFile\1677291328383.jpg

5.1 门控子系统

当检测NFC到门卡,PN532会读取门卡信息,然后在EEPROM查找是否由此卡片,如果有则打开门禁。

C:\Users\BruceOu\Documents\Tencent Files\270139773\FileRecv\MobileFile\1677289868397.jpg

5.2 窗帘控制子模块

窗帘控制子模块很简单,当检测到光强太强的关闭窗帘。

C:\Users\BruceOu\Documents\Tencent Files\270139773\FileRecv\MobileFile\1677289930161.jpg

当然BT也可直接控制窗帘的打开和关闭。

5.3 温湿度子模块

温湿度读取后,实时显示在OLED上,也会通过串口打印,当然BT也可获取温湿度信息。

C:\Users\BruceOu\Documents\Tencent Files\270139773\FileRecv\MobileFile\1677290373329.jpg

当然也可使用串口查看打印信息。

1677306596321cjc7vk5jnm

5.4 人机交互

当手机APP连接到BT后,BT的指示灯会来量,接下来就可以进行数据交互了。

C:\Users\BruceOu\Documents\Tencent Files\270139773\FileRecv\MobileFile\1677289817515.jpg

1677306597346ztgtp0g82s

6 总结

感谢瑞萨电子和电子发烧友举办此次活动。

在开发过程,遇到了很多问题,大多在网上搜索答案,从而提高自己的解决问题的能力。

待完善项:

1.开发人机交互APP

2.增加ZigBee子系统,从而介入更多的设备

3.增加云服务功能

4.将光电传感器改为光强传感器,设置阈值来关闭或者打开传窗帘。

更多回帖

发帖
×
20
完善资料,
赚取积分