功能描述:
使用STM32F103R8T6,红外遥控器,数码管,串口,预留ADC(4~20mA输入、0~10V输入)、485、以太网、WiFi、SD卡、USB_OTG等功能。单总线的方式采集温湿度(因整个系统时序要求,所以使用状态机采集),ST自带的RTC时钟。单片机采集到温湿度数值通过串口自制的4G模块通讯,上传数据到指定服务器,然后在微信小程序关注蓝星物联,就可以看到设备数据,我们还做了一个网站,专门管理底层设备。现在分享单片机程序和部分原理图。程序源码在一个资源里面,下载需要积分,没有积分的可以私信我。源码地址:https://download.csdn.net/download/L_e_c/14029632
硬件设计:
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\":20u8 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%10Usart2_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;//PC5GPIO_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; //子优先级1NVIC_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还是1irtime=0; //清零irtime变量RecvCnt++; //数组下标自增1if(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))){ // BIT1RecvData <<= 1;RecvData |= 1;}else if((irdata[i] > IR_BIT0) && (irdata[i] < (IR_BIT0 + DataTolerance_IR))){ // BIT0RecvData <<= 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; // 按键 0case 0x00A2:InfraredCode=Infrared_1;break; // 按键 1case 0x0062:InfraredCode=Infrared_2;break; // 按键 2case 0x00E2:InfraredCode=Infrared_3;break; // 按键 3case 0x0022:InfraredCode=Infrared_4;break; // 按键 4case 0x0002:InfraredCode=Infrared_5;break; // 按键 5case 0x00C2:InfraredCode=Infrared_6;break; // 按键 6case 0x00E0:InfraredCode=Infrared_7;break; // 按键 7case 0x00A8:InfraredCode=Infrared_8;break; // 按键 8case 0x0090:InfraredCode=Infrared_9;break; // 按键 9case 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; // 按键 vcase 0x0038:InfraredCode=Infrared_OK;break; // 按键 OKdefault: 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); //100usEXTIX_Init();}
3)其他
具体使用到的可以看下面的工程的目录,下面有源码分享的链接。
协议及调试效果
1)硬件实物图
2)微信小程序检测界面
3)演示视频
感兴趣的朋友可以关注一下抖音账号,分享了一些做的东西的视频在上面,不是太多
总结
博客就只是分享了原理图和部分程序,一些注释也不全。有需要的可以下载源码。底层的设备都是做好和服务器直接对接的,要是连接服务器的话可以直接使用。或者加上中转设备,有WiFi,以太网或者4G。协议也是有的,因最近比较忙,等我回苏之后贴出来。谢谢阅览。