2020/11/1
读博警告:
本人纯属小白一枚,刚开始接触学习STM32。本篇微博只是想记录自己在做项目中过程中遇到的问题以及最后的解决办法。第一次更博,语言表达能力不好还很乱,望见谅。如果有表述不对的地方,大家可以指正出来,欢迎大家一起学习交流!

文章目录

  • 项目描述
    • 要求:
    • 预计方案:
  • 项目实施计划
    • 通信
      • 上位机与单片机之间
      • 电力表与单片机之间
        • 单片机——>电力表
          • MODBUS_RTU通讯协议补充
          • 串口发送指令补充
    • 数据处理
      • IEEE-754标准16进制(32位)转化成10进制float
      • float浮点型数据转化成char字符型数据输出

项目描述

要求:

1、监测设备一天的用电量。
2、统计开机、关机时间,计算使用率。
3、有可能的话实现手机端实时监控

预计方案:

STM32单片机通过RS-485串口驱动电力表模块采集数据,然后再通过TTL串口将数据发送给ZigBee终端。ZigBee终端在接收到数据后通过无线传输将数据发送给协调器,ZigBee协调器通过RS232串口通讯将数据发送给上位机。上位机实时显示数据。另外,还可以开发一款手机APP,上位机将数据保存,发送到手机端,通过手机端对数据进行实时监控。
其中,电力表模块、STM32模块、ZigBee终端模块这三部分组成一个数据采集模块,放置在机器人上,用于采集数据。ZigBee协调器模块和上位机构成显示模块,用于显示耗电量、使用效率等数据。
项目系统结构图如下:

项目实施计划

通信

单片机与电力表之间通过串口1通讯,上位机与单片机之间通过串口2通讯 。
串口1、2的初始化程序、中端配置程序如下:

//串口1初始化函数
void usart1_init(u32 bound)
{//GPIO端口设置GPIO_InitTypeDef GPIO_InitStructure;//创建GPIO的初始化结构体USART_InitTypeDef USART_InitStructure;//创建USART的初始化结构体RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //使能USART1,GPIOA时钟//USART1_TX   GPIOA.9GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9 RX  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9//USART1_RX     GPIOA.10初始化GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10  //USART 初始化设置USART_InitStructure.USART_BaudRate = bound;//串口波特率USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;  //收发模式USART_Init(USART1, &USART_InitStructure); //初始化串口1USART_Cmd(USART1, ENABLE);                    //使能串口1 }
//串口1中断配置
void usart1_NVIC__init(void)
{NVIC_InitTypeDef NVIC_InitStructure;//创建NVIC的初始化结构体//Usart1 NVIC 配置NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;      //子优先级3NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;         //IRQ通道使能NVIC_Init(&NVIC_InitStructure);    //根据指定的参数初始化VIC寄存器USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断}
void usart2_init(u32 bound)
{  //GPIO端口设置GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//使能GPIOA时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);//使能USART2时钟GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;  //PA2 txGPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;    //复用推挽GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;//PA3 RxGPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入GPIO_Init(GPIOA, &GPIO_InitStructure);  //USART2配置USART_InitStructure.USART_BaudRate = bound;//波特率设置USART_InitStructure.USART_WordLength = USART_WordLength_8b;//8位数据长度USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位USART_InitStructure.USART_Parity = USART_Parity_No;///奇偶校验位USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//收发模式USART_Init(USART2, &USART_InitStructure); ; //初始化串口USART_Cmd(USART2, ENABLE);                    //使能串口
}
//串口2中断配置
void usart2_NVIC__init(void)
{NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; //使能串口2中断NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //先占优先级2级NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //从优先级2级NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启中?}

上位机与单片机之间

上位机与单片机之间通过串口2通讯 。当上位机向单片机发送命令时,单片机接收到命令执行相应操作。比如上位机发送“ I”,单片机就会向电力表发送相应的查电流的代码。
串口2中断服务程序如下:
此处千万要注意,千万要注意中断服务程序的名字不能写错,否则就不能进中断。(当时这个Bug找了好久…想哭 悲伤 哭泣 委屈)

//串口2中断服务函数
void USART2_IRQHandler(void)
{if(USART_GetITStatus(USART2, USART_IT_RXNE) == SET)//检测中断接收标志位是否置1uctemp=USART_ReceiveData(USART2);}

电力表与单片机之间

单片机——>电力表

MODBUS_RTU通讯协议补充

电力表提供了RS485通讯接口,采用MODBUS——RTU通讯规约。

例如:主机发送数据帧:读三相电流值

地址 命令 起始地址(高位在前) 寄存器数(低位在前) 校验码
01H 03H 00H,45H 00H,06H D4H,1DH

存电流寄存器的起始地址是“00H,45H”(查说明书MODBUS——RTU地址信息表得,即69),存放三相电流的寄存器一共有六个,所以寄存器数是“00H,06H”。

校验码是通过专用计算器算的。

所以定义读取电压、电流、功率的数组如下:

 u8 current[8]={0x01,0x03,0x00,0x45,0x00,0x06,0xD4,0x1D};u8 voltage[8]={0x01,0x03,0x00,0x39,0x00,0x06,0x15,0xC5};u8 power[8]={0x01,0x03,0x00,0x4B,0x00,0x06,0xB5,0xDE};
串口发送指令补充

//发送一个字节
void sendByte(USART_TypeDef* USARTx,u8 date)
{USART_SendData(USARTx, date);while(USART_GetFlagStatus(USARTx, USART_FLAG_TXE)==RESET);
}//发送两个字节
void sendTwoByte(USART_TypeDef* USARTx,u16 date)
{u8 temp_h,temp_l;temp_h =(date&0xff00)>>8;temp_l=(date&0xff);sendByte(USARTx,temp_h);sendByte(USARTx,temp_l);}//发送四个字节
void sendFourByte(USART_TypeDef* USARTx,u32 date)
{u8 temp1,temp2,temp3,temp4;temp1 =(date&0xff000000)>>24;temp2=(date&0xff0000)>>16;temp3=(date&0xff00)>>8;temp4=(date&0xff);sendByte(USARTx,temp1);sendByte(USARTx,temp2);sendByte(USARTx,temp3);sendByte(USARTx,temp4);}
//发送8位数组
void sendArry(USART_TypeDef* USARTx,u8 *arry,u8 num)
{u8 i;for(i=0;i<num;i++){sendByte(USARTx,arry[i]);}while(USART_GetFlagStatus(USARTx, USART_FLAG_TC)==RESET);
}
//发送字符串
void Usart_Sendstr(USART_TypeDef* USARTx,char*str)
{u8 i=0;do{sendByte(USARTx,str[i]);i++; }while(*(str+i)!='\n');while(USART_GetFlagStatus(USARTx, USART_FLAG_TC)==RESET);}

单片机接收到命令后,通过串口1向电力表发送相应指令,电力表通过串口1返回数据
例如:
其中数据段一共有12个字节,没4个字节表示一相的电流值。
串口1接收到数据后进入串口1的中断服务子程序,把接收到的数据存在接收缓存寄存器里面,具体程序如下:

//串口1中断服务函数
//USART_RX_BUF[17];接收缓冲,最大17个字节 。 USART_RX_STA;接收状态标记,记录接收到第几个字节
void USART1_IRQHandler(void)
{u8 Res;if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断{Res =USART_ReceiveData(USART1); //读取接收到的数据USART_RX_BUF[USART_RX_STA] = Res;USART_RX_STA++;if(USART_RX_STA==17)//返回的数据一共有17个字节{USART_RX_STA=0;flag =1;//flag是接收完成标志位}}
}

单片机再把返回的数据发送给上位机。

if(flag)//串口1接收中断完成{ for(t=3;t<15;t++)//数组里的第四个字节开始是电流值{USART_SendData(USART2, USART_RX_BUF[t]);//向串口2发送数据while(USART_GetFlagStatus(USART2,USART_FLAG_TC)!=SET);//等待发送结束}flag=0;}

只接A相负载的实验结果如图

可见A相的电流值为“32 30 20 C5”,这是一个4个字节表示的浮点型数据,标准的IEEE-754标准,后续还要对数据进行处理,直接用ASCLL码表示浮点型。
未完待续。。。。

2020/11/11
双十一剁完手就要好好工作啦,努力学习,天天向上哈!!

数据处理

上次已经通过串口读到电表采集的电流值了,但是它是用4个字节的16进制数表示的,不方便读取,所以要根据IEEE-754标准将4个字节的16进制数转换成10进制浮点数。

IEEE-754标准16进制(32位)转化成10进制float

关于IEEE-754标准的只是大家可以参见这篇文章:IEEE754 浮点数的表示方法
1、首先返回来的一相数据是“32 30 20 C5”,它占4个字节存储在4个内存单元中,目前还不是一个32位的数,所以要先把它转换成一下存在一个内存单元里 ,故需要定义一个变量temp,具体程序如下:

         u32 temp1=0,temp2=0,temp3=0;//用于存放每相转化后的32位16进制数temp1=temp1|(USART_RX_BUF[3]<<24);//USART_RX_BUF[3]变为temp1的高8位(25~32位)temp1=temp1|(USART_RX_BUF[4]<<16);temp1=temp1|(USART_RX_BUF[5]<<8);temp1=temp1|(USART_RX_BUF[6]);

USART_RX_BUF[]是一个含有17个元素的全局变量数组,用来存放电表返回的数据。从第4个元素开始是数据值,故需要从USART_RX_BUF[3]开始取值。

extern u8  USART_RX_BUF[17];

!!!注意全局变量不能重复定义,只定义在一个.h文件中就可以了。

2、现在16进制的32位数已经保存在了temp1这个内存单元中,下面要把temp1内存单元的数按照IEEE-754标准转化成10进制浮点型。
16进制(32位)转化成10进制float程序如下:

//============================================================
//函数名称 :Float32
//函数功能 :将32位的float转换为10进制数,IEEE 754标准
//输入变量 :NumFloat32:待转换32位浮点数
//返 回 值 :计算结果 无符号整形 u32 ,返回-1为负数或者超限;
//============================================================
float Float32(u32 NumFloat32)
{signed char  sbit, ebits;// sbit符号位,ebits指数位signed int mbits,temp,i;//mbits位数位float Float32_sum;float Float32_result =0;temp = 0x00400000;Float32_sum = 1.0;i = 1;sbit = NumFloat32 >> 31;ebits = (NumFloat32 >> 23) & 0xff;if (sbit)    //若需要负数部分,sbit为1时,result*-1,去掉判断;{return -1;}ebits -= 127;    //得到阶码<0,即结果小于1,返回0;if (ebits < 0)//负数{return 0;}while (temp != 0){mbits = NumFloat32 & temp;if (mbits!=0)Float32_sum += pow(2, -i);//pow是指数运算函数,2是底数,-i是指数temp = temp >> 1;i++;}Float32_result =Float32_sum* pow(2, ebits);return Float32_result;}

3、后来师兄说利用共用体就可以实现16进制(32位)转化成10进制float的功能,不用调用复杂的Float32函数。
共用体可以使不同的数据类型来共享相同的地址空间。首先定义一个共用体,既可以存放浮点型又可以存放整型。程序如下:

union data
{float dataInFloat;//存放浮点型数u32 dataInUnsingedInt;//存放整数
};      

定义共用体变量:

union data currentData;//定义一个共用体变量。

将32位的16进制数(整型)存放在共用体中,然后调用的时候以浮点型取出。这样它就可以实现16进制(32位)转化成10进制float的功能。

         currentData.dataInUnsingedInt = temp1;Turn_Float_to_str(currentData.dataInFloat);

float浮点型数据转化成char字符型数据输出

通常我们发送数据的模式有两种,一种16进制,一种是ASCII码。16进制的经常会用来和仪器PLC等设备通讯。ACSII码是一种文本模式。当我们不点选16进制时,按文本模式发送。这时我们输入的文本区的内容是一个个字符。比如输入50 ,这时50为‘5’和‘0’两个字符。发送的时候会将字符‘5’的ASCII码和字符‘0’的ASCII码发送出去,即是0x35,0x30 。当我们按16进制发送50 时,这时50为一个数即0x50。
如果要通过串口调试助手显示浮点型数据,就要把浮点型数据每一位对应的ASCLL码发送给串口。
具体程序如下:

//============================================================
//函数名称 :Turn_Float_to_str
//函数功能 :将浮点型数据转换成字符串数组输出给串口调试助手
//输入变量 :要转化的数据date 。
//返 回 值 :字符串的数组str[]
//============================================================
void Turn_Float_to_str(float date)
{u8 len1,len2,i;//len1是整数部分的位数,len2是小数部分的位数//【!】字符串类型的数据定义的时候需要初始化,发送完成之后需要复位,因为u8 str1[5],str2[5],str[10];//str1[],str2[5]分别存放整数部分 小数部分的每一位数字(肯定是从个位开始倒序存的)str[10]把他们正过来再添加小数点u16 zhengshu,xiaoshu;u16 a1;//a1做中间变量,防止提取整数部分时zhengshu的值改变 影响下面xiaoshu计算,//test by zzz 20201101【!】//经测试,若使用强制数据类型转换在某些情况下会出现数据异常,但是问题并没能复现,所以将代码改了回来,如果后续测试出现问题可以考虑修改此处,下面小数部分同zhengshu=(u16)date;//把浮点数的整数部分提取出来a1=zhengshu;len1=number_figure(zhengshu);//调用函数number_figure()计算整数部分的长度//把整数部分每位提取出来//如果整数部分是零if(len1==0){str[0]=0x30;len1=1;}else{for(i=0;i<len1;i++){str1[i]=(u8)(a1%10);a1 /=10;str[len1-1-i]=str1[i]+0x30;//十进制数与其对应的字符他们的ASCLL码相差0x30}}xiaoshu=(u16)((date-zhengshu)*100);//保留两位小数len2=number_figure(xiaoshu);for(i=0;i<len2;i++){str2[i]=(u8)(xiaoshu%10);xiaoshu /=10;str[len1+len2-i]=str2[i]+0x30;}str[len1]=0x2E;sendArry(USART2,str,(len1+len2+1));a1 = 0;}

这样就可以将浮点型数转化成字符串通过串口输出。
附上成果图:

至此,第一阶段的任务基本完成,下面就该写上位机程序了。先暂时拜拜了,我去学上位机了!!!

项目记录1——基于STM32和Zigbee的电力表数据采集相关推荐

  1. 基于STM32开发板实现传感数据采集-DHT11温湿度采集

    基于STM32开发板实现传感数据采集-DHT11温湿度采集 一.项目简介 本次项目是基于STM32开发板实现传感数据采集-DHT11温湿度采集.采用ARM结构中最为代表的Cortex-M4系列的芯片, ...

  2. STM32实例——基于STM32开发板实现传感数据采集-DHT11温湿度采集

    STM32开发板实现传感数据采集-DHT11温湿度采集 一.前言 本项目是基于STM32开发板的温湿度采集,传感器采用DHT11温湿度传感器,软件采用keil5等.本项目采用ARM结构中最为代表的Co ...

  3. 基于STM32开发板实现传感数据采集及wifi上云

    目录 前言 一.实验目的 二.实验环境 三.实验步骤 四.实验代码 五.实验结果 六.实验体会 前言 温度和湿度值是重要的物理参数,例如精密机械加工.实验室.温室大棚.粮库.重要活动场所.会议室.居住 ...

  4. 项目实战|基于STM32的无刷电机开环控制

    电机转动的本质是在磁场的作用下,转子绕定子运动,从而带动整个机械结构转动.从有无电刷,我们可以将电机分为:有刷电机和无刷电机:从供电方式,我们又可以将电机分为直流电机和交流电机.我最近学习的内容主要集 ...

  5. 【项目实战】基于STM32单片机的智能小车设计(有代码)

    [1]背景意义 近些年随着国民生活水平的提升,以小车为载体的轮式机器人进入了我们的生活,尤其是在一些布线复杂困难的安全生活区和需要监控的施工作业场合都必须依赖轮式机器人的视频监控技术.因此,基于嵌入式 ...

  6. 项目记录:基于QT的简易音乐播放器

    一.功能介绍 1.能够自定义背景图片 2.实现了音乐播放器的最基本功能,即播放与否.切换歌曲及歌单.显示歌曲信息.调节音量.播     放模式等 3.根据所选中的框体,更新播放列表及播放顺序 4.从播 ...

  7. 基于stm32的智能家居项目

    基于stm32的智能家居 具体教程可以看文中的B站链接,上面有手把手教程 本人其他项目链接基于linux的智能仓储项目 基于Qt的人脸识别 移植人脸识别到Linux开发板上 基于正点原子的IMX6UL ...

  8. 基于STM32的桌面数控电源项目连载

    基于STM32的桌面数控电源项目连载 一个数控桌面电源项目 文章目录 基于STM32的桌面数控电源项目连载 前言 一.尺寸与外观 二.元器件选型 1.升压芯片 2.辅助电源 3.参考电源 4.电流检测 ...

  9. STM32毕业设计——基于STM32+MQTT+WiFi技术的智能家居系统设计与实现(毕业论文+程序源码)——智能家居系统

    基于STM32+MQTT+WiFi技术的智能家居系统设计与实现(毕业论文+程序源码) 大家好,今天给大家介绍基于STM32+MQTT+WiFi技术的智能家居系统设计与实现,文章末尾附有本毕业设计的论文 ...

最新文章

  1. JavaSE--jdom解析之bom
  2. 芯片项目烂尾怎么办?国家发改委回应了!
  3. Python之ffmpeg-python:ffmpeg-python库的简介、安装、使用方法之详细攻略
  4. 图论--最短路--SPFA
  5. 随时随地能写代码, vscode.dev 出手了
  6. cisco 双ISP线路接入,链路自动切换方案
  7. RabbitMQ 管控台配置导入导出和重要性
  8. 异常的概念和Java异常体系结构
  9. java jdk1.8使用_Java jdk1.8配置
  10. 我只能说,Spring Data REST真的很燥辣
  11. Spring——依赖注入的三种方式
  12. 计算机“存储容量”、“速率”(换算详解)
  13. USB Host、USB Device和USB otg的理论简析
  14. 用react制作半圆形进度条
  15. 常用坐标系汇总(更新)
  16. Github文件夹下载到本地
  17. 程序员专属小情话,哎呦,不错哦!!!
  18. 字节跳动梁汝波:管理者过于依靠规则会使组织僵化 |王兴:反垄断无损美团竞争优势...
  19. 数据分析-数据规范化的一些方法
  20. ! LaTeX Error: File xxx.sty not found-统一解决办法

热门文章

  1. [MatLab]矩阵运算和程序结构
  2. cpu高速缓存命中率
  3. Codeforces 1634 B. Fortune Telling —— 简单思维,奇偶性
  4. 湖南大学_数电实验_模型机设计_CPU设计_verilog_课程实验报告
  5. 苹果airpods android,AirPods
  6. 微服务之服务监控稳定性
  7. 2018年今日头条白皮书,OPPO品牌用户粘度荣获第一!
  8. SQL Server:主键与外键设置与相关理解
  9. 6月红包广告插屏广告 视频广 激励广告源码【开源无授权版】2021最新微信红包封面小程序源码/带文字搭建教程
  10. 领带的打法10种[zz]