本帖最后由 萧风吹衣 于 2022-10-23 08:58 编辑
一、作品简介(100-200字)
本作品名称是物联网智能家居系统,主机、从机使用的是STM32芯片,两者之间通过CAN总线进行通信,保证数据传输的可靠性,从而提高整个系统的安全性。主机采集温湿度信息,上传到阿里云平台,通过Web查看相关数据。此外,K210负责进行人脸识别,并将结果通过串口发送到主机,主机再将数据通过CAN发送给从机,从机控制门禁继电器。
二、系统框图(图文结合)
(系统软硬件实现框图)
硬件:STM32F429I-DISC1(主机)
STM32F103C8T6(从机)
K210(人脸识别模块)
ESP8266(WIFI模块)
DHT11(温湿度采集模块)
SN65HVD230(CAN通信模块)
开发工具:Keil+STM32CubeMx
软件:软件框架基于FreeRTOS嵌入式实时操作系统,进行任务调度实现项目功能
任务1:主机闪烁LD3,指示系统运行状态
任务2:主机将K210人脸识别结果发送到CAN总线,从机接收信息,控制继电器
任务3:主机通过ESP8266上传温湿度信息到阿里云
任务4:主机通过DHT11采集温湿度信息
三、各部分功能说明(图文结合)
(各部分实现的功能说明及讲解,以图文结合的展示。)
- 温湿度采集模块(DHT11)
硬件接线如下图所示,5V电源、GND、DATA接到主机PD4引脚上进行数据传输
软件上的主要问题就是DHT11在进行数据传输时需要用到微秒级延时,具体做法就是使用HAL库建立一个微秒级延时函数
用定时器1是给FreeRTOS使用的,这里的用定时器2创建微秒级延时函数给DHT11用,具体配置如下
生成代码如下:
在任务4中调用DHT11_Read_Data读取温湿度数据
#include "dht11.h"
#include "tim.h"
unsigned char Data[5];
//复位DHT11
void DHT11_Rst(void)
{
DHT11_IO_OUT(); //设置为输出
DHT11_DQ_OUT=0; //拉低DQ
bsp_delay_us(18000); //拉低至少18ms
DHT11_DQ_OUT=1; //DQ=1
bsp_delay_us(30); //主机拉高20~40us 微秒级延时
}
//等待DHT11的回应
//返回1:未检测到DHT11的存在
//返回0:存在
u8 DHT11_Check(void)
{
u8 retry=0;
DHT11_IO_IN(); //设置为输出
while (DHT11_DQ_IN&&retry<100)//DHT11会拉低40~80us
{
retry++;
bsp_delay_us(1);
};
if(retry>=100)return 1;
else retry=0;
while (!DHT11_DQ_IN&&retry<100)//DHT11拉低后会再次拉高40~80us
{
retry++;
bsp_delay_us(1);
};
if(retry>=100)return 1;
return 0;
}
//从DHT11读取一个位
//返回值:1/0
u8 DHT11_Read_Bit(void)
{
u8 retry=0;
while(DHT11_DQ_IN&&retry<100)//等待变为低电平
{
retry++;
bsp_delay_us(1);
}
retry=0;
while(!DHT11_DQ_IN&&retry<100)//等待变高电平
{
retry++;
bsp_delay_us(1);
}
bsp_delay_us(40);//等待40us
if(DHT11_DQ_IN)return 1;
else return 0;
}
//从DHT11读取一个字节
//返回值:读到的数据
u8 DHT11_Read_Byte(void)
{
u8 i,dat;
dat=0;
for (i=0;i<8;i++)
{
dat<<=1;
dat|=DHT11_Read_Bit();
}
return dat;
}
//从DHT11读取一次数据
//temp:温度值(范围:0~50°)
//humi:湿度值(范围:20%~90%)
//返回值:0,正常;1,读取失败
u8 DHT11_Read_Data(u8 *temp1,u8 *temp2,u8 *humi1,u8 *humi2)
{
u8 buf[5];
u8 i;
DHT11_Rst();
if(DHT11_Check()==0)
{
for(i=0;i<5;i++)//读取40位数据
{
buf=DHT11_Read_Byte();
}
if((buf[0]+buf[1]+buf[2]+buf[3])==buf[4])
{
*humi1=buf[0];
*humi2=buf[1];
*temp1=buf[2];
*temp2=buf[3];
}
}else return 1;
return 0;
}
//初始化DHT11的IO口 DQ 同时检测DHT11的存在
//返回1:不存在
//返回0:存在
u8 DHT11_Init(void)
{
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_GPIOD_CLK_ENABLE(); //开启GPIOD时钟
GPIO_Initure.Pin=GPIO_PIN_4; //PD4
GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP; //推挽输出
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_HIGH; //高速
HAL_GPIO_Init(GPIOD,&GPIO_Initure); //初始化
DHT11_Rst();
return DHT11_Check();
}
- WIFI模块(ESP8266)+阿里云
这部分难点在于通过AT指令连接阿里云,以及上传数据的格式
ESP8266连接阿里云的顺序大致可以分为以下几个步骤
配置模块为STA模式➡连接热点➡连接TCP➡配置传输模式为透传模式➡验证用户名与密钥➡订阅主题➡发送心跳包➡接收/发送数据
(1)通过三元组数据连接到阿里云,测试过程如下
AT+CWMODE=1
AT+CIPSNTPCFG=1,8,"ntp1.aliyun.com"
AT+CWJAP="ChinaNet-B47A","42q7unu7"
AT+MQTTUSERCFG=0,1,”NULL”,”TestDevice01&hhpbfI5ahHV”,”6e01e2488d9facabdc11800ee750b2bcbfd99449”,0,0,””
AT+MQTTCLIENTID=0,”ESP8266|securemode=3\,signmethod=hmacsha1|”
AT+MQTTCONN=0,”hhpbfI5ahHV.iot-as-mqtt.cn-shanghai.aliyuncs.com”,1883,1
通过上述过程,就将设备连接到阿里云平台
(2)主机订阅,云端发布数据
AT+MQTTSUB=0,"/sys/hhpbfI5ahHV/TestDevice01/thing/service/property/set",1
(3)主机发布数据,云端订阅
AT+MQTTPUB=0,"/sys/hhpbfI5ahHV/TestDevice01/thing/event/property/post",“test”,1,0
AT+MQTTPUB=0,
"/sys/hhpbfI5ahHV/TestDevice01/thing/event/property/post",
"{
\"method\":\"thing.service.property.set\"\,
\"id\":\"2012934115\"\,
\"params\":{\"temperature\":36.5}\,
\"version\":\"1.0.0\"
}",
1,0
需要点击获取
需要点击获取
自动更新数据
通过以上介绍,可以实现数据上传到阿里云
- 人脸识别模块K210
这部分难点在于人脸识别的精度以及K210与主机通信
人脸识别原理及代码如下
import sensor
import image
import lcd
import KPU as kpu
import time
from Maix import FPIOA, GPIO
import gc
from fpioa_manager import fm
#from board import board_info
import utime
#import 相关库
task_fd = kpu.load(0x300000) #从flash 0 0x300000 加载人脸检测模型
task_ld = kpu.load(0x400000) #从flash 0 0x400000 加载人脸五点关键点检测模型
task_fe = kpu.load(0x500000) #从flash 0 0x500000 加载人脸196维特征值模型
clock = time.clock() #初始化系统时钟,计算帧率
fm.register(16, fm.fpioa.GPIOHS0) #设置 boot按键 的io
key_gpio = GPIO(GPIO.GPIOHS0, GPIO.IN)
start_processing = False
BOUNCE_PROTECTION = 50
def set_key_state(*_): #按键中断
global start_processing
start_processing = True
utime.sleep_ms(BOUNCE_PROTECTION)
key_gpio.irq(set_key_state, GPIO.IRQ_RISING, GPIO.WAKEUP_NOT_SUPPORT)
lcd.init() #初始化 lcd
sensor.reset()#初始化sensor摄像头
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.set_hmirror(1) #设置摄像头镜像
sensor.set_vflip(1) #设置摄像头翻转
sensor.run(1) #试能摄像头
anchor = (1.889, 2.5245, 2.9465, 3.94056, 3.99987, 5.3658, 5.155437,
6.92275, 6.718375, 9.01025) # 人脸检测锚
dst_point = [(44, 59), (84, 59), (64, 82), (47, 105),
(81, 105)] # 标准人脸关键点位置 标准正脸的5关键点坐标 分别为 左眼 右眼 鼻子 左嘴角 右嘴角
a = kpu.init_yolo2(task_fd, 0.5, 0.3, 5, anchor) #初始化人脸检测模型
img_lcd = image.Image() #设置显示buf
img_face = image.Image(size=(128, 128))#设置 128*128 人脸图片buf
a = img_face.pix_to_ai() #将图片转换为kpu接受的格式
record_ftr = [] #空列表 用于存储当前196维特征值
record_ftrs = [] #空列表 用于存储按键记录下人脸特征,可以将特征以txt等文件形式保存到sd卡后,读取到此列表,即可实现人脸断电存储。
names = ['Mr.1', 'Mr.2', 'Mr.3', 'Mr.4', 'Mr.5',
'Mr.6', 'Mr.7', 'Mr.8', 'Mr.9', 'Mr.10'] # # 人名标签,与上面列表特征值一一对应。
ACCURACY = 85 # 设定分数标准 人脸阈值
while (1):
img = sensor.snapshot()
clock.tick()
code = kpu.run_yolo2(task_fd, img) # 运行人脸检测模型,获取人脸坐标位置
if code: #如果检测到人脸
for i in code: #迭代坐标框
# Cut face and resize to 128x128
a = img.draw_rectangle(i.rect()) #在屏幕显示人脸方框
face_cut = img.cut(i.x(), i.y(), i.w(), i.h()) #裁剪人脸部分照片到 face_cut
face_cut_128 = face_cut.resize(128, 128) #将裁剪出的人脸照片缩放到 128*128 像素
a = face_cut_128.pix_to_ai() #将裁剪出的照片转换为kpu接受的格式
# a = img.draw_image(face_cut_128, (0,0))
# Landmark for face 5 points
fmap = kpu.forward(task_ld, face_cut_128) #运行人脸5点关键点模型
plist = fmap[:] #获取关键点预测结果
le = (i.x() + int(plist[0] * i.w() - 10), i.y() + int(plist[1] * i.h())) #计算左眼位置,这里在w方向-10 用来补偿模型转换带来的精度损失
re = (i.x() + int(plist[2] * i.w()), i.y() + int(plist[3] * i.h())) #计算右眼位置
nose = (i.x() + int(plist[4] * i.w()), i.y() + int(plist[5] * i.h())) #计算鼻子位置
lm = (i.x() + int(plist[6] * i.w()), i.y() + int(plist[7] * i.h())) #计算左嘴角位置
rm = (i.x() + int(plist[8] * i.w()), i.y() + int(plist[9] * i.h())) #计算右嘴角位置
#在相应位置处画小圈圈
a = img.draw_circle(le[0], le[1], 4)
a = img.draw_circle(re[0], re[1], 4)
a = img.draw_circle(nose[0], nose[1], 4)
a = img.draw_circle(lm[0], lm[1], 4)
a = img.draw_circle(rm[0], rm[1], 4)
#在相应位置处画小圈圈
# align face to standard position 将面对齐到标准位置
src_point = [le, re, nose, lm, rm] #图片中五点坐标的位置
T = image.get_affine_transform(src_point, dst_point) #根据获得的5点坐标和标准正脸坐标获取仿射变换矩阵
a = image.warp_affine_ai(img, img_face, T) #对原始图片人脸图片进行仿射变换,变换为正脸图像
a = img_face.ai_to_pix() #将图片转换为kpu接受的格式
# a = img.draw_image(img_face, (128,0))
del (face_cut_128) #释放裁剪人脸部分照片
# calculate face feature vector 计算人脸特征向量
fmap = kpu.forward(task_fe, img_face) #计算正脸图片的196维特征值
feature = kpu.face_encode(fmap[:]) #获得计算结果
reg_flag = False
scores = [] #存储特征比对分数
for j in range(len(record_ftrs)): #迭代已存特征值
score = kpu.face_compare(record_ftrs[j], feature) #计算当前人脸特征值与已存特征值的分数
scores.append(score) #添加分数总表
max_score = 0
index = 0
for k in range(len(scores)): #迭代所有比对分数,找到最大分数和索引值
if max_score < scores[k]:
max_score = scores[k]
index = k
if max_score > ACCURACY: #如果最大分数大于85, 可以被认定为同一个人
a = img.draw_string(i.x(), i.y(), ("%s :%2.1f" % (
names[index], max_score)), color=(0, 255, 0), scale=2) # 显示人名 与 分数
else:
a = img.draw_string(i.x(), i.y(), ("X :%2.1f" % (
max_score)), color=(255, 0, 0), scale=2) #显示未知 与 分数
if start_processing:
record_ftr = feature
record_ftrs.append(record_ftr)
start_processing = False
break
fps = clock.fps() #计算帧率
print("%2.1f fps" % fps) #打印帧率
a = lcd.display(img) #刷屏显示
gc.collect()
# kpu.memtest()
# a = kpu.deinit(task_fe)
# a = kpu.deinit(task_ld)
# a = kpu.deinit(task_fd)
识别前:
识别后:
主机通信代码
if(huart->Instance == USART3) // 判断是由哪个串口触发的中断 USART3 K210
{
printf("传输人脸识别结果!\r\n");
//将接收到的数据放入接收usart3接收数组
usart3_rxbuf[usart3_rxcounter] = usart3_rxone[0];
usart3_rxcounter++; //接收数量+1
printf("%d \r\n",usart3_rxone[0]);
if(usart3_rxone[0]==49)
{
printf("人脸识别成功!\r\n");
//通过CAN总线发送信息,打开继电器
send_data[0]=1;
}
else if(usart3_rxone[0]==50)
{
printf("人脸识别失败!\r\n");
send_data[0]=0;
}
//重新使能串口5接收中断
HAL_UART_Receive_IT(&huart3,usart3_rxone,1);
}
- CAN总线数据传输
在CubeMX设置PB12 CAN2_RX
在CubeMX设置PB13 CAN2_TX
HAL库关于CAN的所有函数定义和结构体定义分别在 Drivers/STM32F4xx_HAL_Driver 文件夹下 stm32f4xx_hal_can.c 和 stm32f4xx_hal_spi.h 中
常用函数
HAL_StatusTypeDef HAL_CAN_Start(CAN_HandleTypeDef *hcan)**
开启CAN,一般在主函数中调用
HAL_StatusTypeDef HAL_CAN_ConfigFilter(CAN_HandleTypeDef *hcan, CAN_FilterTypeDef *sFilterConfig)
配置CAN过滤器
HAL_StatusTypeDef HAL_CAN_ActivateNotification(CAN_HandleTypeDef *hcan, uint32_t ActiveITs)
使能中断
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
接收回调函数,CAN回调后进行的操作。一般在此接收数据
HAL_StatusTypeDef HAL_CAN_AddTxMessage(CAN_HandleTypeDef *hcan, CAN_TxHeaderTypeDef *pHeader, uint8_t aData[], uint32_t *pTxMailbox)**
增加一个消息到第一个空闲的 Tx 邮箱,并且激活对应的传输请求
HAL_StatusTypeDef HAL_CAN_GetRxMessage(CAN_HandleTypeDef *hcan, uint32_t RxFifo, CAN_RxHeaderTypeDef *pHeader, uint8_t aData[])
从Rx FIFO收取一个 CAN 帧
HAL_StatusTypeDef HAL_CAN_ConfigFilter(CAN_HandleTypeDef *hcan, CAN_FilterTypeDef *sFilterConfig)
设置接收过滤器
CAN收发思路
定义接收过滤器类型定义结构体(CAN_FilterTypeDef)变量
配置CAN接收过滤器HAL_CAN_ConfigFilter
开启CAN,HAL_CAN_Start
发送,HAL_CAN_AddTxMessage
接收,需要先开启中断HAL_CAN_ActivateNotification,第二个参数设置为CAN_IT_RX_FIFO0_MSG_PENDING
接收中断回调函数HAL_CAN_RxFifo0MsgPendingCallback,里面定义接收报文定义结构体(CAN_RxHeaderTypeDef)结构体变量。用接收函数HAL_CAN_GetRxMessage接收
根据上述介绍,主机发送数据到CAN总线上,从机接收数据,控制继电器的开合。
四、作品源码
五、作品功能演示视频
|