AS5047U Driver
API reference
Loading...
Searching...
No Matches
as5047u.ipp
Go to the documentation of this file.
1
6#ifndef AS5047U_IMPL
7#define AS5047U_IMPL
8
9// Normally included at the end of as5047u.hpp. Found through the include path (src/ in the source
10// tree, the include directory once installed), never by a path relative to the header.
11#ifndef AS5047U_HEADER_INCLUDED
12#include "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 return decode<AS5047U_REG::ANGLECOM>(readWithRetries(AS5047U_REG::ANGLECOM::ADDRESS, retries))
40 .bits.ANGLECOM_value;
41}
42
43template <typename SpiType>
44float AS5047U<SpiType>::GetAngle(AngleUnit unit, uint8_t retries) const {
45 switch (unit) {
46 case AngleUnit::Lsb:
47 return static_cast<float>(GetAngle(retries));
49 return GetAngleDegrees(retries);
51 return GetAngleRadians(retries);
52 default:
53 return static_cast<float>(GetAngle(retries));
54 }
55}
56
57template <typename SpiType>
58float AS5047U<SpiType>::GetAngleDegrees(uint8_t retries) const {
59 return static_cast<float>(GetAngle(retries)) * Angle::DEG_PER_LSB;
60}
61
62template <typename SpiType>
63float AS5047U<SpiType>::GetAngleRadians(uint8_t retries) const {
64 return static_cast<float>(GetAngle(retries)) * Angle::RAD_PER_LSB;
65}
66
67template <typename SpiType>
68uint16_t AS5047U<SpiType>::GetRawAngle(uint8_t retries) const {
69 return decode<AS5047U_REG::ANGLEUNC>(readWithRetries(AS5047U_REG::ANGLEUNC::ADDRESS, retries))
70 .bits.ANGLEUNC_value;
71}
72
73template <typename SpiType>
74int16_t AS5047U<SpiType>::GetVelocity(uint8_t retries) const {
75 const auto v =
76 decode<AS5047U_REG::VEL>(readWithRetries(AS5047U_REG::VEL::ADDRESS, retries)).bits.VEL_value;
77 return static_cast<int16_t>((static_cast<int16_t>(v << 2)) >> 2);
78}
79
80template <typename SpiType>
81float AS5047U<SpiType>::GetVelocity(VelocityUnit unit, uint8_t retries) const {
82 switch (unit) {
84 return static_cast<float>(GetVelocity(retries));
86 return GetVelocityDegPerSec(retries);
88 return GetVelocityRadPerSec(retries);
90 return GetVelocityRPM(retries);
91 default:
92 return static_cast<float>(GetVelocity(retries));
93 }
94}
95
96template <typename SpiType>
97float AS5047U<SpiType>::GetVelocityDegPerSec(uint8_t retries) const {
98 return GetVelocity(retries) * Velocity::DEG_PER_LSB;
99}
100
101template <typename SpiType>
102float AS5047U<SpiType>::GetVelocityRadPerSec(uint8_t retries) const {
103 return GetVelocity(retries) * Velocity::RAD_PER_LSB;
104}
105
106template <typename SpiType>
107float AS5047U<SpiType>::GetVelocityRPM(uint8_t retries) const {
108 return GetVelocity(retries) * Velocity::RPM_PER_LSB;
109}
110
111template <typename SpiType>
112uint8_t AS5047U<SpiType>::GetAGC(uint8_t retries) const {
113 return decode<AS5047U_REG::AGC>(readWithRetries(AS5047U_REG::AGC::ADDRESS, retries))
114 .bits.AGC_value;
115}
116
117template <typename SpiType>
118uint16_t AS5047U<SpiType>::GetMagnitude(uint8_t retries) const {
119 return decode<AS5047U_REG::MAG>(readWithRetries(AS5047U_REG::MAG::ADDRESS, retries))
120 .bits.MAG_value;
121}
122
123template <typename SpiType>
124uint16_t AS5047U<SpiType>::GetErrorFlags(uint8_t retries) const {
125 // ERRFL clears when read, so a retry is only made when the read itself failed (bus error); every
126 // value read is ORed into the result and, by readRegister(), into the sticky flags.
127 uint16_t flags = 0;
128 for (uint8_t i = 0; i <= retries; ++i) {
129 uint16_t val = 0;
130 const bool ok = readRegister(AS5047U_REG::ERRFL::ADDRESS, val);
131 flags |= val;
132 if (ok) {
133 break;
134 }
135 }
136 return flags;
137}
138
139template <typename SpiType>
140uint16_t AS5047U<SpiType>::GetZeroPosition(uint8_t retries) const {
141 const uint8_t m =
142 decode<AS5047U_REG::ZPOSM>(readWithRetries(AS5047U_REG::ZPOSM::ADDRESS, retries))
143 .bits.ZPOSM_bits;
144 const uint8_t l =
145 decode<AS5047U_REG::ZPOSL>(readWithRetries(AS5047U_REG::ZPOSL::ADDRESS, retries))
146 .bits.ZPOSL_bits;
147 return static_cast<uint16_t>((m << 6) | l);
148}
149
150template <typename SpiType>
151bool AS5047U<SpiType>::SetZeroPosition(uint16_t angle_lsb, uint8_t retries) {
153 m.bits.ZPOSM_bits = (angle_lsb >> 6) & 0xFF;
155 l.bits.ZPOSL_bits = angle_lsb & 0x3F;
156 return this->WriteReg(m, retries) && this->WriteReg(l, retries);
157}
158
159template <typename SpiType>
160bool AS5047U<SpiType>::SetABIResolution(uint8_t resolution_bits, uint8_t retries) {
161 resolution_bits = std::clamp(resolution_bits, uint8_t(10), uint8_t(14));
162 // Datasheet SETTINGS3 ABIRES (binary mode): 12-bit=0, 11=1, 10=2, 13=3, 14=4 (non-linear)
163 static constexpr uint8_t kBitsToAbires[] = {2, 1, 0, 3, 4}; // index (bits-10) -> ABIRES code
165 if (!this->ReadReg(s3, retries)) {
166 return false;
167 }
168 s3.bits.ABIRES = kBitsToAbires[resolution_bits - 10];
169 return this->WriteReg(s3, retries);
170}
171
172template <typename SpiType>
173bool AS5047U<SpiType>::SetUVWPolePairs(uint8_t pairs, uint8_t retries) {
174 pairs = std::clamp(pairs, uint8_t(1), uint8_t(7));
176 if (!this->ReadReg(s3, retries)) {
177 return false;
178 }
179 s3.bits.UVWPP = static_cast<uint8_t>(pairs - 1);
180 return this->WriteReg(s3, retries);
181}
182
183template <typename SpiType>
184bool AS5047U<SpiType>::SetIndexPulseLength(uint8_t lsb_len, uint8_t retries) {
186 if (!this->ReadReg(s2, retries)) {
187 return false;
188 }
189 s2.bits.IWIDTH = (lsb_len == 1) ? 1 : 0;
190 return this->WriteReg(s2, retries);
191}
192
193// Truth table for configureInterface():
194// | ABI | UVW | PWM | Pin 8 (I) | Pin 14 (W) |
195// |-----|-----|-----|-----------|------------|
196// | 1 | 0 | 0 | I | A/B |
197// | 1 | 0 | 1 | I | PWM |
198// | 0 | 1 | 0 | UVW | W |
199// | 0 | 1 | 1 | PWM | W |
200// | 1 | 1 | x | I | W |
201// | 0 | 0 | 1 | - | PWM |
202// | 0 | 0 | 0 | - | - |
203//
204template <typename SpiType>
205bool AS5047U<SpiType>::ConfigureInterface(bool abi, bool uvw, bool pwm, uint8_t retries) {
207 if (!this->ReadReg(dis, retries)) {
208 return false;
209 }
211 if (!this->ReadReg(s2, retries)) {
212 return false;
213 }
214 dis.bits.ABI_off = abi ? 0 : 1;
215 dis.bits.UVW_off = uvw ? 0 : 1;
216 if (abi && !uvw) {
217 s2.bits.UVW_ABI = 0;
218 s2.bits.PWMon = pwm;
219 } else if (!abi && uvw) {
220 s2.bits.UVW_ABI = 1;
221 s2.bits.PWMon = pwm;
222 } else {
223 s2.bits.UVW_ABI = 0;
224 s2.bits.PWMon = pwm;
225 }
226 return this->WriteReg(dis, retries) && this->WriteReg(s2, retries);
227}
228
229template <typename SpiType>
230bool AS5047U<SpiType>::SetDynamicAngleCompensation(bool enable, uint8_t retries) {
232 if (!this->ReadReg(s2, retries)) {
233 return false;
234 }
235 s2.bits.DAECDIS = enable ? 0 : 1;
236 return this->WriteReg(s2, retries);
237}
238
239template <typename SpiType>
240bool AS5047U<SpiType>::SetAdaptiveFilter(bool enable, uint8_t retries) {
242 if (!this->ReadReg(dis, retries)) {
243 return false;
244 }
245 dis.bits.FILTER_disable = enable ? 0 : 1;
246 return this->WriteReg(dis, retries);
247}
248
249template <typename SpiType>
250bool AS5047U<SpiType>::SetFilterParameters(uint8_t k_min, uint8_t k_max, uint8_t retries) {
251 k_min = std::min(k_min, uint8_t(7));
252 k_max = std::min(k_max, uint8_t(7));
254 if (!this->ReadReg(s1, retries)) {
255 return false;
256 }
257 s1.bits.K_min = k_min;
258 s1.bits.K_max = k_max;
259 return this->WriteReg(s1, retries);
260}
261
262template <typename SpiType>
263bool AS5047U<SpiType>::SetFilterPreset(FilterPreset preset, uint8_t retries) {
264 if (!SetAdaptiveFilter(true, retries)) {
265 return false;
266 }
267 // Register codes for SETTINGS1 K_min/K_max. See SETTINGS1 enums; presets use
268 // (K_min_code, K_max_code) to get effective K per datasheet Figure 17.
269 uint8_t k_min_code = 0;
270 uint8_t k_max_code = 0;
271 switch (preset) {
273 k_min_code = 5; // actual K = 0
274 k_max_code = 6; // actual K = 0
275 break;
277 k_min_code = 0; // actual K = 2
278 k_max_code = 3; // actual K = 3
279 break;
281 k_min_code = 4; // actual K = 6
282 k_max_code = 0; // actual K = 6
283 break;
284 }
285 return SetFilterParameters(k_min_code, k_max_code, retries);
286}
287
288template <typename SpiType>
290 const auto dis =
291 decode<AS5047U_REG::DISABLE>(readWithRetries(AS5047U_REG::DISABLE::ADDRESS, retries));
292 return (dis.bits.FILTER_disable == 0);
293}
294
295template <typename SpiType>
296std::pair<uint8_t, uint8_t> AS5047U<SpiType>::GetFilterParameters(uint8_t retries) const {
297 const auto s1 =
298 decode<AS5047U_REG::SETTINGS1>(readWithRetries(AS5047U_REG::SETTINGS1::ADDRESS, retries));
299 return {static_cast<uint8_t>(s1.bits.K_min), static_cast<uint8_t>(s1.bits.K_max)};
300}
301
302template <typename SpiType>
303bool AS5047U<SpiType>::Set150CTemperatureMode(bool enable, uint8_t retries) {
305 if (!this->ReadReg(s2, retries)) {
306 return false;
307 }
308 s2.bits.NOISESET = enable ? 1 : 0;
309 return this->WriteReg(s2, retries);
310}
311
312template <typename SpiType>
314 OtpResult local{};
315 OtpResult& r = (result != nullptr) ? *result : local;
316 r = OtpResult{};
317 // The procedure writes registers: use frames with CRC (16-bit frames are for reads only).
318 const FrameFormat saved = frame_format_;
319 if (frame_format_ == FrameFormat::SPI_16) {
320 frame_format_ = FrameFormat::SPI_24;
321 }
322 const bool ok = programOtpSteps(r);
323 frame_format_ = saved;
324 return ok;
325}
326
327template <typename SpiType>
328bool AS5047U<SpiType>::readOtpRegisters(std::array<uint16_t, kOtpRegisterCount>& regs,
329 uint8_t retries) const {
330 for (std::size_t i = 0; i < kOtpRegisterCount; ++i) {
331 if (!readWithRetries(static_cast<uint16_t>(kOtpFirstAddress + i), retries, regs[i])) {
332 return false;
333 }
334 }
335 return true;
336}
337
338// Datasheet DS000637 v1-01, "Step-by-Step Procedure to Permanently Program the Non-Volatile Memory
339// (OTP)" (page 41-42) and Figure 60 (OTP Memory Burn and Verification Flowchart, page 43). The step
340// text reads 0x0016..0x001A / 0x001B; Figure 60 reads and clears 0x0015 (DISABLE) as well, and
341// DISABLE is one of the non-volatile registers (Figure 42), so 0x0015..0x001B are read and
342// compared. The PROG values are those of Figure 60. Steps 1-3 and 20-22 are the caller's (see
343// ProgramOTP()).
344template <typename SpiType>
345bool AS5047U<SpiType>::programOtpSteps(OtpResult& r) {
346 constexpr uint8_t kRetries = AS5047U_CFG::CRC_RETRIES;
347 constexpr std::size_t kZposm = AS5047U_REG::ZPOSM::ADDRESS - kOtpFirstAddress;
348 constexpr std::size_t kZposl = AS5047U_REG::ZPOSL::ADDRESS - kOtpFirstAddress;
349 constexpr std::size_t kEcc = AS5047U_REG::ECC::ADDRESS - kOtpFirstAddress;
350 constexpr uint16_t kProg = AS5047U_REG::PROG::ADDRESS;
351 const auto fail = [&r](uint8_t step) {
352 r.failed_step = step;
353 return false;
354 };
355
356 // 4. Read the measured angle from ANGLECOM.
357 uint16_t angle = 0;
358 if (!readWithRetries(AS5047U_REG::ANGLECOM::ADDRESS, kRetries, angle)) {
359 return fail(4);
360 }
361 // 5. ANGLECOM[5:0] into ZPOSL[5:0] (bits 7:6 kept), ANGLECOM[13:6] into ZPOSM[7:0].
362 AS5047U_REG::ZPOSL zposl{};
363 if (!ReadReg(zposl, kRetries)) {
364 return fail(5);
365 }
366 zposl.bits.ZPOSL_bits = angle & 0x3FU;
367 AS5047U_REG::ZPOSM zposm{};
368 zposm.bits.ZPOSM_bits = (angle >> 6) & 0xFFU;
369 if (!WriteReg(zposl, kRetries) || !WriteReg(zposm, kRetries)) {
370 return fail(5);
371 }
372 // 6. Read the settings (0x0015..0x001B): what the caller wrote in step 2.
373 std::array<uint16_t, kOtpRegisterCount> expected{};
374 if (!readOtpRegisters(expected, kRetries)) {
375 return fail(6);
376 }
377 // 7. ECC_en = 1 (Figure 60: Reg(0x001B) = 0x80).
378 AS5047U_REG::ECC ecc{};
379 ecc.bits.ECC_en = 1;
380 if (!WriteReg(ecc, kRetries)) {
381 return fail(7);
382 }
383 // 8. Read the ECC key ECC_s (0x3FD0).
385 if (!ReadReg(key, kRetries)) {
386 return fail(8);
387 }
388 // 9. Write the key into ECC without clearing ECC_en.
389 ecc.bits.ECC_chsum = key.bits.ECC_s;
390 if (!WriteReg(ecc, kRetries)) {
391 return fail(9);
392 }
393 expected[kZposl] = zposl.value;
394 expected[kZposm] = zposm.value;
395 expected[kEcc] = ecc.value;
396 // 10. Read 0x0015..0x001B: read register step 1.
397 std::array<uint16_t, kOtpRegisterCount> step1{};
398 if (!readOtpRegisters(step1, kRetries)) {
399 return fail(10);
400 }
401 // 11. Compare with the written content (settings and angle).
402 if (step1 != expected) {
403 return fail(11);
404 }
405 r.written = step1;
406 // 12. PROGEN = 1 (Reg(0x0003) = 0x01): verified, it reads back as written.
407 if (!writeRegister(kProg, 0x0001, kRetries)) {
408 return fail(12);
409 }
410 // 13. PROGOTP = 1 (Reg(0x0003) = 0x08) starts the burn. Sent once, never retried, and from here
411 // on the part may be programmed whatever happens next.
412 r.burn_started = true;
413 if (!writeRegister(kProg, 0x0008, 0, false)) {
414 return fail(13);
415 }
416 // 14. Read PROG until it reads 0x0001 (programming procedure complete).
417 bool complete = false;
418 for (uint32_t i = 0; i < kOtpPollLimit && !complete; ++i) {
419 uint16_t prog = 0;
420 complete = readRegister(kProg, prog) && prog == 0x0001;
421 }
422 if (!complete) {
423 return fail(14);
424 }
425 // 15. Clear the volatile copy: 0x0000 into every non-volatile register (Figure 60).
426 for (std::size_t i = 0; i < kOtpRegisterCount; ++i) {
427 if (!writeRegister(static_cast<uint16_t>(kOtpFirstAddress + i), 0x0000, kRetries)) {
428 return fail(15);
429 }
430 }
431 // 16. PROGVER = 1: the guard band for the guard-band test (Reg(0x0003) = 0x40).
432 if (!writeRegister(kProg, 0x0040, 0, false)) {
433 return fail(16);
434 }
435 // 17. OTPREF = 1: refresh the registers from the OTP content (Reg(0x0003) = 0x04).
436 if (!writeRegister(kProg, 0x0004, 0, false)) {
437 return fail(17);
438 }
439 // 18. Read 0x0015..0x001B: read register step 2.
440 std::array<uint16_t, kOtpRegisterCount> step2{};
441 if (!readOtpRegisters(step2, kRetries)) {
442 return fail(18);
443 }
444 // 19. Compare with the written content: a deviation fails the guard-band test.
445 if (step2 != r.written) {
446 return fail(19);
447 }
448 return true;
449}
450
451template <typename SpiType>
452bool AS5047U<SpiType>::VerifyOTP(const std::array<uint16_t, kOtpRegisterCount>& burned,
453 uint8_t retries) const {
454 std::array<uint16_t, kOtpRegisterCount> now{};
455 return readOtpRegisters(now, retries) && now == burned;
456}
457
458template <typename SpiType>
459void AS5047U<SpiType>::updateStickyErrors(uint16_t err_fl) const {
460 // Map ERRFL bits (0-10) to sticky error enum
461 if (err_fl & (1u << 0))
462 sticky_errors_ |= static_cast<uint16_t>(AS5047U_Error::AgcWarning);
463 if (err_fl & (1u << 1))
464 sticky_errors_ |= static_cast<uint16_t>(AS5047U_Error::MagHalf);
465 if (err_fl & (1u << 2))
466 sticky_errors_ |= static_cast<uint16_t>(AS5047U_Error::P2ramWarning);
467 if (err_fl & (1u << 3))
468 sticky_errors_ |= static_cast<uint16_t>(AS5047U_Error::P2ramError);
469 if (err_fl & (1u << 4))
470 sticky_errors_ |= static_cast<uint16_t>(AS5047U_Error::FramingError);
471 if (err_fl & (1u << 5))
472 sticky_errors_ |= static_cast<uint16_t>(AS5047U_Error::CommandError);
473 if (err_fl & (1u << 6))
474 sticky_errors_ |= static_cast<uint16_t>(AS5047U_Error::CrcError);
475 if (err_fl & (1u << 7))
476 sticky_errors_ |= static_cast<uint16_t>(AS5047U_Error::WatchdogError);
477 if (err_fl & (1u << 9))
478 sticky_errors_ |= static_cast<uint16_t>(AS5047U_Error::OffCompError);
479 if (err_fl & (1u << 10))
480 sticky_errors_ |= static_cast<uint16_t>(AS5047U_Error::CordicOverflow);
481}
482
483template <typename SpiType>
485 return static_cast<AS5047U_Error>(sticky_errors_.load());
486}
487
488template <typename SpiType>
490 return static_cast<AS5047U_Error>(sticky_errors_.exchange(0));
491}
492
493// Public API implementations
494template <typename SpiType>
495void AS5047U<SpiType>::SetPad(uint8_t pad) noexcept {
496 this->pad_byte_ = pad;
497}
498
499template <typename SpiType>
501 uint8_t retries) {
503 if (!this->ReadReg(s3, retries)) {
504 return false;
505 }
506 s3.bits.HYS = static_cast<uint8_t>(hysteresis);
507 return this->WriteReg(s3, retries);
508}
509
510template <typename SpiType>
512 auto s3 = this->template ReadReg<AS5047U_REG::SETTINGS3>();
513 return static_cast<AS5047U_REG::SETTINGS3::Hysteresis>(s3.bits.HYS);
514}
515
516template <typename SpiType>
518 uint8_t retries) {
520 if (!this->ReadReg(s2, retries)) {
521 return false;
522 }
523 s2.bits.Data_select = static_cast<uint8_t>(source);
524 return this->WriteReg(s2, retries);
525}
526
527template <typename SpiType>
529 auto s2 = this->template ReadReg<AS5047U_REG::SETTINGS2>();
530 return static_cast<AS5047U_REG::SETTINGS2::AngleOutputSource>(s2.bits.Data_select);
531}
532
533template <typename SpiType>
535 return this->template ReadReg<AS5047U_REG::DIA>();
536}
537
538// ══════════════════════════════════════════════════════════════════════════════════════════
539// PRIVATE - COMMUNICATION API
540// ══════════════════════════════════════════════════════════════════════════════════════════
541//
542// SPI frame layout per AS5047U datasheet (DS000637 v1-01, "SPI Transaction"):
543// - 16-bit (Figures 20-22): MOSI [15] don't care, [14] R, [13:0] ADDR;
544// MISO [15] warning, [14] error, [13:0] DATA.
545// - 24-bit (Figures 23-25): MOSI [23] don't care, [22] RW, [21:8] ADDR or DATA, [7:0] CRC;
546// MISO [23] warning, [22] error, [21:8] DATA, [7:0] CRC.
547// - 32-bit (Figures 26-28): MOSI [31:24] PAD, then the 24-bit frame;
548// MISO [31] warning, [30] error, [29:16] DATA, [15:8] CRC, [7:0] PAD.
549// In every format the 16-bit word (RW and address, or the data) leads the frame after the PAD, and
550// the CRC covers that word, MOSI and MISO alike (Figure 31 and "CRC Checksum"). The warning and
551// error bits of a MISO frame report a problem in the previous command frame (Figure 28 text).
552
553// One frame: send `word` in `format`, return the 16-bit reply word (warning, error, 14 data bits).
554// False when the port's transfer() reports a bus error; that also sets the BusError sticky flag.
555// The warning / error bits of every reply with a valid CRC (any 16-bit reply) are recorded as the
556// ReplyWarning / ReplyError sticky flags. They do not fail the frame: the data of the reply is
557// still valid, and ERRFL, read after every access, has the cause. `inspect` marks a reply whose
558// content the caller uses: a wrong CRC on it sets ReplyCrcError and fails the frame. The CRC of a
559// reply the driver discards is not enforced (its bits are then not recorded either).
560template <typename SpiType>
561bool AS5047U<SpiType>::transferFrame(FrameFormat format, uint16_t word, uint16_t& reply,
562 bool inspect) const {
563 uint8_t tx[4] = {};
564 uint8_t rx[4] = {};
565 std::size_t len = 2;
566 std::size_t o = 0; // offset of the word: 1 in 32-bit frames, after the PAD
567 if (format == FrameFormat::SPI_32) {
568 tx[0] = pad_byte_;
569 o = 1;
570 len = 4;
571 } else if (format == FrameFormat::SPI_24) {
572 len = 3;
573 }
574 tx[o] = static_cast<uint8_t>(word >> 8);
575 tx[o + 1] = static_cast<uint8_t>(word & 0xFF);
576 if (format != FrameFormat::SPI_16) {
577 tx[o + 2] = ComputeCRC8(word);
578 }
579 if (!detail::Transfer(spi_, tx, rx, len)) {
580 sticky_errors_.fetch_or(static_cast<uint16_t>(AS5047U_Error::BusError));
581 reply = 0;
582 return false;
583 }
584 reply = static_cast<uint16_t>((static_cast<uint16_t>(rx[0]) << 8) | rx[1]);
585 const bool crc_ok = (format == FrameFormat::SPI_16) || (rx[2] == ComputeCRC8(reply));
586 if (crc_ok) {
587 if ((reply & kReplyWarningBit) != 0U) {
588 sticky_errors_.fetch_or(static_cast<uint16_t>(AS5047U_Error::ReplyWarning));
589 }
590 if ((reply & kReplyErrorBit) != 0U) {
591 sticky_errors_.fetch_or(static_cast<uint16_t>(AS5047U_Error::ReplyError));
592 }
593 }
594 if (inspect && !crc_ok) {
595 sticky_errors_.fetch_or(static_cast<uint16_t>(AS5047U_Error::ReplyCrcError));
596 return false;
597 }
598 return true;
599}
600
601// Register read without the ERRFL read. DS: "the data is transmitted on MISO with the next read
602// command" (Figure 29), so the read command is followed by a NOP whose reply carries the data.
603template <typename SpiType>
604bool AS5047U<SpiType>::rawReadRegister(uint16_t address, uint16_t& value) const {
605 uint16_t reply = 0;
606 const bool ok =
607 transferFrame(frame_format_, static_cast<uint16_t>(kReadBit | (address & 0x3FFF)), reply) &&
608 transferFrame(frame_format_, kReadBit | AS5047U_REG::NOP::ADDRESS, reply, true);
609 value = ok ? static_cast<uint16_t>(reply & 0x3FFF) : 0U;
610 return ok;
611}
612
613// Register read that also fetches ERRFL (cleared on the chip by that read, datasheet Figure 33) and
614// ORs it into the sticky flags. A read of ERRFL itself is accumulated directly: reading it a second
615// time would return 0 and lose the flags. True when every frame went through and, for a register
616// other than ERRFL, the chip reported no CRC or framing error for the access.
617template <typename SpiType>
618bool AS5047U<SpiType>::readRegister(uint16_t address, uint16_t& value) const {
619 if (!rawReadRegister(address, value)) {
620 return false;
621 }
622 if (address == AS5047U_REG::ERRFL::ADDRESS) {
623 updateStickyErrors(value);
624 return true;
625 }
626 uint16_t err = 0;
627 if (!rawReadRegister(AS5047U_REG::ERRFL::ADDRESS, err)) {
628 return false;
629 }
630 updateStickyErrors(err);
631 return (err & kRetryMask) == 0U;
632}
633
634template <typename SpiType>
635uint16_t AS5047U<SpiType>::readRegister(uint16_t address) const {
636 uint16_t value = 0;
637 (void)readRegister(address, value);
638 return value;
639}
640
641template <typename SpiType>
642bool AS5047U<SpiType>::readWithRetries(uint16_t address, uint8_t retries, uint16_t& value) const {
643 for (uint8_t i = 0; i <= retries; ++i) {
644 if (readRegister(address, value)) {
645 return true;
646 }
647 }
648 return false;
649}
650
651template <typename SpiType>
652uint16_t AS5047U<SpiType>::readWithRetries(uint16_t address, uint8_t retries) const {
653 uint16_t value = 0;
654 (void)readWithRetries(address, retries, value);
655 return value;
656}
657
658template <typename SpiType>
659bool AS5047U<SpiType>::writeRegister(uint16_t address, uint16_t value, uint8_t retries,
660 bool verify) const {
661 // The AS5047U datasheet specifies 16-bit frames for read operations only.
662 // Writes require 24-bit or 32-bit frames (which include CRC). If the current
663 // frame format is SPI_16, temporarily promote to SPI_24 for the write, then
664 // restore. This ensures writes always reach the IC correctly.
665 const FrameFormat format =
666 (frame_format_ == FrameFormat::SPI_16) ? FrameFormat::SPI_24 : frame_format_;
667
668 // DS Fig.30: Write = command frame then data frame. MISO during data = old content.
669 // "At the next command" MISO = new content — so we send NOP after data and use that MISO to verify.
670 // CS may toggle between command and data frames.
671 const uint16_t expected = value & 0x3FFF;
672 for (uint8_t attempt = 0; attempt <= retries; ++attempt) {
673 uint16_t reply = 0;
674 const bool sent = transferFrame(format, address & 0x3FFF, reply) && // RW = 0: write
675 transferFrame(format, expected, reply, true) && // MISO: old content
676 transferFrame(format, kReadBit | AS5047U_REG::NOP::ADDRESS, reply, true);
677 const uint16_t read_back = reply & 0x3FFF; // MISO on the NOP: new content
678 if (sent && verify && read_back == expected) {
679 return true;
680 }
681 // Why it failed (or, unverified, whether the chip took it): ERRFL goes to the sticky flags,
682 // where the caller finds it. The driver itself prints nothing.
683 uint16_t errfl = 0;
684 const bool errfl_ok = readRegister(AS5047U_REG::ERRFL::ADDRESS, errfl);
685 if (!verify && sent && errfl_ok && (errfl & kCommandErrorMask) == 0U) {
686 return true;
687 }
688 }
689 return false;
690}
691
692// ════════════════════════════════════════════════════════════════════════════════════════════
693// Public API: retry-enabled getters and status dump
694// ════════════════════════════════════════════════════════════════════════════════════════════
695
696// Complete dumpStatus with full register dump
697template <typename SpiType>
699 printf("\n=== AS5047U Comprehensive Status ===\n");
700 // Core measurements
701 printf("Angle (COM) : %u\n", GetAngle());
702 printf("Angle (UNC) : %u\n", GetRawAngle());
703 printf("Velocity : %d counts (%.3f deg/s, %.3f rad/s, %.3f RPM)\n", GetVelocity(),
704 GetVelocityDegPerSec(), GetVelocityRadPerSec(), GetVelocityRPM());
705 printf("AGC : %u\n", GetAGC());
706 printf("Magnitude : %u\n", GetMagnitude());
707 // Error flags
708 uint16_t err = GetErrorFlags();
709 printf("ERRFL : 0x%04X\n", err);
710 // Diagnostic register bits
711 auto dia = this->template ReadReg<AS5047U_REG::DIA>();
712 printf("DIA (0x3FF5): 0x%04X\n", dia.value);
713 printf(" VDD_mode : %u\n", dia.bits.VDD_mode);
714 printf(" LoopsFinished : %u\n", dia.bits.LoopsFinished);
715 printf(" CORDIC_overflow : %u\n", dia.bits.CORDIC_overflow_flag);
716 printf(" Comp_l : %u\n", dia.bits.Comp_l);
717 printf(" Comp_h : %u\n", dia.bits.Comp_h);
718 printf(" MagHalf_flag : %u\n", dia.bits.MagHalf_flag);
719 printf(" CosOff_fin : %u\n", dia.bits.CosOff_fin);
720 printf(" SinOff_fin : %u\n", dia.bits.SinOff_fin);
721 printf(" OffComp_finished : %u\n", dia.bits.OffComp_finished);
722 printf(" AGC_finished : %u\n", dia.bits.AGC_finished);
723 printf(" SPI_cnt : %u\n", dia.bits.SPI_cnt);
724 // Configuration registers
725 auto dis = this->template ReadReg<AS5047U_REG::DISABLE>();
726 printf("DISABLE (0x0015): 0x%04X UVW_off=%u ABI_off=%u FILTER_disable=%u\n", dis.value,
727 dis.bits.UVW_off, dis.bits.ABI_off, dis.bits.FILTER_disable);
728 auto s1 = this->template ReadReg<AS5047U_REG::SETTINGS1>();
729 printf("SETTINGS1(0x0016): K_max=%u K_min=%u Dia3_en=%u Dia4_en=%u\n", s1.bits.K_max,
730 s1.bits.K_min, s1.bits.Dia3_en, s1.bits.Dia4_en);
731 auto s2 = this->template ReadReg<AS5047U_REG::SETTINGS2>();
732 printf("SETTINGS2(0x0019): IWIDTH=%u NOISESET=%u DIR=%u UVW_ABI=%u "
733 "DAECDIS=%u ABI_DEC=%u "
734 "Data_select=%u PWMon=%u\n",
735 s2.bits.IWIDTH, s2.bits.NOISESET, s2.bits.DIR, s2.bits.UVW_ABI, s2.bits.DAECDIS,
736 s2.bits.ABI_DEC, s2.bits.Data_select, s2.bits.PWMon);
737 auto s3 = this->template ReadReg<AS5047U_REG::SETTINGS3>();
738 printf("SETTINGS3(0x001A): UVWPP=%u HYS=%u ABIRES=%u\n", s3.bits.UVWPP, s3.bits.HYS,
739 s3.bits.ABIRES);
740 // Other registers
741 auto sind = this->template ReadReg<AS5047U_REG::SINDATA>();
742 printf("SINDATA(0x3FFA): %d\n", sind.bits.SINDATA);
743 auto eccsum = this->template ReadReg<AS5047U_REG::ECC_Checksum>();
744 printf("ECC_Checksum(0x3FD0): %u\n", eccsum.bits.ECC_s);
745 auto prog = this->template ReadReg<AS5047U_REG::PROG>();
746 printf("PROG(0x0003): PROGEN=%u PROGOTP=%u OTPREF=%u PROGVER=%u\n", prog.bits.PROGEN,
747 prog.bits.PROGOTP, prog.bits.OTPREF, prog.bits.PROGVER);
748 // Interface settings
749 printf("FrameFormat : %u PadByte=0x%02X\n", static_cast<uint8_t>(this->frame_format_),
750 this->pad_byte_);
751 printf("========================================\n\n");
752}
753
754} // namespace as5047u
755
756#endif // AS5047U_IMPL
Driver for AMS AS5047U Magnetic Rotary Position Sensor (C++21)
AS5047U_Error
Definition as5047u.hpp:21
@ CommandError
Invalid SPI command received.
@ BusError
Driver: the port's transfer() reported a bus error.
@ P2ramWarning
ECC corrected 1 bit in P2RAM customer area.
@ OffCompError
Internal offset compensation not finished.
@ P2ramError
ECC detected 2+ uncorrectable errors in P2RAM.
@ ReplyError
Chip: error bit set in a MISO frame (DS Figures 22, 25, 28)
@ ReplyCrcError
Driver: the CRC of a 24/32-bit MISO frame was wrong.
@ AgcWarning
AGC reached minimum (0) or maximum (255) value.
@ ReplyWarning
Chip: warning bit set in a MISO frame (DS Figures 22, 25, 28)
@ 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:114
float GetVelocityDegPerSec(uint8_t retries=AS5047U_CFG::CRC_RETRIES) const
Get rotational velocity in degrees per second.
Definition as5047u.ipp:97
bool SetAdaptiveFilter(bool enable, uint8_t retries=AS5047U_CFG::CRC_RETRIES)
Enable/disable the adaptive filter (Dynamic Filter System).
Definition as5047u.ipp:240
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:68
void SetPad(uint8_t pad) noexcept
Set the daisy-chain pad byte for 32-bit SPI frames.
Definition as5047u.ipp:495
AS5047U_REG::SETTINGS3::Hysteresis GetHysteresis() const
Get current incremental output hysteresis setting.
Definition as5047u.ipp:511
uint16_t GetMagnitude(uint8_t retries=AS5047U_CFG::CRC_RETRIES) const
Read the current magnetic field magnitude (14-bit value).
Definition as5047u.ipp:118
uint16_t GetErrorFlags(uint8_t retries=AS5047U_CFG::CRC_RETRIES) const
Read the ERRFL register (0x0001) from the chip.
Definition as5047u.ipp:124
bool SetDynamicAngleCompensation(bool enable, uint8_t retries=AS5047U_CFG::CRC_RETRIES)
Enable/disable Dynamic Angle Error Compensation (DAEC).
Definition as5047u.ipp:230
AS5047U_REG::SETTINGS2::AngleOutputSource GetAngleOutputSource() const
Get currently selected angle output source for 0x3FFF reads.
Definition as5047u.ipp:528
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:263
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:205
float GetVelocityRPM(uint8_t retries=AS5047U_CFG::CRC_RETRIES) const
Get rotational velocity in revolutions per minute.
Definition as5047u.ipp:107
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:160
bool VerifyOTP(const std::array< uint16_t, kOtpRegisterCount > &burned, uint8_t retries=AS5047U_CFG::CRC_RETRIES) const
Steps 21 and 22 of the OTP procedure: after the power-on cycle (step 20), read DISABLE....
Definition as5047u.ipp:452
float GetAngleDegrees(uint8_t retries=AS5047U_CFG::CRC_RETRIES) const
Read absolute angle in degrees.
Definition as5047u.ipp:58
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:173
uint16_t GetZeroPosition(uint8_t retries=AS5047U_CFG::CRC_RETRIES) const
Get the currently configured soft zero position offset (14-bit).
Definition as5047u.ipp:140
bool Set150CTemperatureMode(bool enable, uint8_t retries=AS5047U_CFG::CRC_RETRIES)
Set temperature mode for 150°C operation (NOISESET bit).
Definition as5047u.ipp:303
bool SetHysteresis(AS5047U_REG::SETTINGS3::Hysteresis hysteresis, uint8_t retries=AS5047U_CFG::CRC_RETRIES)
Set incremental output hysteresis level.
Definition as5047u.ipp:500
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:296
bool GetAdaptiveFilterEnabled(uint8_t retries=AS5047U_CFG::CRC_RETRIES) const
Read whether the adaptive filter is enabled.
Definition as5047u.ipp:289
void DumpStatus() const
Dump formatted status and diagnostics using printf.
Definition as5047u.ipp:698
bool SetZeroPosition(uint16_t angle_lsb, uint8_t retries=AS5047U_CFG::CRC_RETRIES)
Set a new zero reference position (soft offset).
Definition as5047u.ipp:151
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:517
AS5047U_Error ClearStickyErrorFlags() noexcept
Clear the sticky error flags.
Definition as5047u.ipp:489
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:250
bool ProgramOTP(OtpResult *result=nullptr)
Permanently program the current settings into OTP memory.
Definition as5047u.ipp:313
int16_t GetVelocity(uint8_t retries=AS5047U_CFG::CRC_RETRIES) const
Read the current rotational velocity (signed 14-bit).
Definition as5047u.ipp:74
AS5047U_Error GetStickyErrorFlags() const noexcept
Error flags accumulated since the last ClearStickyErrorFlags().
Definition as5047u.ipp:484
AS5047U_REG::DIA GetDiagnostics() const
Read the full diagnostic register (DIA).
Definition as5047u.ipp:534
float GetAngleRadians(uint8_t retries=AS5047U_CFG::CRC_RETRIES) const
Read absolute angle in radians.
Definition as5047u.ipp:63
bool SetIndexPulseLength(uint8_t lsb_len, uint8_t retries=AS5047U_CFG::CRC_RETRIES)
Set the index pulse width for ABI output.
Definition as5047u.ipp:184
float GetVelocityRadPerSec(uint8_t retries=AS5047U_CFG::CRC_RETRIES) const
Get rotational velocity in radians per second.
Definition as5047u.ipp:102
uint8_t GetAGC(uint8_t retries=AS5047U_CFG::CRC_RETRIES) const
Read the current Automatic Gain Control (AGC) value (0-255).
Definition as5047u.ipp:112
constexpr uint8_t CRC_RETRIES
Definition as5047u_config.hpp:26
bool Transfer(Port &port, const uint8_t *tx, uint8_t *rx, std::size_t len)
Call the port's transfer() and return its status.
Definition as5047u_spi_interface.hpp:66
Definition as5047u.hpp:52
VelocityUnit
Units for velocity-returning APIs.
Definition as5047u.hpp:89
constexpr std::size_t kOtpRegisterCount
Number of non-volatile registers burned into OTP: DISABLE (0x0015) to ECC (0x001B).
Definition as5047u.hpp:55
constexpr uint16_t kOtpFirstAddress
First non-volatile register address (DISABLE).
Definition as5047u.hpp:57
AngleUnit
Units for angle-returning APIs.
Definition as5047u.hpp:82
static constexpr uint16_t ADDRESS
Definition as5047u_registers.hpp:216
static constexpr uint16_t ADDRESS
Definition as5047u_registers.hpp:378
static constexpr uint16_t ADDRESS
Definition as5047u_registers.hpp:331
DIA – Diagnostic register (0x3FF5, read-only)
Definition as5047u_registers.hpp:180
DISABLE – Outputs and filter disable register (0x0015, default 0x0000)
Definition as5047u_registers.hpp:439
static constexpr uint16_t ADDRESS
Definition as5047u_registers.hpp:440
struct AS5047U_REG::DISABLE::@36::@38 bits
uint16_t FILTER_disable
Definition as5047u_registers.hpp:447
ECC_Checksum – ECC checksum register (0x3FD0, default 0x0000)
Definition as5047u_registers.hpp:351
ECC – ECC Settings register (0x001B, default 0x0000)
Definition as5047u_registers.hpp:750
uint16_t ECC_en
Definition as5047u_registers.hpp:756
static constexpr uint16_t ADDRESS
Definition as5047u_registers.hpp:751
struct AS5047U_REG::ECC::@54::@56 bits
static constexpr uint16_t ADDRESS
Definition as5047u_registers.hpp:81
static constexpr uint16_t ADDRESS
Definition as5047u_registers.hpp:307
static constexpr uint16_t ADDRESS
Definition as5047u_registers.hpp:42
static constexpr uint16_t ADDRESS
Definition as5047u_registers.hpp:133
SETTINGS1 – Custom setting register 1 (0x0018, default 0x0000)
Definition as5047u_registers.hpp:533
uint16_t K_min
Definition as5047u_registers.hpp:539
struct AS5047U_REG::SETTINGS1::@45::@47 bits
static constexpr uint16_t ADDRESS
Definition as5047u_registers.hpp:534
SETTINGS2 – Custom setting register 2 (0x0019, default 0x0000)
Definition as5047u_registers.hpp:597
AngleOutputSource
Angle data source for 0x3FFF output (Data_select bit)
Definition as5047u_registers.hpp:644
uint16_t Data_select
Definition as5047u_registers.hpp:608
struct AS5047U_REG::SETTINGS2::@48::@50 bits
uint16_t DAECDIS
Definition as5047u_registers.hpp:606
uint16_t IWIDTH
Definition as5047u_registers.hpp:602
uint16_t UVW_ABI
Definition as5047u_registers.hpp:605
uint16_t NOISESET
Definition as5047u_registers.hpp:603
SETTINGS3 – Custom setting register 3 (0x001A, default 0x0000)
Definition as5047u_registers.hpp:691
uint16_t HYS
Definition as5047u_registers.hpp:697
Hysteresis
Incremental output hysteresis (HYS) settings:
Definition as5047u_registers.hpp:714
uint16_t UVWPP
Definition as5047u_registers.hpp:696
struct AS5047U_REG::SETTINGS3::@51::@53 bits
uint16_t ABIRES
Definition as5047u_registers.hpp:698
static constexpr uint16_t ADDRESS
Definition as5047u_registers.hpp:282
ZPOSL – Zero Position LSB register (0x0017, default 0x0000)
Definition as5047u_registers.hpp:505
struct AS5047U_REG::ZPOSL::@42::@44 bits
static constexpr uint16_t ADDRESS
Definition as5047u_registers.hpp:506
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
static constexpr uint16_t ADDRESS
Definition as5047u_registers.hpp:483
Outcome of AS5047U::ProgramOTP().
Definition as5047u.hpp:65