Low Voltage Detect (LVD) & Low Voltage Warning (LVW) on Teensy 3.6

Status
Not open for further replies.

jrw

Member
I am trying to understand LVD/LVW so I can use it in my project. The ultimate goal is to detect when power is disconnected so that I can cease writing to the SD card, and avoid corruption. I have not been able to find any libraries or examples, so I've been trying to figure this out from Freescale documentation. I thought I figured it out and wrote some test code. However, it does not work as I expect. Does anyone have insight that might help with this? Where am I going wrong?

Code:
/******************************************************/
/* Test the Low Voltage Detect system on a Teensy 3.6 */
/******************************************************/

/*
 * Teensy 3.6 uses the MK66FX1M0VMD18 MCU from NXP Semiconductors.
 * This is from the Kinetis K66 sub-family. 180 MHz ARM Cortex-M4F
 * Microcontroller.
 *
 *
 * Freescale
 * K66 Sub-Family Reference Manual
 * Supports: MK66FN2M0VMD18, MK66FX1M0VMD18, MK66FN2M0VLQ18, MK66FX1M0VLQ18,
 * Document Number: K66P144M180SF5RMV2 Rev. 2, May 2015
 *
 * This document has useful information. In particular, chapter 17 on
 * the Power Management Controller (PMC). It appears to reference the
 * different levels, but does not define the values for them.
 *
 *
 * NXP Semiconductors
 * K66P144M180SF5V2
 * Data Sheet: Technical Data
 * Rev. 4, 04/2017
 *
 * This has a lot of specifics for chips, but less discussion. Section
 * 2.2.2 shows LVD values referenced in above document.
 *
 *
 * Freescale Semiconductor User’s Guide
 * KQRUG Rev. 3, 05/2014
 * Kinetis Peripheral Module Quick Reference
 * A Compilation of Demonstration Software for Kinetis Modules
 *
 * This is a wonderful document with a ton of great information I
 * have been unable to find anywhere else.
 */

// signal low voltage detection by raising this line
#define FLAG    0

// Specify whether to use low or high value for detection
// 0x00: min 1.54V, typ 1.60V, max 1.66V
// 0x01: min 2.48V, typ 2.56V, max 2.64V
// 0x02: reserved
// 0x03: reserved

#define LVD_TRIP_L  0x00U
#define LVD_TRIP_H  0x01U

// Low-voltage warning thresholds if low detect range in effect
// 0x00: LOW  min 1.74V, typ 1.80V, max 1.86V
// 0x01: MID1 min 1.84V, typ 1.90V, max 1.96V
// 0x02: MID2 min 1.94V, typ 2.00V, max 2.06V
// 0x03: HIGH min 2.04V, typ 2.10V, max 2.16V

// Low-voltage warning thresholds if high detect range in effect
// 0x00: LOW  min 2.62V, typ 2.70V, max 2.78V
// 0x01: MID1 min 2.72V, typ 2.80V, max 2.88V
// 0x02: MID2 min 2.82V, typ 2.90V, max 2.98V
// 0x03: HIGH min 2.92V, typ 3.00V, max 3.08V

// Select a warn voltage within chosen range
#define LVW1 0x00U
#define LVW2 0x01U
#define LVW3 0x02U
#define LVW4 0x03U

/************************************************************************/
// Configure Low Voltage Detect and Low Voltage Warning
/************************************************************************/
void lvd_init(
        boolean LVDInterruptEnable, // interrupt on lvd?
        boolean LVDResetEnable,     // reset mcu on lvd?
        uint8_t LVDVoltageSelect,   // select high or low range
        boolean LVWInterruptEnable, // interrupt on lvw?
        uint8_t LVWVoltageSelect)   // select one of four vals
{
    // this bit is write-once. do it first so desired setting takes effect.
    if (LVDResetEnable) {
        PMC_LVDSC1 |= PMC_LVDSC1_LVDRE;     // enable LVD Reset
    } else {
        PMC_LVDSC1 &= ~PMC_LVDSC1_LVDRE;    // disable LVD Reset
    }

    // write value of LVDV to register. will clear other bits, set below.
    switch (LVDVoltageSelect) {
        case 0x00U:                         // Low Trip point
        case 0x01U:                         // High Trip point
            PMC_LVDSC1 = LVDVoltageSelect;
            break;
        default:                            // invalid, ignore
            break;
    }

    if (LVDInterruptEnable) {
        PMC_LVDSC1 |= PMC_LVDSC1_LVDIE;     // set interrupt enable
    } else {
        PMC_LVDSC1 &= ~PMC_LVDSC1_LVDIE;    // clear interrupt enable
    }

    // write value of LVWV to register. will clear other bits, set below.
    switch (LVWVoltageSelect) {
        case 0x00U:                         // Low Warn point
        case 0x01U:                         // Mid1 Warn point
        case 0x02U:                         // Mid2 Warn point
        case 0x03U:                         // High Warn point
            PMC_LVDSC2 = LVWVoltageSelect;
            break;
        default:                            // invalid, ignore
            break;
    }

    if (LVWInterruptEnable) {
        PMC_LVDSC2 |= PMC_LVDSC2_LVWIE;     // set interrupt enable
    } else {
        PMC_LVDSC2 &= ~PMC_LVDSC2_LVWIE;    // clear interrupt enable
    }

    PMC_LVDSC1 |= PMC_LVDSC1_LVDACK;        // clear detect flag if present
    PMC_LVDSC2 |= PMC_LVDSC2_LVWACK;        // clear warning flag if present
}

/************************************************************************/
// returns true if the LVD Flag is set
/************************************************************************/
boolean lvd_flag()
{
    uint8_t flag = PMC_LVDSC1 | PMC_LVDSC1_LVDF;
    return flag == 0;
}

/************************************************************************/
// acknowledge the LVD Flag, flag only cleared after voltage raises
/************************************************************************/
void lvd_acknowledge()
{
    PMC_LVDSC1 |= PMC_LVDSC1_LVDACK;
}

/************************************************************************/
// returns true if the LVW Flag is set
/************************************************************************/
boolean lvw_flag()
{
    uint8_t flag = PMC_LVDSC2 | PMC_LVDSC2_LVWF;
    return flag == 0;
}

/************************************************************************/
// acknowledge the LVW Flag, flag only cleared after voltage raises
/************************************************************************/
void lvw_acknowledge()
{
    PMC_LVDSC2 |= PMC_LVDSC2_LVWACK;
}

/************************************************************************/
// utility functions for formatting output
/************************************************************************/
void binary8(uint8_t val, char *buf) {
    uint8_t mask;

    for (int i=7 ; i>=0 ; i--) {
        mask = 0x01U << i;
        if (val & mask) {
            *buf = '1';
        } else {
            *buf = '0';
        }
        buf++;
    }
    *buf = '\0';
}

void ts(void) {
    // print seconds since reboot
    unsigned long m = millis();
    Serial.print(m/1000);
    Serial.print(".");
    m %= 1000;
    if (m<100) Serial.print("0");
    if (m<10) Serial.print("0");
    Serial.print(m);
    Serial.print("\t");
}

/************************************************************************/
// print contents of the Power Management Controller registers related to
// Low Voltage Detect and Low Voltage Warning
/************************************************************************/
void lvd_status()
{
    uint8_t value;
    char str[10];

    ts();
    // UPPERCASE if a value is set; lowercase if a value is clear
    value = PMC_LVDSC1;
    binary8(value, str);
    Serial.print(str);
    if (value & PMC_LVDSC1_LVDF) {
        Serial.print(": LVDF ");
    } else {
        Serial.print(": lvdf ");
    }
    if (value & PMC_LVDSC1_LVDIE) {
        Serial.print("LVDIE ");
    } else {
        Serial.print("lvdie ");
    }
    if (value & PMC_LVDSC1_LVDRE) {
        Serial.print("LVDRE ");
    } else {
        Serial.print("lvdre ");
    }
    if (value & 0x02) {
        Serial.print("ERROR:RESERVED_BIT_SET ");
    }
    if (value & 0x01) {
        Serial.print("HI_TRIP ");
    } else {
        Serial.print("LO_TRIP ");
    }

    value = PMC_LVDSC2;
    binary8(value, str);
    Serial.print(str);
    if (value & PMC_LVDSC2_LVWF) {
        Serial.print(": LVWF ");
    } else {
        Serial.print(": lvwf ");
    }
    if (value & PMC_LVDSC2_LVWIE) {
        Serial.print("LVWIE ");
    } else {
        Serial.print("lvwie ");
    }
    switch (value & 0x03) {
        case 0x00:  Serial.println("LOW__WARN");     break;
        case 0x01:  Serial.println("MID1_WARN");     break;
        case 0x02:  Serial.println("MID2_WARN");     break;
        case 0x03:  Serial.println("HIGH_WARN");     break;
        default:    Serial.println("INVALID_WARN");  // can't reach
    }
}

/************************************************************************/
// print info from Reset Control Module
/************************************************************************/
void print_reset_type() {
    if (RCM_SRS0 & RCM_SRS0_LOC)
        {ts(); Serial.println("[RCM_SRS0] - Loss of External Clock Reset");}
    if (RCM_SRS0 & RCM_SRS0_LOL)
        {ts(); Serial.println("[RCM_SRS0] - Loss of Lock in PLL Reset");}
    if (RCM_SRS0 & RCM_SRS0_LVD)
        {ts(); Serial.println("[RCM_SRS0] - Low-voltage Detect Reset");}
    if (RCM_SRS0 & RCM_SRS0_PIN)
        {ts(); Serial.println("[RCM_SRS0] - External Pin Reset");}
    if (RCM_SRS0 & RCM_SRS0_POR)
        {ts(); Serial.println("[RCM_SRS0] - Power-on Reset");}
    if (RCM_SRS0 & RCM_SRS0_WDOG)
        {ts(); Serial.println("[RCM_SRS0] - Watchdog(COP) Reset");}
    if (RCM_SRS1 & RCM_SRS1_LOCKUP)
        {ts(); Serial.println("[RCM_SRS1] - Core Lockup Event Reset");}
    if (RCM_SRS1 & RCM_SRS1_MDM_AP)
        {ts(); Serial.println("[RCM_SRS1] - MDM-AP Reset");}
    if (RCM_SRS1 & RCM_SRS1_SACKERR)
        {ts(); Serial.println("[RCM_SRS1] - Stop Mode Acknowledge Error Reset");}
    if (RCM_SRS1 & RCM_SRS1_SW)
        {ts(); Serial.println("[RCM_SRS1] - Software Reset");}
}

/************************************************************************/
// ready...
/************************************************************************/
void setup() {
    pinMode(LED_BUILTIN, OUTPUT);
    pinMode(FLAG, OUTPUT);
    digitalWrite(LED_BUILTIN, HIGH);
    digitalWrite(FLAG, HIGH);
    delay(1000);
    digitalWrite(LED_BUILTIN, LOW);
    digitalWrite(FLAG, LOW);

    while(!Serial);
    delay(100);

    print_reset_type();

    Serial.println("Initial values");
    lvd_status();
    Serial.println("Test all combinations");
    for (int lvdie=0 ; lvdie<2 ; lvdie++) {
        for (int lvdre=1 ; lvdre>=0; lvdre--) {
            for (uint8_t lvdv=0 ; lvdv<2 ; lvdv++) {
                for (int lvwie=0 ; lvwie<2 ; lvwie++) {
                    for (uint8_t lvwv=0 ; lvwv<4 ; lvwv++) {
                        delay(10);
                        lvd_init((lvdie==1), (lvdre==1), lvdv, (lvwie==1), lvwv);
                        lvd_status();
                    }
                }
            }
        }
    }
    delay(10);
    
    Serial.println("Desired setting");
    lvd_init(false, false, LVD_TRIP_H, false, LVW2);
    lvd_status();
}

/************************************************************************/
// go!
/************************************************************************/
int i,n = 0;
void loop() {

#if 1 // write square wave to help setup scope
    delay(2);
    if (digitalRead(FLAG)) {
        digitalWrite(FLAG, LOW);
    } else {
        digitalWrite(FLAG, HIGH);
    }
#endif

#if 0 // signal when we detect lvd
    if (lvd_flag() || lvw_flag()) {
        digitalWrite(FLAG, HIGH);
    }
#endif
}

Hopefully the code explains itself reasonably well. I have included comments indicating the reference documents that were helpful in understanding the PMC, LVD and LVW. However, I haven't seen any working code so I'm not sure my understanding is correct.

In the setup() routine, I go through all the permutations of setting up the relevant registers. This doesn't affect the operation of the program, but is more a test for myself showing that I am setting and clearing bits as expected. An important point to note is that the LVD Reset bit is write-once. Depending on whether the first call clears or sets this bit, then all subsequent writes are ignored and it remains with the initial value. Flipping the relevant for() loop from 0..1 to 1..0 demonstrates this. Here is the output to the serial console from starting up the program from power-off. Interestingly, note that it powers on with the LVW flag asserted.

Code:
1.975	[RCM_SRS0] - Low-voltage Detect Reset
1.975	[RCM_SRS0] - Power-on Reset
Initial values
1.975	00010000: lvdf lvdie LVDRE LO_TRIP 10000000: LVWF lvwie LOW__WARN
Test all combinations
1.985	00010000: lvdf lvdie LVDRE LO_TRIP 00000000: lvwf lvwie LOW__WARN
1.995	00010000: lvdf lvdie LVDRE LO_TRIP 00000001: lvwf lvwie MID1_WARN
2.005	00010000: lvdf lvdie LVDRE LO_TRIP 00000010: lvwf lvwie MID2_WARN
2.015	00010000: lvdf lvdie LVDRE LO_TRIP 00000011: lvwf lvwie HIGH_WARN
2.025	00010000: lvdf lvdie LVDRE LO_TRIP 00100000: lvwf LVWIE LOW__WARN
2.035	00010000: lvdf lvdie LVDRE LO_TRIP 00100001: lvwf LVWIE MID1_WARN
2.045	00010000: lvdf lvdie LVDRE LO_TRIP 00100010: lvwf LVWIE MID2_WARN
2.055	00010000: lvdf lvdie LVDRE LO_TRIP 00100011: lvwf LVWIE HIGH_WARN
2.065	00010001: lvdf lvdie LVDRE HI_TRIP 00000000: lvwf lvwie LOW__WARN
2.075	00010001: lvdf lvdie LVDRE HI_TRIP 00000001: lvwf lvwie MID1_WARN
2.085	00010001: lvdf lvdie LVDRE HI_TRIP 00000010: lvwf lvwie MID2_WARN
2.095	00010001: lvdf lvdie LVDRE HI_TRIP 00000011: lvwf lvwie HIGH_WARN
2.105	00010001: lvdf lvdie LVDRE HI_TRIP 00100000: lvwf LVWIE LOW__WARN
2.115	00010001: lvdf lvdie LVDRE HI_TRIP 00100001: lvwf LVWIE MID1_WARN
2.125	00010001: lvdf lvdie LVDRE HI_TRIP 00100010: lvwf LVWIE MID2_WARN
2.135	00010001: lvdf lvdie LVDRE HI_TRIP 00100011: lvwf LVWIE HIGH_WARN
2.145	00010000: lvdf lvdie LVDRE LO_TRIP 00000000: lvwf lvwie LOW__WARN
2.155	00010000: lvdf lvdie LVDRE LO_TRIP 00000001: lvwf lvwie MID1_WARN
2.165	00010000: lvdf lvdie LVDRE LO_TRIP 00000010: lvwf lvwie MID2_WARN
2.175	00010000: lvdf lvdie LVDRE LO_TRIP 00000011: lvwf lvwie HIGH_WARN
2.185	00010000: lvdf lvdie LVDRE LO_TRIP 00100000: lvwf LVWIE LOW__WARN
2.195	00010000: lvdf lvdie LVDRE LO_TRIP 00100001: lvwf LVWIE MID1_WARN
2.205	00010000: lvdf lvdie LVDRE LO_TRIP 00100010: lvwf LVWIE MID2_WARN
2.215	00010000: lvdf lvdie LVDRE LO_TRIP 00100011: lvwf LVWIE HIGH_WARN
2.225	00010001: lvdf lvdie LVDRE HI_TRIP 00000000: lvwf lvwie LOW__WARN
2.235	00010001: lvdf lvdie LVDRE HI_TRIP 00000001: lvwf lvwie MID1_WARN
2.245	00010001: lvdf lvdie LVDRE HI_TRIP 00000010: lvwf lvwie MID2_WARN
2.255	00010001: lvdf lvdie LVDRE HI_TRIP 00000011: lvwf lvwie HIGH_WARN
2.265	00010001: lvdf lvdie LVDRE HI_TRIP 00100000: lvwf LVWIE LOW__WARN
2.275	00010001: lvdf lvdie LVDRE HI_TRIP 00100001: lvwf LVWIE MID1_WARN
2.285	00010001: lvdf lvdie LVDRE HI_TRIP 00100010: lvwf LVWIE MID2_WARN
2.295	00010001: lvdf lvdie LVDRE HI_TRIP 00100011: lvwf LVWIE HIGH_WARN
2.305	00110000: lvdf LVDIE LVDRE LO_TRIP 00000000: lvwf lvwie LOW__WARN
2.315	00110000: lvdf LVDIE LVDRE LO_TRIP 00000001: lvwf lvwie MID1_WARN
2.325	00110000: lvdf LVDIE LVDRE LO_TRIP 00000010: lvwf lvwie MID2_WARN
2.335	00110000: lvdf LVDIE LVDRE LO_TRIP 00000011: lvwf lvwie HIGH_WARN
2.345	00110000: lvdf LVDIE LVDRE LO_TRIP 00100000: lvwf LVWIE LOW__WARN
2.355	00110000: lvdf LVDIE LVDRE LO_TRIP 00100001: lvwf LVWIE MID1_WARN
2.365	00110000: lvdf LVDIE LVDRE LO_TRIP 00100010: lvwf LVWIE MID2_WARN
2.375	00110000: lvdf LVDIE LVDRE LO_TRIP 00100011: lvwf LVWIE HIGH_WARN
2.385	00110001: lvdf LVDIE LVDRE HI_TRIP 00000000: lvwf lvwie LOW__WARN
2.395	00110001: lvdf LVDIE LVDRE HI_TRIP 00000001: lvwf lvwie MID1_WARN
2.405	00110001: lvdf LVDIE LVDRE HI_TRIP 00000010: lvwf lvwie MID2_WARN
2.415	00110001: lvdf LVDIE LVDRE HI_TRIP 00000011: lvwf lvwie HIGH_WARN
2.425	00110001: lvdf LVDIE LVDRE HI_TRIP 00100000: lvwf LVWIE LOW__WARN
2.435	00110001: lvdf LVDIE LVDRE HI_TRIP 00100001: lvwf LVWIE MID1_WARN
2.445	00110001: lvdf LVDIE LVDRE HI_TRIP 00100010: lvwf LVWIE MID2_WARN
2.455	00110001: lvdf LVDIE LVDRE HI_TRIP 00100011: lvwf LVWIE HIGH_WARN
2.465	00110000: lvdf LVDIE LVDRE LO_TRIP 00000000: lvwf lvwie LOW__WARN
2.475	00110000: lvdf LVDIE LVDRE LO_TRIP 00000001: lvwf lvwie MID1_WARN
2.485	00110000: lvdf LVDIE LVDRE LO_TRIP 00000010: lvwf lvwie MID2_WARN
2.495	00110000: lvdf LVDIE LVDRE LO_TRIP 00000011: lvwf lvwie HIGH_WARN
2.505	00110000: lvdf LVDIE LVDRE LO_TRIP 00100000: lvwf LVWIE LOW__WARN
2.515	00110000: lvdf LVDIE LVDRE LO_TRIP 00100001: lvwf LVWIE MID1_WARN
2.525	00110000: lvdf LVDIE LVDRE LO_TRIP 00100010: lvwf LVWIE MID2_WARN
2.535	00110000: lvdf LVDIE LVDRE LO_TRIP 00100011: lvwf LVWIE HIGH_WARN
2.545	00110001: lvdf LVDIE LVDRE HI_TRIP 00000000: lvwf lvwie LOW__WARN
2.555	00110001: lvdf LVDIE LVDRE HI_TRIP 00000001: lvwf lvwie MID1_WARN
2.565	00110001: lvdf LVDIE LVDRE HI_TRIP 00000010: lvwf lvwie MID2_WARN
2.575	00110001: lvdf LVDIE LVDRE HI_TRIP 00000011: lvwf lvwie HIGH_WARN
2.586	00110001: lvdf LVDIE LVDRE HI_TRIP 00100000: lvwf LVWIE LOW__WARN
2.596	00110001: lvdf LVDIE LVDRE HI_TRIP 00100001: lvwf LVWIE MID1_WARN
2.606	00110001: lvdf LVDIE LVDRE HI_TRIP 00100010: lvwf LVWIE MID2_WARN
2.616	00110001: lvdf LVDIE LVDRE HI_TRIP 00100011: lvwf LVWIE HIGH_WARN
Desired setting
2.626	00010001: lvdf lvdie LVDRE HI_TRIP 00000001: lvwf lvwie MID1_WARN

The the relevant setting is the final iteration, showing how the LVD/LVW is configured for the main loop().

In the main loop(), I have two different behaviors coded. Use the #if to choose which one is used. The first block does nothing with the LVD/LVW registers, and instead just turns pin 0 on and off every 2 milliseconds. When this code is enabled, I hooked an oscilloscope up to pin 0 and to the 3.3V pin of the Teensy 3.6. I set it to do a single acquisition, triggered when the 3.3V line dropped below 3.0V. On this plot, I have the horizontal cursor set to 2.80V, which is the typical value where the Low Voltage Warning would be asserted based on the configuration in effect. You can see that the code continues to run even after the Teensy 3.3V line drops below 2.80V.

F0026TEK.JPG

So far, so good. Now I switch to the second block in the main loop(). In this, we sit in a tight loop checking the LFW flag and the LVD flag, and if either is detected, we assert pin 0. Here is the result on the scope when this code is run. Again, it is a single acquisition triggered when the 3.3V line drops below 3.0V. Here the cursor is showing the 2.56V level, which is the typical value for when the LVD reset would happen. From this, it seems to me that the LVW flag is never asserted, even though we drop all the way down to the reset voltage.

F0025TEK.JPG

So either my code is not written correctly, I'm not understanding how this is supposed to work, or the LVW/LVD system is not functioning correctly. I am guessing it is one of the first two. Any help? Thanks!
 
@jrw
Did you manage to get anywhere with this? I am looking for a similar solution; at the moment I am looking at adding an interrupt driven by an upstream regulator's power good signal; but if the local chip has a detection then maybe that would be an easier/better solution.
 
I got no response, could find no information that helped and didn't find any examples of anyone using this feature. After spending considerable time trying to get this working, I instead put a voltage divider on the +5VDC input and use one of the pins of the Teensy to monitor this.
 
Status
Not open for further replies.
Back
Top