From f3298ecffb03d671da0825eeddc4541a9c3de09a Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Thu, 11 Aug 2022 16:24:43 +0200 Subject: [PATCH 01/23] trigger docs build --- docs/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Makefile b/docs/Makefile index d4bb2cbb9..2e7f1c67c 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -18,3 +18,4 @@ help: # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + \ No newline at end of file From 146b67fed3abe7a6e7171238a18bb7af56c286a3 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Thu, 11 Aug 2022 16:30:49 +0200 Subject: [PATCH 02/23] [docs] fix version indicator --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 7490c08d6..e6c3e2f22 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -79,5 +79,5 @@ 'https://docs.odriverobotics.com/docsInject.js' ] -version = "0.5.4" +version = "0.5.5" release = version From a308314ed2ca613164b81e7bbdfacc53cd1859ff Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 2 May 2023 11:47:09 +0200 Subject: [PATCH 03/23] fix release action --- .github/actions/upload-release/action.yml | 9 +++++---- .github/workflows/firmware.yaml | 10 +++++++++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/.github/actions/upload-release/action.yml b/.github/actions/upload-release/action.yml index a84d306d1..3a32de321 100644 --- a/.github/actions/upload-release/action.yml +++ b/.github/actions/upload-release/action.yml @@ -23,8 +23,8 @@ inputs: odrive_api_key: description: 'Key to our release index server' required: true - product: - description: 'ODrive product name (for firmware releases only).' + board: + description: 'ODrive board version triplet ("PRODUCT_LINE.VERSION.VARIANT") (for firmware releases only).' required: false app: description: 'Firmware app name (default, bootloader) (for firmware releases only).' @@ -95,6 +95,7 @@ runs: shell: python run: | import asyncio + import re import sys import aiohttp @@ -115,8 +116,8 @@ runs: release_api = PrivateReleaseApi(api_client) qualifiers = {} - if '${{ inputs.product }}': - qualifiers['product'] = '${{ inputs.product }}' + if '${{ inputs.board }}': + qualifiers['board'] = tuple(int(i) for i in re.match(r'^([0-9]+)\.([0-9]+).([0-9]+)$', '${{ inputs.board }}').groups()) if '${{ inputs.app }}': qualifiers['app'] = '${{ inputs.app }}' if '${{ inputs.variant }}': diff --git a/.github/workflows/firmware.yaml b/.github/workflows/firmware.yaml index 90f3e4002..5f73efd65 100644 --- a/.github/workflows/firmware.yaml +++ b/.github/workflows/firmware.yaml @@ -147,6 +147,14 @@ jobs: mkdir ${{ github.workspace }}/out cp ${{ github.workspace }}/Firmware/build/ODriveFirmware.elf ${{ github.workspace }}/out/firmware.elf + - name: Parse and format matrix.board_version + if: ${{ matrix.os == 'ubuntu-latest' }} + id: format-board-version + run: | + formatted_version=$(echo "${{ matrix.board_version }}" | sed 's/v\([0-9]\+\)\.\([0-9]\+\)-\([0-9]\+\)V/\1.\2.\3/') + echo "Formatted board version: $formatted_version" + echo "::set-output name=formatted_board_version::$formatted_version" + - name: Upload firmware to ODrive release system if: ${{ steps.release-info.outputs.channel == 'master' && matrix.os == 'ubuntu-latest' && matrix.debug == false && (startsWith(matrix.board_version, 'v3.5-') || startsWith(matrix.board_version, 'v3.6-')) }} uses: ./.github/actions/upload-release @@ -157,7 +165,7 @@ jobs: do_access_key: ${{ secrets.DIGITALOCEAN_ACCESS_KEY }} do_secret_key: ${{ secrets.DIGITALOCEAN_SECRET_KEY }} odrive_api_key: ${{ secrets.ODRIVE_API_KEY }} - product: ODrive ${{ matrix.board_version }} + board: ${{ steps.format-board-version.outputs.formatted_board_version }} app: default variant: public From 58fdd3fdfdcaeff76547870f5e7acdf1673479e8 Mon Sep 17 00:00:00 2001 From: samuelsadok Date: Tue, 9 May 2023 12:18:21 +0200 Subject: [PATCH 04/23] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bc5d741c..c3aad6087 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ -## [0.5.6] - Unreleased +## [0.5.6] - 2023-04-29 ### Fixed From 6c3cefdeacce600ba4524f4adc5fd634c7bebd18 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Thu, 21 Mar 2024 12:42:00 +0100 Subject: [PATCH 05/23] add note about compatibility --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 97b77abf4..17132726c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,11 @@ +## Important Note + +The firmware in this repository is compatible with the ODrive v3.x (NRND) and is no longer under active development. + +Firmware for the new generation of ODrives ([ODrive Pro](https://odriverobotics.com/shop/odrive-pro), [S1](https://odriverobotics.com/shop/odrive-s1), [Micro](https://odriverobotics.com/shop/odrive-micro), etc.) is currently being actively maintained and developed, however its source code is currently not publicly available. Access may be available under NDA, please [reach out to us](mailto:info@odriverobotics.com) for inquiries. + +## Overview + ![ODrive Logo](https://static1.squarespace.com/static/58aff26de4fcb53b5efd2f02/t/59bf2a7959cc6872bd68be7e/1505700483663/Odrive+logo+plus+text+black.png?format=1000w) This project is all about accurately driving brushless motors, for cheap. The aim is to make it possible to use inexpensive brushless motors in high performance robotics projects, like [this](https://www.youtube.com/watch?v=WT4E5nb3KtY). From ca132888f1a3882be354d7289fae1417d2ebe041 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 2 Oct 2024 18:53:18 +0200 Subject: [PATCH 06/23] fix compile on GCC 12 --- Firmware/fibre-cpp/include/fibre/cpp_utils.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Firmware/fibre-cpp/include/fibre/cpp_utils.hpp b/Firmware/fibre-cpp/include/fibre/cpp_utils.hpp index 7472c2214..8d211a8fa 100644 --- a/Firmware/fibre-cpp/include/fibre/cpp_utils.hpp +++ b/Firmware/fibre-cpp/include/fibre/cpp_utils.hpp @@ -976,10 +976,12 @@ TRet* dynamic_get(size_t i, const std::tuple& t) { template -class simple_iterator : std::iterator { +class simple_iterator { TDereferenceable *container_; size_t i_; public: + using iterator_category = std::random_access_iterator_tag; + using value_type = TResult; using reference = TResult; explicit simple_iterator(TDereferenceable& container, size_t pos) : container_(&container), i_(pos) {} simple_iterator& operator++() { ++i_; return *this; } From f9b5419c820c6fb2f609322a5131cdf8c5f71657 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 2 Oct 2024 18:53:42 +0200 Subject: [PATCH 07/23] Remove unused buggy FreeRTOS functions --- .../FreeRTOS/Source/stream_buffer.c | 143 +----------------- 1 file changed, 4 insertions(+), 139 deletions(-) diff --git a/Firmware/ThirdParty/FreeRTOS/Source/stream_buffer.c b/Firmware/ThirdParty/FreeRTOS/Source/stream_buffer.c index 85519707b..3efe82ce0 100644 --- a/Firmware/ThirdParty/FreeRTOS/Source/stream_buffer.c +++ b/Firmware/ThirdParty/FreeRTOS/Source/stream_buffer.c @@ -214,146 +214,11 @@ static void prvInitialiseNewStreamBuffer( StreamBuffer_t * const pxStreamBuffer, /*-----------------------------------------------------------*/ -#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) +// REMOVED IMPLEMENTATIONS OF xStreamBufferGenericCreate and xStreamBufferGenericCreateStatic. +// We're not using them but the implementation we were using contained a +// vulnerability (CVE-2021-31572) and got flagged by analysis tools. +// https://github.com/odriverobotics/ODrive/issues/751 - StreamBufferHandle_t xStreamBufferGenericCreate( size_t xBufferSizeBytes, size_t xTriggerLevelBytes, BaseType_t xIsMessageBuffer ) - { - uint8_t *pucAllocatedMemory; - uint8_t ucFlags; - - /* In case the stream buffer is going to be used as a message buffer - (that is, it will hold discrete messages with a little meta data that - says how big the next message is) check the buffer will be large enough - to hold at least one message. */ - if( xIsMessageBuffer == pdTRUE ) - { - /* Is a message buffer but not statically allocated. */ - ucFlags = sbFLAGS_IS_MESSAGE_BUFFER; - configASSERT( xBufferSizeBytes > sbBYTES_TO_STORE_MESSAGE_LENGTH ); - } - else - { - /* Not a message buffer and not statically allocated. */ - ucFlags = 0; - configASSERT( xBufferSizeBytes > 0 ); - } - configASSERT( xTriggerLevelBytes <= xBufferSizeBytes ); - - /* A trigger level of 0 would cause a waiting task to unblock even when - the buffer was empty. */ - if( xTriggerLevelBytes == ( size_t ) 0 ) - { - xTriggerLevelBytes = ( size_t ) 1; - } - - /* A stream buffer requires a StreamBuffer_t structure and a buffer. - Both are allocated in a single call to pvPortMalloc(). The - StreamBuffer_t structure is placed at the start of the allocated memory - and the buffer follows immediately after. The requested size is - incremented so the free space is returned as the user would expect - - this is a quirk of the implementation that means otherwise the free - space would be reported as one byte smaller than would be logically - expected. */ - xBufferSizeBytes++; - pucAllocatedMemory = ( uint8_t * ) pvPortMalloc( xBufferSizeBytes + sizeof( StreamBuffer_t ) ); /*lint !e9079 malloc() only returns void*. */ - - if( pucAllocatedMemory != NULL ) - { - prvInitialiseNewStreamBuffer( ( StreamBuffer_t * ) pucAllocatedMemory, /* Structure at the start of the allocated memory. */ /*lint !e9087 Safe cast as allocated memory is aligned. */ /*lint !e826 Area is not too small and alignment is guaranteed provided malloc() behaves as expected and returns aligned buffer. */ - pucAllocatedMemory + sizeof( StreamBuffer_t ), /* Storage area follows. */ /*lint !e9016 Indexing past structure valid for uint8_t pointer, also storage area has no alignment requirement. */ - xBufferSizeBytes, - xTriggerLevelBytes, - ucFlags ); - - traceSTREAM_BUFFER_CREATE( ( ( StreamBuffer_t * ) pucAllocatedMemory ), xIsMessageBuffer ); - } - else - { - traceSTREAM_BUFFER_CREATE_FAILED( xIsMessageBuffer ); - } - - return ( StreamBufferHandle_t ) pucAllocatedMemory; /*lint !e9087 !e826 Safe cast as allocated memory is aligned. */ - } - -#endif /* configSUPPORT_DYNAMIC_ALLOCATION */ -/*-----------------------------------------------------------*/ - -#if( configSUPPORT_STATIC_ALLOCATION == 1 ) - - StreamBufferHandle_t xStreamBufferGenericCreateStatic( size_t xBufferSizeBytes, - size_t xTriggerLevelBytes, - BaseType_t xIsMessageBuffer, - uint8_t * const pucStreamBufferStorageArea, - StaticStreamBuffer_t * const pxStaticStreamBuffer ) - { - StreamBuffer_t * const pxStreamBuffer = ( StreamBuffer_t * ) pxStaticStreamBuffer; /*lint !e740 !e9087 Safe cast as StaticStreamBuffer_t is opaque Streambuffer_t. */ - StreamBufferHandle_t xReturn; - uint8_t ucFlags; - - configASSERT( pucStreamBufferStorageArea ); - configASSERT( pxStaticStreamBuffer ); - configASSERT( xTriggerLevelBytes <= xBufferSizeBytes ); - - /* A trigger level of 0 would cause a waiting task to unblock even when - the buffer was empty. */ - if( xTriggerLevelBytes == ( size_t ) 0 ) - { - xTriggerLevelBytes = ( size_t ) 1; - } - - if( xIsMessageBuffer != pdFALSE ) - { - /* Statically allocated message buffer. */ - ucFlags = sbFLAGS_IS_MESSAGE_BUFFER | sbFLAGS_IS_STATICALLY_ALLOCATED; - } - else - { - /* Statically allocated stream buffer. */ - ucFlags = sbFLAGS_IS_STATICALLY_ALLOCATED; - } - - /* In case the stream buffer is going to be used as a message buffer - (that is, it will hold discrete messages with a little meta data that - says how big the next message is) check the buffer will be large enough - to hold at least one message. */ - configASSERT( xBufferSizeBytes > sbBYTES_TO_STORE_MESSAGE_LENGTH ); - - #if( configASSERT_DEFINED == 1 ) - { - /* Sanity check that the size of the structure used to declare a - variable of type StaticStreamBuffer_t equals the size of the real - message buffer structure. */ - volatile size_t xSize = sizeof( StaticStreamBuffer_t ); - configASSERT( xSize == sizeof( StreamBuffer_t ) ); - } /*lint !e529 xSize is referenced is configASSERT() is defined. */ - #endif /* configASSERT_DEFINED */ - - if( ( pucStreamBufferStorageArea != NULL ) && ( pxStaticStreamBuffer != NULL ) ) - { - prvInitialiseNewStreamBuffer( pxStreamBuffer, - pucStreamBufferStorageArea, - xBufferSizeBytes, - xTriggerLevelBytes, - ucFlags ); - - /* Remember this was statically allocated in case it is ever deleted - again. */ - pxStreamBuffer->ucFlags |= sbFLAGS_IS_STATICALLY_ALLOCATED; - - traceSTREAM_BUFFER_CREATE( pxStreamBuffer, xIsMessageBuffer ); - - xReturn = ( StreamBufferHandle_t ) pxStaticStreamBuffer; /*lint !e9087 Data hiding requires cast to opaque type. */ - } - else - { - xReturn = NULL; - traceSTREAM_BUFFER_CREATE_STATIC_FAILED( xReturn, xIsMessageBuffer ); - } - - return xReturn; - } - -#endif /* ( configSUPPORT_STATIC_ALLOCATION == 1 ) */ /*-----------------------------------------------------------*/ void vStreamBufferDelete( StreamBufferHandle_t xStreamBuffer ) From 3a2e4dd1bfbda9e4c534d5c7d8428a99c361e783 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 20 Jan 2026 11:54:32 +0100 Subject: [PATCH 08/23] update deprecated action dependencies --- .github/workflows/documentation.yml | 2 +- .github/workflows/firmware.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 5ff5da141..ba03f19b6 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -24,7 +24,7 @@ jobs: print("::set-output name=version::" + version) - name: Cache pip - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-sphinx-sphinx-tabs-sphinx-design-sphinx_copybutton-sphinx_panels-sphinx_rtd_theme diff --git a/.github/workflows/firmware.yaml b/.github/workflows/firmware.yaml index 5f73efd65..ae6bad918 100644 --- a/.github/workflows/firmware.yaml +++ b/.github/workflows/firmware.yaml @@ -84,7 +84,7 @@ jobs: - name: Cache chocolatey - uses: actions/cache@v2 + uses: actions/cache@v4 if: startsWith(matrix.os, 'windows-') with: path: C:\Users\runneradmin\AppData\Local\Temp\chocolatey\gcc-arm-embedded From c9d473b55358f91a9b40993ee738a8caf8c8c2b8 Mon Sep 17 00:00:00 2001 From: JIE Date: Sat, 4 Apr 2026 23:28:47 +0800 Subject: [PATCH 09/23] Trigger GitHub Actions CI build for v3.6-56V From f2d06650cbf414ea0d5dd14737e99dbef4a4f7c0 Mon Sep 17 00:00:00 2001 From: JIE Date: Sat, 4 Apr 2026 23:56:04 +0800 Subject: [PATCH 10/23] Add ci-v3.6-56V branch to workflow triggers --- .github/workflows/firmware.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/firmware.yaml b/.github/workflows/firmware.yaml index ae6bad918..e98977ae2 100644 --- a/.github/workflows/firmware.yaml +++ b/.github/workflows/firmware.yaml @@ -4,7 +4,7 @@ on: pull_request: branches: [master, devel] push: - branches: [master, devel] + branches: [master, devel, ci-v3.6-56V] tags: ['fw-v*'] jobs: From e1595dd62e87746796a2a74e19a4d87d41fa1f43 Mon Sep 17 00:00:00 2001 From: JIE Date: Sun, 5 Apr 2026 00:03:09 +0800 Subject: [PATCH 11/23] Fix deprecated upload-artifact action version --- .github/workflows/firmware.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/firmware.yaml b/.github/workflows/firmware.yaml index e98977ae2..812ab67c3 100644 --- a/.github/workflows/firmware.yaml +++ b/.github/workflows/firmware.yaml @@ -134,7 +134,7 @@ jobs: - name: Upload binary as artifact if: ${{ matrix.os == 'ubuntu-latest' && matrix.debug == false }} - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: firmware-${{ matrix.board_version }} path: | From ffb07acd2b9454af93131dd92d1d42aad87ec24d Mon Sep 17 00:00:00 2001 From: JIE Date: Sun, 5 Apr 2026 00:06:56 +0800 Subject: [PATCH 12/23] Fix macOS pip install for externally managed Python environment --- .github/workflows/firmware.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/firmware.yaml b/.github/workflows/firmware.yaml index 812ab67c3..be8a6641c 100644 --- a/.github/workflows/firmware.yaml +++ b/.github/workflows/firmware.yaml @@ -80,7 +80,8 @@ jobs: # See https://github.com/osxfuse/osxfuse/issues/801#issuecomment-833419942 brew install --cask macfuse brew install gromgit/fuse/tup-mac - pip3 install PyYAML Jinja2 jsonschema + python3 -m pip install --user PyYAML Jinja2 jsonschema + echo 'export PATH="$HOME/.local/bin:$PATH"' >> $GITHUB_ENV - name: Cache chocolatey From 5956e49a7e5ae803b8a2a8c9f29ef38c6e59ded1 Mon Sep 17 00:00:00 2001 From: JIE Date: Sun, 5 Apr 2026 00:12:26 +0800 Subject: [PATCH 13/23] Remove macOS matrix to avoid unsupported runner failure --- .github/workflows/firmware.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/firmware.yaml b/.github/workflows/firmware.yaml index be8a6641c..fc1fb34f3 100644 --- a/.github/workflows/firmware.yaml +++ b/.github/workflows/firmware.yaml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macOS-latest] + os: [ubuntu-latest, windows-latest] board_version: [v3.6-56V] debug: [true] From 1744f2ec0968f81be9eafd07678c0584849bf26a Mon Sep 17 00:00:00 2001 From: dwj <2650303277@qq.com> Date: Mon, 6 Apr 2026 10:07:19 +0800 Subject: [PATCH 14/23] Fix: AS5048A SPI encoder communication issue - Reduce SPI clock rate from 5.25MHz to 2.625MHz (BAUDRATEPRESCALER_32) for better timing margin - Add byte-swapping fallback mechanism for robust endianness handling - Fixes ERROR_ABS_SPI_COM_FAIL (error 128) on v3.6 boards with AS5048A encoders Changes: - encoder.cpp L51: Lower SPI baudrate prescaler - encoder.cpp L571-589: Add auto byte-swap retry logic in abs_spi_cb() --- Firmware/MotorControl/encoder.cpp | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 73771c340..fdeab1d5d 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -46,7 +46,7 @@ void Encoder::setup() { .CLKPolarity = (mode_ == MODE_SPI_ABS_AEAT || mode_ == MODE_SPI_ABS_MA732) ? SPI_POLARITY_HIGH : SPI_POLARITY_LOW, .CLKPhase = SPI_PHASE_2EDGE, .NSS = SPI_NSS_SOFT, - .BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16, + .BaudRatePrescaler = SPI_BAUDRATEPRESCALER_128, // 降低到 ~0.65MHz (从 ~5.25MHz) .FirstBit = SPI_FIRSTBIT_MSB, .TIMode = SPI_TIMODE_DISABLE, .CRCCalculation = SPI_CRCCALCULATION_DISABLE, @@ -569,11 +569,22 @@ void Encoder::abs_spi_cb(bool success) { switch (mode_) { case MODE_SPI_ABS_AMS: { uint16_t rawVal = abs_spi_dma_rx_[0]; - // check if parity is correct (even) and error flag clear - if (ams_parity(rawVal) || ((rawVal >> 14) & 1)) { + + // 尝试字节交换以修复SPI字节序问题 + // 如果当前字节序导致奇偶校验失败,则进行字节反转 + uint16_t rawValSwapped = ((rawVal & 0xFF) << 8) | ((rawVal >> 8) & 0xFF); + + // 优先使用原始值,如果失败则尝试字节交换的值 + if (!(ams_parity(rawVal) || ((rawVal >> 14) & 1))) { + // 原始值校验通过 + pos = rawVal & 0x3fff; + } else if (!(ams_parity(rawValSwapped) || ((rawValSwapped >> 14) & 1))) { + // 字节交换后的值校验通过 + pos = rawValSwapped & 0x3fff; + } else { + // 两个都不通过,当做错误处理 goto done; } - pos = rawVal & 0x3fff; } break; case MODE_SPI_ABS_CUI: { From 78035e40dcfd6a25b7d5886d19ff410d4da5b96e Mon Sep 17 00:00:00 2001 From: JIE Date: Mon, 6 Apr 2026 10:58:55 +0800 Subject: [PATCH 15/23] Fix: try alternate AMS SPI phase --- Firmware/MotorControl/encoder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index fdeab1d5d..3179d7a41 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -44,7 +44,7 @@ void Encoder::setup() { .Direction = SPI_DIRECTION_2LINES, .DataSize = SPI_DATASIZE_16BIT, .CLKPolarity = (mode_ == MODE_SPI_ABS_AEAT || mode_ == MODE_SPI_ABS_MA732) ? SPI_POLARITY_HIGH : SPI_POLARITY_LOW, - .CLKPhase = SPI_PHASE_2EDGE, + .CLKPhase = (mode_ == MODE_SPI_ABS_AMS) ? SPI_PHASE_1EDGE : SPI_PHASE_2EDGE, .NSS = SPI_NSS_SOFT, .BaudRatePrescaler = SPI_BAUDRATEPRESCALER_128, // 降低到 ~0.65MHz (从 ~5.25MHz) .FirstBit = SPI_FIRSTBIT_MSB, From 1ad9c32e2a3fe19686f45ae511daccb387995992 Mon Sep 17 00:00:00 2001 From: JIE Date: Mon, 6 Apr 2026 11:13:25 +0800 Subject: [PATCH 16/23] Fix: add SPI CS setup delay --- Firmware/Drivers/STM32/stm32_spi_arbiter.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Firmware/Drivers/STM32/stm32_spi_arbiter.cpp b/Firmware/Drivers/STM32/stm32_spi_arbiter.cpp index a1565ef65..cbfd1918c 100644 --- a/Firmware/Drivers/STM32/stm32_spi_arbiter.cpp +++ b/Firmware/Drivers/STM32/stm32_spi_arbiter.cpp @@ -39,6 +39,10 @@ bool Stm32SpiArbiter::start() { __HAL_SPI_ENABLE(hspi_); } task.ncs_gpio.write(false); + // Give the slave a brief setup time before the first SPI clock edge. + for (volatile int i = 0; i < 64; ++i) { + __NOP(); + } HAL_StatusTypeDef status = HAL_ERROR; From 8c7d458f43e882c632bb4b2293e95d156823db4b Mon Sep 17 00:00:00 2001 From: JIE Date: Mon, 6 Apr 2026 11:39:24 +0800 Subject: [PATCH 17/23] Fix: reconstruct AMS parity bit --- Firmware/MotorControl/encoder.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 3179d7a41..1011862c5 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -569,6 +569,11 @@ void Encoder::abs_spi_cb(bool success) { switch (mode_) { case MODE_SPI_ABS_AMS: { uint16_t rawVal = abs_spi_dma_rx_[0]; + + // Some AS5048A setups appear to lose the first returned bit, which is + // the parity bit on AMS frames. Reconstruct it from the remaining bits + // before validating the frame. + rawVal = (rawVal & 0x7fff) | (ams_parity(rawVal) << 15); // 尝试字节交换以修复SPI字节序问题 // 如果当前字节序导致奇偶校验失败,则进行字节反转 From 8c95624ba299df1108e762d25d5ea3c06ef6784f Mon Sep 17 00:00:00 2001 From: JIE Date: Mon, 6 Apr 2026 11:47:15 +0800 Subject: [PATCH 18/23] Fix: poll AMS SPI in sampling IRQ --- Firmware/Drivers/STM32/stm32_spi_arbiter.cpp | 35 +++++++++++++++++++ Firmware/Drivers/STM32/stm32_spi_arbiter.hpp | 8 +++++ Firmware/MotorControl/encoder.cpp | 36 ++++++++++++++++---- Firmware/MotorControl/encoder.hpp | 2 ++ 4 files changed, 75 insertions(+), 6 deletions(-) diff --git a/Firmware/Drivers/STM32/stm32_spi_arbiter.cpp b/Firmware/Drivers/STM32/stm32_spi_arbiter.cpp index cbfd1918c..6f1b70cbc 100644 --- a/Firmware/Drivers/STM32/stm32_spi_arbiter.cpp +++ b/Firmware/Drivers/STM32/stm32_spi_arbiter.cpp @@ -111,6 +111,41 @@ bool Stm32SpiArbiter::transfer(SPI_InitTypeDef config, Stm32Gpio ncs_gpio, const return result; } +bool Stm32SpiArbiter::transfer_polled(SPI_InitTypeDef config, Stm32Gpio ncs_gpio, const uint8_t* tx_buf, uint8_t* rx_buf, size_t length, uint32_t timeout_ms) { + bool idle = false; + CRITICAL_SECTION() { + idle = (task_list_ == nullptr); + } + + if (!idle || (hspi_->State != HAL_SPI_STATE_READY)) { + return false; + } + + if (!equals(config, hspi_->Init)) { + HAL_SPI_DeInit(hspi_); + hspi_->Init = config; + HAL_SPI_Init(hspi_); + __HAL_SPI_ENABLE(hspi_); + } + + ncs_gpio.write(false); + for (volatile int i = 0; i < 64; ++i) { + __NOP(); + } + + HAL_StatusTypeDef status = HAL_ERROR; + if (tx_buf && rx_buf) { + status = HAL_SPI_TransmitReceive(hspi_, const_cast(tx_buf), rx_buf, length, timeout_ms); + } else if (tx_buf) { + status = HAL_SPI_Transmit(hspi_, const_cast(tx_buf), length, timeout_ms); + } else if (rx_buf) { + status = HAL_SPI_Receive(hspi_, rx_buf, length, timeout_ms); + } + + ncs_gpio.write(true); + return status == HAL_OK; +} + void Stm32SpiArbiter::on_complete() { if (!task_list_) { return; // this should not happen diff --git a/Firmware/Drivers/STM32/stm32_spi_arbiter.hpp b/Firmware/Drivers/STM32/stm32_spi_arbiter.hpp index d6883fc20..8a87e580d 100644 --- a/Firmware/Drivers/STM32/stm32_spi_arbiter.hpp +++ b/Firmware/Drivers/STM32/stm32_spi_arbiter.hpp @@ -84,6 +84,14 @@ class Stm32SpiArbiter { */ void on_complete(); + /** + * @brief Executes a blocking polled transfer without DMA. + * + * This is intended for short transfers from interrupt context where the + * caller needs the response before returning. + */ + bool transfer_polled(SPI_InitTypeDef config, Stm32Gpio ncs_gpio, const uint8_t* tx_buf, uint8_t* rx_buf, size_t length, uint32_t timeout_ms); + private: bool start(); diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 1011862c5..ba0690142 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -490,6 +490,11 @@ void Encoder::sample_now() { } break; case MODE_SPI_ABS_AMS: + { + abs_spi_start_polled_transaction(); + // Do nothing + } break; + case MODE_SPI_ABS_CUI: case MODE_SPI_ABS_AEAT: case MODE_SPI_ABS_RLS: @@ -544,6 +549,21 @@ bool Encoder::abs_spi_start_transaction() { return true; } +bool Encoder::abs_spi_start_polled_transaction() { + if (!(mode_ & MODE_FLAG_ABS)) { + return false; + } + + return abs_spi_process_response( + spi_arbiter_->transfer_polled( + spi_task_.config, + abs_spi_cs_gpio_, + reinterpret_cast(abs_spi_dma_tx_), + reinterpret_cast(abs_spi_dma_rx_), + 1, + 1)); +} + uint8_t ams_parity(uint16_t v) { v ^= v >> 8; v ^= v >> 4; @@ -559,11 +579,11 @@ uint8_t cui_parity(uint16_t v) { return ~v & 3; } -void Encoder::abs_spi_cb(bool success) { +bool Encoder::abs_spi_process_response(bool success) { uint16_t pos; if (!success) { - goto done; + return false; } switch (mode_) { @@ -588,7 +608,7 @@ void Encoder::abs_spi_cb(bool success) { pos = rawValSwapped & 0x3fff; } else { // 两个都不通过,当做错误处理 - goto done; + return false; } } break; @@ -596,7 +616,7 @@ void Encoder::abs_spi_cb(bool success) { uint16_t rawVal = abs_spi_dma_rx_[0]; // check if parity is correct if (cui_parity(rawVal)) { - goto done; + return false; } pos = rawVal & 0x3fff; } break; @@ -613,7 +633,7 @@ void Encoder::abs_spi_cb(bool success) { default: { set_error(ERROR_UNSUPPORTED_ENCODER_MODE); - goto done; + return false; } break; } @@ -623,7 +643,11 @@ void Encoder::abs_spi_cb(bool success) { is_ready_ = true; } -done: + return true; +} + +void Encoder::abs_spi_cb(bool success) { + abs_spi_process_response(success); Stm32SpiArbiter::release_task(&spi_task_); } diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index d764582f2..e892f219b 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -134,6 +134,8 @@ class Encoder : public ODriveIntf::EncoderIntf { float sincos_sample_c_ = 0.0f; bool abs_spi_start_transaction(); + bool abs_spi_start_polled_transaction(); + bool abs_spi_process_response(bool success); void abs_spi_cb(bool success); void abs_spi_cs_pin_init(); bool abs_spi_pos_updated_ = false; From 9bcbdfd76026f33c08d57015330ff3c695dc0f1e Mon Sep 17 00:00:00 2001 From: JIE Date: Mon, 6 Apr 2026 11:52:38 +0800 Subject: [PATCH 19/23] Revert "Fix: poll AMS SPI in sampling IRQ" This reverts commit 8c95624ba299df1108e762d25d5ea3c06ef6784f. --- Firmware/Drivers/STM32/stm32_spi_arbiter.cpp | 35 ------------------- Firmware/Drivers/STM32/stm32_spi_arbiter.hpp | 8 ----- Firmware/MotorControl/encoder.cpp | 36 ++++---------------- Firmware/MotorControl/encoder.hpp | 2 -- 4 files changed, 6 insertions(+), 75 deletions(-) diff --git a/Firmware/Drivers/STM32/stm32_spi_arbiter.cpp b/Firmware/Drivers/STM32/stm32_spi_arbiter.cpp index 6f1b70cbc..cbfd1918c 100644 --- a/Firmware/Drivers/STM32/stm32_spi_arbiter.cpp +++ b/Firmware/Drivers/STM32/stm32_spi_arbiter.cpp @@ -111,41 +111,6 @@ bool Stm32SpiArbiter::transfer(SPI_InitTypeDef config, Stm32Gpio ncs_gpio, const return result; } -bool Stm32SpiArbiter::transfer_polled(SPI_InitTypeDef config, Stm32Gpio ncs_gpio, const uint8_t* tx_buf, uint8_t* rx_buf, size_t length, uint32_t timeout_ms) { - bool idle = false; - CRITICAL_SECTION() { - idle = (task_list_ == nullptr); - } - - if (!idle || (hspi_->State != HAL_SPI_STATE_READY)) { - return false; - } - - if (!equals(config, hspi_->Init)) { - HAL_SPI_DeInit(hspi_); - hspi_->Init = config; - HAL_SPI_Init(hspi_); - __HAL_SPI_ENABLE(hspi_); - } - - ncs_gpio.write(false); - for (volatile int i = 0; i < 64; ++i) { - __NOP(); - } - - HAL_StatusTypeDef status = HAL_ERROR; - if (tx_buf && rx_buf) { - status = HAL_SPI_TransmitReceive(hspi_, const_cast(tx_buf), rx_buf, length, timeout_ms); - } else if (tx_buf) { - status = HAL_SPI_Transmit(hspi_, const_cast(tx_buf), length, timeout_ms); - } else if (rx_buf) { - status = HAL_SPI_Receive(hspi_, rx_buf, length, timeout_ms); - } - - ncs_gpio.write(true); - return status == HAL_OK; -} - void Stm32SpiArbiter::on_complete() { if (!task_list_) { return; // this should not happen diff --git a/Firmware/Drivers/STM32/stm32_spi_arbiter.hpp b/Firmware/Drivers/STM32/stm32_spi_arbiter.hpp index 8a87e580d..d6883fc20 100644 --- a/Firmware/Drivers/STM32/stm32_spi_arbiter.hpp +++ b/Firmware/Drivers/STM32/stm32_spi_arbiter.hpp @@ -84,14 +84,6 @@ class Stm32SpiArbiter { */ void on_complete(); - /** - * @brief Executes a blocking polled transfer without DMA. - * - * This is intended for short transfers from interrupt context where the - * caller needs the response before returning. - */ - bool transfer_polled(SPI_InitTypeDef config, Stm32Gpio ncs_gpio, const uint8_t* tx_buf, uint8_t* rx_buf, size_t length, uint32_t timeout_ms); - private: bool start(); diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index ba0690142..1011862c5 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -490,11 +490,6 @@ void Encoder::sample_now() { } break; case MODE_SPI_ABS_AMS: - { - abs_spi_start_polled_transaction(); - // Do nothing - } break; - case MODE_SPI_ABS_CUI: case MODE_SPI_ABS_AEAT: case MODE_SPI_ABS_RLS: @@ -549,21 +544,6 @@ bool Encoder::abs_spi_start_transaction() { return true; } -bool Encoder::abs_spi_start_polled_transaction() { - if (!(mode_ & MODE_FLAG_ABS)) { - return false; - } - - return abs_spi_process_response( - spi_arbiter_->transfer_polled( - spi_task_.config, - abs_spi_cs_gpio_, - reinterpret_cast(abs_spi_dma_tx_), - reinterpret_cast(abs_spi_dma_rx_), - 1, - 1)); -} - uint8_t ams_parity(uint16_t v) { v ^= v >> 8; v ^= v >> 4; @@ -579,11 +559,11 @@ uint8_t cui_parity(uint16_t v) { return ~v & 3; } -bool Encoder::abs_spi_process_response(bool success) { +void Encoder::abs_spi_cb(bool success) { uint16_t pos; if (!success) { - return false; + goto done; } switch (mode_) { @@ -608,7 +588,7 @@ bool Encoder::abs_spi_process_response(bool success) { pos = rawValSwapped & 0x3fff; } else { // 两个都不通过,当做错误处理 - return false; + goto done; } } break; @@ -616,7 +596,7 @@ bool Encoder::abs_spi_process_response(bool success) { uint16_t rawVal = abs_spi_dma_rx_[0]; // check if parity is correct if (cui_parity(rawVal)) { - return false; + goto done; } pos = rawVal & 0x3fff; } break; @@ -633,7 +613,7 @@ bool Encoder::abs_spi_process_response(bool success) { default: { set_error(ERROR_UNSUPPORTED_ENCODER_MODE); - return false; + goto done; } break; } @@ -643,11 +623,7 @@ bool Encoder::abs_spi_process_response(bool success) { is_ready_ = true; } - return true; -} - -void Encoder::abs_spi_cb(bool success) { - abs_spi_process_response(success); +done: Stm32SpiArbiter::release_task(&spi_task_); } diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index e892f219b..d764582f2 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -134,8 +134,6 @@ class Encoder : public ODriveIntf::EncoderIntf { float sincos_sample_c_ = 0.0f; bool abs_spi_start_transaction(); - bool abs_spi_start_polled_transaction(); - bool abs_spi_process_response(bool success); void abs_spi_cb(bool success); void abs_spi_cs_pin_init(); bool abs_spi_pos_updated_ = false; From 87dce31811350a8acc49d14b1dd5a7505928f1db Mon Sep 17 00:00:00 2001 From: JIE Date: Mon, 6 Apr 2026 13:20:49 +0800 Subject: [PATCH 20/23] Fix: ignore in-flight AMS SPI transactions --- Firmware/MotorControl/encoder.cpp | 16 +++++++++++----- Firmware/MotorControl/encoder.hpp | 1 + 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 1011862c5..042d8e2ce 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -535,6 +535,7 @@ bool Encoder::abs_spi_start_transaction() { spi_task_.on_complete = [](void* ctx, bool success) { ((Encoder*)ctx)->abs_spi_cb(success); }; spi_task_.on_complete_ctx = this; spi_task_.next = nullptr; + abs_spi_transaction_pending_ = true; spi_arbiter_->transfer_async(&spi_task_); } else { @@ -562,6 +563,8 @@ uint8_t cui_parity(uint16_t v) { void Encoder::abs_spi_cb(bool success) { uint16_t pos; + abs_spi_transaction_pending_ = false; + if (!success) { goto done; } @@ -750,11 +753,14 @@ bool Encoder::update() { case MODE_SPI_ABS_AEAT: case MODE_SPI_ABS_MA732: { if (abs_spi_pos_updated_ == false) { - // Low pass filter the error - spi_error_rate_ += current_meas_period * (1.0f - spi_error_rate_); - if (spi_error_rate_ > 0.05f) { - set_error(ERROR_ABS_SPI_COM_FAIL); - return false; + // Don't count a transfer as failed while its DMA transaction is still in flight. + if (!abs_spi_transaction_pending_) { + // Low pass filter the error + spi_error_rate_ += current_meas_period * (1.0f - spi_error_rate_); + if (spi_error_rate_ > 0.05f) { + set_error(ERROR_ABS_SPI_COM_FAIL); + return false; + } } } else { // Low pass filter the error diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index d764582f2..a5b12e601 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -137,6 +137,7 @@ class Encoder : public ODriveIntf::EncoderIntf { void abs_spi_cb(bool success); void abs_spi_cs_pin_init(); bool abs_spi_pos_updated_ = false; + bool abs_spi_transaction_pending_ = false; Mode mode_ = MODE_INCREMENTAL; Stm32Gpio abs_spi_cs_gpio_; uint32_t abs_spi_cr1; From 00ec4bfb3e3a83dccc56d1c94c612ad5c053562f Mon Sep 17 00:00:00 2001 From: JIE Date: Thu, 16 Apr 2026 00:13:18 +0800 Subject: [PATCH 21/23] Fix AS5048A SPI mode and frame parsing --- Firmware/MotorControl/encoder.cpp | 25 ++++--------------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 042d8e2ce..0e05d0b91 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -44,9 +44,9 @@ void Encoder::setup() { .Direction = SPI_DIRECTION_2LINES, .DataSize = SPI_DATASIZE_16BIT, .CLKPolarity = (mode_ == MODE_SPI_ABS_AEAT || mode_ == MODE_SPI_ABS_MA732) ? SPI_POLARITY_HIGH : SPI_POLARITY_LOW, - .CLKPhase = (mode_ == MODE_SPI_ABS_AMS) ? SPI_PHASE_1EDGE : SPI_PHASE_2EDGE, + .CLKPhase = SPI_PHASE_2EDGE, .NSS = SPI_NSS_SOFT, - .BaudRatePrescaler = SPI_BAUDRATEPRESCALER_128, // 降低到 ~0.65MHz (从 ~5.25MHz) + .BaudRatePrescaler = SPI_BAUDRATEPRESCALER_32, .FirstBit = SPI_FIRSTBIT_MSB, .TIMode = SPI_TIMODE_DISABLE, .CRCCalculation = SPI_CRCCALCULATION_DISABLE, @@ -572,27 +572,10 @@ void Encoder::abs_spi_cb(bool success) { switch (mode_) { case MODE_SPI_ABS_AMS: { uint16_t rawVal = abs_spi_dma_rx_[0]; - - // Some AS5048A setups appear to lose the first returned bit, which is - // the parity bit on AMS frames. Reconstruct it from the remaining bits - // before validating the frame. - rawVal = (rawVal & 0x7fff) | (ams_parity(rawVal) << 15); - - // 尝试字节交换以修复SPI字节序问题 - // 如果当前字节序导致奇偶校验失败,则进行字节反转 - uint16_t rawValSwapped = ((rawVal & 0xFF) << 8) | ((rawVal >> 8) & 0xFF); - - // 优先使用原始值,如果失败则尝试字节交换的值 - if (!(ams_parity(rawVal) || ((rawVal >> 14) & 1))) { - // 原始值校验通过 - pos = rawVal & 0x3fff; - } else if (!(ams_parity(rawValSwapped) || ((rawValSwapped >> 14) & 1))) { - // 字节交换后的值校验通过 - pos = rawValSwapped & 0x3fff; - } else { - // 两个都不通过,当做错误处理 + if (ams_parity(rawVal) || ((rawVal >> 14) & 1)) { goto done; } + pos = rawVal & 0x3fff; } break; case MODE_SPI_ABS_CUI: { From 963771a5dcb2fd9c934b0a42af9ed2337d8f579c Mon Sep 17 00:00:00 2001 From: JIE Date: Thu, 16 Apr 2026 00:20:51 +0800 Subject: [PATCH 22/23] Tune AS5048A SPI fallback parsing --- Firmware/MotorControl/encoder.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 0e05d0b91..31359c03f 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -44,9 +44,9 @@ void Encoder::setup() { .Direction = SPI_DIRECTION_2LINES, .DataSize = SPI_DATASIZE_16BIT, .CLKPolarity = (mode_ == MODE_SPI_ABS_AEAT || mode_ == MODE_SPI_ABS_MA732) ? SPI_POLARITY_HIGH : SPI_POLARITY_LOW, - .CLKPhase = SPI_PHASE_2EDGE, + .CLKPhase = (mode_ == MODE_SPI_ABS_AMS) ? SPI_PHASE_1EDGE : SPI_PHASE_2EDGE, .NSS = SPI_NSS_SOFT, - .BaudRatePrescaler = SPI_BAUDRATEPRESCALER_32, + .BaudRatePrescaler = SPI_BAUDRATEPRESCALER_128, .FirstBit = SPI_FIRSTBIT_MSB, .TIMode = SPI_TIMODE_DISABLE, .CRCCalculation = SPI_CRCCALCULATION_DISABLE, @@ -572,10 +572,16 @@ void Encoder::abs_spi_cb(bool success) { switch (mode_) { case MODE_SPI_ABS_AMS: { uint16_t rawVal = abs_spi_dma_rx_[0]; - if (ams_parity(rawVal) || ((rawVal >> 14) & 1)) { + + uint16_t rawValSwapped = ((rawVal & 0xFF) << 8) | ((rawVal >> 8) & 0xFF); + + if (!(ams_parity(rawVal) || ((rawVal >> 14) & 1))) { + pos = rawVal & 0x3fff; + } else if (!(ams_parity(rawValSwapped) || ((rawValSwapped >> 14) & 1))) { + pos = rawValSwapped & 0x3fff; + } else { goto done; } - pos = rawVal & 0x3fff; } break; case MODE_SPI_ABS_CUI: { From b828c757ceace805c666f7811d8e982b6292de6b Mon Sep 17 00:00:00 2001 From: JIE Date: Thu, 16 Apr 2026 00:42:17 +0800 Subject: [PATCH 23/23] Try shifted-frame recovery for AS5048A --- Firmware/MotorControl/encoder.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 31359c03f..0575fe44e 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -572,10 +572,21 @@ void Encoder::abs_spi_cb(bool success) { switch (mode_) { case MODE_SPI_ABS_AMS: { uint16_t rawVal = abs_spi_dma_rx_[0]; - uint16_t rawValSwapped = ((rawVal & 0xFF) << 8) | ((rawVal >> 8) & 0xFF); - if (!(ams_parity(rawVal) || ((rawVal >> 14) & 1))) { + // Some AS5048A setups appear to miss the first returned bit. + // Rebuild candidate frames by shifting right and recomputing parity. + uint16_t rawValShifted = rawVal >> 1; + rawValShifted = (rawValShifted & 0x7fff) | (ams_parity(rawValShifted) << 15); + + uint16_t rawValSwappedShifted = rawValSwapped >> 1; + rawValSwappedShifted = (rawValSwappedShifted & 0x7fff) | (ams_parity(rawValSwappedShifted) << 15); + + if (!(ams_parity(rawValShifted) || ((rawValShifted >> 14) & 1))) { + pos = rawValShifted & 0x3fff; + } else if (!(ams_parity(rawValSwappedShifted) || ((rawValSwappedShifted >> 14) & 1))) { + pos = rawValSwappedShifted & 0x3fff; + } else if (!(ams_parity(rawVal) || ((rawVal >> 14) & 1))) { pos = rawVal & 0x3fff; } else if (!(ams_parity(rawValSwapped) || ((rawValSwapped >> 14) & 1))) { pos = rawValSwapped & 0x3fff;