STM32 AM2320 温湿度万年历 微信小程序显示及控制
作者:互联网
功能描述:
使用STM32F103R8T6,红外遥控器,数码管,串口,预留ADC(4~20mA输入、0~10V输入)、485、以太网、WiFi、SD卡、USB_OTG等功能。单总线的方式采集温湿度(因整个系统时序要求,所以使用状态机采集),ST自带的RTC时钟。单片机采集到温湿度数值通过串口自制的4G模块通讯,上传数据到指定服务器,然后在微信小程序关注蓝星物联,就可以看到设备数据,我们还做了一个网站,专门管理底层设备。现在分享单片机程序和部分原理图。程序源码在一个资源里面,下载需要积分,没有积分的可以私信我。源码地址:
硬件设计:
1)电源及MCU部分
使用LM2596DC-DC,降压后放上一个LDO输出3.3V。预留SWD下载,纽扣电池,BOOT脚选择,红外接收头,温湿度模块,多预留一路时钟模块,防止内部RTC随温漂的误差。以及其他一些外设接口预留。
2)通讯及数据保存部分
预留485接口,232接口,USB接口,WiFi接口,以太网接口。通信接口只要做数据汇总、数据上传使用。数据保存有SD卡保存和flash保存两种,SD卡保存使用模块外接的形式,可以把采集到的数据直接保存成文件的形式。flash芯片可以保存需要的字库等等。
3)输入输出部分
预留ADC采集4~20mA及0~10V模拟信号。按键输入,不是全隔离,只是防止客户使用的时候会接错烧坏MCU引脚。继电器输出,可带12V声光报警器。
4)数码管显示部分
使用驱动芯片JXI5020级联,做到三线带多位数码管。
5)PCB展示
软件设计:
1)main.c
main函数进来,先是各种外设初始化,第一个while(1)的循环是发送消息给服务器或者上层设备注册设备用的,设备长时间不按照协议通讯的话,设备会软复位,重新注册。在第二个while(1)的循环是处理注册后的数据,包括温湿度和时间的显示,按键(红外遥控和轻触按键)设置,报警状态设置,设置定时,每1S钟发送一次实时数据到服务器,处理串口2接收到服务器发送的数据(JSON格式,使用CJSON库)。
/*********************************************************************************
* 文件名 :main.c
* 描述 :
* 实验平台:
* 库版本 :ST3.5.0
* 作者 :
* 论坛 :
* 版本 ?VV1.0
* 时间 :2017.6.6
备注 : STM32F103_R8T6 工程模版
**********************************************************************************/
#include "Header_File.h"
u8 Send_Buff1[114];
u16 Crc_Send_Num=0;
u8 Crc_Send_H=0;
u8 Crc_Send_L=0;
u32 Set_MS_Num=0;
#define SW_RESET() NVIC_SystemReset()
int processMessage(char *msg) {
cJSON *jsonObj=cJSON_Parse(msg);
cJSON *method;
cJSON *typ;
// cJSON *temmin;
// cJSON *temmax;
// cJSON *temcalibrate;
// cJSON *tembkwd;
// cJSON *hummin;
// cJSON *hummax;
// cJSON *humcalibrate;
// cJSON *humbkwd;
char *m;
int x;
//json字符串解析失败,直接退出
if(!jsonObj)
{
cJSON_Delete(jsonObj);
return 0;
}
method = cJSON_GetObjectItem(jsonObj, "TYPE");
m = method->valuestring;
if(strncmp(m, "AUTHERROR", 9) == 0 ) //设备未注册,重新注册
{
cJSON_Delete(jsonObj);
SW_RESET();
return 1;
// char *content = cJSON_GetObjectItem(jsonObj, "DATA")->valuestring;
// if(strncmp(content, "No identity", 4) == 0)
// {
//
// }
}
if(strncmp(m, "HSOK", 4) == 0 ) //注册信息返回
{
char *content = cJSON_GetObjectItem(jsonObj, "DATA")->valuestring;
if(strncmp(content, "HSOK", 4) == 0)
{
cJSON_Delete(jsonObj);
return 1;
}
}
if(strncmp(m, "DATAOK", 6) == 0 ) //
{
char *content = cJSON_GetObjectItem(jsonObj, "TYPE")->valuestring;
if(strncmp(content, "DATAOK", 6) == 0)
{
Resert_Cnt_10S=0; //重启时间清零
cJSON_Delete(jsonObj);
return 1;
}
}
if(strncmp(m, "CDATA", 5) == 0 ) //
{
typ = cJSON_GetObjectItem(jsonObj, "ctype");
x=typ->valueint;
// if(strncmp(m, "1", 1) == 0 )
if(x==1)
{
int ye = cJSON_GetObjectItem(jsonObj, "y")->valueint;
int mo = cJSON_GetObjectItem(jsonObj, "m")->valueint;
int da = cJSON_GetObjectItem(jsonObj, "d")->valueint;
int ho = cJSON_GetObjectItem(jsonObj, "h")->valueint;
int mi = cJSON_GetObjectItem(jsonObj, "i")->valueint;
int se = cJSON_GetObjectItem(jsonObj, "s")->valueint;
RTC_Set(ye,mo,da,ho,mi,se);
// Set_MS_Num=atoi(ye);
}
else if(x==2) //
{
double temmin = cJSON_GetObjectItem(jsonObj, "tem_min")->valuedouble;
double temmax = cJSON_GetObjectItem(jsonObj, "tem_max")->valuedouble;
double temcalibrate = cJSON_GetObjectItem(jsonObj, "tem_calibrate")->valuedouble;
double tembkwd = cJSON_GetObjectItem(jsonObj, "tem_bkwd")->valuedouble;
double hummin = cJSON_GetObjectItem(jsonObj, "hum_min")->valuedouble;
double hummax = cJSON_GetObjectItem(jsonObj, "hum_max")->valuedouble;
double humcalibrate = cJSON_GetObjectItem(jsonObj, "hum_calibrate")->valuedouble;
double humbkwd = cJSON_GetObjectItem(jsonObj, "hum_bkwd")->valuedouble;
// Temperature_Down=atoi(temmin);
// Temperature_Up=atoi(temmax);
Temperature_Up=(s16)(temmax*10);
Temperature_Down=(s16)(temmin*10);
Temperature_Temp=(s16)(temcalibrate*10);
Temperature_hc=(s16)(tembkwd*10);
Humidity_Up=(s16)(hummax*10);
Humidity_Down=(s16)(hummin*10);
Humidity_Temp=(s16)(humcalibrate*10);
Humidity_hc=(s16)(humbkwd*10);
}
}
// if(jsonObj)
cJSON_Delete(jsonObj);
return 0;
}
int main(void)
{
// char* out="{\"TYPE\":\"fengxin\",\"passwd\":123,\"y\":2020,\"m\":6,\"d\":3,\"h\":23,\"minu\":33}"; // ,\"miao\":20
u8 ack_Usart1=0;
u8 cclen=0;
u16 Cnt_500S=0;
u16 Cnt_500Ms=0;
SystemInit(); //系统时钟初始化为72M SYSCLK_FREQ_72MHz
TIM3_Cnt_Init(999,71); //配置定时器3 1Ms中断一次
RTC_Init(); //RTC时钟初始化
IO_Init(); //GPIO初始化
Uart1_Init(115200); //串口1初始化
// Uart3_Init(9600);
HC595_IO_Init(); //74HC595使用初始化
Init_AOSONG(); //温湿度模块初始化
Init_Infrared(); //红外模块初始化,开一个定时器和一个外部中断
// Adc_Init();
Key_IO(); //按键输入初始化 就是这么优秀
Uart2_Init(115200); //数据上传
// Interface_Changes=1;
W25QXX_Init(); //W25QXX初始化
FLASH_SIZE=32*1024*1024; //FLASH 大小为xxM字节
Read_Data_From_Flash();
Time_Num[8]=calendar.week%10;
delay_ms(100);
show_led12();
while(1) //注册设备信息,待服务器返回后退出
{
if(System1S==1)
{
System1S=0;
cclen=sprintf((char *)Send_Server,"{\"TYPE\":\"HS\",\"CID\":\"WJVSAIL1\"}");
Usart2_Printf((uint8_t *)Send_Server,cclen);
}
if(USART2_RX_STA == 1)
{
USART2_RX_STA = 0;
ack_Usart1=processMessage((char*)Usart2_RxBuff);
Clear_Buffer((uint8_t*)Usart2_RxBuff,strlen((char*)Usart2_RxBuff));
rx_counter_Usart2 = 0;
if(ack_Usart1==1) break;
}
}
while(1)
{
show_led12(); //数码管显示
Key_use(); //按键处理
Data_Change();
if(Display_Complete_Flag==1)
{
if(((Temp_001>Temperature_Up)||(Temp_001<Temperature_Down)||(Humi_001>Humidity_Up)||(Humi_001<Humidity_Down))&&(Mute_Flag==0)&&(Set_Position==0)&&(Key_Set_Position==0))
{
if(Temp_001>Temperature_Up) Temperature_Up_Flag=1;
if(Humi_001>Humidity_Up) Humidity_Up_Flag=1;
if(Temp_001<Temperature_Down) Temperature_Down_Flag=1;
if(Humi_001<Humidity_Down) Humidity_Down_Flag=1;
Out2=1;
}
else if((Set_Position==0)&&(Key_Set_Position==0))
{
if(Temp_001<=(Temperature_Up-Temperature_hc)) Temperature_Up_Flag=0;
if(Temp_001>=(Temperature_Down+Temperature_hc)) Temperature_Down_Flag=0;
if(Humi_001<=(Humidity_Up-Humidity_hc)) Humidity_Up_Flag=0;
if(Humi_001>=(Humidity_Down+Humidity_hc)) Humidity_Down_Flag=0;
if((Temperature_Up_Flag==0)&&(Temperature_Down_Flag==0)&&(Humidity_Up_Flag==0)&&(Humidity_Down_Flag==0))
Out2=0;
if(Mute_Flag==1) Out2=0;
}
}
if((Set_Position==0)&&(Key_Set_Position==0))
{
Time_Num[8]=calendar.week%10;
if(Time_Num[8]==0) Time_Num[8]=7;
}
if(System10Ms==1)
{
System10Ms=0;
if(++Cnt_500Ms>=50)
{
Cnt_500Ms=0;
LED1=~LED1;
if((Set_Position==0)&&(Key_Set_Position==0))
{
RTC_LED=~RTC_LED;
}
else
RTC_LED=0;
}
}
if(System1S==1)
{
System1S=0;
Clear_Buffer((uint8_t*)Send_Server,strlen((char*)Send_Server));
cclen=sprintf((char *)Send_Server,"{\"TYPE\":\"DATA\",\"Tmp\":\"%d.%01d\",\"Hum\":\"%d.%01d\"}",Temp_001/10,Temp_001%10,Humi_001/10,Humi_001%10); //,Temp_001/10,Temp_001%10,Humi_001/10,Humi_001%10
Usart2_Printf((uint8_t *)Send_Server,cclen);
if(++Resert_Cnt_10S>=20) //10s后没有接到来自服务器的数据进行软重启,再次注册
{
Resert_Cnt_10S=0;
SW_RESET();
}
}
if(USART2_RX_STA == 1)
{
USART2_RX_STA = 0;
processMessage((char*)Usart2_RxBuff);
Clear_Buffer((uint8_t*)Usart2_RxBuff,strlen((char*)Usart2_RxBuff));
rx_counter_Usart2 = 0;
}
}
}
2)Infrared.c
红外接收模块处理文件。在主函数里面只需要直接访问InfraredCode变量就可以知道是否有按键按下。开启一个定时器和一个外部中断,检测接收头(HS0038B)是否收到遥控器的信号,并按照固定的格式给信号做一个处理。识别对应的按钮按下对应的码值,做一个赋值,供主函数读取使用。
#include "infrared.h"
//#include "Timer2_Cnt.h"
u8 timercnt_up=0;
u8 timercnt_down=0;
u8 Infraredbuf[4];
u8 InfraredCode=0;
// 红外遥控器 编码格式
// START + 8Bit厂商编码 + 8Bit反码 + 8Bit数据 + 8Bit反码
// START L=9.00ms H=4.500ms 引导码
// BIT0 L=0.56ms H=0.565ms 数据0
// BIT1 L=0.56ms H=1.690ms 数据1
// PRESS L=9.00ms H=2.250ms 重复码
u8 irdata[33];
u8 irok;
u16 irtime;
static u8 ir_flag; //是否开始处理标志位
void TIM2_Cnt_Init(u16 arr,u16 psc) // TIM2_Cnt_Init(999,71); 1ms中断一次
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //①时钟TIM2使能
//定时器TIM2初始化
TIM_TimeBaseStructure.TIM_Period = arr; //设置自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //②初始化TIM2
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE ); //③允许更新中断
//中断优先级NVIC设置
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //TIM2中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //抢占优先级1级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //从优先级0级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ 通道被使能
NVIC_Init(&NVIC_InitStructure); //④初始化NVIC寄存器
TIM_Cmd(TIM2, ENABLE); //⑤使能TIM2
}
//定时器2中断服务程序
void TIM2_IRQHandler(void) //TIM2中断
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) //检查TIM2更新中断发生与否
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update); //清除TIM2更新中断标志
irtime++; //用于计数2个下降沿之间的时间,0.1ms进一次定时器中断
if(irtime>=1000) //irtime>=1000说明按键已经松开有1000*0.1ms=100ms了,故可以使能红外接收功能
{
ir_flag=1; //使能红外接收功能
}
// if(INIR) timercnt_up++;
// else timercnt_down++;
// System1Ms=1;
// if(++i>=10)
// {
// i=0;
// System10Ms=1;
// }
}
}
void EXTIX_Init(void)
{
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//外部中断,需要使能AFIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//使能PORTA,PORTC时钟
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);//关闭jtag,使能SWD,可以用SWD模式调试
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;//PC5
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //设置成上拉输入
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOC5
//GPIOC.1 中断线以及中断初始化配置
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource4);
EXTI_InitStructure.EXTI_Line=EXTI_Line4;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;//下降沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure); //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器
NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn; //使能按键所在的外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00; //子优先级1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
}
void EXTI4_IRQHandler(void)
{
static u8 RecvCnt; //接收红外信号处理
u8 a,b,c,d,i,e,f,tmp;
u32 RecvData = 0;
u16 VS1838_KEY_VALUE;
if(EXTI_GetITStatus(EXTI_Line4)!=RESET)
{
EXTI_ClearITPendingBit(EXTI_Line4); //清除EXTI0线路挂起位
if(ir_flag==0)
{
irtime=0; //清零计数器
return;
}
else if(ir_flag==1)
{
if((irtime>IR_START) && (irtime<(IR_START + DataTolerance_IR))) //接收到引导码 9ms+4.5ms
{
RecvCnt=0;
}
irdata[RecvCnt]=irtime; //存储每个电平的持续时间,用于以后判断是0还是1
irtime=0; //清零irtime变量
RecvCnt++; //数组下标自增1
if(RecvCnt>=33)
{
irok=1; //接收完33个红外数据
RecvCnt=0; //数组下标清零
ir_flag=0; //失能红外接收功能
VS1838_KEY_VALUE=0;
for(i=1;i<33;i++){
if((irdata[i] > IR_BIT1) && (irdata[i] < (IR_BIT1 + DataTolerance_IR))){ // BIT1
RecvData <<= 1;
RecvData |= 1;
}else if((irdata[i] > IR_BIT0) && (irdata[i] < (IR_BIT0 + DataTolerance_IR))){ // BIT0
RecvData <<= 1;
}
}
a=(RecvData>>24)&0xFF;
b=(RecvData>>16)&0xFF;
c=(RecvData>>8)&0xFF;
d= RecvData&0xFF;
e=~b;
f=~d;
if((a==e)&&(c==f))
{
VS1838_KEY_VALUE=(a<<8)|c;
}
switch(VS1838_KEY_VALUE){
case 0x0098:InfraredCode=Infrared_0;break; // 按键 0
case 0x00A2:InfraredCode=Infrared_1;break; // 按键 1
case 0x0062:InfraredCode=Infrared_2;break; // 按键 2
case 0x00E2:InfraredCode=Infrared_3;break; // 按键 3
case 0x0022:InfraredCode=Infrared_4;break; // 按键 4
case 0x0002:InfraredCode=Infrared_5;break; // 按键 5
case 0x00C2:InfraredCode=Infrared_6;break; // 按键 6
case 0x00E0:InfraredCode=Infrared_7;break; // 按键 7
case 0x00A8:InfraredCode=Infrared_8;break; // 按键 8
case 0x0090:InfraredCode=Infrared_9;break; // 按键 9
case 0x0068:InfraredCode=Infrared_Xing;break; // 按键 *
case 0x00B0:InfraredCode=Infrared_Jing;break; // 按键 #
case 0x0010:InfraredCode=Infrared_Left;break; // 按键 <
case 0x005A:InfraredCode=Infrared_Right;break; // 按键 >
case 0x0018:InfraredCode=Infrared_Up;break; // 按键 ^
case 0x004A:InfraredCode=Infrared_Down;break; // 按键 v
case 0x0038:InfraredCode=Infrared_OK;break; // 按键 OK
default: tmp=0;break; // NO_KEY
}
}
}
// irok=0;
}
}
/* 红外解码,返回按键键值 */
void HS0038B_DeCode(void)
{
// u8 a,b,c,d,i,tmp;
// u32 RecvData = 0;
// u16 VS1838_KEY_VALUE;
//
// if(irok){
// irok=0;
// VS1838_KEY_VALUE=0;
// for(i=1;i<33;i++){
// if((irdata[i] > IR_BIT1) && (irdata[i] < (IR_BIT1 + DataTolerance_IR))){ // BIT1
// RecvData <<= 1;
// RecvData |= 1;
// }else if((irdata[i] > IR_BIT0) && (irdata[i] < (IR_BIT0 + DataTolerance_IR))){ // BIT0
// RecvData <<= 1;
// }
// }
// a=(RecvData>>24)&0xFF;
// b=(RecvData>>16)&0xFF;
// c=(RecvData>>8)&0xFF;
// d= RecvData&0xFF;
// if((a==~b)&&(c==~d))
// VS1838_KEY_VALUE=(a<<8)|c;
//
// switch(VS1838_KEY_VALUE){
// case 0x0098:InfraredCode=Infrared_0;break; // 按键 0
// case 0x00A2:InfraredCode=Infrared_1;break; // 按键 1
// case 0x0062:InfraredCode=Infrared_2;break; // 按键 2
// case 0x00E2:InfraredCode=Infrared_3;break; // 按键 3
// case 0x0022:InfraredCode=Infrared_4;break; // 按键 4
// case 0x0002:InfraredCode=Infrared_5;break; // 按键 5
// case 0x00C2:InfraredCode=Infrared_6;break; // 按键 6
// case 0x00E0:InfraredCode=Infrared_7;break; // 按键 7
// case 0x00A8:InfraredCode=Infrared_8;break; // 按键 8
// case 0x0090:InfraredCode=Infrared_9;break; // 按键 9
// case 0x0068:InfraredCode=Infrared_Xing;break; // 按键 *
// case 0x00B0:InfraredCode=Infrared_Jing;break; // 按键 #
// case 0x0010:InfraredCode=Infrared_Left;break; // 按键 <
// case 0x005A:InfraredCode=Infrared_Right;break; // 按键 >
// case 0x0018:InfraredCode=Infrared_Up;break; // 按键 ^
// case 0x004A:InfraredCode=Infrared_Down;break; // 按键 v
// case 0x0038:InfraredCode=Infrared_OK;break; // 按键 OK
// default: tmp=0;break; // NO_KEY
// }
return tmp;
// }
// return 0;
}
void Init_Infrared(void)
{
TIM2_Cnt_Init(99,71); //100us
EXTIX_Init();
}
3)其他
具体使用到的可以看下面的工程的目录,下面有源码分享的链接。
协议及调试效果
1)硬件实物图
2)微信小程序检测界面
总结
博客就只是分享了原理图和部分程序,一些注释也不全。有需要的可以下载源码。底层的设备都是做好和服务器直接对接的,要是连接服务器的话可以直接使用。或者加上中转设备,有WiFi,以太网或者4G。协议也是有的,因最近比较忙,等我回苏之后贴出来。谢谢阅览。
标签:NVIC,jsonObj,cJSON,温湿度,STM32,TIM,InitStructure,微信,EXTI 来源: https://blog.csdn.net/L_e_c/article/details/111318396