本文基于实际验证数据,从低功耗设计与超声系统功耗两个维度,剖析Zynq SoC搭配SDIO WiFi在便携超声设备中的产品化瓶颈。
一、背景:便携超声的功耗挑战
便携式超声设备对功耗极为敏感——电池容量有限,而超声前端(AFE)、FPGA信号处理、无线传输三者均为耗电大户。采用Xilinx Zynq SoC(如7010)集成ARM Cortex-A9与FPGA,理论上兼具灵活性与性能,但实际产品化时,功耗与发热成为首要拦路虎。特别是通过SDIO接口外挂WiFi模块,进一步加剧了功耗失控。本文通过两轮实测,揭示问题根源。
二、Zynq SoC低功耗验证(裸机环境)
2.1 实验平台与工程搭建
硬件:Zynq 7010核心板 + 小米快充(5.19V供电)+ USB电流表
FPGA工程:例化ZYNQ处理器、PLL、QSPI、UART等外设(工程截图略)
FSBL(第一阶段引导程序):修改
xparameters.h,添加DDR地址宏定义;注
释
main.c中的DDR自检代码(避免低功耗模式下误触发)。
裸机Helloworld工程:修改链接脚本(将
ps7_ram_0_S_AXI_BASEADDR替换为ps7_ram_1_S_AXI_BASEADDR),主程序循环打印计数,20次后调用低功耗配置函数sleep_cfg()。
在FSBL_BSP修改xparameter.h,在此文件中增加:
/* Definitions for peripheralPS7_DDR_0 */
#define XPAR_PS7_DDR_0_S_AXI_BASEADDR 0x00100000#define XPAR_PS7_DDR_0_S_AXI_HIGHADDR 0x3FFFFFFF
#ifdef XPAR_PS7_DDR_0_S_AXI_BASEADDR#ifdef XPAR_PS7_DDR_0_S/** DDR Read/write test*/Status = DDRInitCheck();if (Status == XST_FAILURE) {fsbl_printf(DEBUG_GENERAL,"DDR_INIT_FAIL \r\n");/* Error Handling here */OutputStatus(DDR_INIT_FAIL);/** Calling FsblHookFallback instead of Fallback* since, devcfg driver is not yet initialized*/FsblHookFallback();}#endif
2.2 低功耗配置步骤(sleep_cfg核心操作)
代码中依次执行(注释部分为调试打印,实际已屏蔽):
关闭中断(
cpsid if)使能L2 Cache动态时钟门控(写
l2cpl310寄存器)解锁SLCR(写
0xDF0D到解锁寄存器)使能SCU待机模式(设置
SCU_CONTROL)使能Topswitch时钟停止(
TOPSW_CLK_CTRL置位)设置CP15电源控制寄存器(开启动态时钟门控)
DDR进入自刷新模式(代码中已注释,实际未启用)
PLL旁路与关断(ARM PLL旁路并断电,DDR/IO PLL部分注释)
降低CPU时钟分频系数(
ARM_CLK_CTRL分频设为0x3F),执行wfi(等待中断)指令进入休眠
代码如下
低功耗代码实现如下,相关步骤均在sleep_cfg函数:#include <stdio.h>#include "platform.h"#include <stdio.h>#include "platform.h"#include "xil_io.h"#define wfi() __asm__("wfi")#define ddrc_ctrl_reg1 0xF8006060#define ddrc_para_reg3 0xF8006020#define ddr_clk_ctrl 0xF8000124#define dci_clk_ctrl 0xF8000128#define aper_reg 0xF800012Cvoid sleep_cfg(void){int data;//xil_printf("Step 1 : ---------------------------------------\n\r");//xil_printf("Disable interrupts. Execute cpsid if.\n\r");//xil_printf("\n\n\n\n");//xil_printf("Step 2 : ---------------------------------------\n\r");//xil_printf("Configure wake-up device.\n\r");//xil_printf("\n\n\n\n");//xil_printf("Step 3 : ---------------------------------------\n\r");//xil_printf("Enable L2 cache dynamic clock gating. Set l2cpl310.reg15_power_ctrl[dynamic_clk_gating_en]= 1.\n\r");data = Xil_In32(0xF8F02000 + 0X00000F80);//xil_printf("Before l2cpl310.reg15_power_ctrl[dynamic_clk_gating_en]: %x\n\r",data);data |= 0x03;Xil_Out32(0xF8F02000 + 0X00000F80, data);//xil_printf("Write l2cpl310.reg15_power_ctrl[dynamic_clk_gating_en]: %x\n\r",data);data = Xil_In32(0xF8F02000 + 0X00000F80);//xil_printf("After l2cpl310.reg15_power_ctrl[dynamic_clk_gating_en]: %x\n\r",data);//xil_printf("----------Unlock the SLCR -----------------------\n\r");//xil_printf("SLCR Unlock.\n\r");data = Xil_In32(0xF8000000 + 0X00000008);//xil_printf("Before slcr.Unlock: %x\n\r",data);data = 0xDF0D;Xil_Out32(0xF8000000 + 0X00000008, 0xDF0D);//xil_printf("Write slcr.Unlock: %x\n\r",data);//xil_printf("\n\n\n\n");//data = Xil_In32(aper_reg); //clock gate unused peripherals//printf("aper_reg = %x\n\r", (unsigned int) data);//data = 0x1600001;//Xil_Out32(aper_reg,data);//data = Xil_In32(aper_reg);//printf("aper_reg = %x\n\r", (unsigned int) data);//xil_printf("Step 4 : ---------------------------------------\n\r");//xil_printf("Enable SCU standby mode. Set mpcore.SCU_CONTROL_REGISTER[SCU_standby_enable] = 1.\n\r");data = Xil_In32(0xF8F00000 + 0X00000000);//xil_printf("Before slcr.TOPSW_CLK_CTRL[CLK_DIS]: %x\n\r",data);data |= 0x20;Xil_Out32(0xF8F00000 + 0X00000000, data);//xil_printf("Write slcr.TOPSW_CLK_CTRL[CLK_DIS]: %x\n\r",data);data = Xil_In32(0xF8F00000 + 0X00000000);//xil_printf("After slcr.TOPSW_CLK_CTRL[CLK_DIS]: %x\n\r",data);//xil_printf("\n\n\n\n");//xil_printf("Step 5 : ---------------------------------------\n\r");//xil_printf("Enable topswitch clock stop. Set slcr.TOPSW_CLK_CTRL[CLK_DIS] = 1.\n\r");data = Xil_In32(0xF8000000 + 0X0000016C);//xil_printf("Before slcr.TOPSW_CLK_CTRL[CLK_DIS]: %x\n\r",data);data |= 0x01;Xil_Out32(0xF8000000 + 0X0000016C, data);//xil_printf("Write slcr.TOPSW_CLK_CTRL[CLK_DIS]: %x\n\r",data);data = Xil_In32(0xF8000000 + 0X0000016C);//xil_printf("After slcr.TOPSW_CLK_CTRL[CLK_DIS]: %x\n\r",data);//xil_printf("\n\n\n\n");//xil_printf("Step 6 : ---------------------------------------\n\r");//xil_printf("Set cp15.power_control_register[dynamic_clock_gating] = 1.\n\r");//xil_printf("Enable write access to some system controlprocessor (CP15) registers.\n\r");//data = Xil_In32(0xF8000000 + 0X00000300);////xil_printf("Before slcr.AP_CTRL: %x\n\r",data);//data |= 0x03;//Xil_Out32(0xF8000000 + 0X00000300, data);////xil_printf("Write slcr.AP_CTRL: %x\n\r",data);//data = Xil_In32(0xF8000000 + 0X00000300);////xil_printf("After slcr.AP_CTRL: %x\n\r",data);//set CP15data = mfcp(XREG_CP15_POWER_CTRL);//printf("cp15 Reg = %x\n\r", (unsigned int) data);mtcp(XREG_CP15_POWER_CTRL,0x701);data = mfcp(XREG_CP15_POWER_CTRL);//printf("cp15 Reg = %x\n\r", (unsigned int) data);//xil_printf("\n\n\n\n");//xil_printf("Step 7 : ---------------------------------------\n\r");//xil_printf("Put the external DDR memory into self-refresh mode. Refer to section 10.9.6 DDR Power Reduction.\n\r");//data = Xil_In32(ddrc_ctrl_reg1);//data |= 0x00001000; //enable standby mode and dynamic clock gating//Xil_Out32(ddrc_ctrl_reg1,data);////data = Xil_In32(ddrc_para_reg3);//data |= 0x00100000; //enable standby mode and dynamic clock gating//Xil_Out32(ddrc_para_reg3,data);////data = Xil_In32(ddr_clk_ctrl);//data &= 0xFFFFFFF0; //enable standby mode and dynamic clock gating//Xil_Out32(ddr_clk_ctrl,data);////data = Xil_In32(dci_clk_ctrl);//data &= 0xFFFFFFF0; //enable standby mode and dynamic clock gating//Xil_Out32(dci_clk_ctrl,data);//xil_printf("\n\n\n\n");//xil_printf("Step 8 : ---------------------------------------\n\r");//xil_printf("Put the PLLs into bypass mode. Set slcr.{ARM, DDR, IO}_PLL_CTRL[PLL_BYPASS_FORCE] = 1.\n\r");data = Xil_In32(0xF8000000 + 0X00000100);//xil_printf("Before slcr.{ARM}_PLL_CTRL[PLL_BYPASS_FORCE]: %x\n\r",data);data |= 0x10;Xil_Out32(0xF8000000 + 0X00000100, data);//xil_printf("Write slcr.{ARM}_PLL_CTRL[PLL_BYPASS_FORCE]: %x\n\r",data);data = Xil_In32(0xF8000000 + 0X00000100);//xil_printf("After slcr.{ARM}_PLL_CTRL[PLL_BYPASS_FORCE]: %x\n\r",data);//xil_printf("\n\n\n\n");//CPU Haltdata = Xil_In32(0xF8000000 + 0X00000104);//xil_printf("Before slcr.{DDR}_PLL_CTRL[PLL_BYPASS_FORCE]: %x\n\r",data);data |= 0x10;Xil_Out32(0xF8000000 + 0X00000104, data);//xil_printf("Write slcr.{DDR}_PLL_CTRL[PLL_BYPASS_FORCE]: %x\n\r",data);data = Xil_In32(0xF8000000 + 0X00000104);//xil_printf("After slcr.{DDR}_PLL_CTRL[PLL_BYPASS_FORCE]: %x\n\r",data);//xil_printf("\n\n\n\n");////data = Xil_In32(0xF8000000 + 0X00000108);////xil_printf("Before slcr.{IO}_PLL_CTRL[PLL_BYPASS_FORCE]: %x\n\r",data);//data |= 0x10;//Xil_Out32(0xF8000000 + 0X00000108, data);////xil_printf("Write slcr.{IO}_PLL_CTRL[PLL_BYPASS_FORCE]: %x\n\r",data);//data = Xil_In32(0xF8000000 + 0X00000108);////xil_printf("After slcr.{IO}_PLL_CTRL[PLL_BYPASS_FORCE]: %x\n\r",data);////xil_printf("\n\n\n\n");//xil_printf("Step 9 : ---------------------------------------\n\r");//xil_printf("Shut down the PLLs. Set slcr.{ARM, DDR, IO}_PLL_CTRL[PLL_PWRDWN] = 1.\n\r");data = Xil_In32(0xF8000000 + 0X00000100);//xil_printf("Before slcr.{ARM}_PLL_CTRL[PLL_PWRDWN]: %x\n\r",data);data |= 0x02;Xil_Out32(0xF8000000 + 0X00000100, data);//xil_printf("Write slcr.{ARM}_PLL_CTRL[PLL_PWRDWN]: %x\n\r",data);data = Xil_In32(0xF8000000 + 0X00000100);//xil_printf("After slcr.{ARM}_PLL_CTRL[PLL_PWRDWN]: %x\n\r",data);//data = Xil_In32(0xF8000000 + 0X00000104);////xil_printf("Before slcr.{DDR}_PLL_CTRL[PLL_PWRDWN]: %x\n\r",data);//data |= 0x02;//Xil_Out32(0xF8000000 + 0X00000104, data);////xil_printf("Write slcr.{DDR}_PLL_CTRL[PLL_PWRDWN]: %x\n\r",data);//data = Xil_In32(0xF8000000 + 0X00000104);////xil_printf("After slcr.{DDR}_PLL_CTRL[PLL_PWRDWN]: %x\n\r",data);//while(1)//wfi();//data = Xil_In32(0xF8000000 + 0X00000108);////xil_printf("Before slcr.{IO}_PLL_CTRL[PLL_PWRDWN]: %x\n\r",data);//data |= 0x02;//Xil_Out32(0xF8000000 + 0X00000108, data);////xil_printf("Write slcr.{IO}_PLL_CTRL[PLL_PWRDWN]: %x\n\r",data);//data = Xil_In32(0xF8000000 + 0X00000108);////xil_printf("After slcr.{IO}_PLL_CTRL[PLL_PWRDWN]: %x\n\r",data);//xil_printf("Step 10 : ---------------------------------------\n\r");//xil_printf("Increase the clock divisor to slow down the CPU clock. Set slcr.ARM_CLK_CTRL[DIVISOR] = 0x3f.\n\r");data = Xil_In32(0xF8000000 + 0X00000120);//xil_printf("Before slcr.ARM_CLK_CTRL[DIVISOR]: %x\n\r",data);data |= (0x3F00);Xil_Out32(0xF8000000 + 0X00000120, data);//xil_printf("Write slcr.ARM_CLK_CTRL[DIVISOR]: %x\n\r",data);data = Xil_In32(0xF8000000 + 0X00000120);//xil_printf("After slcr.ARM_CLK_CTRL[DIVISOR]: %x\n\r",data);while(1){wfi();}}int main(){init_platform();int i=0,j=0,k=0;while(1){for(i=100;i>0;i--)for(j=100000; j>0; j--){}k=k+1;xil_printf("Hello Lowper Demo,%d\n\r",k);if(k==20)break;}sleep_cfg();return 0;
2.3 功耗实测对比
| 状态 | 电压 | 电流 | 功率 |
|---|---|---|---|
| 正常运行(打印循环) | 5.19V | 0.14A | 0.73W |
进入低功耗模式(sleep_cfg后) | 5.19V | 0.08A | 0.42W |
结论:裸机下功耗降低约42%,但该模式需要CPU停歇、PLL降频,无法支持实时超声信号处理,仅适用于深度休眠场景。
三、完整超声系统功耗验证(Linux + WiFi)
3.1 测试环境
系统启动后,自动运行无线AP(hostapd)、DHCP服务器、以及超声业务进程(
wifi_board.elf)。默认电流:1.68A @ 3.8V(约6.4W),典型手持超声功耗水平。
3.2 逐步剥离耗电模块
| 操作 | 电流变化 | 说明 |
|---|---|---|
| 初始状态 | 1.68A | 全功能运行 |
kill 683(hostapd) | 无明显变化 | 关闭AP发射对功耗影响甚微 |
kill 682(dhcpd) | 无明显变化 | DHCP后台开销可忽略 |
echo mem > /sys/power/state(系统挂起) | 降至约0.75A | 但系统睡眠,无法工作 |
| AFE PowerDown(关闭模拟前端) | 降至0.64A @ 3.8V(约2.4W) | 数据上传仍正常,但前端已不工作 |
3.3 关键发现
WiFi模块(SDIO接口)本身并非最大耗电源,关闭hostapd/dhcpd后电流几乎不变。
AFE(模拟前端)是功耗大头,关闭后功耗直接下降约60%(从6.4W降至2.4W)。
系统级挂起(
echo mem)虽能降低功耗,但无法保持实时采集,对产品无实际价值。即便杀掉WiFi进程,系统仍维持0.75A,睡眠模式对超声系统意义不大,因为唤醒恢复时间长,且无法同步采集。
四、总结:为何难以产品化?
| 瓶颈维度 | 具体问题 |
|---|---|
| 实时性与功耗矛盾 | Zynq运行Linux+WiFi协议栈时,CPU负载高,无法深度休眠;而深度休眠又无法响应超声触发信号。 |
| AFE功耗过高 | 即便优化数字部分,AFE的功耗仍占主导(约4W),电池供电下续航不足1小时。 |
| SDIO WiFi效率低 | SDIO接口带宽有限,且WiFi发射功耗不低,但与AFE相比次要;然而其协议栈(hostapd/dhcpd)增加了系统复杂度和待机电流。 |
| 散热问题 | 6W以上的持续功耗在小尺寸手持设备中难以被动散热,影响用户体验和可靠性。 |
| 启动与唤醒延迟 | 从挂起恢复需重新初始化DDR、PLL,耗时数百毫秒,无法满足实时成像需求。 |
五、未来
虽然Zynq+SDIO WIFI Linux方案,手持式产品化比较难,但是,对于数据采集,及其便携式方案,没有问题。
本文验证数据基于Zynq 7010平台,不同厂商模块或软件版本可能有差异,请以实际测试为准。