- sample point = ((tseg1 + 1) / (tseg1 + 1 + tseg2)) * 100.0
- fcan = fsys / brp
- tq = 1.0 / fcan
- bt = 1.0 / bitrate
- btq = int(round(bt / tq))
- bus_delay = (bus_length * 5) * 1e-9
- transceiver_delay = transceiver_delay * 1e-9
- t_prop_seg = 2 * (transceiver_delay + bus_delay)
- prop_seg = -(-t_prop_seg / tq)
- phase_seg = btq - prop_seg - 1
- if phase_seg > 3
- prop_seg += phase_seg % 2
- if phase_seg < 3
- phase_seg1 = 0
- phase_seg2 = 0
- else if phase_seg == 3
- phase_seg1 = 1
- phase_seg2 = 2
- else
- phase_seg1 = phase_seg / 2
- phase_seg2 = phase_seg1
- BTR0, BTR1 (sja1000) = brp - 1 + (sjw - 1) * 64, tseg1 - 2 + (tseg2 - 1) * 16
- CAN0BT (Silabs) = brp + 64 * (sjw - 1) + tseg1 * 256 + tseg2 * 4096
- CANBR (Atmel) = phase_seg2 - 1 | (phase_seg1 - 1) << 4 | (prop_seg - 1) << 8 | (sjw - 1) << 12 | (brp - 1) << 16
- CANCTRL (Microchip) = (brp - 1) << 24 | (sjw - 1) << 22 | (phase_seg1 - 1) << 19 | (phase_seg2 - 1) << 16 | (prop_seg - 1) << 0
- CNF1, CNF2, CNF3 (Microchip) = brp - 1 + (sjw - 1) * 64, prop_seg - 2 + (phase_seg1 - 1) * 8 + 128, tseg2 - 1
- CANBTC (Texas Instruments) = (phase_seg2 - 1) & 0x7 | ((phase_seg1 + prop_seg - 1) & 0xF) << 3 | ((sjw - 1) & 0x3) << 8 | ((brp - 1) & 0xFF) << 16
- CXCONR (Renesas) = ((brp - 1 + (prop_seg - 1) * 32) + (phase_seg1 - 2 + (_tseg2 - 1) * 8 + (self.sjw - 1) * 64)) * 256
- CIBCR (Renesas) = (((phase_seg1 + prop_seg - 1) & 0x0F) << 20 | ((brp - 1) & 0x3FF) << 8 | ((sjw - 1) & 0x3) << 4 | ((phase_seg2 - 1) & 0x07)) << 8
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;
- }