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?
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.
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.
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.
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!
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.
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.
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!