#include "debug_tools.h" #if !defined(GLOBAL_Q) #define GLOBAL_Q 16 #endif DebugLowLevel_t debug_ll = DEBUG_LOWLEVEL_INIT; ///< Структура отладки нижнего уровня (инициализация) static int getDebugVar(DebugVar_t *var, int32_t *int_var, float *float_var); static int convertDebugVarToIQx(DebugVar_t *var, int32_t *ret_var); static int iqTypeToQ(DebugVarIQType_t t); static int is_addr_in_allowed_ranges(uint32_t addr_val, const AddrRange_t *ranges, int count); /** * @brief Массив допустимых диапазонов адресов для отладочного чтения * * Включает в себя набор диапазонов памяти, разрешённых для доступа * функцией Debug_LowLevel_ReadVar. */ static const AddrRange_t debug_allowed_ranges[] = ALLOWED_ADDRESS_RANGES; /** * @brief Количество элементов в массиве debug_allowed_ranges */ static const int debug_allowed_ranges_count = sizeof(debug_allowed_ranges) / sizeof(debug_allowed_ranges[0]); ///////////////////////////----EXAPLE-----////////////////////////////// int var_numb = 1; ///< Пример переменной для отладки DebugVarName_t var_name; ///< Имя переменной int32_t return_var; ///< Переменная для возврата результата int32_t return_ll_var; ///< Возвращаемое значение с нижнего уровня int result; ///< Переменная результата DateTime_t ext_date = {2025, 11, 07, 16, 50}; ///< Пример внешней даты сборки /** * @brief Пример использования функций отладки. * @details Демонстрационная функция для работы с переменными отладки. */ void Debug_Test_Example(void) { return; result = Debug_ReadVar(var_numb, &return_var); result = Debug_ReadVarName(var_numb, var_name, 0); if(Debug_LowLevel_Initialize(&ext_date) == 0) result = Debug_LowLevel_ReadVar(&return_ll_var); } ///////////////////////////----PUBLIC-----////////////////////////////// /** * @brief Читает переменную по индексу. * @param var_ind – индекс переменной. * @param return_32b – указатель для возврата результата. * @return int – 0: успех, 1: ошибка. * @details Используется для чтения значений переменных по их индексу. */ int Debug_ReadVar(int var_ind, int32_t *return_32b) { int32_t tmp_var; if(return_32b == NULL) return DEBUG_ERR_INTERNAL; if (var_ind >= DebugVar_Qnt) return DEBUG_ERR_VAR_NUMB; if((dbg_vars[var_ind].ptr_type == pt_struct) || (dbg_vars[var_ind].ptr_type == pt_union) || (dbg_vars[var_ind].ptr_type == pt_unknown)) return DEBUG_ERR_INVALID_VAR; return convertDebugVarToIQx(&dbg_vars[var_ind], return_32b); } /** * @brief Читает возвращаемый тип (IQ) переменной по индексу. * @param var_ind – индекс переменной. * @param vartype – указатель для возврата типа. * @return int – 0: успех, 1: ошибка. * @details Используется для чтения возвращаемого типа (IQ) переменных по их индексу. */ int Debug_ReadVarReturnType(int var_ind, int *vartype) { int rettype; if(vartype == NULL) return DEBUG_ERR_INTERNAL; if (var_ind >= DebugVar_Qnt) return DEBUG_ERR_VAR_NUMB; if((dbg_vars[var_ind].ptr_type == pt_struct) || (dbg_vars[var_ind].ptr_type == pt_union) || (dbg_vars[var_ind].ptr_type == pt_unknown)) return DEBUG_ERR_INVALID_VAR; *vartype = iqTypeToQ(dbg_vars[var_ind].return_type); return 0; } /** * @brief Читает тип переменной по индексу. * @param var_ind – индекс переменной. * @param vartype – указатель для возврата типа. * @return int – 0: успех, 1: ошибка. * @details Используется для чтения типа переменных по их индексу. */ int Debug_ReadVarType(int var_ind, int *vartype) { int rettype; if(vartype == NULL) return DEBUG_ERR_INTERNAL; if (var_ind >= DebugVar_Qnt) return DEBUG_ERR_VAR_NUMB; if((dbg_vars[var_ind].ptr_type == pt_struct) || (dbg_vars[var_ind].ptr_type == pt_union) || (dbg_vars[var_ind].ptr_type == pt_unknown)) return DEBUG_ERR_INVALID_VAR; *vartype = dbg_vars[var_ind].ptr_type; switch(dbg_vars[var_ind].ptr_type) { case pt_int8: case pt_int16: case pt_int32: case pt_float: *vartype = dbg_vars[var_ind].ptr_type | DEBUG_SIGNED_VAR; break; default: *vartype = dbg_vars[var_ind].ptr_type; break; } return 0; } /** * @brief Читает имя переменной по индексу. * @param var_ind – индекс переменной. * @param name_ptr – указатель на буфер имени (DebugVarName_t). * @return int – 0: успех, 1: ошибка. * @details Копирует имя переменной в предоставленный буфер. */ int Debug_ReadVarName(int var_ind, DebugVarName_t name_ptr, int *length) { int i; if(name_ptr == NULL) return DEBUG_ERR_INTERNAL; if (var_ind >= DebugVar_Qnt) return DEBUG_ERR_VAR_NUMB; // Копирование с защитой от переполнения и явной остановкой по '\0' for (i = 0; i < sizeof(dbg_vars[var_ind].name); i++) { name_ptr[i] = dbg_vars[var_ind].name[i]; if (dbg_vars[var_ind].name[i] == '\0') { if(length != NULL) *length = i; break; } } // Гарантированное завершение строки (на случай, если в var->name не было '\0') name_ptr[sizeof(dbg_vars[var_ind].name) - 1] = '\0'; return 0; } /** * @brief Читает значение переменной отладки с нижнего уровня. * @param return_32b – указатель на переменную, куда записывается результат. * @return int – 0: успех, 1: ошибка, 2: недопустимый адрес. * @details Использует адресс, передаваемый с терминалки для получения значения. */ int Debug_LowLevel_ReadVar(int32_t *return_32b) { uint8_t *addr = debug_ll.dbg_var.Ptr; uint32_t addr_val = (uint32_t)addr; if (return_32b == NULL) return DEBUG_ERR_INTERNAL; if (debug_ll.isVerified == 0) return DEBUG_ERR_DATATIME; if (is_addr_in_allowed_ranges(addr_val, debug_allowed_ranges, debug_allowed_ranges_count) != 0) { return DEBUG_ERR_ADDR; // Запрещённый адрес — нельзя читать } return convertDebugVarToIQx(&debug_ll.dbg_var, return_32b); } /** * @brief Инициализация отладки на нижнем уровне по дате сборки. * @param external_date – структура с датой DateTime_t * @return int – 0: совпадает, 1: не совпадает, -1: ошибка. * @details Сравнивает дату компиляции с запрашиваемой и инициализирует отладочную переменную. */ int Debug_LowLevel_Initialize(DateTime_t* external_date) { if (external_date == NULL) { return DEBUG_ERR_INTERNAL; } // Сравнение всех полей if (external_date->year == debug_ll.build_date.year && external_date->month == debug_ll.build_date.month && external_date->day == debug_ll.build_date.day && external_date->hour == debug_ll.build_date.hour && external_date->minute == debug_ll.build_date.minute) { debug_ll.isVerified = 1; return 0; // Совпало } debug_ll.isVerified = 0; return DEBUG_ERR_DATATIME; // Не совпало } /** * @brief Читает возвращаемый тип (IQ) низкоуровнено заданной переменной. * @param var_ind – индекс переменной. * @param vartype – указатель для возврата типа. * @return int – 0: успех, 1: ошибка. * @details Используется для чтения возвращаемого типа (IQ) переменных по их индексу. */ int Debug_LowLevel_ReadVarReturnType(int *vartype) { int rettype; if(vartype == NULL) return DEBUG_ERR_INTERNAL; if((debug_ll.dbg_var.ptr_type == pt_struct) || (debug_ll.dbg_var.ptr_type == pt_union) || (debug_ll.dbg_var.ptr_type == pt_unknown)) return DEBUG_ERR_INVALID_VAR; *vartype = iqTypeToQ(debug_ll.dbg_var.return_type); return 0; } /** * @brief Читает тип низкоуровнено заданной переменной. * @param var_ind – индекс переменной. * @param vartype – указатель для возврата типа. * @return int – 0: успех, 1: ошибка. */ int Debug_LowLevel_ReadVarType(int *vartype) { int rettype; if(vartype == NULL) return DEBUG_ERR_INTERNAL; if((debug_ll.dbg_var.ptr_type == pt_struct) || (debug_ll.dbg_var.ptr_type == pt_union) || (debug_ll.dbg_var.ptr_type == pt_unknown)) return DEBUG_ERR_INVALID_VAR; *vartype = debug_ll.dbg_var.ptr_type; switch(debug_ll.dbg_var.ptr_type) { case pt_int8: case pt_int16: case pt_int32: case pt_float: *vartype = debug_ll.dbg_var.ptr_type | DEBUG_SIGNED_VAR; break; default: *vartype = debug_ll.dbg_var.ptr_type; break; } return 0; } /////////////////////----INTERNAL FUNCTIONS-----//////////////////////// /** * @brief Преобразует тип IQ переменной в число битов для сдвига(Q-фактор). * @param t – тип IQ (перечисление DebugVarIQType_t). * @return int – Q-фактор (например, 24), 0: если t_iq_none, -1: ошибка. * @details Сопоставляет тип IQ переменной с соответствующим Q-значением. */ static int iqTypeToQ(DebugVarIQType_t t) { if (t == t_iq_none) return 0; // без IQ, float, int else if (t == t_iq) return GLOBAL_Q; // общий IQ, например 24 else if (t >= t_iq1 && t <= t_iq30) return (int)t - (int)t_iq1 + 1; // например t_iq1 -> 1, t_iq2 -> 2 и т.д. else return 0; // ошибка } /** * @brief Преобразует переменную отладки в IQ формат. * @param var – указатель на переменную отладки. * @param ret_var – указатель для возврата значения в формате 32 бита. * @return int – 0: успех, 1: ошибка чтения, 2: неправильный формат, 3: переполнение. * @details Определяет формат IQ переменной, конвертирует её в 32b с учётом масштаба. */ static int convertDebugVarToIQx(DebugVar_t *var, int32_t *ret_var) { int32_t iq_numb, iq_united, iq_final; int64_t iq_united64 = 0; int64_t iq_final64 = 0; int status; float float_numb; status = getDebugVar(var, &iq_numb, &float_numb); if(status != 0) return status; int src_q = iqTypeToQ(var->iq_type); int dst_q = iqTypeToQ(var->return_type); // Конвертация к GLOBAL_Q (64-бит) if (var->iq_type == t_iq_none) { if (var->ptr_type == pt_float) { // float_numb умножаем на 2^GLOBAL_Q // Результат приводим к 64 бита iq_united64 = (int64_t)(float_numb * ((uint32_t)1 << GLOBAL_Q)); } else { iq_united64 = ((int64_t)iq_numb) << GLOBAL_Q; } } else { int shift = GLOBAL_Q - src_q; if (shift >= 0) iq_united64 = ((int64_t)iq_numb) << shift; else iq_united64 = ((int64_t)iq_numb) >> (-shift); } // Конвертация из GLOBAL_Q в целевой IQ (64-бит) if (var->return_type == t_iq_none) { // Возвращаем целое, отбросив дробную часть *ret_var = (uint32_t)(iq_united64 >> GLOBAL_Q); } else { int shift = dst_q - GLOBAL_Q; if (shift >= 0) iq_final64 = iq_united64 << shift; else iq_final64 = iq_united64 >> (-shift); *ret_var = (int32_t)iq_final64; } return 0; } /** * @brief Прочитать значение переменной отладки. * @param var – указатель на структуру DebugVar. * @param int_var – указатель на переменную типа 32 бита для возврата целочисленного значения. * @param float_var – указатель на переменную типа float для возврата значения с плавающей точкой. * @return int – 0: успех, 1: ошибка указателей или неподдерживаемый тип, 3/4: ошибка выравнивания. * @details В зависимости от типа переменной считывает её значение и сохраняет в соответствующем указателе. */ static int getDebugVar(DebugVar_t *var, int32_t *int_var, float *float_var) { uint8_t *addr = var->Ptr; uint32_t addr_val = (uint32_t)addr; if (!var || !int_var || !float_var || !var->Ptr) return DEBUG_ERR_INTERNAL; // ошибка: null указатель switch (var->ptr_type) { case pt_int8: // 8 бит if ((addr_val & ALIGN_8BIT) != 0) // проверяем выравнивание return DEBUG_ERR_ADDR_ALIGN; // ошибка выравнивания *int_var = *((volatile int8_t *)addr); break; case pt_uint8: if ((addr_val & ALIGN_8BIT) != 0) // проверяем выравнивание return DEBUG_ERR_ADDR_ALIGN; // ошибка выравнивания *int_var = *((volatile uint8_t *)addr); break; case pt_int16: // 16 бит (int) if ((addr_val & ALIGN_16BIT) != 0) // проверяем выравнивание return DEBUG_ERR_ADDR_ALIGN; // ошибка выравнивания *int_var = *((volatile int16_t *)addr); break; case pt_uint16: if ((addr_val & ALIGN_16BIT) != 0) // проверяем выравнивание return DEBUG_ERR_ADDR_ALIGN; // ошибка выравнивания *int_var = *((volatile uint16_t *)addr); break; case pt_int32: // 32 бит if ((addr_val & ALIGN_32BIT) != 0) // проверяем выравнивание return DEBUG_ERR_ADDR_ALIGN; // ошибка выравнивания *int_var = *((volatile int32_t *)addr); break; case pt_uint32: if ((addr_val & ALIGN_32BIT) != 0) // проверяем выравнивание return DEBUG_ERR_ADDR_ALIGN; // ошибка выравнивания *int_var = *((volatile uint32_t *)addr); break; case pt_float: // float (4 байта) if ((addr_val & ALIGN_FLOAT) != 0) // проверка выравнивания return DEBUG_ERR_ADDR_ALIGN; // ошибка выравнивания *float_var = *((volatile float *)addr); break; default: return DEBUG_ERR_INVALID_VAR; // неподдерживаемый тип // для указателей и массивов не поддерживается чтение // case pt_ptr_int8: // case pt_ptr_int16: // case pt_ptr_int32: // case pt_ptr_uint8: // case pt_ptr_uint16: // case pt_ptr_uint32: // case pt_arr_int8: // case pt_arr_int16: // case pt_arr_int32: // case pt_arr_uint8: // case pt_arr_uint16: // case pt_arr_uint32: } return 0; // успех } /** * @brief Проверяет, входит ли адрес в один из допустимых диапазонов * * @param addr_val - Значение адреса для проверки * @param ranges - Указатель на массив диапазонов AddrRange_t * @param count - Количество диапазонов в массиве * @return 0 если адрес находится в одном из диапазонов, иначе 1 */ static int is_addr_in_allowed_ranges(uint32_t addr_val, const AddrRange_t *ranges, int count) { int i; for (i = 0; i < count; i++) { if (addr_val >= ranges[i].start && addr_val <= ranges[i].end) { return 0; } } return 1; }