项目介绍
项目为课程设计,实现厨房着火,煤气泄漏的实时检测,STM32可以采集温度传感器DS18b20,烟雾MQ-7,CO MQ-2传感器的数据,同时采集OV7670摄像头的图片数据,通过MQTT协议发送到ONENET平台。在ONENET平台中可以通过可视化界面实时显示相应数据,同时可以调节相应传感器的报警阈值,出现报警时发送报警信息至用户邮箱。
硬件相关
STM32F103zet6
板子采用正点原子的精英板,引脚资源比较充裕,当然主要是因为没有其他板子了 嘿嘿。
温度传感器
DB18B20大概所有人都听过此传感器吧,好用就完事了。
MQ系列气体检测传感器
MQ系列真是个大家族,啥都有,一个传感器还能测好多数据,不过唯一麻烦的是我很难找到将电压值对应到具体PPM单位的数值,本项目直接简化处理了。可以参考这个视频中的相关计算。
lcd显示屏
采用了一个1.3的TFTlcd屏幕,spi驱动,为什么选这个呢,其实是做HOLOCUBIC剩下的屏幕,所以拿来用了。
OV7670(有FIFO)摄像头
朋友的摄像头,直接拿来用了,前期不太会玩,花了好长时间才明白。
ESP8266
用的正点原子的wifi模块,用了好长时间了,这个项目用完下个项目用。采用AT指令集的方式控制,STM32通过串口2发送AT信息给ESP8266。
接线
表格是本项目的接线
程序相关
传感器数据获取
DS18B20
直接copy的正点原子的例程,换了一下引脚,里面可能会有一些寄存器的知识,简单说一下吧。
//IO方向设置
#define DS18B20_IO_IN() {GPIOA->CRH&=0XFFFF0FFF;GPIOA->CRH|=8<<12;}
#define DS18B20_IO_OUT() {GPIOA->CRH&=0XFFFF0FFF;GPIOA->CRH|=3<<12;}
由于连接的是PA11口,所以GPIOA->CRH&=0XFFFF0FFF先把对应位置清0,GPIOA->CRH|=8<<12把对应位置设为输入模式。大家可以参考这篇博客:https://blog.csdn.net/zhuguanlin121/article/details/118659348
ADC采集
用来获取MQ-2和MQ-7的数据,采用了ADC1的通道1,和ADC2的通道0,相应的配置基本网上都有,可以参考附件代码,下面贴一下部分代码。
void Adc_Init(void)
{
ADC_InitTypeDef ADC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_ADC1|RCC_APB2Periph_ADC2, ENABLE ); //使能ADC1通道时钟
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M
//PA1 作为模拟通道输入引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模拟输入引脚PA1//MQ-2
GPIO_Init(GPIOA, &GPIO_InitStructure);
ADC_DeInit(ADC1); //复位ADC1,将外设 ADC1 的全部寄存器重设为缺省值
ADC_DeInit(ADC2);
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //ADC工作模式:ADC1和ADC2工作在独立模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE; //模数转换工作在单通道模式
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //模数转换工作在单次转换模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //转换由软件而不是外部触发启动
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //ADC数据右对齐
ADC_InitStructure.ADC_NbrOfChannel = 1; //顺序进行规则转换的ADC通道的数目
ADC_Init(ADC1, &ADC_InitStructure); //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //ADC工作模式:ADC1和ADC2工作在独立模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE; //模数转换工作在单通道模式
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //模数转换工作在单次转换模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //转换由软件而不是外部触发启动
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //ADC数据右对齐
ADC_InitStructure.ADC_NbrOfChannel = 1; //顺序进行规则转换的ADC通道的数目
ADC_Init(ADC2, &ADC_InitStructure);
ADC_Cmd(ADC1, ENABLE); //使能指定的ADC1
ADC_Cmd(ADC2, ENABLE);
ADC_ResetCalibration(ADC1); //使能复位校准
ADC_ResetCalibration(ADC2);
while(ADC_GetResetCalibrationStatus(ADC1)); //等待复位校准结束
while(ADC_GetResetCalibrationStatus(ADC2));
ADC_StartCalibration(ADC1); //开启AD校准
ADC_StartCalibration(ADC2);
while(ADC_GetCalibrationStatus(ADC1)); //等待校准结束
while(ADC_GetCalibrationStatus(ADC2));
float Smog_Get_Vol(void) //MQ-2烟雾电压值
{
u16 adc_value1 = 0;
float voltage1 = 0;
adc_value1 = Get_Adc1_Average(ADC_Channel_0,5);
delay_ms(5);
voltage1 = (3.3/4096.0)*(adc_value1);
return voltage1;
}
float CO_Get_Vol(void)
{
u16 adc_value2 = 0;
float voltage2 = 0;
adc_value2 = Get_Adc2_Average(ADC_Channel_1,5);
delay_ms(5);
voltage2 = (3.3/4096.0)*(adc_value2);
return voltage2;
}
float Smog_GetPPM()
{
float ppm=80*Smog_Get_Vol();
return ppm;
}
}
ONENET代码相关
发送传感器数据
这里很奇怪的是当数据量超过4个后,有时会自动离线,所以改为了三个,分不同批次的发送。
//以TYPE 1形式发送传感器数据
unsigned char OneNet_FillBuf(char *buf)
{
char text[128];
memset(text, 0, sizeof(text));
strcpy(buf, "{\"datastreams\":[");
memset(text, 0, sizeof(text));
sprintf(text, "{\"id\":\"smog\",\"datapoints\":[{\"value\":%0.2f}]},", MQ_2);
strcat(buf, text);
memset(text, 0, sizeof(text));
sprintf(text, "{\"id\":\"CO\",\"datapoints\":[{\"value\":%0.2f}]},", MQ_7);
strcat(buf, text);
memset(text, 0, sizeof(text));
sprintf(text, "{\"id\":\"temp\",\"datapoints\":[{\"value\":%0.2f}]}", temp);
strcat(buf, text);
strcat(buf, "]}");
return strlen(buf);
}
unsigned char OneNet_FillBuf_alarm(char *buf)
{
u8 temp;
char text[128];
temp=alarm^stop;
memset(text, 0, sizeof(text));
strcpy(buf, "{\"datastreams\":[");
memset(text, 0, sizeof(text));
sprintf(text, "{\"id\":\"alarm\",\"datapoints\":[{\"value\":%d}]}", temp);
strcat(buf, text);
strcat(buf, "]}");
return strlen(buf);
}
void OneNet_SendData(void)
{
MQTT_PACKET_STRUCTURE mqttPacket = {NULL, 0, 0, 0}; //协议包
char buf[256];
short body_len = 0, i = 0;
printf("Send--------1\r\n");
//UsartPrintf(USART_DEBUG, "Tips: OneNet_SendData-MQTT\r\n");
memset(buf, 0, sizeof(buf));
body_len = OneNet_FillBuf(buf); //获取当前需要发送的数据流的总长度
if(body_len)
{
if(MQTT_PacketSaveData(DEVID, body_len, NULL, 1, &mqttPacket) == 0) //封包
{
for(; i < body_len; i++)
mqttPacket._data[mqttPacket._len++] = buf[i];
ESP8266_SendData(mqttPacket._data, mqttPacket._len); //上传数据到平台
//UsartPrintf(USART_DEBUG, "Send %d Bytes\r\n", mqttPacket._len);
MQTT_DeleteBuffer(&mqttPacket); //删包
}
else
UsartPrintf(USART_DEBUG, "WARN: EDP_NewBuffer Failed\r\n");
}
}
void OneNet_SendData_alarm(void)
{
MQTT_PACKET_STRUCTURE mqttPacket = {NULL, 0, 0, 0}; //协议包
char buf[256];
short body_len = 0, i = 0;
printf("Send--------3\r\n");
//UsartPrintf(USART_DEBUG, "Tips: OneNet_SendData-MQTT\r\n");
memset(buf, 0, sizeof(buf));
body_len = OneNet_FillBuf_alarm(buf); //获取当前需要发送的数据流的总长度
if(body_len)
{
if(MQTT_PacketSaveData(DEVID, body_len, NULL, 1, &mqttPacket) == 0) //封包
{
for(; i < body_len; i++)
mqttPacket._data[mqttPacket._len++] = buf[i];
ESP8266_SendData(mqttPacket._data, mqttPacket._len); //上传数据到平台
//UsartPrintf(USART_DEBUG, "Send %d Bytes\r\n", mqttPacket._len);
MQTT_DeleteBuffer(&mqttPacket); //删包
}
else
UsartPrintf(USART_DEBUG, "WARN: EDP_NewBuffer Failed\r\n");
}
}
发送图片数据
TPYE 2类型MQTT发送OV7670截取的图片数据
RGB565格式需要转成JPEG的格式,需要有一个转换的步骤。
#define PKT_SIZE 1024
void OneNet_SendData_Picture(char *devid, unsigned char* picture, unsigned int pic_len)
{
MQTT_PACKET_STRUCTURE mqttPacket = {NULL, 0, 0, 0}; //协议包
unsigned char *pImage = (unsigned char *)picture;
if(MQTT_PacketSaveBinData("pic",pic_len, &mqttPacket)==0)
{
//发送头
ESP8266_Clear();
UsartPrintf(USART_DEBUG, "Send %d Bytes\r\n", mqttPacket._len);
ESP8266_SendData(mqttPacket._data, mqttPacket._len); //上传数据到平台
MQTT_DeleteBuffer(&mqttPacket); //删包
UsartPrintf(USART_DEBUG, "image len = %d\r\n", pic_len);
//发送数据
while(pic_len > 0)
{
delay_ms(300); //传图时,时间间隔会大一点,这里额外增加一个延时
if(pic_len >= PKT_SIZE)
{
ESP8266_SendData(pImage, PKT_SIZE); //串口发送分片
pImage += PKT_SIZE;
pic_len -= PKT_SIZE;
}
else
{
ESP8266_SendData(pImage, (unsigned short)pic_len); //串口发送最后一个分片
pic_len = 0;
}
}
UsartPrintf(USART_DEBUG, "image send ok\r\n");
}
else
{
UsartPrintf(USART_DEBUG, "WARN: EDP_NewBuffer Failed\r\n");
}
}
void OneNet_SendData_pic(void)
{
camera_refresh();
if(get_shoot==1){
OneNet_SendData_Picture(NULL, JPG_enc_buf, pt_buf);
ESP8266_Clear();
yfy=1;
}
}
void camera_refresh(void)
{
int j,i,t;
u16 color;
int width, height;
jpeg_compress_info *cinfo;
pt_buf = 0;
printf(" prepare shoot\r\n");
if(ov_sta==2)
{
width = 320;//图像的宽度 ------
height = 240;//图像的高度 4------
cinfo = jpeg_create_compress();//------
if (!cinfo) //------
{
printf("error in create cinfo, malloc faild!\n");
}
cinfo->image_width = width;
cinfo->image_height= height;
cinfo->output =(char *)JPG_enc_buf;//fopen("test.jpg", "wb");
jpeg_set_default(cinfo);
jpeg_start_compress(cinfo);
OV7670_CS=0;
OV7670_RRST=0; //开始复位读指针
OV7670_RCK=0;
OV7670_RCK=1;
OV7670_RCK=0;
OV7670_RRST=1; //复位读指针结束
OV7670_RCK=1;
for(j=0;j<240;j++)
{
for(i=319;i>=0;i--)
{
GPIOB->CRL=0X88888888;
OV7670_RCK=0;
color=OV7670_DATA; //读数据
OV7670_RCK=1;
color<<=8;
OV7670_RCK=0;
color|=OV7670_DATA; //读数据
OV7670_RCK=1;
GPIOB->CRL=0X33333333;
//LCD_WR_DATA(color);
//转换
R = (color>>8)&0xF8;
G = (color>>3)&0xFC;
B = (color<<3)&0xF8;
buffer[i*3+0] = R;//jpeg格式顺序RGB 如果是bmp格式写成BGR
buffer[i*3+1] = G;
buffer[i*3+2] = B;
}
jpeg_write_scanline(cinfo, buffer);//一行一行的压缩
}
jpeg_finish_compress(cinfo);
jpeg_destory_compress(cinfo);
OV7670_CS=1;
OV7670_RCK=0;
OV7670_RCK=1;
EXTI->PR=1<<15; //清除LINE8上的中断标志位
get_shoot=1;
}
else get_shoot=0;
}
任务调度相关
由于代码中任务众多,因此使用了一种任务调度的方式,具体可参考以下文章
STM32 简单多任务调度的方法与程序例程
具体代码可看附件。
//结构体创建
typedef struct{
void (*fTask)(void); //fTask为任务指针,指向函数
long uNextTick; //该任务下一次执行的时间
int uLenTick; //调度周期
}Task;
// 任务列表
static Task myTaskTab[] =
{
{Task_SysTick, 0, 0} //计算系统时间
,{KEY_pro, 0, 100} //100ms执行一次
,{LCD_display, 150, 100} //第150ms开始执行,每隔100ms执行一次
,{Sensor_get, 500, 2000}
,{Control_pro, 3000, 1000}
,{Control_shoot,750,100}
,{Senddata_pro,4000,2000}//4000
,{Receivedata_pro,50,30}
,{if_disconnect,2000,5000}
};
int main(void)
{
//主函数变量定义
int i = 0;
//初始化相关
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
dev_SysTick_init(); // 系统时钟初始化
Hardware_init();
Onenet_init();
Value_init();
OneNet_SendData_pic();
TIM_Cmd(TIM2, ENABLE);
LCD_Fill(0,0,240,240,WHITE); //刷新页面
while(1)
{
// 任务循环
for (i = 0; i < ARRAYSIZE(myTaskTab); i++)
{
if (myTaskTab[i].uNextTick <= GetTimingTick())
{
myTaskTab[i].uNextTick += myTaskTab[i].uLenTick;
myTaskTab[i].fTask();
}
}
}
}
ONENET平台相关
各种控件使用的都是私有过滤器,模板基本一样,目的是显示最新得到的数据。
资源
stm32 ESP8266 Onenet 图片上传,数据上传
注:第一次写文章,错误可能会有,请多包涵。