├── .gitattributes ├── resources ├── hx711F_EN.pdf ├── hx711_80sps_serialout.gif └── hx711_80sps_nogainchange.sr ├── .vscode ├── tasks.json ├── c_cpp_properties.json ├── launch.json └── settings.json ├── .github └── workflows │ ├── build_doxy.yml │ ├── generate_pio.yml │ └── cppcheck.yml ├── cppcheck_report.txt ├── LICENSE ├── include ├── common.h ├── hx711_multi_awaiter.pio.h ├── hx711_reader.pio.h ├── hx711_multi_reader.pio.h ├── hx711.h ├── util.h └── hx711_multi.h ├── tests ├── CMakeLists.txt └── main.c ├── src ├── common.c ├── hx711_multi_awaiter.pio ├── hx711_multi_reader.pio ├── hx711_reader.pio ├── util.c ├── hx711.c └── hx711_multi.c ├── pico_sdk_import.cmake ├── CMakeLists.txt ├── .gitignore └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /resources/hx711F_EN.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endail/hx711-pico-c/HEAD/resources/hx711F_EN.pdf -------------------------------------------------------------------------------- /resources/hx711_80sps_serialout.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endail/hx711-pico-c/HEAD/resources/hx711_80sps_serialout.gif -------------------------------------------------------------------------------- /resources/hx711_80sps_nogainchange.sr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/endail/hx711-pico-c/HEAD/resources/hx711_80sps_nogainchange.sr -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "CMake Build", 6 | "command": "${command:cmake.tasksBuildCommand}", 7 | "type": "shell", 8 | "group": "build", 9 | "problemMatcher": [] 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /.github/workflows/build_doxy.yml: -------------------------------------------------------------------------------- 1 | name: Doxygen GitHub Pages Deploy Action 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: DenverCoder1/doxygen-github-pages-action@v1.1.0 13 | with: 14 | github_token: ${{ secrets.GITHUB_TOKEN }} 15 | branch: gh-pages 16 | folder: docs/html 17 | config_file: Doxyfile 18 | -------------------------------------------------------------------------------- /cppcheck_report.txt: -------------------------------------------------------------------------------- 1 | tests/main.c:138:5: portability: %i in format string (no. 1) requires 'int' but the argument type is 'size_t {aka unsigned long}'. [invalidPrintfArgType_sint] 2 | PRINT_ARR(arr, hxmcfg.chips_len); 3 | ^ 4 | tests/main.c:142:9: portability: %i in format string (no. 1) requires 'int' but the argument type is 'size_t {aka unsigned long}'. [invalidPrintfArgType_sint] 5 | PRINT_ARR(arr, hxmcfg.chips_len); 6 | ^ 7 | tests/main.c:156:5: portability: %i in format string (no. 1) requires 'int' but the argument type is 'size_t {aka unsigned long}'. [invalidPrintfArgType_sint] 8 | PRINT_ARR(arr, hxmcfg.chips_len); 9 | ^ 10 | -------------------------------------------------------------------------------- /.github/workflows/generate_pio.yml: -------------------------------------------------------------------------------- 1 | name: Generate PIO header files 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | 7 | jobs: 8 | build: 9 | 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: write 13 | 14 | steps: 15 | 16 | - uses: actions/checkout@v3 17 | with: 18 | ref: ${{ github.ref }} 19 | 20 | - uses: endail/pioasm-action@v1 21 | with: 22 | files: src/*.pio 23 | outdir: include 24 | format: c-sdk 25 | 26 | - uses: stefanzweifel/git-auto-commit-action@v4 27 | with: 28 | commit_message: Assemble .pio files 29 | file_pattern: include/*.h 30 | -------------------------------------------------------------------------------- /.github/workflows/cppcheck.yml: -------------------------------------------------------------------------------- 1 | name: cppcheck-action 2 | 3 | on: [push, workflow_dispatch] 4 | 5 | jobs: 6 | build: 7 | name: cppcheck 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | with: 12 | ref: ${{ github.ref }} 13 | 14 | - name: cppcheck 15 | uses: deep5050/cppcheck-action@main 16 | with: 17 | enable: all 18 | inline_suppression: enable 19 | force_language: c 20 | other_options: --suppress=unusedFunction --suppress=missingInclude 21 | github_token: ${{ secrets.GITHUB_TOKEN }} 22 | 23 | - name: publish report 24 | uses: mikeal/publish-to-github-action@master 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | BRANCH_NAME: ${{ github.ref_name }} 28 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Pico", 5 | "includePath": [ 6 | "${workspaceFolder}/include/**", 7 | "${env:PICO_TOOLCHAIN_PATH}/lib/gcc/arm-none-eabi/**", 8 | "${env:PICO_SDK_PATH}/**" 9 | ], 10 | "defines": [ 11 | "_DEBUG", 12 | "UNICODE", 13 | "_UNICODE" 14 | ], 15 | "windowsSdkVersion": "10.0.22000.0", 16 | "compilerPath": "${env:PICO_TOOLCHAIN_PATH}/bin/arm-none-eabi-gcc.exe", 17 | "cppStandard": "c++11", 18 | "intelliSenseMode": "gcc-arm", 19 | "cStandard": "c11", 20 | "configurationProvider": "ms-vscode.cmake-tools" 21 | } 22 | ], 23 | "version": 4 24 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Debug file->elf", 6 | "cwd": "${workspaceFolder}", 7 | "executable": "${workspaceFolder}${pathSeparator}build${pathSeparator}${relativeFileDirname}${pathSeparator}${fileBasenameNoExtension}.elf", 8 | "request": "launch", 9 | "type": "cortex-debug", 10 | "servertype": "openocd", 11 | "device": "Pico2040", 12 | "showDevDebugOutput": "both", 13 | // "runToEntryPoint": "main", 14 | "preLaunchTask": "CMake Build", 15 | "configFiles": [ 16 | "interface${pathSeparator}picoprobe.cfg", 17 | "target${pathSeparator}rp2040.cfg" 18 | ], 19 | "searchDir": [ 20 | "${env:OPENOCD_PATH}${pathSeparator}tcl" 21 | ], 22 | "svdFile": "${env:PICO_SDK_PATH}${pathSeparator}src${pathSeparator}rp2040${pathSeparator}hardware_regs${pathSeparator}rp2040.svd" 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Daniel Robertson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cmake.statusbar.advanced": { 3 | "debug": { 4 | "visibility": "hidden" 5 | }, 6 | "launch": { 7 | "visibility": "hidden" 8 | }, 9 | "launchTarget": { 10 | "visibility": "hidden" 11 | } 12 | }, 13 | "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools", 14 | "cmake.buildBeforeRun": true, 15 | "cmake.configureOnOpen": true, 16 | "files.associations": { 17 | "stdio.h": "c", 18 | "hx711.h": "c", 19 | "cstdlib": "c", 20 | "stdint.h": "c", 21 | "cstdint": "c", 22 | "time.h": "c", 23 | "_timespec.h": "c", 24 | "_types.h": "c", 25 | "gpio.h": "c", 26 | "hx711_multi.h": "c", 27 | "array": "c", 28 | "deque": "c", 29 | "string": "c", 30 | "unordered_map": "c", 31 | "vector": "c", 32 | "string_view": "c", 33 | "initializer_list": "c", 34 | "util.h": "c", 35 | "random": "c", 36 | "limits": "c", 37 | "cstddef": "c", 38 | "mutex.h": "c", 39 | "platform.h": "c" 40 | }, 41 | "C_Cpp.intelliSenseEngineFallback": "enabled", 42 | "C_Cpp.default.intelliSenseMode": "gcc-arm", 43 | "C_Cpp.default.browse.limitSymbolsToIncludedHeaders": false, 44 | "C_Cpp.default.cppStandard": "", 45 | "C_Cpp.default.cStandard": "c11", 46 | "C_Cpp.default.mergeConfigurations": true 47 | } -------------------------------------------------------------------------------- /include/common.h: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2023 Daniel Robertson 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | #ifndef COMMON_H_D032FD58_DAFE_4AE1_8E48_54A388BFECE4 24 | #define COMMON_H_D032FD58_DAFE_4AE1_8E48_54A388BFECE4 25 | 26 | #include "hx711.h" 27 | #include "hx711_multi.h" 28 | 29 | #ifdef __cplusplus 30 | extern "C" { 31 | #endif 32 | 33 | extern const hx711_config_t HX711__DEFAULT_CONFIG; 34 | extern const hx711_multi_config_t HX711__MULTI_DEFAULT_CONFIG; 35 | 36 | void hx711_get_default_config(hx711_config_t* const cfg); 37 | void hx711_multi_get_default_config(hx711_multi_config_t* const cfg); 38 | 39 | #ifdef __cplusplus 40 | } 41 | #endif 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2022 Daniel Robertson 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | cmake_minimum_required(VERSION 3.12) 24 | 25 | add_compile_options( 26 | -Wall 27 | -Wextra 28 | -Werror 29 | -Wfatal-errors 30 | -Wfloat-equal 31 | -Wunreachable-code 32 | -Wno-unused-function 33 | -Wno-maybe-uninitialized 34 | -Wno-ignored-qualifiers 35 | -Wno-stringop-overflow # rp2040_usb.c:61:3 36 | -Wno-array-bounds # rp2040_usb.c:61:3 37 | ) 38 | 39 | add_executable(main 40 | ${CMAKE_CURRENT_LIST_DIR}/main.c 41 | ) 42 | 43 | target_link_libraries(main 44 | hx711-pico-c 45 | pico_stdlib 46 | pico_stdio 47 | ) 48 | 49 | pico_enable_stdio_usb(main 1) 50 | pico_enable_stdio_uart(main 1) 51 | pico_add_extra_outputs(main) 52 | -------------------------------------------------------------------------------- /src/common.c: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2023 Daniel Robertson 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | #include 24 | #include 25 | #include "hardware/pio.h" 26 | #include "../include/common.h" 27 | #include "../include/hx711.h" 28 | #include "../include/hx711_reader.pio.h" 29 | #include "../include/hx711_multi.h" 30 | #include "../include/hx711_multi_awaiter.pio.h" 31 | #include "../include/hx711_multi_reader.pio.h" 32 | 33 | const hx711_config_t HX711__DEFAULT_CONFIG = { 34 | .clock_pin = 0, 35 | .data_pin = 0, 36 | .pio = pio0, 37 | .pio_init = hx711_reader_pio_init, 38 | .reader_prog = &hx711_reader_program, 39 | .reader_prog_init = hx711_reader_program_init 40 | }; 41 | 42 | const hx711_multi_config_t HX711__MULTI_DEFAULT_CONFIG = { 43 | .clock_pin = 0, 44 | .data_pin_base = 0, 45 | .chips_len = 0, 46 | .pio_irq_index = HX711_MULTI_ASYNC_PIO_IRQ_IDX, 47 | .dma_irq_index = HX711_MULTI_ASYNC_DMA_IRQ_IDX, 48 | .pio = pio0, 49 | .pio_init = hx711_multi_pio_init, 50 | .awaiter_prog = &hx711_multi_awaiter_program, 51 | .awaiter_prog_init = hx711_multi_awaiter_program_init, 52 | .reader_prog = &hx711_multi_reader_program, 53 | .reader_prog_init = hx711_multi_reader_program_init 54 | }; 55 | 56 | void hx711_get_default_config(hx711_config_t* const cfg) { 57 | assert(cfg != NULL); 58 | *cfg = HX711__DEFAULT_CONFIG; 59 | } 60 | 61 | void hx711_multi_get_default_config(hx711_multi_config_t* const cfg) { 62 | assert(cfg != NULL); 63 | *cfg = HX711__MULTI_DEFAULT_CONFIG; 64 | } 65 | -------------------------------------------------------------------------------- /pico_sdk_import.cmake: -------------------------------------------------------------------------------- 1 | # This is a copy of /external/pico_sdk_import.cmake 2 | 3 | # This can be dropped into an external project to help locate this SDK 4 | # It should be include()ed prior to project() 5 | 6 | if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) 7 | set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) 8 | message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") 9 | endif () 10 | 11 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) 12 | set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) 13 | message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") 14 | endif () 15 | 16 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) 17 | set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) 18 | message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") 19 | endif () 20 | 21 | set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") 22 | set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") 23 | set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") 24 | 25 | if (NOT PICO_SDK_PATH) 26 | if (PICO_SDK_FETCH_FROM_GIT) 27 | include(FetchContent) 28 | set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) 29 | if (PICO_SDK_FETCH_FROM_GIT_PATH) 30 | get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") 31 | endif () 32 | # GIT_SUBMODULES_RECURSE was added in 3.17 33 | if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") 34 | FetchContent_Declare( 35 | pico_sdk 36 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk 37 | GIT_TAG master 38 | GIT_SUBMODULES_RECURSE FALSE 39 | ) 40 | else () 41 | FetchContent_Declare( 42 | pico_sdk 43 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk 44 | GIT_TAG master 45 | ) 46 | endif () 47 | 48 | if (NOT pico_sdk) 49 | message("Downloading Raspberry Pi Pico SDK") 50 | FetchContent_Populate(pico_sdk) 51 | set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) 52 | endif () 53 | set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) 54 | else () 55 | message(FATAL_ERROR 56 | "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." 57 | ) 58 | endif () 59 | endif () 60 | 61 | get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") 62 | if (NOT EXISTS ${PICO_SDK_PATH}) 63 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") 64 | endif () 65 | 66 | set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) 67 | if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) 68 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") 69 | endif () 70 | 71 | set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) 72 | 73 | include(${PICO_SDK_INIT_CMAKE_FILE}) 74 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2023 Daniel Robertson 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | cmake_minimum_required(VERSION 3.12) 24 | 25 | # this must go above project()! 26 | include(pico_sdk_import.cmake) 27 | 28 | project(hx711-pico-c 29 | VERSION 2.0.2 30 | DESCRIPTION "Implementation of HX711 use via RP2040's state machine" 31 | HOMEPAGE_URL "https://github.com/endail/hx711-pico-c" 32 | LANGUAGES C CXX ASM 33 | ) 34 | 35 | # only do this when this project is the one being built 36 | if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) 37 | 38 | set(CMAKE_C_STANDARD 11) 39 | set_property(GLOBAL PROPERTY USE_FOLDERS ON) 40 | 41 | include(CTest) 42 | 43 | find_package(Doxygen) 44 | 45 | if(Doxygen_FOUND) 46 | add_subdirectory(docs) 47 | else() 48 | message(STATUS "Doxygen not found, not building docs") 49 | endif() 50 | 51 | # init the pico sdk only when testing 52 | if(BUILD_TESTING) 53 | # this must go below project() 54 | pico_sdk_init() 55 | endif() 56 | 57 | endif() 58 | 59 | # check for minimum pico sdk version 60 | if(PICO_SDK_VERSION_STRING VERSION_LESS "1.5.0") 61 | message(FATAL_ERROR "hx711-pico-c requires Raspberry Pi Pico SDK version 1.5.0 (or later). Your version is ${PICO_SDK_VERSION_STRING}") 62 | endif() 63 | 64 | add_library(hx711-pico-c INTERFACE) 65 | 66 | #target_compile_definitions(hx711-pico-c INTERFACE 67 | # HX711_NO_MUTEX 68 | # ) 69 | 70 | target_link_libraries(hx711-pico-c INTERFACE 71 | hardware_clocks 72 | hardware_dma 73 | hardware_gpio 74 | hardware_irq 75 | hardware_pio 76 | hardware_timer 77 | pico_platform 78 | pico_sync 79 | pico_time 80 | ) 81 | 82 | target_sources(hx711-pico-c INTERFACE 83 | ${CMAKE_CURRENT_LIST_DIR}/src/hx711.c 84 | ${CMAKE_CURRENT_LIST_DIR}/src/hx711_multi.c 85 | ${CMAKE_CURRENT_LIST_DIR}/src/common.c 86 | ${CMAKE_CURRENT_LIST_DIR}/src/util.c 87 | ) 88 | 89 | # when running the tests in this project, build the main test exe 90 | # side effect is that no tests are run, but we don't care; just 91 | # want to build the test program 92 | if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND BUILD_TESTING) 93 | add_subdirectory(tests) 94 | endif() 95 | -------------------------------------------------------------------------------- /include/hx711_multi_awaiter.pio.h: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------- // 2 | // This file is autogenerated by pioasm; do not edit! // 3 | // -------------------------------------------------- // 4 | 5 | #pragma once 6 | 7 | #if !PICO_NO_HARDWARE 8 | #include "hardware/pio.h" 9 | #endif 10 | 11 | // ------------------- // 12 | // hx711_multi_awaiter // 13 | // ------------------- // 14 | 15 | #define hx711_multi_awaiter_wrap_target 0 16 | #define hx711_multi_awaiter_wrap 6 17 | 18 | #define hx711_multi_awaiter_offset_wait_in_pins_bit_count 0u 19 | 20 | static const uint16_t hx711_multi_awaiter_program_instructions[] = { 21 | // .wrap_target 22 | 0x4001, // 0: in pins, 1 23 | 0xa046, // 1: mov y, isr 24 | 0x8000, // 2: push noblock 25 | 0x0066, // 3: jmp !y, 6 26 | 0xc044, // 4: irq clear 4 27 | 0x0000, // 5: jmp 0 28 | 0xc004, // 6: irq nowait 4 29 | // .wrap 30 | }; 31 | 32 | #if !PICO_NO_HARDWARE 33 | static const struct pio_program hx711_multi_awaiter_program = { 34 | .instructions = hx711_multi_awaiter_program_instructions, 35 | .length = 7, 36 | .origin = -1, 37 | }; 38 | 39 | static inline pio_sm_config hx711_multi_awaiter_program_get_default_config(uint offset) { 40 | pio_sm_config c = pio_get_default_sm_config(); 41 | sm_config_set_wrap(&c, offset + hx711_multi_awaiter_wrap_target, offset + hx711_multi_awaiter_wrap); 42 | return c; 43 | } 44 | 45 | // MIT License 46 | // 47 | // Copyright (c) 2023 Daniel Robertson 48 | // 49 | // Permission is hereby granted, free of charge, to any person obtaining a copy 50 | // of this software and associated documentation files (the "Software"), to deal 51 | // in the Software without restriction, including without limitation the rights 52 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 53 | // copies of the Software, and to permit persons to whom the Software is 54 | // furnished to do so, subject to the following conditions: 55 | // 56 | // The above copyright notice and this permission notice shall be included in all 57 | // copies or substantial portions of the Software. 58 | // 59 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 60 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 61 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 62 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 63 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 64 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 65 | // SOFTWARE. 66 | #include 67 | #include 68 | #include "hardware/pio.h" 69 | #include "hardware/pio_instructions.h" 70 | #include "hx711_multi.h" 71 | void hx711_multi_awaiter_program_init(hx711_multi_t* const hxm) { 72 | assert(hxm != NULL); 73 | assert(hxm->_pio != NULL); 74 | assert(hxm->_chips_len > 0); 75 | pio_sm_config cfg = hx711_multi_awaiter_program_get_default_config( 76 | hxm->_awaiter_offset); 77 | //replace placeholder in instruction with the number of pins 78 | //to read 79 | hxm->_pio->instr_mem[hxm->_awaiter_offset + hx711_multi_awaiter_offset_wait_in_pins_bit_count] = 80 | pio_encode_in(pio_pins, hxm->_chips_len); 81 | //data pins 82 | pio_sm_set_in_pins( 83 | hxm->_pio, 84 | hxm->_awaiter_sm, 85 | hxm->_data_pin_base); 86 | pio_sm_set_consecutive_pindirs( 87 | hxm->_pio, 88 | hxm->_awaiter_sm, 89 | hxm->_data_pin_base, 90 | hxm->_chips_len, 91 | false); //false = output pins 92 | sm_config_set_in_pins( 93 | &cfg, 94 | hxm->_data_pin_base); 95 | //even though the program reads data into the ISR, 96 | //it does not push any data, so make sure autopushing 97 | //is disabled 98 | sm_config_set_in_shift( 99 | &cfg, 100 | false, //false = shift left 101 | false, //false = autopush disabled 102 | hxm->_chips_len); //autopush threshold 103 | pio_sm_clear_fifos( 104 | hxm->_pio, 105 | hxm->_awaiter_sm); 106 | hxm->_awaiter_default_config = cfg; 107 | } 108 | 109 | #endif 110 | 111 | -------------------------------------------------------------------------------- /tests/main.c: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2023 Daniel Robertson 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | #include 24 | #include 25 | #include "pico/stdio.h" 26 | #include "tusb.h" 27 | #include "../include/common.h" 28 | 29 | #define PRINT_ARR(arr, len) \ 30 | do { \ 31 | for(size_t i = 0; i < len; ++i) { \ 32 | printf("hx711_multi_t chip %i: %li\n", i, arr[i]); \ 33 | } \ 34 | } while(0) 35 | 36 | int main(void) { 37 | 38 | stdio_init_all(); 39 | 40 | while (!tud_cdc_connected()) { 41 | sleep_ms(1); 42 | } 43 | 44 | #if 1 45 | hx711_config_t hxcfg; 46 | hx711_get_default_config(&hxcfg); 47 | 48 | hxcfg.clock_pin = 14; 49 | hxcfg.data_pin = 15; 50 | 51 | hx711_t hx; 52 | 53 | // 1. Initialise 54 | hx711_init(&hx, &hxcfg); 55 | 56 | //2. Power up the hx711 and set gain on chip 57 | hx711_power_up(&hx, hx711_gain_128); 58 | 59 | //3. This step is optional. Only do this if you want to 60 | //change the gain AND save it to the HX711 chip 61 | // 62 | //hx711_set_gain(&hx, hx711_gain_64); 63 | //hx711_power_down(&hx); 64 | //hx711_wait_power_down(); 65 | //hx711_power_up(&hx, hx711_gain_64); 66 | 67 | // 4. Wait for readings to settle 68 | hx711_wait_settle(hx711_rate_80); 69 | 70 | // 5. Read values 71 | // You can now... 72 | 73 | // wait (block) until a value is obtained 74 | // cppcheck-suppress invalidPrintfArgType_sint 75 | printf("blocking value: %li\n", hx711_get_value(&hx)); 76 | 77 | // or use a timeout 78 | int32_t val; 79 | const uint timeout = 250000; //microseconds 80 | if(hx711_get_value_timeout(&hx, &val, timeout)) { 81 | // value was obtained within the timeout period, 82 | // in this case within 250 milliseconds 83 | // cppcheck-suppress invalidPrintfArgType_sint 84 | printf("timeout value: %li\n", val); 85 | } 86 | else { 87 | printf("value was not obtained within the timeout period\n"); 88 | } 89 | 90 | // or see if there's a value, but don't block if there isn't one ready 91 | if(hx711_get_value_noblock(&hx, &val)) { 92 | // cppcheck-suppress invalidPrintfArgType_sint 93 | printf("noblock value: %li\n", val); 94 | } 95 | else { 96 | printf("value was not present\n"); 97 | } 98 | 99 | //6. Stop communication with HX711 100 | hx711_close(&hx); 101 | 102 | printf("Closed communication with single HX711 chip\n"); 103 | #endif 104 | 105 | #if 1 106 | hx711_multi_config_t hxmcfg; 107 | hx711_multi_get_default_config(&hxmcfg); 108 | hxmcfg.clock_pin = 14; 109 | hxmcfg.data_pin_base = 15; 110 | hxmcfg.chips_len = 1; 111 | hxmcfg.pio_irq_index = 1; 112 | hxmcfg.dma_irq_index = 1; 113 | 114 | hx711_multi_t hxm; 115 | 116 | // 1. initialise 117 | hx711_multi_init(&hxm, &hxmcfg); 118 | 119 | // 2. Power up the HX711 chips and set gain on each chip 120 | hx711_multi_power_up(&hxm, hx711_gain_128); 121 | 122 | //3. This step is optional. Only do this if you want to 123 | //change the gain AND save it to each HX711 chip 124 | // 125 | //hx711_multi_set_gain(&hxm, hx711_gain_64); 126 | //hx711_multi_power_down(&hxm); 127 | //hx711_wait_power_down(); 128 | //hx711_multi_power_up(&hxm, hx711_gain_64); 129 | 130 | // 4. Wait for readings to settle 131 | hx711_wait_settle(hx711_rate_80); 132 | 133 | // 5. Read values 134 | int32_t arr[hxmcfg.chips_len]; 135 | 136 | // wait (block) until a values are read 137 | hx711_multi_get_values(&hxm, arr); 138 | PRINT_ARR(arr, hxmcfg.chips_len); 139 | 140 | // or use a timeout 141 | if(hx711_multi_get_values_timeout(&hxm, arr, 250000)) { 142 | PRINT_ARR(arr, hxmcfg.chips_len); 143 | } 144 | else { 145 | printf("Failed to obtain values within timeout\n"); 146 | } 147 | 148 | hx711_multi_async_start(&hxm); 149 | 150 | while(!hx711_multi_async_done(&hxm)) { 151 | // do other stuff... 152 | tight_loop_contents(); 153 | } 154 | 155 | hx711_multi_async_get_values(&hxm, arr); 156 | PRINT_ARR(arr, hxmcfg.chips_len); 157 | 158 | // 6. Stop communication with all HX711 chips 159 | hx711_multi_close(&hxm); 160 | 161 | printf("Closed communication with multiple HX711 chips\n"); 162 | #endif 163 | 164 | while(1); 165 | 166 | return EXIT_SUCCESS; 167 | 168 | } 169 | -------------------------------------------------------------------------------- /include/hx711_reader.pio.h: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------- // 2 | // This file is autogenerated by pioasm; do not edit! // 3 | // -------------------------------------------------- // 4 | 5 | #pragma once 6 | 7 | #if !PICO_NO_HARDWARE 8 | #include "hardware/pio.h" 9 | #endif 10 | 11 | // ------------ // 12 | // hx711_reader // 13 | // ------------ // 14 | 15 | #define hx711_reader_wrap_target 3 16 | #define hx711_reader_wrap 13 17 | 18 | #define hx711_reader_HZ 10000000 19 | 20 | static const uint16_t hx711_reader_program_instructions[] = { 21 | 0xe020, // 0: set x, 0 22 | 0x8080, // 1: pull noblock 23 | 0x6020, // 2: out x, 32 24 | // .wrap_target 25 | 0xe057, // 3: set y, 23 26 | 0x2020, // 4: wait 0 pin, 0 27 | 0xe001, // 5: set pins, 1 28 | 0x4001, // 6: in pins, 1 29 | 0x1185, // 7: jmp y--, 5 side 0 [1] 30 | 0x9880, // 8: pull noblock side 1 31 | 0x6020, // 9: out x, 32 32 | 0x1023, // 10: jmp !x, 3 side 0 33 | 0xa041, // 11: mov y, x 34 | 0xe101, // 12: set pins, 1 [1] 35 | 0x118c, // 13: jmp y--, 12 side 0 [1] 36 | // .wrap 37 | }; 38 | 39 | #if !PICO_NO_HARDWARE 40 | static const struct pio_program hx711_reader_program = { 41 | .instructions = hx711_reader_program_instructions, 42 | .length = 14, 43 | .origin = -1, 44 | }; 45 | 46 | static inline pio_sm_config hx711_reader_program_get_default_config(uint offset) { 47 | pio_sm_config c = pio_get_default_sm_config(); 48 | sm_config_set_wrap(&c, offset + hx711_reader_wrap_target, offset + hx711_reader_wrap); 49 | sm_config_set_sideset(&c, 2, true, false); 50 | return c; 51 | } 52 | 53 | // MIT License 54 | // 55 | // Copyright (c) 2023 Daniel Robertson 56 | // 57 | // Permission is hereby granted, free of charge, to any person obtaining a copy 58 | // of this software and associated documentation files (the "Software"), to deal 59 | // in the Software without restriction, including without limitation the rights 60 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 61 | // copies of the Software, and to permit persons to whom the Software is 62 | // furnished to do so, subject to the following conditions: 63 | // 64 | // The above copyright notice and this permission notice shall be included in all 65 | // copies or substantial portions of the Software. 66 | // 67 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 68 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 69 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 70 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 71 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 72 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 73 | // SOFTWARE. 74 | #include 75 | #include 76 | #include "hardware/clocks.h" 77 | #include "hardware/pio.h" 78 | #include "hardware/structs/clocks.h" 79 | #include "hx711.h" 80 | void hx711_reader_pio_init(hx711_t* const hx) { 81 | assert(hx != NULL); 82 | assert(hx->_pio != NULL); 83 | pio_gpio_init( 84 | hx->_pio, 85 | hx->_clock_pin); 86 | //clock pin setup 87 | pio_sm_set_out_pins( 88 | hx->_pio, 89 | hx->_reader_sm, 90 | hx->_clock_pin, 91 | 1); 92 | pio_sm_set_set_pins( 93 | hx->_pio, 94 | hx->_reader_sm, 95 | hx->_clock_pin, 96 | 1); 97 | pio_sm_set_consecutive_pindirs( 98 | hx->_pio, 99 | hx->_reader_sm, 100 | hx->_clock_pin, 101 | 1, 102 | true); 103 | //data pin setup 104 | pio_gpio_init( 105 | hx->_pio, 106 | hx->_data_pin); 107 | pio_sm_set_in_pins( 108 | hx->_pio, 109 | hx->_reader_sm, 110 | hx->_data_pin); 111 | pio_sm_set_consecutive_pindirs( 112 | hx->_pio, 113 | hx->_reader_sm, 114 | hx->_data_pin, 115 | 1, 116 | false); 117 | } 118 | void hx711_reader_program_init(hx711_t* const hx) { 119 | assert(hx != NULL); 120 | assert(hx->_pio != NULL); 121 | pio_sm_config cfg = hx711_reader_program_get_default_config( 122 | hx->_reader_offset); 123 | const float div = (float)(clock_get_hz(clk_sys)) / (uint)hx711_reader_HZ; 124 | sm_config_set_clkdiv( 125 | &cfg, 126 | div); 127 | sm_config_set_set_pins( 128 | &cfg, 129 | hx->_clock_pin, 130 | 1); 131 | sm_config_set_out_pins( 132 | &cfg, 133 | hx->_clock_pin, 134 | 1); 135 | sm_config_set_sideset_pins( 136 | &cfg, 137 | hx->_clock_pin); 138 | sm_config_set_in_pins( 139 | &cfg, 140 | hx->_data_pin); 141 | /** 142 | * Why enable autopush? 143 | * 144 | * "The state machine keeps an eye on the total amount of data shifted into the ISR, and on the in which reaches or 145 | * breaches a total shift count of 32 (or whatever number you have configured), the ISR contents, along with the new data 146 | * from the in. goes straight to the RX FIFO. The ISR is cleared to zero in the same operation." 147 | * - Raspberry Pi Pico C/C++ SDK pg. 45 148 | * 149 | * When manually pushing using noblock, the FIFO contents are NOT changed. 150 | * 151 | * "The PIO assembler sets the Block bit by default. If the Block bit is not set, the PUSH does not stall on a full RX FIFO, instead 152 | * continuing immediately to the next instruction. The FIFO state and contents are unchanged when this happens. The ISR 153 | * is still cleared to all-zeroes, and the FDEBUG_RXSTALL flag is set (the same as a blocking PUSH or autopush to a full RX FIFO) 154 | * to indicate data was lost." 155 | * - Raspberry Pi Pico C/C++ SDK pg. 64 156 | * 157 | * Manually pushing is not ideal. Application code should be able to look at the FIFO and assume 158 | * that the value inside is the most up-to-date data available. Autopushing does this. 159 | */ 160 | sm_config_set_in_shift( 161 | &cfg, 162 | false, //false = shift in left 163 | true, //true = autopush enabled 164 | HX711_READ_BITS); //autopush on 24 bits 165 | pio_sm_clear_fifos( 166 | hx->_pio, 167 | hx->_reader_sm); 168 | //store a copy of the configuration for resetting the sm 169 | hx->_reader_prog_default_config = cfg; 170 | } 171 | 172 | #endif 173 | 174 | -------------------------------------------------------------------------------- /include/hx711_multi_reader.pio.h: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------- // 2 | // This file is autogenerated by pioasm; do not edit! // 3 | // -------------------------------------------------- // 4 | 5 | #pragma once 6 | 7 | #if !PICO_NO_HARDWARE 8 | #include "hardware/pio.h" 9 | #endif 10 | 11 | // ------------------ // 12 | // hx711_multi_reader // 13 | // ------------------ // 14 | 15 | #define hx711_multi_reader_wrap_target 3 16 | #define hx711_multi_reader_wrap 18 17 | 18 | #define hx711_multi_reader_HZ 10000000 19 | 20 | #define hx711_multi_reader_offset_bitloop_in_pins_bit_count 9u 21 | 22 | static const uint16_t hx711_multi_reader_program_instructions[] = { 23 | 0xe020, // 0: set x, 0 24 | 0x8080, // 1: pull noblock 25 | 0x6020, // 2: out x, 32 26 | // .wrap_target 27 | 0xe057, // 3: set y, 23 28 | 0x4060, // 4: in null, 32 29 | 0x8000, // 5: push noblock 30 | 0x20c4, // 6: wait 1 irq, 4 31 | 0xc040, // 7: irq clear 0 32 | 0xe001, // 8: set pins, 1 33 | 0x4001, // 9: in pins, 1 34 | 0x8000, // 10: push noblock 35 | 0x1088, // 11: jmp y--, 8 side 0 36 | 0xc000, // 12: irq nowait 0 37 | 0x9880, // 13: pull noblock side 1 38 | 0x6020, // 14: out x, 32 39 | 0x1023, // 15: jmp !x, 3 side 0 40 | 0xa041, // 16: mov y, x 41 | 0xe101, // 17: set pins, 1 [1] 42 | 0x1191, // 18: jmp y--, 17 side 0 [1] 43 | // .wrap 44 | }; 45 | 46 | #if !PICO_NO_HARDWARE 47 | static const struct pio_program hx711_multi_reader_program = { 48 | .instructions = hx711_multi_reader_program_instructions, 49 | .length = 19, 50 | .origin = -1, 51 | }; 52 | 53 | static inline pio_sm_config hx711_multi_reader_program_get_default_config(uint offset) { 54 | pio_sm_config c = pio_get_default_sm_config(); 55 | sm_config_set_wrap(&c, offset + hx711_multi_reader_wrap_target, offset + hx711_multi_reader_wrap); 56 | sm_config_set_sideset(&c, 2, true, false); 57 | return c; 58 | } 59 | 60 | // MIT License 61 | // 62 | // Copyright (c) 2023 Daniel Robertson 63 | // 64 | // Permission is hereby granted, free of charge, to any person obtaining a copy 65 | // of this software and associated documentation files (the "Software"), to deal 66 | // in the Software without restriction, including without limitation the rights 67 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 68 | // copies of the Software, and to permit persons to whom the Software is 69 | // furnished to do so, subject to the following conditions: 70 | // 71 | // The above copyright notice and this permission notice shall be included in all 72 | // copies or substantial portions of the Software. 73 | // 74 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 75 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 76 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 77 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 78 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 79 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 80 | // SOFTWARE. 81 | #include 82 | #include 83 | #include "hardware/clocks.h" 84 | #include "hardware/pio.h" 85 | #include "hardware/pio_instructions.h" 86 | #include "hardware/structs/clocks.h" 87 | #include "hx711_multi.h" 88 | #include "util.h" 89 | void hx711_multi_pio_init(hx711_multi_t* const hxm) { 90 | assert(hxm != NULL); 91 | assert(hxm->_pio != NULL); 92 | assert(hxm->_chips_len > 0); 93 | pio_gpio_init( 94 | hxm->_pio, 95 | hxm->_clock_pin); 96 | util_pio_gpio_contiguous_init( 97 | hxm->_pio, 98 | hxm->_data_pin_base, 99 | hxm->_chips_len); 100 | // make sure conversion done is valid and routable 101 | assert(util_routable_pio_interrupt_num_is_valid( 102 | HX711_MULTI_CONVERSION_DONE_IRQ_NUM)); 103 | pio_interrupt_clear( 104 | hxm->_pio, 105 | HX711_MULTI_CONVERSION_DONE_IRQ_NUM); 106 | // make sure data ready is valid and not routable 107 | // although this is not strictly necessary 108 | assert(util_pio_interrupt_num_is_valid( 109 | HX711_MULTI_DATA_READY_IRQ_NUM)); 110 | assert(!util_routable_pio_interrupt_num_is_valid( 111 | HX711_MULTI_DATA_READY_IRQ_NUM)); 112 | pio_interrupt_clear( 113 | hxm->_pio, 114 | HX711_MULTI_DATA_READY_IRQ_NUM); 115 | } 116 | void hx711_multi_reader_program_init(hx711_multi_t* const hxm) { 117 | assert(hxm != NULL); 118 | assert(hxm->_pio != NULL); 119 | hxm->_pio->instr_mem[hxm->_reader_offset + hx711_multi_reader_offset_bitloop_in_pins_bit_count] = 120 | pio_encode_in(pio_pins, hxm->_chips_len); 121 | pio_sm_config cfg = hx711_multi_reader_program_get_default_config( 122 | hxm->_reader_offset); 123 | const float div = (float)(clock_get_hz(clk_sys)) / (uint)hx711_multi_reader_HZ; 124 | sm_config_set_clkdiv( 125 | &cfg, 126 | div); 127 | //clock pin setup 128 | pio_sm_set_out_pins( 129 | hxm->_pio, 130 | hxm->_reader_sm, 131 | hxm->_clock_pin, 132 | 1); 133 | pio_sm_set_set_pins( 134 | hxm->_pio, 135 | hxm->_reader_sm, 136 | hxm->_clock_pin, 137 | 1); 138 | pio_sm_set_consecutive_pindirs( 139 | hxm->_pio, 140 | hxm->_reader_sm, 141 | hxm->_clock_pin, 142 | 1, 143 | true); 144 | sm_config_set_set_pins( 145 | &cfg, 146 | hxm->_clock_pin, 147 | 1); 148 | sm_config_set_out_pins( 149 | &cfg, 150 | hxm->_clock_pin, 151 | 1); 152 | sm_config_set_sideset_pins( 153 | &cfg, 154 | hxm->_clock_pin); 155 | //data pins 156 | pio_sm_set_in_pins( 157 | hxm->_pio, 158 | hxm->_reader_sm, 159 | hxm->_data_pin_base); 160 | pio_sm_set_consecutive_pindirs( 161 | hxm->_pio, 162 | hxm->_reader_sm, 163 | hxm->_data_pin_base, 164 | hxm->_chips_len, 165 | false); //false = input 166 | sm_config_set_in_pins( 167 | &cfg, 168 | hxm->_data_pin_base); 169 | sm_config_set_in_shift( 170 | &cfg, 171 | false, //false = shift in left 172 | false, //false = autopush disabled 173 | hxm->_chips_len); 174 | pio_sm_clear_fifos( 175 | hxm->_pio, 176 | hxm->_reader_sm); 177 | hxm->_reader_default_config = cfg; 178 | } 179 | 180 | #endif 181 | 182 | -------------------------------------------------------------------------------- /src/hx711_multi_awaiter.pio: -------------------------------------------------------------------------------- 1 | ; MIT License 2 | ; 3 | ; Copyright (c) 2023 Daniel Robertson 4 | ; 5 | ; Permission is hereby granted, free of charge, to any person obtaining a copy 6 | ; of this software and associated documentation files (the "Software"), to deal 7 | ; in the Software without restriction, including without limitation the rights 8 | ; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | ; copies of the Software, and to permit persons to whom the Software is 10 | ; furnished to do so, subject to the following conditions: 11 | ; 12 | ; The above copyright notice and this permission notice shall be included in all 13 | ; copies or substantial portions of the Software. 14 | ; 15 | ; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | ; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | ; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | ; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | ; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | ; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | ; SOFTWARE. 22 | 23 | ; This program constantly reads the input value of each configured 24 | ; data pin simultaneously. If and when all data pins are low, a PIO 25 | ; IRQ is set. If any of the data pins are high, the PIO IRQ is 26 | ; cleared. 27 | ; 28 | ; The program works by reading n number of pins into the ISR as a 29 | ; set of bits. The program, as written below, cannot know how many 30 | ; data pins have been configured beforehand. So the `in pins` 31 | ; instruction is given a placeholder value to be replaced with the 32 | ; number of pins to read when the state machine is configured. 33 | ; 34 | ; It is assumed the data pins could be low at any moment, so the 35 | ; state machine is left unconfigured as to its speed to run at 36 | ; its fastest. It is not dependent on the clock speed of the HX711 37 | ; chip because this program does not clock-in any data. However, 38 | ; it may be possible to set the speed to some predefined minimum 39 | ; for any possible benefits (eg. reduced power use). 40 | 41 | .program hx711_multi_awaiter 42 | 43 | .define DATA_READY_IRQ_NUM 4 ; IRQ is set when all data pins become low. 44 | ; If any data pins are high, the IRQ is 45 | ; cleared. 46 | 47 | .define LOW 0 48 | .define HIGH 1 49 | 50 | .define PLACEHOLDER_IN 1 51 | 52 | .wrap_target 53 | wrap_target: 54 | 55 | PUBLIC wait_in_pins_bit_count: ; Public label is set to modify the following `in` 56 | ; instruction in the state machine init function. 57 | in pins, PLACEHOLDER_IN ; Read in a bitmask of the value of each data pin 58 | ; into the ISR. 59 | 60 | mov y, isr ; Copy the ISR value into the y register to be 61 | ; able to test a jmp condition, as it is not 62 | ; possible to do so directly on the ISR. 63 | 64 | push noblock ; Push pinbits out. 65 | 66 | jmp !y signal_low ; If all the pins are low, y will be 0. The `!y` 67 | ; condition tests for y == 0. If any of the data 68 | ; pins are high, y will be non-zero and execution 69 | ; will fall through. 70 | 71 | irq clear DATA_READY_IRQ_NUM ; Clear the data readiness IRQ to indicate that 72 | ; data is not or no longer ready on all HX711 73 | ; chips. 74 | 75 | jmp wrap_target ; Go back and test the pin values again. 76 | 77 | signal_low: 78 | irq set DATA_READY_IRQ_NUM ; Set the data readiness IRQ to indicate that 79 | ; data is now ready to be obtained from all 80 | ; HX711 chips. 81 | 82 | .wrap ; Go back and test the pin values again. 83 | 84 | % c-sdk { 85 | // MIT License 86 | // 87 | // Copyright (c) 2023 Daniel Robertson 88 | // 89 | // Permission is hereby granted, free of charge, to any person obtaining a copy 90 | // of this software and associated documentation files (the "Software"), to deal 91 | // in the Software without restriction, including without limitation the rights 92 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 93 | // copies of the Software, and to permit persons to whom the Software is 94 | // furnished to do so, subject to the following conditions: 95 | // 96 | // The above copyright notice and this permission notice shall be included in all 97 | // copies or substantial portions of the Software. 98 | // 99 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 100 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 101 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 102 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 103 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 104 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 105 | // SOFTWARE. 106 | 107 | #include 108 | #include 109 | #include "hardware/pio.h" 110 | #include "hardware/pio_instructions.h" 111 | #include "hx711_multi.h" 112 | 113 | void hx711_multi_awaiter_program_init(hx711_multi_t* const hxm) { 114 | 115 | assert(hxm != NULL); 116 | assert(hxm->_pio != NULL); 117 | assert(hxm->_chips_len > 0); 118 | 119 | pio_sm_config cfg = hx711_multi_awaiter_program_get_default_config( 120 | hxm->_awaiter_offset); 121 | 122 | //replace placeholder in instruction with the number of pins 123 | //to read 124 | hxm->_pio->instr_mem[hxm->_awaiter_offset + hx711_multi_awaiter_offset_wait_in_pins_bit_count] = 125 | pio_encode_in(pio_pins, hxm->_chips_len); 126 | 127 | //data pins 128 | pio_sm_set_in_pins( 129 | hxm->_pio, 130 | hxm->_awaiter_sm, 131 | hxm->_data_pin_base); 132 | 133 | pio_sm_set_consecutive_pindirs( 134 | hxm->_pio, 135 | hxm->_awaiter_sm, 136 | hxm->_data_pin_base, 137 | hxm->_chips_len, 138 | false); //false = output pins 139 | 140 | sm_config_set_in_pins( 141 | &cfg, 142 | hxm->_data_pin_base); 143 | 144 | //even though the program reads data into the ISR, 145 | //it does not push any data, so make sure autopushing 146 | //is disabled 147 | sm_config_set_in_shift( 148 | &cfg, 149 | false, //false = shift left 150 | false, //false = autopush disabled 151 | hxm->_chips_len); //autopush threshold 152 | 153 | pio_sm_clear_fifos( 154 | hxm->_pio, 155 | hxm->_awaiter_sm); 156 | 157 | hxm->_awaiter_default_config = cfg; 158 | 159 | } 160 | 161 | %} 162 | -------------------------------------------------------------------------------- /include/hx711.h: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2023 Daniel Robertson 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | #ifndef HX711_H_0ED0E077_8980_484C_BB94_AF52973CDC09 24 | #define HX711_H_0ED0E077_8980_484C_BB94_AF52973CDC09 25 | 26 | #include 27 | #include 28 | #include "hardware/pio.h" 29 | #include "pico/mutex.h" 30 | 31 | #ifdef __cplusplus 32 | extern "C" { 33 | #endif 34 | 35 | #ifndef HX711_NO_MUTEX 36 | #define HX711_MUTEX_BLOCK(mut, ...) \ 37 | do { \ 38 | mutex_enter_blocking(&mut); \ 39 | __VA_ARGS__ \ 40 | mutex_exit(&mut); \ 41 | } while(0) 42 | #else 43 | #define HX711_MUTEX_BLOCK(mut, ...) \ 44 | do { \ 45 | __VA_ARGS__ \ 46 | } while(0) 47 | #endif 48 | 49 | #define HX711_READ_BITS UINT8_C(24) 50 | #define HX711_POWER_DOWN_TIMEOUT UINT8_C(60) //microseconds 51 | 52 | #define HX711_MIN_VALUE INT32_C(-0x800000) //−8,388,608 53 | #define HX711_MAX_VALUE INT32_C(0x7fffff) //8,388,607 54 | 55 | #define HX711_PIO_MIN_GAIN UINT8_C(0) 56 | #define HX711_PIO_MAX_GAIN UINT8_C(2) 57 | 58 | extern const unsigned short HX711_SETTLING_TIMES[3]; //milliseconds 59 | extern const unsigned char HX711_SAMPLE_RATES[2]; 60 | extern const unsigned char HX711_CLOCK_PULSES[3]; 61 | 62 | typedef enum { 63 | hx711_rate_10 = 0, 64 | hx711_rate_80 65 | } hx711_rate_t; 66 | 67 | typedef enum { 68 | hx711_gain_128 = 0, 69 | hx711_gain_32, 70 | hx711_gain_64 71 | } hx711_gain_t; 72 | 73 | typedef struct { 74 | 75 | uint _clock_pin; 76 | uint _data_pin; 77 | 78 | PIO _pio; 79 | const pio_program_t* _reader_prog; 80 | pio_sm_config _reader_prog_default_config; 81 | uint _reader_sm; 82 | uint _reader_offset; 83 | 84 | #ifndef HX711_NO_MUTEX 85 | mutex_t _mut; 86 | #endif 87 | 88 | } hx711_t; 89 | 90 | typedef void (*hx711_pio_init_t)(hx711_t* const); 91 | typedef void (*hx711_program_init_t)(hx711_t* const); 92 | 93 | typedef struct { 94 | 95 | uint clock_pin; 96 | uint data_pin; 97 | 98 | PIO pio; 99 | hx711_pio_init_t pio_init; 100 | 101 | const pio_program_t* reader_prog; 102 | hx711_program_init_t reader_prog_init; 103 | 104 | } hx711_config_t; 105 | 106 | void hx711_init( 107 | hx711_t* const hx, 108 | const hx711_config_t* const config); 109 | 110 | /** 111 | * @brief Stop communication with the HX711. 112 | * 113 | * @param hx 114 | */ 115 | void hx711_close(hx711_t* const hx); 116 | 117 | /** 118 | * @brief Sets the HX711 gain. 119 | * 120 | * @param hx 121 | * @param gain 122 | */ 123 | void hx711_set_gain( 124 | hx711_t* const hx, 125 | const hx711_gain_t gain); 126 | 127 | /** 128 | * @brief Convert a raw value from the HX711 to a 32-bit signed int. 129 | * 130 | * @param raw 131 | * @return int32_t 132 | */ 133 | int32_t hx711_get_twos_comp(const uint32_t raw); 134 | 135 | /** 136 | * @brief Returns true if the HX711 is saturated at its 137 | * minimum level. 138 | * 139 | * @param val 140 | * @return true 141 | * @return false 142 | */ 143 | bool hx711_is_min_saturated(const int32_t val); 144 | 145 | /** 146 | * @brief Returns true if the HX711 is saturated at its 147 | * maximum level. 148 | * 149 | * @param val 150 | * @return true 151 | * @return false 152 | */ 153 | bool hx711_is_max_saturated(const int32_t val); 154 | 155 | /** 156 | * @brief Returns the number of milliseconds to wait according 157 | * to the given HX711 sample rate to allow readings to settle. 158 | * 159 | * @param rate 160 | * @return unsigned short 161 | */ 162 | unsigned short hx711_get_settling_time(const hx711_rate_t rate); 163 | 164 | /** 165 | * @brief Returns the numeric sample rate of the given rate. 166 | * 167 | * @param rate 168 | * @return unsigned char 169 | */ 170 | unsigned char hx711_get_rate_sps(const hx711_rate_t rate); 171 | 172 | /** 173 | * @brief Returns the clock pulse count for a given gain value. 174 | * 175 | * @param gain 176 | * @return unsigned char 177 | */ 178 | unsigned char hx711_get_clock_pulses(const hx711_gain_t gain); 179 | 180 | /** 181 | * @brief Obtains a value from the HX711. Blocks until a value 182 | * is available. 183 | * 184 | * @param hx 185 | * @return int32_t 186 | */ 187 | int32_t hx711_get_value(hx711_t* const hx); 188 | 189 | /** 190 | * @brief Obtains a value from the HX711. Blocks until a value 191 | * is available or the timeout is reached. 192 | * 193 | * @param hx 194 | * @param val pointer to the value 195 | * @param timeout maximum time to wait for a value in microseconds 196 | * @return true if a value was obtained within the timeout 197 | * @return false if a timeout was reached 198 | */ 199 | bool hx711_get_value_timeout( 200 | hx711_t* const hx, 201 | int32_t* const val, 202 | const uint timeout); 203 | 204 | /** 205 | * @brief Obtains a value from the HX711. Returns immediately if 206 | * no value is available. 207 | * 208 | * @param hx 209 | * @param val pointer to the value 210 | * @return true if a value was available and val is set 211 | * @return false if a value was not available 212 | */ 213 | bool hx711_get_value_noblock( 214 | hx711_t* const hx, 215 | int32_t* const val); 216 | 217 | /** 218 | * @brief Check whether the hx struct has been initalised. 219 | * 220 | * @param hx 221 | * @return true 222 | * @return false 223 | */ 224 | static bool hx711__is_initd(hx711_t* const hx); 225 | 226 | /** 227 | * @brief Check whether the hx struct's state machines are 228 | * running. 229 | * 230 | * @param hx 231 | * @return true 232 | * @return false 233 | */ 234 | static bool hx711__is_state_machine_enabled(hx711_t* const hx); 235 | 236 | /** 237 | * @brief Check whether the given value is valid for a HX711 238 | * implementation. 239 | * 240 | * @param v 241 | * @return true 242 | * @return false 243 | */ 244 | bool hx711_is_value_valid(const int32_t v); 245 | 246 | /** 247 | * @brief Check whether a given value is permitted to be 248 | * transmitted to a PIO State Machine to set a HX711's gain 249 | * according to the PIO program implementation. 250 | * 251 | * @param g 252 | * @return true 253 | * @return false 254 | */ 255 | bool hx711_is_pio_gain_valid(const uint32_t g); 256 | 257 | /** 258 | * @brief Check whether the given rate is within the range 259 | * of the predefined rates. 260 | * 261 | * @param r 262 | * @return true 263 | * @return false 264 | */ 265 | bool hx711_is_rate_valid(const hx711_rate_t r); 266 | 267 | /** 268 | * @brief Check whether the given gain is within the range 269 | * of the predefined gains. 270 | * 271 | * @param g 272 | * @return true 273 | * @return false 274 | */ 275 | bool hx711_is_gain_valid(const hx711_gain_t g); 276 | 277 | /** 278 | * @brief Power up the HX711 and start the internal read/write 279 | * functionality. 280 | * 281 | * @related hx711_wait_settle 282 | * @param hx 283 | * @param gain hx711_gain_t initial gain 284 | */ 285 | void hx711_power_up( 286 | hx711_t* const hx, 287 | const hx711_gain_t gain); 288 | 289 | /** 290 | * @brief Power down the HX711 and stop the internal read/write 291 | * functionality 292 | * 293 | * @related hx711_wait_power_down() 294 | * @param hx 295 | */ 296 | void hx711_power_down(hx711_t* const hx); 297 | 298 | /** 299 | * @brief Convenience function for sleeping for the 300 | * appropriate amount of time according to the given sample 301 | * rate to allow readings to settle. 302 | * 303 | * @param rate 304 | */ 305 | void hx711_wait_settle(const hx711_rate_t rate); 306 | 307 | /** 308 | * @brief Convenience function for sleeping for the 309 | * appropriate amount of time to allow the HX711 to power 310 | * down. 311 | */ 312 | void hx711_wait_power_down(); 313 | 314 | /** 315 | * @brief Convert a hx711_gain_t to a numeric value appropriate 316 | * for a PIO State Machine. 317 | * 318 | * @param gain 319 | * @return uint32_t 320 | */ 321 | uint32_t hx711_gain_to_pio_gain(const hx711_gain_t gain); 322 | 323 | /** 324 | * @brief Attempts to obtain a value from the PIO RX FIFO if one is available. 325 | * 326 | * @param pio pointer to PIO 327 | * @param sm state machine 328 | * @param val pointer to raw value from HX711 to set 329 | * @return true if value was obtained 330 | * @return false if value was not obtained 331 | */ 332 | static bool hx711__try_get_value( 333 | PIO const pio, 334 | const uint sm, 335 | uint32_t* const val); 336 | 337 | #ifdef __cplusplus 338 | } 339 | #endif 340 | 341 | #endif 342 | -------------------------------------------------------------------------------- /src/hx711_multi_reader.pio: -------------------------------------------------------------------------------- 1 | ; MIT License 2 | ; 3 | ; Copyright (c) 2023 Daniel Robertson 4 | ; 5 | ; Permission is hereby granted, free of charge, to any person obtaining a copy 6 | ; of this software and associated documentation files (the "Software"), to deal 7 | ; in the Software without restriction, including without limitation the rights 8 | ; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | ; copies of the Software, and to permit persons to whom the Software is 10 | ; furnished to do so, subject to the following conditions: 11 | ; 12 | ; The above copyright notice and this permission notice shall be included in all 13 | ; copies or substantial portions of the Software. 14 | ; 15 | ; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | ; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | ; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | ; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | ; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | ; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | ; SOFTWARE. 22 | 23 | ; This program functions similarly to the single HX711 reader program. However, 24 | ; it has one important difference. Because the state machine has a limited 25 | ; input buffer, it cannot store a full raw HX711 value for each possible chip. 26 | ; It must also be able to be able to read the input values of each data pin 27 | ; simultaneously and do so 24 times for each bit. The state machine must 28 | ; therefore output the pin values for each chip it reads. 29 | ; 30 | ; The program reads each pin as a bitmask. Each bit is the current state of 31 | ; each input pin - whether it is high or low. That bitmask is pushed out of 32 | ; the state machine as a 32 bit unsigned integer where the 0th bit (the 33 | ; least significant bit) is the state of the HX711's data pin connected to 34 | ; the base data pin, the 1th bit is the second HX711, the 2th bit is the 35 | ; third HX711, and so on. This occurs 24 times. Once for each bit in a raw, 36 | ; two's complement HX711 value. 37 | ; 38 | ; After the 24 bits have been pushed out of the state machine, effectively 39 | ; a 2D array of bits has been created. Each row represents the HX711 bit 40 | ; number, and each column represents the all the bits for an individual 41 | ; chip. For example: 42 | ; 43 | ; [ 0, 1, 1, ... 0 ] This is the MSB set of bits for each HX711. 44 | ; [ 1, 1, 0, ... 1 ] This is the next set of bits for each HX711. 45 | ; ... 46 | ; [ 0, 0, 1, ... 0 ] This is the LSB set of bits for each HX711. 47 | ; 48 | ; ========================================================================= 49 | ; 50 | ; The reader program is free-running. It constantly clocks-in data during a 51 | ; period guarded by an IRQ flag. An application consuming data from the 52 | ; state machine can start reading in data by waiting until the conversion 53 | ; period IRQ flag is set and then reading in each push. After reading in 54 | ; bits, the same gain setting procedure is followed as with a single HX711. 55 | ; 56 | ; NOTE: the RX FIFO may have residual data in it at the beginning of a 57 | ; conversion period from the previous conversion period. Application code 58 | ; should ensure the RX FIFO is empty. 59 | ; 60 | 61 | .program hx711_multi_reader 62 | 63 | .define PUBLIC HZ 10000000 64 | 65 | .define CONVERSION_DONE_IRQ_NUM 0 66 | .define DATA_READY_IRQ_NUM 4 67 | 68 | .define LOW 0 69 | .define HIGH 1 70 | 71 | .define PLACEHOLDER_IN 1 72 | 73 | .define READ_BITS 23 74 | .define DEFAULT_GAIN 0 75 | .define GAIN_BITS 32 76 | .define T3 2 77 | .define T4 2 78 | 79 | .side_set 1 opt 80 | 81 | set x, DEFAULT_GAIN 82 | pull noblock 83 | out x, GAIN_BITS 84 | 85 | .wrap_target 86 | wrap_target: 87 | 88 | set y, READ_BITS 89 | 90 | in null, 32 ; Completely clear the ISR and RX FIFO. 91 | push noblock 92 | 93 | wait HIGH irq DATA_READY_IRQ_NUM ; Wait for the IRQ from the other state machine 94 | ; to indicate all HX711s are ready for data 95 | ; retrieval. 96 | 97 | ; At this point it is assumed all HX711 chips are 98 | ; synchronised. 99 | 100 | irq clear CONVERSION_DONE_IRQ_NUM 101 | 102 | bitloop: 103 | set pins, HIGH 104 | 105 | PUBLIC bitloop_in_pins_bit_count: ; Set a public label to modify the following `in pins` 106 | ; instruction. 107 | in pins, PLACEHOLDER_IN 108 | 109 | push noblock ; State machine is free-running, so cannot 110 | ; allow it to block with autopush. 111 | 112 | jmp y-- bitloop side LOW 113 | 114 | irq set CONVERSION_DONE_IRQ_NUM 115 | 116 | pull noblock side HIGH 117 | out x, GAIN_BITS 118 | jmp !x wrap_target side LOW 119 | mov y, x 120 | 121 | gainloop: 122 | set pins, HIGH [T3 - 1] 123 | jmp y-- gainloop side LOW [T4 - 1] 124 | 125 | .wrap 126 | 127 | % c-sdk { 128 | // MIT License 129 | // 130 | // Copyright (c) 2023 Daniel Robertson 131 | // 132 | // Permission is hereby granted, free of charge, to any person obtaining a copy 133 | // of this software and associated documentation files (the "Software"), to deal 134 | // in the Software without restriction, including without limitation the rights 135 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 136 | // copies of the Software, and to permit persons to whom the Software is 137 | // furnished to do so, subject to the following conditions: 138 | // 139 | // The above copyright notice and this permission notice shall be included in all 140 | // copies or substantial portions of the Software. 141 | // 142 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 143 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 144 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 145 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 146 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 147 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 148 | // SOFTWARE. 149 | 150 | #include 151 | #include 152 | #include "hardware/clocks.h" 153 | #include "hardware/pio.h" 154 | #include "hardware/pio_instructions.h" 155 | #include "hardware/structs/clocks.h" 156 | #include "hx711_multi.h" 157 | #include "util.h" 158 | 159 | void hx711_multi_pio_init(hx711_multi_t* const hxm) { 160 | 161 | assert(hxm != NULL); 162 | assert(hxm->_pio != NULL); 163 | assert(hxm->_chips_len > 0); 164 | 165 | pio_gpio_init( 166 | hxm->_pio, 167 | hxm->_clock_pin); 168 | 169 | util_pio_gpio_contiguous_init( 170 | hxm->_pio, 171 | hxm->_data_pin_base, 172 | hxm->_chips_len); 173 | 174 | 175 | // make sure conversion done is valid and routable 176 | assert(util_routable_pio_interrupt_num_is_valid( 177 | HX711_MULTI_CONVERSION_DONE_IRQ_NUM)); 178 | 179 | pio_interrupt_clear( 180 | hxm->_pio, 181 | HX711_MULTI_CONVERSION_DONE_IRQ_NUM); 182 | 183 | 184 | // make sure data ready is valid and not routable 185 | // although this is not strictly necessary 186 | assert(util_pio_interrupt_num_is_valid( 187 | HX711_MULTI_DATA_READY_IRQ_NUM)); 188 | 189 | assert(!util_routable_pio_interrupt_num_is_valid( 190 | HX711_MULTI_DATA_READY_IRQ_NUM)); 191 | 192 | pio_interrupt_clear( 193 | hxm->_pio, 194 | HX711_MULTI_DATA_READY_IRQ_NUM); 195 | 196 | } 197 | 198 | void hx711_multi_reader_program_init(hx711_multi_t* const hxm) { 199 | 200 | assert(hxm != NULL); 201 | assert(hxm->_pio != NULL); 202 | 203 | hxm->_pio->instr_mem[hxm->_reader_offset + hx711_multi_reader_offset_bitloop_in_pins_bit_count] = 204 | pio_encode_in(pio_pins, hxm->_chips_len); 205 | 206 | pio_sm_config cfg = hx711_multi_reader_program_get_default_config( 207 | hxm->_reader_offset); 208 | 209 | const float div = (float)(clock_get_hz(clk_sys)) / (uint)hx711_multi_reader_HZ; 210 | 211 | sm_config_set_clkdiv( 212 | &cfg, 213 | div); 214 | 215 | //clock pin setup 216 | pio_sm_set_out_pins( 217 | hxm->_pio, 218 | hxm->_reader_sm, 219 | hxm->_clock_pin, 220 | 1); 221 | 222 | pio_sm_set_set_pins( 223 | hxm->_pio, 224 | hxm->_reader_sm, 225 | hxm->_clock_pin, 226 | 1); 227 | 228 | pio_sm_set_consecutive_pindirs( 229 | hxm->_pio, 230 | hxm->_reader_sm, 231 | hxm->_clock_pin, 232 | 1, 233 | true); 234 | 235 | sm_config_set_set_pins( 236 | &cfg, 237 | hxm->_clock_pin, 238 | 1); 239 | 240 | sm_config_set_out_pins( 241 | &cfg, 242 | hxm->_clock_pin, 243 | 1); 244 | 245 | sm_config_set_sideset_pins( 246 | &cfg, 247 | hxm->_clock_pin); 248 | 249 | //data pins 250 | pio_sm_set_in_pins( 251 | hxm->_pio, 252 | hxm->_reader_sm, 253 | hxm->_data_pin_base); 254 | 255 | pio_sm_set_consecutive_pindirs( 256 | hxm->_pio, 257 | hxm->_reader_sm, 258 | hxm->_data_pin_base, 259 | hxm->_chips_len, 260 | false); //false = input 261 | 262 | sm_config_set_in_pins( 263 | &cfg, 264 | hxm->_data_pin_base); 265 | 266 | sm_config_set_in_shift( 267 | &cfg, 268 | false, //false = shift in left 269 | false, //false = autopush disabled 270 | hxm->_chips_len); 271 | 272 | pio_sm_clear_fifos( 273 | hxm->_pio, 274 | hxm->_reader_sm); 275 | 276 | hxm->_reader_default_config = cfg; 277 | 278 | } 279 | 280 | %} 281 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/c,c++,visualstudio,visualstudiocode,linux,cmake,ninja 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=c,c++,visualstudio,visualstudiocode,linux,cmake,ninja 4 | 5 | # custom 6 | build/** 7 | 8 | 9 | ### C ### 10 | # Prerequisites 11 | *.d 12 | 13 | # Object files 14 | *.o 15 | *.ko 16 | *.obj 17 | *.elf 18 | 19 | # Linker output 20 | *.ilk 21 | *.map 22 | *.exp 23 | 24 | # Precompiled Headers 25 | *.gch 26 | *.pch 27 | 28 | # Libraries 29 | *.lib 30 | *.a 31 | *.la 32 | *.lo 33 | 34 | # Shared objects (inc. Windows DLLs) 35 | *.dll 36 | *.so 37 | *.so.* 38 | *.dylib 39 | 40 | # Executables 41 | *.exe 42 | *.out 43 | *.app 44 | *.i*86 45 | *.x86_64 46 | *.hex 47 | 48 | # Debug files 49 | *.dSYM/ 50 | *.su 51 | *.idb 52 | *.pdb 53 | 54 | # Kernel Module Compile Results 55 | *.mod* 56 | *.cmd 57 | .tmp_versions/ 58 | modules.order 59 | Module.symvers 60 | Mkfile.old 61 | dkms.conf 62 | 63 | ### C++ ### 64 | # Prerequisites 65 | 66 | # Compiled Object files 67 | *.slo 68 | 69 | # Precompiled Headers 70 | 71 | # Compiled Dynamic libraries 72 | 73 | # Fortran module files 74 | *.mod 75 | *.smod 76 | 77 | # Compiled Static libraries 78 | *.lai 79 | 80 | # Executables 81 | 82 | ### CMake ### 83 | CMakeLists.txt.user 84 | CMakeCache.txt 85 | CMakeFiles 86 | CMakeScripts 87 | Testing 88 | Makefile 89 | cmake_install.cmake 90 | install_manifest.txt 91 | compile_commands.json 92 | CTestTestfile.cmake 93 | _deps 94 | 95 | ### CMake Patch ### 96 | # External projects 97 | *-prefix/ 98 | 99 | ### Linux ### 100 | *~ 101 | 102 | # temporary files which can be created if a process still has a handle open of a deleted file 103 | .fuse_hidden* 104 | 105 | # KDE directory preferences 106 | .directory 107 | 108 | # Linux trash folder which might appear on any partition or disk 109 | .Trash-* 110 | 111 | # .nfs files are created when an open file is removed but is still being accessed 112 | .nfs* 113 | 114 | ### Ninja ### 115 | .ninja_deps 116 | .ninja_log 117 | 118 | ### VisualStudioCode ### 119 | .vscode/* 120 | !.vscode/c_cpp_properties.json 121 | !.vscode/settings.json 122 | !.vscode/tasks.json 123 | !.vscode/launch.json 124 | !.vscode/extensions.json 125 | !.vscode/*.code-snippets 126 | 127 | # Local History for Visual Studio Code 128 | .history/ 129 | 130 | # Built Visual Studio Code Extensions 131 | *.vsix 132 | 133 | ### VisualStudioCode Patch ### 134 | # Ignore all local history of files 135 | .history 136 | .ionide 137 | 138 | # Support for Project snippet scope 139 | 140 | ### VisualStudio ### 141 | ## Ignore Visual Studio temporary files, build results, and 142 | ## files generated by popular Visual Studio add-ons. 143 | ## 144 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 145 | 146 | # User-specific files 147 | *.rsuser 148 | *.suo 149 | *.user 150 | *.userosscache 151 | *.sln.docstates 152 | 153 | # User-specific files (MonoDevelop/Xamarin Studio) 154 | *.userprefs 155 | 156 | # Mono auto generated files 157 | mono_crash.* 158 | 159 | # Build results 160 | [Dd]ebug/ 161 | [Dd]ebugPublic/ 162 | [Rr]elease/ 163 | [Rr]eleases/ 164 | x64/ 165 | x86/ 166 | [Ww][Ii][Nn]32/ 167 | [Aa][Rr][Mm]/ 168 | [Aa][Rr][Mm]64/ 169 | bld/ 170 | [Bb]in/ 171 | [Oo]bj/ 172 | [Ll]og/ 173 | [Ll]ogs/ 174 | 175 | # Visual Studio 2015/2017 cache/options directory 176 | .vs/ 177 | # Uncomment if you have tasks that create the project's static files in wwwroot 178 | #wwwroot/ 179 | 180 | # Visual Studio 2017 auto generated files 181 | Generated\ Files/ 182 | 183 | # MSTest test Results 184 | [Tt]est[Rr]esult*/ 185 | [Bb]uild[Ll]og.* 186 | 187 | # NUnit 188 | *.VisualState.xml 189 | TestResult.xml 190 | nunit-*.xml 191 | 192 | # Build Results of an ATL Project 193 | [Dd]ebugPS/ 194 | [Rr]eleasePS/ 195 | dlldata.c 196 | 197 | # Benchmark Results 198 | BenchmarkDotNet.Artifacts/ 199 | 200 | # .NET Core 201 | project.lock.json 202 | project.fragment.lock.json 203 | artifacts/ 204 | 205 | # ASP.NET Scaffolding 206 | ScaffoldingReadMe.txt 207 | 208 | # StyleCop 209 | StyleCopReport.xml 210 | 211 | # Files built by Visual Studio 212 | *_i.c 213 | *_p.c 214 | *_h.h 215 | *.meta 216 | *.iobj 217 | *.ipdb 218 | *.pgc 219 | *.pgd 220 | *.rsp 221 | *.sbr 222 | *.tlb 223 | *.tli 224 | *.tlh 225 | *.tmp 226 | *.tmp_proj 227 | *_wpftmp.csproj 228 | *.log 229 | *.tlog 230 | *.vspscc 231 | *.vssscc 232 | .builds 233 | *.pidb 234 | *.svclog 235 | *.scc 236 | 237 | # Chutzpah Test files 238 | _Chutzpah* 239 | 240 | # Visual C++ cache files 241 | ipch/ 242 | *.aps 243 | *.ncb 244 | *.opendb 245 | *.opensdf 246 | *.sdf 247 | *.cachefile 248 | *.VC.db 249 | *.VC.VC.opendb 250 | 251 | # Visual Studio profiler 252 | *.psess 253 | *.vsp 254 | *.vspx 255 | *.sap 256 | 257 | # Visual Studio Trace Files 258 | *.e2e 259 | 260 | # TFS 2012 Local Workspace 261 | $tf/ 262 | 263 | # Guidance Automation Toolkit 264 | *.gpState 265 | 266 | # ReSharper is a .NET coding add-in 267 | _ReSharper*/ 268 | *.[Rr]e[Ss]harper 269 | *.DotSettings.user 270 | 271 | # TeamCity is a build add-in 272 | _TeamCity* 273 | 274 | # DotCover is a Code Coverage Tool 275 | *.dotCover 276 | 277 | # AxoCover is a Code Coverage Tool 278 | .axoCover/* 279 | !.axoCover/settings.json 280 | 281 | # Coverlet is a free, cross platform Code Coverage Tool 282 | coverage*.json 283 | coverage*.xml 284 | coverage*.info 285 | 286 | # Visual Studio code coverage results 287 | *.coverage 288 | *.coveragexml 289 | 290 | # NCrunch 291 | _NCrunch_* 292 | .*crunch*.local.xml 293 | nCrunchTemp_* 294 | 295 | # MightyMoose 296 | *.mm.* 297 | AutoTest.Net/ 298 | 299 | # Web workbench (sass) 300 | .sass-cache/ 301 | 302 | # Installshield output folder 303 | [Ee]xpress/ 304 | 305 | # DocProject is a documentation generator add-in 306 | DocProject/buildhelp/ 307 | DocProject/Help/*.HxT 308 | DocProject/Help/*.HxC 309 | DocProject/Help/*.hhc 310 | DocProject/Help/*.hhk 311 | DocProject/Help/*.hhp 312 | DocProject/Help/Html2 313 | DocProject/Help/html 314 | 315 | # Click-Once directory 316 | publish/ 317 | 318 | # Publish Web Output 319 | *.[Pp]ublish.xml 320 | *.azurePubxml 321 | # Note: Comment the next line if you want to checkin your web deploy settings, 322 | # but database connection strings (with potential passwords) will be unencrypted 323 | *.pubxml 324 | *.publishproj 325 | 326 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 327 | # checkin your Azure Web App publish settings, but sensitive information contained 328 | # in these scripts will be unencrypted 329 | PublishScripts/ 330 | 331 | # NuGet Packages 332 | *.nupkg 333 | # NuGet Symbol Packages 334 | *.snupkg 335 | # The packages folder can be ignored because of Package Restore 336 | **/[Pp]ackages/* 337 | # except build/, which is used as an MSBuild target. 338 | !**/[Pp]ackages/build/ 339 | # Uncomment if necessary however generally it will be regenerated when needed 340 | #!**/[Pp]ackages/repositories.config 341 | # NuGet v3's project.json files produces more ignorable files 342 | *.nuget.props 343 | *.nuget.targets 344 | 345 | # Microsoft Azure Build Output 346 | csx/ 347 | *.build.csdef 348 | 349 | # Microsoft Azure Emulator 350 | ecf/ 351 | rcf/ 352 | 353 | # Windows Store app package directories and files 354 | AppPackages/ 355 | BundleArtifacts/ 356 | Package.StoreAssociation.xml 357 | _pkginfo.txt 358 | *.appx 359 | *.appxbundle 360 | *.appxupload 361 | 362 | # Visual Studio cache files 363 | # files ending in .cache can be ignored 364 | *.[Cc]ache 365 | # but keep track of directories ending in .cache 366 | !?*.[Cc]ache/ 367 | 368 | # Others 369 | ClientBin/ 370 | ~$* 371 | *.dbmdl 372 | *.dbproj.schemaview 373 | *.jfm 374 | *.pfx 375 | *.publishsettings 376 | orleans.codegen.cs 377 | 378 | # Including strong name files can present a security risk 379 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 380 | #*.snk 381 | 382 | # Since there are multiple workflows, uncomment next line to ignore bower_components 383 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 384 | #bower_components/ 385 | 386 | # RIA/Silverlight projects 387 | Generated_Code/ 388 | 389 | # Backup & report files from converting an old project file 390 | # to a newer Visual Studio version. Backup files are not needed, 391 | # because we have git ;-) 392 | _UpgradeReport_Files/ 393 | Backup*/ 394 | UpgradeLog*.XML 395 | UpgradeLog*.htm 396 | ServiceFabricBackup/ 397 | *.rptproj.bak 398 | 399 | # SQL Server files 400 | *.mdf 401 | *.ldf 402 | *.ndf 403 | 404 | # Business Intelligence projects 405 | *.rdl.data 406 | *.bim.layout 407 | *.bim_*.settings 408 | *.rptproj.rsuser 409 | *- [Bb]ackup.rdl 410 | *- [Bb]ackup ([0-9]).rdl 411 | *- [Bb]ackup ([0-9][0-9]).rdl 412 | 413 | # Microsoft Fakes 414 | FakesAssemblies/ 415 | 416 | # GhostDoc plugin setting file 417 | *.GhostDoc.xml 418 | 419 | # Node.js Tools for Visual Studio 420 | .ntvs_analysis.dat 421 | node_modules/ 422 | 423 | # Visual Studio 6 build log 424 | *.plg 425 | 426 | # Visual Studio 6 workspace options file 427 | *.opt 428 | 429 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 430 | *.vbw 431 | 432 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 433 | *.vbp 434 | 435 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 436 | *.dsw 437 | *.dsp 438 | 439 | # Visual Studio 6 technical files 440 | 441 | # Visual Studio LightSwitch build output 442 | **/*.HTMLClient/GeneratedArtifacts 443 | **/*.DesktopClient/GeneratedArtifacts 444 | **/*.DesktopClient/ModelManifest.xml 445 | **/*.Server/GeneratedArtifacts 446 | **/*.Server/ModelManifest.xml 447 | _Pvt_Extensions 448 | 449 | # Paket dependency manager 450 | .paket/paket.exe 451 | paket-files/ 452 | 453 | # FAKE - F# Make 454 | .fake/ 455 | 456 | # CodeRush personal settings 457 | .cr/personal 458 | 459 | # Python Tools for Visual Studio (PTVS) 460 | __pycache__/ 461 | *.pyc 462 | 463 | # Cake - Uncomment if you are using it 464 | # tools/** 465 | # !tools/packages.config 466 | 467 | # Tabs Studio 468 | *.tss 469 | 470 | # Telerik's JustMock configuration file 471 | *.jmconfig 472 | 473 | # BizTalk build output 474 | *.btp.cs 475 | *.btm.cs 476 | *.odx.cs 477 | *.xsd.cs 478 | 479 | # OpenCover UI analysis results 480 | OpenCover/ 481 | 482 | # Azure Stream Analytics local run output 483 | ASALocalRun/ 484 | 485 | # MSBuild Binary and Structured Log 486 | *.binlog 487 | 488 | # NVidia Nsight GPU debugger configuration file 489 | *.nvuser 490 | 491 | # MFractors (Xamarin productivity tool) working folder 492 | .mfractor/ 493 | 494 | # Local History for Visual Studio 495 | .localhistory/ 496 | 497 | # Visual Studio History (VSHistory) files 498 | .vshistory/ 499 | 500 | # BeatPulse healthcheck temp database 501 | healthchecksdb 502 | 503 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 504 | MigrationBackup/ 505 | 506 | # Ionide (cross platform F# VS Code tools) working folder 507 | .ionide/ 508 | 509 | # Fody - auto-generated XML schema 510 | FodyWeavers.xsd 511 | 512 | # VS Code files for those working on multiple tools 513 | *.code-workspace 514 | 515 | # Local History for Visual Studio Code 516 | 517 | # Windows Installer files from build outputs 518 | *.cab 519 | *.msi 520 | *.msix 521 | *.msm 522 | *.msp 523 | 524 | # JetBrains Rider 525 | *.sln.iml 526 | 527 | ### VisualStudio Patch ### 528 | # Additional files built by Visual Studio 529 | 530 | # End of https://www.toptal.com/developers/gitignore/api/c,c++,visualstudio,visualstudiocode,linux,cmake,ninja -------------------------------------------------------------------------------- /include/util.h: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2023 Daniel Robertson 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | #ifndef UTIL_H_BC9FF78B_B978_444A_8AA1_FF169B09B09E 24 | #define UTIL_H_BC9FF78B_B978_444A_8AA1_FF169B09B09E 25 | 26 | #include 27 | #include "hardware/pio.h" 28 | #include "hardware/platform_defs.h" 29 | #include "hardware/sync.h" 30 | #include "pico/mutex.h" 31 | #include "pico/types.h" 32 | 33 | #ifdef __cplusplus 34 | extern "C" { 35 | #endif 36 | 37 | // RP2040 sdk doesn't seem to define this 38 | #define UTIL_NUM_DMA_IRQS UINT8_C(2) 39 | 40 | #define UTIL_DMA_IRQ_INDEX_MIN UINT8_C(0) 41 | #define UTIL_DMA_IRQ_INDEX_MAX UINT8_C(UTIL_NUM_DMA_IRQS - 1) 42 | 43 | #define UTIL_PIO_IRQ_INDEX_MIN UINT8_C(0) 44 | #define UTIL_PIO_IRQ_INDEX_MAX UINT8_C(NUM_PIOS - 1) 45 | 46 | #define UTIL_PIO_INTERRUPT_NUM_MIN UINT8_C(0) 47 | #define UTIL_PIO_INTERRUPT_NUM_MAX UINT8_C(7) 48 | 49 | #define UTIL_ROUTABLE_PIO_INTERRUPT_NUM_MIN UINT8_C(0) 50 | #define UTIL_ROUTABLE_PIO_INTERRUPT_NUM_MAX UINT8_C(3) 51 | 52 | /** 53 | * @brief Own a mutex for the duration of this block of 54 | * code. 55 | * @example UTIL_MUTEX_BLOCK(mut, 56 | * // do something... 57 | * ); 58 | */ 59 | #define UTIL_MUTEX_BLOCK(mut, ...) \ 60 | do { \ 61 | mutex_enter_blocking(&mut); \ 62 | __VA_ARGS__ \ 63 | mutex_exit(&mut); \ 64 | } while(0) 65 | 66 | /** 67 | * @brief Disable interrupts for the duration of this block of 68 | * code. 69 | * @example UTIL_INTERRUPTS_OFF_BLOCK( 70 | * // do something atomically... 71 | * ); 72 | * @note the interrupt_status var is uniquely named to avoid 73 | * variable conflicts within the block. 74 | */ 75 | #define UTIL_INTERRUPTS_OFF_BLOCK(...) \ 76 | do { \ 77 | const uint32_t interrupt_status_cb918069_eadf_49bc_9d8c_a8a4defad20c = save_and_disable_interrupts(); \ 78 | __VA_ARGS__ \ 79 | restore_interrupts(interrupt_status_cb918069_eadf_49bc_9d8c_a8a4defad20c); \ 80 | } while(0) 81 | 82 | #define UTIL_DECL_IN_RANGE_FUNC(TYPE) \ 83 | bool util_ ## TYPE ##_in_range( \ 84 | const TYPE val, \ 85 | const TYPE min, \ 86 | const TYPE max); 87 | 88 | UTIL_DECL_IN_RANGE_FUNC(int32_t) 89 | UTIL_DECL_IN_RANGE_FUNC(uint32_t) 90 | UTIL_DECL_IN_RANGE_FUNC(int) 91 | UTIL_DECL_IN_RANGE_FUNC(uint) 92 | 93 | /** 94 | * @brief Quick lookup for finding an NVIC IRQ number 95 | * for a PIO and interrupt index number. 96 | * @note Each PIO has two interrupts, hence why this 97 | * array is doubled. 98 | */ 99 | extern const uint8_t util_pio_to_irq_map[NUM_PIOS * 2]; 100 | 101 | /** 102 | * @brief Quick lookup for finding an NVIC IRQ number 103 | * for a DMA interrupt index number. 104 | */ 105 | extern const uint8_t util_dma_to_irq_map[UTIL_NUM_DMA_IRQS]; 106 | 107 | /** 108 | * @brief Check whether a DMA IRQ index is valid. 109 | * 110 | * @param idx 111 | * @return true 112 | * @return false 113 | */ 114 | bool util_dma_irq_index_is_valid(const uint idx); 115 | 116 | /** 117 | * @brief Gets the NVIC DMA IRQ number using the DMA 118 | * IRQ index. 119 | * 120 | * @param idx 121 | * @return uint 122 | */ 123 | uint util_dma_get_irq_from_index(const uint idx); 124 | 125 | /** 126 | * @brief Gets the DMA IRQ index using the NVIC IRQ 127 | * number. 128 | * 129 | * @param irq_num 130 | * @return int -1 is returned for no match. 131 | */ 132 | int util_dma_get_index_from_irq(const uint irq_num); 133 | 134 | /** 135 | * @brief Set and enable an exclusive handler for a 136 | * DMA channel. 137 | * 138 | * @param irq_index 139 | * @param channel 140 | * @param handler 141 | * @param enabled 142 | */ 143 | void util_dma_set_exclusive_channel_irq_handler( 144 | const uint irq_index, 145 | const uint channel, 146 | const irq_handler_t handler, 147 | const bool enabled); 148 | 149 | /** 150 | * @brief Get the transfer count for a given DMA channel. When a 151 | * DMA transfer is active, this count is the number of transfers 152 | * remaining. 153 | * 154 | * @param channel 155 | * @return uint32_t 156 | */ 157 | uint32_t util_dma_get_transfer_count(const uint channel); 158 | 159 | /** 160 | * @brief Wait until channel has completed transferring up 161 | * to a timeout. 162 | * 163 | * @param channel 164 | * @param end 165 | * @return true if transfer completed 166 | * @return false is timeout was reached 167 | */ 168 | bool util_dma_channel_wait_for_finish_timeout( 169 | const uint channel, 170 | const absolute_time_t* const end); 171 | 172 | /** 173 | * @brief Gets the NVIC IRQ number based on the DMA IRQ 174 | * index. 175 | * 176 | * @param irq_index 0 or 1 177 | * @return uint DMA_IRQ_0 or DMA_IRQ_1 178 | */ 179 | uint util_dma_get_irqn(const uint irq_index); 180 | 181 | /** 182 | * @brief Sets a DMA channel's IRQ quiet mode. 183 | * 184 | * @param channel 185 | * @param quiet true for quiet otherwise false 186 | */ 187 | void util_dma_channel_set_quiet( 188 | const uint channel, 189 | const bool quiet); 190 | 191 | /** 192 | * @brief Sets GPIO pins from base to base + len to input. 193 | * 194 | * @param base 195 | * @param len 196 | */ 197 | void util_gpio_set_contiguous_input_pins( 198 | const uint base, 199 | const uint len); 200 | 201 | /** 202 | * @brief Initialises and sets GPIO pin to output. 203 | * 204 | * @param gpio 205 | */ 206 | void util_gpio_set_output(const uint gpio); 207 | 208 | /** 209 | * @brief Set and enable an exclusive interrupt handler 210 | * for a given pio_interrupt_num. 211 | * 212 | * @param pio 213 | * @param irq_index 214 | * @param pio_interrupt_num 215 | * @param handler 216 | * @param enabled 217 | */ 218 | void util_irq_set_exclusive_pio_interrupt_num_handler( 219 | PIO const pio, 220 | const uint irq_index, 221 | const uint pio_interrupt_num, 222 | const irq_handler_t handler, 223 | const bool enabled); 224 | 225 | /** 226 | * @brief Check whether PIO IRQ index is valid 227 | * 228 | * @param idx 229 | * @return true 230 | * @return false 231 | */ 232 | bool util_pio_irq_index_is_valid(const uint idx); 233 | 234 | /** 235 | * @brief Gets the NVIC PIO IRQ number using a PIO 236 | * pointer and PIO IRQ index. 237 | * 238 | * @param idx 239 | * @return uint 240 | */ 241 | uint util_pio_get_irq_from_index( 242 | PIO const pio, 243 | const uint idx); 244 | 245 | /** 246 | * @brief Gets the PIO IRQ index using the NVIC IRQ 247 | * number. 248 | * 249 | * @param irq_num 250 | * @return int -1 is returned for no match. 251 | */ 252 | int util_pio_get_index_from_irq(const uint irq_num); 253 | 254 | /** 255 | * @brief Gets the PIO using the NVIC IRQ number. 256 | * 257 | * @param irq_num 258 | * @return PIO const 259 | */ 260 | PIO const util_pio_get_pio_from_irq(const uint irq_num); 261 | 262 | /** 263 | * @brief Gets the correct NVIC IRQ number for a PIO 264 | * according to the IRQ index. 265 | * 266 | * @example util_pion_get_irqn(pio1, 1); //returns PIO1_IRQ_1 267 | * 268 | * @param pio 269 | * @param irq_index 0 or 1 270 | * @return uint 271 | */ 272 | uint util_pion_get_irqn( 273 | PIO const pio, 274 | const uint irq_index); 275 | 276 | /** 277 | * @brief Gets the correct PIO interrupt source number according 278 | * to the raw PIO interrupt number used in a .pio file. 279 | * 280 | * @example util_pio_get_pis_from_pio_interrupt_num(3); //returns pis_interrupt3 (11) 281 | * 282 | * @see pio_interrupt_source 283 | * @param pio_interrupt_num 284 | * @return uint 285 | */ 286 | uint util_pio_get_pis_from_pio_interrupt_num( 287 | const uint pio_interrupt_num); 288 | 289 | /** 290 | * @brief Inits GPIO pins for PIO from base to base + len. 291 | * 292 | * @param pio 293 | * @param base 294 | * @param len 295 | */ 296 | void util_pio_gpio_contiguous_init( 297 | PIO const pio, 298 | const uint base, 299 | const uint len); 300 | 301 | /** 302 | * @brief Clears a given state machine's RX FIFO. 303 | * 304 | * @param pio 305 | * @param sm 306 | */ 307 | void util_pio_sm_clear_rx_fifo( 308 | PIO const pio, 309 | const uint sm); 310 | 311 | /** 312 | * @brief Clears a given state machine's OSR. 313 | * 314 | * @param pio 315 | * @param sm 316 | */ 317 | void util_pio_sm_clear_osr( 318 | PIO const pio, 319 | const uint sm); 320 | 321 | /** 322 | * @brief Clears a given state machine's ISR. 323 | * 324 | * @param pio 325 | * @param sm 326 | */ 327 | void util_pio_sm_clear_isr( 328 | PIO const pio, 329 | const uint sm); 330 | 331 | /** 332 | * @brief Check whether a given state machine is enabled. 333 | * 334 | * @param pio 335 | * @param sm 336 | * @return true 337 | * @return false 338 | */ 339 | bool util_pio_sm_is_enabled( 340 | PIO const pio, 341 | const uint sm); 342 | 343 | /** 344 | * @brief Check whether a PIO interrupt number is valid. 345 | * 346 | * @param pio_interrupt_num 347 | * @return true 348 | * @return false 349 | */ 350 | bool util_pio_interrupt_num_is_valid( 351 | const uint pio_interrupt_num); 352 | 353 | /** 354 | * @brief Check whether a PIO interrupt number is 355 | * a valid routable interrupt number. 356 | * 357 | * @param pio_interrupt_num 358 | * @return true 359 | * @return false 360 | */ 361 | bool util_routable_pio_interrupt_num_is_valid( 362 | const uint pio_interrupt_num); 363 | 364 | /** 365 | * @brief Waits for a given PIO interrupt to be set. 366 | * 367 | * @param pio 368 | * @param pio_interrupt_num 369 | */ 370 | void util_pio_interrupt_wait( 371 | PIO const pio, 372 | const uint pio_interrupt_num); 373 | 374 | /** 375 | * @brief Waits until a given PIO interrupt is cleared. 376 | * 377 | * @param pio 378 | * @param pio_interrupt_num 379 | */ 380 | void util_pio_interrupt_wait_cleared( 381 | PIO const pio, 382 | const uint pio_interrupt_num); 383 | 384 | /** 385 | * @brief Waits until the given interrupt is cleared, up to 386 | * a maximum timeout. 387 | * 388 | * @param pio 389 | * @param pio_interrupt_num 390 | * @param end 391 | * @return true 392 | * @return false 393 | */ 394 | bool util_pio_interrupt_wait_cleared_timeout( 395 | PIO const pio, 396 | const uint pio_interrupt_num, 397 | const absolute_time_t* const end); 398 | 399 | /** 400 | * @brief Waits for a given PIO interrupt to be set and then 401 | * clears it. 402 | * 403 | * @param pio 404 | * @param pio_interrupt_num 405 | */ 406 | void util_pio_interrupt_wait_clear( 407 | PIO const pio, 408 | const uint pio_interrupt_num); 409 | 410 | /** 411 | * @brief Waits for a given PIO to be set within the timeout 412 | * period. 413 | * 414 | * @param pio 415 | * @param pio_interrupt_num 416 | * @param end 417 | * @return true if the interrupt was set within the timeout 418 | * @return false if the timeout was reached 419 | */ 420 | bool util_pio_interrupt_wait_timeout( 421 | PIO const pio, 422 | const uint pio_interrupt_num, 423 | const absolute_time_t* const end); 424 | 425 | /** 426 | * @brief Waits for a given PIO to be set within the timeout 427 | * period and then clears it. 428 | * 429 | * @param pio 430 | * @param pio_interrupt_num 431 | * @param end 432 | * @return true if the interrupt was set within the timeout 433 | * @return false if the timeout was reached 434 | */ 435 | bool util_pio_interrupt_wait_clear_timeout( 436 | PIO const pio, 437 | const uint pio_interrupt_num, 438 | const absolute_time_t* const end); 439 | 440 | /** 441 | * @brief Attempts to get a word from the state machine's RX FIFO 442 | * if more than threshold words are in the buffer. 443 | * 444 | * @param pio 445 | * @param sm 446 | * @param word variable to be set 447 | * @param threshold word count (1 word = 8 bytes) 448 | * @return true if word was set with the value from the RX FIFO 449 | * @return false if the number of words in the RX FIFO is below 450 | * the threshold 451 | */ 452 | bool util_pio_sm_try_get( 453 | PIO const pio, 454 | const uint sm, 455 | uint32_t* const word, 456 | const uint threshold); 457 | 458 | #undef UTIL_DECL_IN_RANGE_FUNC 459 | 460 | #ifdef __cplusplus 461 | } 462 | #endif 463 | 464 | #endif 465 | -------------------------------------------------------------------------------- /src/hx711_reader.pio: -------------------------------------------------------------------------------- 1 | ; MIT License 2 | ; 3 | ; Copyright (c) 2023 Daniel Robertson 4 | ; 5 | ; Permission is hereby granted, free of charge, to any person obtaining a copy 6 | ; of this software and associated documentation files (the "Software"), to deal 7 | ; in the Software without restriction, including without limitation the rights 8 | ; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | ; copies of the Software, and to permit persons to whom the Software is 10 | ; furnished to do so, subject to the following conditions: 11 | ; 12 | ; The above copyright notice and this permission notice shall be included in all 13 | ; copies or substantial portions of the Software. 14 | ; 15 | ; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | ; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | ; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | ; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | ; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | ; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | ; SOFTWARE. 22 | 23 | ; This program pulls a 32-bit integer from application code and uses 24 | ; it to set the gain on the HX711. If an integer is not provided, the 25 | ; previously provided integer is used. A default integer of 0 26 | ; corresponding to a gain of 128 is used until an integer is provided. 27 | ; The integer provided should be in the range 0 to 2, inclusive. 28 | ; 29 | ; This program constantly and automatically refills the ISR with the 30 | ; newest value from the HX711 without providing a gain value from 31 | ; application code. 32 | ; 33 | ; The lower 24 bits contain the value from the HX711. The next (upper) 34 | ; bits should all be 0, but in any case should be ignored by 35 | ; application code. 36 | ; 37 | ; Details are given on page 5 of the HX711's datasheet. 38 | ; 39 | ; NOTES: 40 | ; 41 | ; 1. This program assumes the state machine is running at 10MHz. Each 42 | ; instruction/cycle is therefore 100ns (0.1us). 43 | ; 44 | ; 2. The 'x' register is used to store the last count of bits to read 45 | ; and to preload the OSR with if the OSR is empty. See pg. 350 of the 46 | ; RP2040 datasheet for details about this when the 'noblock' option is 47 | ; given. 48 | ; 49 | ; 3. The 'y' register is used to as the decrement counter for the bit 50 | ; read counter. This variable is 0-based. 51 | ; 52 | ; 4. When running in serial, there is an overlap between T2 and T3-T4. 53 | ; That is to say, the data pin can be read from 100ns after the clock 54 | ; pin has gone high. Given each bit is read in serial, the T2 delay is 55 | ; not needed. 56 | ; 57 | ; 5. With the state machine running at 10MHz: 58 | ; 59 | ; T1: With no delay following the wait instruction (ie. []), and 60 | ; assuming the the next instruction executes immediately after the wait 61 | ; condition is met, the clock pin will go high following that second 62 | ; instruction (ie. set pins, 1 [T3 - 1]). The set pins, 1 portion of 63 | ; the instruction will take one cycle - 100ns - which is the absolute 64 | ; minimum for T1 according to the HX711 datasheet. 65 | ; 66 | ; T2: A T2 delay is still unneeded due to the inherent delays in 67 | ; reading the data pin and jmp instruction. 68 | ; 69 | ; 6. There is a small speedup by side-setting on the gain loop jmp 70 | ; instruction. 71 | ; 72 | .program hx711_reader 73 | 74 | .define PUBLIC HZ 10000000 75 | 76 | .define LOW 0 77 | .define HIGH 1 78 | 79 | .define READ_BITS 23 ; 24 bits to read from HX711 (this is 0-based). 80 | .define DEFAULT_GAIN 0 ; Default gain (0=128, 1=32, 2=64). 81 | .define GAIN_BITS 32 82 | .define T3 2 ; 200ns 83 | .define T4 2 ; 200ns 84 | 85 | .side_set 1 opt ; Side set on the clock pin. 86 | 87 | set x, DEFAULT_GAIN ; Set an initial default gain (this is 0-based). 88 | 89 | pull noblock ; Pull in gain from application, but don't block. 90 | ; The effect is that if the TX FIFO is empty, the 91 | ; OSR is loaded with the x register - the default. 92 | 93 | out x, GAIN_BITS ; Copy the OSR contents into the x register. If 94 | ; the default is used, this is effectively a NOP. 95 | 96 | .wrap_target 97 | wrap_target: 98 | 99 | set y, READ_BITS ; Read y number of bits. This is 0-based. 100 | 101 | wait LOW pin 0 ; Wait until data pin falling edge. 102 | 103 | bitloop: 104 | set pins, HIGH ; Set rising edge of clock pin. 105 | in pins, 1 ; Read bit from data pin into ISR. This will also 106 | ; act as a 100ns delay for the clock pin high time. 107 | jmp y-- bitloop side LOW [T4 - 1] ; Keep jumping back to bitloop while y > 0, 108 | ; also set the clock pin falling edge and delay for 109 | ; 100ns after side-setting the clock pin before 110 | ; looping or falling through to ensure a minimum 111 | ; low clock pin for 200ns. 112 | 113 | ; At this point, all 24 bits have been read 114 | ; and can be pushed back to the application. A 115 | ; manual 'push noblock' is not used in favour of 116 | ; autopush, which is configured in the init 117 | ; function below. 118 | 119 | ; Defer obtaining the gain from the application 120 | ; until the last moment. So: 121 | 122 | pull noblock side HIGH ; Pull in any data if it's available, but don't 123 | ; wait if there isn't any. If no data is there, 124 | ; preload from x (this is what noblock does). 125 | ; Also side-set the clock pin for the rising edge 126 | ; of the 25th pulse. 127 | 128 | out x, GAIN_BITS ; 129 | ; x will also 130 | ; persist after the wrap loop. This instruction 131 | ; doubles as a T3 delay for the 25th clock pulse. 132 | 133 | jmp !x wrap_target side LOW ; If x is 0 - meaning a gain of 128 - we 134 | ; can immediately jump back to the start. We can 135 | ; also side-set the falling edge of the clock pin 136 | ; for the 25th clock pulse. 137 | 138 | ; If execution has fallen-through to here, the gain 139 | ; is either 1 (32) or 2 (64). But if execution has 140 | ; jumped back to wrap_target, copying x into y is 141 | ; not needed. So: 142 | 143 | mov y, x ; Copy x into y. y will hold the number of clock 144 | ; pulses minus 1 to be used as the following loop 145 | ; counter. This instruction doubles as a T4 delay 146 | ; for the falling edge of the 25th clock pulse ( 147 | ; which is only really relevant to 26+ clock 148 | ; pulses). 149 | 150 | gainloop: 151 | set pins, HIGH [T3 - 1] ; Set clock pin high and delay to ensure a minimum 152 | ; high clock pin for 200ns. 153 | jmp y-- gainloop side LOW [T4 - 1] ; Keep pulsing clock pin while y > 0. 154 | ; Also use this to side-set the falling edge of 155 | ; the clock pin and delay for the minimum required 156 | ; time. If this is the final loop, the extra delay 157 | ; is superfluous but ultimately does not matter 158 | ; because of the inherent delay in waiting on the 159 | ; data pin again once the program wraps. 160 | 161 | ; No need to read from the data pin. 162 | 163 | ; At this point, the gain has been set for the 164 | ; next reading, so go back to the start. 165 | 166 | .wrap 167 | 168 | % c-sdk { 169 | // MIT License 170 | // 171 | // Copyright (c) 2023 Daniel Robertson 172 | // 173 | // Permission is hereby granted, free of charge, to any person obtaining a copy 174 | // of this software and associated documentation files (the "Software"), to deal 175 | // in the Software without restriction, including without limitation the rights 176 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 177 | // copies of the Software, and to permit persons to whom the Software is 178 | // furnished to do so, subject to the following conditions: 179 | // 180 | // The above copyright notice and this permission notice shall be included in all 181 | // copies or substantial portions of the Software. 182 | // 183 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 184 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 185 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 186 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 187 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 188 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 189 | // SOFTWARE. 190 | 191 | #include 192 | #include 193 | #include "hardware/clocks.h" 194 | #include "hardware/pio.h" 195 | #include "hardware/structs/clocks.h" 196 | #include "hx711.h" 197 | 198 | void hx711_reader_pio_init(hx711_t* const hx) { 199 | 200 | assert(hx != NULL); 201 | assert(hx->_pio != NULL); 202 | 203 | pio_gpio_init( 204 | hx->_pio, 205 | hx->_clock_pin); 206 | 207 | //clock pin setup 208 | pio_sm_set_out_pins( 209 | hx->_pio, 210 | hx->_reader_sm, 211 | hx->_clock_pin, 212 | 1); 213 | 214 | pio_sm_set_set_pins( 215 | hx->_pio, 216 | hx->_reader_sm, 217 | hx->_clock_pin, 218 | 1); 219 | 220 | pio_sm_set_consecutive_pindirs( 221 | hx->_pio, 222 | hx->_reader_sm, 223 | hx->_clock_pin, 224 | 1, 225 | true); 226 | 227 | //data pin setup 228 | pio_gpio_init( 229 | hx->_pio, 230 | hx->_data_pin); 231 | 232 | pio_sm_set_in_pins( 233 | hx->_pio, 234 | hx->_reader_sm, 235 | hx->_data_pin); 236 | 237 | pio_sm_set_consecutive_pindirs( 238 | hx->_pio, 239 | hx->_reader_sm, 240 | hx->_data_pin, 241 | 1, 242 | false); 243 | 244 | } 245 | 246 | void hx711_reader_program_init(hx711_t* const hx) { 247 | 248 | assert(hx != NULL); 249 | assert(hx->_pio != NULL); 250 | 251 | pio_sm_config cfg = hx711_reader_program_get_default_config( 252 | hx->_reader_offset); 253 | 254 | const float div = (float)(clock_get_hz(clk_sys)) / (uint)hx711_reader_HZ; 255 | 256 | sm_config_set_clkdiv( 257 | &cfg, 258 | div); 259 | 260 | sm_config_set_set_pins( 261 | &cfg, 262 | hx->_clock_pin, 263 | 1); 264 | 265 | sm_config_set_out_pins( 266 | &cfg, 267 | hx->_clock_pin, 268 | 1); 269 | 270 | sm_config_set_sideset_pins( 271 | &cfg, 272 | hx->_clock_pin); 273 | 274 | sm_config_set_in_pins( 275 | &cfg, 276 | hx->_data_pin); 277 | 278 | /** 279 | * Why enable autopush? 280 | * 281 | * "The state machine keeps an eye on the total amount of data shifted into the ISR, and on the in which reaches or 282 | * breaches a total shift count of 32 (or whatever number you have configured), the ISR contents, along with the new data 283 | * from the in. goes straight to the RX FIFO. The ISR is cleared to zero in the same operation." 284 | * - Raspberry Pi Pico C/C++ SDK pg. 45 285 | * 286 | * When manually pushing using noblock, the FIFO contents are NOT changed. 287 | * 288 | * "The PIO assembler sets the Block bit by default. If the Block bit is not set, the PUSH does not stall on a full RX FIFO, instead 289 | * continuing immediately to the next instruction. The FIFO state and contents are unchanged when this happens. The ISR 290 | * is still cleared to all-zeroes, and the FDEBUG_RXSTALL flag is set (the same as a blocking PUSH or autopush to a full RX FIFO) 291 | * to indicate data was lost." 292 | * - Raspberry Pi Pico C/C++ SDK pg. 64 293 | * 294 | * Manually pushing is not ideal. Application code should be able to look at the FIFO and assume 295 | * that the value inside is the most up-to-date data available. Autopushing does this. 296 | */ 297 | sm_config_set_in_shift( 298 | &cfg, 299 | false, //false = shift in left 300 | true, //true = autopush enabled 301 | HX711_READ_BITS); //autopush on 24 bits 302 | 303 | pio_sm_clear_fifos( 304 | hx->_pio, 305 | hx->_reader_sm); 306 | 307 | //store a copy of the configuration for resetting the sm 308 | hx->_reader_prog_default_config = cfg; 309 | 310 | } 311 | 312 | %} 313 | -------------------------------------------------------------------------------- /include/hx711_multi.h: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2023 Daniel Robertson 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | #ifndef HX711_MULTI_H_253BF37A_8356_462B_B8F9_39E09A7193E6 24 | #define HX711_MULTI_H_253BF37A_8356_462B_B8F9_39E09A7193E6 25 | 26 | #include 27 | #include 28 | #include "hardware/pio.h" 29 | #include "pico/mutex.h" 30 | #include "pico/platform.h" 31 | #include "hx711.h" 32 | 33 | #ifdef __cplusplus 34 | extern "C" { 35 | #endif 36 | 37 | /** 38 | * @brief PIO interrupt number which is set by the reader 39 | * PIO State Machine when a conversion period ends. It is 40 | * the period of time between a conversion ending and the 41 | * next period beginning. 42 | */ 43 | #define HX711_MULTI_CONVERSION_DONE_IRQ_NUM UINT8_C(0) 44 | 45 | /** 46 | * @brief PIO interrupt number which is used between the 47 | * awaiter and reader PIO State Machines to indicate when 48 | * data is ready to be retrieved. It is not used directly 49 | * within the main set of code, but is used to validate 50 | * that the IRQ number is properly available. 51 | */ 52 | #define HX711_MULTI_DATA_READY_IRQ_NUM UINT8_C(4) 53 | 54 | /** 55 | * @brief Only one instance of a hx711_multi can operate 56 | * within a PIO. So the maximum number of concurrent 57 | * asynchronous read processes is limited to the numeer of 58 | * PIOs available. 59 | */ 60 | #define HX711_MULTI_ASYNC_READ_COUNT UINT8_C(NUM_PIOS) 61 | 62 | /** 63 | * @brief IRQ index defaults for PIO and DMA. 64 | */ 65 | #define HX711_MULTI_ASYNC_PIO_IRQ_IDX UINT8_C(0) 66 | #define HX711_MULTI_ASYNC_DMA_IRQ_IDX UINT8_C(0) 67 | 68 | /** 69 | * @brief Minimum number of chips to connect to a hx711_multi. 70 | */ 71 | #define HX711_MULTI_MIN_CHIPS UINT8_C(1) 72 | 73 | /** 74 | * @brief The max number of chips could technically be the 75 | * same as the number of bits output from the PIO State Machine. 76 | * But this is always going to be limited by the number of GPIO 77 | * input pins available on the RP2040. So we take the minimum of 78 | * the two just in case it ever increases. 79 | */ 80 | #define HX711_MULTI_MAX_CHIPS UINT8_C(MIN(NUM_BANK0_GPIOS, 32)) 81 | 82 | /** 83 | * @brief State of the read as it moves through the async process. 84 | */ 85 | typedef enum { 86 | HX711_MULTI_ASYNC_STATE_NONE = 0, 87 | HX711_MULTI_ASYNC_STATE_WAITING, 88 | HX711_MULTI_ASYNC_STATE_READING, 89 | HX711_MULTI_ASYNC_STATE_DONE 90 | } hx711_multi_async_state_t; 91 | 92 | typedef struct { 93 | 94 | uint _clock_pin; 95 | uint _data_pin_base; 96 | size_t _chips_len; 97 | 98 | PIO _pio; 99 | 100 | const pio_program_t* _awaiter_prog; 101 | pio_sm_config _awaiter_default_config; 102 | uint _awaiter_sm; 103 | uint _awaiter_offset; 104 | 105 | const pio_program_t* _reader_prog; 106 | pio_sm_config _reader_default_config; 107 | uint _reader_sm; 108 | uint _reader_offset; 109 | 110 | uint _dma_channel; 111 | 112 | uint32_t _buffer[HX711_READ_BITS]; 113 | 114 | uint _pio_irq_index; 115 | uint _dma_irq_index; 116 | volatile hx711_multi_async_state_t _async_state; 117 | 118 | #ifndef HX711_NO_MUTEX 119 | mutex_t _mut; 120 | #endif 121 | 122 | } hx711_multi_t; 123 | 124 | typedef void (*hx711_multi_pio_init_t)(hx711_multi_t* const); 125 | typedef void (*hx711_multi_program_init_t)(hx711_multi_t* const); 126 | 127 | typedef struct { 128 | 129 | /** 130 | * @brief GPIO pin number connected to all HX711 chips. 131 | */ 132 | uint clock_pin; 133 | 134 | /** 135 | * @brief Lowest GPIO pin number connected to a HX711 chip. 136 | */ 137 | uint data_pin_base; 138 | 139 | /** 140 | * @brief Number of HX711 chips connected. 141 | */ 142 | size_t chips_len; 143 | 144 | 145 | /** 146 | * @brief Which index to use for a PIO interrupt. Either 0 or 1. 147 | * Corresponds to PIO[PIO_INDEX]_IRQ[IRQ_INDEX] NVIC IRQ number. 148 | */ 149 | uint pio_irq_index; 150 | 151 | /** 152 | * @brief Which index to use for a DMA interrupt. Either 0 or 1. 153 | * Corresponds to DMA_IRQ[IRQ_INDEX] NVIC IRQ number. 154 | */ 155 | uint dma_irq_index; 156 | 157 | 158 | /** 159 | * @brief Which PIO to use. Either pio0 or pio1. 160 | */ 161 | PIO pio; 162 | 163 | /** 164 | * @brief PIO init function. This is called to set up any PIO 165 | * functions (pio_*) as opposed to any State Machine functions 166 | * (pio_sm_*). 167 | */ 168 | hx711_multi_pio_init_t pio_init; 169 | 170 | 171 | /** 172 | * @brief PIO awaiter program. 173 | */ 174 | const pio_program_t* awaiter_prog; 175 | 176 | /** 177 | * @brief PIO awaiter init function. This is called to set up 178 | * the State Machine for the awaiter program. It is called once 179 | * prior to the State Machine being enabled. 180 | */ 181 | hx711_multi_program_init_t awaiter_prog_init; 182 | 183 | 184 | /** 185 | * @brief PIO reader program. 186 | */ 187 | const pio_program_t* reader_prog; 188 | 189 | /** 190 | * @brief PIO reader init function. This is called to set up 191 | * the State Machine for the reader program. It is called once 192 | * prior to the State Machine being enabled. 193 | */ 194 | hx711_multi_program_init_t reader_prog_init; 195 | 196 | } hx711_multi_config_t; 197 | 198 | /** 199 | * @brief Array of hxm for ISR to access. This is a global 200 | * variable. 201 | */ 202 | extern hx711_multi_t* hx711_multi__async_read_array[ 203 | HX711_MULTI_ASYNC_READ_COUNT]; 204 | 205 | static void hx711_multi__init_asert( 206 | const hx711_multi_config_t* const config); 207 | 208 | /** 209 | * @brief Subroutine for initialising PIO. 210 | * 211 | * @param hxm 212 | */ 213 | static void hx711_multi__init_pio(hx711_multi_t* const hxm); 214 | 215 | /** 216 | * @brief Subroutine for initialising DMA. 217 | * 218 | * @param hxm 219 | */ 220 | static void hx711_multi__init_dma(hx711_multi_t* const hxm); 221 | 222 | /** 223 | * @brief Subroutine for initialising IRQ. 224 | * 225 | * @param hxm 226 | */ 227 | static void hx711_multi__init_irq(hx711_multi_t* const hxm); 228 | 229 | /** 230 | * @brief Whether a given hxm is the cause of the current 231 | * DMA IRQ. 232 | * 233 | * @param hxm 234 | * @return true 235 | * @return false 236 | */ 237 | static bool hx711_multi__async_dma_irq_is_set( 238 | hx711_multi_t* const hxm); 239 | 240 | /** 241 | * @brief Whether a given hxm is the cause of the current 242 | * PIO IRQ. 243 | * 244 | * @param hxm 245 | * @return true 246 | * @return false 247 | */ 248 | static bool hx711_multi__async_pio_irq_is_set( 249 | hx711_multi_t* const hxm); 250 | 251 | /** 252 | * @brief Get the hxm which caused the current DMA IRQ. Returns 253 | * NULL if none found. 254 | * 255 | * @return hx711_multi_t* const 256 | */ 257 | static hx711_multi_t* const hx711_multi__async_get_dma_irq_request(); 258 | 259 | /** 260 | * @brief Get the hxm which caused the current PIO IRQ. Returns 261 | * NULL if none found. 262 | * 263 | * @return hx711_multi_t* const 264 | */ 265 | static hx711_multi_t* const hx711_multi__async_get_pio_irq_request(); 266 | 267 | /** 268 | * @brief Triggers DMA reading; moves request state from WAITING to READING. 269 | * 270 | * @param hxm 271 | */ 272 | static void hx711_multi__async_start_dma( 273 | hx711_multi_t* const hxm); 274 | 275 | /** 276 | * @brief Check whether an async read is currently occurring. 277 | * 278 | * @param hxm 279 | * @return true 280 | * @return false 281 | */ 282 | static bool hx711_multi__async_is_running( 283 | hx711_multi_t* const hxm); 284 | 285 | /** 286 | * @brief Stop any current async reads and stop listening for DMA 287 | * and PIO IRQs. 288 | * 289 | * @param hxm 290 | */ 291 | static void hx711_multi__async_finish( 292 | hx711_multi_t* const hxm); 293 | 294 | /** 295 | * @brief ISR handler for PIO IRQs. 296 | */ 297 | static void __isr __not_in_flash_func(hx711_multi__async_pio_irq_handler)(); 298 | 299 | /** 300 | * @brief ISR handler for DMA IRQs. 301 | */ 302 | static void __isr __not_in_flash_func(hx711_multi__async_dma_irq_handler)(); 303 | 304 | /** 305 | * @brief Adds hxm to the array for ISR access. Returns false 306 | * if no space. 307 | * 308 | * @param hxm 309 | * @return true 310 | * @return false 311 | */ 312 | static bool hx711_multi__async_add_reader( 313 | hx711_multi_t* const hxm); 314 | 315 | /** 316 | * @brief Removes the given hxm from the request array. 317 | * 318 | * @param hxm 319 | */ 320 | static void hx711_multi__async_remove_reader( 321 | const hx711_multi_t* const hxm); 322 | 323 | /** 324 | * @brief Check whether the hxm struct has been initialised. 325 | * 326 | * @param hxm 327 | * @return true 328 | * @return false 329 | */ 330 | static bool hx711_multi__is_initd(hx711_multi_t* const hxm); 331 | 332 | /** 333 | * @brief Check whether the hxm struct has PIO State Machines 334 | * which are enabled. 335 | * 336 | * @param hxm 337 | * @return true 338 | * @return false 339 | */ 340 | static bool hx711_multi__is_state_machines_enabled( 341 | hx711_multi_t* const hxm); 342 | 343 | /** 344 | * @brief Convert an array of pinvals to regular HX711 345 | * values. 346 | * 347 | * @param pinvals 348 | * @param values 349 | * @param len number of values to convert 350 | */ 351 | void hx711_multi_pinvals_to_values( 352 | const uint32_t* const pinvals, 353 | int32_t* const values, 354 | const size_t len); 355 | 356 | void hx711_multi_init( 357 | hx711_multi_t* const hxm, 358 | const hx711_multi_config_t* const config); 359 | 360 | /** 361 | * @brief Stop communication with all HX711s. 362 | * 363 | * @param hxm 364 | */ 365 | void hx711_multi_close(hx711_multi_t* const hxm); 366 | 367 | /** 368 | * @brief Sets the HX711s' gain. 369 | * 370 | * @param hxm 371 | * @param gain 372 | */ 373 | void hx711_multi_set_gain( 374 | hx711_multi_t* const hxm, 375 | const hx711_gain_t gain); 376 | 377 | /** 378 | * @brief Fill an array with one value from each HX711. Blocks 379 | * until values are obtained. 380 | * 381 | * @param hxm 382 | * @param values 383 | */ 384 | void hx711_multi_get_values( 385 | hx711_multi_t* const hxm, 386 | int32_t* const values); 387 | 388 | /** 389 | * @brief Fill an array with one value from each HX711, 390 | * timing out if failing to obtain values within the 391 | * timeout period. 392 | * 393 | * @param hxm 394 | * @param values 395 | * @param timeout microseconds 396 | * @return true if values obtained within the timeout period 397 | * @return false if values not obtained within the timeout period 398 | */ 399 | bool hx711_multi_get_values_timeout( 400 | hx711_multi_t* const hxm, 401 | int32_t* const values, 402 | const uint timeout); 403 | 404 | /** 405 | * @brief Start an asynchronos read. This function is not 406 | * mutex protected. 407 | * 408 | * @param hxm 409 | */ 410 | void hx711_multi_async_start(hx711_multi_t* const hxm); 411 | 412 | /** 413 | * @brief Check whether an asynchronous read is complete. 414 | * This function is not mutex protected. 415 | * 416 | * @param hxm 417 | * @return true 418 | * @return false 419 | */ 420 | bool hx711_multi_async_done(hx711_multi_t* const hxm); 421 | 422 | /** 423 | * @brief Get the values from the last asynchronous read. 424 | * This function is not mutex protected. 425 | * 426 | * @param hxm 427 | * @param values 428 | */ 429 | void hx711_multi_async_get_values( 430 | hx711_multi_t* const hxm, 431 | int32_t* const values); 432 | 433 | /** 434 | * @brief Power up each HX711 and start the internal read/write 435 | * functionality. 436 | * 437 | * @related hx711_wait_settle 438 | * @param hxm 439 | * @param gain hx711_gain_t initial gain 440 | */ 441 | void hx711_multi_power_up( 442 | hx711_multi_t* const hxm, 443 | const hx711_gain_t gain); 444 | 445 | /** 446 | * @brief Power down each HX711 and stop the internal read/write 447 | * functionlity. 448 | * 449 | * @related hx711_wait_power_down() 450 | * @param hxm 451 | */ 452 | void hx711_multi_power_down(hx711_multi_t* const hxm); 453 | 454 | /** 455 | * @brief Attempt to synchronise all connected chips. This 456 | * does not include a settling time. 457 | * 458 | * @param hxm 459 | * @param gain initial gain to set to all chips 460 | */ 461 | void hx711_multi_sync( 462 | hx711_multi_t* const hxm, 463 | const hx711_gain_t gain); 464 | 465 | /** 466 | * @brief Returns the state of each chip as a bitmask. The 0th 467 | * bit is the first chip, 1th bit is the second, and so on. 468 | * 469 | * @param hxm 470 | * @return uint32_t 471 | */ 472 | uint32_t hx711_multi_get_sync_state( 473 | hx711_multi_t* const hxm); 474 | 475 | /** 476 | * @brief Determines whether all chips are in sync. 477 | * 478 | * @param hxm 479 | * @return true 480 | * @return false 481 | */ 482 | bool hx711_multi_is_syncd( 483 | hx711_multi_t* const hxm); 484 | 485 | #ifdef __cplusplus 486 | } 487 | #endif 488 | 489 | #endif 490 | -------------------------------------------------------------------------------- /src/util.c: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2023 Daniel Robertson 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | #include 24 | #include 25 | #include 26 | #include "hardware/dma.h" 27 | #include "hardware/gpio.h" 28 | #include "hardware/irq.h" 29 | #include "hardware/pio.h" 30 | #include "hardware/pio_instructions.h" 31 | #include "hardware/regs/intctrl.h" 32 | #include "hardware/regs/pio.h" 33 | #include "hardware/structs/dma.h" 34 | #include "hardware/timer.h" 35 | #include "pico/platform.h" 36 | #include "pico/time.h" 37 | #include "pico/types.h" 38 | #include "../include/util.h" 39 | 40 | #define UTIL_DEF_IN_RANGE_FUNC(TYPE) \ 41 | bool util_ ## TYPE ##_in_range( \ 42 | const TYPE val, \ 43 | const TYPE min, \ 44 | const TYPE max) { \ 45 | return val >= min && val <= max; \ 46 | } 47 | 48 | const uint8_t util_pio_to_irq_map[] = { 49 | PIO0_IRQ_0, 50 | PIO0_IRQ_1, 51 | PIO1_IRQ_0, 52 | PIO1_IRQ_1 53 | }; 54 | 55 | const uint8_t util_dma_to_irq_map[] = { 56 | DMA_IRQ_0, 57 | DMA_IRQ_1 58 | }; 59 | 60 | UTIL_DEF_IN_RANGE_FUNC(int32_t) 61 | UTIL_DEF_IN_RANGE_FUNC(uint32_t) 62 | UTIL_DEF_IN_RANGE_FUNC(int) 63 | UTIL_DEF_IN_RANGE_FUNC(uint) 64 | 65 | bool util_dma_irq_index_is_valid(const uint idx) { 66 | return util_uint_in_range( 67 | idx, 68 | UTIL_DMA_IRQ_INDEX_MIN, 69 | UTIL_DMA_IRQ_INDEX_MAX); 70 | } 71 | 72 | uint util_dma_get_irq_from_index(const uint idx) { 73 | assert(util_dma_irq_index_is_valid(idx)); 74 | assert(util_dma_to_irq_map != NULL); 75 | return util_dma_to_irq_map[idx]; 76 | } 77 | 78 | int util_dma_get_index_from_irq(const uint irq_num) { 79 | 80 | check_irq_param(irq_num); 81 | assert(util_dma_to_irq_map != NULL); 82 | 83 | switch(irq_num) { 84 | case DMA_IRQ_0: 85 | return 0; 86 | case DMA_IRQ_1: 87 | return 1; 88 | default: 89 | return -1; 90 | } 91 | 92 | } 93 | 94 | void util_dma_set_exclusive_channel_irq_handler( 95 | const uint irq_index, 96 | const uint channel, 97 | const irq_handler_t handler, 98 | const bool enabled) { 99 | 100 | assert(util_dma_irq_index_is_valid(irq_index)); 101 | check_dma_channel_param(channel); 102 | assert(handler != NULL); 103 | 104 | const uint irq_num = util_dma_get_irqn(irq_index); 105 | 106 | dma_irqn_set_channel_enabled( 107 | irq_index, 108 | channel, 109 | enabled); 110 | 111 | irq_set_exclusive_handler( 112 | irq_num, 113 | handler); 114 | 115 | irq_set_enabled( 116 | irq_num, 117 | enabled); 118 | 119 | } 120 | 121 | uint32_t util_dma_get_transfer_count(const uint channel) { 122 | check_dma_channel_param(channel); 123 | return (uint32_t)dma_hw->ch[channel].transfer_count; 124 | } 125 | 126 | bool util_dma_channel_wait_for_finish_timeout( 127 | const uint channel, 128 | const absolute_time_t* const end) { 129 | 130 | check_dma_channel_param(channel); 131 | assert(end != NULL); 132 | assert(!is_nil_time(*end)); 133 | 134 | while(!time_reached(*end)) { 135 | if(!dma_channel_is_busy(channel)) { 136 | return true; 137 | } 138 | } 139 | 140 | return false; 141 | 142 | } 143 | 144 | uint util_dma_get_irqn(const uint irq_index) { 145 | 146 | assert(util_dma_to_irq_map != NULL); 147 | assert(util_uint_in_range( 148 | irq_index, 149 | UTIL_DMA_IRQ_INDEX_MIN, 150 | UTIL_DMA_IRQ_INDEX_MAX)); 151 | 152 | const uint irq_num = util_dma_to_irq_map[ 153 | irq_index]; 154 | 155 | check_irq_param(irq_num); 156 | 157 | return irq_num; 158 | 159 | } 160 | 161 | void util_dma_channel_set_quiet( 162 | const uint channel, 163 | const bool quiet) { 164 | check_dma_channel_param(channel); 165 | dma_channel_config cfg = dma_get_channel_config(channel); 166 | channel_config_set_irq_quiet(&cfg, quiet); 167 | dma_channel_set_config(channel, &cfg, false); 168 | } 169 | 170 | void util_gpio_set_contiguous_input_pins( 171 | const uint base, 172 | const uint len) { 173 | 174 | assert(len > 0); 175 | 176 | const uint l = base + len - 1; 177 | 178 | for(uint i = base; i <= l; ++i) { 179 | check_gpio_param(i); 180 | gpio_set_input_enabled(i, true); 181 | } 182 | 183 | } 184 | 185 | void util_gpio_set_output(const uint gpio) { 186 | check_gpio_param(gpio); 187 | gpio_init(gpio); 188 | gpio_set_dir(gpio, true); 189 | } 190 | 191 | void util_irq_set_exclusive_pio_interrupt_num_handler( 192 | PIO const pio, 193 | const uint irq_index, 194 | const uint pio_interrupt_num, 195 | const irq_handler_t handler, 196 | const bool enabled) { 197 | 198 | check_pio_param(pio); 199 | assert(util_pio_irq_index_is_valid(irq_index)); 200 | assert(util_routable_pio_interrupt_num_is_valid(pio_interrupt_num)); 201 | assert(handler != NULL); 202 | 203 | const uint irq_num = util_pio_get_irq_from_index( 204 | pio, 205 | irq_index); 206 | 207 | const uint pis = util_pio_get_pis_from_pio_interrupt_num( 208 | pio_interrupt_num); 209 | 210 | pio_set_irqn_source_enabled( 211 | pio, 212 | irq_index, 213 | pis, 214 | enabled); 215 | 216 | irq_set_exclusive_handler( 217 | irq_num, 218 | handler); 219 | 220 | irq_set_enabled( 221 | irq_num, 222 | enabled); 223 | 224 | } 225 | 226 | bool util_pio_irq_index_is_valid(const uint idx) { 227 | return util_uint_in_range( 228 | idx, 229 | UTIL_PIO_IRQ_INDEX_MIN, 230 | UTIL_PIO_IRQ_INDEX_MAX); 231 | } 232 | 233 | uint util_pion_get_irqn( 234 | PIO const pio, 235 | const uint irq_index) { 236 | 237 | check_pio_param(pio); 238 | assert(util_pio_irq_index_is_valid(irq_index)); 239 | assert(util_pio_to_irq_map != NULL); 240 | 241 | const uint irq_num = util_pio_to_irq_map[ 242 | pio_get_index(pio) + irq_index]; 243 | 244 | check_irq_param(irq_num); 245 | 246 | return irq_num; 247 | 248 | } 249 | 250 | uint util_pio_get_irq_from_index( 251 | PIO const pio, 252 | const uint idx) { 253 | 254 | check_pio_param(pio); 255 | assert(util_pio_irq_index_is_valid(idx)); 256 | assert(util_pio_to_irq_map != NULL); 257 | 258 | const uint irq_num = util_pio_to_irq_map[ 259 | pio_get_index(pio) + idx]; 260 | 261 | check_irq_param(irq_num); 262 | 263 | return irq_num; 264 | 265 | } 266 | 267 | int util_pio_get_index_from_irq(const uint irq_num) { 268 | 269 | check_irq_param(irq_num); 270 | 271 | switch(irq_num) { 272 | case PIO0_IRQ_0: 273 | case PIO1_IRQ_0: 274 | return 0; 275 | case PIO0_IRQ_1: 276 | case PIO1_IRQ_1: 277 | return 1; 278 | default: 279 | return -1; 280 | } 281 | 282 | } 283 | 284 | PIO const util_pio_get_pio_from_irq(const uint irq_num) { 285 | 286 | check_irq_param(irq_num); 287 | 288 | switch(irq_num) { 289 | case PIO0_IRQ_0: 290 | case PIO0_IRQ_1: 291 | return pio0; 292 | case PIO1_IRQ_0: 293 | case PIO1_IRQ_1: 294 | return pio1; 295 | default: 296 | return NULL; 297 | } 298 | 299 | } 300 | 301 | uint util_pio_get_pis_from_pio_interrupt_num( 302 | const uint pio_interrupt_num) { 303 | 304 | assert(util_routable_pio_interrupt_num_is_valid( 305 | pio_interrupt_num)); 306 | 307 | const uint basePis = pis_interrupt0; 308 | const uint pis = basePis + pio_interrupt_num; 309 | 310 | assert(util_uint_in_range( 311 | pis, pis_interrupt0, pis_interrupt3)); 312 | 313 | return pis; 314 | 315 | } 316 | 317 | void util_pio_gpio_contiguous_init( 318 | PIO const pio, 319 | const uint base, 320 | const uint len) { 321 | 322 | check_pio_param(pio); 323 | assert(len > 0); 324 | 325 | const uint l = base + len - 1; 326 | 327 | for(uint i = base; i <= l; ++i) { 328 | check_gpio_param(i); 329 | pio_gpio_init(pio, i); 330 | } 331 | 332 | } 333 | 334 | void util_pio_sm_clear_rx_fifo( 335 | PIO const pio, 336 | const uint sm) { 337 | check_pio_param(pio); 338 | check_sm_param(sm); 339 | while(!pio_sm_is_rx_fifo_empty(pio, sm)) { 340 | pio_sm_get(pio, sm); 341 | } 342 | } 343 | 344 | void util_pio_sm_clear_osr( 345 | PIO const pio, 346 | const uint sm) { 347 | check_pio_param(pio); 348 | check_sm_param(sm); 349 | const uint instr = pio_encode_push(false, false); 350 | while(!pio_sm_is_rx_fifo_empty(pio, sm)) { 351 | pio_sm_exec(pio, sm, instr); 352 | } 353 | } 354 | 355 | void util_pio_sm_clear_isr( 356 | PIO const pio, 357 | const uint sm) { 358 | check_pio_param(pio); 359 | check_sm_param(sm); 360 | const uint instr = pio_encode_pull(false, false); 361 | while(!pio_sm_is_tx_fifo_empty(pio, sm)) { 362 | pio_sm_exec(pio, sm, instr); 363 | } 364 | } 365 | 366 | bool util_pio_sm_is_enabled( 367 | PIO const pio, 368 | const uint sm) { 369 | check_pio_param(pio); 370 | check_sm_param(sm); 371 | return (pio->ctrl & (1u << (PIO_CTRL_SM_ENABLE_LSB + sm))) != 0; 372 | } 373 | 374 | bool util_pio_interrupt_num_is_valid( 375 | const uint pio_interrupt_num) { 376 | return util_uint_in_range( 377 | pio_interrupt_num, 378 | UTIL_PIO_INTERRUPT_NUM_MIN, 379 | UTIL_PIO_INTERRUPT_NUM_MAX); 380 | } 381 | 382 | bool util_routable_pio_interrupt_num_is_valid( 383 | const uint pio_interrupt_num) { 384 | return util_uint_in_range( 385 | pio_interrupt_num, 386 | UTIL_ROUTABLE_PIO_INTERRUPT_NUM_MIN, 387 | UTIL_ROUTABLE_PIO_INTERRUPT_NUM_MAX); 388 | } 389 | 390 | void util_pio_interrupt_wait( 391 | PIO const pio, 392 | const uint pio_interrupt_num) { 393 | check_pio_param(pio); 394 | assert(util_pio_interrupt_num_is_valid(pio_interrupt_num)); 395 | while(!pio_interrupt_get(pio, pio_interrupt_num)) { 396 | tight_loop_contents(); 397 | } 398 | } 399 | 400 | void util_pio_interrupt_wait_cleared( 401 | PIO const pio, 402 | const uint pio_interrupt_num) { 403 | check_pio_param(pio); 404 | assert(util_pio_interrupt_num_is_valid(pio_interrupt_num)); 405 | while(pio_interrupt_get(pio, pio_interrupt_num)) { 406 | tight_loop_contents(); 407 | } 408 | } 409 | 410 | bool util_pio_interrupt_wait_cleared_timeout( 411 | PIO const pio, 412 | const uint pio_interrupt_num, 413 | const absolute_time_t* const end) { 414 | 415 | check_pio_param(pio); 416 | assert(util_pio_interrupt_num_is_valid(pio_interrupt_num)); 417 | assert(end != NULL); 418 | assert(!is_nil_time(*end)); 419 | 420 | while(!time_reached(*end)) { 421 | if(!pio_interrupt_get(pio, pio_interrupt_num)) { 422 | return true; 423 | } 424 | } 425 | 426 | return false; 427 | 428 | } 429 | 430 | void util_pio_interrupt_wait_clear( 431 | PIO const pio, 432 | const uint pio_interrupt_num) { 433 | check_pio_param(pio); 434 | assert(util_pio_interrupt_num_is_valid(pio_interrupt_num)); 435 | util_pio_interrupt_wait(pio, pio_interrupt_num); 436 | pio_interrupt_clear(pio, pio_interrupt_num); 437 | } 438 | 439 | bool util_pio_interrupt_wait_timeout( 440 | PIO const pio, 441 | const uint pio_interrupt_num, 442 | const absolute_time_t* const end) { 443 | 444 | check_pio_param(pio); 445 | assert(util_pio_interrupt_num_is_valid(pio_interrupt_num)); 446 | assert(end != NULL); 447 | assert(!is_nil_time(*end)); 448 | 449 | while(!time_reached(*end)) { 450 | if(pio_interrupt_get(pio, pio_interrupt_num)) { 451 | return true; 452 | } 453 | } 454 | 455 | return false; 456 | 457 | } 458 | 459 | bool util_pio_interrupt_wait_clear_timeout( 460 | PIO const pio, 461 | const uint pio_interrupt_num, 462 | const absolute_time_t* const end) { 463 | 464 | check_pio_param(pio); 465 | assert(util_pio_interrupt_num_is_valid(pio_interrupt_num)); 466 | assert(end != NULL); 467 | assert(!is_nil_time(*end)); 468 | 469 | const bool ok = util_pio_interrupt_wait_timeout( 470 | pio, 471 | pio_interrupt_num, 472 | end); 473 | 474 | if(ok) { 475 | pio_interrupt_clear( 476 | pio, 477 | pio_interrupt_num); 478 | } 479 | 480 | return ok; 481 | 482 | } 483 | 484 | bool util_pio_sm_try_get( 485 | PIO const pio, 486 | const uint sm, 487 | uint32_t* const word, 488 | const uint threshold) { 489 | 490 | check_pio_param(pio); 491 | check_sm_param(sm); 492 | assert(word != NULL); 493 | 494 | if(pio_sm_get_rx_fifo_level(pio, sm) >= threshold) { 495 | *word = pio_sm_get(pio, sm); 496 | return true; 497 | } 498 | 499 | return false; 500 | 501 | } 502 | 503 | #undef UTIL_DEF_IN_RANGE_FUNC 504 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hx711-pico-c 2 | 3 | This is my implementation of reading from a HX711 via a Raspberry Pi Pico. It uses the RP2040's PIO feature to be as efficient as possible. It has two major functions: reading from a [single HX711](#how-to-use-hx711_t) and reading from [multiple HX711s](#how-to-use-hx711_multi_t). 4 | 5 | A MicroPython port is available [here](https://github.com/endail/hx711-pico-mpy). 6 | 7 | __NOTE__: if you are looking for a method to weigh objects (ie. by using the HX711 as a scale), see [pico-scale](https://github.com/endail/pico-scale). 8 | 9 | ![resources/hx711_80sps_serialout.gif](resources/hx711_80sps_serialout.gif) 10 | 11 | The .gif above illustrates obtaining data from a single HX711 operating at 80 samples per second. 12 | 13 | ## Clone Repository 14 | 15 | ```console 16 | git clone https://github.com/endail/hx711-pico-c 17 | ``` 18 | 19 | Run `cmake` to build the example program. The `.uf2` file you upload to your Pico will be found under `build/tests/`. 20 | 21 | Alternatively, include it as a submodule in your project and then `#include "extern/hx711-pico-c/include/common.h"`. 22 | 23 | ```console 24 | git submodule add https://github.com/endail/hx711-pico-c extern/hx711-pico-c 25 | git submodule update --init 26 | ``` 27 | 28 | ## Documentation 29 | 30 | [https://endail.github.io/hx711-pico-c](https://endail.github.io/hx711-pico-c/hx711_8h.html) 31 | 32 | ## How to Use hx711_t 33 | 34 | See [here](https://pico.pinout.xyz/) for a pinout to choose two GPIO pins on the Pico (RP2040). One GPIO pin to connect to the HX711's clock pin and a second GPIO pin to connect to the HX711's data pin. You can choose any two pins as the clock and data pins, as long as they are capable of digital output and input respectively. 35 | 36 | ```c 37 | #include "include/common.h" 38 | 39 | // 1. Set configuration 40 | hx711_config_t hxcfg; 41 | hx711_get_default_config(&hxcfg); 42 | 43 | hxcfg.clock_pin = 14; //GPIO pin connected to HX711 clock pin 44 | hxcfg.data_pin = 15; //GPIO pin connected to HX711 data pin 45 | 46 | //by default, the underlying PIO program will run on pio0 47 | //if you need to change this, you can do: 48 | //hxcfg.pio = pio1; 49 | 50 | // 2. Initialise 51 | hx711_init(&hx, &hxcfg); 52 | 53 | // 3. Power up the hx711 and set gain on chip 54 | hx711_power_up(&hx, hx711_gain_128); 55 | 56 | // 4. This step is optional. Only do this if you want to 57 | // change the gain AND save it to the HX711 chip 58 | // 59 | // hx711_set_gain(&hx, hx711_gain_64); 60 | // hx711_power_down(&hx); 61 | // hx711_wait_power_down(); 62 | // hx711_power_up(&hx, hx711_gain_64); 63 | 64 | // 5. Wait for readings to settle 65 | hx711_wait_settle(rate); 66 | 67 | // 6. Read values 68 | // You can now... 69 | 70 | // wait (block) until a value is obtained 71 | printf("blocking value: %li\n", hx711_get_value(&hx)); 72 | 73 | // or use a timeout 74 | int32_t val; 75 | const uint timeout = 250000; //microseconds 76 | if(hx711_get_value_timeout(&hx, timeout, &val)) { 77 | // value was obtained within the timeout period 78 | printf("timeout value: %li\n", val); 79 | } 80 | else { 81 | printf("value was not obtained within the timeout period\n"); 82 | } 83 | 84 | // or see if there's a value, but don't block if there isn't one ready 85 | if(hx711_get_value_noblock(&hx, &val)) { 86 | printf("noblock value: %li\n", val); 87 | } 88 | else { 89 | printf("value was not present\n"); 90 | } 91 | 92 | //6. Stop communication with HX711 93 | hx711_close(&hx); 94 | ``` 95 | 96 | ## How to Use hx711_multi_t 97 | 98 | See [here](https://pico.pinout.xyz/) for a pinout to choose at least two separate GPIO pins on the Pico (RP2040). 99 | 100 | * One GPIO pin to connect to __every__ HX711's clock pin. 101 | * One or more __contiguous__ GPIO pins to separately connect to each HX711's data pin. 102 | 103 | For example, if you wanted to connect four HX711 chips, you could: 104 | 105 | * Connect GPIO pin 9 to each HX711's clock pin; and 106 | * Connect GPIO pins 12, 13, 14, and 15 to each separate HX711's data pin. 107 | 108 | See the code example below for how you would set this up. You can choose any pins as the clock and data pins, as long as they are capable of digital output and input respectively. 109 | 110 | You can connect up to 32 HX711s, although the Pico (RP2040) will limit you to the available pins. 111 | 112 | Note: each chip should use the same sample rate. Using chips with different sample rates will lead to unpredictible results. 113 | 114 | ```c 115 | #include "../include/common.h" 116 | 117 | // 1. Set configuration 118 | hx711_multi_config_t hxmcfg; 119 | hx711_multi_get_default_config(&hxmcfg); 120 | hxmcfg.clock_pin = 9; //GPIO pin connected to each HX711 chip 121 | hxmcfg.data_pin_base = 12; //first GPIO pin used to connect to HX711 data pin 122 | hxmcfg.chips_len = 4; //how many HX711 chips connected 123 | 124 | //by default, the underlying PIO programs will run on pio0 125 | //if you need to change this, you can do: 126 | //hxmcfg.pio = pio1; 127 | 128 | // 2. Initialise 129 | hx711_multi_t hxm; 130 | hx711_multi_init(&hxm, &hxmcfg); 131 | 132 | // 3. Power up the HX711 chips and set gain on each chip 133 | hx711_multi_power_up(&hxm, hx711_gain_128); 134 | 135 | // 4. This step is optional. Only do this if you want to 136 | // change the gain AND save it to each HX711 chip 137 | // 138 | // hx711_multi_set_gain(&hxm, hx711_gain_64); 139 | // hx711_multi_power_down(&hxm); 140 | // hx711_wait_power_down(); 141 | // hx711_multi_power_up(&hxm, hx711_gain_64); 142 | 143 | // 5. Wait for readings to settle 144 | hx711_wait_settle(hx711_gain_128); 145 | 146 | // 6. Read values 147 | int32_t arr[hxmcfg.chips_len]; 148 | 149 | // 6a. wait (block) until values are ready 150 | hx711_multi_get_values(&hxm, arr); 151 | 152 | // then print the value from each chip 153 | // the first value in the array is from the HX711 154 | // connected to the first configured data pin and 155 | // so on 156 | for(uint i = 0; i < hxmcfg.chips_len; ++i) { 157 | printf("hx711_multi_t chip %i: %li\n", i, arr[i]); 158 | } 159 | 160 | // 6b. use a timeout 161 | if(hx711_multi_get_values_timeout(&hxm, arr, 250000)) { 162 | // do something with arr 163 | } 164 | 165 | // 6c. or read values asynchronously 166 | hx711_multi_async_start(&hxm); 167 | 168 | // do other work while waiting for values... 169 | 170 | // use hx711_multi_async_done(&hxm) to check 171 | // if values are ready, then... 172 | 173 | hx711_multi_async_get_values(&hxm, arr); 174 | 175 | // do something with arr 176 | 177 | // 7. Stop communication with all HX711 chips 178 | hx711_multi_close(&hxm); 179 | ``` 180 | 181 | ## Notes 182 | 183 | ### Where is Channel A and Channel B? 184 | 185 | Channel A is selectable by setting the gain to 128 or 64. Channel B is selectable by setting the gain to 32. 186 | 187 | The HX711 has no option for Channel A at a gain of 32, nor is there an option for Channel B at a gain of 128 or 64. Similarly, the HX711 is not capable of reading from Channel A and Channel B simultaneously. The gain must first be changed. 188 | 189 | ### What is hx711_wait_settle? 190 | 191 | After powering up, the HX711 requires a small "settling time" before it can produce "valid stable output data" (see: HX711 datasheet pg. 3). By calling `hx711_wait_settle()` and passing in the correct data rate, you can ensure your program is paused for the correct settling time. Alternatively, you can call `hx711_get_settling_time()` and pass in a `hx711_rate_t` which will return the number of milliseconds of settling time for the given data rate. 192 | 193 | ### What is hx711_wait_power_down? 194 | 195 | The HX711 requires the clock pin to be held high for at least 60us (60 microseconds) before it powers down. By calling `hx711_wait_power_down()` after `hx711_power_down()` you can ensure the chip is properly powered-down. 196 | 197 | ### Save HX711 Gain to Chip 198 | 199 | By setting the HX711 gain with `hx711_set_gain` and then powering down, the chip saves the gain for when it is powered back up. This is a feature built-in to the HX711. 200 | 201 | ### Powering up with Unknown or Incorrect Gain 202 | 203 | When calling `hx711_power_up()` or `hx711_multi_power_up()` it is assumed that the gain value passed to these functions indicates the [previously saved gain](#save-hx711-gain-to-chip) value in the chip. If the previously saved gain is unknown, you can either: 204 | 205 | 1. Power up with the gain you want then perform at least one read of the chip (eg. `hx711_get_value()`, `hx711_multi_get_values()`, etc...), and the subsequent reads will have the correct gain; or 206 | 207 | 2. Power up with any gain and then call `hx711_set_gain()` or `hx711_multi_set_gain()` with the gain you want. 208 | 209 | ### hx711_close/hx711_multi_close vs hx711_power_down/hx711_multi_power_down 210 | 211 | In the example code above, the final statement closes communication with the HX711. This leaves the HX711 in a powered-up state. `hx711_close` and `hx711_multi_close` stops the internal state machines from reading data from the HX711. Whereas `hx711_power_down` and `hx711_multi_power_down` also begins the power down process on a HX711 chip by setting the clock pin high. 212 | 213 | ### Synchronising Multiple Chips 214 | 215 | When using multiple HX711 chips, it is possible they may be desynchronised if not powered up simultaneously. You can use `hx711_multi_sync()` which will power down and then power up all chips together. 216 | 217 | ### PIO + DMA Interrupt Specifics 218 | 219 | When using `hx711_multi_t`, two interrupts are claimed: one for a PIO interrupt and one for a DMA interrupt. By default, `PIO[N]_IRQ_0` and `DMA_IRQ_0` are used, where `[N]` is the PIO index being used (ie. configuring `hx711_multi_t` with `pio0` means the resulting interrupt is `PIO0_IRQ_0` and `pio1` results in `PIO1_IRQ_0`). If you need to change the IRQ _index_ for either PIO or DMA, you can do this when configuring. 220 | 221 | ```c 222 | hx711_multi_config_t hxmcfg; 223 | hx711_multi_get_default_config(&hxmcfg); 224 | hxmcfg.pio = pio1; 225 | hxmcfg.pio_irq_index = 0; //PIO1_IRQ_0 is claimed 226 | hxmcfg.dma_irq_index = 1; //DMA_IRQ_1 is claimed 227 | ``` 228 | 229 | ### Mutex? 230 | 231 | Mutex functionality is included and enabled by default to protect the HX711 conversion process. If you are sure you do not need it, define the preprocessor flag `HX711_NO_MUTEX` then recompile. 232 | 233 | ### Custom PIO Programs 234 | 235 | `#include include/common.h` includes the PIO programs I have created for both `hx711_t` and `hx711_multi_t`. Calling `hx711_get_default_config()` and `hx711_multi_get_default_config()` will include those PIO programs in the configurations. If you want to change or use your own PIO programs, set the relevant `hx711_*_config_t` defaults, and do the following: 236 | 237 | ```c 238 | hx711_config_t hxcfg; 239 | hxcfg.pio_init = my_hx_pio_init_func; // function to setup the PIO 240 | hxcfg.reader_prog = my_pio_program; // pio_program_t* 241 | hxcfg.reader_prog_init = my_pio_program_init_func; // function to setup the PIO program 242 | ``` 243 | 244 | `hxcfg.pio_init` and `hxcfg.pio_prog_init` take a pointer to the `hx711_t` as the only parameter. 245 | 246 | ```c 247 | hx711_multi_config_t hxmcfg; 248 | hxmcfg.pio_init = my_hxm_pio_init_func; // function to setup the PIO 249 | hxmcfg.awaiter_prog = my_pio_awaiter_program; // pio_program_t* 250 | hxmcfg.awaiter_prog_init = my_hxm_awaiter_init_func; // function to setup the awaiter PIO program 251 | hxmcfg.reader_prog = my_pio_reader_progam; // pio_program_t* 252 | hxmcfg.reader_prog_init = my_pio_reader_program_init; // function to setup the reader PIO progam 253 | ``` 254 | 255 | `hxmcfg.pio_init`, `hxmcfg.awaiter_prog_init`, and `hxmcfg.reader_prog_init` take a pointer to the `hx711_multi_t` as the only parameter. 256 | 257 | ## Overview of Functionality 258 | 259 | ### `hx711_t` 260 | 261 | The single chip `hx711_t` functions with a single RP2040 State Machine (SM) in one PIO. This includes setting and changing the HX711's gain. The SM is configured to be free-running which constantly obtains values from the HX711. Values are buffered in the SM's RX FIFO which enables application code to retrieve the most up-to-date value possible. Reading from the RX FIFO simultaneously clears it, so applications are simply able to busy-wait on the RX_FIFO being filled for the next value. 262 | 263 | ### `hx711_multi_t` 264 | 265 | The multi chip `hx711_multi_t` functions with two RP2040 State Machines (SM) in one PIO. This includes setting and changing all HX711s gains. The first SM is the "awaiter". It is free-running. It constantly reads the pin state of each RP2040 GPIO pin configured as a data input pin. If and when every pin is low - indicating that every chip has data ready - a PIO interrupt is set. 266 | 267 | The second SM is the "reader". It is also free-running. The reader waits for the data ready PIO interrupt from the awaiter and then begins the HX711 conversion period of reading in bits. The conversion period is synchronised with application code by setting and clearing an interrupt to denote when a conversion period is in progress. 268 | 269 | The reader clocks in the state of each data input pin as a bitmask and then pushes it back out of the SM into the RX FIFO. There are 24 pushes; one for each HX711 bit. Due to the size of the RX FIFO only being 32 bits, a SM is not capable of buffering all HX711 input bits when there are multiple chips. Hence why there is a `push` for each HX711 bit. 270 | 271 | On the receiving end of the SM is a DMA channel which automatically reads in each bitmask of HX711 bits into an array. These bitmasks are then transformed into HX711 values for each chip and returned to application code. 272 | 273 | ### Additional Notes 274 | 275 | * `channel_config_set_ring` in conjunction with a static array buffer to constantly read in values from the SM lead to misaligned write addresses. As the HX711 uses 3 bytes to represent a value and the ring buffer requires a "naturally aligned buffer", it would take another byte to "reset" the ring back to the initial address. An application could not simply read the buffer and obtain valid value. 276 | 277 | * Two DMA channels in a ping-pong configuration to activate each other were tried as a method to keep the application-side reading in values. But there did not appear to be a straightforward method to constantly reset the write address back to the address of an array buffer. There also seemed to be an inherent race condition in having DMA write to a buffer when an application could read from it at any moment without a method to protect access to it which DMA would abide by. 278 | 279 | * A major disdvantage of the HX711 conversion period process is the time it takes to both wait for it to begin and complete. A processor could be doing other work. This is where DMA is particularly advantageous because the processor _can_ do other work even while waiting for a value to be clocked-in. 280 | 281 | * Reading in the state of all configured data input pins as a bitmask has the advantage of being simultaneous on all HX711 chips. If each bit of each HX711 were to be clocked in round robin style, it would be much slower and run the risk of holding the clock pin for longer than the power down period. The second issue would lead to one or more chips powering down and becoming desynchronised from each other. 282 | -------------------------------------------------------------------------------- /src/hx711.c: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2023 Daniel Robertson 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | #include 24 | #include 25 | #include "hardware/gpio.h" 26 | #include "hardware/pio.h" 27 | #include "hardware/timer.h" 28 | #include "pico/platform.h" 29 | #include "pico/mutex.h" 30 | #include "pico/time.h" 31 | #include "../include/hx711.h" 32 | #include "../include/util.h" 33 | 34 | const unsigned short HX711_SETTLING_TIMES[] = { 35 | 400, 36 | 50 37 | }; 38 | 39 | const unsigned char HX711_SAMPLE_RATES[] = { 40 | 10, 41 | 80 42 | }; 43 | 44 | const unsigned char HX711_CLOCK_PULSES[] = { 45 | 25, 46 | 26, 47 | 27 48 | }; 49 | 50 | void hx711_init( 51 | hx711_t* const hx, 52 | const hx711_config_t* const config) { 53 | 54 | assert(!hx711__is_initd(hx)); 55 | 56 | assert(hx != NULL); 57 | assert(config != NULL); 58 | 59 | assert(config->pio != NULL); 60 | assert(config->pio_init != NULL); 61 | 62 | assert(config->reader_prog != NULL); 63 | assert(config->reader_prog_init != NULL); 64 | 65 | check_gpio_param(config->clock_pin); 66 | check_gpio_param(config->data_pin); 67 | assert(config->clock_pin != config->data_pin); 68 | 69 | #ifndef HX711_NO_MUTEX 70 | mutex_init(&hx->_mut); 71 | #endif 72 | 73 | HX711_MUTEX_BLOCK(hx->_mut, 74 | 75 | hx->_clock_pin = config->clock_pin; 76 | hx->_data_pin = config->data_pin; 77 | hx->_pio = config->pio; 78 | hx->_reader_prog = config->reader_prog; 79 | 80 | util_gpio_set_output(hx->_clock_pin); 81 | 82 | /** 83 | * There was originally a call here to gpio_put on the 84 | * clock pin to power up the HX711. I have decided to 85 | * remove this and also remove enabling the state 86 | * machine from the pio init function. This does leave 87 | * the HX711's power state undefined from the 88 | * perspective of the code, but does give a much clearer 89 | * separation of duties. This function merely init's the 90 | * hardware and state machine, and the hx711_set_power 91 | * function sets the power and enables/disables the 92 | * state machine. 93 | */ 94 | 95 | gpio_set_input_enabled( 96 | hx->_data_pin, 97 | true); 98 | 99 | /** 100 | * There was originally a call here to gpio_pull_up 101 | * on the data pin to prevent erroneous data ready 102 | * states. This was incorrect. Page 4 of the datasheet 103 | * states: "The 25th pulse at PD_SCK input will pull 104 | * DOUT pin back to high (Fig.2)." 105 | */ 106 | 107 | //either statement below will panic if it fails 108 | hx->_reader_offset = pio_add_program( 109 | hx->_pio, 110 | hx->_reader_prog); 111 | 112 | hx->_reader_sm = (uint)pio_claim_unused_sm( 113 | hx->_pio, 114 | true); 115 | 116 | config->pio_init(hx); 117 | config->reader_prog_init(hx); 118 | 119 | ); 120 | 121 | } 122 | 123 | void hx711_close(hx711_t* const hx) { 124 | 125 | //state machines do not have to be running in order 126 | //to close 127 | assert(hx711__is_initd(hx)); 128 | 129 | HX711_MUTEX_BLOCK(hx->_mut, 130 | 131 | pio_sm_set_enabled( 132 | hx->_pio, 133 | hx->_reader_sm, 134 | false); 135 | 136 | pio_sm_unclaim( 137 | hx->_pio, 138 | hx->_reader_sm); 139 | 140 | pio_remove_program( 141 | hx->_pio, 142 | hx->_reader_prog, 143 | hx->_reader_offset); 144 | 145 | ); 146 | 147 | } 148 | 149 | void hx711_set_gain(hx711_t* const hx, const hx711_gain_t gain) { 150 | 151 | assert(hx711__is_state_machine_enabled(hx)); 152 | assert(hx711_is_gain_valid(gain)); 153 | 154 | const uint32_t pioGain = hx711_gain_to_pio_gain(gain); 155 | 156 | assert(hx711_is_pio_gain_valid(pioGain)); 157 | 158 | HX711_MUTEX_BLOCK(hx->_mut, 159 | 160 | /** 161 | * Before putting anything in the TX FIFO buffer, 162 | * assume the worst-case scenario which is that 163 | * there's something already in there. There ought 164 | * not to be, but clearing it ensures the following 165 | * pio_sm_put* call does not need to block as this 166 | * function to change the gain should take precedence. 167 | */ 168 | pio_sm_drain_tx_fifo( 169 | hx->_pio, 170 | hx->_reader_sm); 171 | 172 | pio_sm_put( 173 | hx->_pio, 174 | hx->_reader_sm, 175 | pioGain); 176 | 177 | /** 178 | * At this point the current value in the RX FIFO will 179 | * have been calculated based on whatever the previous 180 | * set gain was. So, the RX FIFO needs to be cleared. 181 | * 182 | * NOTE: checking for whether the RX FIFO is not empty 183 | * won't work. A conversion may have already begun 184 | * before any bits have been moved into the ISR. 185 | * 186 | * UPDATE: the worst-case scenario here is that the 187 | * pio_sm_put call has occurred after the pio "pull", 188 | * because we then need to wait until the following 189 | * "pull" in the state machine. If this happens: 190 | * 191 | * 1. there may already be a value in the RX FIFO; and 192 | * 2. another value will need to be read and discarded 193 | * following which the new gain will be set. 194 | * 195 | * To handle 1.: Clear the RX FIFO with a non-blocking 196 | * read. If the RX FIFO is empty, no harm done because 197 | * the call won't block. 198 | * 199 | * To handle 2.: Read the "next" value with a blocking 200 | * read to ensure the "next, next" value will be set 201 | * to the desired gain. 202 | */ 203 | 204 | //1. clear the RX FIFO with the non-blocking read 205 | pio_sm_get( 206 | hx->_pio, 207 | hx->_reader_sm); 208 | 209 | //2. wait until the value from the currently-set gain 210 | //can be safely read and discarded 211 | pio_sm_get_blocking( 212 | hx->_pio, 213 | hx->_reader_sm); 214 | 215 | /** 216 | * Immediately following the above blocking call, the 217 | * state machine will pull in the data in the 218 | * pio_sm_put call above and pulse the HX711 the 219 | * correct number of times to set the desired gain. 220 | * 221 | * No further communication with the state machine 222 | * from this function is required. Any other function(s) 223 | * wishing to obtain a value from the HX711 need only 224 | * block until one is there (or check the RX FIFO level). 225 | */ 226 | 227 | ); 228 | 229 | } 230 | 231 | int32_t hx711_get_twos_comp(const uint32_t raw) { 232 | return 233 | (int32_t)(-(raw & +HX711_MIN_VALUE)) + 234 | (int32_t)(raw & HX711_MAX_VALUE); 235 | } 236 | 237 | bool hx711_is_min_saturated(const int32_t val) { 238 | assert(hx711_is_value_valid(val)); 239 | return val == HX711_MIN_VALUE; 240 | } 241 | 242 | bool hx711_is_max_saturated(const int32_t val) { 243 | assert(hx711_is_value_valid(val)); 244 | return val == HX711_MAX_VALUE; 245 | } 246 | 247 | unsigned short hx711_get_settling_time(const hx711_rate_t rate) { 248 | assert(hx711_is_rate_valid(rate)); 249 | assert((int)rate <= count_of(HX711_SETTLING_TIMES) - 1); 250 | return HX711_SETTLING_TIMES[(int)rate]; 251 | } 252 | 253 | unsigned char hx711_get_rate_sps(const hx711_rate_t rate) { 254 | assert(hx711_is_rate_valid(rate)); 255 | assert((int)rate <= count_of(HX711_SAMPLE_RATES) - 1); 256 | return HX711_SAMPLE_RATES[(int)rate]; 257 | } 258 | 259 | unsigned char hx711_get_clock_pulses(const hx711_gain_t gain) { 260 | assert(hx711_is_gain_valid(gain)); 261 | assert((int)gain <= count_of(HX711_CLOCK_PULSES) - 1); 262 | return HX711_CLOCK_PULSES[(int)gain]; 263 | } 264 | 265 | int32_t hx711_get_value(hx711_t* const hx) { 266 | 267 | assert(hx711__is_state_machine_enabled(hx)); 268 | 269 | uint32_t rawVal; 270 | 271 | HX711_MUTEX_BLOCK(hx->_mut, 272 | 273 | /** 274 | * Block until a value is available 275 | * 276 | * NOTE: remember that reading from the RX FIFO 277 | * simultaneously clears it. That's why we can keep 278 | * calling this function hx711_get_value and be 279 | * assured we'll be getting a new value each time, 280 | * even if the RX FIFO is currently empty. 281 | */ 282 | rawVal = pio_sm_get_blocking( 283 | hx->_pio, 284 | hx->_reader_sm); 285 | 286 | ); 287 | 288 | return hx711_get_twos_comp(rawVal); 289 | 290 | } 291 | 292 | bool hx711_get_value_timeout( 293 | hx711_t* const hx, 294 | int32_t* const val, 295 | const uint timeout) { 296 | 297 | assert(hx711__is_state_machine_enabled(hx)); 298 | assert(val != NULL); 299 | 300 | bool success = false; 301 | const absolute_time_t endTime = make_timeout_time_us(timeout); 302 | uint32_t tempVal; 303 | 304 | assert(!is_nil_time(endTime)); 305 | 306 | HX711_MUTEX_BLOCK(hx->_mut, 307 | while(!time_reached(endTime)) { 308 | if((success = hx711__try_get_value(hx->_pio, hx->_reader_sm, &tempVal))) { 309 | break; 310 | } 311 | } 312 | ); 313 | 314 | if(success) { 315 | *val = hx711_get_twos_comp(tempVal); 316 | } 317 | 318 | return success; 319 | 320 | } 321 | 322 | bool hx711_get_value_noblock( 323 | hx711_t* const hx, 324 | int32_t* const val) { 325 | 326 | assert(hx711__is_state_machine_enabled(hx)); 327 | assert(val != NULL); 328 | 329 | bool success; 330 | uint32_t tempVal; 331 | 332 | HX711_MUTEX_BLOCK(hx->_mut, 333 | success = hx711__try_get_value( 334 | hx->_pio, 335 | hx->_reader_sm, 336 | &tempVal); 337 | ); 338 | 339 | if(success) { 340 | *val = hx711_get_twos_comp(tempVal); 341 | } 342 | 343 | return success; 344 | 345 | } 346 | 347 | bool hx711__is_initd(hx711_t* const hx) { 348 | return hx != NULL && 349 | hx->_pio != NULL && 350 | #ifndef HX711_NO_MUTEX 351 | mutex_is_initialized(&hx->_mut) && 352 | #endif 353 | pio_sm_is_claimed(hx->_pio, hx->_reader_sm); 354 | } 355 | 356 | bool hx711__is_state_machine_enabled(hx711_t* const hx) { 357 | return hx711__is_initd(hx) && 358 | util_pio_sm_is_enabled(hx->_pio, hx->_reader_sm); 359 | } 360 | 361 | bool hx711_is_value_valid(const int32_t v) { 362 | return util_int32_t_in_range( 363 | v, 364 | HX711_MIN_VALUE, 365 | HX711_MAX_VALUE); 366 | } 367 | 368 | bool hx711_is_pio_gain_valid(const uint32_t g) { 369 | return util_uint32_t_in_range( 370 | g, 371 | HX711_PIO_MIN_GAIN, 372 | HX711_PIO_MAX_GAIN); 373 | } 374 | 375 | bool hx711_is_rate_valid(const hx711_rate_t r) { 376 | return util_int_in_range( 377 | (int)r, 378 | hx711_rate_10, 379 | hx711_rate_80); 380 | } 381 | 382 | bool hx711_is_gain_valid(const hx711_gain_t g) { 383 | return util_int_in_range( 384 | (int)g, 385 | hx711_gain_128, 386 | hx711_gain_64); 387 | } 388 | 389 | void hx711_power_up( 390 | hx711_t* const hx, 391 | const hx711_gain_t gain) { 392 | 393 | //the struct should be init'd, but the state machines 394 | //should not be running 395 | assert(hx711__is_initd(hx)); 396 | assert(!hx711__is_state_machine_enabled(hx)); 397 | 398 | assert(hx711_is_gain_valid(gain)); 399 | 400 | const uint32_t gainVal = hx711_gain_to_pio_gain(gain); 401 | 402 | assert(hx711_is_pio_gain_valid(gainVal)); 403 | 404 | HX711_MUTEX_BLOCK(hx->_mut, 405 | 406 | /** 407 | * NOTE: pio_sm_restart should not be used here. 408 | * That function clears the clock divider which is 409 | * currently set in the dedicated pio init function. 410 | */ 411 | 412 | /** 413 | * 1. set the clock pin low to power up the chip 414 | * 415 | * There does not appear to be any delay after 416 | * powering up. Any actual delay would presumably be 417 | * dealt with by the HX711 prior to the data pin 418 | * going low. Which, in turn, is handled by the state 419 | * machine in waiting for the low signal. 420 | */ 421 | gpio_put( 422 | hx->_clock_pin, 423 | false); 424 | 425 | //2. reset the state machine using the default config 426 | //obtained when init'ing. 427 | pio_sm_init( 428 | hx->_pio, 429 | hx->_reader_sm, 430 | hx->_reader_offset, 431 | &hx->_reader_prog_default_config); 432 | 433 | //make sure TX FIFO is empty before putting the 434 | //gain in. 435 | pio_sm_drain_tx_fifo( 436 | hx->_pio, 437 | hx->_reader_sm); 438 | 439 | //3. Push the initial gain into the TX FIFO 440 | pio_sm_put( 441 | hx->_pio, 442 | hx->_reader_sm, 443 | gainVal); 444 | 445 | //4. start the state machine 446 | pio_sm_set_enabled( 447 | hx->_pio, 448 | hx->_reader_sm, 449 | true); 450 | 451 | ); 452 | 453 | } 454 | 455 | void hx711_power_down(hx711_t* const hx) { 456 | 457 | //don't have to have SMs running; just check for init 458 | assert(hx711__is_initd(hx)); 459 | 460 | HX711_MUTEX_BLOCK(hx->_mut, 461 | 462 | //1. stop the state machine 463 | pio_sm_set_enabled( 464 | hx->_pio, 465 | hx->_reader_sm, 466 | false); 467 | 468 | /** 469 | * 2. set clock pin high to start the power down 470 | * process 471 | * 472 | * NOTE: the HX711 chip requires the clock pin to 473 | * be held high for 60+ us 474 | * calling functions should therefore do: 475 | * 476 | * hx711_power_down(&hx); 477 | * hx711_wait_power_down(); 478 | */ 479 | gpio_put( 480 | hx->_clock_pin, 481 | true); 482 | 483 | ); 484 | 485 | } 486 | 487 | void hx711_wait_settle(const hx711_rate_t rate) { 488 | sleep_ms(hx711_get_settling_time(rate)); 489 | } 490 | 491 | void hx711_wait_power_down() { 492 | sleep_us(HX711_POWER_DOWN_TIMEOUT); 493 | } 494 | 495 | uint32_t hx711_gain_to_pio_gain(const hx711_gain_t gain) { 496 | 497 | /** 498 | * gain value is 0-based and calculated by: 499 | * gain = clock pulses - 24 - 1 500 | * ie. gain of 128 is 25 clock pulses, so 501 | * gain = 25 - 24 - 1 502 | * gain = 0 503 | */ 504 | 505 | assert(hx711_is_gain_valid(gain)); 506 | 507 | const uint32_t clockPulses = 508 | hx711_get_clock_pulses(gain) - HX711_READ_BITS - 1; 509 | 510 | assert(hx711_is_pio_gain_valid(clockPulses)); 511 | 512 | return clockPulses; 513 | 514 | } 515 | 516 | bool hx711__try_get_value( 517 | PIO const pio, 518 | const uint sm, 519 | uint32_t* const val) { 520 | 521 | assert(pio != NULL); 522 | check_sm_param(sm); 523 | assert(util_pio_sm_is_enabled(pio, sm)); 524 | assert(val != NULL); 525 | 526 | static const uint byteThreshold = HX711_READ_BITS / 8; 527 | 528 | return util_pio_sm_try_get( 529 | pio, 530 | sm, 531 | val, 532 | byteThreshold); 533 | 534 | } 535 | -------------------------------------------------------------------------------- /src/hx711_multi.c: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2023 Daniel Robertson 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include "hardware/dma.h" 29 | #include "hardware/gpio.h" 30 | #include "hardware/irq.h" 31 | #include "hardware/pio.h" 32 | #include "pico/mutex.h" 33 | #include "pico/platform.h" 34 | #include "pico/time.h" 35 | #include "pico/types.h" 36 | #include "../include/hx711.h" 37 | #include "../include/hx711_multi.h" 38 | #include "../include/util.h" 39 | 40 | hx711_multi_t* hx711_multi__async_read_array[] = { 41 | NULL, //... 42 | }; 43 | 44 | void hx711_multi__init_asert( 45 | const hx711_multi_config_t* const config) { 46 | 47 | assert(config != NULL); 48 | 49 | assert(util_uint_in_range( 50 | config->chips_len, 51 | HX711_MULTI_MIN_CHIPS, 52 | HX711_MULTI_MAX_CHIPS)); 53 | 54 | assert(config->pio != NULL); 55 | check_pio_param(config->pio); 56 | assert(config->pio_init != NULL); 57 | 58 | assert(config->awaiter_prog != NULL); 59 | assert(config->awaiter_prog_init != NULL); 60 | 61 | assert(config->reader_prog != NULL); 62 | assert(config->reader_prog_init != NULL); 63 | 64 | assert(util_pio_irq_index_is_valid(config->pio_irq_index)); 65 | assert(util_dma_irq_index_is_valid(config->dma_irq_index)); 66 | 67 | #ifndef NDEBUG 68 | { 69 | //make sure none of the data pins are also the clock pin 70 | const uint l = config->data_pin_base + config->chips_len - 1; 71 | for(uint i = config->data_pin_base; i <= l; ++i) { 72 | assert(i != config->clock_pin); 73 | } 74 | } 75 | #endif 76 | 77 | } 78 | 79 | void hx711_multi__init_pio(hx711_multi_t* const hxm) { 80 | 81 | //adding programs and claiming state machines 82 | //will panic if unable; this is appropriate. 83 | hxm->_awaiter_offset = pio_add_program( 84 | hxm->_pio, 85 | hxm->_awaiter_prog); 86 | 87 | hxm->_reader_offset = pio_add_program( 88 | hxm->_pio, 89 | hxm->_reader_prog); 90 | 91 | /** 92 | * Casting pio_claim_unused_sm to uint is OK in this 93 | * circumstance. Ordinarily it would return -1 if the 94 | * claim failed, but since the flag is given to require 95 | * a PIO State Machine, panic would be called instead. 96 | */ 97 | 98 | hxm->_awaiter_sm = (uint)pio_claim_unused_sm( 99 | hxm->_pio, 100 | true); 101 | 102 | hxm->_reader_sm = (uint)pio_claim_unused_sm( 103 | hxm->_pio, 104 | true); 105 | 106 | } 107 | 108 | void hx711_multi__init_dma(hx711_multi_t* const hxm) { 109 | 110 | /** 111 | * Casting dma_claim_unused_channel to uint is OK in this 112 | * circumstance. Ordinarily it would return -1 if the 113 | * claim failed, but since the flag is given to require 114 | * a DMA channel, panic would be called instead. 115 | */ 116 | hxm->_dma_channel = (uint)dma_claim_unused_channel(true); 117 | 118 | dma_channel_config cfg = dma_channel_get_default_config( 119 | hxm->_dma_channel); 120 | 121 | /** 122 | * Do not set ring buffer. 123 | * ie. do not use channel_config_set_ring. 124 | * If, for whatever reason, the DMA transfer 125 | * fails, subsequent transfer invocations 126 | * will reset the write address. 127 | */ 128 | 129 | /** 130 | * PIO RX FIFO output is 32 bits, so the DMA read needs 131 | * to match or be larger. 132 | */ 133 | channel_config_set_transfer_data_size( 134 | &cfg, 135 | DMA_SIZE_32); 136 | 137 | /** 138 | * DMA is always going to read from the same location, 139 | * which is the PIO FIFO. 140 | */ 141 | channel_config_set_read_increment( 142 | &cfg, 143 | false); 144 | 145 | /** 146 | * Each successive read from the PIO RX FIFO needs to be 147 | * to the next array buffer position. 148 | */ 149 | channel_config_set_write_increment( 150 | &cfg, 151 | true); 152 | 153 | /** 154 | * DMA transfers are paced based on the PIO RX DREQ. 155 | */ 156 | channel_config_set_dreq( 157 | &cfg, 158 | pio_get_dreq( 159 | hxm->_pio, 160 | hxm->_reader_sm, 161 | false)); 162 | 163 | /** 164 | * Quiet needs to be disabled in order for DMA to raise 165 | * an interrupt when each transfer is complete. This is 166 | * necessary for this implementation to work. 167 | */ 168 | channel_config_set_irq_quiet( 169 | &cfg, 170 | false); 171 | 172 | dma_channel_configure( 173 | hxm->_dma_channel, 174 | &cfg, 175 | NULL, //don't set a write address yet 176 | &hxm->_pio->rxf[hxm->_reader_sm], //read from reader pio program rx fifo 177 | HX711_READ_BITS, //24 transfers; one for each HX711 bit 178 | false); //false = don't start now 179 | 180 | } 181 | 182 | void hx711_multi__init_irq(hx711_multi_t* const hxm) { 183 | 184 | /** 185 | * The idea here is that the PIO and DMA IRQs can be 186 | * set up, enabled, and routed out to NVIC IRQs and 187 | * be enabled at this point. If and when IRQs need to 188 | * be disabled, they can be done at the source BEFORE 189 | * being routed out to NVIC and triggering a 190 | * system-wide interrupt. 191 | */ 192 | 193 | /** 194 | * DMA interrupts can always remain enabled. They will 195 | * only trigger following a PIO interrupt. 196 | */ 197 | dma_irqn_set_channel_enabled( 198 | hxm->_dma_irq_index, 199 | hxm->_dma_channel, 200 | true); 201 | 202 | irq_set_exclusive_handler( 203 | util_dma_get_irqn(hxm->_dma_irq_index), 204 | hx711_multi__async_dma_irq_handler); 205 | 206 | irq_set_enabled( 207 | util_dma_get_irqn(hxm->_dma_irq_index), 208 | true); 209 | 210 | /** 211 | * The PIO source interrupt MUST REMAIN DISABLED 212 | * until the point at which it is required to listen 213 | * to them. 214 | */ 215 | pio_set_irqn_source_enabled( 216 | hxm->_pio, 217 | hxm->_pio_irq_index, 218 | util_pio_get_pis_from_pio_interrupt_num( 219 | HX711_MULTI_CONVERSION_DONE_IRQ_NUM), 220 | false); 221 | 222 | irq_set_exclusive_handler( 223 | util_pio_get_irq_from_index(hxm->_pio, hxm->_pio_irq_index), 224 | hx711_multi__async_pio_irq_handler); 225 | 226 | irq_set_enabled( 227 | util_pio_get_irq_from_index(hxm->_pio, hxm->_pio_irq_index), 228 | true); 229 | 230 | } 231 | 232 | bool hx711_multi__async_dma_irq_is_set( 233 | hx711_multi_t* const hxm) { 234 | 235 | assert(hx711_multi__is_initd(hxm)); 236 | 237 | return dma_irqn_get_channel_status( 238 | hxm->_dma_irq_index, 239 | hxm->_dma_channel); 240 | 241 | } 242 | 243 | bool hx711_multi__async_pio_irq_is_set( 244 | hx711_multi_t* const hxm) { 245 | 246 | assert(hx711_multi__is_initd(hxm)); 247 | 248 | return pio_interrupt_get( 249 | hxm->_pio, 250 | HX711_MULTI_CONVERSION_DONE_IRQ_NUM); 251 | 252 | } 253 | 254 | hx711_multi_t* const hx711_multi__async_get_dma_irq_request() { 255 | 256 | assert(hx711_multi__async_read_array != NULL); 257 | 258 | for(uint i = 0; i < HX711_MULTI_ASYNC_READ_COUNT; ++i) { 259 | 260 | if(hx711_multi__async_read_array[i] == NULL) { 261 | continue; 262 | } 263 | 264 | if(hx711_multi__async_dma_irq_is_set(hx711_multi__async_read_array[i])) { 265 | return hx711_multi__async_read_array[i]; 266 | } 267 | 268 | } 269 | 270 | return NULL; 271 | 272 | } 273 | 274 | hx711_multi_t* const hx711_multi__async_get_pio_irq_request() { 275 | 276 | assert(hx711_multi__async_read_array != NULL); 277 | 278 | for(uint i = 0; i < HX711_MULTI_ASYNC_READ_COUNT; ++i) { 279 | 280 | if(hx711_multi__async_read_array[i] == NULL) { 281 | continue; 282 | } 283 | 284 | if(hx711_multi__async_pio_irq_is_set(hx711_multi__async_read_array[i])) { 285 | return hx711_multi__async_read_array[i]; 286 | } 287 | 288 | } 289 | 290 | return NULL; 291 | 292 | } 293 | 294 | void hx711_multi__async_start_dma( 295 | hx711_multi_t* const hxm) { 296 | 297 | assert(hx711_multi__is_state_machines_enabled(hxm)); 298 | assert(hxm->_async_state == HX711_MULTI_ASYNC_STATE_WAITING); 299 | 300 | util_pio_sm_clear_rx_fifo( 301 | hxm->_pio, 302 | hxm->_reader_sm); 303 | 304 | //listen for DMA done 305 | dma_irqn_set_channel_enabled( 306 | hxm->_dma_irq_index, 307 | hxm->_dma_channel, 308 | true); 309 | 310 | hxm->_async_state = HX711_MULTI_ASYNC_STATE_READING; 311 | 312 | dma_channel_set_write_addr( 313 | hxm->_dma_channel, 314 | hxm->_buffer, 315 | true); //trigger 316 | 317 | } 318 | 319 | bool hx711_multi__async_is_running( 320 | hx711_multi_t* const hxm) { 321 | 322 | assert(hx711_multi__is_state_machines_enabled(hxm)); 323 | 324 | switch(hxm->_async_state) { 325 | case HX711_MULTI_ASYNC_STATE_WAITING: 326 | case HX711_MULTI_ASYNC_STATE_READING: 327 | return true; 328 | default: 329 | //anything else is not considered running 330 | return false; 331 | } 332 | 333 | } 334 | 335 | static void hx711_multi__async_finish( 336 | hx711_multi_t* const hxm) { 337 | 338 | assert(hx711_multi__is_initd(hxm)); 339 | 340 | //stop listening for IRQs 341 | 342 | dma_channel_abort(hxm->_dma_channel); 343 | 344 | dma_irqn_set_channel_enabled( 345 | hxm->_dma_irq_index, 346 | hxm->_dma_channel, 347 | false); 348 | 349 | pio_set_irqn_source_enabled( 350 | hxm->_pio, 351 | hxm->_pio_irq_index, 352 | util_pio_get_pis_from_pio_interrupt_num(HX711_MULTI_CONVERSION_DONE_IRQ_NUM), 353 | false); 354 | 355 | #ifndef HX711_NO_MUTEX 356 | mutex_exit(&hxm->_mut); 357 | #endif 358 | 359 | } 360 | 361 | void __isr __not_in_flash_func(hx711_multi__async_pio_irq_handler)() { 362 | 363 | hx711_multi_t* const hxm = 364 | hx711_multi__async_get_pio_irq_request(); 365 | 366 | assert(hx711_multi__is_state_machines_enabled(hxm)); 367 | assert(hxm->_async_state == HX711_MULTI_ASYNC_STATE_WAITING); 368 | 369 | hx711_multi__async_start_dma(hxm); 370 | 371 | //disable listening until required again 372 | pio_set_irqn_source_enabled( 373 | hxm->_pio, 374 | hxm->_pio_irq_index, 375 | util_pio_get_pis_from_pio_interrupt_num(HX711_MULTI_CONVERSION_DONE_IRQ_NUM), 376 | false); 377 | 378 | irq_clear( 379 | util_pio_get_irq_from_index(hxm->_pio, hxm->_pio_irq_index)); 380 | 381 | } 382 | 383 | void __isr __not_in_flash_func(hx711_multi__async_dma_irq_handler)() { 384 | 385 | hx711_multi_t* const hxm = 386 | hx711_multi__async_get_dma_irq_request(); 387 | 388 | assert(hx711_multi__is_state_machines_enabled(hxm)); 389 | assert(hxm->_async_state == HX711_MULTI_ASYNC_STATE_READING); 390 | 391 | hxm->_async_state = HX711_MULTI_ASYNC_STATE_DONE; 392 | 393 | dma_irqn_acknowledge_channel( 394 | hxm->_dma_irq_index, 395 | hxm->_dma_channel); 396 | 397 | hx711_multi__async_finish(hxm); 398 | 399 | irq_clear( 400 | util_dma_get_irqn( 401 | hxm->_dma_irq_index)); 402 | 403 | } 404 | 405 | bool hx711_multi__async_add_reader( 406 | hx711_multi_t* const hxm) { 407 | 408 | assert(hx711_multi__async_read_array != NULL); 409 | 410 | for(uint i = 0; i < HX711_MULTI_ASYNC_READ_COUNT; ++i) { 411 | if(hx711_multi__async_read_array[i] == NULL) { 412 | hx711_multi__async_read_array[i] = hxm; 413 | return true; 414 | } 415 | } 416 | 417 | return false; 418 | 419 | } 420 | 421 | void hx711_multi__async_remove_reader( 422 | const hx711_multi_t* const hxm) { 423 | 424 | //we don't care whether it's initd at this point 425 | //or whether the SMs are running; just remove it 426 | //from the array 427 | assert(hxm != NULL); 428 | assert(hx711_multi__async_read_array != NULL); 429 | 430 | for(uint i = 0; i < HX711_MULTI_ASYNC_READ_COUNT; ++i) { 431 | if(hx711_multi__async_read_array[i] == hxm) { 432 | hx711_multi__async_read_array[i] = NULL; 433 | return; 434 | } 435 | } 436 | 437 | } 438 | 439 | static bool hx711_multi__is_initd(hx711_multi_t* const hxm) { 440 | return hxm != NULL && 441 | hxm->_pio != NULL && 442 | pio_sm_is_claimed(hxm->_pio, hxm->_awaiter_sm) && 443 | pio_sm_is_claimed(hxm->_pio, hxm->_reader_sm) && 444 | dma_channel_is_claimed(hxm->_dma_channel) && 445 | #ifndef HX711_NO_MUTEX 446 | mutex_is_initialized(&hxm->_mut) && 447 | #endif 448 | irq_get_exclusive_handler(util_pio_get_irq_from_index( 449 | hxm->_pio, 450 | hxm->_pio_irq_index)) == hx711_multi__async_pio_irq_handler && 451 | irq_get_exclusive_handler(util_dma_get_irqn( 452 | hxm->_dma_irq_index)) == hx711_multi__async_dma_irq_handler; 453 | } 454 | 455 | static bool hx711_multi__is_state_machines_enabled( 456 | hx711_multi_t* const hxm) { 457 | return hx711_multi__is_initd(hxm) && 458 | util_pio_sm_is_enabled(hxm->_pio, hxm->_awaiter_sm) && 459 | util_pio_sm_is_enabled(hxm->_pio, hxm->_reader_sm); 460 | } 461 | 462 | void hx711_multi_pinvals_to_values( 463 | const uint32_t* const pinvals, 464 | int32_t* const values, 465 | const size_t len) { 466 | 467 | assert(pinvals != NULL); 468 | assert(values != NULL); 469 | assert(len > 0); 470 | 471 | //construct an individual chip value by OR-ing 472 | //together the bits from the pinvals array. 473 | // 474 | //each n-th bit of the pinvals array makes up all 475 | //the bits for an individual chip. ie.: 476 | // 477 | //pinvals[0] contains all the 24th bit HX711 values 478 | //for each chip, pinvals[1] contains all the 23rd bit 479 | //values, and so on... 480 | // 481 | //(pinvals[0] >> 0) & 1 is the 24th HX711 bit of the 0th chip 482 | //(pinvals[1] >> 0) & 1 is the 23rd HX711 bit of the 0th chip 483 | //(pinvals[1] >> 2) & 1 is the 23rd HX711 bit of the 3rd chip 484 | //(pinvals[23] >> 0) & 1 is the 0th HX711 bit of the 0th chip 485 | // 486 | //eg. 487 | //rawvals[0] = 488 | // ((pinvals[0] >> 0) & 1) << 24 | 489 | // ((pinvals[1] >> 0) & 1) << 23 | 490 | // ((pinvals[2] >> 0) & 1) << 22 | 491 | //... 492 | // ((pinvals[23]) >> 0) & 1) << 0; 493 | 494 | for(size_t chipNum = 0; chipNum < len; ++chipNum) { 495 | 496 | //reset to 0 497 | //this is the raw value for an individual chip 498 | uint32_t rawVal = 0; 499 | 500 | //reconstruct an individual twos comp HX711 value from pinbits 501 | for(size_t bitPos = 0; bitPos < HX711_READ_BITS; ++bitPos) { 502 | const uint shift = HX711_READ_BITS - bitPos - 1; 503 | const uint32_t bit = (pinvals[bitPos] >> chipNum) & 1; 504 | rawVal |= bit << shift; 505 | } 506 | 507 | //then convert to a regular ones comp 508 | values[chipNum] = hx711_get_twos_comp(rawVal); 509 | 510 | assert(hx711_is_value_valid(values[chipNum])); 511 | 512 | } 513 | 514 | } 515 | 516 | void hx711_multi_init( 517 | hx711_multi_t* const hxm, 518 | const hx711_multi_config_t* const config) { 519 | 520 | hx711_multi__init_asert(config); 521 | 522 | #ifndef HX711_NO_MUTEX 523 | //this doesn't need to be an interrupts-off block 524 | //because the mutex will protect the hxm from reads 525 | //until init returns, and the source IRQs aren't 526 | //enabled anyway 527 | mutex_init(&hxm->_mut); 528 | #endif 529 | 530 | HX711_MUTEX_BLOCK(hxm->_mut, 531 | 532 | hxm->_clock_pin = config->clock_pin; 533 | hxm->_data_pin_base = config->data_pin_base; 534 | hxm->_chips_len = config->chips_len; 535 | 536 | hxm->_pio = config->pio; 537 | hxm->_awaiter_prog = config->awaiter_prog; 538 | hxm->_reader_prog = config->reader_prog; 539 | 540 | hxm->_pio_irq_index = config->pio_irq_index; 541 | hxm->_dma_irq_index = config->dma_irq_index; 542 | 543 | hxm->_async_state = HX711_MULTI_ASYNC_STATE_NONE; 544 | 545 | hx711_multi__async_add_reader(hxm); 546 | 547 | util_gpio_set_output(hxm->_clock_pin); 548 | 549 | util_gpio_set_contiguous_input_pins( 550 | hxm->_data_pin_base, 551 | hxm->_chips_len); 552 | 553 | hx711_multi__init_pio(hxm); 554 | 555 | config->pio_init(hxm); 556 | config->awaiter_prog_init(hxm); 557 | config->reader_prog_init(hxm); 558 | 559 | hx711_multi__init_dma(hxm); 560 | hx711_multi__init_irq(hxm); 561 | 562 | ); 563 | 564 | } 565 | 566 | void hx711_multi_close(hx711_multi_t* const hxm) { 567 | 568 | assert(hx711_multi__is_initd(hxm)); 569 | 570 | #ifndef HX711_NO_MUTEX 571 | mutex_enter_blocking(&hxm->_mut); 572 | #endif 573 | 574 | //make sure the disabling and removal of IRQs and 575 | //handlers is atomic 576 | UTIL_INTERRUPTS_OFF_BLOCK( 577 | 578 | //interrupts are off, but cancel any running 579 | //async reads 580 | dma_channel_abort(hxm->_dma_channel); 581 | 582 | irq_set_enabled( 583 | util_pio_get_irq_from_index(hxm->_pio, hxm->_pio_irq_index), 584 | false); 585 | 586 | irq_set_enabled( 587 | util_dma_get_irqn(hxm->_dma_irq_index), 588 | false); 589 | 590 | pio_set_irqn_source_enabled( 591 | hxm->_pio, 592 | hxm->_pio_irq_index, 593 | util_pio_get_pis_from_pio_interrupt_num(HX711_MULTI_CONVERSION_DONE_IRQ_NUM), 594 | false); 595 | 596 | dma_irqn_set_channel_enabled( 597 | hxm->_dma_irq_index, 598 | hxm->_dma_channel, 599 | false); 600 | 601 | hxm->_async_state = HX711_MULTI_ASYNC_STATE_NONE; 602 | 603 | hx711_multi__async_remove_reader(hxm); 604 | 605 | irq_remove_handler( 606 | util_pio_get_irq_from_index(hxm->_pio, hxm->_pio_irq_index), 607 | hx711_multi__async_pio_irq_handler); 608 | 609 | irq_remove_handler( 610 | util_dma_get_irqn(hxm->_dma_irq_index), 611 | hx711_multi__async_pio_irq_handler); 612 | 613 | ); 614 | 615 | //at this point it is impossible for a relevant DMA 616 | //or PIO IRQ to occur, so we can turn interrupts 617 | //back on 618 | 619 | util_dma_channel_set_quiet( 620 | hxm->_dma_channel, 621 | true); 622 | 623 | pio_set_sm_mask_enabled( 624 | hxm->_pio, 625 | (1 << hxm->_awaiter_sm) | (1 << hxm->_reader_sm), 626 | false); 627 | 628 | dma_channel_unclaim( 629 | hxm->_dma_channel); 630 | 631 | pio_sm_unclaim( 632 | hxm->_pio, 633 | hxm->_awaiter_sm); 634 | 635 | pio_sm_unclaim( 636 | hxm->_pio, 637 | hxm->_reader_sm); 638 | 639 | pio_remove_program( 640 | hxm->_pio, 641 | hxm->_awaiter_prog, 642 | hxm->_awaiter_offset); 643 | 644 | pio_remove_program( 645 | hxm->_pio, 646 | hxm->_reader_prog, 647 | hxm->_reader_offset); 648 | 649 | #ifndef HX711_NO_MUTEX 650 | mutex_exit(&hxm->_mut); 651 | #endif 652 | 653 | } 654 | 655 | void hx711_multi_set_gain( 656 | hx711_multi_t* const hxm, 657 | const hx711_gain_t gain) { 658 | 659 | assert(hx711_multi__is_state_machines_enabled(hxm)); 660 | 661 | const uint32_t gainVal = hx711_gain_to_pio_gain(gain); 662 | 663 | assert(hx711_is_pio_gain_valid(gain)); 664 | 665 | pio_sm_drain_tx_fifo( 666 | hxm->_pio, 667 | hxm->_reader_sm); 668 | 669 | pio_sm_put( 670 | hxm->_pio, 671 | hxm->_reader_sm, 672 | gainVal); 673 | 674 | hx711_multi_async_start(hxm); 675 | 676 | while(!hx711_multi_async_done(hxm)) { 677 | tight_loop_contents(); 678 | } 679 | 680 | } 681 | 682 | void hx711_multi_get_values( 683 | hx711_multi_t* const hxm, 684 | int32_t* const values) { 685 | 686 | assert(hx711_multi__is_state_machines_enabled(hxm)); 687 | assert(values != NULL); 688 | assert(!hx711_multi__async_is_running(hxm)); 689 | 690 | hx711_multi_async_start(hxm); 691 | while(!hx711_multi_async_done(hxm)) { 692 | tight_loop_contents(); 693 | } 694 | hx711_multi_async_get_values(hxm, values); 695 | 696 | } 697 | 698 | bool hx711_multi_get_values_timeout( 699 | hx711_multi_t* const hxm, 700 | int32_t* const values, 701 | const uint timeout) { 702 | 703 | assert(hx711_multi__is_state_machines_enabled(hxm)); 704 | assert(values != NULL); 705 | assert(!hx711_multi__async_is_running(hxm)); 706 | 707 | const absolute_time_t end = make_timeout_time_us(timeout); 708 | bool success = false; 709 | 710 | hx711_multi_async_start(hxm); 711 | 712 | while(!time_reached(end)) { 713 | if(hx711_multi_async_done(hxm)) { 714 | success = true; 715 | break; 716 | } 717 | } 718 | 719 | if(success) { 720 | hx711_multi_async_get_values(hxm, values); 721 | } 722 | else { 723 | //if timed out, cancel DMA and stop listening 724 | //for IRQs and exit mutex. Do this atomically! 725 | UTIL_INTERRUPTS_OFF_BLOCK( 726 | hx711_multi__async_finish(hxm); 727 | ); 728 | } 729 | 730 | return success; 731 | 732 | } 733 | 734 | void hx711_multi_async_start(hx711_multi_t* const hxm) { 735 | 736 | assert(hx711_multi__is_state_machines_enabled(hxm)); 737 | assert(!hx711_multi__async_is_running(hxm)); 738 | 739 | //if starting the following statements would lead to an 740 | //immediate interrupt, DMA may not be properly set up, 741 | //so disable until it is 742 | const uint32_t status = save_and_disable_interrupts(); 743 | 744 | #ifndef HX711_NO_MUTEX 745 | mutex_enter_blocking(&hxm->_mut); 746 | #endif 747 | 748 | hxm->_async_state = HX711_MULTI_ASYNC_STATE_WAITING; 749 | 750 | //if pio interrupt is already set, we can bypass the 751 | //IRQ handler and immediately trigger dma 752 | if(pio_interrupt_get(hxm->_pio, HX711_MULTI_CONVERSION_DONE_IRQ_NUM)) { 753 | hx711_multi__async_start_dma(hxm); 754 | } 755 | else { 756 | pio_set_irqn_source_enabled( 757 | hxm->_pio, 758 | hxm->_pio_irq_index, 759 | util_pio_get_irq_from_index(hxm->_pio, hxm->_pio_irq_index), 760 | true); 761 | } 762 | 763 | restore_interrupts(status); 764 | 765 | } 766 | 767 | bool hx711_multi_async_done(hx711_multi_t* const hxm) { 768 | assert(hx711_multi__is_initd(hxm)); 769 | return hxm->_async_state == HX711_MULTI_ASYNC_STATE_DONE; 770 | } 771 | 772 | void hx711_multi_async_get_values( 773 | hx711_multi_t* const hxm, 774 | int32_t* const values) { 775 | assert(hx711_multi__is_initd(hxm)); 776 | assert(hx711_multi_async_done(hxm)); 777 | hx711_multi_pinvals_to_values( 778 | hxm->_buffer, 779 | values, 780 | hxm->_chips_len); 781 | } 782 | 783 | void hx711_multi_power_up( 784 | hx711_multi_t* const hxm, 785 | const hx711_gain_t gain) { 786 | 787 | assert(hx711_multi__is_initd(hxm)); 788 | 789 | const uint32_t pioGainVal = hx711_gain_to_pio_gain(gain); 790 | 791 | assert(hx711_is_pio_gain_valid(pioGainVal)); 792 | 793 | HX711_MUTEX_BLOCK(hxm->_mut, 794 | 795 | gpio_put( 796 | hxm->_clock_pin, 797 | false); 798 | 799 | pio_sm_init( 800 | hxm->_pio, 801 | hxm->_reader_sm, 802 | hxm->_reader_offset, 803 | &hxm->_reader_default_config); 804 | 805 | pio_sm_clear_fifos( 806 | hxm->_pio, 807 | hxm->_reader_sm); 808 | 809 | //put the gain value into the reader FIFO 810 | pio_sm_put( 811 | hxm->_pio, 812 | hxm->_reader_sm, 813 | pioGainVal); 814 | 815 | pio_sm_init( 816 | hxm->_pio, 817 | hxm->_awaiter_sm, 818 | hxm->_awaiter_offset, 819 | &hxm->_awaiter_default_config); 820 | 821 | pio_set_sm_mask_enabled( 822 | hxm->_pio, 823 | (1 << hxm->_awaiter_sm) | (1 << hxm->_reader_sm), 824 | true); 825 | 826 | ); 827 | 828 | } 829 | 830 | void hx711_multi_power_down(hx711_multi_t* const hxm) { 831 | 832 | assert(hx711_multi__is_initd(hxm)); 833 | 834 | HX711_MUTEX_BLOCK(hxm->_mut, 835 | 836 | UTIL_INTERRUPTS_OFF_BLOCK( 837 | hx711_multi__async_finish(hxm); 838 | ); 839 | 840 | pio_set_sm_mask_enabled( 841 | hxm->_pio, 842 | (1 << hxm->_awaiter_sm) | (1 << hxm->_reader_sm), 843 | false); 844 | 845 | gpio_put( 846 | hxm->_clock_pin, 847 | true); 848 | 849 | ); 850 | 851 | } 852 | 853 | void hx711_multi_sync( 854 | hx711_multi_t* const hxm, 855 | const hx711_gain_t gain) { 856 | assert(hx711_multi__is_initd(hxm)); 857 | hx711_multi_power_down(hxm); 858 | hx711_wait_power_down(); 859 | hx711_multi_power_up(hxm, gain); 860 | } 861 | 862 | uint32_t hx711_multi_get_sync_state( 863 | hx711_multi_t* const hxm) { 864 | assert(hx711_multi__is_state_machines_enabled(hxm)); 865 | return pio_sm_get_blocking(hxm->_pio, hxm->_awaiter_sm); 866 | } 867 | 868 | bool hx711_multi_is_syncd( 869 | hx711_multi_t* const hxm) { 870 | 871 | assert(hx711_multi__is_state_machines_enabled(hxm)); 872 | 873 | //all chips should either be 0 or 1 which translates 874 | //to a bitmask of exactly 0 or 2^chips 875 | const uint32_t allReady = (uint32_t)pow(2, hxm->_chips_len); 876 | const uint32_t state = hx711_multi_get_sync_state(hxm); 877 | 878 | return state == 0 || state == allReady; 879 | 880 | } 881 | --------------------------------------------------------------------------------