June 3, 2022
Editor’s Note: This content is republished from the MicroZed Chronicles, with permission from the author.
One of the projects we are working on now is battery powered and only acts when awakened on a set schedule. This requires the Zynq-7000 device to power down into a low-power state on the SOM in between the awake and sleep periods.. Being battery powered, we want the power to be as low as possible so we will be powering off many peripheral voltage rails including the PL. We also want to put the PS into a low-power sleep state, which is then awakened by an RTC interrupt on a PS GPIO.
Most likely we will use a Pynq or PetaLinux solution on the final development, however, I wanted to know how low the current on the Zynq device could go. Using a MiniZed, USB power monitor, and a simple design in Vivado and Vitis, we can measure the current when sleeping and operating. Of course, we want the application to be able to sleep and then resume the application.
The MiniZed is a good application for this because it has a PS button and a tricolour LED connected to the PL also. The Vivado design is created so that the pull up connected to the push button on the PS MIO is disabled. The push button will be high when it is pressed so we want it low otherwise.
The remainder of the PL design is simple and connects the PL LED to the PS using a AXI GPIO. I wanted to use the PL to ensure that we could still communicate and operate with the PL when I restarted the system from its sleeping state and that I had not accidentally disabled anything and forgotten to reenable it.
With this completed, the Vivado project can be built and the XSA exported to Vitis for the embedded software development flow.
The USB monitor I am using is inline and reports the power being taken over USB. It is also Bluetooth capable. This can be connected to a mobile phone and the current profile plotted over the course of the experiment.
When the reset button is pushed, the Zynq device is held in reset and is at its lowest power. At 180 mA, this accounts for the power convertors, DDR, eMMC, and WiFi etc. This also includes the Zynq leakage current.
The software application performs the following steps:
1. Enables interrupts
2. Configures the GPIO MIO 0 for interrupts
3. Enables L2 cache dynamic clock gating
4. Enables snoop control unit standby mode
5. Enables topswitch clock stop
6. Enables dynamic gating
7. Puts DDR into self-refresh mode
8. Puts PLL into bypass
9. Powers down PLL
10. Slows down the clock
11. Executes the WFI instruction
The wake up sequency in the interrupt service routine is as follows:
1. Restore the clock
2. Power on PLL
3. Wait for the PLL to lock
4. Disable PLL bypass mode
5. Disable L2 cache dynamic clock gating
6. Disable snoop control unit standby mode
7. Disable topswitch clock stop
8. Disable dynamic gating
9. Restore DDR controller clocks
10. Resume operation
The code running on my MiniZed can be see below. The PL LED will be toggled once a second when running after the WFI and interrupt has occurred.
#include <stdio.h>
#include "platform.h"
#include "xgpiops.h"
#include "xil_types.h"
#include "Xscugic.h"
#include "Xil_exception.h"
#include "unistd.h"
#include "xpseudo_asm.h"
#include "xreg_cortexa9.h"
#define wfi() __asm__("wfi")
#define GPIO_DEVICE_ID XPAR_XGPIOPS_0_DEVICE_ID
#define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID
#define GPIO_INTERRUPT_ID XPS_GPIO_INT_ID
#define l2cpl310_reg15 0xF8F02F80
#define scu_control_reg 0xF8F00000
#define topsw_clk 0xF800016C
#define slcr_unlock 0xF8000008
#define ddrc_ctrl_reg1 0xF8006060
#define ddrc_para_reg3 0xF8006020
#define ddr_clk_ctrl 0xF8000124
#define dci_clk_ctrl 0xF8000128
#define arm_pll_ctrl 0xF8000100
#define ddr_pll_ctrl 0xF8000104
#define io_pll_ctrl 0xF8000108
#define arm_clk_ctrl 0xF8000120
#define gpio_int_en0 0xE000A210
#define pll_status 0xE800010C
#define uart_sel 0xF8000154
#define aper_reg 0xF800012C
#define ledpin 52
#define pbsw 0
static XScuGic Intc; /* The Instance of the Interrupt Controller Driver */
static XGpioPs Gpio; /* The driver instance for GPIO Device. */
int toggle = 0;//used to toggle the LED
static void SetupInterruptSystem(XScuGic *GicInstancePtr, XGpioPs *Gpio, u16 GpioIntrId);
static void IntrHandler(void *CallBackRef, int Bank, u32 Status);
int main()
{
int Status;
XGpioPs_Config *GPIOConfigPtr;
init_platform();
printf("low power example\n\r");
//GPIO Initilization
GPIOConfigPtr = XGpioPs_LookupConfig(XPAR_XGPIOPS_0_DEVICE_ID);
Status = XGpioPs_CfgInitialize(&Gpio, GPIOConfigPtr,GPIOConfigPtr->BaseAddr);
if (Status != XST_SUCCESS) {
print("GPIO INIT FAILED\n\r");
return XST_FAILURE;
}
//set direction and enable output
XGpioPs_SetDirectionPin(&Gpio, ledpin, 1);
XGpioPs_SetOutputEnablePin(&Gpio, ledpin, 1);
//set direction input pin
XGpioPs_SetDirectionPin(&Gpio, pbsw, 0x0)
SetupInterruptSystem(&Intc, &Gpio, GPIO_INTERRUPT_ID);
u32 data;
XGpioPs_WritePin(&Gpio, ledpin, 0);
Xil_Out32(slcr_unlock,0xDF0D);//unlock SLCR registers
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);
data = Xil_In32(arm_clk_ctrl); //clock gate unused peripherals
printf("arm_clk_ctrl = %x\n\r", (unsigned int) data);
data = Xil_In32(uart_sel);
printf("uart_sel = %x\n\r", (unsigned int) data);
usleep(100);
data = Xil_In32(arm_clk_ctrl);
printf("arm_clk_ctrl = %x\n\r", (unsigned int) data);
data = Xil_In32(gpio_int_en0);
data = 0x00000400;
//configure cache for low power mode
data = Xil_In32(l2cpl310_reg15);
printf("ls cache reg 15 power = %x\n\r", (unsigned int) data);
data = 0x3; //enable standby mode and dynamic clock gating
Xil_Out32(l2cpl310_reg15,data);
data = Xil_In32(l2cpl310_reg15);
printf("ls cache reg 15 power = %x\n\r", (unsigned int) data);
//configure scu
data = Xil_In32(scu_control_reg);
printf("scu control reg = %x\n\r", (unsigned int) data);
data |= 0x00000020; //enable standby mode and dynamic clock gating
Xil_Out32(scu_control_reg,data);
data = Xil_In32(scu_control_reg);
printf("scu control reg = %x\n\r", (unsigned int) data);
//configure slcr
data = Xil_In32(topsw_clk);
printf("slcr control reg = %x\n\r", (unsigned int) data);
data |= 0x00000001; //enable standby mode and dynamic clock gating
Xil_Out32(topsw_clk,data);
data = Xil_In32(topsw_clk);
printf("slcr control reg = %x\n\r", (unsigned int) data);
XGpioPs_WritePin(&Gpio, ledpin, 1);
//set CP15
data = 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);
//set up ddr
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);
// arm_pll_ctrl
data = Xil_In32(arm_pll_ctrl);
data |= 0x00000010; //enable standby mode and dynamic clock gating
Xil_Out32(arm_pll_ctrl,data);
data = Xil_In32(ddr_pll_ctrl);
data |= 0x00000010; //enable standby mode and dynamic clock gating
Xil_Out32(ddr_pll_ctrl,data);
data = Xil_In32(io_pll_ctrl);
data |= 0x00000010; //enable standby mode and dynamic clock gating
Xil_Out32(io_pll_ctrl,data);
data = Xil_In32(arm_pll_ctrl);
data |= 0x00000002; //enable standby mode and dynamic clock gating
Xil_Out32(arm_pll_ctrl,data);
data = Xil_In32(ddr_pll_ctrl);
data |= 0x00000002; //enable standby mode and dynamic clock gating
Xil_Out32(ddr_pll_ctrl,data);
data = Xil_In32(io_pll_ctrl);
data |= 0x00000002; //enable standby mode and dynamic clock gating
Xil_Out32(io_pll_ctrl,data);
data = Xil_In32(arm_clk_ctrl);
data |= 0x0003f00; //enable standby mode and dynamic clock gating
Xil_Out32(arm_clk_ctrl,data);
wfi();
while(1){
toggle = !toggle;
XGpioPs_WritePin(&Gpio, ledpin, toggle);
//printf("hello world\n\r");
usleep(1000000);
}
return 0;
}
static void SetupInterruptSystem(XScuGic *GicInstancePtr, XGpioPs *Gpio, u16 GpioIntrId)
{
XScuGic_Config *IntcConfig;
Xil_ExceptionInit();
IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);
XScuGic_CfgInitialize(GicInstancePtr, IntcConfig,
IntcConfig->CpuBaseAddress);
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler)XScuGic_InterruptHandler,
GicInstancePtr);
XScuGic_Connect(GicInstancePtr, GpioIntrId,
(Xil_ExceptionHandler)XGpioPs_IntrHandler,
(void *)Gpio);
//Enable interrupts for all the pins in bank 1.
XGpioPs_SetIntrTypePin(Gpio, pbsw, XGPIOPS_IRQ_TYPE_LEVEL_HIGH);
//Set the handler for gpio interrupts.
XGpioPs_SetCallbackHandler(Gpio, (void *)Gpio, IntrHandler);
XGpioPs_IntrClear(Gpio, 1,0xFFFFFFFF);
//Enable the GPIO interrupts of Bank 1.
XGpioPs_IntrEnablePin(Gpio, pbsw);
//Enable the interrupt for the GPIO device.
XScuGic_Enable(GicInstancePtr, GpioIntrId);
// Enable interrupts in the Processor.
Xil_ExceptionEnableMask(XIL_EXCEPTION_ALL);
}
static void IntrHandler(void *CallBackRef, int Bank, u32 Status)
{
u32 ddrc;
u32 data;
data = 0x1f000200;
Xil_Out32(arm_clk_ctrl,data);
Xil_Out32(aper_reg,0x01fc0c0d);
ddrc = Xil_In32(io_pll_ctrl);
ddrc &= ~0x00000002; //remove standby mode and dynamic clock gating
Xil_Out32(io_pll_ctrl,ddrc);
ddrc = Xil_In32(ddr_pll_ctrl);
ddrc &= ~0x00000002; //remove standby mode and dynamic clock gating
Xil_Out32(ddr_pll_ctrl,ddrc);
ddrc = Xil_In32(arm_pll_ctrl);
ddrc &= ~0x00000002; //remove standby mode and dynamic clock gating
Xil_Out32(arm_pll_ctrl,ddrc);
ddrc = Xil_In32(pll_status);
while(ddrc != 0x0000003f){ //wait for DLL to lock and be stable
ddrc = Xil_In32(pll_status);
}
ddrc = Xil_In32(arm_pll_ctrl);
ddrc &= ~0x00000010; //enable standby mode and dynamic clock gating
Xil_Out32(arm_pll_ctrl,ddrc);
ddrc = Xil_In32(ddr_pll_ctrl);
ddrc &= ~0x00000010; //enable standby mode and dynamic clock gating
Xil_Out32(ddr_pll_ctrl,ddrc);
ddrc = Xil_In32(ddr_pll_ctrl);
ddrc = Xil_In32(io_pll_ctrl);
ddrc &= ~0x00000010; //enable standby mode and dynamic clock gating
Xil_Out32(io_pll_ctrl,ddrc);
Xil_Out32(slcr_unlock,0xDF0D);
//configure slcr
data = Xil_In32(topsw_clk);
data &= ~0x00000001; //enable standby mode and dynamic clock gating
Xil_Out32(topsw_clk,data);
XGpioPs_WritePin(&Gpio, ledpin, 0);
//set CP15
data = mfcp(XREG_CP15_POWER_CTRL);
mtcp(XREG_CP15_POWER_CTRL,0x700);
data = mfcp(XREG_CP15_POWER_CTRL);
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 |= 0x00000003; //enable standby mode and dynamic clock gating
Xil_Out32(ddr_clk_ctrl,data);
data = Xil_In32(dci_clk_ctrl);
data |= 0x00000003; //enable standby mode and dynamic clock gating
Xil_Out32(dci_clk_ctrl,data);
XGpioPs_IntrDisablePin(&Gpio, pbsw);
printf("****button pressed****\n\r");
XGpioPs_IntrClear(&Gpio, 1,0xFFFFFFFF);
XGpioPs_IntrEnablePin(&Gpio, pbsw);
}
The application in Vitis is designed to execute from the DDR. This ensures that we get the DDR clocking and self-refreshing correct. We will not be able to easily resume operation of the program if it is not correctly configured.
The software application is design to put the processor to sleep. Issue the WFI and wait for the PB switch to be pressed to wake up the processor and resume processing.
When the application is running, the board is taking 390 mA. This drops to 220 mA while the processor is sleeping. When the push button is pressed, the interrupt is generated and the processor reawakens thereby restoring the current to about 390 mA again.
Once the processor wakes, it continues to execute the application and the LED continues to flash on the MiniZed once a second.
It is good that we can save 170 MZ by putting the Zynq device to sleep. However, the battery-powered application needs to be able to power down many more of its peripherals in order to have a battery life which extends for the time defined in the requirements.