第1章 前言
多功能电表是采集配电参数的主要设备,应用广泛。Modbus通讯在工业网络通讯中应用十分广泛,而且方便,受到大家的欢迎。
电能作为工业和生产的主要能源之一,随着“碳达峰”和“碳中和”双碳概念的提出,节能降耗无论是从企业自身还是从政策上都势在必行;智能电表的功能不再是简单计量电能,而是通过赋能帮助企业发现节能空间。
随着工业发展和人民生活水平的提高,电力电子器件被广泛运用,社会用电结构发生了很大的变化,电能的质量不仅仅只是电压偏差和频率,谐波、无功和闪变危害日益突出;这就要求终端计量表能发现谐波、无功需量和闪变,并计算这些问题带来的电能损耗,并整改设计和保护设备。
1.1、编写原因
一直以来,想做一个仪表开发的简介,恰逢Tencent和沁恒联合举办的活动,提出一种《基于TencentOS Tiny和ch32v307的三相多功能表方案》。
1.2、版权说明
本套软件最初虽然是为了我们自用而开发,但现已开源。所以任何人都可以复制、传播和使用,无论是个人学习还是商业应用都没有限制。对应用方法及修改欢迎探讨,但对于在使用过程中出现了任何问题我们均不负责。
软件源码及其说明可以自由下载。
链接:https://share.weiyun.com/WD2Ttzp5 密码:cdh4xc
链接:https://pan.baidu.com/s/1Lfm_D4GYA7sm0pyVV1uvSA
提取码:ogmo
1.3、更新记录
这是第一个版本,我们已经实现了基于串行链路的Modbus RTU的移植,基于实序列的傅里叶变换和基于复数序列的傅里叶变换,基于CH32V307和电量计量芯片的硬件SPI通讯,实现电压、电流、频率、有功功率、无功功率、视在功率、有功电能、无功电能、视在电能、谐波电流、基波电流、谐波电压、基波电压等参数的读取,各参数在LCD上的显示。
软件设计及本文档更新记录如下:
版本 | 更新说明 | 作者 | 日期 |
---|---|---|---|
V1.0.0 | 初版,共5章,介绍了主要API函数 | 张恩寿 | 2022年8月10日 |
第2章 电量计量芯片配置
电量计量芯片采用RN8302B,是一颗较高性价比的专用DSP芯片,可以提供计量参数(全波、基波有功电能,全波、基波无功电能,全波、基波视在电能,有功、无功功率方向)和测量参数(全波和基波有功、无功、视在功率,全波、基波和谐波三相电压电流有效值,全波、基波功率因数,电压线频率,各相电压电流相角),七路 24bits ADC 采样数据也可存放至内置的波形存储单元中,供用户进行 FFT 分析,并且RN8302B提供固定采样率模式和同步采样模式,可以实现灵活配置,让用户无需软件实现准同步算法。
2.1、硬件连接
l 支持串行通信接口 SPI。工作在从属方式。
l SPI 接口速率:最大 3.5Mbps
l 传输可靠性:SPI 帧格式包含校验和字节
l 读波形缓存区支持 Burst 1/4/8/16 模式
l 3.3V/5V 兼容
2.2、SPI时序
SPI 写时序
SPI 读时序
2.3、电量计量芯片配置
代码语言:javascript复制void RN8302_interface_set(void)
{
GPIO_InitTypeDef GPIO_InitStructure={0};
SPI_InitTypeDef RN8302_structure={0};
//使能GPIOA时钟和SPI时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
//配置SPI时钟和MOSI引脚
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_15|GPIO_Pin_13;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
//配置SPI的MISO引脚
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_14;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
//配置SPI的NSS引脚
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_12);
//设置SPI的时钟极性和相位、设备模式为主机、字节传输为高字节在前、全双工模式
RN8302_structure.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_64;
RN8302_structure.SPI_CPHA=SPI_CPHA_2Edge;
RN8302_structure.SPI_CPOL=SPI_CPOL_Low;
RN8302_structure.SPI_CRCPolynomial=7;
RN8302_structure.SPI_DataSize=SPI_DataSize_8b;
RN8302_structure.SPI_Direction=SPI_Direction_2Lines_FullDuplex;
RN8302_structure.SPI_FirstBit=SPI_FirstBit_MSB;
RN8302_structure.SPI_Mode=SPI_Mode_Master;
RN8302_structure.SPI_NSS=SPI_NSS_Soft;
SPI_Init(SPI2, &RN8302_structure);
//SPI校验设置
SPI_Cmd(SPI2, ENABLE);
}
u8 RN8302_send_byte(u8 byte)
{
//等待发送缓冲区为空
while (RESET == SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_TXE));
//发送数据、等待发送完成
SPI_I2S_SendData(SPI2,byte);
while(RESET == SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_RXNE));
//返回接收数据
return(SPI_I2S_ReceiveData(SPI2));
}
//写寄存器
void RN8302_reg_write(u16 regAddr,u8 *regBuf,u8 regLen)
{
uint8_t i = 0;
uint8_t buf[2]={0};
uint8_t chksum = 0;
GPIO_ResetBits(GPIOB,GPIO_Pin_12);
buf[0] = (uint8_t)(regAddr&0xFF);
buf[1] = write_cmd_config|((uint8_t)(regAddr>>4))&0xF0;
for(i=0;i<2;i ){
RN8302_send_byte(buf[i]);
chksum = buf[i];
}
for(i=0;i<regLen;i ){
chksum = regBuf[i];
}
//最后一个字节是cs校验,所以要多写一个
for(i=0;i<regLen;i ){
RN8302_send_byte(regBuf[i]);
}
RN8302_send_byte(~chksum);
RN8302_CS_H();
}
//读寄存器
u8 RN8302_reg_read(u16 regAddr,u8 *regBuf,u8 regLen)
{
uint8_t i = 0,chksum = 0;
uint8_t buf[2]={0};
RN8302_CS_L();
buf[0] = (uint8_t)(regAddr&0x00FF);
buf[1] = ((uint8_t)(regAddr>>4))&0xF0|read_cmd_meter;
for(i=0;i<2;i )
{
RN8302_send_byte(buf[i]);
chksum = buf[i];
}
//最后一个字节是cs校验,所以要多收一个
for(i=0;i<regLen 1;i )
{
regBuf[i] = RN8302_send_byte(read_cmd_meter);
}
for(i=0;i<regLen;i )
{
chksum = regBuf[i];
}
RN8302_CS_H();
chksum ^= 0xFF;
if(chksum == regBuf[regLen])
return 0;
else
return 1;
}
2.4、电量计量芯片寄存器访问
代码语言:javascript复制//读电压
u32 RN8302_read_phase_voltage(u8 phase)
{
u8 regbuf[5];
u32 regtemp = 0;
const u16 regArry[]={0x0007,0x0008,0x0009};
if(RN8302_reg_read(regArry[phase],regbuf,4)==0)
{
regtemp = (regbuf[0]<<24)|(regbuf[1]<<16)|(regbuf[2]<<8)|(regbuf[3]);
if(regtemp>=0x80000000)
{
regtemp=(u32)((~(regtemp-1))&0x7ffffff);
}
else
{
regtemp = (regbuf[0]<<24)|(regbuf[1]<<16)|(regbuf[2]<<8)|(regbuf[3]);
}
}
return regtemp;
}
电量计量芯片采用RN8302B,提供的计量参数(全波、基波有功电能,全波、基波无功电能,全波、基波视在电能,有功、无功功率方向)和测量参数(全波和基波有功、无功、视在功率,全波、基波和谐波三相电压电流有效值,全波、基波功率因数,电压线频率,各相电压电流相角)读取方式与读取电压相同,只需变换寄存器地址即可,另外部分参数是带符号和以补码形式存取,读取转换时注意即可。
第3章 快速傅里叶变换
快速傅里叶变换在电力谐波分析应用较广,通过对采集数据进行傅里叶变换,可以检测出各次谐波电流或各次谐波电压的大小,指导配电滤波器的参数选取或有源电力滤波器的设计。
电量计量芯片采用RN8302B,具有七路 24bits ADC 采样数据也可存放至内置的波形存储单元中,供用户进行 FFT 分析,缓存区共768个地址单元,最大能存储六路ADC UA、UB、UC、IA或IN、IB、IC 一个周波128点的数据,如果采用固定采样率模式的话,采样率配置为6.4KHz,。
在本次活动中,快速傅里叶变换实现了基于ARM CMSIS DSP的FFT移植,运行效果较好,但由于该方式的参数表较多,占用内存较大,最终选择基于数字信号处理的实序列和复序列FFT实现。
3.1、实序列的快速傅里叶变换实现
由于实序列的变换过程中,变换结果往往都是复数,直流分量变为了真实值的两倍,原因是傅里叶变换幅值还原需要除上采样点数的1半,变换结果分布在正频和负频,负频在物理上无意义,但其确实含有能量,物理上能量一般与幅值对应,因此傅里叶变换幅值还原需要除上采样点数的1半,而直流分量既不属于正频也不属于负频,也不区分正直流和负直流,因此直流分量幅值还原需除上采样点数。
代码语言:javascript复制static void rfft_cal(float x[],u16 n)
{
int i,j,k,m,i1,i2,i3,i4,n1,n2,n4;
float a,e,cc,ss,xt,t1,t2;
for(j=1,i=1;i<16;i )
{
m=i;
j=2*j;
if(j==n)
{
break;
}
}
n1=n-1;
for(j=0,i=0;i<n1;i )
{
if(i<j)
{
xt=x[j];
x[j]=x[i];
x[i]=xt;
}
k=n/2;
while(k<(j 1))
{
j=j-k;
k=k/2;
}
j=j k;
}
for(i=0;i<n;i =2)
{
xt=x[i];
x[i]=xt x[i 1];
x[i 1]=xt-x[i 1];
}
n2=1;
for(k=2;k<=m;k )
{
n4=n2;
n2=2*n4;
n1=2*n2;
e=6.28318530718f/n1;
for(i=0;i<n;i =n1)
{
xt=x[i];
x[i]=xt x[i n2];
x[i n2]=xt-x[i n2];
x[i n2 n4]=-x[i n2 n4];
a=e;
for(j=1;j<=(n4-1);j )
{
i1=i j;
i2=i-j n2;
i3=i j n2;
i4=i-j n1;
cc=cos(a);
ss=sin(a);
a=a e;
t1=cc*x[i3] ss*x[i4];
t2=ss*x[i3]-cc*x[i4];
x[i4]=x[i2]-t2;
x[i3]=-x[i2]-t2;
x[i2]=x[i1]-t1;
x[i1]=x[i1] t1;
}
}
}
}
void rfft(float x[],u16 n,float y[],float rel[],float ima[])
{
u16 i=0;
rfft_cal(x,n);
for(i=0;i<(n/2 1);i )
{
rel[i]=x[i];
}
ima[0]=0.f;
ima[n/2]=0.f;
for(i=n-1;i>n/2;i--)
{
ima[n-i]=x[i];
}
for(i=0;i<(n/2 1);i )
{
y[i]=sqrt(rel[i]*rel[i] ima[i]*ima[i])*2/n;
}
y[0]=y[0]/2;
}
3.2、复序列的快速傅里叶变换实现
由于复序列在恢复真实值会存在一定误差,约为不万分之一,虽然该误差很小,使用中还是推荐使用基于实序列的快速傅里叶变换,一方面是RN8302B采样的数据本身就是实序列,另一方面实序列在恢复真实值时误差比复序列的小。
代码语言:javascript复制void kfft(float pr[],float pi[],u16 n,u8 k,float fr[],float fi[])
{
int it,m,is,i,j,nv,l0;
float p,q,s,vr,vi,poddr,poddi;
for (it=0; it<=n-1; it ) //将pr[0]和pi[0]循环赋值给fr[]和fi[]
{
m=it;
is=0;
for(i=0; i<=k-1; i )
{
j=m/2;
is=2*is (m-2*j);
m=j;
}
fr[it]=pr[is];
fi[it]=pi[is];
}
pr[0]=1.0;
pi[0]=0.0;
p=(2*PI)/(1.0f*n);
pr[1]=cos(p); //将w=e^-j2pi/n用欧拉公式表示
pi[1]=-sin(p);
for (i=2; i<=n-1; i ) //计算pr[]
{
p=pr[i-1]*pr[1];
q=pi[i-1]*pi[1];
s=(pr[i-1] pi[i-1])*(pr[1] pi[1]);
pr[i]=p-q; pi[i]=s-p-q;
}
for (it=0; it<=n-2; it=it 2)
{
vr=fr[it];
vi=fi[it];
fr[it]=vr fr[it 1];
fi[it]=vi fi[it 1];
fr[it 1]=vr-fr[it 1];
fi[it 1]=vi-fi[it 1];
}
m=n/2;
nv=2;
for (l0=k-2; l0>=0; l0--) //蝴蝶操作
{
m=m/2;
nv=2*nv;
for (it=0; it<=(m-1)*nv; it=it nv)
for (j=0; j<=(nv/2)-1; j )
{
p=pr[m*j]*fr[it j nv/2];
q=pi[m*j]*fi[it j nv/2];
s=pr[m*j] pi[m*j];
s=s*(fr[it j nv/2] fi[it j nv/2]);
poddr=p-q;
poddi=s-p-q;
fr[it j nv/2]=fr[it j]-poddr;
fi[it j nv/2]=fi[it j]-poddi;
fr[it j]=fr[it j] poddr;
fi[it j]=fi[it j] poddi;
}
}
for (i=0; i<=n-1; i )
{
pr[i]=sqrt(fr[i]*fr[i] fi[i]*fi[i]); //幅值计算
}
pr[0]=pr[0]/n;
for (i=1; i<=n-1; i )
{
pr[i]=pr[i]*2.f/n;
}
return;
}
第4章 Modbus-RTU移植
Modbus是全球第一个真正用于工业现场的总线协议。Modbus通讯在工业网络通讯中应用十分广泛,而且方便,受到大家的欢迎。一直以来,在我们自己的产品和项目中都多次使用Modbus通讯协议。每次都是使用者自行开发或者网上搜索符合要求的源码。但每次的应用都有不同,每次都需要很多的重复劳动。而且协议站如应用软件的紧密结合也使得代码有些混乱。所以一直以来都想要开发一个比较通用的协议栈能在后续的项目中复用,而不必每次都写一遍。
本次移植的Modbus协议源码为XTinyModbus,作者为张正,开源地址为:
https://gitee.com/IsYourGod/XTinyModbus
因为该软件趋于稳定,本次活动采用该软件,这里再次感谢作者的无私奉献和敬业的开发态度。
4.1、XTinyModbus的代码结构
XTinyModbus实现了RTU下的主机模式和从机模式,文档包括Doc文件夹、Example文件夹、Modbus文件夹、Port文件夹和MD_RTU_Config.h文件及Sys_Config.h文件,Example文件夹包括一些主机和从机的示例,接口包括了RS232和RS485;Modbus文件夹包括协议和应用程序,是比较核心的文件;Port文件夹主要是接口对接;MD_RTU_Config.h文件是一些控制配置列表长度,队列大小和是否使用操作系统等;Sys_Config.h文件配置采用从机还是主机,对应的串口号等。
与用户相关的主要是串口和定时器,以及配置文件MD_RTU_Config.h和Sys_Config.h,此外是用户的应用文件Modbus_RTU_APP文件,这里需要访问的数据在这里修改即可。
4.2、XTinyModbus之定时器配置
代码语言:javascript复制void TIM3_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
TIM_TimeBaseStructure.TIM_Period = arr;
TIM_TimeBaseStructure.TIM_Prescaler =psc;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE );
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM3, ENABLE);
}
void TIM3_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void TIM3_IRQHandler(void)
{
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
sys_tick_100us ;
#if !MD_RTU_USED_OS
#if MD_USD_SALVE
// MDSTimeHandler100US(sys_tick_100us);
MDSTimeHandler100US_1(sys_tick_100us);
#else
MDMTimeHandler100US(sys_tick_100us);
#endif
#endif
}
}
4.3、XTinyModbus之串口配置
代码语言:javascript复制void init_usart3(u32 baudRate)
{
USART_InitTypeDef USART_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStruct;
/* Enable GPIO clock */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 |
RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = baudRate;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
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;
/* Configure USARTz */
USART_Init(USART1, &USART_InitStructure);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
USART_Cmd(USART1, ENABLE);
NVIC_InitStruct.NVIC_IRQChannel=USART1_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority=0;
NVIC_Init(&NVIC_InitStruct);
//RS485RWConvInit();
}
void usart3_send_byte(u8 byte)
{
while(USART_GetFlagStatus(USART1,USART_FLAG_TC )==RESET);
USART_SendData(USART1,byte);
while(USART_GetFlagStatus(USART1,USART_FLAG_TC )==RESET);
}
void usart3_send_bytes(u8 *bytes,int len)
{
int i;
for(i=0;i<len;i )
{
usart3_send_byte(bytes[i]);
}
}
void usart3_send_string(char *string)
{
while(*string)
{
usart3_send_byte(*string );
}
}
void USART1_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void USART1_IRQHandler(void)
{
if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
uint8_t data = USART_ReceiveData(USART1);
#if !MD_RTU_USED_OS
#if MD_USD_SALVE
MDSSerialRecvByte_1(data);
#else
#if MDM_USD_USART3
MDMSerialRecvByte(data);
#endif
#endif
#else
extern Modbus_RTU modbus_RTU;
MD_RTU_MsgPut((PModbusBase)(&modbus_RTU), MD_RTU_MSG_HANDLE_ARG(&modbus_RTU),(void*)(data),0);
#endif
}
}
第5章 TencentOS-Tiny
腾讯物联网终端操作系统(TencentOS tiny)是腾讯面向物联网领域开发的实时操作系统,具有低功耗,低资源占用,模块化,安全可靠等特点,可有效提升物联网终端产品开发效率。TencentOS tiny 提供精简的 RTOS 内核,内核组件可裁剪可配置,可快速移植到多种主流 MCU 及模组芯片上。而且,基于RTOS内核提供了丰富的物联网组件,内部集成主流物联网协议栈(如 CoAP/MQTT/TLS/DTLS/LoRaWAN/NB-IoT 等),可助力物联网终端设备及业务快速接入腾讯云物联网平台。其开源地址为:https://gitee.com/Tencent/TencentOS-tiny
此次活动采用TencentOS tiny实时操作系统一方面主要是相关应用需要,modbus协议的同步,阻塞等;另一方面是活动主要内容之一,本着学习的态度,尝试不同的实时操作系统。
5.1、TencentOS tiny任务建立
此次活动建立了四个任务,第一个是参数显示任务,第二个是快速傅里叶变换谐波电流显示任务,第三个是Modbus通讯任务,第四个任务为应用创建任务,启动创建前三个任务,当前三个任务创建成功后,该任务自动销毁。
代码语言:javascript复制/***********任务栈*****************/
k_stack_t task_modbus_stack[4096];
k_stack_t task_disp_stack[4096];
k_stack_t task_rfft_stack[4096];
k_stack_t app_stack[4096];
/**********任务控制块***************/
k_task_t task_modbus;
k_task_t task_disp;
k_task_t app_task;
k_task_t task_rfft;
/**********任务入口函数**************/
static void task_modbus_entry(void* arg);
static void task_disp_entry(void* arg);
static void app_task_entry(void* arg);
static void task_rfft_entry(void* arg);
static void task_modbus_entry(void* arg)
{
while(K_TRUE)
{
MDS_RTU_Loop_1();
tos_task_delay(30);
}
}
static void task_disp_entry(void* arg)
{
while(K_TRUE)
{
show_parameter(key_num);
tos_task_delay(650);
}
}
static void task_rfft_entry(void* arg)
{
while(K_TRUE)
{
show_rfft_parameter(key_fft);
tos_task_delay(650);
}
}
static void app_task_entry(void* arg)
{
k_err_t statues=K_ERR_NONE;
statues=tos_task_create(&task_modbus,"modbus",task_modbus_entry,
K_NULL,7,task_modbus_stack,4096,0);
if(K_ERR_NONE==statues)
{
tos_kprintf("modbus线程创建成功rn");
}
statues=tos_task_create(&task_disp,"disp",task_disp_entry,K_NULL,
4,task_disp_stack,4096,0);
if(K_ERR_NONE==statues)
{
tos_kprintf("disp线程创建成功rn");
}
statues=tos_task_create(&task_rfft,"rfft",task_rfft_entry,
K_NULL,6,task_rfft_stack,4096,0);
if(K_ERR_NONE==statues)
{
tos_kprintf("rfft线程创建成功rn");
}
tos_task_destroy(&app_task);
}
int main(void)
{
Delay_Init();
led_key_init();
LCD_Init();
SPI_Flash_Init();
fnRN8302_Init();
TIM3_Int_Init(72-1,100-1);
MDS_RTU_APPInit_1();
tos_knl_init();
tos_task_create(&app_task,"APP",app_task_entry,K_NULL,
1,app_stack,4096,0);
tos_knl_start();
while(1);
}
5.2、测试最终视频
5.3、测试照片
6、 总结
校准仪输出 | 测试结果 | 误差 |
---|---|---|
电压20.000 | 19.99 | 0.05% |
电流1.4999 | 1.49 | 0.7% |
有功功率30.000 | 29.99 | 0.03% |
无功功率0.22 | 0.22 | 0% |
频率49.999 | 49.99 | 0.02% |
由此,经校准的电量计量芯片准确度较高;误差分析,由于LCD显示浮点数的函数默认输出两位小数,由此后面的位的数据将被舍去,造成误差。
相关附件如下: