It is exceedingly quick and has a small memory footprint for what it does. if doing an identical match the impact on ram is something along the lines of a couple hundred bytes, if wanting a bunch of possible matches then how much ram is used is going to depend on the number of matches.
If someone can get me the oscillator tolerance in the ESP32 I can add the calculations needed to ensure that the selected timings are within that percentage.
Code: Untitled.c Select all
/* The MIT License (MIT)
*
* Copyright (c) 2021 Kevin Schlosser
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "driver/twai.h"
#include <math.h>
#define TSEG1_MIN 2
#define TSEG1_MAX 16
#define TSEG2_MIN 1
#define TSEG2_MAX 8
#define BRP_MIN 2
#define BRP_INC 2
#define SJW_MAX 4
#define FSYS 80000000
typedef struct _machine_can_bitrate_t {
uint32_t bitrate;
uint16_t brp;
uint8_t tseg1;
uint8_t tseg2;
uint8_t sjw;
float br_err;
float sp_err;
} machine_can_bitrate_t;
// the function needs to be called 2 times, the first time passing NULL to matches and 0 to num_matches.
// The function will return the number of matches found and that number is used to create the proper sized array
// that gets passed to the matches parameter and you must also pass that value to the num_matches parameter.
// see code examples below for usage.
int get_bitrates(
uint32_t nominal_bitrate, // target bitrate
float bitrate_tolerance, // allowed percentage of drift from the target bitrate
float nominal_sample_point, // target sample point, if 0.0 is given then a CIA standard sample point will get used
float sample_point_tolerance, // allowed percentage of drift from the target sample point
uint16_t bus_length, // bus length in meters, round up or down if bus length if fractional
uint16_t transceiver_delay, // processing time of the transceiver being used (nanoseconds)
machine_can_bitrate_t *matches, // NULL or array of machine_can_bitrate_t structures
int num_matches // 0 or the length of matches
) {
// check to see if a sample point was supplied.
// If one was not then we use the CIA standard to assign a sample point
if (nominal_sample_point == 0) {
if (nominal_bitrate > 800000) {
nominal_sample_point = 75.0F;
} else if (nominal_bitrate > 500000) {
nominal_sample_point = 80.0F;
} else {
nominal_bitrate = 87.5F;
}
}
float tq;
float btq;
float bt = 1.0F / (float) nominal_bitrate;
float sample_point;
uint32_t t_prop_seg;
int match_count = 0;
uint8_t btq_rounded;
machine_can_bitrate_t match;
for (match.brp = BRP_MIN;match.brp <= TWAI_BRP_MAX;match.brp += BRP_INC) {
// the macro used here is to validate the brp. This is done because
// a V2 or greater revision ESP32 has 2 ranges of brps.
// The first range is any even number from 2 to 128, ad the second range is every 4th number from 132 to 256.
// the brp increment starts the same at 2 for both ranges so we need to verify a correct brp when
// running through the second range
if (!TWAI_BRP_IS_VALID(match.brp)) {
continue;
}
// calculate the time quanta
tq = 1.0F / ((float) FSYS / (float) match.brp);
// calculate the number of time quanta needed for the given bitrate
btq = bt / tq;
// we need to use the quanta as a whole and not fractions.
btq_rounded = (uint8_t) roundf(btq);
// if time quanta < 1.0 then the brp is unsupported for the wanted bitrate
if (btq_rounded < (TSEG1_MIN + TSEG2_MIN + 1)) {
continue;
}
// calculate the actual bitrate for the brp being used.
match.bitrate = (uint32_t) roundf(
(float)nominal_bitrate * (1.0F - (roundf(-(btq / (float) btq_rounded - 1.0F) * 10000.0F) / 10000.0F))
);
// Calculate the amount of drift from the target bitrate
match.br_err = (float) abs(match.bitrate - nominal_bitrate) / (float) nominal_bitrate * 100.0F;
// if the amount of drift exceeds the allowed amount then the brp cannot be used
if (match.br_err > bitrate_tolerance) {
continue;
}
// because we know the target sample point we are able to calculate a starting tseg1 and tseg2
// using that sample point
match.tseg1 = (uint8_t) ((float) btq_rounded * (nominal_sample_point / 100.0F)) - 1;
match.tseg2 = (uint8_t) (btq_rounded - (match.tseg1 + 1));
if (match.tseg2 < TSEG2_MIN) {
match.tseg1 += match.tseg2 - TSEG2_MIN;
match.tseg2 = TSEG2_MIN;
}
// just because we have a given sample point doesn't mean it will align on a whole tq.
// so once we get the tseg1 and tseg2 we need to calculate what the "real" sample point is
sample_point = (float) (match.tseg1 + 1) / (float) btq_rounded * 100.0F;
// once we have the sample point we need to calculate how much it drifts from the target sample point
match.sp_err = (float) fabs(
(double) ((sample_point - nominal_sample_point) / nominal_sample_point * 100.0F)
);
// I could have iterated over all of the available tseg1 values and calculated the tseg2 from that
// then checked to see if the sample point was within the allotted error for each iteration.
// This is actually a waste to do do that. The minimum allowed sample point is 50% and if the btq is 10
// that means the minimum the tseg1 value could be is 5 and that would make the tseg2 have a value of 4.
// There is a synchronization bit that gets added to make the total of 10 needed.
// it would be pointless to do iterations for tseg1 values of 1-4. wasted time.
// you also have to consider the allowed sample point shift that is given. This is what I am focusing on
// due to it creating the smallest number of iterations.
// so say we have a supplied 80% sample point with a 5% allowed drift. that would make tseg1 = 7, tseg2 = 2
// if we change the tseg1 to 6 and the tseg2 to 3 we now have a 70% sample point which is outside of the
// allowed 5% deviation.
// so what I have done is the first while loop decreases the tseg1 by 1 and increases the tseg2 by one until
// the drive is outside of the allowed amount. The second while loop then increases the tseg1 and decreases
// the tseg2 and adding each iteration to the matches until it is outside of the allowed amount. Best case
// scenario is no iteration gets performed if the initial tseg1 and tseg2 is not within the sample point
// tolerance. This saves quite a bit of time. If using an ESP32S2 the total number of brps are 16384 and
// say we use a target bitrate of 500000bps and a bitrate tolerance of 0.0 there are a total of 23 brps that
// matched. then having to do 50 iterations for each of the brps brings the total up to 345 iterations for
// the tseg1. By using the code below it lowers that iteration count to 35 between both while loops.
// That is a HUGE difference. Running the same code in Python iterating over all of the tseg1 values
// has a calculation time of 130ms. and using the code below the calculation time is 16ms.
// That's an 87.69% reduction in the time it takes to run the calculations. I have not measured the time it
// takes with C code. Typically there is a 200% increase in speed compared to Python
// I also threw in another nicety and that is if there is an exact match it will return immediately with
// only the one match.
while (
match.sp_err <= sample_point_tolerance &&
match.tseg1 + 1 >= match.tseg2 &&
match.tseg1 <= TSEG1_MAX &&
match.tseg1 >= TSEG1_MIN &&
match.tseg2 <= TSEG2_MAX &&
match.tseg2 >= TSEG2_MIN
) {
match.tseg1--;
match.tseg2++;
sample_point = (float) (match.tseg1 + 1) / (float) btq_rounded * 100.0F;
match.sp_err = (float) fabs(
(double) ((sample_point - nominal_sample_point) / nominal_sample_point * 100.0F)
);
}
match.tseg1++;
match.tseg2--;
sample_point = (float) (match.tseg1 + 1) / (float) btq_rounded * 100.0F;
match.sp_err = (float) fabs(
(double) ((sample_point - nominal_sample_point) / nominal_sample_point * 100.0F)
);
while (
match.sp_err <= sample_point_tolerance &&
match.tseg1 + 1 >= match.tseg2 &&
match.tseg1 <= TSEG1_MAX &&
match.tseg1 >= TSEG1_MIN &&
match.tseg2 <= TSEG2_MAX &&
match.tseg2 >= TSEG2_MIN
) {
if (num_matches > 0) {
t_prop_seg = (uint32_t) (
2.0F * (((float) transceiver_delay * 0.000000001F) + ((float) (bus_length * 5) * 0.000000001F))
);
match.sjw = (uint8_t) (btq_rounded - ((uint8_t) -((float) -t_prop_seg / tq)) - 1);
if (match.sjw < 3) {
match.sjw = 0;
} else if (match.sjw == 3) {
match.sjw = 1;
} else {
match.sjw = match.sjw / 2;
}
if (match.sjw > SJW_MAX) {
match.sjw = SJW_MAX;
}
if (match.sp_err == 0.0F && match.br_err == 0.0F) {
matches[0] = match;
return 1;
}
if (match_count == num_matches) {
if (num_matches == 1) {
if (
match.br_err <= matches[0].br_err &&
match.sp_err <= matches[0].sp_err
) {
matches[0] = match;
}
} else {
return -1;
}
} else {
matches[match_count] = match;
match_count++;
}
} else if (match.sp_err == 0.0F && match.br_err == 0.0F) {
return 1;
} else {
match_count ++;
}
match.tseg1++;
match.tseg2--;
sample_point = (float) (match.tseg1 + 1) / (float) btq_rounded * 100.0F;
match.sp_err = (float) fabs(
(double) ((sample_point - nominal_sample_point) / nominal_sample_point * 100.0F)
);
}
}
return match_count;
}
// ************* CODE EXAMPLES **************
// this code example returns all found bitrates that match the input values.
// if an identical match is found that is the only one that will get populated.
// If I knew what the oscillator drift % was I could fine tune the script so it
// would return only bitrates that are within that percentage
int count = get_bitrates(
500000,
2.0F,
62.0F,
10.0F,
1,
150,
NULL,
0
);
if (count > 0) {
machine_can_bitrate_t bitrates[count]
if (get_bitrates(500000, 2.0F, 62.0F, 10.0F, 1, 150, bitrates, count) > 0) {
for (int i = 0;i < count;i++) {
bitrates[i].bitrate;
bitrates[i].brp;
bitrates[i].tseg1;
bitrates[i].tseg2;
bitrates[i].sjw;
bitrates[i].br_err;
bitrates[i].sp_err;
}
}
}
// this code example returns the closest match to the input values
machine_can_bitrate_t bitrates[1]
if (get_bitrates(500000, 2.0F, 62.0F, 10.0F, 1, 150, bitrates, 1) > 0) {
bitrates[0].bitrate;
bitrates[0].brp;
bitrates[0].tseg1;
bitrates[0].tseg2;
bitrates[0].sjw;
bitrates[0].br_err;
bitrates[0].sp_err;
}