HF-AS5047U Driver  0.1.0-dev
HF-AS5047U C++ Driver
Loading...
Searching...
No Matches
as5047u.ipp
Go to the documentation of this file.
1
6#ifndef AS5047U_IMPL
7#define AS5047U_IMPL
8
9#ifdef AS5047U_HEADER_INCLUDED
10#include "../inc/as5047u.hpp"
11#else
12#include "../inc/as5047u.hpp"
13#endif
14
15#include <iomanip> // for std::hex and formatting
16
17namespace as5047u {
18
19// Member function definitions
20template <typename SpiType>
22 this->frame_format_ = format;
23}
24
25// ══════════════════════════════════════════════════════════════════════════════════════════
26// PRIVATE HELPERS
27// ══════════════════════════════════════════════════════════════════════════════════════════
28
29// Helper: encode/decode register value
30
31// Encoding/decoding helpers are defined inline in the header
32
33// ══════════════════════════════════════════════════════════════════════════════════════════
34// PUBLIC HIGH-LEVEL API
35// ══════════════════════════════════════════════════════════════════════════════════════════
36
37template <typename SpiType>
38uint16_t AS5047U<SpiType>::GetAngle(uint8_t retries) const {
39 uint16_t val = 0;
40 constexpr uint16_t retryMask = static_cast<uint16_t>(AS5047U_Error::CrcError) |
41 static_cast<uint16_t>(AS5047U_Error::FramingError);
42 for (uint8_t i = 0; i <= retries; ++i) {
43 val = this->template ReadReg<AS5047U_REG::ANGLECOM>().bits.ANGLECOM_value;
44 auto err = GetStickyErrorFlags();
45 if ((static_cast<uint16_t>(err) & retryMask) == 0U) {
46 break;
47 }
48 }
49 return val;
50}
51
52template <typename SpiType>
53float AS5047U<SpiType>::GetAngle(AngleUnit unit, uint8_t retries) const {
54 switch (unit) {
55 case AngleUnit::Lsb:
56 return static_cast<float>(GetAngle(retries));
58 return GetAngleDegrees(retries);
60 return GetAngleRadians(retries);
61 default:
62 return static_cast<float>(GetAngle(retries));
63 }
64}
65
66template <typename SpiType>
67float AS5047U<SpiType>::GetAngleDegrees(uint8_t retries) const {
68 return static_cast<float>(GetAngle(retries)) * Angle::DEG_PER_LSB;
69}
70
71template <typename SpiType>
72float AS5047U<SpiType>::GetAngleRadians(uint8_t retries) const {
73 return static_cast<float>(GetAngle(retries)) * Angle::RAD_PER_LSB;
74}
75
76template <typename SpiType>
77uint16_t AS5047U<SpiType>::GetRawAngle(uint8_t retries) const {
78 uint16_t val = 0;
79 constexpr uint16_t retryMask = static_cast<uint16_t>(AS5047U_Error::CrcError) |
80 static_cast<uint16_t>(AS5047U_Error::FramingError);
81 for (uint8_t i = 0; i <= retries; ++i) {
82 val = this->template ReadReg<AS5047U_REG::ANGLEUNC>().bits.ANGLEUNC_value;
83 auto err = GetStickyErrorFlags();
84 if ((static_cast<uint16_t>(err) & retryMask) == 0U) {
85 break;
86 }
87 }
88 return val;
89}
90
91template <typename SpiType>
92int16_t AS5047U<SpiType>::GetVelocity(uint8_t retries) const {
93 int16_t val = 0;
94 constexpr uint16_t retryMask = static_cast<uint16_t>(AS5047U_Error::CrcError) |
95 static_cast<uint16_t>(AS5047U_Error::FramingError);
96 for (uint8_t i = 0; i <= retries; ++i) {
97 auto v = this->template ReadReg<AS5047U_REG::VEL>().bits.VEL_value;
98 val = static_cast<int16_t>((static_cast<int16_t>(v << 2)) >> 2);
99 auto err = GetStickyErrorFlags();
100 if ((static_cast<uint16_t>(err) & retryMask) == 0U) {
101 break;
102 }
103 }
104 return val;
105}
106
107template <typename SpiType>
108float AS5047U<SpiType>::GetVelocity(VelocityUnit unit, uint8_t retries) const {
109 switch (unit) {
111 return static_cast<float>(GetVelocity(retries));
113 return GetVelocityDegPerSec(retries);
115 return GetVelocityRadPerSec(retries);
117 return GetVelocityRPM(retries);
118 default:
119 return static_cast<float>(GetVelocity(retries));
120 }
121}
122
123template <typename SpiType>
124float AS5047U<SpiType>::GetVelocityDegPerSec(uint8_t retries) const {
125 return GetVelocity(retries) * Velocity::DEG_PER_LSB;
126}
127
128template <typename SpiType>
129float AS5047U<SpiType>::GetVelocityRadPerSec(uint8_t retries) const {
130 return GetVelocity(retries) * Velocity::RAD_PER_LSB;
131}
132
133template <typename SpiType>
134float AS5047U<SpiType>::GetVelocityRPM(uint8_t retries) const {
135 return GetVelocity(retries) * Velocity::RPM_PER_LSB;
136}
137
138template <typename SpiType>
139uint8_t AS5047U<SpiType>::GetAGC(uint8_t retries) const {
140 uint8_t val = 0;
141 constexpr uint16_t retryMask = static_cast<uint16_t>(AS5047U_Error::CrcError) |
142 static_cast<uint16_t>(AS5047U_Error::FramingError);
143 for (uint8_t i = 0; i <= retries; ++i) {
144 val = this->template ReadReg<AS5047U_REG::AGC>().bits.AGC_value;
145 auto err = GetStickyErrorFlags();
146 if ((static_cast<uint16_t>(err) & retryMask) == 0U) {
147 break;
148 }
149 }
150 return val;
151}
152
153template <typename SpiType>
154uint16_t AS5047U<SpiType>::GetMagnitude(uint8_t retries) const {
155 uint16_t val = 0;
156 constexpr uint16_t retryMask = static_cast<uint16_t>(AS5047U_Error::CrcError) |
157 static_cast<uint16_t>(AS5047U_Error::FramingError);
158 for (uint8_t i = 0; i <= retries; ++i) {
159 val = this->template ReadReg<AS5047U_REG::MAG>().bits.MAG_value;
160 auto err = GetStickyErrorFlags();
161 if ((static_cast<uint16_t>(err) & retryMask) == 0U) {
162 break;
163 }
164 }
165 return val;
166}
167
168template <typename SpiType>
169uint16_t AS5047U<SpiType>::GetErrorFlags(uint8_t retries) const {
170 uint16_t val = 0;
171 for (uint8_t i = 0; i <= retries; ++i) {
172 val = this->template ReadReg<AS5047U_REG::ERRFL>().value;
173 if (val == 0U) {
174 break;
175 }
176 }
177 return val;
178}
179
180template <typename SpiType>
181uint16_t AS5047U<SpiType>::GetZeroPosition(uint8_t retries) const {
182 uint8_t m = 0;
183 uint8_t l = 0;
184 constexpr uint16_t retryMask = static_cast<uint16_t>(AS5047U_Error::CrcError) |
185 static_cast<uint16_t>(AS5047U_Error::FramingError);
186
187 // First read ZPOSM with retries
188 for (uint8_t i = 0; i <= retries; ++i) {
189 m = this->template ReadReg<AS5047U_REG::ZPOSM>().bits.ZPOSM_bits;
190 auto err = GetStickyErrorFlags();
191 if ((static_cast<uint16_t>(err) & retryMask) == 0U) {
192 break;
193 }
194 }
195
196 // Then read ZPOSL with retries
197 for (uint8_t i = 0; i <= retries; ++i) {
198 l = this->template ReadReg<AS5047U_REG::ZPOSL>().bits.ZPOSL_bits;
199 auto err = GetStickyErrorFlags();
200 if ((static_cast<uint16_t>(err) & retryMask) == 0U) {
201 break;
202 }
203 }
204
205 return static_cast<uint16_t>((m << 6) | l);
206}
207
208template <typename SpiType>
209bool AS5047U<SpiType>::SetZeroPosition(uint16_t angle_lsb, uint8_t retries) {
211 m.bits.ZPOSM_bits = (angle_lsb >> 6) & 0xFF;
213 l.bits.ZPOSL_bits = angle_lsb & 0x3F;
214 return this->template WriteReg(m, retries) && this->template WriteReg(l, retries);
215}
216
217template <typename SpiType>
218bool AS5047U<SpiType>::SetABIResolution(uint8_t resolution_bits, uint8_t retries) {
219 resolution_bits = std::clamp(resolution_bits, uint8_t(10), uint8_t(14));
220 // Datasheet SETTINGS3 ABIRES (binary mode): 12-bit=0, 11=1, 10=2, 13=3, 14=4 (non-linear)
221 static constexpr uint8_t kBitsToAbires[] = {2, 1, 0, 3, 4}; // index (bits-10) -> ABIRES code
222 auto s3 = this->template ReadReg<AS5047U_REG::SETTINGS3>();
223 s3.bits.ABIRES = kBitsToAbires[resolution_bits - 10];
224 return this->template WriteReg(s3, retries);
225}
226
227template <typename SpiType>
228bool AS5047U<SpiType>::SetUVWPolePairs(uint8_t pairs, uint8_t retries) {
229 pairs = std::clamp(pairs, uint8_t(1), uint8_t(7));
230 auto s3 = this->template ReadReg<AS5047U_REG::SETTINGS3>();
231 s3.bits.UVWPP = static_cast<uint8_t>(pairs - 1);
232 return this->template WriteReg(s3, retries);
233}
234
235template <typename SpiType>
236bool AS5047U<SpiType>::SetIndexPulseLength(uint8_t lsb_len, uint8_t retries) {
237 auto s2 = this->template ReadReg<AS5047U_REG::SETTINGS2>();
238 s2.bits.IWIDTH = (lsb_len == 1) ? 1 : 0;
239 return this->template WriteReg(s2, retries);
240}
241
242// Truth table for configureInterface():
243// | ABI | UVW | PWM | Pin 8 (I) | Pin 14 (W) |
244// |-----|-----|-----|-----------|------------|
245// | 1 | 0 | 0 | I | A/B |
246// | 1 | 0 | 1 | I | PWM |
247// | 0 | 1 | 0 | UVW | W |
248// | 0 | 1 | 1 | PWM | W |
249// | 1 | 1 | x | I | W |
250// | 0 | 0 | 1 | - | PWM |
251// | 0 | 0 | 0 | - | - |
252//
253template <typename SpiType>
254bool AS5047U<SpiType>::ConfigureInterface(bool abi, bool uvw, bool pwm, uint8_t retries) {
255 auto dis = this->template ReadReg<AS5047U_REG::DISABLE>();
256 auto s2 = this->template ReadReg<AS5047U_REG::SETTINGS2>();
257 dis.bits.ABI_off = abi ? 0 : 1;
258 dis.bits.UVW_off = uvw ? 0 : 1;
259 if (abi && !uvw) {
260 s2.bits.UVW_ABI = 0;
261 s2.bits.PWMon = pwm;
262 } else if (!abi && uvw) {
263 s2.bits.UVW_ABI = 1;
264 s2.bits.PWMon = pwm;
265 } else {
266 s2.bits.UVW_ABI = 0;
267 s2.bits.PWMon = pwm;
268 }
269 return this->template WriteReg(dis, retries) && this->template WriteReg(s2, retries);
270}
271
272template <typename SpiType>
273bool AS5047U<SpiType>::SetDynamicAngleCompensation(bool enable, uint8_t retries) {
274 auto s2 = this->template ReadReg<AS5047U_REG::SETTINGS2>();
275 s2.bits.DAECDIS = enable ? 0 : 1;
276 return this->template WriteReg(s2, retries);
277}
278
279template <typename SpiType>
280bool AS5047U<SpiType>::SetAdaptiveFilter(bool enable, uint8_t retries) {
281 auto dis = this->template ReadReg<AS5047U_REG::DISABLE>();
282 dis.bits.FILTER_disable = enable ? 0 : 1;
283 return this->template WriteReg(dis, retries);
284}
285
286template <typename SpiType>
287bool AS5047U<SpiType>::SetFilterParameters(uint8_t k_min, uint8_t k_max, uint8_t retries) {
288 k_min = std::min(k_min, uint8_t(7));
289 k_max = std::min(k_max, uint8_t(7));
290 auto s1 = this->template ReadReg<AS5047U_REG::SETTINGS1>();
291 s1.bits.K_min = k_min;
292 s1.bits.K_max = k_max;
293 return this->template WriteReg(s1, retries);
294}
295
296template <typename SpiType>
297bool AS5047U<SpiType>::SetFilterPreset(FilterPreset preset, uint8_t retries) {
298 if (!SetAdaptiveFilter(true, retries)) {
299 return false;
300 }
301 // Register codes for SETTINGS1 K_min/K_max. See SETTINGS1 enums; presets use
302 // (K_min_code, K_max_code) to get effective K per datasheet Figure 17.
303 uint8_t k_min_code = 0;
304 uint8_t k_max_code = 0;
305 switch (preset) {
307 k_min_code = 5; // actual K = 0
308 k_max_code = 6; // actual K = 0
309 break;
311 k_min_code = 0; // actual K = 2
312 k_max_code = 3; // actual K = 3
313 break;
315 k_min_code = 4; // actual K = 6
316 k_max_code = 0; // actual K = 6
317 break;
318 }
319 return SetFilterParameters(k_min_code, k_max_code, retries);
320}
321
322template <typename SpiType>
324 constexpr uint16_t retryMask = static_cast<uint16_t>(AS5047U_Error::CrcError) |
325 static_cast<uint16_t>(AS5047U_Error::FramingError);
326 auto dis = this->template ReadReg<AS5047U_REG::DISABLE>();
327 for (uint8_t i = 0; i < retries; ++i) {
328 if ((static_cast<uint16_t>(GetStickyErrorFlags()) & retryMask) == 0) {
329 break;
330 }
331 dis = this->template ReadReg<AS5047U_REG::DISABLE>();
332 }
333 return (dis.bits.FILTER_disable == 0);
334}
335
336template <typename SpiType>
337std::pair<uint8_t, uint8_t> AS5047U<SpiType>::GetFilterParameters(uint8_t retries) const {
338 constexpr uint16_t retryMask = static_cast<uint16_t>(AS5047U_Error::CrcError) |
339 static_cast<uint16_t>(AS5047U_Error::FramingError);
340 auto s1 = this->template ReadReg<AS5047U_REG::SETTINGS1>();
341 for (uint8_t i = 0; i < retries; ++i) {
342 if ((static_cast<uint16_t>(GetStickyErrorFlags()) & retryMask) == 0) {
343 break;
344 }
345 s1 = this->template ReadReg<AS5047U_REG::SETTINGS1>();
346 }
347 return {s1.bits.K_min, s1.bits.K_max};
348}
349
350template <typename SpiType>
351bool AS5047U<SpiType>::Set150CTemperatureMode(bool enable, uint8_t retries) {
352 auto s2 = this->template ReadReg<AS5047U_REG::SETTINGS2>();
353 s2.bits.NOISESET = enable ? 1 : 0;
354 return this->template WriteReg(s2, retries);
355}
356
357template <typename SpiType>
359 // Save current frame format and ensure we use CRC for OTP programming
360 FrameFormat backup = this->frame_format_;
361 if (this->frame_format_ == FrameFormat::SPI_16) {
362 this->frame_format_ = FrameFormat::SPI_24;
363 }
364
365 // Set current angle as zero position
366 SetZeroPosition(GetAngle());
367
368 // Backup the volatile shadow registers that will be committed to OTP
369 uint16_t volatile_shadow[5];
370 for (uint16_t a = 0x0016; a <= 0x001A; ++a) {
371 volatile_shadow[a - 0x0016] = readRegister(a);
372 }
373
374 // Enable ECC and compute needed checksum
375 auto ecc = this->template ReadReg<AS5047U_REG::ECC>();
376 ecc.bits.ECC_en = 1;
377 this->template WriteReg(ecc);
378 auto key = this->template ReadReg<AS5047U_REG::ECC_Checksum>().bits.ECC_s;
379 ecc.bits.ECC_chsum = key;
380 this->template WriteReg(ecc);
381
382 // Verify shadow registers are still correct
383 for (uint16_t a = 0x0016; a <= 0x001A; ++a) {
384 if (readRegister(a) != volatile_shadow[a - 0x0016]) {
385 this->frame_format_ = backup;
386 return false;
387 }
388 }
389
390 // Begin OTP programming sequence
392 p.bits.PROGEN = 1;
393 this->template WriteReg(p);
394 p.bits.PROGOTP = 1;
395 this->template WriteReg(p);
396
397 // Wait for programming to complete (timeout after ~15000 cycles)
398 for (uint16_t i = 0; i < 15000; ++i) {
399 if (readRegister(AS5047U_REG::PROG::ADDRESS) == 0x0001) {
400 this->frame_format_ = backup;
401
402 // Guard-band verification: enable PROGVER and refresh OTPREF
403 p.bits.PROGVER = 1;
404 this->template WriteReg(p);
405
406 // Toggle OTPREF to reload from OTP
407 p.bits.OTPREF = 1;
408 this->template WriteReg(p);
409 p.bits.OTPREF = 0;
410 this->template WriteReg(p);
411
412 // Verify shadow registers match OTP
413 for (uint16_t a = 0x0016; a <= 0x001A; ++a) {
414 if (readRegister(a) != volatile_shadow[a - 0x0016]) {
415 this->frame_format_ = backup;
416 return false;
417 }
418 }
419 return true;
420 }
421 }
422
423 // Restore original frame format and return failure if timeout
424 this->frame_format_ = backup;
425 return false;
426}
427
428template <typename SpiType>
429void AS5047U<SpiType>::updateStickyErrors(uint16_t err_fl) const {
430 // Map ERRFL bits (0-10) to sticky error enum
431 if (err_fl & (1u << 0))
432 sticky_errors_ |= static_cast<uint16_t>(AS5047U_Error::AgcWarning);
433 if (err_fl & (1u << 1))
434 sticky_errors_ |= static_cast<uint16_t>(AS5047U_Error::MagHalf);
435 if (err_fl & (1u << 2))
436 sticky_errors_ |= static_cast<uint16_t>(AS5047U_Error::P2ramWarning);
437 if (err_fl & (1u << 3))
438 sticky_errors_ |= static_cast<uint16_t>(AS5047U_Error::P2ramError);
439 if (err_fl & (1u << 4))
440 sticky_errors_ |= static_cast<uint16_t>(AS5047U_Error::FramingError);
441 if (err_fl & (1u << 5))
442 sticky_errors_ |= static_cast<uint16_t>(AS5047U_Error::CommandError);
443 if (err_fl & (1u << 6))
444 sticky_errors_ |= static_cast<uint16_t>(AS5047U_Error::CrcError);
445 if (err_fl & (1u << 7))
446 sticky_errors_ |= static_cast<uint16_t>(AS5047U_Error::WatchdogError);
447 if (err_fl & (1u << 9))
448 sticky_errors_ |= static_cast<uint16_t>(AS5047U_Error::OffCompError);
449 if (err_fl & (1u << 10))
450 sticky_errors_ |= static_cast<uint16_t>(AS5047U_Error::CordicOverflow);
451}
452
453template <typename SpiType>
455 uint16_t val = sticky_errors_.exchange(0);
456 return static_cast<AS5047U_Error>(val);
457}
458
459// Public API implementations
460template <typename SpiType>
461void AS5047U<SpiType>::SetPad(uint8_t pad) noexcept {
462 this->pad_byte_ = pad;
463}
464
465template <typename SpiType>
467 uint8_t retries) {
468 auto s3 = this->template ReadReg<AS5047U_REG::SETTINGS3>();
469 s3.bits.HYS = static_cast<uint8_t>(hysteresis);
470 return this->template WriteReg(s3, retries);
471}
472
473template <typename SpiType>
475 auto s3 = this->template ReadReg<AS5047U_REG::SETTINGS3>();
476 return static_cast<AS5047U_REG::SETTINGS3::Hysteresis>(s3.bits.HYS);
477}
478
479template <typename SpiType>
481 uint8_t retries) {
482 auto s2 = this->template ReadReg<AS5047U_REG::SETTINGS2>();
483 s2.bits.Data_select = static_cast<uint8_t>(source);
484 return this->template WriteReg(s2, retries);
485}
486
487template <typename SpiType>
489 auto s2 = this->template ReadReg<AS5047U_REG::SETTINGS2>();
490 return static_cast<AS5047U_REG::SETTINGS2::AngleOutputSource>(s2.bits.Data_select);
491}
492
493template <typename SpiType>
495 return this->template ReadReg<AS5047U_REG::DIA>();
496}
497
498// ══════════════════════════════════════════════════════════════════════════════════════════
499// PRIVATE - COMMUNICATION API
500// ══════════════════════════════════════════════════════════════════════════════════════════
501//
502// SPI frame layout per AS5047U datasheet (DS000637):
503// - 16-bit: Fig.20-22 MOSI bit14=R(1:Read), 13:0=ADDR; MISO bit15=ER, 14=0, 13:0=RDATA (Fig.21-22).
504// - 24-bit: Fig.23-25 MOSI 22=RW, 21:8=ADDR, 7:0=CRC; MISO 23=ER, 22=Error, 21:8=DATA[13:0], 7:0=CRC (Fig.25). CRC Fig.31.
505// - 32-bit: Fig.26-28 PAD in B0 (MOSI) / B3 (MISO); MISO B0=[ER,Err,Data13:8], B1=Data7:0, B2=CRC, B3=PAD (Fig.28).
506//
507// Low level register read without sticky error update.
508// DS: "The data is transmitted on MISO with the *next* read command." So we always send
509// (1) read command for address, (2) NOP; the NOP response carries the data for that address.
510template <typename SpiType>
511uint16_t AS5047U<SpiType>::rawReadRegister(uint16_t address) const {
512 uint16_t result = 0;
513 if (this->frame_format_ == FrameFormat::SPI_16) {
514 // ---- 16-bit frame without CRC ----
515 // (1) Read command (bit14=1 for read)
516 uint16_t cmd = static_cast<uint16_t>(0x4000 | (address & 0x3FFF));
517 const uint8_t tx[2] = {static_cast<uint8_t>(cmd >> 8), static_cast<uint8_t>(cmd & 0xFF)};
518 uint8_t rx[2];
519 spi_.transfer(tx, rx, 2);
520
521 // (2) NOP — MISO from this transfer = data for the address we just requested
522 const uint8_t tx_nop[2] = {0x40, 0x00};
523 uint8_t rx_data[2];
524 spi_.transfer(tx_nop, rx_data, 2);
525
526 // Process response
527 uint16_t raw = (static_cast<uint16_t>(rx_data[0]) << 8) | rx_data[1];
528 result = raw & 0x3FFF; // Mask out status flags
529 } else if (this->frame_format_ == FrameFormat::SPI_24) {
530 // ---- 24-bit frame with CRC ----
531 // (1) Read command with CRC
532 uint16_t crc_input = static_cast<uint16_t>((1 << 14) | (address & 0x3FFF));
533 uint8_t crc = ComputeCRC8(crc_input);
534 const uint8_t tx_cmd[3] = {
535 static_cast<uint8_t>(((address >> 8) & 0x3F) | 0x40), // bit6=1 for read
536 static_cast<uint8_t>(address & 0xFF), crc};
537 uint8_t rx_cmd[3];
538 spi_.transfer(tx_cmd, rx_cmd, 3);
539
540 // (2) NOP — MISO from this transfer = data for the requested address
541 uint16_t nop_addr = AS5047U_REG::NOP::ADDRESS;
542 uint16_t nop_crc_input = static_cast<uint16_t>((1 << 14) | (nop_addr & 0x3FFF));
543 uint8_t crc_nop = ComputeCRC8(nop_crc_input);
544 const uint8_t tx_nop[3] = {static_cast<uint8_t>(((nop_addr >> 8) & 0x3F) | 0x40),
545 static_cast<uint8_t>(nop_addr & 0xFF), crc_nop};
546 uint8_t rx_data_frame[3];
547 spi_.transfer(tx_nop, rx_data_frame, 3);
548
549 // Process response with CRC verification
550 uint16_t raw = (static_cast<uint16_t>(rx_data_frame[0]) << 8) | rx_data_frame[1];
551 uint8_t crc_device = rx_data_frame[2];
552 uint8_t crc_calc = ComputeCRC8(raw);
553 if (crc_device != crc_calc) {
554 // crc error, caller will read ERRFL
555 }
556 result = raw & 0x3FFF;
557 } else if (this->frame_format_ == FrameFormat::SPI_32) {
558 // ---- 32-bit frame with CRC and pad byte (DS Fig.26-28) ----
559 // MISO data frame (Fig.28): Bit31=ER, 30=Error, 29:16=Data(14b), 15:8=CRC, 7:0=PAD.
560 // So Byte0=[ER,Err,Data13:8], Byte1=Data7:0, Byte2=CRC, Byte3=PAD. CRC over bits 31:16 (DS p.23).
561 uint16_t crc_input = static_cast<uint16_t>((1 << 14) | (address & 0x3FFF));
562 uint8_t crc = ComputeCRC8(crc_input);
563 const uint8_t tx_cmd[4] = {
564 this->pad_byte_,
565 static_cast<uint8_t>(((address >> 8) & 0x3F) | 0x40), // bit6=1 for read
566 static_cast<uint8_t>(address & 0xFF), crc};
567 uint8_t rx_cmd[4];
568 spi_.transfer(tx_cmd, rx_cmd, 4);
569
570 // (2) NOP — MISO from this transfer = data for the requested address. Fig.28: Byte0/1=Data, Byte2=CRC, Byte3=PAD.
571 uint16_t nop_addr = AS5047U_REG::NOP::ADDRESS;
572 uint16_t nop_crc_input = static_cast<uint16_t>((1 << 14) | (nop_addr & 0x3FFF));
573 uint8_t crc_nop = ComputeCRC8(nop_crc_input);
574 const uint8_t tx_nop[4] = {this->pad_byte_,
575 static_cast<uint8_t>(((nop_addr >> 8) & 0x3F) | 0x40),
576 static_cast<uint8_t>(nop_addr & 0xFF), crc_nop};
577 uint8_t rx_data_frame[4];
578 spi_.transfer(tx_nop, rx_data_frame, 4);
579
580 // Data = bits 29:16 = (Byte0 & 0x3F)<<8 | Byte1; CRC = Byte2 (bits 15:8); Byte3 = PAD
581 uint16_t raw = (static_cast<uint16_t>(rx_data_frame[0] & 0x3Fu) << 8) | rx_data_frame[1];
582 uint16_t crc_payload_32 = (static_cast<uint16_t>(rx_data_frame[0]) << 8) | rx_data_frame[1];
583 uint8_t crc_device = rx_data_frame[2];
584 uint8_t crc_calc = ComputeCRC8(crc_payload_32);
585 if (crc_device != crc_calc) {
586 // crc error, caller will read ERRFL
587 }
588 result = raw & 0x3FFF;
589 }
590 return result;
591}
592
593// High level read that also fetches ERRFL to update sticky errors
594template <typename SpiType>
595uint16_t AS5047U<SpiType>::readRegister(uint16_t address) const {
596 uint16_t val = rawReadRegister(address);
597 uint16_t err = rawReadRegister(AS5047U_REG::ERRFL::ADDRESS);
598 updateStickyErrors(err);
599 return val;
600}
601
602template <typename SpiType>
603bool AS5047U<SpiType>::writeRegister(uint16_t address, uint16_t value, uint8_t retries) const {
604 bool success = false;
605 uint16_t err_mask = static_cast<uint16_t>(AS5047U_Error::CrcError) |
606 static_cast<uint16_t>(AS5047U_Error::FramingError);
607
608 // The AS5047U datasheet specifies 16-bit frames for read operations only.
609 // Writes require 24-bit or 32-bit frames (which include CRC). If the current
610 // frame format is SPI_16, temporarily promote to SPI_24 for the write, then
611 // restore. This ensures writes always reach the IC correctly.
612 FrameFormat active_format = this->frame_format_;
613 if (active_format == FrameFormat::SPI_16) {
614 active_format = FrameFormat::SPI_24;
615 }
616
617 // DS Fig.30: Write = command frame then data frame. MISO during data = old content.
618 // "At the next command" MISO = new content — so we send NOP after data and use that MISO to verify.
619 // CS may toggle between command and data frames.
620 const uint16_t expected = value & 0x3FFF;
621 const uint16_t nop_addr = AS5047U_REG::NOP::ADDRESS;
622 const uint16_t nop_crc_input = static_cast<uint16_t>((1 << 14) | (nop_addr & 0x3FFF));
623 const uint8_t crc_nop = ComputeCRC8(nop_crc_input);
624
625 for (uint8_t attempt = 0; attempt <= retries; ++attempt) {
626 if (active_format == FrameFormat::SPI_24) {
627 // ---- 24-bit write: command, data, then NOP (MISO on NOP = new content) ----
628 uint16_t cmd_payload = static_cast<uint16_t>(address & 0x3FFF);
629 uint8_t cmd_crc = ComputeCRC8(cmd_payload);
630 const uint8_t tx_cmd[3] = {static_cast<uint8_t>((address >> 8) & 0x3F), // bit6=0 for write
631 static_cast<uint8_t>(address & 0xFF), cmd_crc};
632 uint8_t rx_cmd[3];
633 spi_.transfer(tx_cmd, rx_cmd, 3);
634
635 uint16_t data_payload = value & 0x3FFF;
636 uint8_t data_crc = ComputeCRC8(data_payload);
637 const uint8_t tx_data[3] = {static_cast<uint8_t>((data_payload >> 8) & 0xFF),
638 static_cast<uint8_t>(data_payload & 0xFF), data_crc};
639 uint8_t rx_data[3];
640 spi_.transfer(tx_data, rx_data, 3); // MISO here = old content (DS Fig.30)
641
642 // NOP — MISO = new content of the written register (third TX = response to write)
643 // 24-bit MISO same layout as 32-bit: Byte0=[ER,Err,Data13:8], Byte1=Data7:0 → 14-bit = (B0&0x3F)<<8|B1
644 const uint8_t tx_nop[3] = {static_cast<uint8_t>(((nop_addr >> 8) & 0x3F) | 0x40),
645 static_cast<uint8_t>(nop_addr & 0xFF), crc_nop};
646 uint8_t rx_nop[3];
647 spi_.transfer(tx_nop, rx_nop, 3);
648 uint16_t read_back = (static_cast<uint16_t>(rx_nop[0] & 0x3Fu) << 8) | rx_nop[1];
649 if (read_back == expected) {
650 success = true;
651 break;
652 }
653 auto errfl = this->template ReadReg<AS5047U_REG::ERRFL>();
654 updateStickyErrors(errfl.value);
655 printf("AS5047U write verify failed: addr=0x%04X expected=0x%04X read_back=0x%04X "
656 "ERRFL=0x%04X (CRC_error=%u Framing_error=%u Command_error=%u)\n",
657 static_cast<unsigned>(address), static_cast<unsigned>(expected),
658 static_cast<unsigned>(read_back), static_cast<unsigned>(errfl.value),
659 static_cast<unsigned>(errfl.bits.CRC_error),
660 static_cast<unsigned>(errfl.bits.Framing_error),
661 static_cast<unsigned>(errfl.bits.Command_error));
662 } else if (active_format == FrameFormat::SPI_32) {
663 // ---- 32-bit write: command, data, then NOP (MISO on NOP = new content) ----
664 uint16_t cmd_payload = static_cast<uint16_t>(address & 0x3FFF);
665 uint8_t cmd_crc = ComputeCRC8(cmd_payload);
666 const uint8_t tx_cmd[4] = {this->pad_byte_,
667 static_cast<uint8_t>((address >> 8) & 0x3F), // bit6=0 for write
668 static_cast<uint8_t>(address & 0xFF), cmd_crc};
669 uint8_t rx_cmd[4];
670 spi_.transfer(tx_cmd, rx_cmd, 4);
671
672 uint16_t data_payload = value & 0x3FFF;
673 uint8_t data_crc = ComputeCRC8(data_payload);
674 const uint8_t tx_data[4] = {this->pad_byte_, static_cast<uint8_t>((data_payload >> 8) & 0xFF),
675 static_cast<uint8_t>(data_payload & 0xFF), data_crc};
676 uint8_t rx_data[4];
677 spi_.transfer(tx_data, rx_data, 4); // MISO here = old content (DS Fig.30)
678
679 // NOP — MISO = new content (Fig.28: Byte0/1 = Data, Byte2 = CRC, Byte3 = PAD)
680 const uint8_t tx_nop[4] = {this->pad_byte_,
681 static_cast<uint8_t>(((nop_addr >> 8) & 0x3F) | 0x40),
682 static_cast<uint8_t>(nop_addr & 0xFF), crc_nop};
683 uint8_t rx_nop[4];
684 spi_.transfer(tx_nop, rx_nop, 4);
685 uint16_t read_back = (static_cast<uint16_t>(rx_nop[0] & 0x3Fu) << 8) | rx_nop[1];
686 if (read_back == expected) {
687 success = true;
688 break;
689 }
690 auto errfl = this->template ReadReg<AS5047U_REG::ERRFL>();
691 updateStickyErrors(errfl.value);
692 printf("AS5047U write verify failed: addr=0x%04X expected=0x%04X read_back=0x%04X "
693 "ERRFL=0x%04X (CRC_error=%u Framing_error=%u Command_error=%u)\n",
694 static_cast<unsigned>(address), static_cast<unsigned>(expected),
695 static_cast<unsigned>(read_back), static_cast<unsigned>(errfl.value),
696 static_cast<unsigned>(errfl.bits.CRC_error),
697 static_cast<unsigned>(errfl.bits.Framing_error),
698 static_cast<unsigned>(errfl.bits.Command_error));
699 }
700 }
701 return success;
702}
703
704// ════════════════════════════════════════════════════════════════════════════════════════════
705// Public API: retry-enabled getters and status dump
706// ════════════════════════════════════════════════════════════════════════════════════════════
707
708// Complete dumpStatus with full register dump
709template <typename SpiType>
711 printf("\n=== AS5047U Comprehensive Status ===\n");
712 // Core measurements
713 printf("Angle (COM) : %u\n", GetAngle());
714 printf("Angle (UNC) : %u\n", GetRawAngle());
715 printf("Velocity : %d counts (%.3f deg/s, %.3f rad/s, %.3f RPM)\n", GetVelocity(),
716 GetVelocityDegPerSec(), GetVelocityRadPerSec(), GetVelocityRPM());
717 printf("AGC : %u\n", GetAGC());
718 printf("Magnitude : %u\n", GetMagnitude());
719 // Error flags
720 uint16_t err = GetErrorFlags();
721 printf("ERRFL : 0x%04X\n", err);
722 // Diagnostic register bits
723 auto dia = this->template ReadReg<AS5047U_REG::DIA>();
724 printf("DIA (0x3FF5): 0x%04X\n", dia.value);
725 printf(" VDD_mode : %u\n", dia.bits.VDD_mode);
726 printf(" LoopsFinished : %u\n", dia.bits.LoopsFinished);
727 printf(" CORDIC_overflow : %u\n", dia.bits.CORDIC_overflow_flag);
728 printf(" Comp_l : %u\n", dia.bits.Comp_l);
729 printf(" Comp_h : %u\n", dia.bits.Comp_h);
730 printf(" MagHalf_flag : %u\n", dia.bits.MagHalf_flag);
731 printf(" CosOff_fin : %u\n", dia.bits.CosOff_fin);
732 printf(" SinOff_fin : %u\n", dia.bits.SinOff_fin);
733 printf(" OffComp_finished : %u\n", dia.bits.OffComp_finished);
734 printf(" AGC_finished : %u\n", dia.bits.AGC_finished);
735 printf(" SPI_cnt : %u\n", dia.bits.SPI_cnt);
736 // Configuration registers
737 auto dis = this->template ReadReg<AS5047U_REG::DISABLE>();
738 printf("DISABLE (0x0015): 0x%04X UVW_off=%u ABI_off=%u FILTER_disable=%u\n", dis.value,
739 dis.bits.UVW_off, dis.bits.ABI_off, dis.bits.FILTER_disable);
740 auto s1 = this->template ReadReg<AS5047U_REG::SETTINGS1>();
741 printf("SETTINGS1(0x0016): K_max=%u K_min=%u Dia3_en=%u Dia4_en=%u\n", s1.bits.K_max,
742 s1.bits.K_min, s1.bits.Dia3_en, s1.bits.Dia4_en);
743 auto s2 = this->template ReadReg<AS5047U_REG::SETTINGS2>();
744 printf("SETTINGS2(0x0019): IWIDTH=%u NOISESET=%u DIR=%u UVW_ABI=%u "
745 "DAECDIS=%u ABI_DEC=%u "
746 "Data_select=%u PWMon=%u\n",
747 s2.bits.IWIDTH, s2.bits.NOISESET, s2.bits.DIR, s2.bits.UVW_ABI, s2.bits.DAECDIS,
748 s2.bits.ABI_DEC, s2.bits.Data_select, s2.bits.PWMon);
749 auto s3 = this->template ReadReg<AS5047U_REG::SETTINGS3>();
750 printf("SETTINGS3(0x001A): UVWPP=%u HYS=%u ABIRES=%u\n", s3.bits.UVWPP, s3.bits.HYS,
751 s3.bits.ABIRES);
752 // Other registers
753 auto sind = this->template ReadReg<AS5047U_REG::SINDATA>();
754 printf("SINDATA(0x3FFA): %d\n", sind.bits.SINDATA);
755 auto eccsum = this->template ReadReg<AS5047U_REG::ECC_Checksum>();
756 printf("ECC_Checksum(0x3FD0): %u\n", eccsum.bits.ECC_s);
757 auto prog = this->template ReadReg<AS5047U_REG::PROG>();
758 printf("PROG(0x0003): PROGEN=%u PROGOTP=%u OTPREF=%u PROGVER=%u\n", prog.bits.PROGEN,
759 prog.bits.PROGOTP, prog.bits.OTPREF, prog.bits.PROGVER);
760 // Interface settings
761 printf("FrameFormat : %u PadByte=0x%02X\n", static_cast<uint8_t>(this->frame_format_),
762 this->pad_byte_);
763 printf("========================================\n\n");
764}
765
766} // namespace as5047u
767
768#endif // AS5047U_IMPL
Driver for AMS AS5047U Magnetic Rotary Position Sensor (C++21)
AS5047U_Error
Definition as5047u.hpp:21
@ CommandError
Invalid SPI command received.
@ P2ramWarning
ECC corrected 1 bit in P2RAM customer area.
@ OffCompError
Internal offset compensation not finished.
@ P2ramError
ECC detected 2+ uncorrectable errors in P2RAM.
@ AgcWarning
AGC reached minimum (0) or maximum (255) value.
@ FramingError
SPI framing error.
@ WatchdogError
Internal oscillator or watchdog not working proper.
@ CrcError
CRC error during SPI communication.
@ MagHalf
Magnetic field is half of regulated value (AGC=255)
@ CordicOverflow
CORDIC algorithm overflow.
FilterPreset
Presets for the adaptive velocity/angle filter (Dynamic Filter System).
Definition as5047u_types.hpp:26
FrameFormat
Supported SPI frame formats for AS5047U communication.
Definition as5047u_types.hpp:12
AS5047U magnetic rotary sensor driver class.
Definition as5047u.hpp:83
float GetVelocityDegPerSec(uint8_t retries=AS5047U_CFG::CRC_RETRIES) const
Get rotational velocity in degrees per second.
Definition as5047u.ipp:124
bool SetAdaptiveFilter(bool enable, uint8_t retries=AS5047U_CFG::CRC_RETRIES)
Enable/disable the adaptive filter (Dynamic Filter System).
Definition as5047u.ipp:280
uint16_t GetRawAngle(uint8_t retries=AS5047U_CFG::CRC_RETRIES) const
Read the 14-bit absolute angle without dynamic compensation (raw angle).
Definition as5047u.ipp:77
void SetPad(uint8_t pad) noexcept
Set the daisy-chain pad byte for 32-bit SPI frames.
Definition as5047u.ipp:461
bool ProgramOTP()
Permanently program current settings into OTP memory.
Definition as5047u.ipp:358
AS5047U_REG::SETTINGS3::Hysteresis GetHysteresis() const
Get current incremental output hysteresis setting.
Definition as5047u.ipp:474
uint16_t GetMagnitude(uint8_t retries=AS5047U_CFG::CRC_RETRIES) const
Read the current magnetic field magnitude (14-bit value).
Definition as5047u.ipp:154
uint16_t GetErrorFlags(uint8_t retries=AS5047U_CFG::CRC_RETRIES) const
Read and clear error flags.
Definition as5047u.ipp:169
bool SetDynamicAngleCompensation(bool enable, uint8_t retries=AS5047U_CFG::CRC_RETRIES)
Enable/disable Dynamic Angle Error Compensation (DAEC).
Definition as5047u.ipp:273
AS5047U_REG::SETTINGS2::AngleOutputSource GetAngleOutputSource() const
Get currently selected angle output source for 0x3FFF reads.
Definition as5047u.ipp:488
bool SetFilterPreset(FilterPreset preset, uint8_t retries=AS5047U_CFG::CRC_RETRIES)
Set adaptive filter from a preset (easiest way to configure filter).
Definition as5047u.ipp:297
uint16_t GetAngle(uint8_t retries=AS5047U_CFG::CRC_RETRIES) const
Read the 14-bit absolute angle with dynamic compensation (DAEC active).
Definition as5047u.ipp:38
bool ConfigureInterface(bool abi, bool uvw, bool pwm, uint8_t retries=AS5047U_CFG::CRC_RETRIES)
Configure interface outputs (ABI, UVW) and PWM output.
Definition as5047u.ipp:254
float GetVelocityRPM(uint8_t retries=AS5047U_CFG::CRC_RETRIES) const
Get rotational velocity in revolutions per minute.
Definition as5047u.ipp:134
void SetFrameFormat(FrameFormat format) noexcept
Set the SPI frame format (16, 24, or 32-bit).
Definition as5047u.ipp:21
bool SetABIResolution(uint8_t resolution_bits, uint8_t retries=AS5047U_CFG::CRC_RETRIES)
Set the ABI (incremental encoder) resolution.
Definition as5047u.ipp:218
float GetAngleDegrees(uint8_t retries=AS5047U_CFG::CRC_RETRIES) const
Read absolute angle in degrees.
Definition as5047u.ipp:67
bool SetUVWPolePairs(uint8_t pairs, uint8_t retries=AS5047U_CFG::CRC_RETRIES)
Set the number of pole pairs for UVW commutation outputs.
Definition as5047u.ipp:228
uint16_t GetZeroPosition(uint8_t retries=AS5047U_CFG::CRC_RETRIES) const
Get the currently configured soft zero position offset (14-bit).
Definition as5047u.ipp:181
bool Set150CTemperatureMode(bool enable, uint8_t retries=AS5047U_CFG::CRC_RETRIES)
Set temperature mode for 150°C operation (NOISESET bit).
Definition as5047u.ipp:351
bool SetHysteresis(AS5047U_REG::SETTINGS3::Hysteresis hysteresis, uint8_t retries=AS5047U_CFG::CRC_RETRIES)
Set incremental output hysteresis level.
Definition as5047u.ipp:466
std::pair< uint8_t, uint8_t > GetFilterParameters(uint8_t retries=AS5047U_CFG::CRC_RETRIES) const
Read current K_min and K_max register codes (0–7 each).
Definition as5047u.ipp:337
bool GetAdaptiveFilterEnabled(uint8_t retries=AS5047U_CFG::CRC_RETRIES) const
Read whether the adaptive filter is enabled.
Definition as5047u.ipp:323
void DumpStatus() const
Dump formatted status and diagnostics using printf.
Definition as5047u.ipp:710
bool SetZeroPosition(uint16_t angle_lsb, uint8_t retries=AS5047U_CFG::CRC_RETRIES)
Set a new zero reference position (soft offset).
Definition as5047u.ipp:209
bool SetAngleOutputSource(AS5047U_REG::SETTINGS2::AngleOutputSource source, uint8_t retries=AS5047U_CFG::CRC_RETRIES)
Select which angle register (0x3FFF) is returned on reads.
Definition as5047u.ipp:480
bool SetFilterParameters(uint8_t k_min, uint8_t k_max, uint8_t retries=AS5047U_CFG::CRC_RETRIES)
Set adaptive filter parameters (K_min and K_max as 3-bit register codes).
Definition as5047u.ipp:287
int16_t GetVelocity(uint8_t retries=AS5047U_CFG::CRC_RETRIES) const
Read the current rotational velocity (signed 14-bit).
Definition as5047u.ipp:92
AS5047U_Error GetStickyErrorFlags() const
Retrieve and clear the accumulated sticky error flags.
Definition as5047u.ipp:454
AS5047U_REG::DIA GetDiagnostics() const
Read the full diagnostic register (DIA).
Definition as5047u.ipp:494
float GetAngleRadians(uint8_t retries=AS5047U_CFG::CRC_RETRIES) const
Read absolute angle in radians.
Definition as5047u.ipp:72
bool SetIndexPulseLength(uint8_t lsb_len, uint8_t retries=AS5047U_CFG::CRC_RETRIES)
Set the index pulse width for ABI output.
Definition as5047u.ipp:236
float GetVelocityRadPerSec(uint8_t retries=AS5047U_CFG::CRC_RETRIES) const
Get rotational velocity in radians per second.
Definition as5047u.ipp:129
uint8_t GetAGC(uint8_t retries=AS5047U_CFG::CRC_RETRIES) const
Read the current Automatic Gain Control (AGC) value (0-255).
Definition as5047u.ipp:139
Definition as5047u.ipp:17
VelocityUnit
Units for velocity-returning APIs.
Definition as5047u.hpp:58
AngleUnit
Units for angle-returning APIs.
Definition as5047u.hpp:51
DIA – Diagnostic register (0x3FF5, read-only)
Definition as5047u_registers.hpp:180
static constexpr uint16_t ADDRESS
Definition as5047u_registers.hpp:81
static constexpr uint16_t ADDRESS
Definition as5047u_registers.hpp:42
PROG – OTP programming control register (0x0003, default 0x0000)
Definition as5047u_registers.hpp:132
static constexpr uint16_t ADDRESS
Definition as5047u_registers.hpp:133
struct AS5047U_REG::PROG::@6::@8 bits
uint16_t PROGEN
Definition as5047u_registers.hpp:137
AngleOutputSource
Angle data source for 0x3FFF output (Data_select bit)
Definition as5047u_registers.hpp:644
Hysteresis
Incremental output hysteresis (HYS) settings:
Definition as5047u_registers.hpp:714
ZPOSL – Zero Position LSB register (0x0017, default 0x0000)
Definition as5047u_registers.hpp:505
struct AS5047U_REG::ZPOSL::@42::@44 bits
uint16_t ZPOSL_bits
Definition as5047u_registers.hpp:510
ZPOSM – Zero Position MSB register (0x0016, default 0x0000)
Definition as5047u_registers.hpp:482
struct AS5047U_REG::ZPOSM::@39::@41 bits
uint16_t ZPOSM_bits
Definition as5047u_registers.hpp:487