Requesting Algorithm genuis to examine this and point me in a direction.

Status
Not open for further replies.

Detroit_Aristo

Well-known member
So, I have an equation that I need to satisfy to convert a 2 byte message into the formula to properly display a variable via a serial stream.

The formula is such that:
uint32_t var = 1/((Data1 * 0x10000 + Data2 * 0x100 + Data3) * 1.66666666666667E-07)

I create three uint8_t variables (d1, d2, d3)
Code:
d1 = incoming16BitVar >> 8;
d2 = incoming16BitVar;
d3 = 0;
For example. The message is 00000111 11010000 (7 208) (int 2,000)
I can't seem to get a bitshift pattern that satisfies this equation.

For example. Where var = 2000, what would the byte1, byte2, and byte3 be to satisfy the equation? I know as n approaches infinity, var approaches 0, but I just can't seem to satisfy this algorithm.

The serial message is as such:
Code:
uint8_t msg[5];
msg[0] = 18;
msg[1] = d1;
msg[2] = d2;
msg[3] = d3;
msg[4] = (18 + d1 + d2 + d3) & 0xFF;

Any advice would be greatly appreciated!

*snippets are posted as they are just thoughts and nothing tangible is coded.
 
I can't seem to get a bitshift pattern that satisfies this equation.
You don't need bitshifts or algorithms (which is not the same as programs/programming) - you need arithmetic.
First of all, don't take the 16 bit variable apart.

Data1 * 0x10000 + Data2 * 0x100 is the same as incoming16BitVar*256

1.666666666 is 5/3 so the equation becomes:
1/((incoming16BitVar*256 + data3) * 5 / 3 *10e-7)

we can move 3*10e-7 to the numerator
3*10e7/((incoming16BitVar*256 + data3) * 5)

and divide the 5 into the numerator
3*2*10e6/(incoming16BitVar*256 + data3) => 6*10e6/(incoming16BitVar*256 + data3)
=> 6000000/(incoming16BitVar*256 + data3)

Now you need to decide how to handle the result. Assuming that incoming16BitVar is a signed integer, then the maximum positive value is 32767, so the maximum value of the denominator is (32767*256 + 255) = 8388607. This is larger than the numerator so there are a lot of values of incoming16BitVar which will give a result of zero when stored in uint32_t. You need to know the range of values that incoming16BitVar can have.

Pete
 
Hello Pete.

First of, Thank You. This is absolutely doing my head in.

the values incoming are { 0,1,2.. ... 13998, 13999, 14000 } (It's RPM data).

The Serial stream is 5 bytes. Bytes 1,2 and 3 are in that formula. Byte 0 is the channel (18), and byte 4 is the checksum. I've just spent the better part of 12 hours trying to create something that works and I just can't seem to.

That formula is what the receiver uses to calculate the value to be displayed. The receiver expects a serial stream of [channel, byte1, byte2, byte3, checksum].
 
This is absolutely doing my head in.

Honestly, I'm confused. I guess I don't understand what you're really doing. It's hard to give useful advice without the understanding that comes from knowing the context of the project.

When you say "the values incoming are ... RPM data", these are coming into Teensy? Are you using the FreqMeasure library? Something else? You're running this on a Teensy, right? Which one?

You're sending Serial to something else? But what? Another Teensy? A PC or Mac? Some of your code fragments look like they might be meant to run on that "something else" to turn the bytes back into real units. But even there, what you're really doing would help shine light onto the weird parts like the 1.66666666666667E-07 conversion factor.

Normally we talk about complete programs on this forum. Maybe you noticed the forum rule in red at the top of every page? The idea is you can create a small program and post the complete program here. Complete programs are important, because small details matter. For example, in your original message you have a few code fragments. The first shows "uint32_t var". But then you've got "incoming16BitVar", which presumably isn't 32 bits. We can't see what type this variable is, and that detail might be important.
 
You are absolutely correct Paul, and I sincerely apologize for the ambiguity in my post.

The data is coming "in" via a CAN bus at 1Mbps. The Teensy 3.6 is "translating" the data from the CAN bus to a RS232 stream to a digital dashboard where is is decoded. I am only using Colin's FlexCan library.

The Teensy is essentially taking the data from the CAN message and encoding it in a manner that the dashboard is expecting so the decode will display the proper data and in the proper format.

The only information that is given regarding what the dashboard is expecting is that formula. There is no other documentation.

I will post complete source code this evening when I am back at home. And I am genuinely sorry for the lack of clarity in my initial post.
 
Surely the docs for the dashboard say something more than just a formula? If you're sending a RS232 stream it must mention that it accepts RS232 data. Is there on online document somewhere?

Pete
 
First, I'd suggest just connecting Teensy 3.6 to the dashboard, using whatever voltage translation it needs (eg, RS232 levels?)

Then try to write some simple program that just send the 5 bytes. To start, just send the same 5 constants. The idea is to get that part working and have some known good 5 byte sets. Do this before you worry about equations and CAN bus. Sure, keep all that other stuff in mind, but take things one step at a time. Get each part working and let the entire project build about each success.
 
Surely the docs for the dashboard say something more than just a formula? If you're sending a RS232 stream it must mention that it accepts RS232 data. Is there on online document somewhere?

Pete

This is it. http://www.race-technology.com/wiki/index.php/DataAndConfigurationMessages/18RPMInput


First, I'd suggest just connecting Teensy 3.6 to the dashboard, using whatever voltage translation it needs (eg, RS232 levels?)

Then try to write some simple program that just send the 5 bytes. To start, just send the same 5 constants. The idea is to get that part working and have some known good 5 byte sets. Do this before you worry about equations and CAN bus. Sure, keep all that other stuff in mind, but take things one step at a time. Get each part working and let the entire project build about each success.

I have tried sending just 2,000 over the two necessary bytes.

I actually have a Teensy 3.6 set up to broadcast the can message every 400ms as a constant. The ECU is not on the bus.

I am using the race-technology software and a CP2101 Serial to USB converter to read the serial stream into the software. It works flawlessly for the speed data.

The handler for the messages:
Code:
void CANBUSClass::gotFrame(CAN_message_t &frame, int mailbox)
{

	if (frame.id == 0x102) //Speed Data
	{
		uint32_t speedinCMsec = frame.buf[1] * 44.704; //Gets MPS to CM/Sec conversion 

		decoder->writeSpeedInMsec(speedinCMsec);
		return;
	}
	else if (frame.id == 0x108) //RPM data
	{
		uint16_t rpmData = 0;
		rpmData = frame.buf[0] | frame.buf[1];
		decoder->writeRPM(rpmData);

		return;
	}

	Serial.println("");
	Serial.print("Got Frame ID:");
	Serial.println(frame.id, HEX);
	Serial.print("Length is:");
	Serial.println(frame.len);
	for (int m = 0; m < frame.len; m++)
	{
		Serial.print("byte[");
		Serial.print(m);
		Serial.print("] is ");
		Serial.println(frame.buf[m]);
	}
	
}

And the writeRPM function:
Code:
void decode::writeRPM(uint16_t rpmIn)
{
	double tickPeriod = 1.66666666666667E-07;
	uint8_t channel = 18;
	uint8_t outgoingSerialMessage[5];
	outgoingSerialMessage[0] = channel;
	uint32_t completeMsg = 0;

	uint8_t Data1 = 0, Data2 = 0, Data3 = 0;
	Data1 = rpmIn >> 8;
	Data2 = rpmIn;
	completeMsg = 1 / (((Data1 * 0x10000) + (Data2 * 0x100) + (Data3))* tickPeriod);

	// 1,000 RPM = 00000011 11101000
	// 2,000 RPM = 00000111 11010000
	// 3,000 RPM = 00001011 10111000
	// 4,000 RPM = 00001111 10100000
	// 5,000 RPM = 00010011 10001000
	// 6,000 RPM = 00010111 01110000
	// 7,000 RPM = 00011011 01011000
	// 8,000 RPM = 00011111 01000000
	// 9,000 RPM = 00100011 00101000

	//5 bytes. Channel Data1 Data2 Data3 Checksum

	//Frequency = 1 / (((Data1 * 0x10000) + (Data2 * 0x100) + (Data3)) * TickPeriod) 
	//Freq = 1 / ((D1 * 65536 + D2 * 256 + d3) * TickPeriod)
	// 5000 = 1 / ((d1 * 65536) + (d2 * 256) + (d3) * 1.667;
	
	uint8_t checksum = 0;
	
	outgoingSerialMessage[3] = rpmIn >> 8;
	outgoingSerialMessage[2] = rpmIn;
	outgoingSerialMessage[1] = 0;
	for (int t = 0; t < 4; t++)
	{
		checksum += outgoingSerialMessage[t];
	}
	outgoingSerialMessage[4] = checksum;
	Serial1.write(&outgoingSerialMessage[0], 5);
	Serial.print(outgoingSerialMessage[1], BIN);
	Serial.print(":");
	Serial.print(outgoingSerialMessage[1]);
	Serial.print(" ");
	Serial.print(outgoingSerialMessage[2], BIN);
	Serial.print(":");
	Serial.print(outgoingSerialMessage[2]);
	Serial.print(" ");
	Serial.print(outgoingSerialMessage[3], BIN);
	Serial.print(":");
	Serial.print(outgoingSerialMessage[3]);
	Serial.println(" ");
	return;

}


I am the first to admit that my bitwise operations need some attention.
 
The (data1, data2, data3) is just a 24-bit integer. That integer is the time per revolution (with the 1.666e-7 as scaling factor).

Code:
uint32_t rpm_value = ...
uint32_t scaled_time_per_rev =  360000000 / rpm_value;

uint8_t data1 = (scaled_time_per_rev >> 16) & 0xffu;
uint8_t data2 = (scaled_time_per_rev >> 8) & 0xffu;
uint8_t data3 = (scaled_time_per_rev) & 0xffu;
 
Last edited:
Code:
	completeMsg = 1 / (((Data1 * 0x10000) + (Data2 * 0x100) + (Data3))* tickPeriod);
This simplifies to
Code:
	completeMsg = 6000000/(256 * rpmIn);
which removes the need for floating point division - even though you do have a fancy FPU in the T3.6 that you're probably dying to use :)

This is in the wrong order:
Code:
	outgoingSerialMessage[3] = rpmIn >> 8;
	outgoingSerialMessage[2] = rpmIn;
	outgoingSerialMessage[1] = 0;
The first byte of the three to be transmitted is the high order byte. It should be:
Code:
	outgoingSerialMessage[1] = rpmIn >> 8;
	outgoingSerialMessage[2] = rpmIn;
	outgoingSerialMessage[3] = 0;

Pete
 
This simplifies to
Code:
	completeMsg = 6000000 / (256 * rpmIn);
It actually simplifies even further, because 6000000/256 = 46875/2, to
Code:
       completeMsg = 46875 / (2*rpmIn);
which makes it an unsigned 16 bit division (assuming rpmIn < 32768).

The Cortex-M4 instruction set (including Teensy 3.6) has a hardware unsigned division that takes at most 12 cycles, so that's definitely how I'd write the code if I needed that.

More generally,
rpmIn = 0 is not defined (due to division by zero).
rpmIn = 1 .. 91 map to completeMsg = 23437 .. 257, respectively, so you could use a 92-entry 16-bit lookup table for these (including zero).
rpmIn = 92 .. 183 map to completeMsg = 254 .. 128, respectively, so you could use a 92-entry 8-bit lookup table for these -- assuming you are interested in low, < 256, RPM values.
rpmIn = 184 .. 65535 map to completeMsg = 127 .. 0, respectively, so you could use a 128-entry 16-bit lookup table and a binary search (7 steps) to locate the completeMsg value -- assuming you are at all interested in very low, < 127, RPM.
In all, that is 532 bytes of lookup tables, so you could do that even on an 8-bit AVR like Teensy 2, in less than a hundred cycles (even if written in C/C++), I think.

If you only display RPMs above 100 RPM or so, all you need is a 235-entry 16-bit lookup table (470 bytes in flash).

If you obtain the RPM value many times a second, it might make sense to apply some kind of filtering to the completeMsg value. Lower values of rpmIn -- which might be better described as rotationDuration -- map to vastly different completeMsg values, so a bit of filtering would probably make a lot of sense there. Maybe even an exponential filter? (existingValue = ((N - K) * existingValue + K * newValue) / N, where N is a power of two, and K small, say 1.)
 
This is why this forum is the absolute best.

My math was fuzzy (even on a good day) and my logic was flawed.

Thank you all, truly for pointing me in the right direction.

There will be times where the message will be 0 RPM (Key on, not started), so I'll have to compensate for that. I didn't even think about that.

Thank you all. If there ever is anything that I can do to repay your kindness, please call on me.
 
This works exactly as expected:

Code:
void decode::writeRPM(uint16_t rpmIn)
{
	uint8_t channel = 18;
	uint8_t outgoingSerialMessage[5];
	outgoingSerialMessage[0] = channel;

	//5 bytes. Channel Data1 Data2 Data3 Checksum

	//Frequency = 1 / (((Data1 * 0x10000) + (Data2 * 0x100) + (Data3)) * TickPeriod) 
	//Freq = 1 / ((D1 * 65536 + D2 * 256 + d3) * TickPeriod)
	// 5000 = 1 / ((d1 * 65536) + (d2 * 256) + (d3) * 1.667;
	
	uint8_t checksum = 0;
	
	uint32_t rpm_value = rpmIn;

	uint32_t scaled_time_per_rev = (360000000 / (rpm_value * 4));
	uint8_t data1 = (scaled_time_per_rev >> 16) & 0xffu;
	uint8_t data2 = (scaled_time_per_rev >> 8) & 0xffu;
	uint8_t data3 = (scaled_time_per_rev) & 0xffu;

	outgoingSerialMessage[1] = data1;
	outgoingSerialMessage[2] = data2;
	outgoingSerialMessage[3] = data3;
	for (int t = 0; t < 4; t++)
	{
		checksum += outgoingSerialMessage[t];
	}
	outgoingSerialMessage[4] = checksum;
	Serial1.write(&outgoingSerialMessage[0], 5);

}
 
Last edited:
Status
Not open for further replies.
Back
Top