元件清单:
stm32f103c8t6、mq2 检测烟雾浓度(模拟量输出)、mq7 检测一氧化碳浓度、mq135 检测空气质量、OLED屏幕(四引脚仅支持iic协议通信)、dht11检测温湿度(数字量输出)、风扇模块、无源蜂鸣器、两引脚按键、WH-NB73-B5、ttl-usb
接线图:


0:实现了dht11的温湿度以及mq2烟雾浓度的采集并通过OLED显示屏显示

/*
湿度整数 湿度小数 温度整数 温度小数 校验位
00000000 00000000 00000000 00000000 00000000
1 看原理图确认GPIO引脚
2、 输出模式, 输出起始信号 :输出低电平18~30ms, 20ms
3、 IO口配置浮空输入模式,准检测响应信号
传感器把数据总线( SDA)拉低 83μs,
再接高 87μs 以响应主机的起始信号。
4、 40 个位的数据,高位先发;
一位一位的收,数据0: 54us低电平 + 23~27高电平
数据1: 54us低电平 + 68~74高电平
注意高位先发的(每个字节)
5、校验数据
前4个字节,求和,把和值的末八位和校验位对比
相同数据正确、否则数据异常
*/
//程序未写零下
char tmp = 0,hum = 0;
void DHT_GPIO_Config(u8 flag)
{
GPIO_InitTypeDef GPIO_Config;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //开启端口时钟
GPIO_Config.GPIO_Pin = GPIO_Pin_8;
if(flag==OUTPUT)
GPIO_Config.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
else
GPIO_Config.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Config.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_Config);
}
u8 DHT_GetData(void)
{
u8 i = 0;
u8 count = 0;
u8 data[5]={0};
//输出模式, 输出起始信号 :输出低电平18~30ms, 20ms
DHT_GPIO_Config(OUTPUT);
DHT_High;
DHT_Low;
Delay_ms(20);
//DHT_High;
DHT_GPIO_Config(INPUT);
/*IO口配置浮空输入模式,准检测响应信号
传感器把数据总线( SDA)拉低 83μs,
再接高 87μs 以响应主机的起始信号。
*/
while(DHT_CHECK==1)
{
delay_1us();
count++;
if(count>100)
return 1;
}
count=0;
while(DHT_CHECK==0)
{
delay_1us();
count++;
if(count>100)
return 2;
}
for(i=0;i<40;i++)
{
count=0;
while(DHT_CHECK==1)
{
delay_1us();
count++;
if(count>100)
return 3;
}
count=0;
while(DHT_CHECK==0)
{
delay_1us();
count++;
if(count>100)
return 4;
}
Delay_us(30);
if(DHT_CHECK==1)
{
data[i/8] |= (1<<(7-i%8)); //置1
}else
{
data[i/8] &=~ (1<<(7-i%8));//清零
}
}
/*校验数据
前4个字节,求和,把和值的末八位和校验位对比
相同数据正确、否则数据异常*/
if((data[0]+data[1]+data[2]+data[3])==data[4])
{
tmp=data[2];
hum=data[0];
return 0;
}
else
{
return 5;
}
}
1:在上边的基础上利用DMA实现多通道的数据采集(设置阀值,驱动风扇转动,并可手动按键改变阀值,并在屏幕显示变化;通过NB模块上传数据至有人云)
//mq2 mq7 mq135的采集
void ADC1_Config(void)
{
GPIO_InitTypeDef GPIO_Struct = {0};
ADC_InitTypeDef ADC_Struct = {0};
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_Struct.GPIO_Mode = GPIO_Mode_AIN;//模拟输入
GPIO_Struct.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_6|GPIO_Pin_7;
GPIO_Struct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_Struct);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//设置ADC时钟 72/6<14
ADC_Struct.ADC_Mode = ADC_Mode_Independent; //独立工作模式
ADC_Struct.ADC_ContinuousConvMode = ENABLE;//连续模式
ADC_Struct.ADC_ScanConvMode = ENABLE; //多通道模式
ADC_Struct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //转换由软件触发启动
ADC_Struct.ADC_DataAlign = ADC_DataAlign_Right;//右对齐
ADC_Struct.ADC_NbrOfChannel = 3;//规定了顺序进行规则转换的 ADC 通道的数目
ADC_Init(ADC1,&ADC_Struct);
//设置指定 ADC 的规则组通道,设置它们的转化顺序和采样时间
//MQ2
ADC_RegularChannelConfig(ADC1,ADC_Channel_1,1,ADC_SampleTime_239Cycles5);
//MQ7
ADC_RegularChannelConfig(ADC1,ADC_Channel_6,2,ADC_SampleTime_239Cycles5);
//MQ135
ADC_RegularChannelConfig(ADC1,ADC_Channel_7,3,ADC_SampleTime_239Cycles5);
ADC_DMACmd(ADC1,ENABLE);
DMA_Config();
ADC_Cmd(ADC1,ENABLE);
//校准:减小误差
ADC_ResetCalibration(ADC1);//重置寄存器
while(ADC_GetResetCalibrationStatus(ADC1)==SET)//等待重置完成
{}
ADC_StartCalibration(ADC1);//启动校准,用校准寄存器 校准 ADC1
while(ADC_GetCalibrationStatus(ADC1)==SET)//等待校准完成
{}
ADC_SoftwareStartConvCmd(ADC1,ENABLE); //启动转换 使能或者失能指定的 ADC 的软件转换启动功能
}
u16 DMA_buf[3]={0};
void DMA_Config(void)
{
DMA_InitTypeDef DMA_Struct={0};
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE); //开启DMA时钟
DMA_Struct.DMA_PeripheralBaseAddr =(u32) &ADC1->DR; //定义DMA外设基地址
DMA_Struct.DMA_DIR = DMA_DIR_PeripheralSRC; //外设作为数据传输的来源
DMA_Struct.DMA_BufferSize = 3; //地址递增两次(单位为字宽)
DMA_Struct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变
DMA_Struct.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址寄存器递增
DMA_Struct.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //数据宽度为16位
DMA_Struct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //数据宽度为16位
DMA_Struct.DMA_Mode = DMA_Mode_Circular; //工作在循环缓存模式
DMA_Struct.DMA_Priority = DMA_Priority_High;//优先级
DMA_Struct.DMA_M2M = DMA_M2M_Disable;//DMA通道没有设置为内存到内存传输
DMA_Struct.DMA_MemoryBaseAddr = (u32)&DMA_buf[0]; //内存基地址
DMA_Init(DMA1_Channel1,&DMA_Struct);
DMA_Cmd(DMA1_Channel1,ENABLE);
}
//不采用DMA的多通道采集方法
//u16 ADC_Result(u8 ADC_Channel_x)
//{
// u16 ADC_val = ADC_GetConversionValue(ADC1); //返回最近一次 ADCx 规则组的转换结果
// ADC_RegularChannelConfig(ADC1,ADC_Channel_x,1,ADC_SampleTime_239Cycles5);
// ADC_SoftwareStartConvCmd(ADC1,ENABLE); //启动转换 使能或者失能指定的 ADC 的软件转换启动功能
// while((ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC))==RESET);
//
// //float ADC_Cha = (ADC_val*3.3/4096);
// //printf("ADC_val == %d\r\n",ADC_val);
// return ADC_val;
//}
2:将按键连接PB引脚,通过外部终端配置实现部分功能

3、在进行有人云端链接之前,需要先再云端添加设备模板、创建设备。有人透传云

在通信过程中,由底层开发板采集数据,并将数据封装成MODBUS-RTU格式,通过串口发送给NB模块,然后NB模块将数据上传到云端(创建模板时选择了MODBUS-RTU格式),NB模块是直连有人云的,在这里,我们访问云端,并将数据写入到云端的寄存器中。
在云端设置完成后,NB模块主动发送数据、或重新上电之后即可上线。因为在测试阶段已经保证了设备是正常工作的,在这里我们直接尝试上传数据了。
在上传数据时,有人云平台支持MODBUS-RTU协议,我们只需要将采集的数据进行封装,然后将数据通过串口发送给NB模块。数据上传成功后,可以在设备概况、监控大屏或者云组态当中查看数据内容、上传时间、异常信息、设备上下线等。
通信格式: 设备号 功能码 起始地址 寄存器数量 数据长度 数据块(寄存器) 校验(CRC)
#include "nbiot.h"
/*
buf[0] :温度
buf[1] :湿度
buf[2] :光照强度
buf[3] :烟雾浓度
*/
void NB_Send_IOT1(uint16_t buf[4])
{
char sendbuf[256]={0};
char sendbuf1[256]={0};
uint8_t tmpbuf[64]={0};
uint16_t CRC_Tmp;
tmpbuf[0]=0x01;//从机地址
tmpbuf[1]=0x46;//操作码
tmpbuf[2]=0x00;
tmpbuf[3]=0x00;//寄存器起始地址
tmpbuf[4]=0x00;
tmpbuf[5]=0x04;//寄存器数量
tmpbuf[6]=0x08;//字节数 = 寄存器数量 * 2
tmpbuf[7]=(buf[0]>>8);
tmpbuf[8]=(buf[0]&0xFF);//高位清零
tmpbuf[9]=(buf[1]>>8);
tmpbuf[10]=(buf[1]&0xFF);
tmpbuf[11]=(buf[2]>>8);
tmpbuf[12]=(buf[2]&0xFF);
tmpbuf[13]=(buf[3]>>8);
tmpbuf[14]=(buf[3]&0xFF);
CRC_Tmp=CRC_16_Tab(tmpbuf,15);
tmpbuf[15]=(CRC_Tmp>>8);
tmpbuf[16]=(CRC_Tmp&0xFF);
//把16进制数据转换为字符串,放入sendbuf
SIM7020_Hex_to_Str((char *)tmpbuf,17,sendbuf,256);
//拼接字符串,拼接成上述格式
//设备号 功能码 起始地址 寄存器数量 数据长度 数据块(寄存器) 校验(CRC)
strcpy((char *)tmpbuf,"AT+NMGS=17,");
strcat(sendbuf1,(char *)tmpbuf);
strcat(sendbuf1,sendbuf);
strcat(sendbuf1,"\r\n");
NB_SendString((char *)sendbuf1);
//Send_String_NBlot((uint8_t *)sendbuf1);
printf("发送内容= %s\r\n",sendbuf1);
}
/***************************************************************************/
//转化数据,转成16进制字符串
/*
char *data :数据来源
int data_len :数据长度
char *out :存储地址
int out_len :存储地址长度
*/
void SIM7020_Hex_to_Str(char *data, int data_len, char *out, int out_len)
{
char temp[2];
int i;
memset(out,0,out_len); //清空缓冲区
for(i=0;i<data_len;i++)
{ //for循环
sprintf(temp,"%02X",data[i]); //转化数据,转成16进制字符串
strcat(out,temp); //追加到out缓冲区
}
}

上传后:可以发现,数据已经改变
也可以在“监控大屏”中左侧选择设备、右侧查看实时数据,点击变量可以下发数据,或控制指令,完成数据指令下发。
在数据或指令下发时,云端下发指令也是MODBUS-RTU格式,以03功能码为例:云端下发读保持寄存器指令(03功能码),通过 UDP链接传输到我们的NB模块,然后NB模块将相应的指令转到我们的设备串口,在开发板上我们可以检测串口的接收,在串口接收数据完成后,将所接收的数据,按照MODBUS协议进行解析,如果下发的为03码,则参照03功能码的响应方式对云端进行数据响应。如果是其他功能码,则根据需求进行解析。
程序资料已经上传到资源可以下载。
stm32c8t6+dht11+MQ系列环境检测模块+oled显示屏(基于物联网的家庭环境检测系统设计)-智能家居文档类资源-CSDN下载
如果这篇博客对你有帮助,给博主一个免费的点赞或者评论收藏以示鼓励呀~感谢!😘😘😘
有任何问题可以评论区留言~🤞🤞🤞
假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于
作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代
我得到了一个包含嵌套链接的表单。编辑时链接字段为空的问题。这是我的表格:Editingkategori{:action=>'update',:id=>@konkurrancer.id})do|f|%>'Trackingurl',:style=>'width:500;'%>'Editkonkurrence'%>|我的konkurrencer模型:has_one:link我的链接模型:classLink我的konkurrancer编辑操作:defedit@konkurrancer=Konkurrancer.find(params[:id])@konkurrancer.link_attrib
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
我有一个包含模块的模型。我想在模块中覆盖模型的访问器方法。例如:classBlah这显然行不通。有什么想法可以实现吗? 最佳答案 您的代码看起来是正确的。我们正在毫无困难地使用这个确切的模式。如果我没记错的话,Rails使用#method_missing作为属性setter,因此您的模块将优先,阻止ActiveRecord的setter。如果您正在使用ActiveSupport::Concern(参见thisblogpost),那么您的实例方法需要进入一个特殊的模块:classBlah
所以我在关注Railscast,我注意到在html.erb文件中,ruby代码有一个微弱的背景高亮效果,以区别于其他代码HTML文档。我知道Ryan使用TextMate。我正在使用SublimeText3。我怎样才能达到同样的效果?谢谢! 最佳答案 为SublimeText安装ERB包。假设您安装了SublimeText包管理器*,只需点击cmd+shift+P即可获得命令菜单,然后键入installpackage并选择PackageControl:InstallPackage获取包管理器菜单。在该菜单中,键入ERB并在看到包时选择
我试图在索引页中创建一个超链接,但它没有显示,也没有给出任何错误。这是我的index.html.erb代码。ListingarticlesTitleTextssss我检查了我的路线,我认为它们也没有问题。PrefixVerbURIPatternController#Actionwelcome_indexGET/welcome/index(.:format)welcome#indexarticlesGET/articles(.:format)articles#indexPOST/articles(.:format)articles#createnew_articleGET/article
我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("
我是rails的新手,想在form字段上应用验证。myviewsnew.html.erb.....模拟.rbclassSimulation{:in=>1..25,:message=>'Therowmustbebetween1and25'}end模拟Controller.rbclassSimulationsController我想检查模型类中row字段的整数范围,如果不在范围内则返回错误信息。我可以检查上面代码的范围,但无法返回错误消息提前致谢 最佳答案 关键是您使用的是模型表单,一种显示ActiveRecord模型实例属性的表单。c
我刚刚被困在这个问题上一段时间了。以这个基地为例:moduleTopclassTestendmoduleFooendend稍后,我可以通过这样做在Foo中定义扩展Test的类:moduleTopmoduleFooclassSomeTest但是,如果我尝试通过使用::指定模块来最小化缩进:moduleTop::FooclassFailure这失败了:NameError:uninitializedconstantTop::Foo::Test这是一个错误,还是仅仅是Ruby解析变量名的方式的逻辑结果? 最佳答案 Isthisabug,or