#include <dht.h>
#include <MD_MAX72xx.h>
#include <Keypad.h>
#include "pitches.h"
//限制设置部分
int displayMode = 0;//当前展示模式,0是温度,1是湿度
bool modifying = false;//是否正在修改
int maxOrMin = 0;//当前在修改最大值还是最小值
float max_temp, min_temp;//设置的温度上下限
float max_humi, min_humi;//设置的湿度上下限
float max_temp_t, min_temp_t;//输入的温度上下限
float max_humi_t, min_humi_t;//输入的湿度上下限
bool max_temp_minus, max_temp_t_minus;//两个温度最大值是否为负值
bool min_temp_minus, min_temp_t_minus;//两个温度最小值是否是负值
//LED灯标识显示状态(监测模式、修改模式)
#define DETECTION_PIN 4
#define SETTING_PIN 5
int currState = 0; // 当前状态(0是检测模式,1是修改模式)
// 温湿度检测器设置
#define DHT22_PIN 7// 检测器的接口
float temp, humi;//温度和湿度
bool temp_minus = false;//温度是否是负值
dht DHT;//DHT检测器对象
// 报警器设置
#define ALARM_PIN 6// 蜂鸣器对应的接口
bool alarming = false;//当前是否在报警
// 显示板设置(这个板不是LCD,是点阵显示器。LCD另有设备,不要写错)
#define CS 10//chip select接口
#define DIN 11//输入接口
#define CLK 13//时刻接口
#define MAX_DEVICE 9//最多设备数量(共九块8*8的点阵显示其)
const int numHeight = 6;
const int numWidth = 3;
const int baseline = 1;
const int interval = 4;
MD_MAX72XX mx = MD_MAX72XX(MD_MAX72XX::PAROLA_HW, CS, MAX_DEVICE);
//键盘设置
const uint8_t ROWS = 4;//键盘有几行
const uint8_t COLS = 4;//键盘有几列
char keys[ROWS][COLS] = {//键盘对应字符
{ '1', '2', '3', 'A' },
{ '4', '5', '6', 'T' },
{ '7', '8', '9', 'M' },
{ '-', '0', 'C', 'S' }
};
uint8_t colPins[COLS] = { A3, A2, A1, A0 }; // Pins connected to C1, C2, C3, C4
uint8_t rowPins[ROWS] = { 3, 2, A5, A4 }; // Pins connected to R1, R2, R3, R4
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
struct {
uint32_t total;
uint32_t ok;
uint32_t crc_error;
uint32_t time_out;
uint32_t connect;
uint32_t ack_l;
uint32_t ack_h;
uint32_t unknown;
} stat = { 0, 0, 0, 0, 0, 0, 0, 0};//DHT的状态结构,在现在的程序里没有用处
void display_number(int num, int count) { //输入数字,显示在第count个位置上(一共0~9 10个位置)。
int anchor = baseline + count * (numWidth + interval);// 以(anchor, baseline)为左下角开始显示
switch (num) {//输出对应数字
case 0:
for (int i = 0 ; i <= numHeight; i++) {
mx.setPoint(baseline + i, anchor, true); //(y,x)格式,与坐标相反
mx.setPoint(baseline + i, anchor + numWidth, true);
}
for (int i = 0 ; i <= numWidth; i++) {
mx.setPoint(baseline, anchor + i, true);
mx.setPoint(baseline + numHeight, anchor + i, true); //(y,x)格式,与坐标相反
}
break;
case 1:
for (int i = 0 ; i <= numHeight; i++) {
mx.setPoint(baseline + i, anchor + numWidth - 1, true);
}
mx.setPoint(baseline, anchor + numWidth - 2, true);
mx.setPoint(baseline + numHeight - 1, anchor + numWidth - 2, true);
break;
case 2:
for (int i = 0 ; i <= numHeight / 2; i++) {
mx.setPoint(baseline + i, anchor, true); //(y,x)格式,与坐标相反
}
for (int i = numHeight / 2 ; i <= numHeight; i++) {
mx.setPoint(baseline + i, anchor + numWidth, true);
}
for (int i = 0 ; i <= numWidth; i++) {
mx.setPoint(baseline, anchor + i, true);
mx.setPoint(baseline + numHeight / 2, anchor + i, true);
mx.setPoint(baseline + numHeight, anchor + i, true); //(y,x)格式,与坐标相反
}
break;
case 3:
for (int i = 0; i <= numHeight; i++) {
mx.setPoint(baseline + i, anchor + numWidth, true);
}
for (int i = 0 ; i <= numWidth; i++) {
mx.setPoint(baseline, anchor + i, true);
mx.setPoint(baseline + numHeight / 2, anchor + i, true);
mx.setPoint(baseline + numHeight, anchor + i, true); //(y,x)格式,与坐标相反
}
break;
case 4:
for (int i = 0; i <= numHeight; i++) {
mx.setPoint(baseline + i, anchor + numWidth, true);
}
for (int i = numHeight / 2 ; i <= numHeight; i++) {
mx.setPoint(baseline + i, anchor, true);
}
for (int i = 0 ; i <= numWidth; i++) {
mx.setPoint(baseline + numHeight / 2, anchor + i, true);
}
break;
case 5:
for (int i = 0 ; i <= numHeight / 2; i++) {
mx.setPoint(baseline + i, anchor + numWidth, true); //(y,x)格式,与坐标相反
}
for (int i = numHeight / 2 ; i <= numHeight; i++) {
mx.setPoint(baseline + i, anchor, true);
}
for (int i = 0 ; i <= numWidth; i++) {
mx.setPoint(baseline, anchor + i, true);
mx.setPoint(baseline + numHeight / 2, anchor + i, true);
mx.setPoint(baseline + numHeight, anchor + i, true); //(y,x)格式,与坐标相反
}
break;
case 6:
for (int i = 0 ; i <= numHeight / 2; i++) {
mx.setPoint(baseline + i, anchor + numWidth, true);
}
for (int i = 0 ; i <= numHeight; i++) {
mx.setPoint(baseline + i, anchor, true); //(y,x)格式,与坐标相反
}
for (int i = 0 ; i <= numWidth; i++) {
mx.setPoint(baseline, anchor + i, true);
mx.setPoint(baseline + numHeight / 2, anchor + i, true);
mx.setPoint(baseline + numHeight, anchor + i, true); //(y,x)格式,与坐标相反
}
break;
case 7:
for (int i = 0 ; i <= numHeight; i++) {
mx.setPoint(baseline + i, anchor + numWidth, true);
}
for (int i = 0 ; i <= numWidth; i++) {
mx.setPoint(baseline + numHeight, anchor + i, true); //(y,x)格式,与坐标相反
}
break;
case 8:
for (int i = 0 ; i <= numHeight; i++) {
mx.setPoint(baseline + i, anchor, true); //(y,x)格式,与坐标相反
}
for (int i = 0 ; i <= numHeight; i++) {
mx.setPoint(baseline + i, anchor + numWidth, true);
}
for (int i = 0 ; i <= numWidth; i++) {
mx.setPoint(baseline, anchor + i, true);
mx.setPoint(baseline + numHeight / 2, anchor + i, true);
mx.setPoint(baseline + numHeight, anchor + i, true); //(y,x)格式,与坐标相反
}
break;
case 9:
for (int i = numHeight / 2 ; i <= numHeight; i++) {
mx.setPoint(baseline + i, anchor, true); //(y,x)格式,与坐标相反
}
for (int i = 0 ; i <= numHeight; i++) {
mx.setPoint(baseline + i, anchor + numWidth, true);
}
for (int i = 0 ; i <= numWidth; i++) {
mx.setPoint(baseline, anchor + i, true);
mx.setPoint(baseline + numHeight / 2, anchor + i, true);
mx.setPoint(baseline + numHeight, anchor + i, true); //(y,x)格式,与坐标相反
}
break;
}
mx.update();//让修改生效,将数字显示出来
}
void Temp_Humi_Detect() {
uint32_t start = micros();
int chk = DHT.read22(DHT22_PIN);//读取温度
uint32_t stop = micros();
stat.total++;
switch (chk)//设置状态,确定是否存在错误
{
case DHTLIB_OK:
stat.ok++;
// Serial.print("OK,\t");
break;
case DHTLIB_ERROR_CHECKSUM:
stat.crc_error++;
// Serial.print("Checksum error,\t");
break;
case DHTLIB_ERROR_TIMEOUT:
stat.time_out++;
// Serial.print("Time out error,\t");
break;
case DHTLIB_ERROR_CONNECT:
stat.connect++;
// Serial.print("Connect error,\t");
break;
case DHTLIB_ERROR_ACK_L:
stat.ack_l++;
// Serial.print("Ack Low error,\t");
break;
case DHTLIB_ERROR_ACK_H:
stat.ack_h++;
// Serial.print("Ack High error,\t");
break;
default:
stat.unknown++;
// Serial.print("Unknown error,\t");
break;
}
//记录数据
if (humi != DHT.humidity || temp != DHT.temperature) {//仅在温湿度产生变化时修清空屏幕,防止闪烁
mx.clear();
}
humi = DHT.humidity;//记录湿度
temp = DHT.temperature;//记录温度
if (temp < 0) temp = -temp;//取绝对值
temp_minus = DHT.temperature < 0;//记录是否为负值
}
void input_number(char key) {//处理键盘输入的数字
float t;
if (!modifying) return; // 如果不在修改模式,直接退出
//确认需要修改的值
if (displayMode == 0) {//温度
if (maxOrMin == 0) t = min_temp_t;//获取最小值
else if (maxOrMin == 1) t = max_temp_t;//获取最大值
}
if (displayMode == 1) {//湿度
if (maxOrMin == 0) t = min_humi_t;//获取最小值
else if (maxOrMin == 1) t = max_humi_t;//获取最大值
}
t *= 10; // 数字向前移动一位
if (t > 100) return; //如果移动后大于100(即位数已经满了),不修改,直接退出
t += (float)(key - 48) / 10; // 最新输入的数字放入小数点后一位
//写回修改值
if (displayMode == 0) {//温度
if (maxOrMin == 0) min_temp_t = t;//获取最小值
else if (maxOrMin == 1) max_temp_t = t;//获取最大值
}
if (displayMode == 1) {//湿度
if (maxOrMin == 0) min_humi_t = t;//获取最小值
else if (maxOrMin == 1) max_humi_t = t;//获取最大值
}
}
void set_minus() {//处理负号输入
if (!modifying) return; // 如果不在修改模式,直接退出
if (displayMode == 1) return; //湿度不可为负数,退出
if (maxOrMin == 0) min_temp_t_minus = !min_temp_t_minus;//如果在修改最大值,设置最大值为负数
else max_temp_t_minus = !max_temp_t_minus;//如果在修改最小值,设置最小值为负数
}
void setLimit() {//处理键盘输入
char key = keypad.getKey();//获取字符
if (key != NO_KEY) {
mx.clear();//清空屏幕
switch (key) {
case 'A': //adjust: 进入修改模式(默认修改温度)
displayMode = 0;
modifying = true;
break;
case 'M': //maxOrMin: 切换修改最大值或者最小值,0为最小值
if (modifying) maxOrMin = 1 - maxOrMin;
Serial.print("modifying ");
if (maxOrMin == 0) Serial.print("min value\n");
else Serial.print("max value\n");
break;
case 'T': //toggle: 切换显示模式
displayMode = 1 - displayMode;
Serial.print("displaying ");
if (displayMode == 0) Serial.print(" temperature\n");
else Serial.print(" humidity\n");
maxOrMin = 0;
break;
case '-': // 切换当前输入正负值
set_minus();
break;
case 'S': //submit: 提交修改结果,并退出修改模式
if (max_temp_t_minus) max_temp_t = -max_temp_t;
if (min_temp_t_minus) min_temp_t = -min_temp_t;
Serial.print("min temperature: ");
Serial.print(min_temp_t);
Serial.print("\tmax temperature: ");
Serial.println(max_temp_t);
Serial.print("min humidity: ");
Serial.print(min_humi_t);
Serial.print("\tmax humidity: ");
Serial.println(max_humi_t);
if (max_temp_t >= min_temp_t && max_humi_t >= min_humi_t) {
max_temp = abs(max_temp_t);
min_temp = abs(min_temp_t);
max_humi = max_humi_t;
min_humi = min_humi_t;
max_temp_minus = max_temp_t_minus;
min_temp_minus = min_temp_t_minus;
}
else Serial.println("invalid input!");
//不break,重置临时值和修改状态
case 'C': //cancel: 不提交结果,退出修改模式,重置回当前值
max_temp_t = 0.0;
min_temp_t = 0.0;
max_humi_t = 0.0;
min_humi_t = 0.0;
max_temp_t_minus = false;
min_temp_t_minus = false;
maxOrMin = 0;
modifying = false;
break;
default: //输入的是0~9的数字,对其进行处理
input_number(key);
break;
}
}
}
int pow(int x, int y) {//获得x的y次方
if (y == 0) return 1;
int ret = x;
for (int i = 0 ; i < y - 1 ; i++) {
ret = x * ret;
}
return ret;
}
//在pos位置显示type的字符。
//type==0:负号;type==1:百分号;type==2:温度符号
void display_symble(int type, int pos) {
if (type == 0) { // -
int anchor = baseline + pos * (numWidth + interval) + 1;
for (int i = 0; i <= numWidth; i++) {
mx.setPoint(baseline + numHeight / 2, anchor + i, true);
}
}
else if (type == 1) { // %
int anchor = baseline + pos * (numWidth + interval);
for (int i = 0; i <= numHeight; i++) {
mx.setPoint(baseline + i, anchor + i / 2, true);
}
mx.setPoint(baseline + numHeight, anchor, true);
mx.setPoint(baseline + numHeight - 1, anchor, true);
mx.setPoint(baseline + numHeight - 1, anchor - 1, true);
mx.setPoint(baseline + numHeight, anchor - 1, true);
mx.setPoint(baseline, anchor + numWidth, true);
mx.setPoint(baseline + 1, anchor + numWidth, true);
mx.setPoint(baseline + 1, anchor + 1 + numWidth, true);
mx.setPoint(baseline, anchor + 1 + numWidth, true);
}
else { // ℃
int anchor = baseline + pos * (numWidth + interval) - 1;
for (int i = 0; i < 2; i++) {
mx.setPoint(baseline, anchor + 2 + i, true);
mx.setPoint(baseline + numHeight - 1, anchor + 2 + i, true);
}
for (int i = 0; i < numHeight - 2; i++) {
mx.setPoint(baseline + 1 + i, anchor + 1, true);
}
mx.setPoint(baseline + 1, anchor + numWidth + 1, true);
mx.setPoint(baseline + numHeight - 2, anchor + numWidth + 1, true);
mx.setPoint(baseline + numHeight, anchor, true);
mx.setPoint(baseline + numHeight - 1, anchor, true);
mx.setPoint(baseline + numHeight - 1, anchor - 1, true);
mx.setPoint(baseline + numHeight, anchor - 1, true);
}
}
//将参数结果显示在屏幕上
//x,y:要显示的两个数字
//x_minus,y_minus:x和y是否是负值
//x_type,y_type:x和y的类型,0为温度,1为湿度
void Number_Display(
float x, float y,
bool x_minus, bool y_minus,
int x_type, int y_type
) {
int x_digit[4] = {0, 0, 0, 0};
int y_digit[4] = {0, 0, 0, 0};
int base = 100;
//显示负号
if (x_minus) display_symble(0, 0);
if (y_minus) display_symble(0, 5);
//按位提取数字
for (int i = 0 ; i < 4; i++) {
x_digit[i] = (int)(x * pow(10, i) / base) % 10;
y_digit[i] = (int)(y * pow(10, i) / base) % 10;
}
//显示x
for (int i = 1 ; i < 4; i++) {
display_number(x_digit[i], i);
}
mx.setPoint(baseline, 20, true);//显示x的小数点
if (x_type == 0) display_symble(2, 4); //为x显示温度符号
else display_symble(1, 4); //为x显示湿度符号
//显示y
for (int i = 1 ; i < 4; i++) {
display_number(y_digit[i], i + 5);
}
mx.setPoint(baseline, 55, true);//显示y的小数点
if (y_type == 0) display_symble(2, 9);//为y显示温度符号
else display_symble(1, 9);//为y显示湿度符号
mx.update();//将结果显示在屏幕上
}
void alarm() {
//获取根据正负标识温度上下限以及当前温度的实际值
float u = max_temp_minus ? -max_temp : max_temp;
float l = min_temp_minus ? -min_temp : min_temp;
float c = temp_minus ? -temp : temp;
//如果温度超限
if (c > u || c < l) {
Serial.println("irregular temperature!!!");
Serial.print("\tcurrent temperature: ");
Serial.print(c);
Serial.print("\ttemperature limitation: [");
Serial.print(l);
Serial.print(" , ");
Serial.print(u);
Serial.println("]");
//实现间断性蜂鸣
if (alarming) noTone(ALARM_PIN); //如果在报警,则停止蜂鸣器
else tone(ALARM_PIN, NOTE_C5);//如果没在报警,则开启蜂鸣器
alarming = !alarming;//切换状态
}
else if (humi > max_humi || humi < min_humi) {
Serial.println("irregular humidity!!!");
Serial.print("\tcurrent humidity: ");
Serial.print(humi);
Serial.print("\thumidity limitation: [");
Serial.print(min_humi);
Serial.print(" , ");
Serial.print(max_humi);
Serial.println("]");
if (alarming) noTone(ALARM_PIN); //如果在报警,则停止蜂鸣器
else tone(ALARM_PIN, NOTE_C5);//如果没在报警,则开启蜂鸣器
alarming = !alarming;//切换状态
}
else noTone(ALARM_PIN);//没有错误,停止报警
}
void setup() {
//初始化点阵显示屏
mx.begin();
mx.control(MD_MAX72XX::INTENSITY, MAX_INTENSITY / 2);
mx.clear();
//初始化所有数字
temp = 24.0;
humi = 40.0;
max_temp = 50.0;
min_temp = 15.0;
max_humi = 80.0;
min_humi = 20.0;
max_temp_t = 0.0;
min_temp_t = 0.0;
max_humi_t = 0.0;
min_humi_t = 0.0;
max_temp_minus = max_temp_t_minus = min_temp_minus = min_temp_t_minus = false;
Serial.begin(115200);
//设置模式输入端口
pinMode(DETECTION_PIN, INPUT);
pinMode(SETTING_PIN, INPUT);
}
void loop() {
if (digitalRead(DETECTION_PIN) == HIGH) {//如果在检测模式
if (currState == 1) {//如果上一次loop中是修改模式,清空屏幕,准备显示当前温度
Serial.println("Change into DETECTION MODE!");
mx.clear();
}
currState = 0;//修改当前状态为检测模式
Temp_Humi_Detect();//获取温度
alarm();//判断是否有误,有误则间断性蜂鸣
Number_Display(temp, humi, temp_minus, false, 0, 1);//显示温度和湿度
delay(1000);//每一秒检测一次
}
else if (digitalRead(SETTING_PIN) == HIGH) {//如果在修改模式
if (currState == 0) {//如果上一次loop中是检测模式,清空屏幕,准备显示当前上下限
Serial.println("Change into SETTING MODE!");
mx.clear();
}
currState = 1;//修改当前状态为修改模式
setLimit();//处理可能存在的输入
if (modifying) {//修改中,显示临时值
if (displayMode == 0) // 显示温度
Number_Display(min_temp_t, max_temp_t, min_temp_t_minus, max_temp_t_minus, 0, 0);
else // 显示湿度
Number_Display(min_humi_t, max_humi_t, false, false, 1, 1);
}
else {//未在修改或者刚提交,显示当前设置值
if (displayMode == 0)
Number_Display(min_temp, max_temp, min_temp_minus, max_temp_minus, 0, 0);
else
Number_Display(min_humi, max_humi, false, false, 1, 1);
}
}
}