简介:这份资源面向嵌入式软硬件工程师与电力仪表开发者,提供一套基于STM32F103C8T6的三相电能计量完整实现方案,主控搭配ATT7022E高精度计量芯片与HT7036低压差稳压芯片,可用于智能电表、配电监测终端等场景的快速原型搭建与二次开发。压缩包共408个文件,约15.51MB,以C源码、头文件、工程配置与驱动文件为主,另含PDF原理图、芯片数据手册、模块说明书及ModbusPoll测试工具,覆盖从硬件设计到上位机验证的完整链路。资料中ATT7022E规格书详解寄存器配置、校准流程与SPI时序,HT7036手册给出典型应用电路与PCB布局建议;STM32例程支持8位总线模式,集成初始化、参数读写、电能累加与功率因数计算,并附带Modbus RTU协议栈与RS485适配代码。目前已有186人学习,适合需要打通计量采样、通信与电源设计的中高级开发者参考。
1. 三相电能计量硬件方案:为什么STM32F103C8T6加ATT7022E是绕不开的组合
做三相电能计量硬件,绕不开一个现实:MCU自带的12位ADC精度不够,温漂大,线性度在宽动态范围下根本撑不住0.5S级计量要求。所以行业里最常见的做法是MCU加一颗专用计量芯片,MCU负责管理和通信,计量芯片负责采样和计算。STM32F103C8T6加ATT7022E就是这套思路里被大量产品验证过的组合——前者资源够用、价格低、生态成熟,后者是三相互感器接入的专用计量IC,有功无功视在功率、有效值、功率因数、谐波都能算,SPI接口读数据,省去自己写DFT的麻烦。
这套方案适合谁?做三相导轨表、配电监测终端、充电桩计量模块、工业能耗采集器的硬件工程师。你需要同时搞定三件事:计量芯片的外围模拟前端、STM32的SPI通信与数据校准、以及整板电源与隔离。HT7036在这类设计里通常作为电源管理参考,提供系统所需的稳压与监控,具体用法要看整板供电架构。下面按我实际调试的顺序,从芯片选型一路讲到校准和避坑。
2. 硬件架构与关键器件选型:从互感器到SPI的完整链路
2.1 三相计量通道的信号链拆解
三相四线制下,每相需要一路电压采样和一路电流采样,ATT7022E提供6路ADC输入,正好对应3路电压加3路电流。电压通道一般用电阻分压网络,把220V相电压降到芯片可接受的毫伏级;电流通道用电流互感器或分流器,经抗混叠滤波后进芯片。这里有个容易忽略的点:ATT7022E的电压通道输入范围是±1V左右(以实际数据手册为准),分压电阻的温漂直接影响计量精度,建议用25ppm/℃以内的金属膜电阻,且分压比要留足余量。
电流互感器二次侧必须接 burden 电阻,把电流转成电压。burden 电阻的精度和温漂同样关键,1%精度的普通电阻在温度变化20℃时可能带来0.2%以上的误差,直接吃掉你的计量精度预算。我一般选0.1%精度、25ppm/℃的电阻,虽然贵一点,但省去后面反复校准的麻烦。
2.2 ATT7022E外围电路:晶振、基准与滤波
ATT7022E需要外部晶振,典型值是24.576MHz,这个频率和内部PLL配合产生采样时钟。晶振的负载电容要按厂家推荐值配,不要凭感觉选,否则起振困难或频率偏移会导致计量误差。基准电压方面,芯片内部有基准,但要求外部加去耦电容,位置要尽量靠近基准引脚,容值按数据手册推荐,一般是0.1μF加10μF组合。
抗混叠滤波器是另一个重点。电压和电流通道都需要RC低通,截止频率要低于采样频率的一半。ATT7022E的采样率与晶振和配置有关,常见做法是把截止频率设在1kHz到2kHz之间。电容用C0G或X7R材质,电阻用高精度薄膜电阻。滤波器的电阻会与芯片输入阻抗形成分压,计算时要一并考虑,否则实际增益和设计值对不上。
2.3 HT7036电源管理参考设计的接入方式
HT7036在这类方案里通常承担系统电源的稳压与监控。三相表内部一般需要3.3V给MCU和计量芯片的数字部分,以及±5V或±3.3V给模拟部分。HT7036如果作为LDO使用,要注意它的输入电压范围和输出电流能力是否覆盖整板功耗。更关键的是模拟电源和数字电源的隔离——计量芯片的模拟部分对电源噪声非常敏感,建议用独立的LDO供电,或者在HT7036输出后加LC滤波再供给模拟部分。
参考设计里常见的问题是地平面分割。模拟地和数字地要在芯片下方单点连接,不要大面积铺铜混在一起。ATT7022E的AGND和DGND引脚要分别走线,最后在芯片底部汇合。这个细节在原理图上很容易画错,但改板成本很高,画的时候就要盯住。
3. STM32F103C8T6与ATT7022E的SPI通信与数据读取
3.1 SPI接口配置与寄存器读写时序
ATT7022E的SPI接口是从机模式,STM32F103C8T6作为主机。SPI模式要按芯片要求配置,通常是模式1或模式3,具体看数据手册的时序图。时钟频率不要一上来就跑最高,先用低速验证通信,再逐步提高。ATT7022E的寄存器读写有固定格式:先发命令字节,再读或写数据。命令字节包含地址和读写标志位。
// STM32F103C8T6 SPI1 初始化,用于与ATT7022E通信 // SPI1 引脚:PA5-SCK, PA6-MISO, PA7-MOSI, PA4-片选 void ATT7022E_SPI_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE); // SCK/MOSI 复用推挽输出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); // MISO 浮空输入 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); // 片选手动控制,先置高 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_SetBits(GPIOA, GPIO_Pin_4); // SPI1 配置:主机模式,模式3,8位,时钟分频256先低速验证 SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode = SPI_Mode_Master; SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; SPI_InitStructure.SPI_CRCPolynomial = 7; SPI_Init(SPI1, &SPI_InitStructure); SPI_Cmd(SPI1, ENABLE); }这段代码把SPI1配成主机、模式3、软件片选。分频256是为了先确保低速下通信稳定,等寄存器读写验证通过后再改到64或32。CPOL和CPHA的选择必须和ATT7022E的时序要求一致,如果读出来全是0或全是0xFF,先查这两个参数。
3.2 计量寄存器读取与数据格式转换
ATT7022E的计量数据是24位有符号数,存放在连续寄存器里。读的时候要按地址顺序读,注意高低字节顺序。读回来的原始值需要根据校准系数转换成实际物理量。比如电压通道,原始值乘以电压系数得到伏特;电流通道乘以电流系数得到安培。系数在校准环节确定。
// 读取ATT7022E的24位计量寄存器,addr为寄存器地址 // 返回值为有符号24位整数扩展成32位 int32_t ATT7022E_ReadReg24(uint8_t addr) { uint8_t cmd = addr | 0x80; // 读命令,最高位置1 uint8_t buf[3]; int32_t val; GPIO_ResetBits(GPIOA, GPIO_Pin_4); // 片选拉低 SPI_ReadWriteByte(cmd); // 发命令 buf[0] = SPI_ReadWriteByte(0xFF); // 读高字节 buf[1] = SPI_ReadWriteByte(0xFF); // 读中字节 buf[2] = SPI_ReadWriteByte(0xFF); // 读低字节 GPIO_SetBits(GPIOA, GPIO_Pin_4); // 片选拉高 val = ((int32_t)buf[0] << 16) | ((int32_t)buf[1] << 8) | buf[2]; // 24位有符号数扩展:如果最高位为1,则高8位补1 if (val & 0x00800000) { val |= 0xFF000000; } return val; }这里的关键是24位有符号数的符号扩展。如果漏掉这一步,负值会变成很大的正数,功率方向判断就会出错。读回来的原始值先存着,校准后再乘系数。SPI_ReadWriteByte是阻塞式收发,实际产品里可以改成DMA,但调试阶段阻塞式更直观。
3.3 校准系数写入与掉电保存
ATT7022E内部有校准寄存器,增益和相位校准值写进去后生效。但芯片本身不一定带非易失存储,所以校准系数通常存在STM32的Flash里,上电时由MCU写入ATT7022E。写入顺序要注意:先写增益,再写相位,最后写其他配置。写完后要读回来验证,确认写入成功。
// 将校准系数写入ATT7022E并回读验证 void ATT7022E_WriteCalib(uint8_t addr, uint32_t value) { uint8_t cmd = addr & 0x7F; // 写命令,最高位清0 GPIO_ResetBits(GPIOA, GPIO_Pin_4); SPI_ReadWriteByte(cmd); SPI_ReadWriteByte((value >> 16) & 0xFF); SPI_ReadWriteByte((value >> 8) & 0xFF); SPI_ReadWriteByte(value & 0xFF); GPIO_SetBits(GPIOA, GPIO_Pin_4); }写入后建议延时几毫秒再回读,给芯片内部逻辑留出处理时间。校准系数存在STM32的Flash页里,注意擦写寿命,不要频繁写。一般只在产线校准和现场校准时写,运行时不写。
4. 计量校准与精度验证:从原始值到0.5S级
4.1 增益校准与相位校准的操作顺序
校准分两步:增益校准和相位校准。增益校准在纯阻性负载下做,分别加额定电压和额定电流,读回原始值,算出系数。相位校准在感性或容性负载下做,调整相位补偿值使功率因数接近1。顺序不能反,先增益后相位,否则相位校准会受增益误差影响。
校准点一般选三个:5%Ib、100%Ib、Imax。在5%Ib点看线性度,100%Ib点定增益,Imax点验证过载能力。每个点读多次取平均,减少随机误差。校准环境温度要记录,因为温漂会影响系数,后续如果要做温度补偿,这些数据是基础。
4.2 用标准表比对时的三个注意点
第一,标准表的精度要高于被校表一个等级。校0.5S级表,标准表至少0.1级。第二,接线要确保电压和电流同相,相序错了功率符号会反。第三,等待时间要够,ATT7022E内部有滤波器,上电后需要几百毫秒稳定,读数据前先延时。
比对时读的是STM32算出来的实际值,不是原始值。所以校准系数要先写入,再读实际值比对。如果偏差大,先查系数写入是否成功,再查标准表接线。
4.3 温度漂移与长期稳定性验证
温度漂移主要来自分压电阻、burden电阻和芯片内部基准。验证方法是在高低温箱里跑几个温度点,每个点稳定后读计量值,画漂移曲线。如果漂移超过精度预算,就要换更低温度系数的电阻,或者在软件里做温度补偿。
长期稳定性靠老化测试,连续跑48小时以上,看数据是否漂移。ATT7022E本身稳定性不错,问题往往出在外围电阻和电源上。电源纹波大,计量值会跳,用示波器看模拟电源纹波,超过10mV就要加滤波。
5. 避坑与常见问题排查:血泪经验五条
5.1 现象:SPI读回来全是0xFF或0x00
原因:片选时序不对,或者SPI模式配错。ATT7022E要求片选拉低后第一个时钟沿之前数据就准备好,如果片选和时钟同时动作,可能采不到。另外CPOL/CPHA配错也会导致数据全错。
解决:先用示波器看片选、时钟、MOSI、MISO四根线。片选拉低到第一个时钟沿之间要有明显延时。SPI模式改成模式1试试,如果模式3不行就换模式1。时钟先降到最低,确认通信后再提速。
5.2 现象:计量值跳动大,功率读数不稳定
原因:模拟电源纹波大,或者抗混叠滤波器参数不对。电源纹波耦合到ADC输入端,直接反映在计量值上。滤波器截止频率太高,高频噪声没滤掉。
解决:用示波器看ATT7022E的模拟电源引脚,纹波超过10mV就加LC滤波或换LDO。检查RC滤波的截止频率,按1/(2πRC)算,确保低于采样率的一半。电容换C0G材质,电阻换薄膜电阻。
5.3 现象:小电流点误差大,5%Ib以下不准
原因:burden电阻精度不够,或者校准点没覆盖小电流。小电流时信号幅度小,噪声和失调的影响被放大。如果burden电阻温漂大,小信号下误差更明显。
解决:burden电阻换0.1%精度、25ppm/℃的。校准点增加1%Ib和5%Ib,看线性度。如果还是差,检查ATT7022E的增益设置,小信号下可能需要更高的PGA增益。
5.4 现象:三相功率不平衡,某一相读数明显偏低
原因:某一相的互感器或分压电阻焊接不良,或者ATT7022E的某一通道配置错误。也可能是PCB走线不对称,导致通道间串扰。
解决:先交换互感器输入,看问题是否跟着通道走。如果跟着通道走,查ATT7022E配置;如果跟着互感器走,查互感器和burden。PCB走线要保证三通道对称,模拟输入线尽量短,远离数字线。
5.5 现象:上电后ATT7022E不工作,晶振不起振
原因:晶振负载电容不匹配,或者晶振本身损坏。ATT7022E对晶振要求较高,负载电容要按数据手册配,不要随便用22pF。
解决:换晶振,负载电容按推荐值选。用示波器看晶振引脚,起振后应有稳定正弦波。如果不起振,先查焊接,再查负载电容。实在不行换有源晶振,但成本会增加。
6. 进阶技巧:用STM32内部Flash模拟EEPROM存校准系数
校准系数要掉电保存,但STM32F103C8T6没有真正的EEPROM,用内部Flash模拟是常见做法。Flash擦写按页操作,每页1KB或2KB,擦一次全页都清。所以不能每次改一个系数就擦整页,要先把整页读到RAM,改完再擦再写。
// 用STM32F103C8T6内部Flash保存校准系数 // 假设使用页地址0x0801F800(最后一页),存储结构体 #define CALIB_PAGE_ADDR 0x0801F800 #define CALIB_PAGE_SIZE 1024 typedef struct { uint32_t magic; // 标志字,用于判断是否已初始化 int32_t gainU[3]; // 三相电压增益 int32_t gainI[3]; // 三相电流增益 int32_t phase[3]; // 三相相位补偿 uint32_t crc; // 校验值 } CalibData_t; CalibData_t calibData; // 从Flash读取校准数据到RAM void Calib_LoadFromFlash(void) { memcpy(&calibData, (void*)CALIB_PAGE_ADDR, sizeof(CalibData_t)); if (calibData.magic != 0x5A5A5A5A) { // 未初始化,填默认值 memset(&calibData, 0, sizeof(CalibData_t)); calibData.magic = 0x5A5A5A5A; } } // 保存校准数据到Flash:先擦页,再写 void Calib_SaveToFlash(void) { FLASH_Unlock(); FLASH_ErasePage(CALIB_PAGE_ADDR); // 按半字写入,STM32F1 Flash编程为半字 uint16_t *pSrc = (uint16_t*)&calibData; uint32_t addr = CALIB_PAGE_ADDR; for (uint32_t i = 0; i < sizeof(CalibData_t) / 2; i++) { FLASH_ProgramHalfWord(addr, pSrc[i]); addr += 2; } FLASH_Lock(); }这段代码把校准系数打包成结构体,存到Flash最后一页。magic字段用来判断是否已经初始化过,避免第一次上电读到全0xFF。写入前先擦页,擦完再按半字写。注意STM32F1的Flash编程单位是半字,不能按字节写。擦写次数有限,一般10万次,产线校准和现场校准加起来不会超过这个数,但运行时要避免频繁写。
一个实用技巧:在RAM里维护一个副本,每次改系数先改RAM,再统一保存。保存时加一个延时,比如改完5秒后再写Flash,避免频繁擦写。如果掉电发生在延时期间,数据丢失,但校准系数不是实时数据,丢一次可以重新校准,问题不大。
另一个技巧是双页备份。用两页Flash轮流写,每页带一个序列号,上电时读序列号大的那页。这样即使写过程中掉电,至少有一页是完整的。STM32F103C8T6的Flash有64KB,拿两页出来做备份完全够用。
最后说一个我踩过的坑:Flash擦除时CPU会暂停,如果此时有中断触发,可能取不到指令。所以擦写操作要关中断,或者放到空闲任务里做。我一般是在校准完成后,关中断擦写,写完再开中断。时间很短,不影响计量。
希望帮到你。
本文还有配套的精品资源,点击获取