STM32 HAL的一些致命错误处理和日志定位错误
·前言
其实STM32的HAL库是已经有致命错误处理的,只是如果要使用还得自己写操作,比如HAL一些初始化是会有错误处理的。比如这样的一句初始化
if (HAL_DMA_Init(&hdma_memtomem_dma2_stream7) != HAL_OK) { Error_Handler(); }
其实不难发现,HAL_DMA_Init这个函数的类型返回的是HAL_StatusTypeDef这个枚举的内容,具体如下:
/** * @brief HAL Status structures definition */ typedef enum { HAL_OK = 0x00U, HAL_ERROR = 0x01U, HAL_BUSY = 0x02U, HAL_TIMEOUT = 0x03U } HAL_StatusTypeDef;
这些其实就是函数返回的状态,通过这些状态我们就可以清晰的知道某个函数功能运行是否成功什么的了。
然后就是错误处理函数了Error_Handler,我们可以去到函数里面看,如下:
/** * @brief This function is executed in case of error occurrence. * @retval None */ void Error_Handler(void) { /* USER CODE BEGIN Error_Handler_Debug */ /* User can add his own implementation to report the HAL error return state */ __disable_irq(); while (1) { } /* USER CODE END Error_Handler_Debug */ }
这是cubemx自动生成的函数,可以看到这个函数其实就是关闭了所有的中断,再进入死循环,避免出现了致命错误后继续往下运行。
其实,通过参考HAL的Error_Handler函数,我知道一旦函数状态返回不正常值就会卡住,但是这种情况只能是通过仿真才能发现具体卡在哪里。那么能不能发生了致命错误后需要停止运行并且也要知道在哪里停止运行的。
是可以的,我个人想到了一种做法,就是在发生致命错误后,使用串口打印C语言的宏定义__FILE__和__func__来进行错误位置的定位,这应该是比较简单的做法了。
·具体实现
我直接放.h和.c文件代码再说两句
<.h>
/* USER CODE BEGIN Header */ /* USER CODE END Header */ /* Define to prevent recursive inclusion -------------------------------------*/ #ifndef __LOG_H #define __LOG_H #ifdef __cplusplus extern "C" { #endif /* Includes ------------------------------------------------------------------*/ /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ /* USER CODE END Includes */ /* Exported types ------------------------------------------------------------*/ /* USER CODE BEGIN ET */ // 定义互斥锁 // 定义日志级别枚举 typedef enum { MY_LOG_NONE = 0, MY_LOG_ERROR, MY_LOG_WARN, MY_LOG_INFO, MY_LOG_DEBUG, MY_LOG_VERBOSE } my_log_level_t; /* USER CODE END ET */ /* Exported constants --------------------------------------------------------*/ /* USER CODE BEGIN EC */ /* USER CODE END EC */ /* Exported macro ------------------------------------------------------------*/ /* USER CODE BEGIN EM */ /* USER CODE END EM */ /* Exported functions prototypes ---------------------------------------------*/ void my_log_printf(const char *tag, my_log_level_t level, const char *format, ...); void error_handler_msg_log(char *file_msg, const char *func_msg); void error_handler(void); /* USER CODE BEGIN EFP */ /* USER CODE END EFP */ /* Private defines -----------------------------------------------------------*/ /* USER CODE BEGIN Private defines */ /* USER CODE END Private defines */ #ifdef __cplusplus } #endif #define MY_LOGI(tag, format, ...) my_log_printf(tag, MY_LOG_INFO, format, ##__VA_ARGS__) #endif /* __LOG_H */
<.c>
#include "log.h" #include "stm32f4xx_hal.h" #include <stdio.h> #include "FreeRTOS.h" #include "task.h" #include "semphr.h" #include "stdarg.h" #include "mutex.h" /** * @brief my_log_printf * @note 自定义的日志输出函数 * @param tag * @param level * @param format * @param ... */ void my_log_printf(const char *tag, my_log_level_t level, const char *format, ...) { // 获取互斥锁 xSemaphoreTake(logMutex, portMAX_DELAY); // 输出日志级别和标签 printf("[%s]", tag); switch (level) { case MY_LOG_ERROR: printf("[E]"); break; case MY_LOG_WARN: printf("[W]"); break; case MY_LOG_INFO: printf("[I]"); break; case MY_LOG_DEBUG: printf("[D]"); break; case MY_LOG_VERBOSE: printf("[V]"); break; default: break; } // 输出格式化字符串 va_list args; va_start(args, format); vprintf(format, args); va_end(args); // 释放互斥锁 xSemaphoreGive(logMutex); } /** * @brief error_handler_msg_log * * @param msg error message */ void error_handler_msg_log(char *file_msg, const char *func_msg) { /* Start peintf error message, don't use rtos_log */ printf("Error file : %s\r\n", file_msg); printf("Error function : %s\r\n", func_msg); } /** * @brief This function is executed in case of error occurrence. * @retval None */ void error_handler(void) { /* USER CODE BEGIN Error_Handler_Debug */ /* User can add his own implementation to report the HAL error return state */ __disable_irq(); while (1) { NVIC_SystemReset(); /* system start reset */ } /* USER CODE END Error_Handler_Debug */ }
这里面的打印可以不用管,因为那只是带互斥锁的串口打印而已。
使用方法:
/* 用法我就简单举个例子 */ void main(void) { if (HAL_ADC_Start_DMA(&hadc1, (uint32_t *)&ADC1_Value_DMA, 2) != HAL_OK) { /* error handler begin */ error_handler_msg_log(__FILE__, __func__); error_handler(); /* error handler end */ } else { MY_LOGI(TAG, "ADC_DMA_Init OK!\r\n"); } while(1); }
这个代码挺简单的,会C语言的都懂。
具体实现是这样的:如果HAL_ADC_Start_DMA返回的不是HAL_OK,那么就运行error_handler_msg_log和error_handler,前者的__FILE__和__func__传入的是当前.c文件路径和当前函数名的字符串。后者内容就是关闭全部中断再进行复位来避免程序继续运行。
好,也就这么点东西而已,实操看看。
/*该文件名为<adc_convert.c>*/
/** * @brief filter * * @param filter_data * @return HAL_StatusTypeDef */ static HAL_StatusTypeDef filter(uint32_t *filter_data) { if (*filter_data == 0) { return HAL_ERROR; } else { /* filter proc begin */ MY_LOGI(TAG, "filter_data:%d\r\n", *filter_data); *filter_data = *filter_data / 2; /* filter proc end */ return HAL_OK; } } /** * @brief convert * * @param convert_data */ void convert(ADC_CONVERT *convert_data) { if (filter(&convert_data->adc_data[0]) != HAL_OK) { /* error handle proc begin */ error_handler_msg_log(__FILE__, __func__); error_handler(); /* error handle proc end */ } MY_LOGI(TAG, "convert success.%d\r\n", convert_data->adc_data[0]); }
/* 最后在主函数调用convert函数 */
这里我给的初始值是6666,每次运行filter如果输入不为0就除以2,
除到最后就是0,如果是0就返回HAL_ERROR,那就运行
error_handler_msg_log(__FILE__, __func__);
error_handler();
运行结果如下:
打印结果就是错误的.c文件路径和函数名。这样就实现了检错运行了,并且更方便错误的定位。
【以上仅是个人思路,并不一定是最好的,欢迎留言交流】