From 003e11c96ab752c2e062ab74b502f879e32d5de7 Mon Sep 17 00:00:00 2001 From: mypx Date: Mon, 27 Oct 2025 15:18:13 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A7=BB=E6=A4=8D=E9=80=82=E9=85=8D=E6=96=B0?= =?UTF-8?q?=E7=A1=AC=E4=BB=B6ver3,=E5=81=9A=E4=BA=86=E4=B8=80=E4=BA=9B?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=96=9D=E7=BB=93=E6=9E=84=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 131 +-- Core/Inc/main.h | 56 +- Core/Inc/spi.h | 3 + Core/Inc/stm32f1xx_it.h | 4 +- Core/Inc/tim.h | 3 - Core/Src/adc.c | 34 +- Core/Src/dma.c | 9 +- Core/Src/gpio.c | 33 +- Core/Src/main.c | 150 ++- Core/Src/spi.c | 130 ++- Core/Src/stm32f1xx_it.c | 50 +- Core/Src/tim.c | 133 +-- User/app/bsp_debug.c | 79 ++ User/app/data_process.c | 68 +- User/app/data_sampling.c | 54 +- User/app/data_sampling.h | 10 +- User/app/mb_hmi/CMakeLists.txt | 21 +- User/app/mb_hmi/hmi_server.c | 9 +- User/app/mb_hmi/hmi_server.h | 2 +- User/app/mb_hmi/mb_command.c | 2 +- User/app/pm_common.h | 4 +- User/app/pm_device.c | 33 +- User/app/pm_device.h | 2 +- User/app/pm_meas.c | 42 +- User/app/pm_meas.h | 20 +- User/app/servo.c | 10 +- User/app/servo.h | 5 +- User/app/storage.c | 8 +- User/app/tec_control.c | 245 +++-- User/app/tec_control.h | 10 +- User/board/bsp_collect.c | 230 +++++ User/board/bsp_collect.h | 21 + User/board/bsp_hmi.c | 85 ++ User/board/bsp_hmi.h | 21 + User/board/bsp_misc.c | 121 +++ User/board/bsp_misc.h | 40 + User/board/bsp_motor.c | 82 ++ User/board/bsp_motor.h | 16 + User/board/bsp_tec.c | 177 ++++ User/board/bsp_tec.h | 27 + User/board/bsp_temper_sampling.c | 140 +++ User/board/bsp_temper_sampling.h | 27 + User/board/pm_board.c | 552 ----------- User/board/pm_board.h | 109 --- User/driver/ad779x/README.md | 115 ++- User/driver/ad779x/ad7793.c | 325 +++++-- User/driver/ad779x/ad7793.h | 116 ++- User/user_config.h | 17 +- cmake/stm32cubemx/CMakeLists.txt | 18 + .../{Sheet4_new_version.pdf => pm_ver2.pdf} | Bin docs/hardware/pm_ver3.pdf | Bin 0 -> 549273 bytes docs/holding_registers.mbs | Bin 0 -> 142796 bytes docs/input_registers.mbs | Bin 0 -> 145344 bytes docs/pm.msw | Bin 0 -> 1366 bytes etk/src/algorithm/math/include/etk_diff.h | 67 -- .../math/src/et_half-wave_analysis.c | 296 ------ etk/src/algorithm/math/src/etk_diff.c | 255 ------ etk/src/common/include/etk_config.h | 14 - etk/src/common/include/etk_string.h | 219 ----- etk/src/common/src/etk_string.c | 860 ------------------ etk/src/utils/ringbuffer/LICENSE | 661 -------------- polarimeter.ioc | 228 +++-- 62 files changed, 2310 insertions(+), 3889 deletions(-) create mode 100644 User/app/bsp_debug.c create mode 100644 User/board/bsp_collect.c create mode 100644 User/board/bsp_collect.h create mode 100644 User/board/bsp_hmi.c create mode 100644 User/board/bsp_hmi.h create mode 100644 User/board/bsp_misc.c create mode 100644 User/board/bsp_misc.h create mode 100644 User/board/bsp_motor.c create mode 100644 User/board/bsp_motor.h create mode 100644 User/board/bsp_tec.c create mode 100644 User/board/bsp_tec.h create mode 100644 User/board/bsp_temper_sampling.c create mode 100644 User/board/bsp_temper_sampling.h delete mode 100644 User/board/pm_board.c delete mode 100644 User/board/pm_board.h rename docs/hardware/{Sheet4_new_version.pdf => pm_ver2.pdf} (100%) create mode 100644 docs/hardware/pm_ver3.pdf create mode 100644 docs/holding_registers.mbs create mode 100644 docs/input_registers.mbs create mode 100644 docs/pm.msw delete mode 100644 etk/src/algorithm/math/include/etk_diff.h delete mode 100644 etk/src/algorithm/math/src/et_half-wave_analysis.c delete mode 100644 etk/src/algorithm/math/src/etk_diff.c delete mode 100644 etk/src/common/include/etk_string.h delete mode 100644 etk/src/common/src/etk_string.c delete mode 100644 etk/src/utils/ringbuffer/LICENSE diff --git a/CMakeLists.txt b/CMakeLists.txt index 0185180..d490bd3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,127 +59,16 @@ target_link_options(${CMAKE_PROJECT_NAME} PRIVATE -u _printf_float ) -# ETK module configuration options - based on etk CMakeLists.txt option naming -# Algorithm modules -# set(ETK_ALGORITHM ON CACHE BOOL "Enable algorithm module") -# set(ALG_DSP_UTILS ON CACHE BOOL "Enable dsp_utils submodule") -# set(ALG_FILTER ON CACHE BOOL "Enable filter submodule") -# set(ALG_GROOVE_TRACKER OFF CACHE BOOL "Enable groove_tracker submodule") -# set(ALG_MATH ON CACHE BOOL "Enable math submodule") -# set(ALG_PID ON CACHE BOOL "Enable pid submodule") -# set(ALG_TREND OFF CACHE BOOL "Enable trend submodule") - -# # DSP Utils submodules -# set(ALG_FFT OFF CACHE BOOL "Enable FFT submodule") -# set(ALG_FREQ_ANALYZER OFF CACHE BOOL "Enable frequency analyzer submodule") -# set(ALG_GOERTZEL ON CACHE BOOL "Enable Goertzel submodule") -# set(ALG_INTERP OFF CACHE BOOL "Enable interpolation submodule") -# set(ALG_WAVES OFF CACHE BOOL "Enable waves submodule") -# set(ALG_WINDOWS OFF CACHE BOOL "Enable windows submodule") -# set(ETK_ZERO_CROSS ON CACHE BOOL "Enable zero_cross submodule") - -# # Filter submodules -# set(ALG_BUTTERWORTH_FILTER OFF CACHE BOOL "Enable Butterworth filter submodule") -# set(ALG_CHEBYSHEV_FILTER OFF CACHE BOOL "Enable Chebyshev filter submodule") -# set(ALG_EMA_FILTER ON CACHE BOOL "Enable EMA filter submodule") -# set(ALG_FIR_FILTER OFF CACHE BOOL "Enable FIR filter submodule") -# set(ALG_GAUSSIAN_FILTER OFF CACHE BOOL "Enable Gaussian filter submodule") -# set(ALG_HIGH_PASS_FILTER OFF CACHE BOOL "Enable high pass filter submodule") -# set(ALG_IIR_FILTER OFF CACHE BOOL "Enable IIR filter submodule") -# set(ALG_KALMAN_FILTER OFF CACHE BOOL "Enable Kalman filter submodule") -# set(ALG_MEDIAN_FILTER OFF CACHE BOOL "Enable median filter submodule") -# set(ALG_SAVITZKY_FILTER OFF CACHE BOOL "Enable Savitzky filter submodule") -# set(ALG_SLIDING_FILTER OFF CACHE BOOL "Enable sliding filter submodule") - -# # Math submodules -# set(ALG_WAVEFORM_ANALYSIS OFF CACHE BOOL "Enable waveform analysis submodule") -# set(ALG_LINEAR_SOLVER OFF CACHE BOOL "Enable linear solver submodule") -# set(ALG_NEWTON OFF CACHE BOOL "Enable Newton method submodule") -# set(ALG_DIFF OFF CACHE BOOL "Enable differentiation submodule") -# set(ALG_SLOPE ON CACHE BOOL "Enable slope calculation submodule") - -# # Drivers modules -# set(ETK_DRIVERS ON CACHE BOOL "Enable drivers module") -# set(DRV_AD779X OFF CACHE BOOL "Enable ad779x driver submodule") -# set(DRV_ENCODER ON CACHE BOOL "Enable encoder driver submodule") - -# # Utils modules -# set(ETK_UTILS ON CACHE BOOL "Enable utils module") -# set(UTIL_BYTE_CONV ON CACHE BOOL "Enable byte_conversion submodule") -# set(UTIL_FLEX_QUEUE OFF CACHE BOOL "Enable flex_queue submodule") -# set(UTIL_RINGBUFFER ON CACHE BOOL "Enable ringbuffer submodule") - -# # System modules -# set(ETK_OS_ADAPT OFF CACHE BOOL "Enable os_adapt module") -# set(ETK_LOG_FRAMEWORK OFF CACHE BOOL "Enable log framework module") -# set(UTIL_LOG_SIMPLE ON CACHE BOOL "Enable logging module") - -# # Communication modules -# set(ETK_COMMUNICATION OFF CACHE BOOL "Enable communication module") - -# # Thirdparty modules -# set(THIRDPARTY ON CACHE BOOL "Enable thirdparty module") -# set(NANOMODBUS ON CACHE BOOL "Enable nanomodbus submodule") - -# # Common modules (always enabled as base dependency) -# set(ETK_COMMON ON CACHE BOOL "Enable common module") -# set(ETK_ROOT_CMAKE ON CACHE BOOL "Build all modules into a single etk library") -# set(SYSTEM_TYPE 2 CACHE STRING "System type: 0(SYS_NONE_OS), 1(SYS_FREERTOS), 2(SYS_RTTHREAD), 3(SYS_LINUX), 4(SYS_POSIX)") - -# RT-Thread specific configuration for ETK -#set(USE_EXTERNAL_RTTHREAD ON CACHE BOOL "Use external RT-Thread library") -#set(FETCH_RTTHREAD OFF CACHE BOOL "Download RT-Thread via FetchContent") -# Set RTT_ROOT to tell ETK where to find RT-Thread source code -#set(RTT_ROOT "${CMAKE_SOURCE_DIR}/Middlewares/Third_Party/RealThread_RTOS_RT-Thread" CACHE PATH "RT-Thread source root directory for ETK") -# Set RTT_CONFIG_DIR for rtconfig.h location -#set(RTT_CONFIG_DIR "${CMAKE_SOURCE_DIR}/RT-Thread" CACHE PATH "RT-Thread config directory") - - -# if(NOT EXISTS "${RTT_CONFIG_DIR}/rtconfig.h") -# message(WARNING "RT-Thread config file rtconfig.h not found at: ${RTT_CONFIG_DIR}") -# endif() - -# Additional configuration options for specific features -#set(ENABLE_ARM_CMSIS_DSP OFF CACHE BOOL "Enable ARM CMSIS-DSP library") - - -# Set RT-Thread include paths - include both source and config directories -# set(RTTHREAD_INCLUDE_DIRS -# ${RTT_ROOT}/include -# ${RTT_ROOT}/components/finsh -# ${RTT_ROOT}/components/finsh/inc -# ${RTT_ROOT}/components/drivers/include -# ${RTT_CONFIG_DIR} # Add rtconfig.h directory -# ) - -# # Set RTT_ROOT as environment variable for etk os_adapt module to find -# if(DEFINED RTT_ROOT) -# set(ENV{RTT_ROOT} "${RTT_ROOT}") -# message(STATUS "RTT_ROOT environment variable set to: $ENV{RTT_ROOT}") -# else() -# message(FATAL_ERROR "RTT_ROOT is not defined. Please set RTT_ROOT path.") -# endif() - -# # Also set RTT_CONFIG_DIR as environment variable for rtconfig.h -# if(DEFINED RTT_CONFIG_DIR) -# set(ENV{RTT_CONFIG_DIR} "${RTT_CONFIG_DIR}") -# message(STATUS "RTT_CONFIG_DIR environment variable set to: $ENV{RTT_CONFIG_DIR}") -# endif() - -# add_subdirectory(etk) - -# # Link ETK library to main project to get access to all ETK headers -# if(ETK_ROOT_CMAKE) -# target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE etk) -# # Ensure etk library can get SYSTEM_TYPE value -# target_compile_definitions(etk PUBLIC SYSTEM_TYPE=${SYSTEM_TYPE}) -# endif() - # Add sources to executable target_sources(${CMAKE_PROJECT_NAME} PRIVATE # Add user sources here - ${CMAKE_SOURCE_DIR}/User/board/pm_board.c + ${CMAKE_SOURCE_DIR}/User/board/bsp_misc.c ${CMAKE_SOURCE_DIR}/User/board/bsp_encoder.c + ${CMAKE_SOURCE_DIR}/User/board/bsp_collect.c + ${CMAKE_SOURCE_DIR}/User/board/bsp_tec.c + ${CMAKE_SOURCE_DIR}/User/board/bsp_temper_sampling.c + ${CMAKE_SOURCE_DIR}/User/board/bsp_hmi.c + ${CMAKE_SOURCE_DIR}/User/board/bsp_motor.c ${CMAKE_SOURCE_DIR}/User/driver/at24cx/at24cx.c ${CMAKE_SOURCE_DIR}/User/driver/ad779x/ad7793.c ${CMAKE_SOURCE_DIR}/segger_rtt/RTT/SEGGER_RTT_printf.c @@ -208,10 +97,10 @@ target_sources(${CMAKE_PROJECT_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/User/app/mb_hmi/hmi_server.c ${CMAKE_SOURCE_DIR}/User/app/pm_meas.c ${CMAKE_SOURCE_DIR}/User/app/pm_params.c + ${CMAKE_SOURCE_DIR}/User/app/bsp_debug.c ${CMAKE_SOURCE_DIR}/nanoMODBUS/nanomodbus.c ${CMAKE_SOURCE_DIR}/etk/src/drivers/encoder/src/et_encoder.c ${CMAKE_SOURCE_DIR}/etk/src/drivers/encoder/src/et_encoder_utils.c - # ETK logging ${CMAKE_SOURCE_DIR}/etk/src/logging/et_log.c ${CMAKE_SOURCE_DIR}/etk/src/algorithm/filter/src/et_ema_filter.c ${CMAKE_SOURCE_DIR}/etk/src/algorithm/pid/src/etk_pid.c @@ -226,8 +115,6 @@ target_sources(${CMAKE_PROJECT_NAME} PRIVATE # Add include paths target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE - # Add user defined include paths - # Note: ETK module include paths are now automatically included via the etk library ${CMAKE_SOURCE_DIR}/User/board ${CMAKE_SOURCE_DIR}/segger_rtt/Config/ ${CMAKE_SOURCE_DIR}/segger_rtt/RTT/ @@ -239,7 +126,7 @@ target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/User/ ${CMAKE_SOURCE_DIR}/User/app/ ${CMAKE_SOURCE_DIR}/User/app/mb_hmi/ - # Add nanoMODBUS include path explicitly + ${CMAKE_SOURCE_DIR}/nanoMODBUS ${CMAKE_SOURCE_DIR}/etk/src/logging ${CMAKE_SOURCE_DIR}/etk/src/algorithm/math/include @@ -260,7 +147,7 @@ target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE ARM_MATH SYSTEM_TYPE=2 ET_SLIDING_FILTER_SIZE=4 - NMBS_DEBUG + #NMBS_DEBUG USING_RTT_AS_CONSOLE RT_DEBUG ) diff --git a/Core/Inc/main.h b/Core/Inc/main.h index 48321db..ad93a16 100644 --- a/Core/Inc/main.h +++ b/Core/Inc/main.h @@ -57,27 +57,25 @@ void Error_Handler(void); /* USER CODE END EFP */ /* Private defines -----------------------------------------------------------*/ -#define TEC_DEAD_TIME 100 +#define HMI_COM huart2 +#define ENCODER_MAX_COUNT 65535 #define AD7793_SPI hspi3 +#define US_TIM_PRE 71 #define RS485_COM huart5 +#define ENCODER_TIM htim2 #define TEC_TIM_COUNTER 143 -#define SAMPLING_TIM_PRE 71 #define US_TIM_PERIOD_COUNT 9999 #define TEC_TIM_PRESCALER 99 -#define TEC_TIM htim1 -#define HMI_COM huart2 -#define SAMPLING_TIM htim3 -#define US_TIM_PRE 71 -#define TIMESTAMP_TIM htim4 -#define ENCODER_TIM htim2 -#define SAMPLING_TIM_PERIOD_COUNT 499//999 #define SERVO_COM huart3 -#define LIGHT_ADC hadc1 -#define ENCODER_MAX_COUNT 65535 +#define TEC_TIM htim1 +#define COLLECT_TIM htim3 +#define ADS8866_SPI hspi2 #define PE5_LED1_Pin GPIO_PIN_5 #define PE5_LED1_GPIO_Port GPIOE #define PE6_LED2_Pin GPIO_PIN_6 #define PE6_LED2_GPIO_Port GPIOE +#define RS485_DE_Pin GPIO_PIN_1 +#define RS485_DE_GPIO_Port GPIOF #define TIM2_ENCODER_B_Pin GPIO_PIN_0 #define TIM2_ENCODER_B_GPIO_Port GPIOA #define TIM2_ENCODER_A_Pin GPIO_PIN_1 @@ -86,16 +84,24 @@ void Error_Handler(void); #define UART2_TX_LCD_GPIO_Port GPIOA #define UART2_RX_LCD_Pin GPIO_PIN_3 #define UART2_RX_LCD_GPIO_Port GPIOA +#define TEC_LEFT_SD_Pin GPIO_PIN_6 +#define TEC_LEFT_SD_GPIO_Port GPIOA +#define TEC_RIGHT_SD_Pin GPIO_PIN_7 +#define TEC_RIGHT_SD_GPIO_Port GPIOA +#define ADS8866_CONVST_Pin GPIO_PIN_0 +#define ADS8866_CONVST_GPIO_Port GPIOB #define I2C2_EEPROM_SCL_Pin GPIO_PIN_10 #define I2C2_EEPROM_SCL_GPIO_Port GPIOB #define I2C2_EEPROM_SDA_Pin GPIO_PIN_11 #define I2C2_EEPROM_SDA_GPIO_Port GPIOB -#define TIM1_CH1N_TEC_Pin GPIO_PIN_13 -#define TIM1_CH1N_TEC_GPIO_Port GPIOB -#define FAN_CTRL_Pin GPIO_PIN_15 -#define FAN_CTRL_GPIO_Port GPIOD -#define TIM1_CH1_TEC_Pin GPIO_PIN_8 -#define TIM1_CH1_TEC_GPIO_Port GPIOA +#define SPI2_ADS8866_SCK_Pin GPIO_PIN_13 +#define SPI2_ADS8866_SCK_GPIO_Port GPIOB +#define SPI2_ADS8866_MISO_Pin GPIO_PIN_14 +#define SPI2_ADS8866_MISO_GPIO_Port GPIOB +#define SPI2_ADS8866_MOSI_Pin GPIO_PIN_15 +#define SPI2_ADS8866_MOSI_GPIO_Port GPIOB +#define TEC_RIGHT_IN_Pin GPIO_PIN_8 +#define TEC_RIGHT_IN_GPIO_Port GPIOA #define UART1_LCD_TX_Pin GPIO_PIN_9 #define UART1_LCD_TX_GPIO_Port GPIOA #define UART1_LCD_RX_Pin GPIO_PIN_10 @@ -110,14 +116,14 @@ void Error_Handler(void); #define UART5_RS485_RX_GPIO_Port GPIOD #define FAN_CONTROL_Pin GPIO_PIN_5 #define FAN_CONTROL_GPIO_Port GPIOD -#define SPI3_SCK_AD7793_Pin GPIO_PIN_3 -#define SPI3_SCK_AD7793_GPIO_Port GPIOB -#define SPI3_MISO_AD7793_Pin GPIO_PIN_4 -#define SPI3_MISO_AD7793_GPIO_Port GPIOB -#define SPI3_MOSI_AD7793_Pin GPIO_PIN_5 -#define SPI3_MOSI_AD7793_GPIO_Port GPIOB -#define SPI3_CS_AD7793_Pin GPIO_PIN_7 -#define SPI3_CS_AD7793_GPIO_Port GPIOB +#define SPI3_AD7793_SCK_Pin GPIO_PIN_3 +#define SPI3_AD7793_SCK_GPIO_Port GPIOB +#define SPI3_AD7793_MISO_Pin GPIO_PIN_4 +#define SPI3_AD7793_MISO_GPIO_Port GPIOB +#define SPI3_AD7793_MOSI_Pin GPIO_PIN_5 +#define SPI3_AD7793_MOSI_GPIO_Port GPIOB +#define SPI3_AD7793_CS_Pin GPIO_PIN_7 +#define SPI3_AD7793_CS_GPIO_Port GPIOB /* USER CODE BEGIN Private defines */ diff --git a/Core/Inc/spi.h b/Core/Inc/spi.h index 52a60e5..7c4b08a 100644 --- a/Core/Inc/spi.h +++ b/Core/Inc/spi.h @@ -32,12 +32,15 @@ extern "C" { /* USER CODE END Includes */ +extern SPI_HandleTypeDef hspi2; + extern SPI_HandleTypeDef hspi3; /* USER CODE BEGIN Private defines */ /* USER CODE END Private defines */ +void MX_SPI2_Init(void); void MX_SPI3_Init(void); /* USER CODE BEGIN Prototypes */ diff --git a/Core/Inc/stm32f1xx_it.h b/Core/Inc/stm32f1xx_it.h index 01fd82a..8c72063 100644 --- a/Core/Inc/stm32f1xx_it.h +++ b/Core/Inc/stm32f1xx_it.h @@ -51,10 +51,10 @@ void MemManage_Handler(void); void BusFault_Handler(void); void UsageFault_Handler(void); void DebugMon_Handler(void); -void DMA1_Channel1_IRQHandler(void); +void DMA1_Channel4_IRQHandler(void); +void DMA1_Channel5_IRQHandler(void); void TIM2_IRQHandler(void); void TIM3_IRQHandler(void); -void TIM4_IRQHandler(void); void USART1_IRQHandler(void); void USART2_IRQHandler(void); void USART3_IRQHandler(void); diff --git a/Core/Inc/tim.h b/Core/Inc/tim.h index a848354..0e17667 100644 --- a/Core/Inc/tim.h +++ b/Core/Inc/tim.h @@ -38,8 +38,6 @@ extern TIM_HandleTypeDef htim2; extern TIM_HandleTypeDef htim3; -extern TIM_HandleTypeDef htim4; - /* USER CODE BEGIN Private defines */ /* USER CODE END Private defines */ @@ -47,7 +45,6 @@ extern TIM_HandleTypeDef htim4; void MX_TIM1_Init(void); void MX_TIM2_Init(void); void MX_TIM3_Init(void); -void MX_TIM4_Init(void); void HAL_TIM_MspPostInit(TIM_HandleTypeDef *htim); diff --git a/Core/Src/adc.c b/Core/Src/adc.c index 3fe5b23..f2b3304 100644 --- a/Core/Src/adc.c +++ b/Core/Src/adc.c @@ -25,7 +25,6 @@ /* USER CODE END 0 */ ADC_HandleTypeDef hadc1; -DMA_HandleTypeDef hdma_adc1; /* ADC1 init function */ void MX_ADC1_Init(void) @@ -47,7 +46,7 @@ void MX_ADC1_Init(void) hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE; hadc1.Init.ContinuousConvMode = DISABLE; hadc1.Init.DiscontinuousConvMode = DISABLE; - hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T3_TRGO; + hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START; hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion = 1; if (HAL_ADC_Init(&hadc1) != HAL_OK) @@ -57,9 +56,9 @@ void MX_ADC1_Init(void) /** Configure Regular Channel */ - sConfig.Channel = ADC_CHANNEL_10; + sConfig.Channel = ADC_CHANNEL_11; sConfig.Rank = ADC_REGULAR_RANK_1; - sConfig.SamplingTime = ADC_SAMPLETIME_55CYCLES_5; + sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5; if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) { Error_Handler(); @@ -84,29 +83,12 @@ void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle) __HAL_RCC_GPIOC_CLK_ENABLE(); /**ADC1 GPIO Configuration - PC0 ------> ADC1_IN10 + PC1 ------> ADC1_IN11 */ - GPIO_InitStruct.Pin = GPIO_PIN_0; + GPIO_InitStruct.Pin = GPIO_PIN_1; GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); - /* ADC1 DMA Init */ - /* ADC1 Init */ - hdma_adc1.Instance = DMA1_Channel1; - hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY; - hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE; - hdma_adc1.Init.MemInc = DMA_MINC_ENABLE; - hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; - hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; - hdma_adc1.Init.Mode = DMA_CIRCULAR; - hdma_adc1.Init.Priority = DMA_PRIORITY_VERY_HIGH; - if (HAL_DMA_Init(&hdma_adc1) != HAL_OK) - { - Error_Handler(); - } - - __HAL_LINKDMA(adcHandle,DMA_Handle,hdma_adc1); - /* USER CODE BEGIN ADC1_MspInit 1 */ /* USER CODE END ADC1_MspInit 1 */ @@ -125,12 +107,10 @@ void HAL_ADC_MspDeInit(ADC_HandleTypeDef* adcHandle) __HAL_RCC_ADC1_CLK_DISABLE(); /**ADC1 GPIO Configuration - PC0 ------> ADC1_IN10 + PC1 ------> ADC1_IN11 */ - HAL_GPIO_DeInit(GPIOC, GPIO_PIN_0); + HAL_GPIO_DeInit(GPIOC, GPIO_PIN_1); - /* ADC1 DMA DeInit */ - HAL_DMA_DeInit(adcHandle->DMA_Handle); /* USER CODE BEGIN ADC1_MspDeInit 1 */ /* USER CODE END ADC1_MspDeInit 1 */ diff --git a/Core/Src/dma.c b/Core/Src/dma.c index c9bc588..2540b01 100644 --- a/Core/Src/dma.c +++ b/Core/Src/dma.c @@ -43,9 +43,12 @@ void MX_DMA_Init(void) __HAL_RCC_DMA1_CLK_ENABLE(); /* DMA interrupt init */ - /* DMA1_Channel1_IRQn interrupt configuration */ - HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 0); - HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn); + /* DMA1_Channel4_IRQn interrupt configuration */ + HAL_NVIC_SetPriority(DMA1_Channel4_IRQn, 0, 0); + HAL_NVIC_EnableIRQ(DMA1_Channel4_IRQn); + /* DMA1_Channel5_IRQn interrupt configuration */ + HAL_NVIC_SetPriority(DMA1_Channel5_IRQn, 0, 0); + HAL_NVIC_EnableIRQ(DMA1_Channel5_IRQn); } diff --git a/Core/Src/gpio.c b/Core/Src/gpio.c index 26588a4..e649402 100644 --- a/Core/Src/gpio.c +++ b/Core/Src/gpio.c @@ -47,6 +47,7 @@ void MX_GPIO_Init(void) /* GPIO Ports Clock Enable */ __HAL_RCC_GPIOE_CLK_ENABLE(); __HAL_RCC_GPIOC_CLK_ENABLE(); + __HAL_RCC_GPIOF_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); __HAL_RCC_GPIOD_CLK_ENABLE(); @@ -55,10 +56,16 @@ void MX_GPIO_Init(void) HAL_GPIO_WritePin(GPIOE, PE5_LED1_Pin|PE6_LED2_Pin, GPIO_PIN_SET); /*Configure GPIO pin Output Level */ - HAL_GPIO_WritePin(GPIOD, FAN_CTRL_Pin|FAN_CONTROL_Pin, GPIO_PIN_RESET); + HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_RESET); /*Configure GPIO pin Output Level */ - HAL_GPIO_WritePin(SPI3_CS_AD7793_GPIO_Port, SPI3_CS_AD7793_Pin, GPIO_PIN_RESET); + HAL_GPIO_WritePin(GPIOA, TEC_LEFT_SD_Pin|TEC_RIGHT_SD_Pin, GPIO_PIN_RESET); + + /*Configure GPIO pin Output Level */ + HAL_GPIO_WritePin(GPIOD, FAN_CONTROL_Pin, GPIO_PIN_RESET); + + /*Configure GPIO pin Output Level */ + HAL_GPIO_WritePin(SPI3_AD7793_CS_GPIO_Port, SPI3_AD7793_CS_Pin, GPIO_PIN_RESET); /*Configure GPIO pins : PE5_LED1_Pin PE6_LED2_Pin */ GPIO_InitStruct.Pin = PE5_LED1_Pin|PE6_LED2_Pin; @@ -67,19 +74,33 @@ void MX_GPIO_Init(void) GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOE, &GPIO_InitStruct); + /*Configure GPIO pin : RS485_DE_Pin */ + GPIO_InitStruct.Pin = RS485_DE_Pin; + GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; + GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; + HAL_GPIO_Init(RS485_DE_GPIO_Port, &GPIO_InitStruct); + + /*Configure GPIO pins : TEC_LEFT_SD_Pin TEC_RIGHT_SD_Pin */ + GPIO_InitStruct.Pin = TEC_LEFT_SD_Pin|TEC_RIGHT_SD_Pin; + GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; + GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; + HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); + /*Configure GPIO pins : FAN_CTRL_Pin FAN_CONTROL_Pin */ - GPIO_InitStruct.Pin = FAN_CTRL_Pin|FAN_CONTROL_Pin; + GPIO_InitStruct.Pin = FAN_CONTROL_Pin; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOD, &GPIO_InitStruct); - /*Configure GPIO pin : SPI3_CS_AD7793_Pin */ - GPIO_InitStruct.Pin = SPI3_CS_AD7793_Pin; + /*Configure GPIO pin : SPI3_AD7793_CS_Pin */ + GPIO_InitStruct.Pin = SPI3_AD7793_CS_Pin; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; - HAL_GPIO_Init(SPI3_CS_AD7793_GPIO_Port, &GPIO_InitStruct); + HAL_GPIO_Init(SPI3_AD7793_CS_GPIO_Port, &GPIO_InitStruct); } diff --git a/Core/Src/main.c b/Core/Src/main.c index 8004c4d..7b29658 100644 --- a/Core/Src/main.c +++ b/Core/Src/main.c @@ -18,7 +18,6 @@ /* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "main.h" -#include "dma.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ @@ -28,6 +27,7 @@ #include "tim.h" #include #include "et_log.h" +#include "bsp_collect.h" /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ @@ -68,46 +68,45 @@ void SystemClock_Config(void); */ int main(void) { - /* USER CODE BEGIN 1 */ - /* USER CODE END 1 */ + /* USER CODE BEGIN 1 */ - /* MCU Configuration--------------------------------------------------------*/ + /* USER CODE END 1 */ - /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ - HAL_Init(); + /* MCU Configuration--------------------------------------------------------*/ - /* USER CODE BEGIN Init */ + /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ + HAL_Init(); - /* USER CODE END Init */ + /* USER CODE BEGIN Init */ - /* USER CODE BEGIN SysInit */ + /* USER CODE END Init */ - /* USER CODE END SysInit */ + /* USER CODE BEGIN SysInit */ - /* Initialize all configured peripherals */ + /* USER CODE END SysInit */ - /* USER CODE BEGIN 2 */ + /* Initialize all configured peripherals */ + /* USER CODE BEGIN 2 */ et_log_init(DEBUG_LEVEL, false, true); pm_device_init(&pm_device); - /* USER CODE END 2 */ + /* USER CODE END 2 */ - /* Infinite loop */ - /* USER CODE BEGIN WHILE */ - pm_device_start(&pm_device); + /* Infinite loop */ + /* USER CODE BEGIN WHILE */ + //pm_device_start(&pm_device); while (1) { - /* USER CODE END WHILE */ + /* USER CODE END WHILE */ - /* USER CODE BEGIN 3 */ - rt_thread_mdelay(1000); - //pm_lcd_cmd_send("TEST\r\n", 6); + /* USER CODE BEGIN 3 */ + rt_thread_mdelay(1000); #if (TEST_ENCODER_PLUSE == 1) - rt_kprintf("count:%d\r\n", pm_encoder_get_count()); + rt_kprintf("count:%d\r\n", pm_encoder_get_count()); #endif - //pm_system_led_toggle(); // Toggle the system LEDs + //bsp_system_led_toggle(); // Toggle the system LEDs } - /* USER CODE END 3 */ + /* USER CODE END 3 */ } /** @@ -116,43 +115,44 @@ int main(void) */ void SystemClock_Config(void) { - RCC_OscInitTypeDef RCC_OscInitStruct = {0}; - RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; - RCC_PeriphCLKInitTypeDef PeriphClkInit = {0}; + RCC_OscInitTypeDef RCC_OscInitStruct = {0}; + RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; + RCC_PeriphCLKInitTypeDef PeriphClkInit = {0}; - /** Initializes the RCC Oscillators according to the specified parameters + /** Initializes the RCC Oscillators according to the specified parameters * in the RCC_OscInitTypeDef structure. */ - RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; - RCC_OscInitStruct.HSEState = RCC_HSE_ON; - RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; - RCC_OscInitStruct.HSIState = RCC_HSI_ON; - RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; - RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; - RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; - if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) - { - Error_Handler(); - } + RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; + RCC_OscInitStruct.HSEState = RCC_HSE_ON; + RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; + RCC_OscInitStruct.HSIState = RCC_HSI_ON; + RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; + RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; + RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; + if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) + { + Error_Handler(); + } - /** Initializes the CPU, AHB and APB buses clocks + /** Initializes the CPU, AHB and APB buses clocks */ - RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; - RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; - RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; - RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; - RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; + RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK + |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; + RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; + RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; + RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; + RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; - if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) - { - Error_Handler(); - } - PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC; - PeriphClkInit.AdcClockSelection = RCC_ADCPCLK2_DIV6; - if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) - { - Error_Handler(); - } + if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) + { + Error_Handler(); + } + PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC; + PeriphClkInit.AdcClockSelection = RCC_ADCPCLK2_DIV6; + if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) + { + Error_Handler(); + } } /* USER CODE BEGIN 4 */ @@ -169,21 +169,14 @@ void SystemClock_Config(void) */ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { - /* USER CODE BEGIN Callback 0 */ + /* USER CODE BEGIN Callback 0 */ - /* USER CODE END Callback 0 */ - if (htim->Instance == TIM8) - { - HAL_IncTick(); - } - /* USER CODE BEGIN Callback 1 */ - if (htim->Instance == SAMPLING_TIM.Instance) - { - pm_system_led_toggle(); -#if (TEST_ENCODER_PLUSE == 0) - //data_sampling_once(); -#endif - } + /* USER CODE END Callback 0 */ + if (htim->Instance == TIM8) + { + HAL_IncTick(); + } + /* USER CODE BEGIN Callback 1 */ #if (USING_SOFT_ENCODER == 0) if (htim->Instance == ENCODER_TIM.Instance) { @@ -194,14 +187,12 @@ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) encoder_tim_overflow_count++; rt_interrupt_leave(); // Enable interrupts } -#endif // USING_SOFT_ENCODER - if (htim->Instance == TIMESTAMP_TIM.Instance) +#endif + if (htim->Instance == TIM3) { - rt_interrupt_enter(); - timestamp_tim_overflow_count++; - rt_interrupt_leave(); - } - /* USER CODE END Callback 1 */ + sampling_tim_elapsed_callback(); + } + /* USER CODE END Callback 1 */ } /** @@ -210,11 +201,12 @@ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) */ void Error_Handler(void) { - /* USER CODE BEGIN Error_Handler_Debug */ + /* USER CODE BEGIN Error_Handler_Debug */ /* User can add his own implementation to report the HAL error return state */ __disable_irq(); + ET_ERR("Error Handler Entered!\r\n"); while (1) {} - /* USER CODE END Error_Handler_Debug */ + /* USER CODE END Error_Handler_Debug */ } #ifdef USE_FULL_ASSERT /** @@ -226,9 +218,9 @@ void Error_Handler(void) */ void assert_failed(uint8_t *file, uint32_t line) { - /* USER CODE BEGIN 6 */ + /* USER CODE BEGIN 6 */ /* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ - /* USER CODE END 6 */ + /* USER CODE END 6 */ } #endif /* USE_FULL_ASSERT */ diff --git a/Core/Src/spi.c b/Core/Src/spi.c index 803215f..6f15db4 100644 --- a/Core/Src/spi.c +++ b/Core/Src/spi.c @@ -24,8 +24,43 @@ /* USER CODE END 0 */ +SPI_HandleTypeDef hspi2; SPI_HandleTypeDef hspi3; +DMA_HandleTypeDef hdma_spi2_rx; +DMA_HandleTypeDef hdma_spi2_tx; +/* SPI2 init function */ +void MX_SPI2_Init(void) +{ + + /* USER CODE BEGIN SPI2_Init 0 */ + + /* USER CODE END SPI2_Init 0 */ + + /* USER CODE BEGIN SPI2_Init 1 */ + + /* USER CODE END SPI2_Init 1 */ + hspi2.Instance = SPI2; + hspi2.Init.Mode = SPI_MODE_MASTER; + hspi2.Init.Direction = SPI_DIRECTION_2LINES; + hspi2.Init.DataSize = SPI_DATASIZE_16BIT; + hspi2.Init.CLKPolarity = SPI_POLARITY_LOW; + hspi2.Init.CLKPhase = SPI_PHASE_2EDGE; + hspi2.Init.NSS = SPI_NSS_SOFT; + hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16; + hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB; + hspi2.Init.TIMode = SPI_TIMODE_DISABLE; + hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; + hspi2.Init.CRCPolynomial = 10; + if (HAL_SPI_Init(&hspi2) != HAL_OK) + { + Error_Handler(); + } + /* USER CODE BEGIN SPI2_Init 2 */ + + /* USER CODE END SPI2_Init 2 */ + +} /* SPI3 init function */ void MX_SPI3_Init(void) { @@ -63,7 +98,68 @@ void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle) { GPIO_InitTypeDef GPIO_InitStruct = {0}; - if(spiHandle->Instance==SPI3) + if(spiHandle->Instance==SPI2) + { + /* USER CODE BEGIN SPI2_MspInit 0 */ + + /* USER CODE END SPI2_MspInit 0 */ + /* SPI2 clock enable */ + __HAL_RCC_SPI2_CLK_ENABLE(); + + __HAL_RCC_GPIOB_CLK_ENABLE(); + /**SPI2 GPIO Configuration + PB13 ------> SPI2_SCK + PB14 ------> SPI2_MISO + PB15 ------> SPI2_MOSI + */ + GPIO_InitStruct.Pin = SPI2_ADS8866_SCK_Pin|SPI2_ADS8866_MOSI_Pin; + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; + HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); + + GPIO_InitStruct.Pin = SPI2_ADS8866_MISO_Pin; + GPIO_InitStruct.Mode = GPIO_MODE_INPUT; + GPIO_InitStruct.Pull = GPIO_NOPULL; + HAL_GPIO_Init(SPI2_ADS8866_MISO_GPIO_Port, &GPIO_InitStruct); + + /* SPI2 DMA Init */ + /* SPI2_RX Init */ + hdma_spi2_rx.Instance = DMA1_Channel4; + hdma_spi2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; + hdma_spi2_rx.Init.PeriphInc = DMA_PINC_DISABLE; + hdma_spi2_rx.Init.MemInc = DMA_MINC_ENABLE; + hdma_spi2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; + hdma_spi2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; + hdma_spi2_rx.Init.Mode = DMA_NORMAL; + hdma_spi2_rx.Init.Priority = DMA_PRIORITY_HIGH; + if (HAL_DMA_Init(&hdma_spi2_rx) != HAL_OK) + { + Error_Handler(); + } + + __HAL_LINKDMA(spiHandle,hdmarx,hdma_spi2_rx); + + /* SPI2_TX Init */ + hdma_spi2_tx.Instance = DMA1_Channel5; + hdma_spi2_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; + hdma_spi2_tx.Init.PeriphInc = DMA_PINC_DISABLE; + hdma_spi2_tx.Init.MemInc = DMA_MINC_ENABLE; + hdma_spi2_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; + hdma_spi2_tx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; + hdma_spi2_tx.Init.Mode = DMA_NORMAL; + hdma_spi2_tx.Init.Priority = DMA_PRIORITY_HIGH; + if (HAL_DMA_Init(&hdma_spi2_tx) != HAL_OK) + { + Error_Handler(); + } + + __HAL_LINKDMA(spiHandle,hdmatx,hdma_spi2_tx); + + /* USER CODE BEGIN SPI2_MspInit 1 */ + + /* USER CODE END SPI2_MspInit 1 */ + } + else if(spiHandle->Instance==SPI3) { /* USER CODE BEGIN SPI3_MspInit 0 */ @@ -77,15 +173,15 @@ void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle) PB4 ------> SPI3_MISO PB5 ------> SPI3_MOSI */ - GPIO_InitStruct.Pin = SPI3_SCK_AD7793_Pin|SPI3_MOSI_AD7793_Pin; + GPIO_InitStruct.Pin = SPI3_AD7793_SCK_Pin|SPI3_AD7793_MOSI_Pin; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); - GPIO_InitStruct.Pin = SPI3_MISO_AD7793_Pin; + GPIO_InitStruct.Pin = SPI3_AD7793_MISO_Pin; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; - HAL_GPIO_Init(SPI3_MISO_AD7793_GPIO_Port, &GPIO_InitStruct); + HAL_GPIO_Init(SPI3_AD7793_MISO_GPIO_Port, &GPIO_InitStruct); /* USER CODE BEGIN SPI3_MspInit 1 */ @@ -96,7 +192,29 @@ void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle) void HAL_SPI_MspDeInit(SPI_HandleTypeDef* spiHandle) { - if(spiHandle->Instance==SPI3) + if(spiHandle->Instance==SPI2) + { + /* USER CODE BEGIN SPI2_MspDeInit 0 */ + + /* USER CODE END SPI2_MspDeInit 0 */ + /* Peripheral clock disable */ + __HAL_RCC_SPI2_CLK_DISABLE(); + + /**SPI2 GPIO Configuration + PB13 ------> SPI2_SCK + PB14 ------> SPI2_MISO + PB15 ------> SPI2_MOSI + */ + HAL_GPIO_DeInit(GPIOB, SPI2_ADS8866_SCK_Pin|SPI2_ADS8866_MISO_Pin|SPI2_ADS8866_MOSI_Pin); + + /* SPI2 DMA DeInit */ + HAL_DMA_DeInit(spiHandle->hdmarx); + HAL_DMA_DeInit(spiHandle->hdmatx); + /* USER CODE BEGIN SPI2_MspDeInit 1 */ + + /* USER CODE END SPI2_MspDeInit 1 */ + } + else if(spiHandle->Instance==SPI3) { /* USER CODE BEGIN SPI3_MspDeInit 0 */ @@ -109,7 +227,7 @@ void HAL_SPI_MspDeInit(SPI_HandleTypeDef* spiHandle) PB4 ------> SPI3_MISO PB5 ------> SPI3_MOSI */ - HAL_GPIO_DeInit(GPIOB, SPI3_SCK_AD7793_Pin|SPI3_MISO_AD7793_Pin|SPI3_MOSI_AD7793_Pin); + HAL_GPIO_DeInit(GPIOB, SPI3_AD7793_SCK_Pin|SPI3_AD7793_MISO_Pin|SPI3_AD7793_MOSI_Pin); /* USER CODE BEGIN SPI3_MspDeInit 1 */ diff --git a/Core/Src/stm32f1xx_it.c b/Core/Src/stm32f1xx_it.c index 12cc6cd..317df1e 100644 --- a/Core/Src/stm32f1xx_it.c +++ b/Core/Src/stm32f1xx_it.c @@ -22,11 +22,13 @@ #include "stm32f1xx_it.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ -#include "pm_board.h" +#include "bsp_misc.h" #include "pm_device.h" #include "pm_common.h" #include "rtthread.h" #include "et_log.h" +#include "bsp_hmi.h" +#include "bsp_motor.h" /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ @@ -60,10 +62,10 @@ extern pm_device_t pm_device; /* USER CODE END 0 */ /* External variables --------------------------------------------------------*/ -extern DMA_HandleTypeDef hdma_adc1; +extern DMA_HandleTypeDef hdma_spi2_rx; +extern DMA_HandleTypeDef hdma_spi2_tx; extern TIM_HandleTypeDef htim2; extern TIM_HandleTypeDef htim3; -extern TIM_HandleTypeDef htim4; extern UART_HandleTypeDef huart5; extern UART_HandleTypeDef huart1; extern UART_HandleTypeDef huart2; @@ -158,17 +160,31 @@ void DebugMon_Handler(void) /******************************************************************************/ /** - * @brief This function handles DMA1 channel1 global interrupt. + * @brief This function handles DMA1 channel4 global interrupt. */ -void DMA1_Channel1_IRQHandler(void) +void DMA1_Channel4_IRQHandler(void) { - /* USER CODE BEGIN DMA1_Channel1_IRQn 0 */ + /* USER CODE BEGIN DMA1_Channel4_IRQn 0 */ - /* USER CODE END DMA1_Channel1_IRQn 0 */ - HAL_DMA_IRQHandler(&hdma_adc1); - /* USER CODE BEGIN DMA1_Channel1_IRQn 1 */ + /* USER CODE END DMA1_Channel4_IRQn 0 */ + HAL_DMA_IRQHandler(&hdma_spi2_rx); + /* USER CODE BEGIN DMA1_Channel4_IRQn 1 */ - /* USER CODE END DMA1_Channel1_IRQn 1 */ + /* USER CODE END DMA1_Channel4_IRQn 1 */ +} + +/** + * @brief This function handles DMA1 channel5 global interrupt. + */ +void DMA1_Channel5_IRQHandler(void) +{ + /* USER CODE BEGIN DMA1_Channel5_IRQn 0 */ + + /* USER CODE END DMA1_Channel5_IRQn 0 */ + HAL_DMA_IRQHandler(&hdma_spi2_tx); + /* USER CODE BEGIN DMA1_Channel5_IRQn 1 */ + + /* USER CODE END DMA1_Channel5_IRQn 1 */ } /** @@ -199,20 +215,6 @@ void TIM3_IRQHandler(void) /* USER CODE END TIM3_IRQn 1 */ } -/** - * @brief This function handles TIM4 global interrupt. - */ -void TIM4_IRQHandler(void) -{ - /* USER CODE BEGIN TIM4_IRQn 0 */ - - /* USER CODE END TIM4_IRQn 0 */ - HAL_TIM_IRQHandler(&htim4); - /* USER CODE BEGIN TIM4_IRQn 1 */ - - /* USER CODE END TIM4_IRQn 1 */ -} - /** * @brief This function handles USART1 global interrupt. */ diff --git a/Core/Src/tim.c b/Core/Src/tim.c index 79439ab..d1fc7fc 100644 --- a/Core/Src/tim.c +++ b/Core/Src/tim.c @@ -27,7 +27,6 @@ uint32_t encoder_mode = 0; TIM_HandleTypeDef htim1; TIM_HandleTypeDef htim2; TIM_HandleTypeDef htim3; -TIM_HandleTypeDef htim4; /* TIM1 init function */ void MX_TIM1_Init(void) @@ -45,12 +44,12 @@ void MX_TIM1_Init(void) /* USER CODE END TIM1_Init 1 */ htim1.Instance = TIM1; - htim1.Init.Prescaler = TEC_TIM_PRESCALER; + htim1.Init.Prescaler = 7; htim1.Init.CounterMode = TIM_COUNTERMODE_UP; - htim1.Init.Period = TEC_TIM_COUNTER; + htim1.Init.Period = 899; htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim1.Init.RepetitionCounter = 0; - htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; + htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; if (HAL_TIM_PWM_Init(&htim1) != HAL_OK) { Error_Handler(); @@ -62,7 +61,7 @@ void MX_TIM1_Init(void) Error_Handler(); } sConfigOC.OCMode = TIM_OCMODE_PWM1; - sConfigOC.Pulse = 0; + sConfigOC.Pulse = 100; sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH; sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; @@ -75,7 +74,7 @@ void MX_TIM1_Init(void) sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE; sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE; sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF; - sBreakDeadTimeConfig.DeadTime = TEC_DEAD_TIME; + sBreakDeadTimeConfig.DeadTime = 0; sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE; sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH; sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE; @@ -144,14 +143,15 @@ void MX_TIM3_Init(void) TIM_ClockConfigTypeDef sClockSourceConfig = {0}; TIM_MasterConfigTypeDef sMasterConfig = {0}; + TIM_OC_InitTypeDef sConfigOC = {0}; /* USER CODE BEGIN TIM3_Init 1 */ /* USER CODE END TIM3_Init 1 */ htim3.Instance = TIM3; - htim3.Init.Prescaler = SAMPLING_TIM_PRE; + htim3.Init.Prescaler = 71; htim3.Init.CounterMode = TIM_COUNTERMODE_UP; - htim3.Init.Period = SAMPLING_TIM_PERIOD_COUNT; + htim3.Init.Period = 499; htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; if (HAL_TIM_Base_Init(&htim3) != HAL_OK) @@ -163,55 +163,28 @@ void MX_TIM3_Init(void) { Error_Handler(); } - sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; + if (HAL_TIM_PWM_Init(&htim3) != HAL_OK) + { + Error_Handler(); + } + sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK) { Error_Handler(); } + sConfigOC.OCMode = TIM_OCMODE_PWM1; + sConfigOC.Pulse = 10; + sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; + sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; + if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_3) != HAL_OK) + { + Error_Handler(); + } /* USER CODE BEGIN TIM3_Init 2 */ /* USER CODE END TIM3_Init 2 */ - -} -/* TIM4 init function */ -void MX_TIM4_Init(void) -{ - - /* USER CODE BEGIN TIM4_Init 0 */ - __HAL_RCC_TIM4_CLK_ENABLE(); - /* USER CODE END TIM4_Init 0 */ - - TIM_ClockConfigTypeDef sClockSourceConfig = {0}; - TIM_MasterConfigTypeDef sMasterConfig = {0}; - - /* USER CODE BEGIN TIM4_Init 1 */ - - /* USER CODE END TIM4_Init 1 */ - htim4.Instance = TIM4; - htim4.Init.Prescaler = US_TIM_PRE; - htim4.Init.CounterMode = TIM_COUNTERMODE_UP; - htim4.Init.Period = US_TIM_PERIOD_COUNT; - htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; - htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; - if (HAL_TIM_Base_Init(&htim4) != HAL_OK) - { - Error_Handler(); - } - sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; - if (HAL_TIM_ConfigClockSource(&htim4, &sClockSourceConfig) != HAL_OK) - { - Error_Handler(); - } - sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; - sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; - if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig) != HAL_OK) - { - Error_Handler(); - } - /* USER CODE BEGIN TIM4_Init 2 */ - HAL_TIM_Base_Start(&htim4); - /* USER CODE END TIM4_Init 2 */ + HAL_TIM_MspPostInit(&htim3); } @@ -280,21 +253,6 @@ void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle) /* USER CODE END TIM3_MspInit 1 */ } - else if(tim_baseHandle->Instance==TIM4) - { - /* USER CODE BEGIN TIM4_MspInit 0 */ - - /* USER CODE END TIM4_MspInit 0 */ - /* TIM4 clock enable */ - __HAL_RCC_TIM4_CLK_ENABLE(); - - /* TIM4 interrupt Init */ - HAL_NVIC_SetPriority(TIM4_IRQn, 0, 0); - HAL_NVIC_EnableIRQ(TIM4_IRQn); - /* USER CODE BEGIN TIM4_MspInit 1 */ - - /* USER CODE END TIM4_MspInit 1 */ - } } void HAL_TIM_MspPostInit(TIM_HandleTypeDef* timHandle) { @@ -305,27 +263,38 @@ void HAL_TIM_MspPostInit(TIM_HandleTypeDef* timHandle) /* USER CODE BEGIN TIM1_MspPostInit 0 */ /* USER CODE END TIM1_MspPostInit 0 */ - - __HAL_RCC_GPIOB_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); /**TIM1 GPIO Configuration - PB13 ------> TIM1_CH1N PA8 ------> TIM1_CH1 */ - GPIO_InitStruct.Pin = TIM1_CH1N_TEC_Pin; + GPIO_InitStruct.Pin = TEC_RIGHT_IN_Pin; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; - HAL_GPIO_Init(TIM1_CH1N_TEC_GPIO_Port, &GPIO_InitStruct); - - GPIO_InitStruct.Pin = TIM1_CH1_TEC_Pin; - GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; - HAL_GPIO_Init(TIM1_CH1_TEC_GPIO_Port, &GPIO_InitStruct); + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; + HAL_GPIO_Init(TEC_RIGHT_IN_GPIO_Port, &GPIO_InitStruct); /* USER CODE BEGIN TIM1_MspPostInit 1 */ /* USER CODE END TIM1_MspPostInit 1 */ } + else if(timHandle->Instance==TIM3) + { + /* USER CODE BEGIN TIM3_MspPostInit 0 */ + + /* USER CODE END TIM3_MspPostInit 0 */ + + __HAL_RCC_GPIOB_CLK_ENABLE(); + /**TIM3 GPIO Configuration + PB0 ------> TIM3_CH3 + */ + GPIO_InitStruct.Pin = ADS8866_CONVST_Pin; + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; + HAL_GPIO_Init(ADS8866_CONVST_GPIO_Port, &GPIO_InitStruct); + + /* USER CODE BEGIN TIM3_MspPostInit 1 */ + + /* USER CODE END TIM3_MspPostInit 1 */ + } } @@ -387,20 +356,6 @@ void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* tim_baseHandle) /* USER CODE END TIM3_MspDeInit 1 */ } - else if(tim_baseHandle->Instance==TIM4) - { - /* USER CODE BEGIN TIM4_MspDeInit 0 */ - - /* USER CODE END TIM4_MspDeInit 0 */ - /* Peripheral clock disable */ - __HAL_RCC_TIM4_CLK_DISABLE(); - - /* TIM4 interrupt Deinit */ - HAL_NVIC_DisableIRQ(TIM4_IRQn); - /* USER CODE BEGIN TIM4_MspDeInit 1 */ - - /* USER CODE END TIM4_MspDeInit 1 */ - } } /* USER CODE BEGIN 1 */ diff --git a/User/app/bsp_debug.c b/User/app/bsp_debug.c new file mode 100644 index 0000000..644b7a4 --- /dev/null +++ b/User/app/bsp_debug.c @@ -0,0 +1,79 @@ +/* + * @Author: mypx + * @Date: 2025-10-15 14:22:42 + * @LastEditTime: 2025-10-24 15:07:15 + * @LastEditors: mypx mypx_coder@163.com + * @Description: + */ +#include "bsp_tec.h" +#include "et_log.h" +#include +#include +#include + +static int test_tec_cooling_open(int argc, char **argv) +{ + (void)argc; + (void)argv; + bsp_tec_cooling_open(); + return 0; +} +MSH_CMD_EXPORT_ALIAS(test_tec_cooling_open, cool_open, "open cooling"); + +static int test_tec_heating_open(int argc, char **argv) +{ + (void)argc; + (void)argv; + bsp_tec_heating_open(); + return 0; +} +MSH_CMD_EXPORT_ALIAS(test_tec_heating_open, heat_open, "open heating"); + + +static int test_adjust_tec_cooling_duty_cycle(int argc, char **argv) +{ + uint32_t duty_cycle = 0; + if (argc != 2) + { + rt_kprintf("Usage: tldc \n"); + return -1; + } + duty_cycle = atof(argv[1]); + ET_DBG("duty_cycle:%.2f", duty_cycle); + bsp_tec_cooling_adjust(duty_cycle); + return 0; +} +MSH_CMD_EXPORT_ALIAS(test_adjust_tec_cooling_duty_cycle, tldc, "adjust cool level"); + +static int test_adjust_tec_heating_duty_cycle(int argc, char **argv) +{ + uint32_t duty_cycle = 0; + if (argc != 2) + { + rt_kprintf("Usage: trdc \n"); + return -1; + } + duty_cycle = atof(argv[1]); + ET_DBG("duty_cycle:%.2f", duty_cycle); + bsp_tec_heating_adjust(duty_cycle); + return 0; +} +MSH_CMD_EXPORT_ALIAS(test_adjust_tec_heating_duty_cycle, trdc, "adjust heat level"); + +static int test_fan_start(int argc, char **argv) +{ + (void)argc; + (void)argv; + bsp_fan_start(); + return 0; +} +MSH_CMD_EXPORT_ALIAS(test_fan_start, fan_start, "start fan"); + +static int test_fan_stop(int argc, char **argv) +{ + (void)argc; + (void)argv; + bsp_fan_stop(); + return 0; +} +MSH_CMD_EXPORT_ALIAS(test_fan_stop, fan_stop, "stop fan"); diff --git a/User/app/data_process.c b/User/app/data_process.c index 7b5a11e..dcdc32e 100644 --- a/User/app/data_process.c +++ b/User/app/data_process.c @@ -2,12 +2,13 @@ * @Date: 2025-07-18 09:24:34 * @Author: mypx * @LastEditors: mypx mypx_coder@163.com - * @LastEditTime: 2025-09-26 08:58:26 + * @LastEditTime: 2025-10-27 10:42:07 * @FilePath: data_process.c * @Description: * Copyright (c) 2025 by mypx, All Rights Reserved. */ #include "data_process.h" +#include "bsp_collect.h" #include "data_sampling.h" #include "et_log.h" #include "pm_common.h" @@ -47,8 +48,8 @@ float adc_filter_buffer[DATA_PROCESS_UNIT_COUNT] = {0}; #define ANALYSIS_SIZE DATA_PROCESS_UNIT_COUNT -static bool cali_flag = false; -static bool rdy_flag = false; +// static bool cali_flag = false; +// static bool rdy_flag = false; pm_meas_t pm_meas; @@ -66,7 +67,8 @@ void set_pm_stage(int argc, char **argv) pm_meas.state = stage; } MSH_CMD_EXPORT_ALIAS(set_pm_stage, sst, set state); -uint32_t data_count = 0; + +// uint32_t data_count = 0; // 只考虑校准和测量状态,其他状态由采集线程 或者主线程管理 void data_process_thread_entry(void *parameter) @@ -76,14 +78,29 @@ void data_process_thread_entry(void *parameter) int find_status = 0; uint16_t *raw_buf; uint16_t *used_buf; + pm_fsm_t *fsm = get_pm_fsm(); + uint32_t times = 0; + + bsp_light_data_sampling_start(); while (1) { - result = rt_mq_recv(&adc_mq, &raw_buf, sizeof(adc_raw_buffer), RT_WAITING_FOREVER); + /* receive pointer to buffer (sender uses sizeof(ptr)) */ + result = rt_mq_recv(&adc_mq, &raw_buf, sizeof(void *), RT_WAITING_FOREVER); if (result == RT_EOK) { - data_sampling_stop(); - if (get_find_zero_state(&pm_meas) == ZF_FAST_CLOSE_STAGE && fsm_state == false && pm_meas.p50 > 0.1f) +#if (DEBUG_LIGHT_DATA_ONLY == 1) + for (int i = 0; i < DATA_PROCESS_UNIT_COUNT; i++) + { + ET_DBG("[%d] r:%d", times++, raw_buf[i]); + if (times >= ANALYSIS_SIZE) + times = 0; + } + bsp_light_data_sampling_start(); + continue; +#endif + if (get_find_zero_state(&pm_meas) == ZF_FAST_CLOSE_STAGE && fsm->fz_fsm_state == PM_FSM_STOP + && pm_meas.p50 > 0.1f) sv630p_speed_mode_stop(&sv630p_handle); #if (ENABLE_IIR_FILTER == 1) if (et_iir_filter_process(&iir_filter, (const void *)raw_buf, (void *)adc_filter_buffer) == IIR_OK) @@ -93,22 +110,15 @@ void data_process_thread_entry(void *parameter) #else used_buf = raw_buf; #endif -#if (SAMPLING_RAW_DATA == 1) - for (int i = 0; i < DATA_PROCESS_UNIT_COUNT; i++) - { - ET_DBG("r:%d, f:%d", raw_buf[i], (uint16_t)adc_filter_buffer[i]); - } -#endif - -#if (SAMPLING_RAW_DATA == 0) - pm_error_led_on(); // for timing + bsp_error_led_on(); // for timing find_status = find_zero_process(&pm_meas, (const uint16_t *)used_buf, DATA_PROCESS_UNIT_COUNT); - pm_error_led_off(); -#endif + bsp_error_led_off(); + if (find_status == 0) { - data_sampling_start(); - if (get_find_zero_state(&pm_meas) == ZF_FAST_CLOSE_STAGE && fsm_state == false && pm_meas.p50 > 0.1f) + bsp_light_data_sampling_start(); + if (get_find_zero_state(&pm_meas) == ZF_FAST_CLOSE_STAGE && fsm->fz_fsm_state == PM_FSM_STOP + && pm_meas.p50 > 0.1f) sv630p_speed_mode_resume(&sv630p_handle); } else @@ -138,7 +148,7 @@ int data_process_init(void) return -1; } //valley_seek_init(&pm_meas.valley_seek); - working_set_state(PM_MEAS_TEST_Mode); + if (et_zero_cross_create(&pm_meas.zc, 100) != 0) { ET_ERR("zc create failed\n"); @@ -177,13 +187,13 @@ int data_process_init(void) #endif etk_position_pid_init(&pm_meas.pid, - 1.8f, // kp: 提高比例增益,加快响应速度 - 1.8f, // ki: 增加积分增益,加快接近目标时的收敛速度 - 0.25f, // kd: 进一步降低微分作用,减少阻尼 - PID_TARGET_ZERO_POS_VAL, // 目标值设置为0.6 - 2.5f, // 积分限幅,允许更多积分累积加快收敛 - -FZ_FAST_CLOSE_STAGE_RPM, // 最小输出(限制最大反转速度) - FZ_FAST_CLOSE_STAGE_RPM, -1); + 1.8f, // kp: 提高比例增益,加快响应速度 + 1.8f, // ki: 增加积分增益,加快接近目标时的收敛速度 + 0.25f, // kd: 进一步降低微分作用,减少阻尼 + PID_TARGET_ZERO_POS_VAL, // 目标值设置为0.6 + 2.5f, // 积分限幅,允许更多积分累积加快收敛 + -FZ_FAST_CLOSE_STAGE_RPM, // 最小输出(限制最大反转速度) + FZ_FAST_CLOSE_STAGE_RPM, -1); result = rt_thread_init(&data_process_thread, // 线程控制块指针 "d-process", // 线程名称 data_process_thread_entry, // 入口函数 @@ -204,4 +214,4 @@ int data_process_init(void) rt_kprintf("Thread init failed: 0x%04X\n", result); } return 0; -} \ No newline at end of file +} diff --git a/User/app/data_sampling.c b/User/app/data_sampling.c index 6179c49..fbd4630 100644 --- a/User/app/data_sampling.c +++ b/User/app/data_sampling.c @@ -2,7 +2,7 @@ * @Date: 2025-07-15 11:12:00 * @Author: mypx * @LastEditors: mypx mypx_coder@163.com - * @LastEditTime: 2025-09-24 14:24:04 + * @LastEditTime: 2025-10-24 10:05:49 * @FilePath: data_sampling.c * @Description: * Copyright (c) 2025 by mypx, All Rights Reserved. @@ -14,6 +14,7 @@ #include "servo.h" #include #include +#include "bsp_collect.h" uint16_t adc_raw_buffer[DATA_PROCESS_UNIT_COUNT] = {0}; @@ -21,54 +22,22 @@ float sampling_rate = 0; static void *mq_pool[8]; struct rt_messagequeue adc_mq; -/* 半缓冲完成回调 */ -// void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef *hadc) -// { -// if (hadc->Instance == ADC1) -// { -// __HAL_DMA_CLEAR_FLAG(hadc->DMA_Handle, __HAL_DMA_GET_HT_FLAG_INDEX(hadc->DMA_Handle)); -// uint16_t *ptr = &adc_raw_buffer[0]; -// pm_system_led_on(); -// rt_mq_send(&adc_mq, &ptr, sizeof(ptr)); -// } -// } /* 整个缓冲区完成回调 */ -void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) +void light_data_sampling_complete(void) { - if (hadc->Instance == ADC1) + uint16_t *ptr = &adc_raw_buffer[0]; + //bsp_system_led_toggle(); + if (rt_mq_send(&adc_mq, &ptr, sizeof(ptr)) != RT_EOK) { - __HAL_DMA_CLEAR_FLAG(hadc->DMA_Handle, __HAL_DMA_GET_TC_FLAG_INDEX(hadc->DMA_Handle)); - uint16_t *ptr = &adc_raw_buffer[0]; - pm_system_led_toggle - (); - if (rt_mq_send(&adc_mq, &ptr, sizeof(ptr)) != RT_EOK) - { - ET_ERR("ADC MQ full!"); - } + ET_ERR("ADC MQ full!"); } } -void data_sampling_start(void) +int light_data_sampling_init(void) { - pm_sampling_adc_start(adc_raw_buffer, DATA_PROCESS_UNIT_COUNT); - pm_sampling_tim_start(); - //pm_timestamp_start(); -} - -void data_sampling_stop(void) -{ - //pm_encoder_stop(); - // pm_sampling_tim_stop(); - // pm_timestamp_stop(); - pm_sampling_adc_stop(); -} - -int data_sampling_init(void) -{ - pm_adc_init(); - pm_sampling_tim_init(); + bsp_light_data_sampling_init(adc_raw_buffer, DATA_PROCESS_UNIT_COUNT, light_data_sampling_complete); pm_encoder_init(); //pm_timestamp_tim_init(); pm_encoder_start(); @@ -77,9 +46,8 @@ int data_sampling_init(void) rt_mq_init(&adc_mq, "adc_mq", mq_pool, sizeof(void *), sizeof(mq_pool), RT_IPC_FLAG_FIFO); // test-> set state manually //pm_set_state(PM_CALIBRATION_STATE); - sampling_rate = pm_sampling_tim_get_freq(); + sampling_rate = bsp_sampling_tim_get_freq(); ET_WARN("Sampling rate:%.2f", sampling_rate); - data_sampling_start(); // test->start at init return 0; } @@ -116,4 +84,4 @@ void m_fast(void) { sv630p_speed_mode_start(&sv630p_handle, 400); } -MSH_CMD_EXPORT(m_fast, motor fast forward); \ No newline at end of file +MSH_CMD_EXPORT(m_fast, motor fast forward); diff --git a/User/app/data_sampling.h b/User/app/data_sampling.h index c1159b9..252bfae 100644 --- a/User/app/data_sampling.h +++ b/User/app/data_sampling.h @@ -2,7 +2,7 @@ * @Date: 2025-07-15 11:12:08 * @Author: mypx * @LastEditors: mypx mypx_coder@163.com - * @LastEditTime: 2025-09-16 11:25:32 + * @LastEditTime: 2025-10-14 13:47:14 * @FilePath: data_sampling.h * @Description: * Copyright (c) 2025 by mypx, All Rights Reserved. @@ -10,7 +10,7 @@ #ifndef __DATA_SAMPLING_H__ #define __DATA_SAMPLING_H__ #include "bsp_encoder.h" -#include "pm_board.h" +#include "bsp_misc.h" extern struct rt_messagequeue adc_mq; @@ -19,10 +19,6 @@ extern uint16_t adc_raw_buffer[DATA_PROCESS_UNIT_COUNT]; extern float sampling_rate; -int data_sampling_init(void); - -void data_sampling_start(void); - -void data_sampling_stop(void); +int light_data_sampling_init(void); #endif diff --git a/User/app/mb_hmi/CMakeLists.txt b/User/app/mb_hmi/CMakeLists.txt index ce8f4aa..8ee5651 100644 --- a/User/app/mb_hmi/CMakeLists.txt +++ b/User/app/mb_hmi/CMakeLists.txt @@ -1,31 +1,14 @@ -# mb_hmi模块的CMakeLists.txt - -# 创建mb_hmi库 add_library(mb_hmi STATIC mb_command.c mb_server.c mb_interface.c ) -# 设置包含目录 target_include_directories(mb_hmi PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ) -# 链接依赖库 target_link_libraries(mb_hmi - PUBLIC - nanomodbus - # 根据需要添加其他依赖 -) - -# 如果etk_os_adapt可用,链接它 -if(TARGET etk_os_adapt) - target_link_libraries(mb_hmi PUBLIC etk_os_adapt) -endif() - -# 根据系统类型设置编译选项 -if(DEFINED SYSTEM_TYPE) - message(STATUS "[mb_hmi] System type: ${SYSTEM_TYPE}") -endif() \ No newline at end of file + PUBLIC nanomodbus +) \ No newline at end of file diff --git a/User/app/mb_hmi/hmi_server.c b/User/app/mb_hmi/hmi_server.c index 002de12..2250623 100644 --- a/User/app/mb_hmi/hmi_server.c +++ b/User/app/mb_hmi/hmi_server.c @@ -3,6 +3,9 @@ #include "etk_utils.h" #include "mb_command.h" #include "mb_interface.h" +#include "bsp_hmi.h" +#include "bsp_motor.h" + nmbs_t nmbs; static rt_uint8_t mb_hmi_stack[HMI_THREAD_STACK_SIZE]; @@ -25,8 +28,8 @@ static nmbs_error nmbs_server_init(nmbs_t *nmbs) nmbs_platform_conf_create(&conf); conf.transport = NMBS_TRANSPORT_RTU; - conf.read = pm_hmi_uart_read; - conf.write = pm_hmi_uart_write; + conf.read = bsp_hmi_uart_read; + conf.write = bsp_hmi_uart_write; nmbs_callbacks_create(&cb); cb.read_coils = NULL; @@ -142,7 +145,7 @@ int hmi_server_handler_register(hmi_server_t *hmi, mb_cmd_handler_t *handler) int hmi_server_init(hmi_server_t *hmi) { - if (pm_mb_hmi_init() != 0) + if (bsp_mb_hmi_init() != 0) { ET_ERR("Failed to initialize Modbus HMI"); return -1; diff --git a/User/app/mb_hmi/hmi_server.h b/User/app/mb_hmi/hmi_server.h index 689dc70..cf785bc 100644 --- a/User/app/mb_hmi/hmi_server.h +++ b/User/app/mb_hmi/hmi_server.h @@ -1,7 +1,7 @@ #ifndef __HMI_SERVER_H__ #define __HMI_SERVER_H__ #include "nanomodbus.h" -#include "pm_board.h" +#include "bsp_misc.h" #include "pm_common.h" #include "mb_command.h" diff --git a/User/app/mb_hmi/mb_command.c b/User/app/mb_hmi/mb_command.c index a485560..ee4b1cb 100644 --- a/User/app/mb_hmi/mb_command.c +++ b/User/app/mb_hmi/mb_command.c @@ -1,7 +1,7 @@ /* * @Author: mypx * @Date: 2025-08-01 16:41:44 - * @LastEditTime: 2025-09-26 15:00:17 + * @LastEditTime: 2025-09-26 09:21:13 * @LastEditors: mypx mypx_coder@163.com * @Description: */ diff --git a/User/app/pm_common.h b/User/app/pm_common.h index b9890a4..537100a 100644 --- a/User/app/pm_common.h +++ b/User/app/pm_common.h @@ -2,7 +2,7 @@ * @Date: 2025-07-18 08:53:16 * @Author: mypx * @LastEditors: mypx mypx_coder@163.com - * @LastEditTime: 2025-09-24 16:11:11 + * @LastEditTime: 2025-10-14 13:56:58 * @FilePath: pm_common.h * @Description: * Copyright (c) 2025 by mypx, All Rights Reserved. @@ -10,7 +10,7 @@ #ifndef __PM_COMMON__ #define __PM_COMMON__ #include "bsp_encoder.h" -#include "pm_board.h" +#include "bsp_misc.h" #include #include diff --git a/User/app/pm_device.c b/User/app/pm_device.c index 7b132ce..f737202 100644 --- a/User/app/pm_device.c +++ b/User/app/pm_device.c @@ -1,7 +1,7 @@ /* * @Author: mypx * @Date: 2025-07-07 14:25:47 - * @LastEditTime: 2025-09-26 10:49:49 + * @LastEditTime: 2025-10-24 13:06:47 * @LastEditors: mypx mypx_coder@163.com * @Description: */ @@ -14,6 +14,7 @@ #include "pt100x.h" #include "servo.h" #include "storage.h" +#include "bsp_tec.h" extern ad7793_dev_t ad7793_dev; @@ -89,21 +90,29 @@ int pm_device_start(pm_device_t *dev) int pm_device_init(pm_device_t *dev) { - pm_board_init(); + int ret = 0; + pm_board_init(); // TODO:Delete this later + bsp_tec_init(); pm_params_init(&dev->params); - hmi_server_handler_register(&dev->hmi, &hmi_cmd_handler); - hmi_server_init(&dev->hmi); + // hmi_server_handler_register(&dev->hmi, &hmi_cmd_handler); + // hmi_server_init(&dev->hmi); pm_fsm_init(&dev->fz_fsm); //storage_init(); - tec_control_init(&dev->tec); - // if (servo_init() != 0) - // { - // ET_ERR("Servo init failed!"); - // //return; - // } - // data_sampling_init(); - // data_process_init(); + ret = tec_control_init(&dev->tec); + if(ret != 0) + { + ET_WARN("TEC control init failed!"); + return -1; + } + tec_control_sercie_start(&dev->tec); + if (servo_init() != 0) + { + ET_ERR("Servo init failed!"); + return -1; + } + light_data_sampling_init(); + data_process_init(); return 0; } diff --git a/User/app/pm_device.h b/User/app/pm_device.h index b100837..5ae791f 100644 --- a/User/app/pm_device.h +++ b/User/app/pm_device.h @@ -9,7 +9,7 @@ */ #ifndef __PM_DEVICE_H__ #define __PM_DEVICE_H__ -#include "pm_board.h" +#include "bsp_misc.h" #include "bsp_encoder.h" #include "mb_command.h" #include "tec_control.h" diff --git a/User/app/pm_meas.c b/User/app/pm_meas.c index ec9b96e..b50060c 100644 --- a/User/app/pm_meas.c +++ b/User/app/pm_meas.c @@ -1,7 +1,7 @@ /* * @Author: mypx * @Date: 2025-08-11 08:30:45 - * @LastEditTime: 2025-09-24 16:12:52 + * @LastEditTime: 2025-10-27 15:06:18 * @LastEditors: mypx mypx_coder@163.com * @Description: */ @@ -9,15 +9,13 @@ #include "data_process.h" #include "data_sampling.h" #include "et_waveform_analysis.h" -#include "etk_diff.h" -#include "etk_ringbuffer.h" #include "servo.h" #include #include #include -static pm_fsm_t *pm_fsm = NULL; +static pm_fsm_t *pm_fsm = NULL; pm_fsm_t *get_pm_fsm(void) { @@ -39,7 +37,7 @@ void calibrate_mode_toggle(void) if (!fsm) return; fsm->fz_fsm_state = false; - fsm->meas_mode = fsm->meas_mode == PM_MEAS_NORMAL_Mode ? PM_MEAS_TEST_Mode : PM_MEAS_NORMAL_Mode; + fsm->meas_mode = fsm->meas_mode == PM_MEAS_NORMAL_Mode ? PM_MEAS_TEST_Mode : PM_MEAS_NORMAL_Mode; } MSH_CMD_EXPORT_ALIAS(calibrate_mode_toggle, cali, calibrate mode toggle); @@ -168,17 +166,18 @@ void accuracy_auto_measurement_once(pm_meas_t *fz, float angle) #endif uint32_t delay = convert_encoder_angle_rpm_to_to_time(diff, RUN_OFFSET_ANGLE_RPM); ET_DBG("wait %d ms to target:%.2f", delay, target_angle); - int speed = (target_angle > angle) ? -RUN_OFFSET_ANGLE_RPM - : RUN_OFFSET_ANGLE_RPM; // FIXME:note encoder dir and motor dir + int speed = (target_angle > angle) ? (RUN_OFFSET_ANGLE_RPM * ENCODER_MOTOR_DIR_FACTOR) + : (-RUN_OFFSET_ANGLE_RPM * ENCODER_MOTOR_DIR_FACTOR); // FIXME:note encoder dir and motor dir sv630p_speed_mode_start(&sv630p_handle, speed); fz->target_dir = (speed > 0) ? -1 : 1; ET_WARN("SPEED:%d", speed); + uint32_t start_time = rt_tick_get_millisecond(); while (1) { angle = get_real_time_angle_degree(); //ET_DBG("angle:%.2f", angle); // 根据方向判断是否达到目标角度,处理负数角度情况 - if (((speed > 0) && (target_angle > angle)) || ((speed < 0) && (target_angle < angle))) + if (((speed > 0) && (target_angle > angle)) || ((speed < 0) && (target_angle < angle))) //1000, -183,1124 { sv630p_speed_mode_stop(&sv630p_handle); ET_INFO("Set angle:%.3f, reached:%.3f", target_angle, angle); @@ -197,6 +196,12 @@ void accuracy_auto_measurement_once(pm_meas_t *fz, float angle) break; } + else if (rt_tick_get_millisecond() - start_time > delay) + { + ET_WARN("Timeout, set angle:%.3f, reached:%.3f, speed:%d", target_angle, angle, speed); + sv630p_speed_mode_stop(&sv630p_handle); + break; + } } } @@ -321,10 +326,12 @@ StageResult is_prepare_leave_stage(pm_meas_t *fz) StageResult is_wave_leave_stage(pm_meas_t *fz) { - if ((fz->slope < -100000) || (fz->freq > 52 && fz->freq < 98 && fz->p50 < 1000 && fz->trend == TREND_DECREASE) - || (fz->freq < 1.0 && fz->trend == TREND_DECREASE)) - return STAGE_RESULT_OK; - else + // if ((fz->slope < -100000) || (fz->freq > 52 && fz->freq < 98 && fz->p50 < 1000 && fz->trend == TREND_DECREASE) + // || (fz->freq < 1.0 && fz->trend == TREND_DECREASE)) // old version parameter + // if ((fz->slope < -200 && (fz->freq < 48 || fz->freq > 102) && fz->p50 < 1000 && fz->trend == TREND_DECREASE) + // || (fz->freq < 1.0 && fz->trend == TREND_DECREASE)) + // return STAGE_RESULT_OK; + // else return STAGE_RESULT_NO; } @@ -522,10 +529,10 @@ int find_zero_process(pm_meas_t *fz, const uint16_t *adc_buf, uint32_t len) { sv630p_speed_mode_stop(&sv630p_handle); measurement_info_print(fz); - fsm->fz_fsm_state = true; // test use - fz->calib_cnt = 0; - fsm->meas_mode = PM_MEAS_NORMAL_Mode; - fz->target_dir = MEASURE_DEF_DIR; + fsm->fz_fsm_state = true; // test use + fz->calib_cnt = 0; + fsm->meas_mode = PM_MEAS_NORMAL_Mode; + fz->target_dir = MEASURE_DEF_DIR; //ET_WARN("Mesaure time:%d ms", rt_tick_get_millisecond() - fz->calib_start_time); } break; @@ -553,6 +560,7 @@ int find_zero_process(pm_meas_t *fz, const uint16_t *adc_buf, uint32_t len) int pm_fsm_init(pm_fsm_t *fsm) { - pm_fsm = fsm; + pm_fsm = fsm; + fsm->fz_fsm_state = true; return 0; } diff --git a/User/app/pm_meas.h b/User/app/pm_meas.h index ec7861a..a4f9445 100644 --- a/User/app/pm_meas.h +++ b/User/app/pm_meas.h @@ -1,7 +1,7 @@ /* * @Author: mypx * @Date: 2025-08-11 08:30:53 - * @LastEditTime: 2025-09-24 16:10:18 + * @LastEditTime: 2025-10-24 17:11:50 * @LastEditors: mypx mypx_coder@163.com * @Description: */ @@ -13,18 +13,18 @@ #include "etk_slope.h" #include "pm_common.h" -#define FZ_CALI_MAX_COUNT 10 +#define FZ_CALI_MAX_COUNT 4 #define ZC_COUNT_STABLE_TIMES 5 -#define ZERO_POS_THRESHOLD 0.5f +#define ZERO_POS_THRESHOLD 1.0f #define PID_TARGET_ZERO_POS_VAL 0.0f #define RUN_OFFSET_ANGLE_RPM (1000) -#define FZ_UNKNOWN_STAGE_RPM 1000 -#define FZ_SATURATED_STAGE_RPM 1000 -#define FZ_WAVE_ENTER_STAGE_RPM 1000 -#define FZ_PREPARE_STAGE_RPM 1000 -#define FZ_FAST_CLOSE_STAGE_RPM 700 +#define FZ_UNKNOWN_STAGE_RPM 2000 +#define FZ_SATURATED_STAGE_RPM 2000 +#define FZ_WAVE_ENTER_STAGE_RPM 2000 +#define FZ_PREPARE_STAGE_RPM 2000 +#define FZ_FAST_CLOSE_STAGE_RPM 1400 #define FZ_PRECISE_ADJUST_MIN_RPM (0.6f * MOTOR_TO_ENCODER_FACTOR) // 0.6rpm->0.001 #define FZ_SATURATED_POWER_MAX 5.0f @@ -40,8 +40,6 @@ #define FZ_PREPARE_LEAVE_FREQ_MIN 48.0f #define FZ_PREPARE_LEAVE_FREQ_MAX 52.0f -extern bool fsm_state; - typedef enum { TREND_UNKNOWN = 0, // 未知(首次调用,无历史数据) @@ -146,4 +144,6 @@ int find_zero_process(pm_meas_t *fz, const uint16_t *adc_val, uint32_t len); int pm_fsm_init(pm_fsm_t *fsm); +pm_fsm_t *get_pm_fsm(void); + #endif diff --git a/User/app/servo.c b/User/app/servo.c index ebb9358..d5dca2d 100644 --- a/User/app/servo.c +++ b/User/app/servo.c @@ -2,14 +2,14 @@ * @Date: 2025-06-26 14:49:09 * @Author: mypx * @LastEditors: mypx mypx_coder@163.com - * @LastEditTime: 2025-08-29 09:08:23 + * @LastEditTime: 2025-10-15 16:53:50 * @FilePath: servo.c * @Description: * Copyright (c) 2025 by mypx, All Rights Reserved. */ #include "servo.h" #include "nanomodbus.h" -#include "pm_board.h" +#include "bsp_misc.h" #include "rtdef.h" #include "rtthread.h" @@ -102,12 +102,12 @@ int servo_init(void) int ret = 0; nmbs_platform_conf stm32_conf; // board layer init - pm_servo_init(); + bsp_servo_init(); // nmbs layer init nmbs_platform_conf_create(&stm32_conf); stm32_conf.transport = NMBS_TRANSPORT_RTU; - stm32_conf.read = pm_servo_uart_read; - stm32_conf.write = pm_servo_uart_write; + stm32_conf.read = bsp_servo_uart_read; + stm32_conf.write = bsp_servo_uart_write; nmbs_error status = nmbs_client_create(&servo_nmbs, &stm32_conf); if (status != NMBS_ERROR_NONE) diff --git a/User/app/servo.h b/User/app/servo.h index eac7aa6..732e76a 100644 --- a/User/app/servo.h +++ b/User/app/servo.h @@ -2,20 +2,21 @@ * @Date: 2025-06-26 14:49:15 * @Author: mypx * @LastEditors: mypx mypx_coder@163.com - * @LastEditTime: 2025-08-19 08:51:18 + * @LastEditTime: 2025-10-15 16:55:31 * @FilePath: servo.h * @Description: * Copyright (c) 2025 by mypx, All Rights Reserved. */ #ifndef __SERVO_H__ #define __SERVO_H__ -#include "pm_board.h" +#include "bsp_misc.h" #include "sv_device.h" #include "pm_common.h" #include "et_log.h" #include "h02_basic_control.h" #include "h31_comm_related_var.h" #include "h0d_af.h" +#include "bsp_motor.h" #if (ENABLE_SV630P == 1) extern sv630p_handle_t sv630p_handle; diff --git a/User/app/storage.c b/User/app/storage.c index 41a2aeb..f4976e2 100644 --- a/User/app/storage.c +++ b/User/app/storage.c @@ -2,15 +2,15 @@ #include #include #include "storage.h" -#include "pm_board.h" +#include "bsp_misc.h" #include "i2c.h" void storage_dbg(const char *tag, const char *fmt, ...); at24cx_dev_t at24c02_device = { .dev_addr = 0xA0, - .write_bytes = pm_i2c_write_bytes, - .read_bytes = pm_i2c_read_bytes, + .write_bytes = bsp_i2c_write_bytes, + .read_bytes = bsp_i2c_read_bytes, .log = storage_dbg, }; @@ -51,7 +51,7 @@ void storage_test(void) rt_kprintf("Failed to write data to EEPROM\r\n"); } rt_thread_delay(1000); // Delay for 1 second - pm_system_led_toggle(); // Toggle the system LEDs + bsp_system_led_toggle(); // Toggle the system LEDs //rt_kprintf("[%d] System LED toggled\n", rt_tick_get()); // Print message to console if (at24cx_read(&at24c02_device, 0x0a, read_buf, 8) != 0) { diff --git a/User/app/tec_control.c b/User/app/tec_control.c index ea84704..83e36b2 100644 --- a/User/app/tec_control.c +++ b/User/app/tec_control.c @@ -2,34 +2,87 @@ * @Date: 2025-06-23 13:04:20 * @Author: mypx * @LastEditors: mypx mypx_coder@163.com - * @LastEditTime: 2025-09-25 16:49:59 + * @LastEditTime: 2025-10-27 08:42:50 * @FilePath: tec_control.c * @Description: * Copyright (c) 2025 by mypx, All Rights Reserved. */ #include "tec_control.h" #include "ad7793.h" +#include "bsp_misc.h" +#include "bsp_tec.h" +#include "bsp_temper_sampling.h" #include "et_log.h" #include "etk_utils.h" #include "mb_interface.h" -#include "pm_board.h" #include "pm_common.h" #include "pt100x.h" +#include +#include #include #include +#include /* rand, RAND_MAX */ + #if TEC_CONTROL_PERIOD < ADC_SAMPLE_PERIOD #error "TEC_CONTROL_PERIOD must be greater than ADC_SAMPLE_PERIOD" #endif +#define TEST_TEMPER_XFER 0 + +/* Local helpers to avoid implicit prototypes on some embedded libc's */ +static inline int _isnan_f(float v) +{ + return v != v; +} +static inline float _fabs_f(float v) +{ + return v < 0.0f ? -v : v; +} + /* Define AD7793 device structure */ -ad7793_dev_t ad7793_dev; -extern ad7793_hw_if_t ad7793_hw_if; +ad7793_dev_t ad7793_dev; + #if (USING_SOFT_SPI == 1) extern soft_spi_t soft_spi; #endif tec_control_t *tec_control = NULL; +ad7793_hw_if_t ad7793_hw_if = { + .spi_transfer = bsp_ad7793_spi_transfer, + .spi_write = bsp_ad7793_spi_write, + .spi_read = bsp_ad7793_spi_read, + .gpio_set = bsp_ad7793_gpio_set, + .gpio_get = bsp_ad7793_gpio_get, + .delay_ms = bsp_ad7793_delay_ms, +}; + +/* ------------------------------------------------------------- + * AD7793 应用层配置 + * 依据提供原理图的典型 RTD/PT100 测温拓扑做如下假设: + * 1. 仅测量正向电压 => 使用单极 (unipolar) + * 2. 传感器电流源(或恒流驱动)下 PT100 产生的电压几十 mV 级,选择增益 16: + * - 内部参考 1.17V / GAIN16 满量程约 73mV,有足够余量覆盖 0~200+℃ (约 21mV~37mV) + * - 若后续发现仍远低于满量程,可改为 GAIN32;若出现接近饱和则降为 GAIN8。 + * 3. 启用输入缓冲 (buffered=true) 以减小前端阻抗对 ADC 的影响。 + * 4. 采用内部参考 (1.17V) 简化;若外部精密参考芯片实际接入 REF+ / REF-, + * 将 use_internal_ref 改为 false 并填写 external_ref (例如 2.500f 或 3.000f)。 + * 5. 选择较低输出数据速率 8.33Hz 获取更好噪声性能(速率越低,数字滤波越强)。 + * 6. 上电后做一次 system zero 校准,提高 offset 精度。 + * ------------------------------------------------------------- */ +static const ad7793_config_t g_ad7793_config = { + .use_internal_ref = false, /* 现在使用板上 2.5V 外部参考 (已实测 REFIN+=2.5V) */ + .external_ref = 2.500f, + /* 传感器差分约 0.044V, 需保证 < Vref / Gain + 选择 GAIN=4 -> 满量程 2.5/4=0.625V (安全覆盖 0.44V); 若后续电压更低可再调高增益 */ + .gain = AD7793_GAIN_4, + .channel = AD7793_CHANNEL_1, /* AIN1(+) - AIN1(-) */ + .rate = AD7793_RATE_8_33HZ, + .unipolar_mode = true, /* 只测正向信号 */ + .buffered = true, /* 启用缓冲 */ + .calibrate_system_zero = false /* 暂停 system zero 校准 (之前 OFFSET/FULLSC 已被污染) */ +}; + /* Shared data structure for temperature sampling and TEC control */ static struct { @@ -93,7 +146,7 @@ void tec_control_reset(void) } } -int tec_control_start(void) +int tec_control_resume(void) { if (tec_control == NULL) { @@ -110,7 +163,7 @@ int tec_control_start(void) return 0; } -int tec_control_stop(void) +int tec_control_pause(void) { if (tec_control == RT_NULL) { @@ -127,19 +180,20 @@ void tec_calculate_temperature(uint32_t adc_value) float mv = 0; double resistance; mv = ad7793_convert_to_voltage(&ad7793_dev, adc_value) * 1000; - ET_DBG("voltage:%.2f", mv); + //ET_DBG("voltage:%.2f", mv); resistance = solve_resistance(mv / 1000); + //ET_DBG("resistance:%.2f", resistance); //ET_PRINTF_FLOAT("resistance:", resistance); tec_control->cur_temper = pt_precise_temperature(resistance, PT100); tec_control->cur_temper = et_ema_filter_process(&tec_control->ema_filter, tec_control->cur_temper); - if (isnan(tec_control->cur_temper)) + if (_isnan_f(tec_control->cur_temper)) { - rt_kprintf("Invalid resistance value, cannot calculate temperature.\n"); + ET_DBG("Invalid resistance value, cannot calculate temperature.\n"); } else { - ET_DBG("temperature:%.2f", tec_control->cur_temper); + //ET_DBG("temperature:%.4f", tec_control->cur_temper); /* Update shared data */ rt_mutex_take(temp_shared_data.data_mutex, RT_WAITING_FOREVER); temp_shared_data.current_temperature = tec_control->cur_temper; @@ -148,46 +202,87 @@ void tec_calculate_temperature(uint32_t adc_value) } } +#if TEST_TEMPER_XFER float generate_random_temperature(void) { - // 生成0到RAND_MAX之间的随机整数 - int randomInt = rand(); - // 转换为0到1之间的浮点数,再映射到0到55范围 - float randomFloat = (float)randomInt / RAND_MAX * 55.0f; - return randomFloat; + static uint32_t lfsr = 0xACE1u; /* simple LFSR to avoid rand() dependency */ + lfsr ^= lfsr << 7; + lfsr ^= lfsr >> 9; + lfsr ^= lfsr << 8; + return (lfsr & 0xFFFF) * (55.0f / 65535.0f); } +#endif + /* Temperature sampling thread entry function */ -static void temp_sampling_thread_entry(void *parameter) +static void temper_sampling_thread_entry(void *parameter) { ETK_UNUSED(parameter); - //uint32_t adc_value = 0; + uint32_t adc_value = 0; + const uint32_t LOW_CODE_THRESHOLD = 0x200; /* 约 0.125% 满量程,可调 */ + uint32_t consecutive_err = 0; ET_INFO("Temperature sampling thread started"); while (1) { +#if !TEST_TEMPER_XFER /* Wait for ADC ready and read data */ - // if (ad7793_wait_ready(&ad7793_dev, 1000) == true) - // { - // if (ad7793_read_data(&ad7793_dev, &adc_value)) - // { - // /* Calculate temperature from ADC value */ - // tec_calculate_temperature(adc_value); - // } - // else - // { - // ET_ERR("Failed to read ADC data.\n"); - // } - // } - //mb_write_current_temp(tec_control->cur_temper); - mb_write_current_temp(generate_random_temperature()); // test only + if (ad7793_wait_ready(&ad7793_dev, 1000) == true) + { + if (ad7793_read_data(&ad7793_dev, &adc_value)) + { + /* 低码过滤:避免极低噪声或参考丢失导致的虚假高温 */ + if (adc_value < LOW_CODE_THRESHOLD) + { + ad7793_dev.low_code_drops++; + ET_DBG("drop low code: 0x%06X (drops=%lu)", (unsigned)adc_value, + (unsigned long)ad7793_dev.low_code_drops); + } + else + { + //ET_DBG("raw code: 0x%06X", (unsigned)adc_value); + tec_calculate_temperature(adc_value); + } + } + else + { + ET_ERR("Failed to read ADC data.\n"); + } + } + mb_write_current_temp(tec_control->cur_temper); +#else + mb_write_current_temp(generate_random_temperature()); // test only mb_write_current_rotation(generate_random_temperature()); // test only +#endif /* Delay for next sampling period */ rt_thread_mdelay(ADC_SAMPLE_PERIOD); } } +// pwm: 0.0 ~ 1.0 +void tec_adjust_pwm(float pwm, float cur_temper, float target_temper) +{ + if (_fabs_f(cur_temper - target_temper) < TEC_TEMPER_PRECISE) + { + // within precise range, stop adjustment + bsp_tec_close(); + bsp_fan_stop(); + return; + } + //bsp_fan_start(); + if (cur_temper > target_temper) + { + // cooling + bsp_tec_cooling_adjust(pwm); + } + else + { + // heating + bsp_tec_heating_adjust(pwm); + } +} + void tec_control_temperature(tec_control_t *tec) { float pid_out = 0; @@ -213,7 +308,7 @@ void tec_control_temperature(tec_control_t *tec) // dt convert to seconds pid_out = tec_pid_update(tec, tec->target_temper, current_temp, TEC_CONTROL_PERIOD / 1000.0f); ET_INFO("pid_out:%.2f", pid_out); - pm_tec_set_duty(pid_out); + tec_adjust_pwm(pid_out, current_temp, tec->target_temper); } static void tec_control_thread_entry(void *parameter) @@ -222,11 +317,10 @@ static void tec_control_thread_entry(void *parameter) rt_tick_t last_ms = rt_tick_get_millisecond(); rt_uint32_t received; - et_ema_filter_init(&tec_control->ema_filter, EMA_FILTER_ALPHA); tec_pid_init(tec_control); tec_control->is_control = false; // control disabled by default - pm_tec_start(); - pm_fan_start(); + + //bsp_fan_start(); tec_control->target_temper = 25.0f; ET_INFO("TEC control thread started"); @@ -241,13 +335,13 @@ static void tec_control_thread_entry(void *parameter) // handle received events if (received & TEC_CONTROL_START_EVENT) { - tec_control_start(); + tec_control_resume(); ET_INFO("TEC control started by event"); } if (received & TEC_CONTROL_STOP_EVENT) { - tec_control_stop(); + tec_control_pause(); ET_INFO("TEC control stopped by event"); } @@ -265,19 +359,46 @@ static void tec_control_thread_entry(void *parameter) } } +int tec_control_sercie_start(tec_control_t *tec) +{ + (void)tec; /* unused parameter for now */ + rt_thread_t temp_tid; + rt_thread_t ctrl_tid; + (void)ctrl_tid; + // create temperature sampling thread + temp_tid = rt_thread_create("temp-sample", temper_sampling_thread_entry, RT_NULL, TEMP_SAMPLE_THREAD_STACK_SIZE, + TEMP_SAMPLE_THREAD_PRIORITY, TEMP_SAMPLE_THREAD_TIMESLICE); + if (temp_tid == RT_NULL) + { + ET_ERR("Failed to create temperature sampling thread"); + return -6; + } + rt_thread_startup(temp_tid); + /* create TEC control thread */ + ctrl_tid = rt_thread_create("tec-control", tec_control_thread_entry, RT_NULL, TEC_CONTROL_THREAD_STACK_SIZE, + TEC_CONTROL_THREAD_PRIORITY, TEC_CONTROL_THREAD_TIMESLICE); + if (ctrl_tid == RT_NULL) + { + ET_ERR("Failed to create tec-control thread"); + return -7; + } + rt_thread_startup(ctrl_tid); + return 0; +} + int tec_control_init(tec_control_t *tec) { - rt_thread_t tid; - rt_thread_t temp_tid; if (tec == NULL) { ET_ERR("tec is NULL"); return -1; } - pm_tec_init(); + tec_control = tec; + et_ema_filter_init(&tec_control->ema_filter, EMA_FILTER_ALPHA); + // create event object for temperature control tec_control->control_event = rt_event_create("tec_event", RT_IPC_FLAG_FIFO); if (tec_control->control_event == RT_NULL) @@ -298,39 +419,19 @@ int tec_control_init(tec_control_t *tec) temp_shared_data.current_temperature = 25.0f; temp_shared_data.temp_updated = false; - // #if (USING_SOFT_SPI == 1) - // if (ad7793_init(&ad7793_dev, &ad7793_hw_if, soft_spi.cs_pin)) - // #else - // if (ad7793_init(&ad7793_dev, &ad7793_hw_if, SPI3_CS_AD7793_Pin)) - // #endif - // { - // rt_kprintf("AD7793 device initialized successfully.\n"); - // } - // else - // { - // rt_kprintf("Failed to initialize AD7793 device.\n"); - // return -2; - // } - - // create temperature sampling thread - temp_tid = rt_thread_create("temp-sample", temp_sampling_thread_entry, RT_NULL, TEMP_SAMPLE_THREAD_STACK_SIZE, - TEMP_SAMPLE_THREAD_PRIORITY, TEMP_SAMPLE_THREAD_TIMESLICE); - if (temp_tid == RT_NULL) +#if (USING_SOFT_SPI == 1) + if (ad7793_init(&ad7793_dev, &ad7793_hw_if, soft_spi.cs_pin, &g_ad7793_config)) +#else + if (ad7793_init(&ad7793_dev, &ad7793_hw_if, SPI3_AD7793_CS_Pin, &g_ad7793_config)) +#endif { - ET_ERR("Failed to create temperature sampling thread"); - return -6; + rt_kprintf("AD7793 device initialized successfully.\n"); + } + else + { + rt_kprintf("Failed to initialize AD7793 device.\n"); + return -2; } - rt_thread_startup(temp_tid); - // create TEC control thread - // tid = rt_thread_create("tec-control", tec_control_thread_entry, RT_NULL, TEC_CONTROL_THREAD_STACK_SIZE, - // TEC_CONTROL_THREAD_PRIORITY, TEC_CONTROL_THREAD_TIMESLICE); - - // if (tid != RT_NULL) - // { - // rt_thread_startup(tid); - // return 0; - // } - - return -4; + return 0; } diff --git a/User/app/tec_control.h b/User/app/tec_control.h index 0b5c9b1..ed0c5ec 100644 --- a/User/app/tec_control.h +++ b/User/app/tec_control.h @@ -1,7 +1,7 @@ /* * @Author: mypx * @Date: 2025-06-23 13:05:04 - * @LastEditTime: 2025-09-25 13:13:20 + * @LastEditTime: 2025-10-16 14:01:47 * @LastEditors: mypx mypx_coder@163.com * @Description: */ @@ -19,7 +19,7 @@ #include #include -#define EMA_FILTER_ALPHA 0.1 +#define EMA_FILTER_ALPHA 0.9 #define EXCEPTION_TEMPERATURE_DEF 45.0f // exception temperature value, control will not be performed if exceeded #define TEC_CONTROL_PERIOD 1000 // temperature calculation period, unit ms #define ADC_SAMPLE_PERIOD 500 // ADC sampling period, unit ms @@ -47,9 +47,11 @@ int tec_control_init(tec_control_t *tec); void tec_control_reset(void); -int tec_control_start(void); +int tec_control_resume(void); -int tec_control_stop(void); +int tec_control_sercie_start(tec_control_t *tec); + +int tec_control_pause(void); float tec_get_target_voltage(float R); diff --git a/User/board/bsp_collect.c b/User/board/bsp_collect.c new file mode 100644 index 0000000..0d14625 --- /dev/null +++ b/User/board/bsp_collect.c @@ -0,0 +1,230 @@ +/* + * @Author: mypx + * @Description: ADS8866采集-3线模式(TIM3中断驱动) + */ +#include "bsp_collect.h" +#include "bsp_misc.h" +#include "et_log.h" +#include "main.h" +#include "spi.h" +#include "tim.h" +#include +#include + +static uint16_t *g_buffer = NULL; +static uint16_t g_buf_size = 0; +static uint16_t g_target_count = 0; // 目标采集数 +static volatile uint16_t g_count = 0; // 已采集数 +static dma_full_cb_t g_complete_cb = NULL; + +// 3线模式: DIN保持高, CONVST控制转换+片选 +#define ADS8866_DIN_HIGH() GPIOB->BSRR = (1u << 15) +#define PB15_AS_GPIO() GPIOB->CRH = (GPIOB->CRH & ~(0xFu << 28)) | (0x3u << 28) + +// 延时n个时钟周期(每个周期≈13.89ns) +static inline void ns_delay(uint32_t cycles) +{ + // 关闭编译器优化,确保NOP不被删除 + __asm volatile("1: SUBS %0, %0, #1\n" // 计数器减1 + " BNE 1b" // 若未到0则循环 + : "=r"(cycles) // 输出操作数 + : "0"(cycles) // 输入操作数 + ); +} + +void delay_100ns(void) +{ + ns_delay(7); +} + +// 单次读取(在中断里调用,要快速) +static inline uint16_t ads8866_read_once(void) +{ + uint16_t rx = 0; + // ADS8866转换时间约3µs, 等待转换完成 + // 72MHz系统时钟, 1个NOP约14ns, 300个NOP约4.2µs + for (volatile int i = 0; i < 6; i++) + delay_100ns(); + + // CONVST回到高电平, 数据就绪, 直接SPI读取 + HAL_SPI_Receive(&ADS8866_SPI, (uint8_t *)&rx, 1, 10); + return rx; +} + +int bsp_light_data_sampling_init(uint16_t *adc_buf, uint16_t length, dma_full_cb_t cb) +{ + g_buffer = adc_buf; + g_buf_size = length; + g_complete_cb = cb; + g_count = 0; + g_target_count = 0; + + // 初始化TIM3(产生CONVST), SPI2(读取数据) + MX_TIM3_Init(); + MX_SPI2_Init(); + __HAL_SPI_ENABLE(&ADS8866_SPI); + + // PB15(DIN)配置为GPIO并保持高电平 + PB15_AS_GPIO(); + ADS8866_DIN_HIGH(); + + ET_INFO("bsp_collect: init done, buf_size=%u", length); + return 0; +} + +// // Shell命令: 启动采集指定数量 +// int cstart(int argc, char **argv) +// { +// if (argc < 2) +// { +// rt_kprintf("Usage: cstart \r\n"); +// return -1; +// } + +// int count = atoi(argv[1]); +// if (count <= 0 || count > g_buf_size) +// { +// rt_kprintf("cstart: invalid count (1~%u)\r\n", g_buf_size); +// return -2; +// } + +// if (!g_buffer) +// { +// rt_kprintf("cstart: buffer not init\r\n"); +// return -3; +// } + +// // 设置目标数量并启动 +// g_count = 0; +// g_target_count = count; +// bsp_system_led_on(); + +// rt_kprintf("cstart: collecting %d samples...\r\n", count); + +// // 启动TIM3 PWM + 中断 +// HAL_TIM_PWM_Start(&COLLECT_TIM, TIM_CHANNEL_3); +// HAL_TIM_Base_Start_IT(&COLLECT_TIM); + +// rt_kprintf("cstart: started (non-blocking)\r\n"); +// return 0; +// } + +// int cprint(int argc, char **argv) +// { +// uint16_t n = 32; +// if (argc >= 2) +// n = (uint16_t)atoi(argv[1]); +// if (!g_buffer || g_count == 0) +// { +// rt_kprintf("cprint: no data\r\n"); +// return -1; +// } +// if (n > g_count) +// n = g_count; +// uint16_t minv = 0xFFFF, maxv = 0; +// uint32_t sum = 0; +// for (uint16_t i = 0; i < g_count; i++) +// { +// uint16_t v = g_buffer[i]; +// if (v < minv) +// minv = v; +// if (v > maxv) +// maxv = v; +// sum += v; +// } +// rt_kprintf("cprint: N=%u min=%u max=%u avg=%lu\r\n", g_count, minv, maxv, sum / g_count); +// rt_kprintf("first %u (dec / hex):\r\n", n); +// for (uint16_t i = 0; i < n; i++) +// { +// if (i % 8 == 0) +// rt_kprintf("\r\n[%04u] ", i); +// rt_kprintf("%5u/0x%04X ", g_buffer[i], g_buffer[i]); +// } +// rt_kprintf("\r\n"); +// return 0; +// } + +// MSH_CMD_EXPORT_ALIAS(cstart, cstart, collect N samples) +// MSH_CMD_EXPORT_ALIAS(cprint, cprint, print data) + +// 启动采集: 设置目标数量, 启动TIM3+中断 +int bsp_light_data_sampling_start(void) +{ + if (!g_buffer || g_buf_size == 0) + { + ET_ERR("bsp_collect: buffer not initialized"); + return -1; + } + + g_count = 0; + g_target_count = g_buf_size; // 采集满缓冲区 + bsp_system_led_on(); + + // 启动TIM3 PWM(产生CONVST) + Update中断 + HAL_TIM_PWM_Start(&COLLECT_TIM, TIM_CHANNEL_3); + HAL_TIM_Base_Start_IT(&COLLECT_TIM); + + //ET_INFO("bsp_collect: start, target=%u", g_target_count); + return 0; +} + +// 停止采集 +int bsp_light_data_sampling_stop(void) +{ + HAL_TIM_Base_Stop_IT(&COLLECT_TIM); + HAL_TIM_PWM_Stop(&COLLECT_TIM, TIM_CHANNEL_3); + bsp_system_led_off(); + + //ET_INFO("bsp_collect: stopped, collected=%u/%u", g_count, g_target_count); + return 0; +} + +uint16_t light_raw_map_u16(uint16_t val) +{ + /* Avoid integer truncation: do multiply before divide using 32-bit intermediate. + * Map 0..65535 -> 0..4095. Use rounding by adding half-divisor. */ + uint32_t tmp = (uint32_t)val * 4095u; + return (uint16_t)(tmp / 65535u + 0.5f); +} + +float light_raw_map_f32(uint16_t val) +{ + return (float)(val / 65535.0f * 4095.0f); +} + +// TIM3中断回调: 每次CONVST触发后采集一个点 +void sampling_tim_elapsed_callback(void) +{ + // 未启动或已完成 + if (g_target_count == 0 || g_count >= g_target_count) + return; + + // 读取一个点 + if (g_buffer && g_count < g_buf_size) + { + /* drive LED pin high at the start of ISR and low at the end + * so each interrupt produces one pulse. This makes the pulse + * frequency equal to the timer update frequency (no 2x toggle + * ambiguity). */ + bsp_system_led_on(); + g_buffer[g_count] = light_raw_map_u16(ads8866_read_once()); + g_count++; + bsp_system_led_off(); + + // 达到目标数量, 停止并回调 + if (g_count >= g_target_count) + { + HAL_TIM_Base_Stop_IT(&COLLECT_TIM); + HAL_TIM_PWM_Stop(&COLLECT_TIM, TIM_CHANNEL_3); + bsp_system_led_off(); + + //ET_INFO("bsp_collect: complete, N=%u", g_count); + + // 调用完成回调 + if (g_complete_cb) + g_complete_cb(); + + g_target_count = 0; // 标记完成 + } + } +} diff --git a/User/board/bsp_collect.h b/User/board/bsp_collect.h new file mode 100644 index 0000000..0bfa1db --- /dev/null +++ b/User/board/bsp_collect.h @@ -0,0 +1,21 @@ +/* + * @Author: mypx + * @Date: 2025-10-14 11:25:04 + * @LastEditTime: 2025-10-14 14:00:13 + * @LastEditors: mypx mypx_coder@163.com + * @Description: + */ +#ifndef __BSP_COLLECT_H__ +#define __BSP_COLLECT_H__ +#include + +typedef void (*dma_full_cb_t)(void); + +int bsp_light_data_sampling_init(uint16_t *adc_buf, uint16_t length, dma_full_cb_t cb); + +int bsp_light_data_sampling_start(void); + +int bsp_light_data_sampling_stop(void); + +void sampling_tim_elapsed_callback(void); +#endif // __BSP_COLLECT_H__ \ No newline at end of file diff --git a/User/board/bsp_hmi.c b/User/board/bsp_hmi.c new file mode 100644 index 0000000..366ff35 --- /dev/null +++ b/User/board/bsp_hmi.c @@ -0,0 +1,85 @@ +/* + * @Author: mypx + * @Date: 2025-10-15 16:24:26 + * @LastEditTime: 2025-10-22 08:44:30 + * @LastEditors: mypx mypx_coder@163.com + * @Description: + */ +#include "bsp_hmi.h" +#include "usart.h" +#include "rtthread.h" +#include "et_log.h" + +struct rt_messagequeue mb_hmi_mq; +static char mq_pool[MB_HMI_RX_QUEUE_SIZE]; + +#if (DEBUG_MB_ENABLE == 1) +#define DBG_MB(...) ET_DBG("[MB] " __VA_ARGS__) +#else +#define DBG_MB(...) +#endif + + +int bsp_mb_hmi_init(void) +{ + MX_USART1_UART_Init(); // used tx + MX_USART2_UART_Init(); // used rx + + rt_err_t result = rt_mq_init(&mb_hmi_mq, "mb_hmi_mq", mq_pool, 1, MB_HMI_RX_QUEUE_SIZE, RT_IPC_FLAG_FIFO); + if (result != RT_EOK) + { + ET_ERR("mb_hmi mq init failed\n"); + return -1; + } + + __HAL_UART_ENABLE_IT(&HMI_COM, UART_IT_RXNE); + + return 0; +} + +int32_t bsp_hmi_uart_read(uint8_t *buf, uint16_t count, int32_t byte_timeout_ms, void *arg) +{ + UNUSED(arg); + uint16_t bytes_read = 0; + uint8_t byte; + rt_tick_t timeout_ticks = rt_tick_from_millisecond(byte_timeout_ms); + DBG_MB("start read:count:%d, timeout:%d\n", count, byte_timeout_ms); + + while (bytes_read < count) + { + rt_err_t result = rt_mq_recv(&mb_hmi_mq, &byte, sizeof(byte), timeout_ticks); + if (result == RT_EOK) + { + DBG_MB("rx: %02X\n", byte); + buf[bytes_read++] = byte; + } + else + { + if (bytes_read > 0) + { + DBG_MB("Modbus read finished %d\n", bytes_read); + return bytes_read; + } + DBG_MB("HMI read timeout, no data received\n"); + return 0; + } + } + DBG_MB("Actually read:%d, count:%d\r\n", bytes_read, count); + + return bytes_read; +} + +int32_t bsp_hmi_uart_write(const uint8_t *buf, uint16_t count, int32_t byte_timeout_ms, void *arg) +{ + UNUSED(arg); + rt_thread_mdelay(2); + if (HAL_UART_Transmit(&HMI_COM, buf, count, byte_timeout_ms) == HAL_OK) + { + ET_DBG("HMI write:%d", count); + return count; + } + else + { + return 0; + } +} \ No newline at end of file diff --git a/User/board/bsp_hmi.h b/User/board/bsp_hmi.h new file mode 100644 index 0000000..7be13b7 --- /dev/null +++ b/User/board/bsp_hmi.h @@ -0,0 +1,21 @@ +/* + * @Author: mypx + * @Date: 2025-10-15 16:24:35 + * @LastEditTime: 2025-10-15 16:52:46 + * @LastEditors: mypx mypx_coder@163.com + * @Description: + */ +#ifndef __BSP_HMI_H__ +#define __BSP_HMI_H__ +#include + +#define MB_HMI_RX_QUEUE_SIZE 128 // Modbus HMI 接收队列大小 +extern struct rt_messagequeue mb_hmi_mq; + +int bsp_mb_hmi_init(void); + +int32_t bsp_hmi_uart_read(uint8_t *buf, uint16_t count, int32_t byte_timeout_ms, void *arg); + +int32_t bsp_hmi_uart_write(const uint8_t *buf, uint16_t count, int32_t byte_timeout_ms, void *arg); + +#endif // __BSP_HMI_H__ diff --git a/User/board/bsp_misc.c b/User/board/bsp_misc.c new file mode 100644 index 0000000..675a880 --- /dev/null +++ b/User/board/bsp_misc.c @@ -0,0 +1,121 @@ +/* + * @Author: mypx + * @Email: mypx_coder@163.com + * @Date: 2025-06-19 12:29:17 + * @LastEditors: mypx mypx_coder@163.com + * @Description: + */ +#include "bsp_misc.h" +#include "ad7793.h" +#include "adc.h" +#include "dma.h" +#include "et_log.h" +#include "gpio.h" +#include "i2c.h" +#include "rtdef.h" +#include "rthw.h" + +#include "usart.h" +#include "tim.h" + +#include +#include + +#define AT24C128_I2C_ADDR 0xA0 + +void bsp_system_led_toggle(void) +{ + HAL_GPIO_TogglePin(PE5_LED1_GPIO_Port, PE5_LED1_Pin); +} + +void bsp_system_led_on(void) +{ + HAL_GPIO_WritePin(PE5_LED1_GPIO_Port, PE5_LED1_Pin, GPIO_PIN_SET); +} + +void bsp_system_led_off(void) +{ + HAL_GPIO_WritePin(PE5_LED1_GPIO_Port, PE5_LED1_Pin, GPIO_PIN_RESET); +} + +void bsp_error_led_on(void) +{ + HAL_GPIO_WritePin(PE6_LED2_GPIO_Port, PE6_LED2_Pin, GPIO_PIN_SET); +} + +void bsp_error_led_off(void) +{ + HAL_GPIO_WritePin(PE6_LED2_GPIO_Port, PE6_LED2_Pin, GPIO_PIN_RESET); +} + +void bsp_error_led_toggle(void) +{ + HAL_GPIO_TogglePin(PE6_LED2_GPIO_Port, PE6_LED2_Pin); +} + +int bsp_i2c_write_bytes(uint16_t dev_addr, uint16_t mem_addr, uint8_t *data, uint16_t len) +{ + HAL_StatusTypeDef res; + + // AT24C128 单页为 64 字节,写入时建议不要跨页,否则需要拆包 + if (len > 64) + return HAL_ERROR; + + res = HAL_I2C_Mem_Write(&hi2c2, dev_addr, mem_addr, I2C_MEMADD_SIZE_16BIT, data, len, HAL_MAX_DELAY); + + HAL_Delay(5); // EEPROM写入周期,典型为5ms + + return res; +} + +int bsp_i2c_read_bytes(uint16_t dev_addr, uint16_t mem_addr, uint8_t *data, uint16_t len) +{ + return HAL_I2C_Mem_Read(&hi2c2, dev_addr, mem_addr, I2C_MEMADD_SIZE_16BIT, data, len, HAL_MAX_DELAY); +} + + +void bsp_uart_print_send(const uint8_t *data, uint16_t len) +{ +#ifdef USING_RTT_AS_CONSOLE +#warning "USING_RTT_AS_CONSOLE defined, use uart1 as hmi interface" + +#else + HAL_UART_Transmit(&huart1, data, len, 1000); +#endif +} + +float bsp_sampling_tim_get_freq(void) +{ + uint32_t pclk1 = HAL_RCC_GetPCLK1Freq(); + uint32_t timer_clk; + + // 获取APB1预分频系数(来自RCC配置寄存器) + uint32_t apb1_prescaler = (RCC->CFGR & RCC_CFGR_PPRE1) >> RCC_CFGR_PPRE1_Pos; + + // 根据APB1预分频计算定时器时钟源 + if (apb1_prescaler == RCC_HCLK_DIV1) + { + // APB1预分频=1,定时器时钟=PCLK1 + timer_clk = pclk1; + } + else + { + // APB1预分频>1,定时器时钟=2×PCLK1 + timer_clk = pclk1 * 2; + } + + // 计算定时器计数频率(经过PSC分频后) + uint32_t counter_freq = timer_clk / (COLLECT_TIM.Init.Prescaler + 1); + + // 计算最终输出频率(周期触发频率 = 计数频率 / (Period + 1)) + // 注意:如果是向上计数模式,完整周期是从0到Period,共Period+1个计数 + return (float)counter_freq / (COLLECT_TIM.Init.Period + 1); +} + +int pm_board_init(void) +{ + MX_GPIO_Init(); + // MX_USART1_UART_Init(); + + return 0; +} diff --git a/User/board/bsp_misc.h b/User/board/bsp_misc.h new file mode 100644 index 0000000..982c39a --- /dev/null +++ b/User/board/bsp_misc.h @@ -0,0 +1,40 @@ +/* + * @Date: 2025-06-19 12:29:23 + * @Author: mypx + * @LastEditors: mypx mypx_coder@163.com + * @LastEditTime: 2025-10-15 16:52:14 + * @FilePath: board_pm.h + * @Description: + * Copyright (c) 2025 by mypx, All Rights Reserved. + */ +#ifndef __BSP_MISC_H__ +#define __BSP_MISC_H__ +#include "adc.h" +#include "main.h" +#include "rtthread.h" +#include "tim.h" +#include "user_config.h" +#include +#include + +int pm_board_init(void); + +void bsp_system_led_toggle(void); + +void bsp_system_led_on(void); +void bsp_system_led_off(void); + +void bsp_error_led_on(void); +void bsp_error_led_off(void); +void bsp_error_led_toggle(void); + +int bsp_i2c_write_bytes(uint16_t dev_addr, uint16_t reg, uint8_t *data, uint16_t size); +int bsp_i2c_read_bytes(uint16_t dev_addr, uint16_t reg, uint8_t *data, uint16_t size); + +void bsp_uart_print_send(const uint8_t *data, uint16_t len); + +void bsp_lcd_cmd_send(const uint8_t *data, uint16_t len); + +float bsp_sampling_tim_get_freq(void); + +#endif // __BOARD_H__ diff --git a/User/board/bsp_motor.c b/User/board/bsp_motor.c new file mode 100644 index 0000000..d1fc1bd --- /dev/null +++ b/User/board/bsp_motor.c @@ -0,0 +1,82 @@ +/* + * @Author: mypx + * @Date: 2025-10-15 16:40:13 + * @LastEditTime: 2025-10-15 16:47:38 + * @LastEditors: mypx mypx_coder@163.com + * @Description: + */ +#include "bsp_motor.h" +#include "et_log.h" +#include "rtthread.h" +#include "usart.h" + +#if (DEBUG_MB_ENABLE == 1) +#define DBG_MB(...) ET_DBG("[MB] " __VA_ARGS__) +#else +#define DBG_MB(...) +#endif + +struct rt_messagequeue servo_mq; +static char mq_pool[SERVO_RX_QUEUE_SIZE]; + + +int bsp_servo_init(void) +{ + MX_USART3_UART_Init(); + + rt_err_t result = rt_mq_init(&servo_mq, "servo_mq", mq_pool, 1, SERVO_RX_QUEUE_SIZE, RT_IPC_FLAG_FIFO); + if (result != RT_EOK) + { + ET_ERR("servo mq init failed\n"); + return -1; + } + + __HAL_UART_ENABLE_IT(&SERVO_COM, UART_IT_RXNE); + return 0; +} + +int32_t bsp_servo_uart_read(uint8_t *buf, uint16_t count, int32_t byte_timeout_ms, void *arg) +{ + UNUSED(arg); + uint16_t bytes_read = 0; + uint8_t byte; + rt_tick_t timeout_ticks = rt_tick_from_millisecond(byte_timeout_ms); + DBG_MB("start read:count:%d, timeout:%d\n", count, byte_timeout_ms); + + while (bytes_read < count) + { + rt_err_t result = rt_mq_recv(&servo_mq, &byte, sizeof(byte), timeout_ticks); + if (result == RT_EOK) + { + DBG_MB("rx: %02X\n", byte); + buf[bytes_read++] = byte; + } + else + { + if (bytes_read > 0) + { + DBG_MB("Modbus read finished %d\n", bytes_read); + return bytes_read; + } + DBG_MB("Servo read timeout, no data received\n"); + return 0; + } + } + DBG_MB("Actually read:%d, count:%d\r\n", bytes_read, count); + + return bytes_read; +} + +int32_t bsp_servo_uart_write(const uint8_t *buf, uint16_t count, int32_t byte_timeout_ms, void *arg) +{ + UNUSED(arg); + rt_thread_mdelay(2); + if (HAL_UART_Transmit(&SERVO_COM, buf, count, byte_timeout_ms) == HAL_OK) + { + return count; + } + else + { + return 0; + } +} diff --git a/User/board/bsp_motor.h b/User/board/bsp_motor.h new file mode 100644 index 0000000..1d6275d --- /dev/null +++ b/User/board/bsp_motor.h @@ -0,0 +1,16 @@ +#ifndef __BSP_MOTOR_H__ +#define __BSP_MOTOR_H__ +#include +#include + + +#define SERVO_RX_QUEUE_SIZE 128 // Modbus RTU 接收队列大小 +extern struct rt_messagequeue servo_mq; + +int bsp_servo_init(void); + +int32_t bsp_servo_uart_read(uint8_t *buf, uint16_t count, int32_t timeout, void *arg); + +int32_t bsp_servo_uart_write(const uint8_t *buf, uint16_t count, int32_t timeout, void *arg); + +#endif // __BSP_MOTOR_H__ diff --git a/User/board/bsp_tec.c b/User/board/bsp_tec.c new file mode 100644 index 0000000..158b625 --- /dev/null +++ b/User/board/bsp_tec.c @@ -0,0 +1,177 @@ +/* + * @Author: mypx + * @Date: 2025-10-15 13:03:38 + * @LastEditTime: 2025-10-17 13:37:16 + * @LastEditors: mypx mypx_coder@163.com + * @Description: + */ +#include "bsp_tec.h" +#include "main.h" +#include "spi.h" +#include "tim.h" +/* + * 当前板子存在的问题: + * PE8-TEC-IN1(left) TIM1_CH1N默认为PE8 + * PA8-TEC-IN2(right) TIM1_CH1默认为PA9,需要映射到PA8 + * 问题: + * 映射只能成对映射,也就是PE8也要做映射,所以一次配置,同时使能两个管脚做不到 + * 解决方法: + * 1. 分别配置PE8和PA8为TIM1_CH1N和TIM1_CH1 + * 2. 分别使能PE8和PA8的TIM1_CH1N和TIM1_CH1 + * stm32cubemx 使能TIM1_CH1N和TIM1_CH1默认的管脚为REMAP后的管脚,感觉是bug + * +*/ + +static void stop_tec_pwm(void) +{ + // 一块关闭,避免左右搞混没关掉 + HAL_TIM_PWM_Stop(&TEC_TIM, TIM_CHANNEL_1); + HAL_TIMEx_PWMN_Stop(&TEC_TIM, TIM_CHANNEL_1); +} + +static void tec_sd_disable(void) +{ + HAL_GPIO_WritePin(GPIOA, TEC_LEFT_SD_Pin | TEC_RIGHT_SD_Pin, GPIO_PIN_RESET); +} + +static void left_tec_sd_enable(void) +{ + HAL_GPIO_WritePin(GPIOA, TEC_LEFT_SD_Pin, GPIO_PIN_SET); +} + +static void right_tec_sd_enable(void) +{ + HAL_GPIO_WritePin(GPIOA, TEC_RIGHT_SD_Pin, GPIO_PIN_SET); +} + +// PE8-TEC-IN1(left) TIM1_CH1N, 需要做full remap +static void left_tec_reconfig_pwm(void) +{ + GPIO_InitTypeDef GPIO_InitStruct = {0}; + /* Stop PWM outputs first to avoid both outputs being active during transition */ + stop_tec_pwm(); + + /* Ensure GPIO clocks */ + __HAL_RCC_GPIOE_CLK_ENABLE(); + + /* Enable TIM1 full remap if you need the remapped mapping (CubeMX generated code might expect this) + Note: remap affects mapping for all TIM1 channels - do this before initializing the GPIOs */ + __HAL_AFIO_REMAP_TIM1_ENABLE(); + + /* Deinit any previous GPIO config on PE8/PA8 to be safe */ + HAL_GPIO_DeInit(GPIOE, GPIO_PIN_8); + HAL_GPIO_DeInit(GPIOA, TEC_RIGHT_IN_Pin); + + /**TIM1 GPIO Configuration + PE8 ------> TIM1_CH1N + */ + GPIO_InitStruct.Pin = GPIO_PIN_8; + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; + HAL_GPIO_Init(GPIOE, &GPIO_InitStruct); + + /* + * Hardware note: main output (CH1) and complementary output (CH1N) cannot + * be enabled at the same time on this board. For the 'left' mapping (PE8) + * we only enable the complementary output (CH1N) and ensure CH1 is stopped. + */ + /* Ensure main channel is off, then start complementary output only */ + HAL_TIMEx_PWMN_Start(&TEC_TIM, TIM_CHANNEL_1); +} + +// PA8-TEC-IN2(right) TIM1_CH1,默认CH1,不需要映射 +void right_tec_reconfig_pwm(void) +{ + stop_tec_pwm(); + + /* Disable TIM1 remap so TIM1_CH1 maps to the default (PA8/PA9 depending on device) + Do this before re-initializing the GPIO so HAL init uses the correct port/pin */ + __HAL_AFIO_REMAP_TIM1_DISABLE(); + + /* Deinit any previous GPIO config on PE8/PA8 to be safe */ + HAL_GPIO_DeInit(GPIOE, GPIO_PIN_8); + HAL_GPIO_DeInit(GPIOA, TEC_RIGHT_IN_Pin); + + /* Re-run the post-init which will configure PA8 as AF (HAL_TIM_MspPostInit uses TEC_RIGHT_IN_Pin) */ + HAL_TIM_MspPostInit(&TEC_TIM); + + /* + * For the 'right' mapping (PA8) we only enable the main output (CH1) and + * ensure the complementary output (CH1N) is stopped to avoid hardware conflict. + */ + HAL_TIM_PWM_Start(&TEC_TIM, TIM_CHANNEL_1); +} + +void bsp_fan_start(void) +{ + HAL_GPIO_WritePin(FAN_CONTROL_GPIO_Port, FAN_CONTROL_Pin, GPIO_PIN_SET); +} + +void bsp_fan_stop(void) +{ + HAL_GPIO_WritePin(FAN_CONTROL_GPIO_Port, FAN_CONTROL_Pin, GPIO_PIN_RESET); +} + +void bsp_left_tec_open(void) +{ + tec_sd_disable(); + left_tec_reconfig_pwm(); + left_tec_sd_enable(); +} + +void bsp_right_tec_open(void) +{ + tec_sd_disable(); + right_tec_reconfig_pwm(); + right_tec_sd_enable(); +} + +static void left_tec_adjust_duty_cycle(uint32_t duty_cycle) +{ + __HAL_TIM_SET_COMPARE(&TEC_TIM, TIM_CHANNEL_1, duty_cycle); +} + +static void right_tec_adjust_duty_cycle(uint32_t duty_cycle) +{ + __HAL_TIM_SET_COMPARE(&TEC_TIM, TIM_CHANNEL_1, duty_cycle); +} + +void bsp_tec_cooling_open(void) +{ + bsp_left_tec_open(); +} + +void bsp_tec_heating_open(void) +{ + bsp_right_tec_open(); +} + +// pwm: 0.0 ~ 1.0 +void bsp_tec_cooling_adjust(float pwm) +{ + extern TIM_HandleTypeDef TEC_TIM; + uint32_t max_period = __HAL_TIM_GET_AUTORELOAD(&TEC_TIM); + left_tec_adjust_duty_cycle((uint32_t)(pwm * max_period)); +} + +// pwm: 0.0 ~ 1.0 +void bsp_tec_heating_adjust(float pwm) +{ + extern TIM_HandleTypeDef TEC_TIM; + uint32_t max_period = __HAL_TIM_GET_AUTORELOAD(&TEC_TIM); + right_tec_adjust_duty_cycle((uint32_t)(pwm * max_period)); +} + +void bsp_tec_close(void) +{ + stop_tec_pwm(); + tec_sd_disable(); +} + + +void bsp_tec_init(void) +{ + MX_SPI3_Init(); + MX_TIM1_Init(); +} + diff --git a/User/board/bsp_tec.h b/User/board/bsp_tec.h new file mode 100644 index 0000000..f597555 --- /dev/null +++ b/User/board/bsp_tec.h @@ -0,0 +1,27 @@ +/* + * @Author: mypx + * @Date: 2025-10-15 13:03:28 + * @LastEditTime: 2025-10-15 14:58:18 + * @LastEditors: mypx mypx_coder@163.com + * @Description: + */ +#ifndef __BSP_TEC_H__ +#define __BSP_TEC_H__ + +void bsp_tec_init(void); + +// 这俩接口内部必须做好互斥,不能同时打开 +void bsp_tec_cooling_open(void); +void bsp_tec_heating_open(void); + +void bsp_tec_close(void); +// pwm: 0.0 ~ 1.0 +void bsp_tec_cooling_adjust(float pwm); +// pwm: 0.0 ~ 1.0 +void bsp_tec_heating_adjust(float pwm); + + + +void bsp_fan_start(void); +void bsp_fan_stop(void); +#endif // __BSP_TEC_H__ diff --git a/User/board/bsp_temper_sampling.c b/User/board/bsp_temper_sampling.c new file mode 100644 index 0000000..90aa794 --- /dev/null +++ b/User/board/bsp_temper_sampling.c @@ -0,0 +1,140 @@ +/* + * @Author: mypx + * @Date: 2025-10-15 15:32:36 + * @LastEditTime: 2025-10-16 10:26:19 + * @LastEditors: mypx mypx_coder@163.com + * @Description: + */ +#include "bsp_temper_sampling.h" +#include "et_log.h" +#include "spi.h" + +#if (USING_SOFT_SPI == 1) +soft_spi_t soft_spi = { + .sck_port = GPIOB, + .sck_pin = GPIO_PIN_5, + .mosi_port = GPIOB, + .mosi_pin = GPIO_PIN_6, + .miso_port = GPIOB, + .miso_pin = GPIO_PIN_4, + .cs_port = GPIOB, + .cs_pin = GPIO_PIN_7, + .mode = SOFT_SPI_MODE0, + .bit_order = SOFT_SPI_MSB_FIRST, + .lock = RT_NULL, // 使用 RT_NULL,初始化时会创建 + .delay_us = 1, // 每半个 SPI 时钟周期延时 1 微秒 +}; + +void bsp_ad7793_spi_transfer(uint8_t *tx_buf, uint8_t *rx_buf, uint16_t len) +{ + soft_spi_transfer_buffer(&soft_spi, tx_buf, rx_buf, len); +} + +void bsp_ad7793_spi_write(uint8_t cmd, uint8_t *data, uint16_t len) +{ + soft_spi_write(&soft_spi, &cmd, 1, data, len); +} + +void bsp_ad7793_spi_read(uint8_t cmd, uint8_t *rx_buf, uint16_t len) +{ + soft_spi_read(&soft_spi, &cmd, 1, rx_buf, len); +} + +void bsp_ad7793_gpio_set(uint8_t pin, bool state) +{ + if (pin == soft_spi.cs_pin) + { + HAL_GPIO_WritePin(soft_spi.cs_port, soft_spi.cs_pin, state); + } +} + +bool bsp_ad7793_gpio_get(uint8_t pin) +{ + if (pin == soft_spi.miso_pin) + { + return HAL_GPIO_ReadPin(soft_spi.miso_port, soft_spi.miso_pin) == GPIO_PIN_SET; + } + return false; +} +#else +#define AD7793_CS_LOW() HAL_GPIO_WritePin(SPI3_AD7793_CS_GPIO_Port, SPI3_AD7793_CS_Pin, GPIO_PIN_RESET) +#define AD7793_CS_HIGH() HAL_GPIO_WritePin(SPI3_AD7793_CS_GPIO_Port, SPI3_AD7793_CS_Pin, GPIO_PIN_SET) +void bsp_ad7793_spi_transfer(uint8_t *tx_buf, uint8_t *rx_buf, uint16_t len) +{ + HAL_StatusTypeDef status; + status = HAL_SPI_TransmitReceive(&AD7793_SPI, tx_buf, rx_buf, len, 1000); + if (status != HAL_OK) + { + ET_ERR("SPI transfer error:%d!", status); + } +} + +void bsp_ad7793_spi_write(uint8_t cmd, uint8_t *data, uint16_t len) +{ + uint8_t tx_buffer[256]; + HAL_StatusTypeDef status; + + tx_buffer[0] = cmd; + if (data && len > 0) + { + for (uint16_t i = 0; i < len; i++) + { + tx_buffer[i + 1] = data[i]; + } + } + status = HAL_SPI_Transmit(&AD7793_SPI, tx_buffer, len + 1, 1000); + if (status != HAL_OK) + { + ET_ERR("SPI write error:%d!", status); + } +} + +void bsp_ad7793_spi_read(uint8_t cmd, uint8_t *rx_buf, uint16_t len) +{ + uint8_t tx_buffer[1] = {cmd}; + HAL_StatusTypeDef status; + status = HAL_SPI_Transmit(&AD7793_SPI, tx_buffer, 1, 1000); + if (status != HAL_OK) + { + ET_ERR("SPI write error:%d!", status); + } + status = HAL_SPI_Receive(&AD7793_SPI, rx_buf, len, 1000); + if (status != HAL_OK) + { + ET_ERR("SPI read error:%d!", status); + } +} + +void bsp_ad7793_gpio_set(uint8_t pin, bool state) +{ + if (pin == SPI3_AD7793_CS_Pin) + { + HAL_GPIO_WritePin(SPI3_AD7793_CS_GPIO_Port, SPI3_AD7793_CS_Pin, state); + } +} + +bool bsp_ad7793_gpio_get(uint8_t pin) +{ + if (pin == SPI3_AD7793_MISO_Pin) + { + return HAL_GPIO_ReadPin(SPI3_AD7793_MISO_GPIO_Port, SPI3_AD7793_MISO_Pin) == GPIO_PIN_SET; + } + return false; +} +#endif + +void bsp_ad7793_delay_ms(uint16_t ms) +{ + rt_thread_mdelay(ms); // 使用 RT-Thread 的延时函数 +} + +void bsp_temper_sampling_init(void) +{ +#if (USING_SOFT_SPI == 1) + soft_spi_init(&soft_spi); + soft_spi_set_mode(&soft_spi, SOFT_SPI_MODE3); // 设置 SPI 模式 + soft_spi_set_bit_order(&soft_spi, SOFT_SPI_MSB_FIRST); // 设置位序 + soft_spi_set_delay(&soft_spi, 1); // 设置延时为 1 微秒 + soft_spi_select(&soft_spi); // 选择 SPI 设备 +#endif +} diff --git a/User/board/bsp_temper_sampling.h b/User/board/bsp_temper_sampling.h new file mode 100644 index 0000000..144443e --- /dev/null +++ b/User/board/bsp_temper_sampling.h @@ -0,0 +1,27 @@ +/* + * @Author: mypx + * @Date: 2025-10-15 15:32:55 + * @LastEditTime: 2025-10-15 16:35:56 + * @LastEditors: mypx mypx_coder@163.com + * @Description: + */ +#ifndef __BSP_TEMPER_SAMPLING_H__ +#define __BSP_TEMPER_SAMPLING_H__ +#include +#include + +void bsp_temper_sampling_init(void); + +void bsp_ad7793_spi_transfer(uint8_t *tx_buf, uint8_t *rx_buf, uint16_t len); + +void bsp_ad7793_spi_write(uint8_t cmd, uint8_t *data, uint16_t len); + +void bsp_ad7793_spi_read(uint8_t cmd, uint8_t *rx_buf, uint16_t len); + +void bsp_ad7793_gpio_set(uint8_t pin, bool state); + +bool bsp_ad7793_gpio_get(uint8_t pin); + +void bsp_ad7793_delay_ms(uint16_t ms); + +#endif \ No newline at end of file diff --git a/User/board/pm_board.c b/User/board/pm_board.c deleted file mode 100644 index c07f1e3..0000000 --- a/User/board/pm_board.c +++ /dev/null @@ -1,552 +0,0 @@ -/* - * @Author: mypx - * @Email: mypx_coder@163.com - * @Date: 2025-06-19 12:29:17 - * @LastEditors: mypx mypx_coder@163.com - * @Description: - */ -#include "pm_board.h" -#include "ad7793.h" -#include "adc.h" -#include "dma.h" -#include "et_log.h" -#include "gpio.h" -#include "i2c.h" -#include "rtdef.h" -#include "rthw.h" -#include "rtthread.h" -#include "spi.h" -#include "tim.h" -#include "usart.h" -#include -#include - -#define AT24C128_I2C_ADDR 0xA0 - -#if (DEBUG_MB_ENABLE == 1) -#define DBG_MB(...) ET_DBG("[MB] " __VA_ARGS__) -#else -#define DBG_MB(...) -#endif - -volatile uint64_t timestamp_tim_overflow_count = 0; - -#if (USING_SOFT_SPI == 1) -soft_spi_t soft_spi = { - .sck_port = GPIOB, - .sck_pin = GPIO_PIN_5, - .mosi_port = GPIOB, - .mosi_pin = GPIO_PIN_6, - .miso_port = GPIOB, - .miso_pin = GPIO_PIN_4, - .cs_port = GPIOB, - .cs_pin = GPIO_PIN_7, - .mode = SOFT_SPI_MODE0, - .bit_order = SOFT_SPI_MSB_FIRST, - .lock = RT_NULL, // 使用 RT_NULL,初始化时会创建 - .delay_us = 1, // 每半个 SPI 时钟周期延时 1 微秒 -}; - -void pm_ad7793_spi_transfer(uint8_t *tx_buf, uint8_t *rx_buf, uint16_t len) -{ - soft_spi_transfer_buffer(&soft_spi, tx_buf, rx_buf, len); -} - -void pm_ad7793_spi_write(uint8_t cmd, uint8_t *data, uint16_t len) -{ - soft_spi_write(&soft_spi, &cmd, 1, data, len); -} - -void pm_ad7793_spi_read(uint8_t cmd, uint8_t *rx_buf, uint16_t len) -{ - soft_spi_read(&soft_spi, &cmd, 1, rx_buf, len); -} - -void pm_ad7793_gpio_set(uint8_t pin, bool state) -{ - if (pin == soft_spi.cs_pin) - { - HAL_GPIO_WritePin(soft_spi.cs_port, soft_spi.cs_pin, state); - } -} - -bool pm_ad7793_gpio_get(uint8_t pin) -{ - if (pin == soft_spi.miso_pin) - { - return HAL_GPIO_ReadPin(soft_spi.miso_port, soft_spi.miso_pin) == GPIO_PIN_SET; - } - return false; -} -#else -#define AD7793_CS_LOW() HAL_GPIO_WritePin(AD7793_CS_GPIO_Port, AD7793_CS_Pin, GPIO_PIN_RESET) -#define AD7793_CS_HIGH() HAL_GPIO_WritePin(AD7793_CS_GPIO_Port, AD7793_CS_Pin, GPIO_PIN_SET) -void pm_ad7793_spi_transfer(uint8_t *tx_buf, uint8_t *rx_buf, uint16_t len) -{ - HAL_StatusTypeDef status; - status = HAL_SPI_TransmitReceive(&AD7793_SPI, tx_buf, rx_buf, len, 1000); - if (status != HAL_OK) - { - ET_ERR("SPI transfer error:%d!", status); - } -} - -void pm_ad7793_spi_write(uint8_t cmd, uint8_t *data, uint16_t len) -{ - uint8_t tx_buffer[256]; - HAL_StatusTypeDef status; - - tx_buffer[0] = cmd; - if (data && len > 0) - { - for (uint16_t i = 0; i < len; i++) - { - tx_buffer[i + 1] = data[i]; - } - } - status = HAL_SPI_Transmit(&AD7793_SPI, tx_buffer, len + 1, 1000); - if (status != HAL_OK) - { - ET_ERR("SPI write error:%d!", status); - } -} - -void pm_ad7793_spi_read(uint8_t cmd, uint8_t *rx_buf, uint16_t len) -{ - uint8_t tx_buffer[1] = {cmd}; - HAL_StatusTypeDef status; - status = HAL_SPI_Transmit(&AD7793_SPI, tx_buffer, 1, 1000); - if (status != HAL_OK) - { - ET_ERR("SPI write error:%d!", status); - } - status = HAL_SPI_Receive(&AD7793_SPI, rx_buf, len, 1000); - if (status != HAL_OK) - { - ET_ERR("SPI read error:%d!", status); - } -} - -void pm_ad7793_gpio_set(uint8_t pin, bool state) -{ - if (pin == SPI3_CS_AD7793_Pin) - { - HAL_GPIO_WritePin(SPI3_CS_AD7793_GPIO_Port, SPI3_CS_AD7793_Pin, state); - } -} - -bool pm_ad7793_gpio_get(uint8_t pin) -{ - if (pin == SPI3_MISO_AD7793_Pin) - { - return HAL_GPIO_ReadPin(SPI3_MISO_AD7793_GPIO_Port, SPI3_MISO_AD7793_Pin) == GPIO_PIN_SET; - } - return false; -} -#endif - -void pm_ad7793_delay_ms(uint16_t ms) -{ - rt_thread_mdelay(ms); // 使用 RT-Thread 的延时函数 -} - -ad7793_hw_if_t ad7793_hw_if = { - .spi_transfer = pm_ad7793_spi_transfer, - .spi_write = pm_ad7793_spi_write, - .spi_read = pm_ad7793_spi_read, - .gpio_set = pm_ad7793_gpio_set, - .gpio_get = pm_ad7793_gpio_get, - .delay_ms = pm_ad7793_delay_ms, -}; - -#if (ENABLE_SV630P == 1) -struct rt_messagequeue servo_mq; -static char mq_pool[SERVO_RX_QUEUE_SIZE]; -#endif // USING_SOFT_SPI -void pm_system_led_toggle(void) -{ - HAL_GPIO_TogglePin(PE5_LED1_GPIO_Port, PE5_LED1_Pin); -} - -void pm_system_led_on(void) -{ - HAL_GPIO_WritePin(PE5_LED1_GPIO_Port, PE5_LED1_Pin, GPIO_PIN_SET); -} - -void pm_system_led_off(void) -{ - HAL_GPIO_WritePin(PE5_LED1_GPIO_Port, PE5_LED1_Pin, GPIO_PIN_RESET); -} - -void pm_error_led_on(void) -{ - HAL_GPIO_WritePin(PE6_LED2_GPIO_Port, PE6_LED2_Pin, GPIO_PIN_SET); -} - -void pm_error_led_off(void) -{ - HAL_GPIO_WritePin(PE6_LED2_GPIO_Port, PE6_LED2_Pin, GPIO_PIN_RESET); -} - -void pm_error_led_toggle(void) -{ - HAL_GPIO_TogglePin(PE6_LED2_GPIO_Port, PE6_LED2_Pin); -} - -int pm_i2c_write_bytes(uint16_t dev_addr, uint16_t mem_addr, uint8_t *data, uint16_t len) -{ - HAL_StatusTypeDef res; - - // AT24C128 单页为 64 字节,写入时建议不要跨页,否则需要拆包 - if (len > 64) - return HAL_ERROR; - - res = HAL_I2C_Mem_Write(&hi2c2, dev_addr, mem_addr, I2C_MEMADD_SIZE_16BIT, data, len, HAL_MAX_DELAY); - - HAL_Delay(5); // EEPROM写入周期,典型为5ms - - return res; -} - -int pm_i2c_read_bytes(uint16_t dev_addr, uint16_t mem_addr, uint8_t *data, uint16_t len) -{ - return HAL_I2C_Mem_Read(&hi2c2, dev_addr, mem_addr, I2C_MEMADD_SIZE_16BIT, data, len, HAL_MAX_DELAY); -} - - -void pm_uart_print_send(const uint8_t *data, uint16_t len) -{ -#ifdef USING_RTT_AS_CONSOLE -#warning "USING_RTT_AS_CONSOLE defined, use uart1 as hmi interface" - -#else - HAL_UART_Transmit(&huart1, data, len, 1000); -#endif -} - -void pm_lcd_cmd_send(const uint8_t *data, uint16_t len) -{ - //#ifdef USING_RTT_AS_CONSOLE - HAL_UART_Transmit(&huart1, data, strlen(data), 1000); - // #else - // HAL_UART_Transmit(&huart2, data, strlen(data), 1000); - // #endif -} - -struct rt_messagequeue mb_hmi_mq; -static char mq_pool[MB_HMI_RX_QUEUE_SIZE]; - -int pm_mb_hmi_init(void) -{ - MX_USART1_UART_Init(); // used tx - MX_USART2_UART_Init(); // used rx - - rt_err_t result = rt_mq_init(&mb_hmi_mq, "mb_hmi_mq", mq_pool, 1, MB_HMI_RX_QUEUE_SIZE, RT_IPC_FLAG_FIFO); - if (result != RT_EOK) - { - ET_ERR("mb_hmi mq init failed\n"); - return -1; - } - //__HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE); - - __HAL_UART_ENABLE_IT(&HMI_COM, UART_IT_RXNE); - - return 0; -} - -int32_t pm_hmi_uart_read(uint8_t *buf, uint16_t count, int32_t byte_timeout_ms, void *arg) -{ - UNUSED(arg); - uint16_t bytes_read = 0; - uint8_t byte; - rt_tick_t timeout_ticks = rt_tick_from_millisecond(byte_timeout_ms); - DBG_MB("start read:count:%d, timeout:%d\n", count, byte_timeout_ms); - - while (bytes_read < count) - { - rt_err_t result = rt_mq_recv(&mb_hmi_mq, &byte, sizeof(byte), timeout_ticks); - if (result == RT_EOK) - { - DBG_MB("rx: %02X\n", byte); - buf[bytes_read++] = byte; - } - else - { - if (bytes_read > 0) - { - DBG_MB("Modbus read finished %d\n", bytes_read); - return bytes_read; - } - DBG_MB("HMI read timeout, no data received\n"); - return 0; - } - } - DBG_MB("Actually read:%d, count:%d\r\n", bytes_read, count); - - return bytes_read; -} - -int32_t pm_hmi_uart_write(const uint8_t *buf, uint16_t count, int32_t byte_timeout_ms, void *arg) -{ - UNUSED(arg); - rt_thread_mdelay(2); - //#ifdef USING_RTT_AS_CONSOLE - if (HAL_UART_Transmit(&huart1, buf, count, byte_timeout_ms) == HAL_OK) - // #else - // if (HAL_UART_Transmit(&HMI_COM, buf, count, byte_timeout_ms) == HAL_OK) - // #endif - { - ET_DBG("HMI write:%d", count); - return count; - } - else - { - return 0; - } -} - - -#if (ENABLE_SV630P == 1) -int pm_servo_init(void) -{ - MX_USART3_UART_Init(); - - rt_err_t result = rt_mq_init(&servo_mq, "servo_mq", mq_pool, 1, SERVO_RX_QUEUE_SIZE, RT_IPC_FLAG_FIFO); - if (result != RT_EOK) - { - ET_ERR("servo mq init failed\n"); - return -1; - } - - __HAL_UART_ENABLE_IT(&SERVO_COM, UART_IT_RXNE); - return 0; -} - -int32_t pm_servo_uart_read(uint8_t *buf, uint16_t count, int32_t byte_timeout_ms, void *arg) -{ - UNUSED(arg); - uint16_t bytes_read = 0; - uint8_t byte; - rt_tick_t timeout_ticks = rt_tick_from_millisecond(byte_timeout_ms); - DBG_MB("start read:count:%d, timeout:%d\n", count, byte_timeout_ms); - - while (bytes_read < count) - { - rt_err_t result = rt_mq_recv(&servo_mq, &byte, sizeof(byte), timeout_ticks); - if (result == RT_EOK) - { - DBG_MB("rx: %02X\n", byte); - buf[bytes_read++] = byte; - } - else - { - if (bytes_read > 0) - { - DBG_MB("Modbus read finished %d\n", bytes_read); - return bytes_read; - } - DBG_MB("Servo read timeout, no data received\n"); - return 0; - } - } - DBG_MB("Actually read:%d, count:%d\r\n", bytes_read, count); - - return bytes_read; -} - -int32_t pm_servo_uart_write(const uint8_t *buf, uint16_t count, int32_t byte_timeout_ms, void *arg) -{ - UNUSED(arg); - rt_thread_mdelay(2); - if (HAL_UART_Transmit(&SERVO_COM, buf, count, byte_timeout_ms) == HAL_OK) - { - return count; - } - else - { - return 0; - } -} -#endif // ENABLE_SV630P - -#if (ENABLE_TEC == 1) -void pm_tec_init(void) -{ -#if (USING_SOFT_SPI == 0) - MX_SPI3_Init(); -#endif - MX_TIM1_Init(); -} - -void pm_tec_start(void) -{ - HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1); - HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_1); -} - -void pm_tec_stop(void) -{ - HAL_TIM_PWM_Stop(&htim1, TIM_CHANNEL_1); - HAL_TIMEx_PWMN_Stop(&htim1, TIM_CHANNEL_1); -} - -void pm_tec_set_duty(float pid_output) -{ - uint32_t period = htim1.Init.Period; - // 限制PID输出范围 - const float MIN_POWER = 2.0f; // 最小有效功率(%) - if (pid_output > 100.0f) - pid_output = 100.0f; - if (pid_output < -100.0f) - pid_output = -100.0f; - - // 应用最小功率限制 - if (pid_output > 0 && pid_output < MIN_POWER) - pid_output = MIN_POWER; - if (pid_output < 0 && pid_output > -MIN_POWER) - pid_output = -MIN_POWER; - - // 计算占空比 - uint32_t compare_value; - if (pid_output >= 0) - { - // 制热:占空比50%~0% - compare_value = period / 2 + (uint32_t)(period * pid_output / 200.0f); - } - else - { - // 制热:占空比50%~0% - compare_value = period / 2 - (uint32_t)(period * fabs(pid_output) / 200.0f); - } - - ET_DBG("PWM COMAPRE: %d", compare_value); - // 设置PWM占空比 - __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, compare_value); - //__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, 2); -} - -void pm_fan_start(void) -{ - HAL_GPIO_WritePin(FAN_CONTROL_GPIO_Port, FAN_CONTROL_Pin, GPIO_PIN_SET); -} - -void pm_fan_stop(void) -{ - HAL_GPIO_WritePin(FAN_CONTROL_GPIO_Port, FAN_CONTROL_Pin, GPIO_PIN_RESET); -} - -#endif // ENABLE_TEC - -void pm_timestamp_tim_init(void) -{ - MX_TIM4_Init(); -} - -void pm_timestamp_start(void) -{ - __HAL_TIM_SET_COUNTER(&TIMESTAMP_TIM, 0); - timestamp_tim_overflow_count = 0; - HAL_TIM_Base_Start_IT(&TIMESTAMP_TIM); -} - -void pm_timestamp_stop(void) -{ - HAL_TIM_Base_Stop_IT(&TIMESTAMP_TIM); -} - -uint64_t pm_get_timestamp_us(void) -{ - uint16_t current_cnt = __HAL_TIM_GET_COUNTER(&TIMESTAMP_TIM); - return (uint64_t)timestamp_tim_overflow_count * 65536 + current_cnt; -} - -void pm_adc_init(void) -{ - MX_DMA_Init(); - MX_ADC1_Init(); -} - -uint32_t pm_adc_get_value(void) -{ - if (HAL_ADC_PollForConversion(&LIGHT_ADC, 10) != HAL_OK) - { - ET_ERR("ADC read should not wait!"); - pm_error_led_on(); - return 0; - } - return HAL_ADC_GetValue(&LIGHT_ADC); -} - -void pm_sampling_tim_init(void) -{ - MX_TIM3_Init(); -} - -void pm_sampling_tim_start(void) -{ - HAL_TIM_Base_Start_IT(&SAMPLING_TIM); -} - -void pm_sampling_tim_stop(void) -{ - HAL_TIM_Base_Stop_IT(&SAMPLING_TIM); -} - -void pm_sampling_adc_start(uint16_t *buffer, uint32_t length) -{ - if (HAL_ADC_Start_DMA(&LIGHT_ADC, (uint32_t *)buffer, length) != HAL_OK) - { - ET_ERR("ADC start DMA error!"); - pm_error_led_on(); - } - //HAL_ADC_Start(&LIGHT_ADC); -} - -void pm_sampling_adc_stop(void) -{ - //HAL_ADC_Stop(&hadc1); - HAL_ADC_Stop_DMA(&LIGHT_ADC); -} - -float pm_sampling_tim_get_freq(void) -{ - uint32_t pclk1 = HAL_RCC_GetPCLK1Freq(); - uint32_t timer_clk; - - // 获取APB1预分频系数(来自RCC配置寄存器) - uint32_t apb1_prescaler = (RCC->CFGR & RCC_CFGR_PPRE1) >> RCC_CFGR_PPRE1_Pos; - - // 根据APB1预分频计算定时器时钟源 - if (apb1_prescaler == RCC_HCLK_DIV1) - { - // APB1预分频=1,定时器时钟=PCLK1 - timer_clk = pclk1; - } - else - { - // APB1预分频>1,定时器时钟=2×PCLK1 - timer_clk = pclk1 * 2; - } - - // 计算定时器计数频率(经过PSC分频后) - uint32_t counter_freq = timer_clk / (SAMPLING_TIM.Init.Prescaler + 1); - - // 计算最终输出频率(周期触发频率 = 计数频率 / (Period + 1)) - // 注意:如果是向上计数模式,完整周期是从0到Period,共Period+1个计数 - return (float)counter_freq / (SAMPLING_TIM.Init.Period + 1); -} - -int pm_board_init(void) -{ - MX_GPIO_Init(); - // MX_USART1_UART_Init(); -#if (USING_SOFT_SPI == 1) - soft_spi_init(&soft_spi); - soft_spi_set_mode(&soft_spi, SOFT_SPI_MODE3); // 设置 SPI 模式 - soft_spi_set_bit_order(&soft_spi, SOFT_SPI_MSB_FIRST); // 设置位序 - soft_spi_set_delay(&soft_spi, 1); // 设置延时为 1 微秒 - soft_spi_select(&soft_spi); // 选择 SPI 设备 -#endif - - return 0; -} diff --git a/User/board/pm_board.h b/User/board/pm_board.h deleted file mode 100644 index aec124e..0000000 --- a/User/board/pm_board.h +++ /dev/null @@ -1,109 +0,0 @@ -/* - * @Date: 2025-06-19 12:29:23 - * @Author: mypx - * @LastEditors: mypx mypx_coder@163.com - * @LastEditTime: 2025-08-25 17:06:01 - * @FilePath: board_pm.h - * @Description: - * Copyright (c) 2025 by mypx, All Rights Reserved. - */ -/*** - * @Author: mypx - * @Email: mypx_coder@163.com - * @Date: 2025-06-19 12:29:23 - * @LastEditors: mypx mypx_coder@163.com - * @Description: - */ -#ifndef __PM_BOARD_H__ -#define __PM_BOARD_H__ -#include "adc.h" -#include "main.h" -#include "rtthread.h" -#include "tim.h" -#include "user_config.h" -#include -#include - -extern volatile uint64_t timestamp_tim_overflow_count; - -typedef enum -{ - TEC_COOLING = 0, - TEC_HEATING = 1 -} TEC_Direction_TypeDef; - -int pm_board_init(void); - -void pm_system_led_toggle(void); - -void pm_system_led_on(void); -void pm_system_led_off(void); - -void pm_error_led_on(void); -void pm_error_led_off(void); -void pm_error_led_toggle(void); - -int pm_i2c_write_bytes(uint16_t dev_addr, uint16_t reg, uint8_t *data, uint16_t size); -int pm_i2c_read_bytes(uint16_t dev_addr, uint16_t reg, uint8_t *data, uint16_t size); - -void pm_ad7793_spi_transfer(uint8_t *tx_buf, uint8_t *rx_buf, uint16_t len); - -void pm_uart_print_send(const uint8_t *data, uint16_t len); - -void pm_lcd_cmd_send(const uint8_t *data, uint16_t len); - -#define MB_HMI_RX_QUEUE_SIZE 128 // Modbus HMI 接收队列大小 -extern struct rt_messagequeue mb_hmi_mq; - -int pm_mb_hmi_init(void); - -int32_t pm_hmi_uart_read(uint8_t *buf, uint16_t count, int32_t byte_timeout_ms, void *arg); - -int32_t pm_hmi_uart_write(const uint8_t *buf, uint16_t count, int32_t byte_timeout_ms, void *arg); - -#if (ENABLE_SV630P == 1) -#define SERVO_RX_QUEUE_SIZE 128 // Modbus RTU 接收队列大小 -extern struct rt_messagequeue servo_mq; -int pm_servo_init(void); - -int32_t pm_servo_uart_read(uint8_t *buf, uint16_t count, int32_t timeout, void *arg); - -int32_t pm_servo_uart_write(const uint8_t *buf, uint16_t count, int32_t timeout, void *arg); -#endif // ENABLE_SV630P - -#if (ENABLE_TEC == 1) -void pm_tec_init(void); -void pm_tec_start(void); -void pm_tec_stop(void); -void pm_tec_set_duty(float pid_output); - -void pm_fan_start(void); - -void pm_fan_stop(void); -#endif - -void pm_timestamp_tim_init(void); - -void pm_timestamp_start(void); - -void pm_timestamp_stop(void); - -uint64_t pm_get_timestamp_us(void); - -void pm_adc_init(void); - -void pm_sampling_adc_start(uint16_t *buffer, uint32_t length); - -void pm_sampling_adc_stop(void); - -uint32_t pm_adc_get_value(void); - -void pm_sampling_tim_init(void); - -void pm_sampling_tim_start(void); - -void pm_sampling_tim_stop(void); - -float pm_sampling_tim_get_freq(void); - -#endif // __BOARD_H__ diff --git a/User/driver/ad779x/README.md b/User/driver/ad779x/README.md index 2e0c2b7..aa1f549 100644 --- a/User/driver/ad779x/README.md +++ b/User/driver/ad779x/README.md @@ -55,6 +55,63 @@ AD7792则以更低成本覆盖中精度需求场景。 --- + + +## 推荐初始化流程(AD7793) + +1. 配置 `ad7793_hw_if_t` 硬件接口结构体,填充 SPI/GPIO/延时等函数指针。 +2. 定义 `ad7793_config_t` 配置结构体,指定参考源、增益、通道、速率、缓冲等参数。 +3. 调用 `ad7793_init(&dev, &hw_if, cs_pin, &cfg)` 完成初始化。 +4. 采集数据前建议调用 `ad7793_wait_ready` 等待数据就绪。 +5. 采集数据用 `ad7793_read_data`,转换电压用 `ad7793_convert_to_voltage`。 +6. 如需切换通道/增益/参考源,建议先 idle,再切换参数,最后恢复 continuous。 + +## 常见故障排查 + +- STATUS=0x48/0xC8:多为参考源配置与硬件不符、增益/缓冲冲突、IEXC未配置等。 +- 读数全0或极大:检查参考电压、SPI连线、通道/增益配置。 +- 写寄存器失败:检查 SPI 速率、CS 时序、硬件接口实现。 +- 温度跳变/毛刺:建议应用层做温度跳变/低码滤波。 +- 采样速率异常:确认速率配置与主时钟、SPI速率匹配。 + +## IEXC(激励电流)配置注意事项 + +- 仅当硬件实际将 IOUT1/IOUT2 接到 RTD 或传感器回路时才需配置 IEXC。 +- 若 IOUT1/IOUT2 悬空(未连接),请勿使能 IEXC,否则可能导致不可预期行为或错误状态(如 STATUS=0x48)。 +- IEXC 配置必须与硬件连接方式一致。若用外部电流源或定值电阻,IEXC 应保持关闭。 + +## 缓冲/增益策略 + +- 关闭缓冲(BUF=0)时,增益不得大于2(G<=2),驱动已强制限制。 +- 开启缓冲(BUF=1)可用高增益,但输入偏置电流和功耗增加。 + +## 寄存器dump与诊断 + +- 可用 `ad7793_dump_registers` 或 `ad7793_dump_registers_mode` 打印所有寄存器值,便于调试。 +- 检测到持续错误(STATUS.ERR)时建议dump寄存器,辅助定位参考、缓冲、增益、IEXC等配置问题。 + +## API典型用法与边界说明 + +- 所有API均检查设备初始化和参数有效性,未初始化或参数非法均返回false。 +- 具体API用法和边界条件详见头文件注释。 + +## Buffer/Gain Policy + +- When buffer is disabled (BUF=0), gain must not exceed 2 (G<=2). The driver enforces this restriction and will return false if you attempt to set G>2 with buffer off. +- Enabling the buffer (BUF=1) allows higher gain settings, but increases input bias current and power consumption. + +## Register Dump and Diagnostics + +- Use `ad7793_dump_registers` or `ad7793_dump_registers_mode` to print all register values for debugging. +- When persistent error (STATUS.ERR) is detected, perform a register dump to help diagnose the cause (reference, buffer, gain, IEXC, etc.). + +## Typical API Usage and Boundary Notes + +- All API functions check for device initialization and parameter validity; invalid parameters or uninitialized device will return false. +- See header file comments for typical usage and boundary conditions for each API. + +--- + ## 通信寄存器用法 通信寄存器在AD7793中起到控制数据传输方向和指定目标寄存器的作用。它是SPI通信接口的核心控制寄存器,用于告诉AD7793后续的操作是读还是写,以及操作的目标寄存器是哪一个。 @@ -98,4 +155,60 @@ bool ad7793_write_reg(uint8_t reg, uint8_t *data, uint16_t len) 2. **连续操作**:在连续操作同一个寄存器时,不需要每次都重新设置通信寄存器,除非操作类型或目标寄存器发生变化。 3. **数据寄存器的特殊处理**:数据寄存器(REG_DATA)在读模式下有特殊的连续读功能,可以通过设置`CREAD`位来启用。 -理解通信寄存器的工作原理对于正确操作AD7793至关重要,它是实现与器件通信的基础。 \ No newline at end of file +理解通信寄存器的工作原理对于正确操作AD7793至关重要,它是实现与器件通信的基础。 + +--- + +# AD7793/AD7792 应用配置与调试注意事项 + +## 推荐配置(PT100/RTD 测温典型拓扑) + +```c +static const ad7793_config_t g_ad7793_config = { + .use_internal_ref = false, // 使用外部参考(如2.5V),REFIN+已实测2.5V + .external_ref = 2.500f, + // 传感器差分约 0.043V,需保证 < Vref / Gain + // 选择 GAIN=4 -> 满量程 2.5/4=0.625V,安全覆盖 43mV 输入 + .init_gain = AD7793_GAIN_4, + .init_channel = AD7793_CHANNEL_1, // AIN1(+) - AIN1(-) + .init_rate = AD7793_RATE_8_33HZ, + .unipolar = true, // 只测正向信号 + .buffered = true, // 启用缓冲 + .calibrate_system_zero = false // 禁用 system zero 校准,防止 OFFSET 被污染 +}; +``` + +## 配置与调试注意事项 + +1. **参考电压必须与硬件一致**: + - 若硬件 REFIN+ 接外部参考(如2.5V),`use_internal_ref` 必须为 `false`,`external_ref` 填实际电压。 + - 若用内部参考(1.17V),需保证 REFIN+/- 悬空且 `use_internal_ref=true`。 + +2. **增益选择**: + - 满量程 = 参考电压 / 增益。输入信号最大值必须小于满量程。 + - 输入信号远小于满量程时可适当提高增益(如 GAIN=8/16),但不能超量程。 + +3. **校准操作**: + - 禁用 system zero 校准(`calibrate_system_zero=false`),防止在参考/输入异常时 OFFSET 被写坏。 + - 若需校准,必须保证参考和输入都正常且在允许范围内。 + +4. **错误状态与保护**: + - STATUS.ERR(bit6=1)表示参考/输入异常,采样数据无效。 + - 采样线程应对 ERR/低码做过滤,避免异常温度。 + - OFFSET/FULLSCALE 寄存器应在每次初始化和异常时 dump 检查。 + +5. **常见故障排查**: + - STATUS=0x48 持续:多为参考配置与硬件不符或输入超量程。 + - OFFSET=0x800000 为正常中点,极大/极小为校准异常。 + - 采样码值极低或全0,多为参考/输入/校准异常。 + +6. **硬件测量建议**: + - 用万用表测 REFIN+ 对地、AIN1+/AIN1- 差分,确认与配置一致。 + +7. **调试建议**: + - 先用低增益(如 GAIN=2/4)确保不超量程,采样正常后再逐步提高增益。 + - 采样线程建议加低码/ERR保护,防止异常数据影响温度计算。 + +--- + +如需更高分辨率或特殊应用,请根据实际输入信号幅度和参考电压合理调整增益与参考配置。 \ No newline at end of file diff --git a/User/driver/ad779x/ad7793.c b/User/driver/ad779x/ad7793.c index 631adca..ab52b7b 100644 --- a/User/driver/ad779x/ad7793.c +++ b/User/driver/ad779x/ad7793.c @@ -7,17 +7,20 @@ */ #include "ad7793.h" #include -#include +#include #ifdef DEBUG #include -#define DEBUG_PRINTF(fmt, ...) \ - do { \ - rt_kprintf("[AD7793] " fmt, ##__VA_ARGS__); \ +#define DEBUG_PRINTF(...) \ + do \ + { \ + rt_kprintf("[AD7793] "); \ + rt_kprintf(__VA_ARGS__); \ } while (0) #else -#define DEBUG_PRINTF(fmt, ...) \ - do { \ +#define DEBUG_PRINTF(...) \ + do \ + { \ } while (0) #endif /** @@ -56,6 +59,9 @@ static bool ad7793_spi_transfer(ad7793_dev_t *dev, uint8_t cmd, uint8_t *tx_buf, return true; } +/* Forward declaration for diagnostic dump (used early) */ +void ad7793_dump_registers(ad7793_dev_t *dev); + /** * @brief Write communication register. * @param dev Pointer to ad7793_dev_t instance. @@ -113,7 +119,28 @@ bool ad7793_write_reg(ad7793_dev_t *dev, uint8_t reg, uint32_t data, uint16_t le //ad7793_spi_transfer(dev, cmd, data, NULL, len); dev->hw_if->gpio_set(dev->cs_pin, true); - return true; + /* Verify write by reading back the register up to a few retries */ + for (int attempt = 0; attempt < 3; attempt++) + { + uint32_t read_back = 0; + dev->hw_if->delay_ms(2); + if (!ad7793_read_reg(dev, reg, &read_back, len)) + { + DEBUG_PRINTF("Readback failed for reg 0x%02X on attempt %d\n", reg, attempt); + continue; + } + /* mask read_back to len bytes */ + uint32_t mask = (len == 3) ? 0xFFFFFFu : ((len == 2) ? 0xFFFFu : 0xFFu); + if ((read_back & mask) == (data & mask)) + { + return true; /* success */ + } + DEBUG_PRINTF("Write verify mismatch reg 0x%02X: wrote 0x%0*X read 0x%0*X (attempt %d)\n", reg, len * 2, + data & mask, len * 2, read_back & mask, attempt + 1); + } + + DEBUG_PRINTF("Write to reg 0x%02X failed after retries\n", reg); + return false; } /** @@ -173,7 +200,8 @@ bool ad7793_reset(ad7793_dev_t *dev) dev->hw_if->gpio_set(dev->cs_pin, false); uint8_t reset_seq[4] = {0xFF, 0xFF, 0xFF, 0xFF}; - dev->hw_if->spi_transfer(reset_seq, NULL, 4); + uint8_t tmp[4]; + dev->hw_if->spi_transfer(reset_seq, tmp, 4); dev->hw_if->gpio_set(dev->cs_pin, true); dev->hw_if->delay_ms(1); /* Wait for reset to complete */ @@ -245,6 +273,14 @@ bool ad7793_set_gain(ad7793_dev_t *dev, ad7793_gain_t gain) uint32_t config_reg = 0; ad7793_read_reg(dev, REG_CONFIG, &config_reg, 2); + // 缓冲/增益策略规范化:BUF=0且G>2时禁止配置 + bool buf_enabled = (config_reg & CONFIG_BUF) ? true : false; + if (!buf_enabled && gain > 2) + { + DEBUG_PRINTF("[ERR] Attempt to set gain >2 while buffer disabled!\n"); + return false; + } + /* Clear gain bits (G2/G1/G0) */ config_reg &= ~(CONFIG_G2 | CONFIG_G1 | CONFIG_G0); @@ -374,8 +410,9 @@ bool ad7793_set_reference(ad7793_dev_t *dev, bool use_internal, float external_r } else { - dev->external_ref = external_ref; - config_reg &= ~CONFIG_REFSEL; + dev->external_ref = external_ref; + dev->use_internal_ref = false; /* reflect external reference selection */ + config_reg &= ~CONFIG_REFSEL; } DEBUG_PRINTF("[config-w]set reference: 0x%04X\n", config_reg); return ad7793_write_reg(dev, REG_CONFIG, config_reg, 2); @@ -398,11 +435,15 @@ bool ad7793_wait_ready(ad7793_dev_t *dev, uint16_t timeout_ms) while (timeout < timeout_ms) { ad7793_read_reg(dev, REG_STATUS, &status, 1); - + dev->last_status = status; if (status & 0x40) // check if the device is in error state { - DEBUG_PRINTF("Device in error state, status: 0x%02X\n", status); - return false; // device error + dev->err_count++; + if ((dev->err_count % 128u) == 1u) /* 抽样打印,避免刷屏 */ + { + DEBUG_PRINTF("Device in error state (status=0x%02X) - continue polling\n", status); + ad7793_dump_registers(dev); + } } // check RDY bit(bit7)is zero or not if ((status & 0x80) == 0) @@ -418,6 +459,45 @@ bool ad7793_wait_ready(ad7793_dev_t *dev, uint16_t timeout_ms) return false; // timeout } +/* Diagnostic helper: dump key registers */ +void ad7793_dump_registers(ad7793_dev_t *dev) +{ + ad7793_dump_registers_mode(dev, 1); +} + +// mode: 0=简略, 1=全量 +typedef enum +{ + AD7793_DUMP_SIMPLE = 0, + AD7793_DUMP_FULL = 1 +} ad7793_dump_mode_t; +void ad7793_dump_registers_mode(ad7793_dev_t *dev, int full) +{ + if (!dev || !dev->hw_if) + return; + uint32_t reg = 0; + ad7793_read_reg(dev, REG_STATUS, ®, 1); + DEBUG_PRINTF("DUMP STATUS: 0x%02X\n", (unsigned)reg); + ad7793_read_reg(dev, REG_MODE, ®, 2); + DEBUG_PRINTF("DUMP MODE: 0x%04X\n", (unsigned)reg); + ad7793_read_reg(dev, REG_CONFIG, ®, 2); + DEBUG_PRINTF("DUMP CONFIG: 0x%04X\n", (unsigned)reg); + if (full) + { + ad7793_read_reg(dev, REG_IO, ®, 1); + DEBUG_PRINTF("DUMP IO: 0x%02X\n", (unsigned)reg); + ad7793_read_reg(dev, REG_ID, ®, 1); + DEBUG_PRINTF("DUMP ID: 0x%02X\n", (unsigned)reg); + ad7793_read_reg(dev, REG_OFFSET, ®, 3); + DEBUG_PRINTF("DUMP OFFSET: 0x%06X\n", (unsigned)reg); + ad7793_read_reg(dev, REG_FULLSCALE, ®, 3); + DEBUG_PRINTF("DUMP FULLSC: 0x%06X\n", (unsigned)reg); + } + // 关键信息摘要 + DEBUG_PRINTF("SUMMARY: mode=%d gain=%d ch=%d ref=%s buf=%d uni=%d\n", dev->cur_mode, dev->cur_gain, + dev->cur_channel, dev->use_internal_ref ? "int" : "ext", dev->buffered, dev->unipolar_mode); +} + /** * @brief Read the conversion data from the device. * @param dev Pointer to ad7793_dev_t instance. @@ -501,7 +581,10 @@ float ad7793_convert_to_voltage(ad7793_dev_t *dev, uint32_t adc_code) if (!(resolution_bits == 16 || resolution_bits == 24)) return 0.0f; // Invalid resolution if (!buffer_enabled && gain > 2) - return 0.0f; // According to datasheet, buffer must be enabled when gain > 2 + { + DEBUG_PRINTF("Warning: buffer disabled while gain > 2 — result may be invalid\n"); + /* Do not outright fail here; allow conversion but warn the caller. */ + } // Check that the ADC code is within valid range uint32_t max_code = (resolution_bits == 24) ? 0xFFFFFFu : 0xFFFFu; @@ -580,7 +663,10 @@ uint32_t ad7793_convert_voltage_to_code(ad7793_dev_t *dev, float voltage) if (!(resolution_bits == 16 || resolution_bits == 24)) return 0; // Invalid resolution if (!buffer_enabled && gain > 2) - return 0; // According to datasheet, buffer must be enabled when gain > 2 + { + DEBUG_PRINTF("Warning: buffer disabled while gain > 2 — code may be invalid\n"); + /* Allow calculation to proceed but warn */ + } // Calculate maximum ADC code value uint32_t max_code = (resolution_bits == 24) ? 0xFFFFFFu : 0xFFFFu; @@ -651,7 +737,12 @@ bool ad7793_calibrate_internal_full(ad7793_dev_t *dev) { AD7793_CHECK_INITIALIZED(dev); ad7793_set_mode(dev, AD7793_MODE_INTERNAL_FULL); - dev->hw_if->delay_ms(10); /* Wait for calibration to complete */ + /* Wait until RDY clears (calibration completed) or timeout */ + if (!ad7793_wait_ready(dev, 1000)) + { + DEBUG_PRINTF("Internal full-scale calibration timeout\n"); + return false; + } return ad7793_set_mode(dev, AD7793_MODE_CONTINUOUS); } @@ -666,7 +757,11 @@ bool ad7793_calibrate_system_zero(ad7793_dev_t *dev) { AD7793_CHECK_INITIALIZED(dev); ad7793_set_mode(dev, AD7793_MODE_SYSTEM_ZERO); - dev->hw_if->delay_ms(10); /* Wait for calibration to complete */ + if (!ad7793_wait_ready(dev, 1000)) + { + DEBUG_PRINTF("System zero calibration timeout\n"); + return false; + } return ad7793_set_mode(dev, AD7793_MODE_CONTINUOUS); } @@ -681,7 +776,11 @@ bool ad7793_calibrate_system_full(ad7793_dev_t *dev) { AD7793_CHECK_INITIALIZED(dev); ad7793_set_mode(dev, AD7793_MODE_SYSTEM_FULL); - dev->hw_if->delay_ms(10); /* Wait for calibration to complete */ + if (!ad7793_wait_ready(dev, 1000)) + { + DEBUG_PRINTF("System full calibration timeout\n"); + return false; + } return ad7793_set_mode(dev, AD7793_MODE_CONTINUOUS); } @@ -697,36 +796,27 @@ bool ad7793_calibrate_system_full(ad7793_dev_t *dev) bool ad7793_config_iexc(ad7793_dev_t *dev, uint16_t current, uint8_t dir) { AD7793_CHECK_INITIALIZED(dev); - uint32_t io_reg = 0; - - /* Set current value */ - if (current == 10) + /* 根据数据手册:IO 寄存器位: [3:2]=IEXCDIR1:0, [1:0]=IEXCEN1:0 + IEXCEN 00=关, 01=10uA, 10=210uA, 11=1mA (常见映射, 若芯片版本不同需核对手册) */ + uint8_t en_bits; + switch (current) { - io_reg |= 0x01; + case 10: + en_bits = 0x01; + break; + case 210: + en_bits = 0x02; + break; + case 1000: + en_bits = 0x03; + break; + default: + return false; /* unsupported */ } - else if (current == 210) - { - io_reg |= 0x02; - } - else if (current == 1000) - { - io_reg |= 0x03; - } - else - { - return false; - } - - /* Set current direction */ - if (dir) - { - io_reg |= (0x03 << 2); - } - - /* Enable current source */ - io_reg |= (0x03 << 0); - - return ad7793_write_reg(dev, REG_IO, io_reg, 1); + uint8_t dir_bits = (dir & 0x03) << 2; /* 方向/复用: 00=IOUT1->AIN1+, 01=IOUT1&2? 需依据原理图调整 */ + uint8_t io_val = (uint8_t)(dir_bits | en_bits); + DEBUG_PRINTF("[io-w] set IEXC current=%u uA dir=0x%X val=0x%02X\n", (unsigned)current, (unsigned)dir, io_val); + return ad7793_write_reg(dev, REG_IO, io_val, 1); } /** @@ -784,6 +874,14 @@ bool ad7793_set_buffered(ad7793_dev_t *dev, bool enable) // Read current configuration register value ad7793_read_reg(dev, REG_CONFIG, &config_reg, 2); + // 缓冲/增益策略规范化:若当前增益>2且要关闭缓冲,禁止 + uint8_t gain = dev->cur_gain; + if (!enable && gain > 2) + { + DEBUG_PRINTF("[ERR] Attempt to disable buffer while gain >2!\n"); + return false; + } + // Update BUF bit (bit 7 of the configuration register) if (enable) { @@ -813,71 +911,110 @@ bool ad7793_set_buffered(ad7793_dev_t *dev, bool enable) * It resets the device, waits for the reset to complete, and then reads the ID register to confirm the device type. * Finally, it initializes the device with default configuration settings. */ -bool ad7793_init(ad7793_dev_t *dev, ad7793_hw_if_t *hw_if, uint8_t cs_pin) +bool ad7793_init(ad7793_dev_t *dev, ad7793_hw_if_t *hw_if, uint8_t cs_pin, const ad7793_config_t *cfg) { - uint32_t mode_reg = 0; - uint32_t config_reg = 0; - if (hw_if == NULL) + if (!dev || !hw_if) { - DEBUG_PRINTF("hw_if is null\n"); + DEBUG_PRINTF("NULL param in init\n"); return false; } - /* Save hardware interface and chip select info */ + dev->hw_if = hw_if; dev->cs_pin = cs_pin; - dev->is_ad7793 = true; /* Default is AD7793, can be confirmed by ID register */ dev->is_initialized = true; + dev->is_ad7793 = true; /* assume, verify after reset */ + dev->err_count = dev->err_recoveries = dev->low_code_drops = 0; + dev->last_status = 0; - /* Reset device */ + /* Pick configuration (simple & predictable) */ + ad7793_config_t local_cfg = cfg ? *cfg : AD7793_DEFAULT_CONFIG; + + /* Reset & small settle */ ad7793_reset(dev); + dev->hw_if->delay_ms(2); - /* Wait for reset to complete */ - dev->hw_if->delay_ms(1); - - /* Read ID register to confirm device type */ + /* Read ID */ uint32_t id = 0; ad7793_read_reg(dev, REG_ID, &id, 1); - DEBUG_PRINTF("ID: 0x%02X\n", id); - dev->is_ad7793 = ((id & 0x0F) == 0x0B); /* AD7793 ID read out is 0x4B */ + dev->is_ad7793 = ((id & 0x0F) == 0x0B); + DEBUG_PRINTF("ID: 0x%02X (%s)\n", id, dev->is_ad7793 ? "AD7793" : "Unknown"); - /* Initialize default configuration */ - ad7793_set_mode(dev, AD7793_MODE_CONTINUOUS); - ad7793_wait_ready(dev, 1000); - ad7793_read_reg(dev, REG_MODE, &mode_reg, 2); - DEBUG_PRINTF("[mode-r]: 0x%04X\n", mode_reg); + /* Put into continuous mode first */ + if (!ad7793_set_mode(dev, AD7793_MODE_CONTINUOUS)) + return false; + /* Apply reference before gain (gain affects allowed input span vs ref) */ + if (!ad7793_set_reference(dev, local_cfg.use_internal_ref, local_cfg.external_ref)) + return false; + if (!ad7793_set_gain(dev, local_cfg.gain)) + return false; + if (!ad7793_set_channel(dev, local_cfg.channel)) + return false; + if (!ad7793_set_rate(dev, local_cfg.rate)) + return false; + if (!ad7793_set_unipolar(dev, local_cfg.unipolar_mode)) + return false; + if (!ad7793_set_buffered(dev, local_cfg.buffered)) + return false; - ad7793_set_gain(dev, AD7793_GAIN_1); - ad7793_wait_ready(dev, 1000); - ad7793_read_reg(dev, REG_CONFIG, &config_reg, 2); - DEBUG_PRINTF("[config-r]after gain config: 0x%04X\n", config_reg); + /* Optional calibration */ + if (local_cfg.calibrate_system_zero) + { + if (!ad7793_calibrate_system_zero(dev)) + DEBUG_PRINTF("System zero calibration skipped/failed\n"); + } - ad7793_set_channel(dev, AD7793_CHANNEL_1); - ad7793_wait_ready(dev, 1000); - ad7793_read_reg(dev, REG_CONFIG, &config_reg, 2); - DEBUG_PRINTF("[config-r]after channel config: 0x%04X\n", config_reg); - - ad7793_set_rate(dev, AD7793_RATE_8_33HZ); - ad7793_wait_ready(dev, 1000); - ad7793_read_reg(dev, REG_MODE, &mode_reg, 2); - DEBUG_PRINTF("[mode-r]after rate mode: 0x%04X\n", mode_reg); - - ad7793_set_reference(dev, true, 2.5); /* Use internal reference */ - ad7793_wait_ready(dev, 1000); - ad7793_read_reg(dev, REG_CONFIG, &config_reg, 2); - DEBUG_PRINTF("[config-r]after reference config: 0x%04X\n", config_reg); - - ad7793_set_unipolar(dev, true); /* Default to bipolar mode */ - ad7793_wait_ready(dev, 1000); - ad7793_read_reg(dev, REG_CONFIG, &config_reg, 2); - DEBUG_PRINTF("[config-r]after polar config: 0x%04X\n", config_reg); - - ad7793_set_buffered(dev, false); /* Enable input buffer */ - ad7793_wait_ready(dev, 1000); - ad7793_read_reg(dev, REG_CONFIG, &config_reg, 2); - DEBUG_PRINTF("final read config: 0x%04X\n", config_reg); - ad7793_calibrate_system_zero(dev); + /* Quick sanity read (no verbose decode) */ + uint32_t cfg_rd = 0; + ad7793_read_reg(dev, REG_CONFIG, &cfg_rd, 2); + DEBUG_PRINTF("CONFIG=0x%04X\n", cfg_rd); + /* 配置激励电流(可根据实际传感器回路调整) */ + (void)ad7793_config_iexc(dev, 210, 0); + /* 初次 dump offset/fullscale 辅助调试 */ + ad7793_dump_registers(dev); + return true; +} +/* Attempt a soft recovery when persistent ERR occurs. + * Strategy: reset, force internal reference, re-apply basic config, optional system zero cal. */ +bool ad7793_recover(ad7793_dev_t *dev, const ad7793_config_t *base_cfg) +{ + if (!dev || !dev->is_initialized) + return false; + ad7793_config_t cfg = base_cfg ? *base_cfg : AD7793_DEFAULT_CONFIG; + cfg.use_internal_ref = true; /* fallback always internal */ + if (!ad7793_reset(dev)) + return false; + dev->hw_if->delay_ms(2); + /* re-id */ + uint32_t id = 0; + ad7793_read_reg(dev, REG_ID, &id, 1); + dev->is_ad7793 = ((id & 0x0F) == 0x0B); + if (!ad7793_set_mode(dev, AD7793_MODE_CONTINUOUS)) + return false; + if (!ad7793_set_reference(dev, true, 0.0f)) + return false; + if (!ad7793_set_gain(dev, cfg.gain)) + return false; + if (!ad7793_set_channel(dev, cfg.channel)) + return false; + if (!ad7793_set_rate(dev, cfg.rate)) + return false; + if (!ad7793_set_unipolar(dev, cfg.unipolar_mode)) + return false; +#ifndef AD7793_DEFAULT_CONFIG +#define AD7793_DEFAULT_CONFIG \ + { \ + .use_internal_ref = true, .external_ref = 1.17f, .gain = AD7793_GAIN_1, .channel = AD7793_CHANNEL_1, \ + .rate = AD7793_RATE_8_33HZ, .buffered = false, .unipolar_mode = true, .calibrate_system_zero = false \ + } +#endif + if (!ad7793_set_buffered(dev, cfg.buffered)) + return false; + if (cfg.calibrate_system_zero) + (void)ad7793_calibrate_system_zero(dev); + dev->err_recoveries++; + DEBUG_PRINTF("Recovery done (recoveries=%lu)\n", (unsigned long)dev->err_recoveries); return true; } diff --git a/User/driver/ad779x/ad7793.h b/User/driver/ad779x/ad7793.h index ce84d67..2c2e960 100644 --- a/User/driver/ad779x/ad7793.h +++ b/User/driver/ad779x/ad7793.h @@ -123,6 +123,29 @@ typedef enum AD7793_RATE_470HZ = 0x1 /**< 470Hz */ } ad7793_rate_t; +/* Configuration structure for AD7793 */ +typedef struct +{ + bool use_internal_ref; /**< true: use internal reference, false: use external reference */ + float external_ref; /**< External reference voltage (V), used if use_internal_ref is false */ + ad7793_gain_t gain; /**< Gain setting */ + ad7793_channel_t channel; /**< Channel selection */ + ad7793_rate_t rate; /**< Output data rate */ + bool buffered; /**< true: enable input buffer, false: disable buffer */ + bool unipolar_mode; /**< true: unipolar mode, false: bipolar mode */ + bool calibrate_system_zero; /**< true: perform system zero calibration at startup, false: skip calibration */ +} ad7793_config_t; + +/* Default config as a compound literal so it can be used in expressions */ +#ifndef AD7793_DEFAULT_CONFIG +#define AD7793_DEFAULT_CONFIG \ + (ad7793_config_t) \ + { \ + .use_internal_ref = true, .external_ref = 1.17f, .gain = AD7793_GAIN_1, .channel = AD7793_CHANNEL_1, \ + .rate = AD7793_RATE_8_33HZ, .buffered = false, .unipolar_mode = true, .calibrate_system_zero = false \ + } +#endif + /* Device structure */ typedef struct { @@ -138,9 +161,15 @@ typedef struct bool unipolar_mode; /**< True if unipolar mode, false if bipolar mode */ bool buffered; /**< True if buffer enable, false if buffer unused */ bool is_initialized; /**< True if the device has been initialized */ + /* --- Runtime diagnostics --- */ + uint32_t err_count; /**< Total STATUS.ERR (bit6) occurrences */ + uint32_t err_recoveries; /**< Number of automatic recovery attempts */ + uint32_t low_code_drops; /**< Dropped samples due to low code threshold */ + uint32_t last_status; /**< Last read STATUS register */ } ad7793_dev_t; #define AD7793_CHECK_INITIALIZED(dev) \ - do { \ + do \ + { \ if (!dev->is_initialized) \ { \ DEBUG_PRINTF("Device not initialized!\n"); \ @@ -148,6 +177,27 @@ typedef struct } \ } while (0) +/* + * AD7793 Driver Header + * + * Recommended Initialization Flow: + * 1. Configure the ad7793_hw_if_t hardware interface struct, filling in SPI/GPIO/delay function pointers. + * 2. Define the ad7793_config_t configuration struct, specifying reference source, gain, channel, rate, buffer, etc. + * 3. Call ad7793_init(&dev, &hw_if, cs_pin, &cfg) to initialize the device. + * 4. Before data acquisition, call ad7793_wait_ready to wait for data ready. + * 5. Use ad7793_read_data to acquire data, and ad7793_convert_to_voltage to convert to voltage. + * 6. If you need to switch channel/gain/reference, it is recommended to set idle mode first, then switch parameters, and finally restore continuous mode. + * + * Common Troubleshooting: + * - STATUS=0x48/0xC8: Usually caused by reference source mismatch, buffer/gain conflict, or IEXC not configured as required by hardware. + * - All readings zero or extremely large: Check reference voltage, SPI wiring, channel/gain configuration. + * - Register write failure: Check SPI speed, CS timing, hardware interface implementation. + * - Temperature jumps/spikes: Application layer should implement temperature jump/low code filtering. + * - Abnormal sampling rate: Confirm rate configuration matches master clock and SPI speed. + * + * See API comments for typical usage. + */ + /** * @brief Write data to a specified register of the AD7793 device. * @param dev Device structure pointer. @@ -158,6 +208,7 @@ typedef struct * @detail This function constructs a communication command to write data to the specified register. * It first clears the chip select pin, then performs an SPI transfer to send the command and data. * Finally, it sets the chip select pin back to high. + * @note Boundary: Returns false if dev is not initialized or parameters are invalid. Example: ad7793_write_reg(&dev, REG_MODE, 0x200A, 2); */ bool ad7793_write_reg(ad7793_dev_t *dev, uint8_t reg, uint32_t data, uint16_t len); @@ -171,6 +222,7 @@ bool ad7793_write_reg(ad7793_dev_t *dev, uint8_t reg, uint32_t data, uint16_t le * @detail This function constructs a communication command to read data from the specified register. * It first clears the chip select pin, then performs an SPI transfer to receive the data. * Finally, it sets the chip select pin back to high. + * @note Boundary: Returns false if dev is not initialized or parameters are invalid. Example: ad7793_read_reg(&dev, REG_CONFIG, &val, 2); */ bool ad7793_read_reg(ad7793_dev_t *dev, uint8_t reg, uint32_t *data, uint16_t len); @@ -181,6 +233,7 @@ bool ad7793_read_reg(ad7793_dev_t *dev, uint8_t reg, uint32_t *data, uint16_t le * @detail This function resets the device by writing 32 ones to the device. * It clears the chip select pin, sends the reset sequence via SPI, and then sets the chip select pin back to high. * After that, it waits for 1 millisecond to ensure the reset is completed. + * @note Example: ad7793_reset(&dev); */ bool ad7793_reset(ad7793_dev_t *dev); @@ -191,6 +244,7 @@ bool ad7793_reset(ad7793_dev_t *dev); * @return Operation status. True if successful, false otherwise. * @detail This function reads the current mode register, clears the mode bits, and then sets the new mode according to the input. * It updates the current mode in the device structure and writes the new mode to the mode register. + * @note Example: ad7793_set_mode(&dev, AD7793_MODE_CONTINUOUS); */ bool ad7793_set_mode(ad7793_dev_t *dev, ad7793_mode_t mode); @@ -201,6 +255,7 @@ bool ad7793_set_mode(ad7793_dev_t *dev, ad7793_mode_t mode); * @return Operation status. True if successful, false otherwise. * @detail This function reads the current configuration register, clears the gain bits, and then sets the new gain according to the input. * It updates the current gain in the device structure and writes the new configuration to the configuration register. + * @note Boundary: Returns false if BUF=0 and G>2. Example: ad7793_set_gain(&dev, AD7793_GAIN_4); */ bool ad7793_set_gain(ad7793_dev_t *dev, ad7793_gain_t gain); @@ -211,6 +266,7 @@ bool ad7793_set_gain(ad7793_dev_t *dev, ad7793_gain_t gain); * @return Operation status. True if successful, false otherwise. * @detail This function reads the current configuration register, clears the channel bits, and then sets the new channel according to the input. * It updates the current channel in the device structure and writes the new configuration to the configuration register. + * @note Example: ad7793_set_channel(&dev, AD7793_CHANNEL_1); */ bool ad7793_set_channel(ad7793_dev_t *dev, ad7793_channel_t channel); @@ -221,6 +277,7 @@ bool ad7793_set_channel(ad7793_dev_t *dev, ad7793_channel_t channel); * @return Operation status. True if successful, false otherwise. * @detail This function reads the current mode register, clears the rate bits, and then sets the new rate according to the input. * It updates the current update rate in the device structure and writes the new mode to the mode register. + * @note Example: ad7793_set_rate(&dev, AD7793_RATE_8_33HZ); */ bool ad7793_set_rate(ad7793_dev_t *dev, ad7793_rate_t rate); @@ -232,6 +289,7 @@ bool ad7793_set_rate(ad7793_dev_t *dev, ad7793_rate_t rate); * @return Operation status. True if successful, false otherwise. * @detail This function reads the current configuration register and sets the reference select bit according to the input. * It updates the reference source information in the device structure and writes the new configuration to the configuration register. + * @note Example: ad7793_set_reference(&dev, false, 2.5f); */ bool ad7793_set_reference(ad7793_dev_t *dev, bool use_internal, float external_ref); @@ -242,6 +300,7 @@ bool ad7793_set_reference(ad7793_dev_t *dev, bool use_internal, float external_r * @return true=ready, false=timeout. * @detail This function continuously checks the GPIO pin status until the device is ready or the timeout occurs. * It waits for 1 millisecond between each check. + * @note Example: ad7793_wait_ready(&dev, 1000); */ bool ad7793_wait_ready(ad7793_dev_t *dev, uint16_t timeout_ms); @@ -252,6 +311,7 @@ bool ad7793_wait_ready(ad7793_dev_t *dev, uint16_t timeout_ms); * @return Operation status. True if successful, false otherwise. * @detail This function reads the conversion data from the data register. * If the device is AD7793, it reads 3 bytes; if it is AD7792, it reads 2 bytes. + * @note Example: ad7793_read_data(&dev, &val); */ bool ad7793_read_data(ad7793_dev_t *dev, uint32_t *value); @@ -268,6 +328,7 @@ bool ad7793_read_data(ad7793_dev_t *dev, uint32_t *value); * Vref is the reference voltage. * N is the number of ADC bits (24 bits for AD7793). * G is the gain setting. + * @note Example: float v=ad7793_convert_to_voltage(&dev, code); */ float ad7793_convert_to_voltage(ad7793_dev_t *dev, uint32_t raw_data); @@ -276,45 +337,10 @@ float ad7793_convert_to_voltage(ad7793_dev_t *dev, uint32_t raw_data); * @param dev Pointer to the AD7793 device structure. * @param voltage Input voltage value (V). * @return Corresponding ADC code value. + * @note Example: uint32_t code=ad7793_convert_voltage_to_code(&dev, v); */ uint32_t ad7793_convert_voltage_to_code(ad7793_dev_t *dev, float voltage); -/** - * @brief Perform internal zero-scale calibration on the AD7793 device. - * @param dev Device structure pointer. - * @return Operation status. True if successful, false otherwise. - * @detail This function sets the device to internal zero-scale calibration mode, waits for 10 milliseconds for the calibration to complete, - * and then sets the device back to continuous conversion mode. - */ -bool ad7793_calibrate_internal_zero(ad7793_dev_t *dev); - -/** - * @brief Perform internal full-scale calibration on the AD7793 device. - * @param dev Device structure pointer. - * @return Operation status. True if successful, false otherwise. - * @detail This function sets the device to internal full-scale calibration mode, waits for 10 milliseconds for the calibration to complete, - * and then sets the device back to continuous conversion mode. - */ -bool ad7793_calibrate_internal_full(ad7793_dev_t *dev); - -/** - * @brief Perform system zero-scale calibration on the AD7793 device. - * @param dev Device structure pointer. - * @return Operation status. True if successful, false otherwise. - * @detail This function sets the device to system zero-scale calibration mode, waits for 10 milliseconds for the calibration to complete, - * and then sets the device back to continuous conversion mode. - */ -bool ad7793_calibrate_system_zero(ad7793_dev_t *dev); - -/** - * @brief Perform system full-scale calibration on the AD7793 device. - * @param dev Device structure pointer. - * @return Operation status. True if successful, false otherwise. - * @detail This function sets the device to system full-scale calibration mode, waits for 10 milliseconds for the calibration to complete, - * and then sets the device back to continuous conversion mode. - */ -bool ad7793_calibrate_system_full(ad7793_dev_t *dev); - /** * @brief Configure the excitation current source of the AD7793 device. * @param dev Device structure pointer. @@ -323,6 +349,7 @@ bool ad7793_calibrate_system_full(ad7793_dev_t *dev); * @return Operation status. True if successful, false otherwise. * @detail This function configures the excitation current source by setting the current value and direction in the IO register. * It then enables the current source and writes the configuration to the IO register. + * @note Example: ad7793_config_iexc(&dev, 210, 0); */ bool ad7793_config_iexc(ad7793_dev_t *dev, uint16_t current, uint8_t dir); @@ -338,6 +365,7 @@ bool ad7793_config_iexc(ad7793_dev_t *dev, uint16_t current, uint8_t dir); * - Bipolar mode (U/B=0): Converts -VREF/2 to +VREF/2 into 0x000000 to 0xFFFFFF * * Note: Changing this bit affects the interpretation of the ADC output code. + * @note Example: ad7793_set_unipolar(&dev, true); */ bool ad7793_set_unipolar(ad7793_dev_t *dev, bool unipolar); @@ -349,6 +377,7 @@ bool ad7793_set_unipolar(ad7793_dev_t *dev, bool unipolar); * @detail This function configures the input buffer mode of AD7793. * Enabling the buffer reduces input impedance requirements but increases power consumption. * Disabling the buffer is suitable for high-impedance sources but requires attention to input signal range limitations. + * @note Boundary: Returns false if G>2 and buffer is disabled. Example: ad7793_set_buffered(&dev, true); */ bool ad7793_set_buffered(ad7793_dev_t *dev, bool enable); @@ -357,11 +386,22 @@ bool ad7793_set_buffered(ad7793_dev_t *dev, bool enable); * @param dev Device structure pointer. * @param hw_if Hardware interface structure containing function pointers. * @param cs_pin Chip select pin. + * @param cfg Configuration struct * @return Operation status. True if successful, false otherwise. * @detail This function initializes the AD7793 device by saving the hardware interface and chip select information. * It resets the device, waits for the reset to complete, and then reads the ID register to confirm the device type. * Finally, it initializes the device with default configuration settings. + * @note Example: ad7793_init(&dev, &hw_if, cs_pin, &cfg); */ -bool ad7793_init(ad7793_dev_t *dev, ad7793_hw_if_t *hw_if, uint8_t cs_pin); +bool ad7793_init(ad7793_dev_t *dev, ad7793_hw_if_t *hw_if, uint8_t cs_pin, const ad7793_config_t *cfg); + +/** + * @brief Dump the contents of the AD7793 registers for debugging. + * @param dev Device structure pointer. + * @detail This function reads and prints the contents of the AD7793 registers. + * It can be used to verify the register settings and diagnose issues. + */ +void ad7793_dump_registers(ad7793_dev_t *dev); +void ad7793_dump_registers_mode(ad7793_dev_t *dev, int full); #endif // __AD7793_H__ diff --git a/User/user_config.h b/User/user_config.h index 17c00a2..6481547 100644 --- a/User/user_config.h +++ b/User/user_config.h @@ -2,7 +2,7 @@ * @Date: 2025-06-23 09:02:52 * @Author: mypx * @LastEditors: mypx mypx_coder@163.com - * @LastEditTime: 2025-09-25 16:12:45 + * @LastEditTime: 2025-10-27 14:18:40 * @FilePath: user_config.h * @Description: * Copyright (c) 2025 by mypx, All Rights Reserved. @@ -20,27 +20,26 @@ #define USING_SOFT_ENCODER 0 #define ENABLE_SV630P 1 -#define ENABLE_TEC 1 #define TEST_ENCODER_PLUSE 0 #define DEBUG_SERVO_ENABLE 0 #define DEBUG_MB_ENABLE 0 +#define DEBUG_LIGHT_DATA_ONLY 0 #define ENABLE_IIR_FILTER 0 -#define SAMPLING_RAW_DATA 0 #define DATA_PROCESS_UNIT_COUNT 1000 -#define MOTOR_TO_ENCODER_FACTOR 4 -#define MOTOR_TO_ENCODER_RATIO (20 * MOTOR_TO_ENCODER_FACTOR) //80 // 电机转速与编码器转速的比率 +#define MOTOR_TO_ENCODER_FACTOR 1 +#define MOTOR_TO_ENCODER_RATIO (80 * MOTOR_TO_ENCODER_FACTOR) //80 // 电机转速与编码器转速的比率 #define ENCODER_TO_POLARIZER_RATIO 180 #define MOTOR_TO_POLARIZER_RATIO (MOTOR_TO_ENCODER_RATIO * ENCODER_TO_POLARIZER_RATIO) // 电机转速与偏振片速的比率 #define CALIB_OSCI_ANGLE 180.0f -#define DC_OFFSET_ADC_VALUE 1990 +#define DC_OFFSET_ADC_VALUE 2043.48 //1990 // -#define ENABLE_CALIB_DOUBLE_DIR 1 +#define ENABLE_CALIB_DOUBLE_DIR 0 #define CALIB_OFFSET 0.0f //4.995f #define CALIB_ONE_DIR 1 // -1: 逆时针, 1: 顺时针 @@ -50,7 +49,9 @@ #define TREND_BUFFER_SIZE 10 +#define TEC_TEMPER_PRECISE 0.1f - +// speed设置为正数,编码器正向增加,则系数为1, 反之为-1 +#define ENCODER_MOTOR_DIR_FACTOR 1 // -1 or 1 #endif /* __USER_CONFIG_H__ */ diff --git a/cmake/stm32cubemx/CMakeLists.txt b/cmake/stm32cubemx/CMakeLists.txt index 76a54fa..8980731 100644 --- a/cmake/stm32cubemx/CMakeLists.txt +++ b/cmake/stm32cubemx/CMakeLists.txt @@ -116,6 +116,15 @@ add_library(stm32cubemx INTERFACE) target_include_directories(stm32cubemx INTERFACE ${MX_Include_Dirs}) target_compile_definitions(stm32cubemx INTERFACE ${MX_Defines_Syms}) +# Propagate suppression of a few warnings (for GCC) to all consumers of stm32cubemx +if (CMAKE_C_COMPILER_ID STREQUAL "GNU") + target_compile_options(stm32cubemx INTERFACE + "-Wno-pedantic" + "-Wno-cast-function-type" + "-Wno-implicit-fallthrough" + ) +endif() + # Create STM32_Drivers static library add_library(STM32_Drivers OBJECT) target_sources(STM32_Drivers PRIVATE ${STM32_Drivers_Src}) @@ -127,6 +136,15 @@ add_library(RT-Thread OBJECT) target_sources(RT-Thread PRIVATE ${RT-Thread_Src}) target_link_libraries(RT-Thread PUBLIC stm32cubemx) +# For GCC builds, silence a few third-party warnings coming from RT-Thread +if (CMAKE_C_COMPILER_ID STREQUAL "GNU") + target_compile_options(RT-Thread PRIVATE + "-Wno-pedantic" + "-Wno-cast-function-type" + "-Wno-implicit-fallthrough" + ) +endif() + # Add STM32CubeMX generated application sources to the project target_sources(${CMAKE_PROJECT_NAME} PRIVATE ${MX_Application_Src}) diff --git a/docs/hardware/Sheet4_new_version.pdf b/docs/hardware/pm_ver2.pdf similarity index 100% rename from docs/hardware/Sheet4_new_version.pdf rename to docs/hardware/pm_ver2.pdf diff --git a/docs/hardware/pm_ver3.pdf b/docs/hardware/pm_ver3.pdf new file mode 100644 index 0000000000000000000000000000000000000000..cfb96e340af824cd1e70e65248965d908b48e31f GIT binary patch literal 549273 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%0DV%DB2~Op}n4T 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
;uUtu@aW}4pj_YHr9i7Tay%BAQ2;0 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