• [知识分享] 物联网设备上云难?华为云IoT帮你一键完成模型定义,快速在线调试设备
    摘要:在不到3分钟的操作里,不仅完成了一款智慧烟感设备在云端的模型定义,还通过在线调试了解到了设备和远端通信的过程。本文分享自华为云社区《物联网设备上云难?华为云IoT帮你一键完成模型定义,快速在线调试设备》,作者:华为IoT云服务 。物联网,即字面意思,让“物”连上网络,比如,手机需要联网,家里的水表、电表、烟雾感应器等也都可以联网。完整的物联网架构一般分为感知层 - 平台层 - 应用层。在”物”联网的过程中,感知层,也就是“物”负责信息收集和信号处理,通过感知识别技术,让物品“开口说话、发布信息”。应用层是物联网系统面向用户的客户端,通过对感知层收集到的信息进行分析、处理,挖掘有价值的数据,为用户提供丰富的特定服务。而华为云IoT作为中间的平台层,在整个物联网体系架构中起着承上启下的关键作用,它实现了感知层设备的“管、控、营”一体化,为应用层提供统一的数据接口,构建了设备和业务的端到端通道。物联网设备模型碎片,系统构建链条厂,集成成本高通常“物”要联网上云,需要先在物联网平台构建一款设备的抽象模型,使平台能够理解该款设备支持的服务、属性、命令等信息。比如,一个智慧烟雾感应器的模型里,包含以下信息:服务类型描述电池用于烟感设备的电池参数,包含电量、电压、阈值等定连接用于定义设备连接状态,包含连接次数、时间等设备信息用于描述设备基本信息,包含设备类型、名称、编号等传输规则用于定义设备的传输规则,必要的话需要定义命令。……是不是觉得很复杂?别怕,现在以上信息已经可以一键搞定了!在华为云IoT平台上,提供了标准模型和厂商模型。标准模型遵循行业标准的产品模型,适用行业内绝大部分厂商设备,而厂商模型针对设备类型发布的产品模型,适用于用行业内少量厂家设备。我们可以根据需求选择相应的产品模型,一键导入到我们自己的产品中,然后根据实际情况进行增删调整属性即可。接下来以实际体验步骤,来带大家感受一下:环境准备注册华为云账号、实名认证若您还没有通过实名认证的账号,可前往账号中心,完成实名认证。如果您已拥有华为账号且已通过实名认证,可直接 点击体验。开始体验1、进入体验页面,点击右上角“创建产品”*Tips:产品模型 用于描述设备具备的能力和特性。开发者通过定义产品模型,在物联网平台构建一款设备的抽象模型,使平台理解该款设备支持的服务、属性、命令等信息,如颜色、开关等。2、在弹窗中自定义一个产品名称,比如本次以烟感设备为例,我们定义了“烟感8号”产品,设备类型为“烟雾感应器”,点击确定后,即可成功创建产品,再点击“查看详情”进入下一步。3、在以下页面,华为云IoT平台提供了多种定义产品模型的方法,您可以根据自己需求,选择对应的方法定义产品模型。为了快速体验,我们建议您可以选择“导入库模型”,使用平台预置的产品模型,快速完成产品开发。*Tips:当前平台提供了标准模型和厂商模型。标准模型遵循行业标准的产品模型,适用行业内绝大部分厂商设备,而厂商模型针对设备类型发布的产品模型,适用于用行业内少量厂家设备。您可以根据实际需求选择相应的产品模型。4、选择“SmokeDetector”,即烟雾感应器的模型,点击“确定”进入下一步。5、在产品详情页里,可以通过切换“服务列表”中的不同服务,查看每个服务中包含的属性参数,平台还支持根据实际需求修改相应参数。至此,您已完成产品的定义!点击“在线调试”进入下一步。*Tips:“服务”是描述设备具备的业务能力。将设备业务能力拆分成若干个服务后,再定义每个服务具备的属性、命令以及命令的参数。6、在调试页面,点击“新增测试设备”,在弹窗中选择“虚拟设备”,点击确定后即可创建一个用于模拟与云端进行通信的虚拟设备。7、点击右边“调试”,进入调试页面。8、尝试在应用模拟器中,模拟应用端向烟感设备下发命令,在valua中输入“1”,点击发送,可以在调试输出页面看到应用与设备之间通信的过程。9、也可以在右上角切换到设备模拟器,模拟设备向云端平台上报数据,在电量batteryLevel中输入数值,点击发送,可以在调试输出页面看到应用与设备之间通信的过程。10、左下角勾选“自动发送”,并设置自动发送的周期,比如“5秒”,模拟器将模拟设备,每5秒向平台上报一次数据,用于实时监控设备的状态。 恭喜您,到这里我们已经完成了物联网设备注册以及在线调试设备的体验!在不到3分钟的操作里,我们不仅完成了一款智慧烟感设备在云端的模型定义,还通过在线调试了解到了设备和远端通信的过程。相信通过短短几步的操作,能让大家对物联网设备连接到云平台的步骤及相关概念已经有了很全面的了解,欢迎大家在前往华为云IoT平台上尝试更多的物联网开发体验!PS:华为云开发者社区体验活动火热进行中,欢迎来体验各种云上开发,还可领取丰厚奖品哟!点击参与活动
  • [问题求助] 请问怎么用python监听指定端口的消息呀?
    只找到了这个,请问是直接把这个创建一个.py文件,放到服务器的项目代码中吗?
  • [问题求助] 应用服务器接入华为物联网平台
    我部署了一个网站到华为云服务器上,可正常访问,现在想要将设备上报到华为云平台的信息自动转发到服务器中指定的url,现在采用HTTPS,证书也已经制作完毕并且上传,但是,在测试连接时,发现无法连接,返回502,请问是怎么回事呀?
  • [技术干货] 智慧园区统一接入之HLink网关接口分类
    接口分类如表1所示:表1 接口分类说明 编号说明Rx-AHLink对行业APP提供的开放接口,包括智能终端内的控制软件APP、边缘网关侧部署的APP、以及云平台上部署的APP等,不同领域的行业伙伴主要是基于该接口进行APP开发,并可实现互联互通。Rx-LHLink部件间的通信接口,行业APP开发者可以不用关心这部分接口内容。Rx-S行业APP间的业务控制、数据交互逻辑接口,该接口是虚拟接口,即APP间并不是直接通信的,而是通过Rx-A接口实现APP的间接通信。该逻辑接口跟行业的业务属性强相关,不同的行业应用其数据内容不一样,如:BA业务、FA业务等。本文档着重定义图中的Rx-A接口,同时也会以BA业务为例给出Rx-S的参考接口,对Rx-L接口只做简单说明。接口说明如表2所示:表2 接口说明接口名称接口类型涉及的子系统职责R1-AC语言API SDKHLink Device、Terminal APPDevice注册认证、Device控制处理、Device事件/数据上报、Device维护&诊断。R2-AMQTT over TCPHLink Gateway、Edge APPGateway和Device信息查询、向Gateway发送命令、接收Gateway返回结果、接收Gateway上报事件/数据。R3-AMQS over http(s)、REST APICloud APP、数字平台(ROMA)设备管理、设备查询、数据采集、命令下发。R1-LCoAP over DTLSHLink Device、HLink GatewayGateway发现、Device接入认证、Device控制、Device事件/数据上报、Device维护&诊断。R2-LMQTT over TLSIoT平台(新基点IoT,ROMA Link,OC)、HLink GatewayGateway注册、子设备管理、数据采集、命令下发。R1-S参考Rx-A接口Edge APP、Terminal APPDevice控制、Device事件处理、Device数据采集。R2-S参考Rx-A接口Cloud APP、Terminal APPDevice控制、Device事件处理、Device数据采集。R3-S参考Rx-A接口Cloud APP、Edge APP配置、数据同步等业务定义操作。R4-S参考Rx-A接口Edge APP之间配置、数据同步等业务定义操作。
  • [技术干货] 基于STM32设计的实时心率检测仪
    一、开发环境介绍主控芯片: STM32F103ZET6代码编程软件: keil5心率检测模块: PulseSensorWIFI模块: ESP8266 --可选的。直接使用串口有线传输给上位机也可以。上位机: C++(QT) 设计的。 支持PC机电脑、Android手机显示。与上位机的传输协议: 支持串口传输、WIFI网络传输两种。 如果是PC就可以直接连接串口传输数据,如果不方便可以直接通过WIFI---TCP协议传输。二、PulseSensor心率模块介绍PulseSensor 是一款用于脉搏心率测量的光电反射式模拟传感器。可以将其佩戴于手指、耳垂、手腕等处,通过杜邦线--导线将引脚连接到单片机,可将采集到的模拟信号传输给单片机,单片机配置ADC用来转换为数字信号,再通过单片机简单计算后就可以得到心率数值;为了方便联动健康管理系统,也方便自己了解自己的心率,可将脉搏波形通过串口、WIFI等方式上传到电脑、手机显示波形,然后根据提前配置的参数,结合算法确定是否正常。PulseSensor 是一款开源硬件, 目前国外官网上已有其对应的单片机程序,也附带有对应的上位机Processing 程序, 比较适用于心率方面的科学研究和教学演示,也非常适合用于二次开发;上位机也可以自己开发,根据自己的需求定制,达到自己想要的功能。传感器的接口一共 3 个, 其中标有S的为模拟信号输出线 标有+的为电源输入线(中间); 标有-的为地线。总结一下: S → 脉搏信号输出(要接单片机 AD 接口) \+ → 5v(或 3.3v)电源输入 \- → GND 地传感器的硬件参数介绍:电路板直径: 16mm 电路板厚度: 1.2mm LED 峰值波长: 515nm 供电电压: 3.35v 检测信号类型:光反射信号(PPG) 输出信号类型:模拟信号 信号放大倍数: 330 倍 输出信号大小: 0VCC 电流大小: ~4ma(5v 下)传统的测量方法介绍:传统的脉搏测量方法主要有三种:一是从心电信号中提取;二是从测量血压时压力传感器测到的波动来计算脉率;三是光电容积法。前两种方法提取信号都会限制病人的活动,如果长时间使用会增加病人生理和心理上的不舒适感。而光电容积法脉搏测量作为监护测量中最普遍的方法之一,其具有方法简单、佩戴方便、可靠性高等特点。光电容积法的基本原理是利用人体组织在血管搏动时造成透光率不同来进行脉搏测量的。其使用的传感器由光源和光电变换器两部分组成,通过绑带或夹子固定在病人的手指或耳垂上。光源一般采用对动脉血中氧和血红蛋白有选择性的一定波长(500nm~700nm)的发光二极管。当光束透过人体外周血管,由于动脉搏动充血容积变化导致这束光的透光率发生改变,此时由光电变换器接收经人体组织反射的光线,转变为电信号并将其放大和输出。由于脉搏是随心脏的搏动而周期性变化的信号,动脉血管容积也周期性变化,因此光电变换器的电信号变化周期就是脉搏率。根据相关文献和实验结果, 560nm 波长左右的波可以反映皮肤浅部微动脉信息,适合用来提取脉搏信号。本传感器采用了峰值波长为 515nm 的绿光 LED,型号为 AM2520,而光接收器采用了 APDS-9008, 这是一款环境光感受器,感受峰值波长为 565nm,两者的峰值波长相近,灵敏度较高。此外,由于脉搏信号的频带一般在 0.05~200Hz 之间, 信号幅度均很小,一般在毫伏级水平,容易受到各种信号干扰。在传感器后面使用了低通滤波器和由运放 MCP6001 构成的放大器,将信号放大了 330 倍,同时采用分压电阻设置直流偏置电压为电源电压的 1/2,使放大后的信号可以很好地被单片机的 AD 采集到。整个心率传感器的结构如下图:由于传感器使用的是固定倍数的放大器, 而人体生理信号是微弱信号,细微的差异会导致放大后的信号产生巨大的差别。 所以下图的示波器显示的波形只是理想情况下的波形,每个人的实际效果会略有区别。三、STM32的控制代码STM32的采集代码比较简单,因为就只需要配置对应引脚的ADC功能采集即可。可以采集10次,去掉最大值最小值取平均值,拿到最终结果再传递给上位机显示。3.1 ADC的配置代码/* 函数功能: 初始化ADC1 硬件连接: PA1 --ADC1的通道1 配置的模式:模拟输入 */ void ADC1_Init(void) { /*1. 配置GPIO口*/ RCC->APB2ENR|=1<<2; //开启PA时钟 GPIOA->CRL&=0xFFFFFF0F; GPIOA->CRL|=0x00000000; /*2. 配置ADC相关寄存器*/ RCC->APB2ENR|=1<<9;//开启ADC1时钟 RCC->APB2RSTR|=1<<9; //开启ADC1复位时钟 RCC->APB2RSTR&=~(1<<9);//关闭ADC1复位时钟 RCC->CFGR&=~(0x3<<14); //清除ADC的时钟配置 RCC->CFGR|=0x2<<14; //配置6分频 ADC1->CR2|=1<<20; //开启外部事件转换 ADC1->CR2|=0x7<<17; //SW开关方式控制ADC转换(作为外部事件) ADC1->SMPR2|=0x7<<3; //配置通道1的采样时间 ADC1->CR2|=1<<0;//开启ADC并启动转换 ADC1->CR2|=1<<3;//开启ADC校准初始化 while(ADC1->CR2&1<<3){}//等待初始化完成 ADC1->CR2|=1<<2;//开启ADC校准 while(ADC1->CR2&1<<2){} //等待ADC校准完成 } /* 函数功能: 根据传入的通道编号获取一次该通道的ADC值 */ u16 ADC1_GetData(u8 ch) { ADC1->SQR3&=0xFFFFFFE0; //0xE0-->11100000 //清除原来的通道编号 ADC1->SQR3|=ch<<0; //配置现在即将转换的通道号 ADC1->CR2|=1<<22; //开启一次ADC规则通道转换 while(!(ADC1->SR&1<<1)){} //等待转换完成 return ADC1->DR; //读出ADC的结果值 }3.2 ESP8266 WIFI 配置代码#include "esp8266.h" /* 函数功能:向ESP82668266发送命令 函数参数: cmd:发送的命令字符串 ack:期待的应答结果,如果为空,则表示不需要等待应答 waittime:等待时间(单位:10ms) 返 回 值: 0,发送成功(得到了期待的应答结果) 1,发送失败 */ u8 ESP8266_SendCmd(u8 *cmd,u8 *ack,u16 waittime) { u8 res=0; USART3_RX_STA=0; USART3_RX_CNT=0; UsartStringSend(USART3,cmd);//发送命令 if(ack&&waittime) //需要等待应答 { while(--waittime) //等待倒计时 { DelayMs(10); if(USART3_RX_STA)//接收到期待的应答结果 { if(ESP8266_CheckCmd(ack)) { res=0; //printf("cmd->ack:%s,%s\r\n",cmd,(u8*)ack); break;//得到有效数据 } USART3_RX_STA=0; USART3_RX_CNT=0; } } if(waittime==0)res=1; } return res; } /* 函数功能:ESP8266发送命令后,检测接收到的应答 函数参数:str:期待的应答结果 返 回 值:0,没有得到期待的应答结果 其他,期待应答结果的位置(str的位置) */ u8* ESP8266_CheckCmd(u8 *str) { char *strx=0; if(USART3_RX_STA) //接收到一次数据了 { USART3_RX_BUF[USART3_RX_CNT]=0;//添加结束符 strx=strstr((const char*)USART3_RX_BUF,(const char*)str); //查找是否应答成功 //printf("RX=%s",USART3_RX_BUF); } return (u8*)strx; } /* 函数功能:向ESP8266发送指定数据 函数参数: data:发送的数据(不需要添加回车) ack:期待的应答结果,如果为空,则表示不需要等待应答 waittime:等待时间(单位:10ms) 返 回 值:0,发送成功(得到了期待的应答结果)luojian */ u8 ESP8266_SendData(u8 *data,u8 *ack,u16 waittime) { u8 res=0; USART3_RX_STA=0; UsartStringSend(USART3,data);//发送数据 if(ack&&waittime) //需要等待应答 { while(--waittime) //等待倒计时 { DelayMs(10); if(USART3_RX_STA)//接收到期待的应答结果 { if(ESP8266_CheckCmd(ack))break;//得到有效数据 USART3_RX_STA=0; USART3_RX_CNT=0; } } if(waittime==0)res=1; } return res; } /* 函数功能:ESP8266退出透传模式 返 回 值:0,退出成功; 1,退出失败 */ u8 ESP8266_QuitTrans(void) { while((USART3->SR&0X40)==0); //等待发送空 USART3->DR='+'; DelayMs(15); //大于串口组帧时间(10ms) while((USART3->SR&0X40)==0); //等待发送空 USART3->DR='+'; DelayMs(15); //大于串口组帧时间(10ms) while((USART3->SR&0X40)==0); //等待发送空 USART3->DR='+'; DelayMs(500); //等待500ms return ESP8266_SendCmd("AT\r\n","OK",20);//退出透传判断. } /* 函数功能:获取ESP8266模块的连接状态 返 回 值:0,未连接;1,连接成功. */ u8 ESP8266_ConstaCheck(void) { u8 *p; u8 res; if(ESP8266_QuitTrans())return 0; //退出透传 ESP8266_SendCmd("AT+CIPSTATUS\r\n",":",50); //发送AT+CIPSTATUS指令,查询连接状态 p=ESP8266_CheckCmd("+CIPSTATUS\r\n:"); res=*p; //得到连接状态 return res; } /* 函数功能:获取ip地址 函数参数:ipbuf:ip地址输出缓存区 */ void ESP8266_GetWanip(u8* ipbuf) { u8 *p,*p1; if(ESP8266_SendCmd("AT+CIFSR\r\n","OK",50))//获取WAN IP地址失败 { ipbuf[0]=0; return; } p=ESP8266_CheckCmd("\""); p1=(u8*)strstr((const char*)(p+1),"\""); *p1=0; sprintf((char*)ipbuf,"%s",p+1); }四、QT设计的上位机代码4.1 软件运行效果图软件有两个版本: 1. 网络版本 2. 串口版本网络版本主要是通过TCP协议传输数据显示,串口版本直接通过串口传输。4.2 widget.cpp代码代码较多,这里就主UI的部分代码。#include "widget.h" #include "ui_widget.h" #define AppFontName "Microsoft YaHei" #define AppFontSize 9 #define TextColor QColor(255,255,255) #define Plot_NoColor QColor(0,0,0,0) //曲线1的颜色 #define HeartRate_Plot_DotColor QColor(236,110,0) #define HeartRate_Plot_LineColor QColor(246,98,0) #define HeartRate_Plot_BGColor QColor(246,98,0,80) //曲线2的颜色 #define HeartRate_Plot_DotColor_2 Qt::blue #define HeartRate_Plot_LineColor_2 Qt::blue #define HeartRate_Plot_BGColor_2 Qt::blue #define TextWidth 1 #define LineWidth 2 #define DotWidth 5 //一个刻度里的小刻度数量--太小的话显示的时间会重叠 #define HeartRate_Plot_Count 5 //Y轴最大范围值 #define HeartRate_Plot_MaxY 3000 /* * 设置QT界面的样式 */ void Widget::SetStyle(const QString &qssFile) { QFile file(qssFile); if (file.open(QFile::ReadOnly)) { QString qss = QLatin1String(file.readAll()); qApp->setStyleSheet(qss); QString PaletteColor = qss.mid(20,7); qApp->setPalette(QPalette(QColor(PaletteColor))); file.close(); } else { qApp->setStyleSheet(""); } } Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui->setupUi(this); /*服务器线程*/ //开始信号 connect(this,SIGNAL(StartServerThread()),&tcp_server_class,SLOT(run())); //日志信号 connect(&tcp_server_class,SIGNAL(LogSend(QString)),this,SLOT(Log_Display(QString))); //移动到线程 tcp_server_class.moveToThread(&tcp_server_thread); tcp_server_thread.start(); //启动线程 StartServerThread(); //创建服务器 this->setWindowTitle("万邦易嵌-健康监控管家"); //波形图界面初始化 InitForm(); InitPlot(); HeartRate_InitPlot(); HeartRate_LoadPlot(); SetStyle(":/blue.css"); //开始加载数据 plot_timer->start(100); } Widget::~Widget() { delete ui; } //日志显示 void Widget::Log_Display(QString text) { QPlainTextEdit *plainTextEdit_log=ui->plainTextEdit_log; //设置光标到文本末尾 plainTextEdit_log->moveCursor(QTextCursor::End, QTextCursor::MoveAnchor); //当文本数量超出一定范围就清除 if(plainTextEdit_log->toPlainText().size()>1024*4) { plainTextEdit_log->clear(); } plainTextEdit_log->insertPlainText(text); //移动滚动条到底部 QScrollBar *scrollbar = plainTextEdit_log->verticalScrollBar(); if(scrollbar) { scrollbar->setSliderPosition(scrollbar->maximum()); } } //查看服务器状态 void Widget::on_toolButton_server_stat_clicked() { QString text="TCP服务器IP地址列表:\n"; QList list = QNetworkInterface::allAddresses(); for(int i=0;isocketDescriptor()==-1) { text+="设备未连接\n"; } else { text+="设备连接成功\n"; } } else { text+="设备未连接\n"; } text+="数据协议:\n"; text+="A:心电数据1,B:新电数据2,C:运动步数,D:运动距离,E:体表温度\n"; text+="例如: \"A:1633215,B:1833215,C:45,D:28,E:66.55\""; QMessageBox::about(this,"状态信息",text); } //窗口关闭事件 void Widget::closeEvent(QCloseEvent *event) { tcp_server_thread.quit(); tcp_server_thread.wait(); } void Widget::InitForm() { //初始化随机数种子 QTime time = QTime::currentTime(); qsrand(time.msec() + time.second() * 1000); //初始化动态曲线定时器 plot_timer = new QTimer(this); connect(plot_timer, SIGNAL(timeout()), this, SLOT(HeartRate_LoadPlot())); plots.append(ui->plot2); } void Widget::InitPlot() { //设置纵坐标名称 plots.at(0)->yAxis->setLabel("心电数据(单位:%)"); //设置纵坐标范围 plots.at(0)->yAxis->setRange(0, HeartRate_Plot_MaxY); //设置支持鼠标移动缩放波形界面 plots.at(0)->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom); //设置背景颜色 #if 1 foreach (QCustomPlot *plot, plots) { //设置字体大小 QFont font = QFont(AppFontName, AppFontSize - 2); plot->legend->setFont(font); plot->xAxis->setLabelFont(font); plot->yAxis->setLabelFont(font); plot->xAxis->setTickLabelFont(font); plot->yAxis->setTickLabelFont(font); //设置坐标颜色/坐标名称颜色 plot->yAxis->setLabelColor(TextColor); plot->xAxis->setTickLabelColor(TextColor); plot->yAxis->setTickLabelColor(TextColor); plot->xAxis->setBasePen(QPen(TextColor, TextWidth)); plot->yAxis->setBasePen(QPen(TextColor, TextWidth)); plot->xAxis->setTickPen(QPen(TextColor, TextWidth)); plot->yAxis->setTickPen(QPen(TextColor, TextWidth)); plot->xAxis->setSubTickPen(QPen(TextColor, TextWidth)); plot->yAxis->setSubTickPen(QPen(TextColor, TextWidth)); //设置画布背景色 QLinearGradient plotGradient; plotGradient.setStart(0, 0); plotGradient.setFinalStop(0, 350); plotGradient.setColorAt(0, QColor(80, 80, 80)); plotGradient.setColorAt(1, QColor(50, 50, 50)); plot->setBackground(plotGradient); //设置坐标背景色 QLinearGradient axisRectGradient; axisRectGradient.setStart(0, 0); axisRectGradient.setFinalStop(0, 350); axisRectGradient.setColorAt(0, QColor(80, 80, 80)); axisRectGradient.setColorAt(1, QColor(30, 30, 30)); plot->axisRect()->setBackground(axisRectGradient); //设置图例提示位置及背景色 plot->axisRect()->insetLayout()->setInsetAlignment(0, Qt::AlignTop | Qt::AlignRight); plot->legend->setBrush(QColor(255, 255, 255, 200)); plot->replot(); } #endif } void Widget::HeartRate_InitPlot() { plots.at(0)->addGraph(); plots.at(0)->graph(0)->setName("心电数据1"); plots.at(0)->graph(0)->setPen(QPen(HeartRate_Plot_LineColor, LineWidth)); plots.at(0)->graph(0)->setScatterStyle( QCPScatterStyle(QCPScatterStyle::ssCircle, QPen(HeartRate_Plot_DotColor, LineWidth), QBrush(HeartRate_Plot_DotColor), DotWidth)); //设置动态曲线的横坐标格式及范围 plots.at(0)->xAxis->setTickLabelType(QCPAxis::ltDateTime); plots.at(0)->xAxis->setDateTimeFormat("HH:mm:ss"); plots.at(0)->xAxis->setAutoTickStep(true); plots.at(0)->xAxis->setTickStep(0.5); plots.at(0)->xAxis->setRange(0, HeartRate_Plot_Count, Qt::AlignRight); plots.at(0)->addGraph();//相当于添加一条新的曲线 plots.at(0)->graph(1)->setName("心电数据2"); plots.at(0)->graph(1)->setPen(QPen(HeartRate_Plot_LineColor_2, LineWidth)); plots.at(0)->graph(1)->setScatterStyle( QCPScatterStyle(QCPScatterStyle::ssCircle, QPen(HeartRate_Plot_DotColor_2, LineWidth), QBrush(HeartRate_Plot_DotColor_2), DotWidth)); //设置是否需要显示曲线的图例说明 foreach (QCustomPlot *plot, plots) { plot->legend->setVisible(true); plot->replot(); } //得到数据指针 mData_0 = plots.at(0)->graph(0)->data(); mData_1 = plots.at(0)->graph(1)->data(); } void addToDataBuffer(QCPDataMap *mData,double x, double y) { QCPData newData; newData.key = x; newData.value = y; mData->insert(x, newData); } //加载曲线数据 void Widget::HeartRate_LoadPlot() { int i; bool flag=false; for(i=0;i<5;i++) { //得到秒单位的时间 HeartRate_plot_key = QDateTime::currentDateTime().toMSecsSinceEpoch() / 1000.0; //心电数据1 HeartRate_plot_value=uart_queue_data.read_queueA(); if(HeartRate_plot_value>0) { flag=true; addToDataBuffer(mData_0,HeartRate_plot_key,HeartRate_plot_value); } //心电数据2 HeartRate_plot_value=uart_queue_data.read_queueB(); if(HeartRate_plot_value>0) { flag=true; addToDataBuffer(mData_1,HeartRate_plot_key,HeartRate_plot_value); } } if(flag) { plots.at(0)->xAxis->setRange(HeartRate_plot_key, HeartRate_Plot_Count , Qt::AlignRight); plots.at(0)->rescaleAxes(false); //设置图表完全可见 plots.at(0)->replot(); } /* A:心电数据1,B:新电数据2,C:运动步数,D:运动距离,E:体表温度 例如: "A:1633215,B:1833215,C:45,D:28,E:66.55" */ int val=uart_queue_data.read_queueC(); if(val>0) { ui->lcdNumber_bumber->display(val); } val=uart_queue_data.read_queueD(); if(val>0) { ui->lcdNumber_len->display(val); } double tmp_val=uart_queue_data.read_queueE(); if(tmp_val>0) { ui->lcdNumber_temp->display(tmp_val); } } void Widget::on_toolButton_src_data_clicked() { ui->stackedWidget->setCurrentIndex(0); } void Widget::on_toolButton_image_data_clicked() { ui->stackedWidget->setCurrentIndex(1); } void Widget::on_toolButton_clear_clicked() { mData_0->clear(); mData_1->clear(); } void Widget::on_commandLinkButton_clicked() { QDesktopServices::openUrl(QUrl("https://blog.csdn.net/xiaolong1126626497/article/details/116694318")); }
  • [问题求助] 智慧烟感模块蜂鸣器报警阈值
    智慧烟感模块蜂鸣器报警阈值是多少?如何判别报警?
  • [问题求助] 发送MQTT消息,消息显示MQTT
    通过4G CAT1模块Air724UG,利用MQTT方式连接华为云,能够收到设备发送的消息,但是收到的消息显示为“MQTT ”,应该为““MQTT“”,请教一下各位大神是怎么回事?
  • [技术干货] 基于STM32的录音机设计(STM32F103+VS1053B)
    一、环境介绍MCU: STM32F103C8T6开发软件: Keil5音频模块: VS1053B录音文件存储设备: SD卡,采用SPI协议驱动显示屏: SPI接口的0.96寸OLED代码风格: 采用寄存器编程,代码简洁、执行效率高、注释到位、移植方便。二、功能介绍这是基于STM32F103C8T6设计的录音机功能,支持的功能如下:(1). 按下按键1启动自动录音,默认为5秒录音一次,录音完毕自动保存在SD指定目录下。文件名称采用当前时间命名;音频文件格式采用WAV格式存储。(2). 按下按键2启动手动录音,按键按下之后开始录音,再次按下结束录音,录音完毕之后,文件也是一样的保存在SD卡里。(3). SD卡文件系统采用FAT32格式,STM32移植了FATFS开源文件系统对SD卡进行读写操作。(4). OLED显示屏用于显示当前录音机的状态: 空闲、录音、回放等状态。(5). 按下按键3,启动自动回放功能。自动扫描目录,按顺序播放录音文件。 技术介绍:(1). SD卡采用SPI协议驱动,因为对速度没有很高要求,SPI协议已经完全满足;如果要更高的速度,可以采用SDIO协议。(2). 音频模块采用VS1053B,这个芯片支持IIS和SPI协议。我这里采用的是SPI协议驱动,SPI比较简单,代码也好移植,可以很方便的移植到其他单片机上运行。VS1053功能比较强大,支持录音、解码播放。(3). 文件系统采用的是FATFS文件系统,这个文件系统功能比较完善,使用免费,支持FAT16、FAT32等格式。底层也比较好适配移植。当前,除了FATFS以外,还有很多其他的嵌入式文件系统可以选择,移植都大同小异。(4). OLED显示屏是0.96寸的。采用SPI协议驱动,主要是显示一些状态,SPI刷屏比较快,这款OLED也支持IIC接口。(5). VS1053模块上没有喇叭设备,可以适应耳机或者音箱听回放的录音。硬件与STM32的接线说明:OLED显示屏: D0----SCK-----PB14 D1----MOSI----PB13 RES—复位(低电平有效)—PB12 DC---数据和命令控制管脚—PB1 CS---片选引脚-----PA7VS1053: #define VS1053_DREQ PAin(11) //DREQ 数据请求 #define VS1053_RESET PAout(12) //RST 硬件复位--低电平有效 #define VS1053_XCS PAout(13) //XCS 片选--低电平有效 #define VS1053_XDCS PAout(14) //XDCS 用于数据片选、字节同步 #define VS1053_SCLK PAout(15) #define VS1053_OUTPUT PBout(3) #define VS1053_INPUT PBin(4)SD卡接口: 5V----5V GND---GND SPI1_MOSI---PA7 SPI1_MISO---PA6 SPI1_CS---PA4 SPI1_SCK--PA5三、使用的相关硬件STM32F103C8T6系统板:OLED显示屏:VS1053:SD卡卡槽: 四、操作说明开发板有一个复位键和一个K0按键。程序下载:程序支持三种模式:因为开发板只有一个K0按键,所以三种模式都是通过一个按键进行切换的。一个按键是通过按下的计数方式进行切换的,切换的顺序是自动录音模式、手动录音模式、回放模式。(1)自动录音模式:按下一次按键后,进入自动录音模式,自动录音模式下,录音5秒自动退出,退出后自动启动播放状态,就是播放刚才5秒录制的音频,播放过程中按下按键可以退出播放状态。(2)手动录音模式:第二次按下K0按键后,进入手动录音模式,手动录音模式下,可以长时间录音,如果要结束录音,按下K0按键即可结束;结束后自动启动播放状态,就是播放刚才录制的音频,播放过程中按下按键可以退出播放状态。(3)回放模式:第三次按下K0按键后,进入回放模式,自动扫描wav目录,进行顺序播放音频文件。播放过程中可以按下K0按键退出回放模式。 每次录音后的文件是存放在SD卡根目录下的wav目录下。 每个状态都会在OLED显示屏上显示 也会同时通过串口打印到串口调试助手终端。五、SD卡上存放的文件SD卡上有两个目录:font目录和wav目录。 font目录下存放16x16字库文件。 wav目录下存放录音的音频文件。六、部分源码6.1 VS1053.c 这是VS1053的驱动代码#include "vs1053b.h" /* 函数功能:移植接口--SPI时序读写一个字节 函数参数:data:要写入的数据 返 回 值:读到的数据 */ u8 VS1053_SPI_ReadWriteByte(u8 tx_data) { u8 rx_data=0; u8 i; for(i=0;i<8;i++) { VS1053_SCLK=0; if(tx_data&0x80){VS1053_OUTPUT=1;} else {VS1053_OUTPUT=0;} tx_data<<=1; VS1053_SCLK=1; rx_data<<=1; if(VS1053_INPUT)rx_data|=0x01; } return rx_data; } /* 函数功能:初始化VS1053的IO口 */ void VS1053_Init(void) { RCC->APB2ENR|=1<<0; AFIO->MAPR&=~(0x7<<24); //释放PA13/14/15 AFIO->MAPR|=0x4<<24; RCC->APB2ENR|=1<<2; RCC->APB2ENR|=1<<3; GPIOA->CRH&=0x00000FFF; GPIOA->CRH|=0x33338000; GPIOB->CRL&=0xFFF00FFF; GPIOB->CRL|=0x00083000; VS1053_SCLK=1; VS1053_XCS=1; VS1053_RESET=1; } /* 函数功能:软复位VS10XX */ void VS1053_SoftReset(void) { u8 retry=0; while(VS1053_DREQ==0); //等待软件复位结束 VS1053_SPI_ReadWriteByte(0Xff); //启动传输 retry=0; while(VS1053_ReadReg(SPI_MODE)!=0x0800) // 软件复位,新模式 { VS1053_WriteCmd(SPI_MODE,0x0804); // 软件复位,新模式 DelayMs(2);//等待至少1.35ms if(retry++>100)break; } while(VS1053_DREQ==0);//等待软件复位结束 retry=0; while(VS1053_ReadReg(SPI_CLOCKF)!=0X9800)//设置VS10XX的时钟,3倍频 ,1.5xADD { VS1053_WriteCmd(SPI_CLOCKF,0X9800); //设置VS10XX的时钟,3倍频 ,1.5xADD if(retry++>100)break; } DelayMs(20); } /* 函数 功 能:硬复位MP3 函数返回值:1:复位失败;0:复位成功 */ u8 VS1053_Reset(void) { u8 retry=0; VS1053_RESET=0; DelayMs(20); VS1053_XDCS=1;//取消数据传输 VS1053_XCS=1; //取消数据传输 VS1053_RESET=1; while(VS1053_DREQ==0&&retry<200)//等待DREQ为高 { retry++; DelayUs(50); } DelayMs(20); if(retry>=200)return 1; else return 0; } /* 函数功能:向VS10XX写命令 函数参数: address:命令地址 data :命令数据 */ void VS1053_WriteCmd(u8 address,u16 data) { while(VS1053_DREQ==0); //等待空闲 VS1053_XDCS=1; VS1053_XCS=0; VS1053_SPI_ReadWriteByte(VS_WRITE_COMMAND);//发送VS10XX的写命令 VS1053_SPI_ReadWriteByte(address); //地址 VS1053_SPI_ReadWriteByte(data>>8); //发送高八位 VS1053_SPI_ReadWriteByte(data); //第八位 VS1053_XCS=1; } /* 函数参数:向VS1053写数据 函数参数:data:要写入的数据 */ void VS1053_WriteData(u8 data) { VS1053_XDCS=0; VS1053_SPI_ReadWriteByte(data); VS1053_XDCS=1; } /* 函数功能:读VS1053的寄存器 函数参数:address:寄存器地址 返回值:读到的值 */ u16 VS1053_ReadReg(u8 address) { u16 temp=0; while(VS1053_DREQ==0);//非等待空闲状态 VS1053_XDCS=1; VS1053_XCS=0; VS1053_SPI_ReadWriteByte(VS_READ_COMMAND);//发送VS10XX的读命令 VS1053_SPI_ReadWriteByte(address); //地址 temp=VS1053_SPI_ReadWriteByte(0xff); //读取高字节 temp=temp<<8; temp+=VS1053_SPI_ReadWriteByte(0xff); //读取低字节 VS1053_XCS=1; return temp; } /* 函数功能:读取VS1053的RAM 函数参数:addr:RAM地址 返 回 值:读到的值 */ u16 VS1053_ReadRAM(u16 addr) { u16 res; VS1053_WriteCmd(SPI_WRAMADDR, addr); res=VS1053_ReadReg(SPI_WRAM); return res; } /* 函数功能:写VS1053的RAM 函数参数: addr:RAM地址 val:要写入的值 */ void VS1053_WriteRAM(u16 addr,u16 val) { VS1053_WriteCmd(SPI_WRAMADDR,addr); //写RAM地址 while(VS1053_DREQ==0); //等待空闲 VS1053_WriteCmd(SPI_WRAM,val); //写RAM值 } /* 函数参数:发送一次音频数据,固定为32字节 返 回 值:0,发送成功 1,本次数据未成功发送 */ u8 VS1053_SendMusicData(u8* buf) { u8 n; if(VS1053_DREQ!=0) //送数据给VS10XX { VS1053_XDCS=0; for(n=0;n<32;n++) { VS1053_SPI_ReadWriteByte(buf[n]); } VS1053_XDCS=1; }else return 1; return 0;//成功发送了 } /* 函数参数:发送一次音频数据,固定为32字节 返 回 值:0,发送成功 1,本次数据未成功发送 */ void VS1053_SendMusicByte(u8 data) { u8 n; while(VS1053_DREQ==0){} VS1053_XDCS=0; VS1053_SPI_ReadWriteByte(data); VS1053_XDCS=1; } /* 函数功能:设定VS1053播放的音量 函数参数:volx:音量大小(0~254) */ void VS1053_SetVol(u8 volx) { u16 volt=0; //暂存音量值 volt=254-volx; //取反一下,得到最大值,表示最大的表示 volt<<=8; volt+=254-volx; //得到音量设置后大小 VS1053_WriteCmd(SPI_VOL,volt);//设音量 } /*--------------------------------------录音功能-----------------------------------------------------*/ /* 函数功能:vs10xx装载patch 函数参数: patch:patch首地址 len :patch长度 */ void VS1053_LoadPatch(u16 *patch,u16 len) { u16 i; u16 addr, n, val; for(i=0;iriff.ChunkID=0X46464952; //"RIFF" wavhead->riff.ChunkSize=0; //还未确定,最后需要计算 wavhead->riff.Format=0X45564157; //"WAVE" wavhead->fmt.ChunkID=0X20746D66; //"fmt " wavhead->fmt.ChunkSize=16; //大小为16个字节 wavhead->fmt.AudioFormat=0X01; //0X01,表示PCM;0X01,表示IMA ADPCM wavhead->fmt.NumOfChannels=1; //单声道 wavhead->fmt.SampleRate=8000; //8Khz采样率 采样速率 wavhead->fmt.ByteRate=wavhead->fmt.SampleRate*2;//16位,即2个字节 wavhead->fmt.BlockAlign=2; //块大小,2个字节为一个块 wavhead->fmt.BitsPerSample=16; //16位PCM wavhead->data.ChunkID=0X61746164; //"data" wavhead->data.ChunkSize=0; //数据大小,还需要计算 } //VS1053的WAV录音有bug,这个plugin可以修正这个问题 const u16 VS1053_WavPlugin[40]=/* Compressed plugin */ { 0x0007, 0x0001, 0x8010, 0x0006, 0x001c, 0x3e12, 0xb817, 0x3e14, /* 0 */ 0xf812, 0x3e01, 0xb811, 0x0007, 0x9717, 0x0020, 0xffd2, 0x0030, /* 8 */ 0x11d1, 0x3111, 0x8024, 0x3704, 0xc024, 0x3b81, 0x8024, 0x3101, /* 10 */ 0x8024, 0x3b81, 0x8024, 0x3f04, 0xc024, 0x2808, 0x4800, 0x36f1, /* 18 */ 0x9811, 0x0007, 0x0001, 0x8028, 0x0006, 0x0002, 0x2a00, 0x040e, }; /* 函数功能:激活PCM 录音模式 函数参数: agc:0,自动增益 1024相当于1倍 512相当于0.5倍 最大值65535=64倍 */ void VS1053_RecoderInit(u16 agc) { //如果是IMA ADPCM,采样率计算公式如下: //采样率=CLKI/256*d; //假设d=0,并2倍频,外部晶振为12.288M.那么Fc=(2*12288000)/256*6=16Khz //如果是线性PCM,采样率直接就写采样值 VS1053_WriteCmd(SPI_BASS,0x0000); VS1053_WriteCmd(SPI_AICTRL0,8000); //设置采样率,设置为8Khz VS1053_WriteCmd(SPI_AICTRL1,agc); //设置增益,0,自动增益.1024相当于1倍,512相当于0.5倍,最大值65535=64倍 VS1053_WriteCmd(SPI_AICTRL2,0); //设置增益最大值,0,代表最大值65536=64X VS1053_WriteCmd(SPI_AICTRL3,6); //左通道(MIC单声道输入) VS1053_WriteCmd(SPI_CLOCKF,0X2000); //设置VS10XX的时钟,MULT:2倍频;ADD:不允许;CLK:12.288Mhz VS1053_WriteCmd(SPI_MODE,0x1804); //MIC,录音激活 DelayMs(5); //等待至少1.35ms VS1053_LoadPatch((u16*)VS1053_WavPlugin,40);//VS1053的WAV录音需要patch }6.2 SD.c 这是SD卡的驱动代码#include "sdcard.h" static u8 SD_Type=0; //存放SD卡的类型 /* 函数功能:SD卡底层接口,通过SPI时序向SD卡读写一个字节 函数参数:data是要写入的数据 返 回 值:读到的数据 */ u8 SDCardReadWriteOneByte(u8 DataTx) { u16 cnt=0; while((SPI1->SR&1<<1)==0) //等待发送区空--等待发送缓冲为空 { cnt++; if(cnt>=65530)return 0; //超时退出 u16=2个字节 } SPI1->DR=DataTx; //发送一个byte cnt=0; while((SPI1->SR&1<<0)==0) //等待接收完一个byte { cnt++; if(cnt>=65530)return 0; //超时退出 } return SPI1->DR; //返回收到的数据 } /* 函数功能:底层SD卡接口初始化 SPI1接口---SD卡接线原理 5V----5V GND---GND SPI1_MOSI---PA7 SPI1_MISO---PA6 SPI1_CS---PA4 SPI1_SCK--PA5 */ void SDCardSpiInit(void) { /*1. 开启时钟*/ RCC->APB2ENR|=1<<2; //使能PORTA时钟 /*2. 配置GPIO口模式*/ GPIOA->CRL&=0x0000FFFF; GPIOA->CRL|=0xB8B30000; /*3. 上拉*/ GPIOA->ODR|=1<<4; /*SPI1基本配置*/ RCC->APB2ENR|=1<<12; //开启SPI1时钟 RCC->APB2RSTR|=1<<12; RCC->APB2RSTR&=~(1<<12); SPI1->CR1=0X0; //清空寄存器 SPI1->CR1|=0<<15; //选择“双线双向”模式 SPI1->CR1|=0<<11; //使用8位数据帧格式进行发送/接收; SPI1->CR1|=0<<10; //全双工(发送和接收); SPI1->CR1|=1<<9; //启用软件从设备管理 SPI1->CR1|=1<<8; //NSS SPI1->CR1|=0<<7; //帧格式,先发送高位 SPI1->CR1|=0x0<<3;//当总线频率为36MHZ时,SPI速度为18MHZ,高速。 SPI1->CR1|=1<<2; //配置为主设备 SPI1->CR1|=1<<1; //空闲状态时, SCK保持高电平。 SPI1->CR1|=1<<0; //数据采样从第二个时钟边沿开始。 SPI1->CR1|=1<<6; //开启SPI设备。 } /* 函数功能:取消选择,释放SPI总线 */ void SDCardCancelCS(void) { SDCARD_CS=1; SDCardReadWriteOneByte(0xff);//提供额外的8个时钟 } /* 函数 功 能:选择sd卡,并且等待卡准备OK 函数返回值:0,成功;1,失败; */ u8 SDCardSelectCS(void) { SDCARD_CS=0; if(SDCardWaitBusy()==0)return 0;//等待成功 SDCardCancelCS(); return 1;//等待失败 } /* 函数 功 能:等待卡准备好 函数返回值:0,准备好了;其他,错误代码 */ u8 SDCardWaitBusy(void) { u32 t=0; do { if(SDCardReadWriteOneByte(0XFF)==0XFF)return 0;//OK t++; }while(t<0xFFFFFF);//等待 return 1; } /* 函数功能:等待SD卡回应 函数参数: Response:要得到的回应值 返 回 值: 0,成功得到了该回应值 其他,得到回应值失败 */ u8 SDCardGetAck(u8 Response) { u16 Count=0xFFFF;//等待次数 while((SDCardReadWriteOneByte(0XFF)!=Response)&&Count)Count--;//等待得到准确的回应 if(Count==0)return SDCard_RESPONSE_FAILURE;//得到回应失败 else return SDCard_RESPONSE_NO_ERROR;//正确回应 } /* 函数功能:从sd卡读取一个数据包的内容 函数参数: buf:数据缓存区 len:要读取的数据长度. 返回值: 0,成功;其他,失败; */ u8 SDCardRecvData(u8*buf,u16 len) { if(SDCardGetAck(0xFE))return 1;//等待SD卡发回数据起始令牌0xFE while(len--)//开始接收数据 { *buf=SDCardReadWriteOneByte(0xFF); buf++; } //下面是2个伪CRC(dummy CRC) SDCardReadWriteOneByte(0xFF); SDCardReadWriteOneByte(0xFF); return 0;//读取成功 } /* 函数功能:向sd卡写入一个数据包的内容 512字节 函数参数: buf 数据缓存区 cmd 指令 返 回 值:0表示成功;其他值表示失败; */ u8 SDCardSendData(u8*buf,u8 cmd) { u16 t; if(SDCardWaitBusy())return 1; //等待准备失效 SDCardReadWriteOneByte(cmd); if(cmd!=0XFD)//不是结束指令 { for(t=0;t<512;t++)SDCardReadWriteOneByte(buf[t]);//提高速度,减少函数传参时间 SDCardReadWriteOneByte(0xFF); //忽略crc SDCardReadWriteOneByte(0xFF); t=SDCardReadWriteOneByte(0xFF); //接收响应 if((t&0x1F)!=0x05)return 2; //响应错误 } return 0;//写入成功 } /* 函数功能:向SD卡发送一个命令 函数参数: u8 cmd 命令 u32 arg 命令参数 u8 crc crc校验值 返回值:SD卡返回的响应 */ u8 SendSDCardCmd(u8 cmd, u32 arg, u8 crc) { u8 r1; u8 Retry=0; SDCardCancelCS(); //取消上次片选 if(SDCardSelectCS())return 0XFF;//片选失效 //发送数据 SDCardReadWriteOneByte(cmd | 0x40);//分别写入命令 SDCardReadWriteOneByte(arg >> 24); SDCardReadWriteOneByte(arg >> 16); SDCardReadWriteOneByte(arg >> 8); SDCardReadWriteOneByte(arg); SDCardReadWriteOneByte(crc); if(cmd==SDCard_CMD12)SDCardReadWriteOneByte(0xff);//Skip a stuff byte when stop reading Retry=0X1F; do { r1=SDCardReadWriteOneByte(0xFF); }while((r1&0X80) && Retry--); //等待响应,或超时退出 return r1; //返回状态值 } /* 函数功能:获取SD卡的CID信息,包括制造商信息 函数参数:u8 *cid_data(存放CID的内存,至少16Byte) 返 回 值: 0:成功,1:错误 */ u8 GetSDCardCISDCardOutnfo(u8 *cid_data) { u8 r1; //发SDCard_CMD10命令,读CID r1=SendSDCardCmd(SDCard_CMD10,0,0x01); if(r1==0x00) { r1=SDCardRecvData(cid_data,16);//接收16个字节的数据 } SDCardCancelCS();//取消片选 if(r1)return 1; else return 0; } /* 函数说明: 获取SD卡的CSD信息,包括容量和速度信息 函数参数: u8 *cid_data(存放CID的内存,至少16Byte) 返 回 值: 0:成功,1:错误 */ u8 GetSDCardCSSDCardOutnfo(u8 *csd_data) { u8 r1; r1=SendSDCardCmd(SDCard_CMD9,0,0x01); //发SDCard_CMD9命令,读CSD if(r1==0) { r1=SDCardRecvData(csd_data, 16);//接收16个字节的数据 } SDCardCancelCS();//取消片选 if(r1)return 1; else return 0; } /* 函数功能:获取SD卡的总扇区数(扇区数) 返 回 值: 0表示容量检测出错,其他值表示SD卡的容量(扇区数/512字节) 说 明: 每扇区的字节数必为512字节,如果不是512字节,则初始化不能通过. */ u32 GetSDCardSectorCount(void) { u8 csd[16]; u32 Capacity; u16 csize; if(GetSDCardCSSDCardOutnfo(csd)!=0) return 0; //取CSD信息,如果期间出错,返回0 if((csd[0]&0xC0)==0x40) //SDHC卡,按照下面方式计算 { csize = csd[9] + ((u16)csd[8] << 8) + 1; Capacity = (u32)csize << 10;//得到扇区数 } return Capacity; } /* 函数功能: 初始化SD卡 返 回 值: 非0表示初始化失败! */ u8 SDCardDeviceInit(void) { u8 r1; // 存放SD卡的返回值 u16 retry; // 用来进行超时计数 u8 buf[4]; u16 i; SDCardSpiInit(); //初始化底层IO口 for(i=0;i<10;i++)SDCardReadWriteOneByte(0XFF); //发送最少74个脉冲 retry=20; do { r1=SendSDCardCmd(SDCard_CMD0,0,0x95);//进入IDLE状态 闲置 }while((r1!=0X01) && retry--); SD_Type=0; //默认无卡 if(r1==0X01) { if(SendSDCardCmd(SDCard_CMD8,0x1AA,0x87)==1) //SD V2.0 { for(i=0;i<4;i++)buf[i]=SDCardReadWriteOneByte(0XFF); if(buf[2]==0X01&&buf[3]==0XAA) //卡是否支持2.7~3.6V { retry=0XFFFE; do { SendSDCardCmd(SDCard_CMD55,0,0X01); //发送SDCard_CMD55 r1=SendSDCardCmd(SDCard_CMD41,0x40000000,0X01);//发送SDCard_CMD41 }while(r1&&retry--); if(retry&&SendSDCardCmd(SDCard_CMD58,0,0X01)==0)//鉴别SD2.0卡版本开始 { for(i=0;i<4;i++)buf[i]=SDCardReadWriteOneByte(0XFF);//得到OCR值 if(buf[0]&0x40)SD_Type=SDCard_TYPE_V2HC; //检查CCS else SD_Type=SDCard_TYPE_V2; } } } } SDCardCancelCS(); //取消片选 if(SD_Type)return 0; //初始化成功返回0 else if(r1)return r1; //返回值错误值 return 0xaa; //其他错误 } /* 函数功能:读SD卡 函数参数: buf:数据缓存区 sector:扇区 cnt:扇区数 返回值: 0,ok;其他,失败. 说 明: SD卡一个扇区大小512字节 */ u8 SDCardReadData(u8*buf,u32 sector,u32 cnt) { u8 r1; if(SD_Type!=SDCard_TYPE_V2HC)sector<<=9;//转换为字节地址 if(cnt==1) { r1=SendSDCardCmd(SDCard_CMD17,sector,0X01);//读命令 if(r1==0) //指令发送成功 { r1=SDCardRecvData(buf,512); //接收512个字节 } }else { r1=SendSDCardCmd(SDCard_CMD18,sector,0X01);//连续读命令 do { r1=SDCardRecvData(buf,512);//接收512个字节 buf+=512; }while(--cnt && r1==0); SendSDCardCmd(SDCard_CMD12,0,0X01); //发送停止命令 } SDCardCancelCS();//取消片选 return r1;// } /* 函数功能:向SD卡写数据 函数参数: buf:数据缓存区 sector:起始扇区 cnt:扇区数 返回值: 0,ok;其他,失败. 说 明: SD卡一个扇区大小512字节 */ u8 SDCardWriteData(u8*buf,u32 sector,u32 cnt) { u8 r1; if(SD_Type!=SDCard_TYPE_V2HC)sector *= 512;//转换为字节地址 if(cnt==1) { r1=SendSDCardCmd(SDCard_CMD24,sector,0X01);//读命令 if(r1==0)//指令发送成功 { r1=SDCardSendData(buf,0xFE);//写512个字节 } } else { if(SD_Type!=SDCard_TYPE_MMC) { SendSDCardCmd(SDCard_CMD55,0,0X01); SendSDCardCmd(SDCard_CMD23,cnt,0X01);//发送指令 } r1=SendSDCardCmd(SDCard_CMD25,sector,0X01);//连续读命令 if(r1==0) { do { r1=SDCardSendData(buf,0xFC);//接收512个字节 buf+=512; }while(--cnt && r1==0); r1=SDCardSendData(0,0xFD);//接收512个字节 } } SDCardCancelCS();//取消片选 return r1;// }
  • [问题求助] IOTDA如何实现设备”数据变化“才推动到北向应用的”规则转发“配置?
    IOTDA私有化部署。
  • [问题求助] 【IOT产品】【设备接入功能】平台设置设备属性接口老是报topic格式不正确,是什么问题?
    【功能模块】设备接入模块,设备在线调式【操作步骤&问题现象】1、在MQTTX客户端新建一个连接,并发送主题消息2、在线调试,查看日志,提示topic格式有问题【截图信息】【日志信息】(可选,上传日志内容或者附件)
  • [问题求助] 小熊派WIFI8266连接IOTDA
    【功能模块】跟着资料一步一步走但就是上不了云,串口显示一直无法握手成功【操作步骤&问题现象】1、按照图示步骤进行2、无法实现设备激活【截图信息】【日志信息】(可选,上传日志内容或者附件)
  • [问题求助] 【Iot设备接入】【web在线开发】iotstudio下架后的应用开发功能寻求替代
    【功能模块】iot设备接入【操作步骤&问题现象】跟着社区的资料做,发现iotstudio下架了,web在线开发也下架了,翻阅帖子发现可以用oceanconnect替代,结果oceanconnect也已经下架了,还有其他可以根据设备回报的数据自动下发命令的替代吗 例如:智慧农业里的补光灯 那样的自动下发命令【截图信息】
  • [问题求助] 设备接入IOTDA使用Java Demo获取不到token
    【功能模块】【操作步骤&问题现象】1、已经配置好了Java和IDEA2、这两步骤也做了,出现了以下报错:推测:没有获取到token,token中为空,怎么解?
  • [问题求助] 【IOTDA,APPcube产品】输入接入
    IOTda 上传的数据怎么导入到APPcube里我需要将上报的实时数据在地图上显示出来
  • [认证交流] 论微认证《物联网板块》大满贯
    什么是微认证?          华为云微认证是基于线上学习与在线实践,快速获得场景化技能提升的认证。微认证清单 - 物联网1、物联网智慧烟感报警系统消防系统建设是关系到民生的重要工程,借助物联网技术,消防系统展现了新的可能性。本认证为您展示烟感报警系统如何在物联网技术的支持下实现远程报警与控制,助力及早发现与控制火灾,减少损失。物联网应用技术学习认证_物联网智慧烟感报警系统微认证_华为云学院-华为云 (huaweicloud.com)2、基于物联网平台的自贩机销量分析借助物联网平台和大数据分析服务,随时监控自动售货机运行和销售状态,帮助厂家更准确分析消费行为,更精确定位客户需求。物联网应用技术学习认证_基于物联网平台的自贩机销量分析微认证_华为云学院-华为云 (huaweicloud.com)3、基于物联网平台构建智慧路灯应用城市公共照明设施规模日益增大,用电量节节攀升。为解决传统路灯的问题,基于物联网的智慧路灯应运而生,本认证将会为您介绍基于物联的智慧路灯解决方案和如何构建其应用。物联网应用技术学习认证_基于物联网平台构建智慧路灯应用微认证_华为云学院-华为云 (huaweicloud.com)4、HarmonyOS分布式协同办公应用开发HarmonyOS打造全场景分布式操作系统,助力万物互联,轻松实现跨设备共享服务及应用,灵活定制系统,适配更多设备。HarmonyOS__分布式_协同办公_HarmonyOS分布式协同办公应用开发_华为云培训中心-华为云 (huaweicloud.com)
总条数:799 到第
上滑加载中