1 引言
智能家居助手以R7FA4M2AD3CFP为主控器,基于FreeRTOS开发,该系统主要用于监控室内环境信息,自动/远程控制室内设备,还有门禁功能,将门卡信息保存到手机,从此告别钥匙。
2 功能描述
智能家居助手的核心功能如下:
- 实时监控室内环境信息,自动关窗帘,监控室内温湿度,基于NFC的门禁
- 实时检测室内温湿度,并将温湿度显示在OLED上,也可通过BT/WIFI传至云端
- 环境信息可通过WIFI上传至云端,也可通过BT传到手机APP
3 方案设计
3.1 系统硬件方案
智能家居助手以R7FA4M2AD3CFP主控芯片组为核心,由温湿度模块、光感模块、OLED显示模块、PN632、电机等模块构成。
3.3.1 门禁子系统
门禁子系统由AT24C02和PN532构成,由于引脚比较多,这里使用一个RA4M2作为从设备来,通过485总线与主控通信。
这里选用PN532为NFC的读卡器,通信接口可选UART,SPI,I2C,笔者使用I2C作为通信接口,PN532的模块如下图所示。
PN532的硬件电路图如下。
根据官方手册,我们了解到PN532可以使用UART,SPI,I2C与主控MCU进行通信,具体通信方式根据P16和P17引脚来决定。
值得注意的是,硬件通信接口只指定通信介质和时序,跟软件通信协议无关,无论何种通信接口,其传输数据时收包和发包的内容都是一致的,由软件层面的通信协议来决定。具体使用时不同的通信接口单双工模式稍有差别,SPI和UART在硬件层面是双工的,I2C则是单工的,软件层面的协议要仔细看手册,PN532所有通信行为都是单工的。
3.1.2 窗帘控制子模块
窗帘控制子模块由光敏电阻传感器和电机构成。
光敏电阻器一般用于光的测量、光的控制和光电转换(将光的变化转换为电的变化)。光敏电阻传感器模块电路如下图所示:
DO:数字开关量输出(0 和 1),DO 输出端可以与单片机直接相连,通过单片机来检测高低电平,由此来检测环境的光线亮度改变
AO:模拟信号输出,最简单的模拟输出的电路如下:
光敏电阻传感器模块一般有有三线和四线的接法,四线相比三线多了AO。AO的使用相对比较复杂,需要用到ADC,本文将讲解如何使用AO来控制LED。DO就不讲了,就一个检测高低电平,比较简单。
本文使用的光敏电阻模块如下所示:
光敏电阻模块接线图如下:
当然为了方便,这里使用数字开关量作为窗帘控制的中断信号。
3.1.3 温湿度子模块
温湿度子模块由HS3003和OLED构成。
HS3003是一款高精度的温湿度传感器,标准I2C格式。
温度湿度读取时序如下:
Figure ‑ HS3003读取时序
HS3003在睡眠模式下,传感器一直等待主机发送测量命令。CPU此时只执行温湿度测量指令,其余指令无法唤醒传感器进行数据采集。
主机发送采集命令后即可唤醒HS3003进行数据采集,启动采集只需要发送7位从机地址,第八位写0即可。
采集数据时,传感器内部的数字信号处理器会对采集到的温湿度数据进行计算,并且内部算法对数据进行校正运算:采集结束后,传感器的输出寄存器会将数据进行更新,
测量周期由湿度和温度转换后的数字信号处理器(DSP)校正计算。在测量周期结束时,数字电源关闭之前输出寄存器将被更新。测量数据以14位的输出,数据位以右对齐。
HS3003输出的真实的相对湿度(%)和温度(℃)数据通过以下公式进行计算。
相对湿度:
温度转换:
HS3003模块相关电路如下图所示:
根据可知HS300x Datasheet,HS3003的地址为0x44。
HS3003接的RA4M2的I2C0,SCL和SDA分别接的是P400和P401引脚。
3.2 软件设计
智能家居助手的软件基于FreeRTOS实现,其软件架构如下:
整个软件架构分为四层: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部分包含系统任务和用户自定义任务,用户自定义任务包含通信、调试、控制、算法等模块。
整个软件的运行如下:
(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开发,整个工程结构如下。
4.1 通信协议实现
下面对通信协议的具体定义:
#define SF_PACKAGE_LENGTH 34
#define SF_HEAD 0x3A
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
#pragma pack(1)
typedef struct
{
uint8_t Head;
uint8_t Status;
uint8_t FunCode;
uint8_t Addr;
union dataType_un //data type
{
uint16_t DataType16;
uint8_t DataType8[2];
}UN_DataType;
uint8_t DataLen;
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;
}ST_Data_Package;
在通信过程中,发送很简单,直接发送整个buff即可,接收一般采用中断,然后根据协议的内容一步步解析。
4.2 门禁子系统实现
门禁子系统流程如下:
门禁子系统的核心就是对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)
{
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)
{
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;
pn532->wakeup();
}
pn532hw.c实现了PN532与RA4M2的通信。
【pn532.c】
#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
int PN532_WriteFrame(PN532* pn532, uint8_t* data, uint16_t length) {
if (length > PN532_FRAME_MAX_LENGTH || length < 1) {
return PN532_STATUS_ERROR;
}
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;
}
int PN532_ReadFrame(PN532* pn532, uint8_t* response, uint16_t length) {
uint8_t buff[PN532_FRAME_MAX_LENGTH + 7];
uint8_t checksum = 0;
pn532->read_data(buff, length + 7);
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;
}
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;
}
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;
}
for (uint8_t i = 0; i < frame_len; i++) {
response[i] = buff[offset + 2 + i];
}
return frame_len;
}
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
) {
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];
}
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;
}
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;
}
int frame_len = PN532_ReadFrame(pn532, buff, response_length + 2);
if (! ((buff[0] == PN532_PN532TOHOST) && (buff[1] == (command+1)))) {
pn532->log("Received unexpected command response!");
return PN532_STATUS_ERROR;
}
for (uint8_t i = 0; i < response_length; i++) {
response[i] = buff[i + 2];
}
return frame_len - 2;
}
int PN532_GetFirmwareVersion(PN532* pn532, uint8_t* version) {
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;
}
int PN532_SamConfiguration(PN532* pn532) {
uint8_t params[] = {0x01, 0x14, 0x01};
PN532_CallFunction(pn532, PN532_COMMAND_SAMCONFIGURATION,
NULL, 0, params, sizeof(params), PN532_DEFAULT_TIMEOUT);
return PN532_STATUS_OK;
}
int PN532_ReadPassiveTarget(
PN532* pn532,
uint8_t* response,
uint8_t card_baud,
uint32_t timeout
) {
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;
}
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];
}
int PN532_MifareClassicAuthenticateBlock(
PN532* pn532,
uint8_t* uid,
uint8_t uid_length,
uint16_t block_number,
uint16_t key_number,
uint8_t* key
) {
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;
for (uint8_t i = 0; i < MIFARE_KEY_LENGTH; i++) {
params[3 + i] = key[i];
}
for (uint8_t i = 0; i < uid_length; i++) {
params[3 + MIFARE_KEY_LENGTH + i] = uid[i];
}
PN532_CallFunction(pn532, PN532_COMMAND_INDATAEXCHANGE, response, sizeof(response),
params, 3 + MIFARE_KEY_LENGTH + uid_length, PN532_DEFAULT_TIMEOUT);
return response[0];
}
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];
PN532_CallFunction(pn532, PN532_COMMAND_INDATAEXCHANGE, buff, sizeof(buff),
params, sizeof(params), PN532_DEFAULT_TIMEOUT);
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];
}
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;
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];
}
int PN532_Ntag2xxReadBlock(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];
PN532_CallFunction(pn532, PN532_COMMAND_INDATAEXCHANGE, buff, sizeof(buff),
params, sizeof(params), PN532_DEFAULT_TIMEOUT);
if (buff[0] != PN532_ERROR_NONE) {
return buff[0];
}
for (uint8_t i = 0; i < NTAG2XX_BLOCK_LENGTH; i++) {
response[i] = buff[i + 1];
}
return buff[0];
}
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;
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];
}
int PN532_ReadGpio(PN532* pn532, uint8_t* pins_state) {
return PN532_CallFunction(pn532, PN532_COMMAND_READGPIO, pins_state, 3,
NULL, 0, PN532_DEFAULT_TIMEOUT);
}
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;
}
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;
}
int PN532_WriteGpio(PN532* pn532, uint8_t* pins_state) {
uint8_t params[2];
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);
}
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;
}
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;
}
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 窗帘控制子模块实现
窗帘子模块流程如下:
void curtain_task_entry(void * pvParameters)
{
FSP_PARAMETER_NOT_USED(pvParameters);
BaseType_t xReturn = pdPASS;
uint32_t r_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();
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读取流程如下:
HS3003接的是I2C0.
HS3003的温湿度读取很简单,下面是相关的函数。
R_IIC_MASTER_Open()函数为执行IIC初始化,开启配置如下所示。
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。
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);
}
然后就是温湿度读取。
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)
{
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;
}
温湿度子任务的核心代码如下:
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();
while(1)
{
err = BSP_HS3003_Get_data(&HS3003_Data);
if(err == FSP_SUCCESS)
{
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 功能演示
整体效果如下:
5.1 门控子系统
当检测NFC到门卡,PN532会读取门卡信息,然后在EEPROM查找是否由此卡片,如果有则打开门禁。
5.2 窗帘控制子模块
窗帘控制子模块很简单,当检测到光强太强的关闭窗帘。
当然BT也可直接控制窗帘的打开和关闭。
5.3 温湿度子模块
温湿度读取后,实时显示在OLED上,也会通过串口打印,当然BT也可获取温湿度信息。
当然也可使用串口查看打印信息。
5.4 人机交互
当手机APP连接到BT后,BT的指示灯会来量,接下来就可以进行数据交互了。
6 总结
感谢瑞萨电子和电子发烧友举办此次活动。
在开发过程,遇到了很多问题,大多在网上搜索答案,从而提高自己的解决问题的能力。
待完善项:
1.开发人机交互APP
2.增加ZigBee子系统,从而介入更多的设备
3.增加云服务功能
4.将光电传感器改为光强传感器,设置阈值来关闭或者打开传窗帘。