It took me a couple of days to get to it. The problem persists.
I made a new fixture that would let me put a 12.5mm plastic spacer from the end of the stainless shaft to the magnet. That’s about 15mm from the bearing. The noise is about the same.
It’s hard to shift the magnet relative to the axis with the spacer. I’ve done my best to center it. But in the previous tests (before the spacer), I has able to push the magnet to slightly different positions, and it didn’t matter. I just don’t think that’s what’s going wrong.
Finally, I tried your suggested zero-setting test. It also shows bad results. Essentially the noise follows the reported numbers, even when the magnet hasn’t moved. Here is a table of the results. It also works the reverse if I start between 0 and 180 (can’t post a 4th image, apparently).
I’ll include the Arduino sketch that I’m using for this last one, in case you spot anything stupid I’m doing. I can’t attach it as a file, so it’s just below. (Yes, it is vibe coded.)
Maybe it’s time I get a new MA600 breakout. I’ve ordered a MA600A (not MA600 this time) reference board from MPS. I’ll also order a new kind of microcontroller. I’m working on getting a SAMD21 board.
// Adjusts the zero-setting in the registers, to see if that moves where the noise is.
// MPS asked me to try this.
// MA600 / MA600A zero-offset A/B/A test. XIAO RP2350, Arduino-Pico core.
// CS D7, SCK D8, MISO D9, MOSI D10. Same wiring as previous sketches.
// s: ~20 Hz stream; b: ONE original/shifted/restored experiment; d: export.
// Hold the magnet and board stationary throughout all three stages.
// FW is explicitly set to 5 at startup and remains 5. Only volatile writes.
// Registers 0/1 are restored after the experiment, including on detected failure.
// No NVM store commands. If interrupted, power-cycle SENSOR and MCU together.
#include <Arduino.h>
#include <SPI.h>
#include <math.h>
#include <stdio.h>
constexpr uint8_t ENCODER_CS = D7;
constexpr uint8_t FILTER_FW = 5;
constexpr uint32_t SAMPLE_HZ = 1000;
constexpr unsigned SAMPLE_COUNT = 2000;
constexpr uint32_t SETTLE_MS = 3000;
constexpr uint16_t ZERO_SHIFT_COUNTS = 32768; // 180 degrees
constexpr uint32_t PERIOD_US = 1000000UL / SAMPLE_HZ;
constexpr double DEG_PER_COUNT = 360.0 / 65536.0;
static_assert(SAMPLE_HZ > 0 && SAMPLE_HZ <= 20000 && 1000000UL % SAMPLE_HZ == 0,
“Choose a rate <=20 kHz that divides 1000000 exactly.”);
static_assert(SAMPLE_COUNT >= 2 && SAMPLE_COUNT <= 10000, “Use 2..10000 samples.”);
SPISettings encoderSPI(1000000, MSBFIRST, SPI_MODE0);
struct Capture {
uint16_t raw[SAMPLE_COUNT];
uint32_t timeUs[SAMPLE_COUNT];
uint16_t zero;
uint32_t missed, maxLate;
uint8_t status;
bool valid;
};
Capture captures[3];
const char* const stageNames[3] = {“ORIGINAL_BEFORE”, “ZERO_SHIFT_180”, “ORIGINAL_AFTER”};
uint8_t expectedFilter = 5;
uint32_t experiment = 0, lastStream = 0;
bool ready = false, streaming = true;
uint16_t frame(uint16_t command) {
SPI.beginTransaction(encoderSPI);
digitalWrite(ENCODER_CS, LOW);
delayMicroseconds(1);
uint16_t value = SPI.transfer16(command);
delayMicroseconds(1);
digitalWrite(ENCODER_CS, HIGH);
SPI.endTransaction();
delayMicroseconds(5);
return value;
}
uint8_t readRegister(uint8_t address) {
frame(0xD200u | address);
return static_cast<uint8_t>(frame(0));
}
bool writeRegister(uint8_t address, uint8_t value) {
frame(0xEA54);
frame((static_cast<uint16_t>(address) << 8) | value);
uint8_t echo = static_cast<uint8_t>(frame(0));
delay(1);
uint8_t actual = readRegister(address);
bool ok = echo == value && actual == value;
char line[120];
snprintf(line, sizeof(line), “REG%u requested=0x%02X echo=0x%02X readback=0x%02X %s”,
unsigned(address), unsigned(value), unsigned(echo), unsigned(actual), ok ? “OK” : “FAILED”);
Serial.println(line);
return ok;
}
uint16_t readZero() {
uint8_t low = readRegister(0);
uint8_t high = readRegister(1);
return (static_cast<uint16_t>(high) << 8) | low;
}
bool setZero(uint16_t zero) {
// Do not short-circuit: attempt both writes, including during restoration.
bool lowOK = writeRegister(0, static_cast<uint8_t>(zero));
bool highOK = writeRegister(1, static_cast<uint8_t>(zero >> 8));
return lowOK && highOK && readZero() == zero;
}
bool verify(uint16_t zero) {
return readZero() == zero && readRegister(13) == expectedFilter && readRegister(28) == 0;
}
int32_t difference(uint16_t raw, uint16_t anchor) {
int32_t d = static_cast<int32_t>(raw) - anchor;
if (d > 32767) d -= 65536;
if (d < -32768) d += 65536;
return d;
}
bool captureStage(unsigned stage, uint16_t zero) {
Capture& c = captures[stage];
c.valid = false;
c.zero = zero;
c.missed = c.maxLate = 0;
Serial.print(“STAGE “); Serial.print(stageNames[stage]);
Serial.println(” – settling 3 seconds; do not touch anything.”);
Serial.flush();
delay(SETTLE_MS);
if (!verify(zero)) {
Serial.println(“ABORT: configuration verification failed.”);
return false;
}
frame(0); // Discard first angle after register reads.
delay(100);
uint32_t deadline = micros();
SPI.beginTransaction(encoderSPI);
for (unsigned i = 0; i < SAMPLE_COUNT; ++i) {
uint32_t now;
do { now = micros(); } while (static_cast<int32_t>(now - deadline) < 0);
uint32_t late = now - deadline;
if (late > c.maxLate) c.maxLate = late;
if (late >= PERIOD_US) {
c.missed += late / PERIOD_US;
deadline = now; // Avoid a rapid catch-up burst.
}
c.timeUs[i] = micros(); // Host CS timestamp, not sensor conversion time.
digitalWrite(ENCODER_CS, LOW);
delayMicroseconds(1);
c.raw[i] = SPI.transfer16(0);
delayMicroseconds(1);
digitalWrite(ENCODER_CS, HIGH);
deadline += PERIOD_US;
}
SPI.endTransaction();
delayMicroseconds(5);
c.status = readRegister(26);
c.valid = verify(zero);
if (!c.valid) Serial.println(“ABORT: configuration changed during capture.”);
return c.valid;
}
void summary(unsigned stage) {
const Capture& c = captures[stage];
if (!c.valid) return;
double mean = 0, m2 = 0;
int32_t low = 0, high = 0;
uint32_t minDt = UINT32_MAX, maxDt = 0;
for (unsigned i = 0; i < SAMPLE_COUNT; ++i) {
int32_t x = difference(c.raw[i], c.raw[0]);
if (x < low) low = x;
if (x > high) high = x;
double d = x - mean;
mean += d / (i + 1);
m2 += d * (x - mean);
if (i) {
uint32_t dt = c.timeUs[i] - c.timeUs[i-1];
if (dt < minDt) minDt = dt;
if (dt > maxDt) maxDt = dt;
}
}
double angle = fmod((c.raw[0] + mean) * DEG_PER_COUNT + 360.0, 360.0);
uint32_t span = c.timeUs[SAMPLE_COUNT-1] - c.timeUs[0];
char line[340];
snprintf(line, sizeof(line),
“SUMMARY experiment=%lu stage=%s fw=%u zero=0x%04X zero_deg=%.6f n=%u mean_deg=%.6f sd_deg=%.6f pp_deg=%.6f status26=0x%02X”,
(unsigned long)experiment, stageNames[stage], unsigned(FILTER_FW), unsigned(c.zero),
c.zero * DEG_PER_COUNT, SAMPLE_COUNT, angle, sqrt(m2/(SAMPLE_COUNT-1))*DEG_PER_COUNT,
(high-low)*DEG_PER_COUNT, unsigned(c.status));
Serial.println(line);
snprintf(line, sizeof(line),
“TIMING stage=%s actual_hz=%.3f min_interval_us=%lu max_interval_us=%lu max_lateness_us=%lu missed_slots=%lu”,
stageNames[stage], span ? (SAMPLE_COUNT-1)*1000000.0/span : 0,
(unsigned long)minDt, (unsigned long)maxDt, (unsigned long)c.maxLate, (unsigned long)c.missed);
Serial.println(line);
if (c.missed) Serial.println(“WARNING: timing was not uniform; repeat this experiment.”);
if (high-low >= 32768) Serial.println(“WARNING: excessive motion; stationary statistics are invalid.”);
}
void runExperiment() {
streaming = false;
if (!ready) { Serial.println(“Sensor not ready; power-cycle sensor and MCU.”); return; }
uint16_t original = readZero();
if (!verify(original) || readZero() != original) {
Serial.println(“ABORT: unstable/unexpected configuration; no zero writes made.”); return;
}
++experiment;
for (unsigned j=0; j<3; ++j) captures[j].valid = false;
Serial.println(“\n=== ZERO SHIFT TEST BEGIN: FW5, MODE0, SPI 1MHz, direct 1kHz ===”);
Serial.println(“Keep rotor AND sensor stationary until DONE. No external averaging.”);
Serial.println(“SD uses n-1, with wrap-safe differences from each stage’s first sample.”);
bool ok = captureStage(0, original);
if (ok) ok = setZero(static_cast<uint16_t>(original + ZERO_SHIFT_COUNTS));
if (ok) ok = captureStage(1, static_cast<uint16_t>(original + ZERO_SHIFT_COUNTS));
// Always attempt restoration, even if an earlier operation failed.
bool restored = setZero(original);
if (restored && ok) ok = captureStage(2, original);
restored = restored && readZero() == original;
if (!restored) {
ready = false;
Serial.println(“RESTORE FAILED: power-cycle BOTH sensor and MCU before continuing.”);
} else Serial.println(“Original zero restored and verified. FW remains 5.”);
for (unsigned j=0; j<3; ++j) summary(j);
Serial.println(ok && restored ? “=== ZERO SHIFT TEST DONE ===” : “=== ZERO SHIFT TEST INCOMPLETE ===”);
// Discard commands typed during the experiment; a batch never auto-repeats.
while (Serial.available()) Serial.read();
Serial.println(“IDLE: s=stream, b=new experiment, d=export saved valid stages.”);
}
void exportData() {
streaming = false;
Serial.println(“experiment,stage,fw,zero_counts,sample,t_us,raw,angle_deg”);
char line[180];
for (unsigned j=0; j<3; ++j) {
const Capture& c = captures[j];
if (!c.valid) continue;
for (unsigned i=0; i<SAMPLE_COUNT; ++i) {
snprintf(line, sizeof(line), “%lu,%s,%u,%u,%u,%lu,%u,%.6f”,
(unsigned long)experiment, stageNames[j], unsigned(FILTER_FW), unsigned(c.zero), i+1,
(unsigned long)(c.timeUs[i]-c.timeUs[0]), unsigned(c.raw[i]), c.raw[i]*DEG_PER_COUNT);
Serial.println(line);
}
}
Serial.println(“=== EXPORT END ===”);
}
void setup() {
digitalWrite(ENCODER_CS, HIGH);
pinMode(ENCODER_CS, OUTPUT);
SPI.setSCK(D8); SPI.setRX(D9); SPI.setTX(D10); SPI.begin();
Serial.begin(115200);
uint32_t started = millis();
while (!Serial && millis()-started < 10000) delay(10);
delay(300);
Serial.println(“MA600 zero-shift experiment: s=stream, b=one test, d=export.”);
Serial.println(“Writes are volatile; no NVM store. FW explicitly set to 5.”);
if (readRegister(28) != 0) {
Serial.println(“ERROR: expected reg28=0. Power-cycle sensor and MCU.”); return;
}
expectedFilter = (readRegister(13) & 0xF0u) | FILTER_FW;
ready = writeRegister(13, expectedFilter);
delay(100);
}
void loop() {
if (Serial.available()) {
char cmd = Serial.read();
if (cmd==‘b’ || cmd==‘B’) runExperiment();
else if (cmd==‘d’ || cmd==‘D’) exportData();
else if (cmd==‘s’ || cmd==‘S’) streaming = true;
}
if (!ready) {
if (millis()-lastStream >= 2000) {
lastStream = millis();
Serial.println(“CONFIG ERROR: power-cycle sensor and MCU, then reopen monitor.”);
}
return;
}
if (streaming && millis()-lastStream >= 50) {
lastStream = millis();
uint16_t raw = frame(0);
char line[100];
snprintf(line, sizeof(line), “fw=%u raw=%5u angle=%10.6f deg”,
unsigned(FILTER_FW), unsigned(raw), raw*DEG_PER_COUNT);
Serial.println(line);
}
}