CAN bit timing calculator
Posted: Mon Oct 11, 2021 1:54 pm
I am sure there are some of you that will find this code extremely helpful. It calculates the closest match for any bitrate supplied to it. There are adjustments to control the number of returned results. read the comments in the code.
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.
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.
- /* 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;
- }