nImmediateBatteryLevel changes only at power off/on cycle

In Brief:
The battery voltage as indicated by the millivolts value nImmediateBatteryLevel does not change even after significant amounts of motor usage. After a power cycle, the new value reported by nImmediateBatteryLevel shows a significant drop, in proportion to the motor usage.

Is there some trick to getting nImmediateBatteryLevel to update?

Details:
I am mentoring some VEX EDR teams with power monitoring and management. I suggested reading the battery levels for the primary and power expander battery throughout multiple runs and recording the levels in the debug stream. Further, their instructor has asked that they plot the battery drain over time to determine whether they have balanced the load between the two batteries relatively effectively.

So far, so good.

I’m setting up some example code to publish to the various teams we’re working with, so they can incorporate load managment and simple (non-PID) power scaling into some of their autonomous code if desired. Our initial results are…unhelpful. We see the battery attached to the power expander decrease over time, as it is exercised. We do not see any change at all on the primary battery.

We are using nImmediateBatteryLevel to get the millivolts value for the primary battery; we are reading an anolog sensor port (and scaling with 280 to convert to volts) for the expander. After several autonomous runs, we see that the power expander battery has dropped quite a bit, but the main battery never has.

After a power cycle, we can see that the main battery voltage has dropped from the previous value reported a minute or so prior, just before the power off. If we take the overall “drain” of the primary battery as:

and the overall drain on the power expander battery as:

we see the two drain values to be similar.

So both batteries are being used, but only one is seeing it’s usage reported, until a power cycle.

Any help would be appreciated.

Thanks,

KYPyro

Can you post the code you are using? I will check again tomorrow but I’ve never seen any problems with nImmediateBatteryLevel.

I can’t run it at the moment, but I can get to a copy from before today’s session. Not much to see, but here are the pertinent lines:

	
writeDebugStreamLine(">-- first battery is: %f Volts\n",(float)nImmediateBatteryLevel/1000.0);
writeDebugStreamLine(">-- second battery is: %f Volts\n",(float)SensorValue[power]/270.0);

Calculations coerced to float. (It is so very hard not to write (double) after decades of this kind of thing.)

This is either executed in a montoring task at one second intervals, (Okay, in a while loop after


wait1Msec(1000) 

actually) or interspersed directly in the movement code.

Ok, let me double check everything is working as expected tomorrow. One thing to note, the resolution of nImmediateBatteryLevel is only about 59mV. The battery level is only available as an 8 bit value inside the cortex where each bit corresponds to a change of 59mV, this means that readings will be have discrete steps, 7.00v, 7.059v etc. (although not those exact numbers)

Thanks for the reply, and in particular for the details of the battery level A/D. Good to know it’s an 8 bit value internally, 59mv per division. So, representable voltages are 0 to 15.045 volts in 59mv steps. I spent hours looking for that. ( I would say “literally” but people tend to say that when they mean “figuratively.” And I spent hours o’ the clock looking for that information.) Can you say what the sample rate is, by the way? Meaning how frequently is the value updated? I had assumed it is updated quite frequently, given that a variable representing the running average of the past 20 samples is also available.

I used a couple of samples from the debug stream output to show what we were seeing yesterday. It doesn’t look like I can embed a table here, so this might be a bit crowded. Nevertheless:

Begin Before Diff After second Diff Multiples of 59mv
7.2100 7.2100 0.0000 7.0920 0.1180 2.0000
8.0259 7.9519 0.0741 7.9185 0.1074 1.8205

The first data row contains values for the primary battery. Second data row contains values for the power expander battery.

First column shows the voltage reported for each battery right after a power cycle.
Second column shows the voltage reported just before a second power cycle.
Third column shows the indicated aggregate voltage drain right before the second power cycle.
Fourth column shows the voltage reported after the second power cycle.
Fifth column shows the indicated aggregate voltage drain after the second power cycle.
Sixth column shows the fifth column values converted to the number of 59 millivolt divisions the change would represent.

Note that the sixth column implies that:
1.) The battery drain of the two batteries is roughly equal.
2.) The battery drain is high enough to be indicated even at 59 mv per bit

Note that the voltage drain of the second battery before the second power off (row 2, column 3) shows a change of more than one but less than two 59 mv quantities. Since the drain on the first battery is higher, (as seen in the final three columns) I would expect to see that reflected in columns 2 and 4 as well.

So, the 59mv per bit explains some “chunkiness” in the reported voltage of the primary battery. But I don’t think it explains the lack of indicated change until the power is cycled.

(edit: I fixed the original scale error this post had)

As a point of clarification: I understand that the power expander battery voltage is taken at a different resolution and sample rate than the primary battery, so the 59 mv resolution does not apply to those measurements. I only calculated that column in the previous table for easy comparison.

(Edited to correct the scale error)

59 not 49

the master processor is the one that is reading main / backup battery voltage, i assume it is updated in each frame sent to the user processor, heres what PROS does to get battery voltage

SV_IN struct is received from master processor

Thanks Andre; not sure why I got stuck on 49mv instead of 59.
Also, thanks for pointing to the PROS code; I hadn’t thought of looking there. I will in the future. It looks like you meant to point out svGetMainBattery, which is a few lines lower than svGetBackupBattery–not that the code looks much different. For reference, here’s what I found in the code:

// svGetMainBattery - Gets the main battery voltage in millivolts, or 0 if not connected
static INLINE uint32_t svGetMainBattery() {
	uint32_t volts = ((uint32_t)SV_IN->mainBattery) * 59;
	if (volts < 1000)
		volts = 0;
	return volts;
}

Using 59mv, the table from my previous post would be

Begin Before Diff After second Diff Multiples of 59mv
7.2100 7.2100 0.0000 7.0920 0.1180 2.0000
8.0259 7.9519 0.0741 7.9185 0.1074 1.8205

Which is much more satisfactory, since the main battery voltage now occurs as multiples of the 59mv quanta.

Thanks!

Yes, 59mV , not 49mV, I will edit your post so others don’t get confused. I checked nImmediateBatteryLevel with this simple code, I had the cortex hooked up to a bench power supply and varied the input voltage, readings change as expected.

task main()
{
    while(1) {
      writeDebugStreamLine("%d", nImmediateBatteryLevel );
      wait1Msec(500);
    }
}
(sample output)
7387
7387
7387
7446
7505
7505
7505
7446
7269
7210
7269
7387
7387
7269
7210
7210
7210
7092
7151
7092
7151
7092
7092

I was intending to try the cortex on a bench supply today, but we ran out of time. I’ll let you know what I find.

Can you send me the code you are using? It feels more like an issue with task run/priority or something like that.

I appreciate your help and concern, so I’ll send the test code I got it down to today. I would be surprised if this is a task priority related problem, given the simplicity of the setup.

Note that the power expander is used for ports 6 through 9.

Current test code:

#pragma config(Sensor, in1,    armpot,         sensorPotentiometer)
#pragma config(Sensor, in2,    power,          sensorAnalog)
#pragma config(Sensor, dgtl1,  forwardlimit,   sensorTouch)
#pragma config(Motor,  port2,           rightdrive,    tmotorNone, openLoop)
#pragma config(Motor,  port3,           leftdrive,     tmotorNone, openLoop)
#pragma config(Motor,  port6,           pincer,        tmotorNone, openLoop)
#pragma config(Motor,  port7,           righttower,    tmotorNone, openLoop)
#pragma config(Motor,  port8,           lefttower,     tmotorNone, openLoop)
#pragma config(Motor,  port9,           lowertower,    tmotorNone, openLoop)
//*!!Code automatically generated by 'ROBOTC' configuration wizard               !!*//

// forward definitions; implementations after main()
void goForward(float secondsToGo, int speedToGo);
void goBackward(float secondsToGo, int speedToGo);
void turnRight(float secondsToGo, int speedToGo);
void turnLeft(float secondsToGo, int speedToGo);
void raiseArm(float secondsToGo, int speedToGo);
void lowerArm(float secondsToGo, int speedToGo);


task monitorPower();

task main()
{
	clearDebugStream();
	writeDebugStreamLine(">-- first battery is: %f Volts\n",(float)nImmediateBatteryLevel/1000.0);
	writeDebugStreamLine(">-- second battery is: %f Volts\n",(float)SensorValue[power]/270.0);

	bLCDBacklight = true;

	startTask(monitorPower);

	while (1)
	{
		turnLeft(1.0, 80);
		turnRight(1.0, 80);
		raiseArm(1.0, 80);
		lowerArm(1.0, 80);
		
		// typical and customary wait not needed in this circumstance
		// because movement commands used here contains waits
		// but it won't hurt, so I'm leaving it in...
		wait1Msec(10);
	}
} // end of task main

task monitorPower()
{
	string mainBattery;
	string auxBattery;

	// just dwell here
	while (1)
	{
		sprintf(mainBattery," %f Volts\n",(float)nImmediateBatteryLevel/1000.0);
		sprintf(auxBattery," %f Volts\n",(float)SensorValue[power]/270.0);

		clearLCDLine(0);
		clearLCDLine(1);
		displayLCDString(0,0,mainBattery);
		displayLCDString(1,0,auxBattery);

		writeDebugStreamLine(">-- first battery is: %f Volts\n",(float)nImmediateBatteryLevel/1000.0);
		writeDebugStreamLine(">-- second battery is: %f Volts\n",(float)SensorValue[power]/270.0);

		wait1Msec(1000);
	}
} // end of task monitorPower

void goForward(float secondsToGo, int speedToGo)
{
	startMotor(leftdrive, speedToGo);
	startMotor(rightdrive, speedToGo);
	wait(secondsToGo);
	stopMotor(leftdrive);
	stopMotor(rightdrive);
} // end of goForward

void goBackward(float secondsToGo, int speedToGo)
{
	startMotor(leftdrive, -1 * speedToGo);
	startMotor(rightdrive, -1 * speedToGo);
	wait(secondsToGo);
	stopMotor(leftdrive);
	stopMotor(rightdrive);
} // end of goBackward

void turnRight(float secondsToGo, int speedToGo)
{
	startMotor(leftdrive, speedToGo);
	startMotor(rightdrive, -1 * speedToGo);
	wait(secondsToGo);
	stopMotor(leftdrive);
	stopMotor(rightdrive);
} // end of turnRight

void turnLeft(float secondsToGo, int speedToGo)
{
	startMotor(leftdrive, -1 * speedToGo);
	startMotor(rightdrive, speedToGo);
	wait(secondsToGo);
	stopMotor(leftdrive);
	stopMotor(rightdrive);
} // end of turnLeft

void raiseArm(float secondsToGo, int speedToGo)
{
	startMotor(righttower, -1 * speedToGo);
	startMotor(lefttower, -1 * speedToGo);
	startMotor(lowertower, -1 * speedToGo);
	wait(secondsToGo);
	stopMotor(righttower);
	stopMotor(lefttower);
	stopMotor(lowertower);
} // end of raiseArm

void lowerArm(float secondsToGo, int speedToGo)
{
	startMotor(righttower, speedToGo);
	startMotor(lefttower, speedToGo);
	startMotor(lowertower, speedToGo);
	wait(secondsToGo);
	stopMotor(righttower);
	stopMotor(lefttower);
	stopMotor(lowertower);
} // end of lowerArm

I ran the code here and it works as intended. Using a bench supply the value for the “first battery” is changing as the voltage is changed. I’ve assumed you are using the latest version of RobotC (V4.55) and the cortex firmware is also up to date (v4.25). If after you test using your bench supply and still find the same problem we should go back to basics and verify firmware etc.

I’ve loaned out the bench supplies I had at home, and I haven’t been back to the lab, so we haven’t yet run a test identical to your simple monitoring code above. But we have some interesting results from experimenting further with the sample I provided.

First, the confirmation:
Using RobotC v4.5 and Master CPU firmware v4.25.

Now, the observation:
Running the debugger affects the “stability” of nImmediateBatteryLevel. The variable does not change (for us; clearly it does for you) while a debug session is active.

Detail:
We ran the code containing the “monitorPower” task shown above, this time leaving it running for about 8 minutes. We used debugger to stop the code and restart it, running it for several more minutes. These run times were much longer than any runs we’ve done in the past. We did this to have high confidence that we had drained the battery enough to indicate a change even given the 59 mv per bit “chunkiness”. During the entire time, the voltage written to the debugStream window never changed for the first battery. The second battery voltage jumped around considerably, but with an overall downward trend, as expected. (Since the motors were being switched on and off asynchronously to the power monitoring task, we fully expected to see a lot of volatility in the reported voltage.) The numbers shown in the debugStream output matched what was shown on the LCD screen as well. The first battery voltage never changed from 7.092000.

After those two long sessions, we used the debugger to stop the robot and power-cycled both the robot and the joystick. After the robot and joystick established communications, the robot resumed its cyclic dance.

Turn Left. Turn Right. Raise arm. Lower Arm. repeat.

And the values displayed on the LCD for the first battery actually changed.

Then, I plugged the “program” cable back in the joystick. The robot stopped for a second or so, then resumed the cyclic exercise. When it started moving again, the debugStream window refreshed and a number of updated lines flooded into the window. It looked like this:

>-- first battery is: 7.092000 Volts

>-- second battery is: 7.907407 Volts

>-- first battery is: 7.092000 Volts

>-- second battery is: 7.566667 Volts

>-- first battery is: 7.092000 Volts

>-- second battery is: 7.548148 Volts

>-- first battery is: 6.619000 Volts

>-- second battery is: 7.888889 Volts

>-- first battery is: 5.259000 Volts

>-- second battery is: 7.888889 Volts

>-- first battery is: 7.032000 Volts

>-- second battery is: 7.529630 Volts

>-- first battery is: 7.032000 Volts

>-- second battery is: 7.533333 Volts

>-- first battery is: 6.619000 Volts

>-- second battery is: 7.877778 Volts

>-- first battery is: 5.259000 Volts

>-- second battery is: 7.866667 Volts

>-- first battery is: 7.032000 Volts

>-- second battery is: 7.544445 Volts

>-- first battery is: 7.032000 Volts

>-- second battery is: 7.544445 Volts

>-- first battery is: 7.032000 Volts

>-- second battery is: 7.874074 Volts

The lines where the first battery voltage moved around were the ones that all loaded quickly, in under a second or so. Following that refresh of the buffered debugStream, the window would update every second, but the first battery voltage never changed from 7.032000. The first battery voltage reported on the LCD stayed stable as well.

We were able to replicate this every time we tried. Active debugging means the first battery voltage doesn’t change either in the debugStream window or on the LCD. Restart without running the debugger, and the output of the LCD changes as expected.

So, there are a couple of conclusions we have:

1.) Indications are we can trust the nImmediateBatteryLevel to reflect the recent reading of the battery voltage, subject to the 59mv per bit limitation. (It was clear you could. Now it’s clear we can.)
2.) Instantaneous voltage readings during motor loads vary greatly, as expected. So, either take a reading during a quiescent period, use the RobotC provided average variable, nAvgBatteryLevel, or perform your own filtering.
3.) The debugStream is buffered, and the buffer is updated even if a debug session is not running.

Further, there is an interesting implication:

Rather than living in the generated code, the programmatic scaffolding for debugger support is built in to the RobotC VM. Some parts of debugging (real or virtual interrupts to run the check for breakpoints, for instance) are only activated when debugging is active. This could lead to different behaviors and timings between debug vs non-debug runs. While the concept of having a debugger change timing is hardly novel, it often (even usually) is due to running code that was compiled for debug versus code that was not.

I’ll try to run the system with a bench supply if we have time to do so tomorrow.

Thanks,

KYPyro

Ok, so I reproduced what you are seeing, it only happens when using vexNET keys*, I usually do all my testing using an A-A cable between cortex and joystick. Now I just need to figure out if this is a RobotC issue or a firmware issue (I suspect the latter).

Yes, the debug stream is buffered.

The RobotC IDE is able to communicate with the VM for debugging purposes. It will have a (usually minor) effect on performance, it all depends how many debug windows you have open.

edit *and it only happens when the debugger is connected. When using the debugger we switch to a different VexNET channel designed for higher speed communication, this is why you see the pause when you connected the debug cable, VexNET switched channels. When the debugger is not connected then I see battery voltage changing on the LCD.

The same issue is present with the backup battery. I’m pretty sure it’s a master firmware issue as everything works when using the old black VexNET keys. I have not tried a competition channel yet (VexNET uses three different groups of radio channels depending on how the robot is operating). RobotC firmware in this area is very simple, all we do is read the value from a data packet sent internally between the two cortex processors, essentially one line of code, it has no knowledge of how the cortex is communicating with the joystick so I don’t think this is a RobotC specific bug.

Thanks for the feedback. It’s great news this is a bonefide reproducible issue, too, rather than being some unexplained anomaly. At present, I would class it as a bug. However, it is possible that IFI could just document this as a limitation.

It could be, for instance, that when the high bandwidth channel is in use, the interrupt structure or the interrupt handling code changes. Maybe the priorities in place for guaranteed timely handling of communications interrupts leads to lowered update frequency on the 8 bit A/D that feeds into nImmediateBatteryVoltage.

My assumption is that communications over competition channels will not exhibit this issue. If it would, then surely one of the many teams displaying battery voltage on the LCD would have noticed this before now. On the other hand, it seems a little odd to me that nobody has reported this before now.

One important implication of this issue is that that any record-and-play-back system for creating autonomous programs can’t use the debugStream to report battery voltage at various points through the run. This was proposed here: [https://vexforum.com/t/answered-what-rules-are-in-place-to-protect-robots-that-are-hanging/18114/1) and discussed in a few places. Unfortunately, it seems this just won’t work.

However, for our purposes, since autonomous code won’t be using the debugger in an actual competition, this bug or issue is of no particular consequence. So long as it works for competition channels.

Thanks,

KYPyro