Encoder reading using SPI port (Biss-C)

You would have to take the remainder after dividing by thirty, so:

float angle_ = (rawval16 % 30) / 30.0f * _2PI ;

But 16bit / 30 = 2184.5333, i.e. 16 bits is not an exact multiple of the gear-down…

So you may want to convert do the whole thing using float math:

float step = 0x10000/30.0f;
float angle_ = fmod((float)rawval16, step)/step * _2PI;

Does that help any?

I tried it, but result got worse, the gearing is also on output shaft and not on encoder motor shaft.

Same applies to 10 - 16 bit is not an exact multiple, so:

float step = 0x10000/10.0f;
float angle_ = fmod((float)rawval16, step)/step * _2PI;

I tried that too, but now it jumps very fast between 0 - 2PI.

So I suppose some magic math like you made now but it step up 10x? But still makes no sense when encoder states: Absolute position / revolution (motor side)

This code should do that:

float step = 0x10000/10.0f;
float angle_ = fmod((float)rawval16, step)/step * _2PI;

I also don’t quite understand this datasheet…

With this code it jumps from 0 - 2PI each ~1 degree, so it should be the other way around.

Yes the datasheet is very bad sadly, and from big engine company :slight_smile:

Well, you can also multiply by 10 first:

uint32 raw_shaft = raw_angle16 * 10;
_angle = (float)raw_shaft / (float)0x10000 * _2PI;    // range is now 0 - 10x 2PI
_angle = _normalizeAngle(_angle);                            // shaft angle, range 0 - 2PI

But it should be the same result… unless I’ve missed something obvious. That happens sometimes :slight_smile:

Hi,

Yes just small jumps from 0-2PI each degree, like should require to remap the full range ~10xPI2 to 2PI somehow, not sure how to make that. Or maybe increment parameter (adds/subtract during motion) and use that value as raw.

Just spinning ideas :slight_smile:

It’s very wierd, maybe they failed to calibrate it from factory for a full turn. But that is a long shoot :wink:

I still can’t shake the feeling it may be something more basic wrong.
Hey, could you maybe try this:

sensor.update();
Serial.print(buff[0], HEX);
Serial.print(" ");
Serial.print(buff[1], HEX);
Serial.print(" ");
Serial.print(buff[2], HEX);
Serial.print(" ");
Serial.print(rawval16, HEX);
Serial.print(" ");
Serial.println(_angle,4);
delay(500); // print angles 2x per second

while you turn the motor not through 10-20°?

I’d like to see the output…

Hi, Sorry for late response I was travling:

Here is the output for ~10-20° using - uint16_t rawval16 = (buff[1]<<8) | buff[2];

D3 0 7F 7F 0.0122
D3 17 2E 172E 0.5689
D3 22 4F 224F 0.8421
D3 2A B6 2AB6 1.0483
D3 2D CF 2DCF 1.1243
D3 39 BF 39BF 1.4173
D3 49 56 4956 1.7999
D3 53 AF 53AF 2.0539
D3 62 2C 622C 2.4095
D3 70 C7 70C7 2.7680
D3 7B DF 7BDF 3.0403
D3 8C 4E 8C4E 3.4436
D3 96 86 9686 3.6944
D3 A3 C7 A3C7 4.0197
D3 B1 CE B1CE 4.3640
D3 BB 67 BB67 4.5995
D3 CA EE CAEE 4.9806
D3 D9 BE D9BE 5.3442
D3 ED 36 ED36 5.8220
D3 FD CF FDCF 6.2294
D4 11 9F 119F 0.4325

Hey, looking at this output, it looks like there are some more bits in there in the first byte that we need to take into account?

Looking at the last three lines:

D3 ED 36 ED36 5.8220
D3 FD CF FDCF 6.2294
D4 11 9F 119F 0.4325

As the sensor has wrapped around, the first byte changes from D3 → D4 …
On the other hand, it looks like there is in fact no CRC value included in the first byte, otherwise it should be changing for each value transmitted.

So I think we need to investigate the binary protocol some more…

Could you use this method to see which bits are changing as you turn the motor through a whole rotation? We’re especially interested in the first byte.
Could you perhaps also print the value of the first byte like this:
Serial.print(buff[0]&0x3F, HEX);

Hi,

It count from 10 to 17 during a full turn of motor: using Serial.print(buff[0]&0x3F, HEX);about +1 each 20 degree ~ so it clearly notice the full rotation around.

10
11
11
11
11
12
12
12
13
13
13
13
14
14
14
14
14
14
14
14
15
15
15
15
15
16
16
16
16
17
17
17
17

Ps, I found some parsing for Vesc that use biss-c maybe it’s useful info? but it’s for 22 bit encoder ```

https://github.com/manoukianv/vescFirmware4OpenFFBoard/releases

void computeBisscData(SPIDriver *spip) {

	spiUnselectI(spip);

	int lenghtDataBit = enc_counts;

	uint64_t rxData64;
	rxData64 = (uint64_t)decod_buf[0] << 56;
	rxData64 |= (uint64_t)decod_buf[1] << 48;
	rxData64 |= (uint64_t)decod_buf[2] << 40;
	rxData64 |= (uint64_t)decod_buf[3] << 32;
	rxData64 |= (uint64_t)decod_buf[4] << 24;
	rxData64 |= (uint64_t)decod_buf[5] << 16;
	rxData64 |= (uint64_t)decod_buf[6] << 8;
	rxData64 |= (uint64_t)decod_buf[7];

	// sample of rxData64
	// like this 1100000000000000100001100111010000000101110111100000000000000000
	rxData64 <<= __builtin_clzll(rxData64);		// slice rxData to have a value starting with 1
	rxData64 &= 0x3FFFFFFFFFFFFFFF; 			// remove the 2 first bit

	// remove the first 1, count how many digit stay in buffer after removing the 0, if there is more than 32 digits,
	// keep only 32st (on the left)
	// 32 because the format is : (1+1+lenghtDataBit+1+1+6) - Align bitstream to left (Startbit, CDS, 22-bit Position, Error, Warning, CRC)
	int nbBit = log2(rxData64)+1;
	if ( nbBit >= ( lenghtDataBit + 10 ) ) {
		rxData64 >>= nbBit-( lenghtDataBit + 10 );
	}

	uint8_t crcRx = rxData64 & 0x3F; 									 //extract last 6-bit digits to get CRC
	uint32_t dataRx = (rxData64 >> 6) & ((1<<(lenghtDataBit + 2)) - 1);  //Shift out CRC, AND with 24-bit mask to get raw data (position, error, warning)
	spi_val = (dataRx >> 2) & ((1<<lenghtDataBit) - 1); 			     //Shift out error and warning, AND with 22-bit mask to get position

	uint8_t crc = 0;  //CRC seed is 0b000000
	crc = ((dataRx >> 30) & 0x03);
	crc = tableCRC6n[((dataRx >> 24) & 0x3F) ^ crc];
	crc = tableCRC6n[((dataRx >> 18) & 0x3F) ^ crc];
	crc = tableCRC6n[((dataRx >> 12) & 0x3F) ^ crc];
	crc = tableCRC6n[((dataRx >> 6) & 0x3F) ^ crc];
	crc = tableCRC6n[((dataRx >> 0) & 0x3F) ^ crc];
	crc = 0x3F & ~crc; //CRC is output inverted

	if(crc != crcRx)
	{
		++spi_error_cnt;
		UTILS_LP_FAST(spi_error_rate, 1.0, 1./BISSC_SAMPLE_RATE_HZ);
	} else {
		last_enc_angle = ((float)spi_val * 360.0) / ((1<<lenghtDataBit) - 1);
		UTILS_LP_FAST(spi_error_rate, 0.0, 1./BISSC_SAMPLE_RATE_HZ);
	}

#ifndef BISSC_ISR_IMPL
	chEvtSignalI(bissc_tp, (eventmask_t) 1);
#endif

}

That’s interesting…

I can’t make so much sense of that code right now, I don’t have time to look at it in detail now…

But from your output it looks like the bottom 3 bits of buff[0] are still part of the angle.

You can try with:

uint16_t rawval16 = (buff[0]&0x07)<<13) | (buff[1]<<5) | ((buff[2]&0xF8)>>3);
float angle = (float)rawval16 / 0x10000 * _2PI;      // angle in radians

and see if this gives a better result?

With that magic it looks much better! Now a full turn of motor shaft result of 0 - 2PI, Thank you alot! I will try it fully with motor and see results.

0.00
0.31
0.51
0.68
0.80
0.95
1.09
1.30
1.44
1.60
1.65
1.80
1.94
2.14
2.44
2.69
2.92
3.14
3.46
3.37
3.62
3.62
3.70
3.79
3.89
3.97
4.08
4.23
4.43
4.61
4.87
5.04
5.21
5.41
5.56
5.64
5.74
5.87
5.97
6.09
6.26
1 Like

I made some tests on STM32, motor still shake alot after call motor.move(1) and readings seems fine from encoder 0 - 2PI using getSensorAngle, I kind of notice when debug that value from encoder sometimes jump a little and this probably cause it, can this be beacuse of SPI speed or not check CRC?

SimpleFoc Code:

#include <SimpleFOC.h>

#include "biss_c.h"

const uint16_t MOTOR_INH_A{PC6};
const uint16_t MOTOR_INH_B{PC7};
const uint16_t MOTOR_INH_C{PC8};

const uint16_t MOTOR_INL_A{PA7};
const uint16_t MOTOR_INL_B{PB0};
const uint16_t MOTOR_INL_C{PB1};

const uint16_t STATUS_LED{PA0};

const uint16_t OC_ADJ{PB5};
const uint16_t M_OC{PB6};
const uint16_t M_PWM{PB7};
const uint16_t nFAULT{PD2};
const uint16_t OC_GAIN{PA5};
const uint16_t EN_GATE{PB12};

BLDCMotor motor = BLDCMotor(6, 3.7);

BLDCDriver3PWM driver = BLDCDriver3PWM(MOTOR_INH_A, MOTOR_INH_B, MOTOR_INH_C, EN_GATE);

#define CLOCK_SPEED 1'000'000       // 1.0 MHz

encoder::biss_c encoder_bissc(SPI, 0x10000, CLOCK_SPEED, SPI_MODE1);

void setup()
{
    encoder_bissc.init();

    motor.linkSensor(&encoder_bissc);

    pinMode(nFAULT, INPUT);
    pinMode(M_OC,OUTPUT);
    pinMode(M_PWM,OUTPUT);
    pinMode(OC_ADJ,OUTPUT);
    pinMode(OC_GAIN,OUTPUT);

    pinMode(STATUS_LED, OUTPUT);
    digitalWrite(STATUS_LED, LOW);

    // DRV8302 specific code
    digitalWrite(M_OC,LOW);
    digitalWrite(M_PWM,HIGH);
    digitalWrite(OC_ADJ,HIGH);

    _delay(1000);

    // power supply voltage [V]
    driver.voltage_power_supply = 24;
    driver.init();

    motor.linkDriver(&driver);
    motor.phase_resistance = 3.7;
    motor.torque_controller = TorqueControlType::voltage;
    motor.controller = MotionControlType::torque;
    motor.init();
    motor.initFOC();

    _delay(1000);
}

void loop()
{
    motor.loopFOC();
    motor.move(1);
}

simple test driver

#include "biss_c.h"

namespace encoder
{
biss_c::biss_c(SPIClass& handler, uint32_t bitcount, uint32_t clock, uint8_t mode) :
    spi_handler_(handler),
    bitcount_(bitcount),
    clock_speed_(clock),
    mode_(mode)
{
}

void biss_c::init()
{
    settings_ = SPISettings(clock_speed_, MSBFIRST, mode_);

    spi_handler_.setSCLK(PC10);
    spi_handler_.setMISO(PC11);
    spi_handler_.setMOSI(PC12);
    spi_handler_.begin();

    Sensor::init();
}

float biss_c::getSensorAngle()
{
    spi_handler_.beginTransaction(settings_);
    spi_handler_.transfer(buff, 3);
    spi_handler_.endTransaction();

    uint16_t rawval16 = ((buff[0]&0x07)<<13) | (buff[1]<<5) | ((buff[2]&0xF8)>>3);
    float angle_ = (float)rawval16 / bitcount_ * _2PI;      // angle in radians
    return angle_;
}

void biss_c::close()
{
    spi_handler_.end();
}
}
#pragma once

#include <SPI.h>
#include <SimpleFOC.h>

namespace encoder
{
class biss_c : 
    public Sensor
{
public:

    explicit biss_c(SPIClass& handler, uint32_t bitcount, uint32_t clock, uint8_t mode);

    void init();
    float getSensorAngle() override;

private:

    void close();

    SPIClass& spi_handler_;
    SPISettings settings_;
    uint32_t clock_speed_{0};
    uint32_t bitcount_{0};
    uint8_t mode_{0};
    uint8_t buff[3] = { 0x00, 0x00, 0x00 };
};
}

UPDATE:
I also notice when remove motor.initFOC(); encoder show correct values all the time and does not jump around, what can be the cause of it?

When you remove motor.initFOC() the motor is not initialised, and so there is no commutation signals.

So very likely your problems are caused by interference from the motor commutation, either electrical or magnetic.

You can maybe reduce cable length, route the cables in a better way, reduce SPI speed or take other measures to reduce interference.

Adding the CRC check would also help to eliminate bad readings. But at the moment we don’t know where in the data the CRC bits are located…

Hi,

I mean the values for encoder jumps around after the calibration is made(initFOC), if the calibration is skipped the values from encoder looks good, I suppose there is some reading that are wrong during calibration that result in this behaviour.

I will do some more tests, thank you.

no, I think the difference is that the motor is enabled after the initFOC() and it is not enabled if you skip it…

You can test this theory by calling motor.disable() after the initFOC(), it should disable the driver via the EN_GATE pin, and then the sensor values will be stable again, I’m guessing…

Yes you are correct, motor.disable() the encoder show correct values, just can’t understand why this happens when cables are short and shielded correctly.