[其他ST产品] 基于STM32和Zigbee的电力表数据采集

[复制链接]
2134|23
 楼主| 结合国际经验 发表于 2023-5-30 15:51 | 显示全部楼层 |阅读模式
项目描述
要求:
1、监测设备一天的用电量。
2、统计开机、关机时间,计算使用率。
3、有可能的话实现手机端实时监控

预计方案:
STM32单片机通过RS-485串口驱动电力表模块采集数据,然后再通过TTL串口将数据发送给ZigBee终端。ZigBee终端在接收到数据后通过无线传输将数据发送给协调器,ZigBee协调器通过RS232串口通讯将数据发送给上位机。上位机实时显示数据。另外,还可以开发一款手机APP,上位机将数据保存,发送到手机端,通过手机端对数据进行实时监控。
其中,电力表模块、STM32模块、ZigBee终端模块这三部分组成一个数据采集模块,放置在机器人上,用于采集数据。ZigBee协调器模块和上位机构成显示模块,用于显示耗电量、使用效率等数据。
项目系统结构图如下: 733666475ab048a91e.png
————————————————
版权声明:本文为CSDN博主「ShineSmile29」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ShineSmile29/article/details/109547399

 楼主| 结合国际经验 发表于 2023-5-30 16:03 | 显示全部楼层
项目实施计划
通信
单片机与电力表之间通过串口1通讯,上位机与单片机之间通过串口2通讯 。
串口1、2的初始化程序、中端配置程序如下:

  1. //串口1初始化函数
  2. void usart1_init(u32 bound)
  3. {
  4.   //GPIO端口设置
  5.         GPIO_InitTypeDef GPIO_InitStructure;//创建GPIO的初始化结构体
  6.         USART_InitTypeDef USART_InitStructure;//创建USART的初始化结构体
  7.        
  8.          
  9.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);        //使能USART1,GPIOA时钟
  10.   
  11.         //USART1_TX   GPIOA.9
  12.         GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9 RX       
  13.         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  14.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;        //复用推挽输出
  15.     GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9
  16.    
  17.   //USART1_RX          GPIOA.10初始化
  18.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
  19.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
  20.     GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10  

  21.   
  22.    //USART 初始化设置

  23.         USART_InitStructure.USART_BaudRate = bound;//串口波特率
  24.         USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
  25.         USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
  26.         USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
  27.         USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
  28.         USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;        //收发模式

  29.     USART_Init(USART1, &USART_InitStructure); //初始化串口1
  30.    
  31.     USART_Cmd(USART1, ENABLE);                    //使能串口1

  32. }
  33. //串口1中断配置
  34. void usart1_NVIC__init(void)
  35. {
  36.         NVIC_InitTypeDef NVIC_InitStructure;//创建NVIC的初始化结构体
  37.         //Usart1 NVIC 配置
  38.     NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
  39.         NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3
  40.         NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;                //子优先级3
  41.         NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                        //IRQ通道使能
  42.         NVIC_Init(&NVIC_InitStructure);        //根据指定的参数初始化VIC寄存器
  43.         USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断
  44.   


  45. }

  1. void usart2_init(u32 bound)
  2. {  
  3.         //GPIO端口设置
  4.         GPIO_InitTypeDef GPIO_InitStructure;
  5.     USART_InitTypeDef USART_InitStructure;
  6.        

  7.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//使能GPIOA时钟
  8.         RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);//使能USART2时钟
  9.        

  10.         GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;        //PA2 tx
  11.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;        //复用推挽
  12.         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  13.     GPIO_Init(GPIOA, &GPIO_InitStructure);
  14.    
  15.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;//PA3 Rx
  16.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
  17.     GPIO_Init(GPIOA, &GPIO_InitStructure);  

  18.        
  19. //USART2配置
  20.         USART_InitStructure.USART_BaudRate = bound;//波特率设置
  21.         USART_InitStructure.USART_WordLength = USART_WordLength_8b;//8位数据长度
  22.         USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
  23.         USART_InitStructure.USART_Parity = USART_Parity_No;///奇偶校验位
  24.         USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
  25.         USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//收发模式

  26.     USART_Init(USART2, &USART_InitStructure); ; //初始化串口
  27.   
  28.     USART_Cmd(USART2, ENABLE);                    //使能串口
  29. }
  30. //串口2中断配置
  31. void usart2_NVIC__init(void)
  32. {
  33.         NVIC_InitTypeDef NVIC_InitStructure;
  34.        
  35.         NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; //使能串口2中断
  36.         NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //先占优先级2级
  37.         NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //从优先级2级
  38.         NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
  39.         NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
  40.        
  41.         USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启中?


  42. }

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

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

 楼主| 结合国际经验 发表于 2023-5-30 16:07 | 显示全部楼层
电力表与单片机之间
单片机——>电力表
MODBUS_RTU通讯协议补充
电力表提供了RS485通讯接口,采用MODBUS——RTU通讯规约。
 楼主| 结合国际经验 发表于 2023-5-30 16:08 | 显示全部楼层
电力表与单片机之间
单片机——>电力表
MODBUS_RTU通讯协议补充
电力表提供了RS485通讯接口,采用MODBUS——RTU通讯规约。
 楼主| 结合国际经验 发表于 2023-5-30 16:08 | 显示全部楼层
例如:主机发送数据帧:读三相电流值

存电流寄存器的起始地址是“00H,45H”(查说明书MODBUS——RTU地址信息表得,即69),存放三相电流的寄存器一共有六个,所以寄存器数是“00H,06H”。
94726475af10e7031.png
校验码是通过专用计算器算的。 928146475af02bd80d.png
 楼主| 结合国际经验 发表于 2023-5-30 16:09 | 显示全部楼层
所以定义读取电压、电流、功率的数组如下:
  1.         u8 current[8]={0x01,0x03,0x00,0x45,0x00,0x06,0xD4,0x1D};
  2.         u8 voltage[8]={0x01,0x03,0x00,0x39,0x00,0x06,0x15,0xC5};
  3.         u8 power[8]={0x01,0x03,0x00,0x4B,0x00,0x06,0xB5,0xDE};
 楼主| 结合国际经验 发表于 2023-5-30 16:09 | 显示全部楼层
串口发送指令补充

  1. //发送一个字节
  2. void sendByte(USART_TypeDef* USARTx,u8 date)
  3. {
  4.         USART_SendData(USARTx, date);
  5.         while(USART_GetFlagStatus(USARTx, USART_FLAG_TXE)==RESET);
  6. }

  7. //发送两个字节
  8. void sendTwoByte(USART_TypeDef* USARTx,u16 date)
  9. {
  10.         u8 temp_h,temp_l;
  11.         temp_h =(date&0xff00)>>8;
  12.         temp_l=(date&0xff);
  13.         sendByte(USARTx,temp_h);
  14.         sendByte(USARTx,temp_l);
  15.        
  16.        
  17. }

  18. //发送四个字节
  19. void sendFourByte(USART_TypeDef* USARTx,u32 date)
  20. {
  21.         u8 temp1,temp2,temp3,temp4;
  22.         temp1 =(date&0xff000000)>>24;
  23.         temp2=(date&0xff0000)>>16;
  24.         temp3=(date&0xff00)>>8;
  25.         temp4=(date&0xff);
  26.         sendByte(USARTx,temp1);
  27.         sendByte(USARTx,temp2);
  28.         sendByte(USARTx,temp3);
  29.         sendByte(USARTx,temp4);
  30.        
  31.        
  32. }
  33. //发送8位数组
  34. void sendArry(USART_TypeDef* USARTx,u8 *arry,u8 num)
  35. {
  36.         u8 i;
  37.         for(i=0;i<num;i++)
  38.         {
  39.                 sendByte(USARTx,arry[i]);
  40.         }
  41.         while(USART_GetFlagStatus(USARTx, USART_FLAG_TC)==RESET);
  42. }
  43. //发送字符串
  44. void Usart_Sendstr(USART_TypeDef* USARTx,char*str)
  45. {
  46.         u8 i=0;
  47.         do
  48.         {
  49.                 sendByte(USARTx,str[i]);
  50.                 i++;
  51.         }
  52.         while(*(str+i)!='\n');
  53.         while(USART_GetFlagStatus(USARTx, USART_FLAG_TC)==RESET);
  54.        
  55. }
 楼主| 结合国际经验 发表于 2023-5-30 16:10 | 显示全部楼层
单片机接收到命令后,通过串口1向电力表发送相应指令,电力表通过串口1返回数据
例如:
139656475af5410034.png
其中数据段一共有12个字节,没4个字节表示一相的电流值。
 楼主| 结合国际经验 发表于 2023-5-30 16:10 | 显示全部楼层
串口1接收到数据后进入串口1的中断服务子程序,把接收到的数据存在接收缓存寄存器里面,具体程序如下:
  1. //串口1中断服务函数
  2. //USART_RX_BUF[17];接收缓冲,最大17个字节 。 USART_RX_STA;接收状态标记,记录接收到第几个字节
  3. void USART1_IRQHandler(void)
  4. {
  5.         u8 Res;
  6.        
  7.         if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断
  8.                 {
  9.                        
  10.                         Res =USART_ReceiveData(USART1);        //读取接收到的数据
  11.                         USART_RX_BUF[USART_RX_STA] = Res;
  12.                         USART_RX_STA++;
  13.                         if(USART_RX_STA==17)//返回的数据一共有17个字节
  14.                         {
  15.                                 USART_RX_STA=0;
  16.                                 flag =1;//flag是接收完成标志位
  17.                        
  18.                         }
  19.                
  20.                 }
  21. }
 楼主| 结合国际经验 发表于 2023-5-30 16:11 | 显示全部楼层
单片机再把返回的数据发送给上位机。
  1. if(flag)//串口1接收中断完成
  2.                 {       
  3.                        
  4.                         for(t=3;t<15;t++)//数组里的第四个字节开始是电流值
  5.                         {
  6.                                
  7.                                 USART_SendData(USART2, USART_RX_BUF[t]);//向串口2发送数据
  8.                                 while(USART_GetFlagStatus(USART2,USART_FLAG_TC)!=SET);//等待发送结束
  9.                         }
  10.                         flag=0;
  11.                 }
 楼主| 结合国际经验 发表于 2023-5-30 16:22 | 显示全部楼层
只接A相负载的实验结果如图

289196475b2485fee6.png
 楼主| 结合国际经验 发表于 2023-5-30 16:23 | 显示全部楼层
可见A相的电流值为“32 30 20 C5”,这是一个4个字节表示的浮点型数据,标准的IEEE-754标准,后续还要对数据进行处理,直接用ASCLL码表示浮点型。
 楼主| 结合国际经验 发表于 2023-5-30 16:23 | 显示全部楼层
数据处理
上次已经通过串口读到电表采集的电流值了,但是它是用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,具体程序如下:
  1.                         u32 temp1=0,temp2=0,temp3=0;//用于存放每相转化后的32位16进制数
  2.                         temp1=temp1|(USART_RX_BUF[3]<<24);//USART_RX_BUF[3]变为temp1的高8位(25~32位)
  3.                         temp1=temp1|(USART_RX_BUF[4]<<16);
  4.                         temp1=temp1|(USART_RX_BUF[5]<<8);
  5.                         temp1=temp1|(USART_RX_BUF[6]);
 楼主| 结合国际经验 发表于 2023-5-30 16:23 | 显示全部楼层
USART_RX_BUF[]是一个含有17个元素的全局变量数组,用来存放电表返回的数据。从第4个元素开始是数据值,故需要从USART_RX_BUF[3]开始取值。

extern u8  USART_RX_BUF[17];
 楼主| 结合国际经验 发表于 2023-5-30 16:24 | 显示全部楼层
!!!注意全局变量不能重复定义,只定义在一个.h文件中就可以了。

422366475b2a9c077e.png
 楼主| 结合国际经验 发表于 2023-5-30 16:25 | 显示全部楼层
2、现在16进制的32位数已经保存在了temp1这个内存单元中,下面要把temp1内存单元的数按照IEEE-754标准转化成10进制浮点型。
16进制(32位)转化成10进制float程序如下:
  1. //============================================================
  2. //函数名称 :Float32
  3. //函数功能 :将32位的float转换为10进制数,IEEE 754标准
  4. //输入变量 :NumFloat32:待转换32位浮点数       
  5. //返 回 值 :计算结果 无符号整形 u32 ,返回-1为负数或者超限;
  6. //============================================================
  7. float Float32(u32 NumFloat32)
  8. {

  9.         signed char  sbit, ebits;// sbit符号位,ebits指数位
  10.         signed int mbits,temp,i;//mbits位数位
  11.         float Float32_sum;
  12.         float Float32_result =0;
  13.         temp = 0x00400000;
  14.         Float32_sum = 1.0;
  15.         i = 1;

  16.         sbit = NumFloat32 >> 31;
  17.         ebits = (NumFloat32 >> 23) & 0xff;

  18.         if (sbit)    //若需要负数部分,sbit为1时,result*-1,去掉判断;
  19.         {
  20.                 return -1;
  21.         }
  22.         ebits -= 127;    //得到阶码<0,即结果小于1,返回0;
  23.         if (ebits < 0)//负数
  24.         {
  25.                 return 0;
  26.         }
  27.         while (temp != 0)
  28.         {
  29.                 mbits = NumFloat32 & temp;
  30.                 if (mbits!=0)
  31.                         Float32_sum += pow(2, -i);//pow是指数运算函数,2是底数,-i是指数
  32.                 temp = temp >> 1;
  33.                 i++;
  34.         }
  35.        
  36.         Float32_result =Float32_sum* pow(2, ebits);
  37.        
  38.         return Float32_result;

  39. }

 楼主| 结合国际经验 发表于 2023-5-30 16:25 | 显示全部楼层
2、现在16进制的32位数已经保存在了temp1这个内存单元中,下面要把temp1内存单元的数按照IEEE-754标准转化成10进制浮点型。
16进制(32位)转化成10进制float程序如下:
  1. //============================================================
  2. //函数名称 :Float32
  3. //函数功能 :将32位的float转换为10进制数,IEEE 754标准
  4. //输入变量 :NumFloat32:待转换32位浮点数       
  5. //返 回 值 :计算结果 无符号整形 u32 ,返回-1为负数或者超限;
  6. //============================================================
  7. float Float32(u32 NumFloat32)
  8. {

  9.         signed char  sbit, ebits;// sbit符号位,ebits指数位
  10.         signed int mbits,temp,i;//mbits位数位
  11.         float Float32_sum;
  12.         float Float32_result =0;
  13.         temp = 0x00400000;
  14.         Float32_sum = 1.0;
  15.         i = 1;

  16.         sbit = NumFloat32 >> 31;
  17.         ebits = (NumFloat32 >> 23) & 0xff;

  18.         if (sbit)    //若需要负数部分,sbit为1时,result*-1,去掉判断;
  19.         {
  20.                 return -1;
  21.         }
  22.         ebits -= 127;    //得到阶码<0,即结果小于1,返回0;
  23.         if (ebits < 0)//负数
  24.         {
  25.                 return 0;
  26.         }
  27.         while (temp != 0)
  28.         {
  29.                 mbits = NumFloat32 & temp;
  30.                 if (mbits!=0)
  31.                         Float32_sum += pow(2, -i);//pow是指数运算函数,2是底数,-i是指数
  32.                 temp = temp >> 1;
  33.                 i++;
  34.         }
  35.        
  36.         Float32_result =Float32_sum* pow(2, ebits);
  37.        
  38.         return Float32_result;

  39. }

 楼主| 结合国际经验 发表于 2023-5-30 16:26 | 显示全部楼层
3、后来师兄说利用共用体就可以实现16进制(32位)转化成10进制float的功能,不用调用复杂的Float32函数。
共用体可以使不同的数据类型来共享相同的地址空间。首先定义一个共用体,既可以存放浮点型又可以存放整型。程序如下:

  1. union data
  2. {
  3.         float dataInFloat;//存放浮点型数
  4.         u32 dataInUnsingedInt;//存放整数
  5. };          

 楼主| 结合国际经验 发表于 2023-5-30 16:26 | 显示全部楼层
定义共用体变量:

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

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

                        currentData.dataInUnsingedInt = temp1;
                        Turn_Float_to_str(currentData.dataInFloat);
您需要登录后才可以回帖 登录 | 注册

本版积分规则

66

主题

775

帖子

1

粉丝
快速回复 在线客服 返回列表 返回顶部