打印
[其他ST产品]

基于STM32和Zigbee的电力表数据采集

[复制链接]
694|23
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
项目描述
要求:
1、监测设备一天的用电量。
2、统计开机、关机时间,计算使用率。
3、有可能的话实现手机端实时监控

预计方案:
STM32单片机通过RS-485串口驱动电力表模块采集数据,然后再通过TTL串口将数据发送给ZigBee终端。ZigBee终端在接收到数据后通过无线传输将数据发送给协调器,ZigBee协调器通过RS232串口通讯将数据发送给上位机。上位机实时显示数据。另外,还可以开发一款手机APP,上位机将数据保存,发送到手机端,通过手机端对数据进行实时监控。
其中,电力表模块、STM32模块、ZigBee终端模块这三部分组成一个数据采集模块,放置在机器人上,用于采集数据。ZigBee协调器模块和上位机构成显示模块,用于显示耗电量、使用效率等数据。
项目系统结构图如下:
————————————————
版权声明:本文为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初始化函数
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.9
        GPIO_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;//PA10
    GPIO_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); //初始化串口1
   
    USART_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 ;//抢占优先级3
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;                //子优先级3
        NVIC_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 tx
    GPIO_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 Rx
    GPIO_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);//开启中?


}

使用特权

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

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

使用特权

评论回复
地板
结合国际经验|  楼主 | 2023-5-30 16:07 | 只看该作者
电力表与单片机之间
单片机——>电力表
MODBUS_RTU通讯协议补充
电力表提供了RS485通讯接口,采用MODBUS——RTU通讯规约。

使用特权

评论回复
5
结合国际经验|  楼主 | 2023-5-30 16:08 | 只看该作者
电力表与单片机之间
单片机——>电力表
MODBUS_RTU通讯协议补充
电力表提供了RS485通讯接口,采用MODBUS——RTU通讯规约。

使用特权

评论回复
6
结合国际经验|  楼主 | 2023-5-30 16:08 | 只看该作者
例如:主机发送数据帧:读三相电流值

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

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

使用特权

评论回复
7
结合国际经验|  楼主 | 2023-5-30 16:09 | 只看该作者
所以定义读取电压、电流、功率的数组如下:
        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};

使用特权

评论回复
8
结合国际经验|  楼主 | 2023-5-30 16:09 | 只看该作者
串口发送指令补充

//发送一个字节
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);
       
}

使用特权

评论回复
9
结合国际经验|  楼主 | 2023-5-30 16:10 | 只看该作者
单片机接收到命令后,通过串口1向电力表发送相应指令,电力表通过串口1返回数据
例如:

其中数据段一共有12个字节,没4个字节表示一相的电流值。

使用特权

评论回复
10
结合国际经验|  楼主 | 2023-5-30 16:10 | 只看该作者
串口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是接收完成标志位
                       
                        }
               
                }
}

使用特权

评论回复
11
结合国际经验|  楼主 | 2023-5-30 16:11 | 只看该作者
单片机再把返回的数据发送给上位机。
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;
                }

使用特权

评论回复
12
结合国际经验|  楼主 | 2023-5-30 16:22 | 只看该作者
只接A相负载的实验结果如图

使用特权

评论回复
13
结合国际经验|  楼主 | 2023-5-30 16:23 | 只看该作者
可见A相的电流值为“32 30 20 C5”,这是一个4个字节表示的浮点型数据,标准的IEEE-754标准,后续还要对数据进行处理,直接用ASCLL码表示浮点型。

使用特权

评论回复
14
结合国际经验|  楼主 | 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,具体程序如下:
                        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]);

使用特权

评论回复
15
结合国际经验|  楼主 | 2023-5-30 16:23 | 只看该作者
USART_RX_BUF[]是一个含有17个元素的全局变量数组,用来存放电表返回的数据。从第4个元素开始是数据值,故需要从USART_RX_BUF[3]开始取值。

extern u8  USART_RX_BUF[17];

使用特权

评论回复
16
结合国际经验|  楼主 | 2023-5-30 16:24 | 只看该作者
!!!注意全局变量不能重复定义,只定义在一个.h文件中就可以了。

使用特权

评论回复
17
结合国际经验|  楼主 | 2023-5-30 16:25 | 只看该作者
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;

}

使用特权

评论回复
18
结合国际经验|  楼主 | 2023-5-30 16:25 | 只看该作者
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;

}

使用特权

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

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

使用特权

评论回复
20
结合国际经验|  楼主 | 2023-5-30 16:26 | 只看该作者
定义共用体变量:

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

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

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

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

47

主题

601

帖子

1

粉丝