Assert param stm32 что это
Перейти к содержимому

Assert param stm32 что это

  • автор:

HAL stm32

Итак, HAL позволяет абстрагироваться от работы с регистрами и прочей сложной магии. Грубо говоря, HAL это обёртка над низкоуровневыми операциями. Конечно же это не отменяет необходимости понимания устройства микроконтроллеров, но значительно снижает уровень вхождения.

Например, чтоб запустить таймер, достаточно перед бесконечным циклом прописать вот такую функцию…

HAL_TIM_Base_Start_IT(&htim1);

То есть нам не нужно знать какие регистры отвечают за это, и что в них записывать. Более того, эта функция будет работать на любых микроконтроллерах серии stm32.

Сама функция выглядит так:

Вначале происходит проверка параметров на ошибки (assert_param), и после этого активируется прерывание и запускается таймер.

Строчки начинающиеся с __двойного подчеркивания , это макросы, с помощью которых можно устанавливать/снимать необходимые биты в регистрах. Как и в случае с самой функцией, макросы будут одинаковы для всей линейки микроконтроллеров.

Однако я немного забежал вперёд. Прежде чем изучать HAL, нужно познакомиться с программой CubeMX (в просторечии «Куб») так как HAL является неотъемлемой частью «Куба», и именно в нём генерится весь начальный код будущего приложения включая описанные выше функции. Подробно про CubeMX читайте здесь.

Познакомились — тогда продолжим…

Воспользуемся примером, который сделан по ссылке выше, и рассмотрим подробно всю программу.

Итак мы сгенерировали проект, в котором есть таймер вызывающий прерывание при переполнении, и GPIO. Открываем этот проект в среде разработки (у меня TrueStudio) и в левой панели клацаем файл main.c…

Куб создал все необходимые функции инициализации…

void SystemClock_Config(void) — инициализация тактирования.
static void MX_TIM1_Init(void) — инициализация таймера.
static void MX_GPIO_Init(void) — инициализация GPIO.

… и избавил нас от возни с настройками, и от возможных ошибок.

Все функции типичны — параметры записываются в структуры, и адреса этих структур передаются в соответствующие HAL-функции. Каждая функция возвращает статус. Если возвращается ошибка, то вызывается функция Error_Handler() находящаяся в самом низу. В эту функцию хорошо бы прописать что-то, что сигнализировало бы об ошибке, например мигнуть лампочкой.

void Error_Handler(void) < /* USER CODE BEGIN Error_Handler_Debug */ /* User can add his own implementation to report the HAL error return state */ // ШЕФ ВСЁ ПРОПАЛО /* USER CODE END Error_Handler_Debug */ >

Ниже есть ещё одна функция проверок на ошибки — void assert_failed(uint8_t *file, uint32_t line) . Если макрос assert_param (упомянутый в начале статьи) возвращает ошибку, то в эту функцию прилетает имя файла в котором произошла ошибка, и номер строки.

Функция работает при условии, что задефайнен USE_FULL_ASSERT . Сам по себе этот дефаин находится в файле stm32f1xx_hal_conf.h , но он закомментирован…

посмотреть

/* ########################## Assert Selection ############################## */ /** * @brief Uncomment the line below to expanse the "assert_param" macro in the * HAL drivers code */ /* #define USE_FULL_ASSERT 1U */ 

В конце файла обрисован механизм передачи assert_param() в void assert_failed()…

/* Exported macro ------------------------------------------------------------*/ #ifdef USE_FULL_ASSERT /** * @brief The assert_param macro is used for function's parameters check. * @param expr: If expr is false, it calls assert_failed function * which reports the name of the source file and the source * line number of the call that failed. * If expr is true, it returns no value. * @retval None */ #define assert_param(expr) ((expr) ? (void)0U : assert_failed((uint8_t *)__FILE__, __LINE__)) /* Exported functions ------------------------------------------------------- */ void assert_failed(uint8_t* file, uint32_t line); #else #define assert_param(expr) ((void)0U) #endif /* USE_FULL_ASSERT */

Если хотите чтоб он раскомментировался, то надо в Кубе сделать так…

Enable Full Assert. Эти ассерты занимают определённое количество памяти, поэтому их лучше использовать только для отладки, а в релизе отключать.

В общем с проверками на ошибки у HAL’а всё очень удобно и информативно.

Теперь давайте рассмотрим процесс инициализации на примере таймера.

В функции static void MX_TIM1_Init(void) , в объявленную глобально структуру htim1 заносятся различные параметры таймера, после чего эта структура передаётся в функцию HAL_TIM_Base_Init(&htim1) .

Теперь клацните функцию if (HAL_TIM_Base_Init(&htim1) != HAL_OK) левой кнопкой, а потом правой — вылезет контекстное меню, в котором нужно выбрать Open Declaration . Откроется файл stm32f1xx_hal_tim.c …

Здесь происходит следующее:

Проверяется не пустой ли указатель структуры (htim == NULL) и заполнены ли все элементы структуры (assert_param).

Проверяется статус таймера (htim->State == HAL_TIM_STATE_RESET). В данном случае статус HAL_TIM_STATE_RESET говорит о том, что устройство еще не инициализировано или отключено.

посмотреть

Заголовочный файл stm32f1xx_hal_tim.h .

Если статус удовлетворяет, то снимается блокировка (htim->Lock = HAL_UNLOCKED) и вызывается функция HAL_TIM_Base_MspInit(htim) …

посмотреть

Здесь проверяется какой именно таймер настраивается (htim_base->Instance==TIM1) и вызываются функции которые включают тактирование таймера, активирует прерывание и настраивают приоритет.

Далее устанавливается статус «занято» (htim->State= HAL_TIM_STATE_BUSY) — если по каким-то причинам, параллельно будет вызвана ещё одна функция инициализации таймера, то она не сможет ничего испортить.

После этого вызывается функция TIM_Base_SetConfig(htim->Instance, &htim->Init) (у этой функции нет приставки HAL, поэтому можно назвать её низкоуровневой) работающая напрямую с регистрами…

посмотреть

Файл stm32f1xx_hal_tim.c

Ну и наконец устанавливается статус «готов к труду и обороне» (htim->State= HAL_TIM_STATE_READY) и возвращается — return HAL_OK;

Функции связанные с таймером находятся либо в том же файле (stm32f1xx_hal_tim.c), либо в stm32f1xx_hal_tim_ex.c .

Все функции имеют характерные названия определяющие их назначение…

Окончание _IT означает, что устройство будет вызывать прерывание. Это относится к любым функциям используемым в HAL.

Например запуск таймера без прерываний выглядит так:

HAL_TIM_Base_Start(&htim1);

Как вы уже наверно поняли, одна из особенностей библиотеки HAL это большое количество проверок защищающих разработчиков от массы неприятностей. Кто-то расценит это как добродетель, а кто-то решит что такое количество проверок избыточно. Однако не стоит занимать какую-либо позицию на этот счёт так как в процессе изучения вы поймёте где нужно оставлять эти проверки, а где их можно удалить. То же самое касается и всего остального, со временем вы будете отказываться от каких-то функций HAL и заменять их работой с регистрами.

При работе с любой другой периферией, все необходимые функции вы найдёте в соответствующих файлах…

Названия файлов говорят сами за себя.

Функция запуска таймера…

/* USER CODE BEGIN 2 */ HAL_TIM_Base_Start_IT(&htim1); /* USER CODE END 2 */

… сама по себе не особо интересна.

посмотреть

Функция устанавливает бит разрешающий прерывания по переполнению — __HAL_TIM_ENABLE_IT(htim, TIM_IT_UPDATE) и бит активации таймера — __HAL_TIM_ENABLE(htim).

А вот механизм вызова прерывания поможет понять устройство библиотеки HAL. Разберём его…

Когда мы в Кубе активируем прерывание от какой-либо периферии, то в файле stm32f1xx_it.c автоматически создаётся обработчик с соответствующим именем…

Сюда программа переходит как только сработает прерывание от любого из событий таймера №1.

Этот обработчик (условно назовём его низкоуровневым) вызывает HAL-обработчик HAL_TIM_IRQHandler(&htim1) находящийся в файле stm32f1xx_hal_tim.c . HAL-обработчик состоит из нескольких блоков, каждый из которых отвечает за определённое событие — захват/сравнение, переполнение, триггерный сигнал и т.д…

события

Программа войдя в функцию HAL_TIM_IRQHandler проверяет какой из флагов был установлен и найдя нужный блок выполняет его содержимое.

Нас интересует блок TIM Update event…

про макросы

Библиотека HAL под завязку напичкана различными макросами. Как уже говорилось в начале статьи, они начинаются с __двойного подчёркивания и имеют характерные имена определяющие их назначение. Эти макросы очёнь клёвая штука, они позволяют оперировать различными битами в различных регистрах без необходимости копаться в даташитах.

_GET_ — читать биты, _SET_ — устанавливать биты, _CLEAR_ — очищать биты, и т.д. Посмотреть макросы можно в хедерах соответствующей периферии, например, всё что связано с таймерами находится в файле stm32f1xx_hal_tim.h .

Внутри макроса __HAL_TIM_CLEAR_IT(htim, TIM_IT_UPDATE) содержится вот такая конструкция…

#define __HAL_TIM_CLEAR_IT(__HANDLE__, __INTERRUPT__) ((__HANDLE__)->Instance->SR = ~(__INTERRUPT__))

Этот макрос сбрасывает бит (указанный вторым аргументом) в регистре состояния (Status Register).

В первый аргумент подставляется указатель на структуру таймера, а вторым аргументом идёт дефаин флага который взводится при возникновении прерывания…

#define TIM_IT_UPDATE (TIM_DIER_UIE)

Написав программу на HAL вы можете проследить где-какие макросы/функции вызываются, и работать с регистрами напрямую. То есть HAL можно с лёгкостью использовать как пособие для изучения низкоуровневых операций.

Если установлен флаг переполнения (TIM_FLAG_UPDATE) и источником является прерывание по переполнению (TIM_IT_UPDATE), тогда флаг сбрасывается и вызывается колбек — HAL_TIM_PeriodElapsedCallback(htim) .

Колбек это характерная фишка HAL’а. В колбеках выполняются действия которые нужно сделать при возникновении события/прерывания, в нашем случае мы будем мигать лампочкой.

заметка

В принципе нам ничто не мешает мигать лампочкой прямо в обработчике, да ещё и оперировать регистрами напрямую (немного хардкора)

В этом примере делается то же самое, что делает HAL — сбрасывается флаг прерывания и вместо вызова колбека сразу же выполняется действие (мигание светиком).

Таким образом количество кода сократилось бы в разы, но не было бы никаких проверок, и в любом случае нужно было бы самостоятельно писать проверку — какое именно событие случилось (если бы их было несколько). Тем не менее, в дальнейшем, когда хорошенько прокачаетесь, будете потихонечку переходить на работу с регистрами.

Если вы внимательно посмотрите, то увидите что у каждого события есть свой колбек. Например у захвата/сравнения их несколько…

посмотреть

HAL_TIM_IC_CaptureCallback, HAL_TIM_OC_DelayElapsedCallback и HAL_TIM_PWM_PulseFinishedCallback.

Все эти колбеки прописаны в том же файле, с атрибутом __weak .

Атрибут __weak говорит компилятору, что эта функция может быть переопределена. То есть если такую же функцию, но без weak, прописать ещё где-то, то функция с weak будет игнорироваться.

Находим нужный нам колбек…

… и переопределяем его в файл main.c

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) < if(htim->Instance == TIM1) //check if the interrupt comes from TIM1 < HAL_GPIO_TogglePin(led13_GPIO_Port, led13_Pin); //Toggle the state of pin >>

Проверяем что прерывание пришло от таймера №1 и мигаем светиком.

Проверять от какого таймера пришло прерывание нужно в том случае, если используется несколько таймеров. Тут дело вот в чём: если мы настроим ещё один таймер, например №2, и он тоже будет вызывать прерывания, тогда в файле stm32f1xx_it.c появится второй обработчик…

Не смотря на то, что обработчиков два, функция HAL_TIM_IRQHandler() одна и та же. Соответственно и колбек будет вызываться один и тот же. Поэтому для двух таймеров нужно делать так…

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) < if(htim->Instance == TIM1) < HAL_GPIO_TogglePin(led13_GPIO_Port, led13_Pin); >if(htim->Instance == TIM2) < HAL_GPIO_TogglePin(led13_GPIO_Port, led13_Pin); >>

Это касается не только таймеров, но и прочей периферии — USART, SPI, I2C и т.д.

Программирование всего остального выглядит примерно так же как и таймера. Открываем соответствующий файл, например stm32f1xx_hal_uart.c , если работаем с USART’ом, находим там нужные функции, а в файле stm32f1xx_hal_uart.h макросы. Читаем комментарии (все функции и макросы прокомментированы) и пишем код…

Рассмотрим работу USART’а с DMA, там механизм несколько сложнее чем с таймером. В Кубе настройте USART с использованием DMA на приём…

Инициализация USART’а точно такая же как и у таймера…

Параметры загружаются в структуру и передаются в функцию.

Команда запуска опять же схожа с таймером (передаётся структура + доп. аргументы)

HAL_UART_Receive_DMA(&huart1, (uint8_t*)rx_buff, 10);

Первый аргумент это адрес структуры, второй — это адрес массива в который будут складываться полученные данные, третий — это количество байт которые нужно принять.

Здесь у нас много чего интересненького.

В первую очередь происходит проверка — занят USART или нет (HAL_UART_STATE_READY).

Если до этого функция уже запускалась и данные ещё не получены, то эта проверка не пройдёт и функция вернёт статус «занято» (return HAL_BUSY). Если же необходимо перезапустить функцию, то предварительно надо вызвать — HAL_UART_AbortReceive(&huart1). Как видите названия функций говорят сами за себя.

Далее проверяется не пустой ли указатель на приёмный буфер, и чтоб размер данных был не нулевой. Устанавливается блокировка (__HAL_LOCK) и начинается заполнение структуры huart . Первые четыре пункта вопросов не вызывают, а дальше в соответствующие элементы структуры записываются указатели на функции (в языке СИ имя функции без скобок является указателем на эту функцию) содержащие колбеки…

посмотреть

Здесь помимо проверки и нового вида макроса (CLEAR_BIT) мы наконец-то видим колбек — HAL_UART_RxCpltCallback(huart) , который и нужно прописывать в main.c . Этот колбек вызывается когда буфер будет заполнен полностью.

Прерывание может вызываться при заполнении половины буфера. За это отвечает huart->hdmarx->XferHalfCpltCallback = UART_DMARxHalfCplt .

посмотреть

Для ошибки тоже есть функция с колбеком — huart->hdmarx->XferErrorCallback = UART_DMAError .

посмотреть

Следом идёт запуск DMA — HAL_DMA_Start_IT() …

посмотреть

В функцию передаётся: указатель на структуру, источник данных (в нашем случае это регистр данных (DR) USART’а), получатель данных (адрес буфера), и ожидаемое кол-во байт.

Потом всё это хозяйство передаётся в функцию конфигурирования — DMA_SetConfig(hdma, SrcAddress, DstAddress, DataLength) , после чего происходит это…

Если элемент структуры hdma->XferHalfCpltCallback не пустой, то разрешаются прерывания по заполнению буфера полностью (DMA_IT_TC), по заполнению буфера наполовину (DMA_IT_HT), и при ошибке (DMA_IT_TE). Если нам не нужно отслеживать заполнение половины буфера, то надо в huart->hdmarx->XferHalfCpltCallback записать NULL.

Далее сбрасывается флаг ошибки переполнения (__HAL_UART_CLEAR_OREFLAG), снимается блокировка (__HAL_UNLOCK), с помощью макроса SET_BIT устанавливаются различные биты и возвращается статус — return HAL_OK .

На этом функция HAL_UART_Receive_DMA(&huart1, (uint8_t*)rx_buff, BUFSIZE) закончена.

Низкоуровневый обработчик прерываний от DMA выглядит так же как и в случае с таймером…

… вызывает HAL-обработчик HAL_DMA_IRQHandler(&hdma_usart1_rx);

И опять же как и у таймера, функция состоит из нескольких блоков. Первый блок срабатывает при заполнении половинки буфера, второй — целиком, а третий при ошибке. Для примера рассмотрим блок полного буфера…

Проверяются флаги полного буфера (DMA_FLAG_TC1) и разрешённого прерывания (DMA_IT_TC).

Если отключён циклический режим DMA — hdma->Instance->CCR & DMA_CCR_CIRC) == 0U , тогда отключаются прерывания — __HAL_DMA_DISABLE_IT(hdma, DMA_IT_TE | DMA_IT_TC) . При работе DMA в циклическом режиме, отключать прерывания конечно же не нужно.

Заметьте, разработчики HAL снабдили всё функции, макросы и флаги связанные с прерываниями буквами IT .

Далее устанавливается статус готовности к очередному приёму — hdma->State = HAL_DMA_STATE_READY , сбрасывается флаг окончания приёма через DMA — __HAL_DMA_CLEAR_FLAG(hdma, __HAL_DMA_GET_TC_FLAG_INDEX(hdma)) , и снимается блокировка…

блокировка

Блокировка организована очень просто…

Если сделать __HAL_LOCK(huart) , то при обращении к структуре huart будет возвращаться статус «занято» — return HAL_BUSY;

Последнее условие связано с тем, что было сделано в функции запуска. Если мы там сделали так — huart->hdmarx->XferCpltCallback = UART_DMAReceiveCplt , то в элементе структуры будет лежать указатель на функцию UART_DMAReceiveCplt() . Соответственно условие сработает и будет вызвана функция UART_DMAReceiveCplt() , которая в свою очередь вызовет колбек.

Такая вот хитроумная конструкция

Если приём ведётся без DMA…

HAL_UART_Receive_IT(&huart1, (uint8_t*)rx_buff, 10);

Тогда после включения глобального прерывания USART’а появится его обработчик…

Перейдём к функции HAL_UART_IRQHandler(&huart1) . Полностью её рассматривать не будем, разбёрём только часть отвечающую за приём. Отправка схожа с приёмом.

Тут появился ещё один макрос — READ_REG , с помощью которого читаются регистры и проверяется нет ли ошибок — (errorflags == RESET).

Далее проверятся что произошло: USART_SR_RXNE — в USART пришёл байт, USART_CR1_RXNEIE — было сгенерировано прерывание. Если всё так, то вызывается функция UART_Receive_IT(huart) . Эта функция вызывается каждый раз при приёме очередного байта.

В зависимости от длины принимаемого слова (8 или 9 бит) выбирается первая или вторая конструкция, и данные из регистра DR (Data Register) записываются в приёмный буфер — pRxBuffPtr .
Если длина слова 9 бит, то для его сохранения используется два байта — huart->pRxBuffPtr += 2U;

Следом проверяется счётчик принятых байт — RxXferCount (он считает «вниз» от максимального значения буфера), и если он равен нулю (то есть приняты все запрошенные данные), то вызывается колбек — HAL_UART_RxCpltCallback(huart);

Вы наверно обратили внимание, что при принятии одного байта происходит очень много операций (как раз за такую избыточность некоторые пользователи и ругают HAL, хотя если подумать, то там только проверки и ничего лишнего) , поэтому при большом количестве данных и/или интенсивном обмене лучше использовать DMA, там это всё происходит на аппаратном уровне.

В завершение хочется рассказать про копирование через DMA. Для этого режима у DMA есть механизм создания колбеков.

Настроим Куб для копирование массива из одной области памяти в другую при помощи DMA…

Длина слова указана Word (32 бита), то есть копироваться будет по четыре байта за один такт.

программа

#include "main.h" #define BUFFSIZE 20 DMA_HandleTypeDef hdma_memtomem_dma1_channel1; uint8_t src_buff[BUFFSIZE] = ; uint8_t dst_buff[BUFFSIZE] = ; void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_DMA_Init(void); static void MX_USART1_UART_Init(void); void DMA_m2m_Callback(DMA_HandleTypeDef *hdma_memtomem_dma1_channel1) // колбек по окончанию копирования через DMA < // копирование завершено >int main(void) < HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_DMA_Init(); MX_USART1_UART_Init(); // регистрация колбека по окончанию копирования через DMA if(HAL_DMA_RegisterCallback(&hdma_memtomem_dma1_channel1, HAL_DMA_XFER_CPLT_CB_ID, DMA_m2m_Callback) != HAL_OK) < Error_Handler(); >while (1) < // запускаем копирование через DMA HAL_DMA_Start_IT(&hdma_memtomem_dma1_channel1, (uint32_t)src_buff, (uint32_t)dst_buff, BUFFSIZE / 4); HAL_Delay(1000); >>

Функция регистрации колбека…

В функцию передаются три аргумента:

1. Указатель на структуру.
2. Ключ, по которому определяется какое событие должно вызвать колбек — скопирован весь буфер, скопирована половина буфера и т.д.

В нашем случае указан полный буфер — HAL_DMA_XFER_CPLT_CB_ID.

3. Название колбека. Придумайте сами.

Таким образом мы зарегистрировали колбек — DMA_m2m_Callback() , который будет вызываться после полного копирования.

Функция запуска копирования…

HAL_DMA_Start_IT(&hdma_memtomem_dma1_channel1, (uint32_t)src_buff, (uint32_t)dst_buff, BUFFSIZE / 4);

Аргументы: указатель на структуру, массив из которого копируется, массив в который копируется, количество байт (ячейки массива 8-ми битные, а DMA будет копировать по 32 бита за раз).

Содержимое этой функции поизучайте самостоятельно, вы уже всё знаете

По окончанию копирования произойдёт прерывание и будет вызван обработчик…

В функции HAL_DMA_IRQHandler() прописан такой же механизм как и в случае с USART’ом — несколько блоков отвечающих за каждое событие (полный буфер, половинка и т.д.) и вот это…

Элемент структуры hdma->XferCpltCallback был заполнен во время регистрации колбека.

На этом наверно всё.

Всем спасибо

  • 24 марта 2019, 15:29
  • stD
  • 79806
  • —>

    Поддержать автора

    Задать вопрос по статье

    Известит Вас о новых публикациях

    Assert. Что это?

    Assert — это специальная конструкция, позволяющая проверять предположения о значениях произвольных данных в произвольном месте программы. Эта конструкция может автоматически сигнализировать при обнаружении некорректных данных, что обычно приводит к аварийному завершению программы с указанием места обнаружения некорректных данных. Странная, на первый взгляд, конструкция — может завалить программу в самый неподходящий момент. Какой же в ней смысл? Давайте подумаем, что произойдет, если во время исполнения программы в какой-то момент времени некоторые данные программы стали некорректными и мы не «завалили» сразу же программу, а продолжили ее работу, как ни в чем не бывало. Программа может еще долго работать после этого без каких-либо видимых ошибок. А может в любой момент времени в будущем «завалиться» сама по известной только ей причине. Или накачать вам полный винчестер контента с гей-порносайтов. Это называется неопределенное поведение (undefined behavior) и, вопреки расхожему мнению, оно свойственно не только языкам программирования с произвольным доступом к памяти (aka C, C++). Т.к. assert завершает программу сразу же после обнаружения некорректных данных, он позволяет быстро локализировать и исправить баги в программе, которые привели к некорректным данным. Это его основное назначение. Assert’ы доступны во многих языках программирования, включая java, c#, c и python.
    Какие виды assert’ов бывают?
    Assert’ы позволяют отлавливать ошибки в программах на этапе компиляции либо во время исполнения. Проверки на этапе компиляции не так важны — в большинстве случаев их можно заменить аналогичными проверками во время исполнения программы. Иными словами, assert’ы на этапе компиляции являются ничем иным, как синтаксическим сахаром. Поэтому в дальнейшем под assert’ами будем подразумевать лишь проверки во время исполнения программы.

    • Проверка входящих аргументов в начале функции.
    // Считает факториал числа n. // Число n должно лежать в пределах от 0 до 10 включительно. int factorial(int n) < // Факториал отрицательного числа не считается assert(n >= 0); // Если n превысит 10, то это может привести либо к целочисленному // переполнению результата, либо к переполнению стэка. assert(n return factorial(n - 1) * n; > // мы 'забыли' об ограничениях функции factorial() и пытаемся вычислить // факториалы чисел от 0 до 99. // // проверка внутри factorial() любезно напомнит нам о своих ограничениях, // так что мы сможем быстро выявить и исправить этот баг. // // если бы эта проверка отсутствовала, то баг мог бы долго оставаться // незамеченным, периодически давая о себе знать переполнениями стэка и // некорректным поведением программы. for (int i = 0; i

    Важно понимать, что входящие аргументы функции могут быть неявными. Например, при вызове метода класса в функцию неявно передается указатель на объект данного класса (aka this и self). Также функция может обращаться к данным, объявленным в глобальной области видимости, либо к данным из области видимости лексического замыкания. Эти аргументы тоже желательно проверять с помощью assert’ов при входе в функцию.
    Если некорректные данные обнаружены на этом этапе, то код данной функции может содержать баги. Пример:

    int factorial(int n) < int result = 1; for (int i = 2; i // С первого взгляда эта проверка никогда не сработает - факториал должен // быть всегда положительным числом. Но как только n превысит допустимый // предел, произойдет целочисленное переполнение. В этом случае // a[i] может принять отрицательное либо нулевое значение. // // После срабатывания этой проверки мы быстро локализуем баг и поймем, // что либо нужно ограничивать значение n, либо использовать целочисленную // арифметику с бесконечной точностью. assert(result > 0); return result; >
    • Проверка данных, с которыми работает функция, внутри кода функции.
    int factorial(int n) < int result = 1; while (n >1) < // Знакомая нам проверка на целочисленное переполнение. // // При ее срабатывании мы быстро определим, что эта функция должна уметь // корректно обрабатывать слишком большие n, ведущие к переполнению. // // Эта проверка лучше, чем проверка из предыдущего пункта (перед выходом // из функции), т.к. она срабатывает перед первым переполнением result, // тогда как проверка из предыдущего пункта может пропустить случай, когда // в результате переполнения (или серии переполнений) итоговое значение // result остается положительным. assert(result return result; >

    Когда и где стоит использовать assert’ы?
    Ответ прост — используйте assert’ы всегда и везде, где они хоть чуточку могут показаться полезными. Ведь они существенно упрощают локализацию багов в коде. Даже проверка результатов выполнения очевидного кода может оказаться полезной при последующем рефакторинге, после которого код может стать не настолько очевидным и в него может запросто закрасться баг. Не бойтесь, что большое количество assert’ов ухудшит ясность кода и замедлит выполнение вашей программы. Assert’ы визуально выделяются из общего кода и несут важную информацию о предположениях, на основе которых работает данный код. Правильно расставленные assert’ы способны заменить большинство комментариев в коде. Большинство языков программирования поддерживают отключение assert’ов либо на этапе компиляции, либо во время выполнения программы, так что они оказывают минимальное влияние на производительность программы. Обычно assert’ы оставляют включенными во время разработки и тестирования программ, но отключают в релиз-версиях программ. Если программа написана в лучших традициях ООП, либо с помощью enterprise методологии, то assert’ы вообще можно не отключать — производительность вряд ли изменится.

    Когда можно обойтись без assert’ов?
    Понятно, что дублирование assert’ов через каждую строчку кода не сильно улучшит эффективность отлова багов. Не существует единого мнения насчет оптимального количества assert’ов, также как и насчет оптимального количество комментариев в программе. Когда я только узнал про существование assert’ов, мои программы стали содержать 100500 assert’ов, многие из которых многократно дублировали друг друга. С течением времени количество assert’ов в моем коде стало уменьшаться. Следующие правила позволили многократно уменьшить количество assert’ов в моих программах без существенного ухудшения в эффективности отлова багов:
    Можно избегать дублирующих проверок входящих аргументов путем размещения их лишь в функциях, непосредственно работающих с данным аргументом. Т.е. если функция foo() не работает с аргументом, а лишь передает его в функцию bar(), то можно опустить проверку этого аргумента в функции foo(), т.к. она продублирована проверкой аргумента в функции bar().
    Можно опускать assert’ы на недопустимые значения, которые гарантированно приводят к краху программы в непосредственной близости от данных assert’ов, т.е. если по краху программы можно быстро определить местонахождение бага. К таким assert’ам можно отнести проверки указателя на NULL перед его разыменованием и проверки на нулевое значение делителя перед делением. Еще раз повторюсь — такие проверки можно опускать лишь тогда, когда среда исполнения гарантирует крах программы в данных случаях.
    Вполне возможно, что существуют и другие способы, позволяющие уменьшить количество assert’ов без ухудшения эффективности отлова багов. Если вы в курсе этих способов, делитесь ими в комментариях к данному посту.

    Когда нельзя использовать assert’ы?
    Т.к. assert’ы могут быть удалены на этапе компиляции либо во время исполнения программы, они не должны менять поведение программы. Если в результате удаления assert’а поведение программы может измениться, то это явный признак неправильного использования assert’а. Таким образом, внутри assert’а нельзя вызывать функции, изменяющие состояние программы либо внешнего окружения программы. Например, следующий код неправильно использует assert’ы:

    // Захватывает данный мютекс. // // Возвращает 0, если невозможно захватить данный мютекс из-за следующих причин: // - мютекс уже был захвачен. // - mtx указывает на некорректный объект мютекса. // Возвращает 1, если мютекс успешно захвачен. int acquire_mutex(mutex *mtx); // Освобождает данный мютекс. // // Возвращает 0, если невозможно освободить данный мютекс из-за следующих // причин: // - мютекс не был захвачен. // - mtx указывает на некорректный объект мютекса. // Возвращает 1, если мютекс успешно захвачен. int release_mutes(mutex *mtx); // Убеждаемся, что мютекс захвачен. assert(acquire_mutex(mtx)); // Работаем с данными, "защищенными" мютексом. process_data(data_protected_by_mtx); // Убеждаемся, что мютекс освобожден. assert(release_mutes(mtx));

    Очевидно, что данные могут оказаться незащищенными при отключенных assert’ах.
    Чтобы исправить эту ошибку, нужно сохранять результат выполнения функции во временной переменной, после чего использовать эту переменную внутри assert’а:

    int is_success; is_success = acquire_mutex(mtx); assert(is_success); // Теперь данные защищены мютексом даже при отключенных assert'ах. process_data(data_protected_by_mtx); is_success = release_mutex(mtx); assert(is_success);

    Т.к. основное назначение assert’ов — отлов багов (aka ошибки программирования), то они не могут заменить обработку ожидаемых ошибок, которые не являются ошибками программирования. Например:

    // Пытается записать buf_size байт данных, на которые указывает buf, // в указанное сетевое соединение connection. // // Возвращает 0 в случае ошибки записи, возникшей не по нашей вине. Например, // произошел разрыв сетевого соединения во время записи. // Возвращает 1 в случае успешной записи данных. int write(connection *connection, const void *buf, size_t buf_size); int is_success = write(connection, buf, buf_size); // "Убеждаемся", что данные корректно записаны. assert(is_success);

    Если write() возвращает 0, то это вовсе не означает, что в нашей программе есть баг. Если assert’ы в программе будут отключены, то ошибка записи может остаться незамеченной, что впоследствие может привести к печальным результатам. Поэтому assert() тут не подходит. Тут лучше подходит обычная обработка ошибки. Например:

    while (!write(connection, buf, buf_size)) < // Пытаемся создать новое соединение и записать данные туда еще раз. close_connection(connection); connection = create_connection(); >

    Я программирую на javascript. В нем нет assert’ов. Что мне делать?
    В некоторых языках программирования отсутствует явная поддержка assert’ов. При желании они легко могут быть там реализованы, следуя следующему «паттерну проектирования»:

    function assert(condition) < if (!condition) < throw "Assertion failed! See stack trace for details"; >> assert(2 + 2 === 4); assert(2 + 2 === 5);

    Вообще, assert’ы обычно реализованы в различных фреймворках и библиотеках, предназначенных для автоматизированного тестирования. Иногда они там называются expect’ами. Между автоматизированным тестированием и применением assert’ов есть много общего — обе техники предназначены для быстрого выявления и исправления багов в программах. Но, несмотря на общие черты, автоматизированное тестирование и assert’ы являются не взаимоисключающими, а, скорее всего, взаимодополняющими друг друга. Грамотно расставленные assert’ы упрощают автоматизированное тестирование кода, т.к. тестирующая программа может опустить проверки, дублирующие assert’ы в коде программы. Такие проверки обычно составляют существенную долю всех проверок в тестирующей программе.

    • assert
    • тестирование
    • программирование
    • Тестирование IT-систем
    • Программирование

    Assert param stm32 что это

    void ADS1256_Command_Reset(void)
    // Синхронная задержка до готовности АЦП
    while(ADS1256_DRDY_BUSY());

    // Буфер
    uint8_t TxBuffer = ADS1256_COMMAND_RESET;
    // Команда
    assert_param(HAL_OK == HAL_SPI_Transmit(&SPI_ADC_HANDLE, &TxBuffer, 1, HAL_IO_TIMEOUT));

    // Обязательная «задержка между командами», пока АЦП восстановится после только что принятой команды
    delay_us(ADS1256_DELAY_T11_US);

    // Datasheet: «After a reset, the ADS1255/6 performs self-calibration. »
    // Синхронная задержка до готовности АЦП (пока завершится автокалибровка)
    while(ADS1256_DRDY_BUSY());
    >

    В строке
    uint8_t TxBuffer = ADS1256_COMMAND_RESET;
    возникает warning
    ../Drivers/ADS1256_Driver/ads1256.c:330:11: warning: unused variable ‘TxBuffer’ [-Wunused-variable]
    Но ведь она же используется в следующей же строке!
    Что же я делаю не так?
    IDE:
    STM32CubeIDE Version: 1.7.0 Build: 10852_20210715_0634 (UTC)
    Компилятор GCC

    _________________
    Зачем, зубодер распроклятый, мучительный тянешь момент?
    Тебе, стоматолог, сто матов измученный шлет пациент! (с) Вадим Шефнер

    Заголовок сообщения: Re: STM32 Warning при компиляции библиотечной функции
    Добавлено: Вт мар 15, 2022 13:39:38

    Собутыльник Кота

    assert_param — это не макрос случаем, который при «чистовой сборке» выбрасывается?
    Как обычно, советую выкинуть калокуб куда подальше!

    _________________
    Linux rules! Windows must die. Здравомыслящий человек добровольно будет пользоваться мастдаем лишь в двух случаях: под дулом автомата или под влиянием анального зонда.
    Я на гитхабе, в ЖЖ

    Заголовок сообщения: Re: STM32 Warning при компиляции библиотечной функции
    Добавлено: Вт мар 15, 2022 13:44:40

    Это не хвост, это антенна

    Если TxBuffer упоминается в коде один раз усего, компилятор не считает TxBuffer переменной, поскольку TxBuffer не меняется (похожа на константу (адрес константы)). Будете дальше писать текст, упомяните TxBuffer на запись и, Warning уйдет не переживайте это не Error.

    P.S. Чего Вы тушуетесь от недоверия? Компилятор же сказал Вам, что TxBuffer не меняется у Вас. Это намек, что Вы что то не дописали в тексте кода, например, забыли добавить то что запланировали.

    P.P.S. Поэтому я противник «инициализации» буферов, иначе важный намек (Warning) не будет «озвучен». Еще, здорово иметь статистику, где и сколько раз переменная упоминается на запись в явном и особенно неявном виде.

    P.P.S. У меня бывало, отлично работающий код изобиловал Warning-ми, поскольку не желая явно переходить на ассемблер я писал на Си в стиле ассемблера (неявное приведение типов). Я знал эти места, поэтому на Warning ноль внимания (зато не было ошибок времени выполнения, даже удавалось их, таким образом, обойти), кроме Вашего случая, когда «прозрачный намек», что код неполный забыли дописать.

    _________________
    «Every profession is a conspiracy against the uninitiated» (B. Shaw)
    «A textbook can be defined as a book unsuitable for reading» (B. Shaw)
    Tautology is humor in «this» place (Vigo Carpathian)

    Assert param stm32 что это

    Auto-suggest helps you quickly narrow down your search results by suggesting possible matches as you type.

    Showing results for
    Search instead for
    Did you mean:

    • STMicroelectronics Community
    • STM32 MCUs Software development tools
    • STM32CubeMX (MCUs)
    • How assert_param macro work
    • Subscribe to RSS Feed
    • Mark Topic as New
    • Mark Topic as Read
    • Float this Topic for Current User
    • Bookmark
    • Subscribe
    • Mute
    • Printer Friendly Page

    How assert_param macro work
    Associate III

    • Mark as New
    • Bookmark
    • Subscribe
    • Mute
    • Subscribe to RSS Feed
    • Permalink
    • Print
    • Email to a Friend
    • Report Inappropriate Content

    ‎2016-05-29 12:50 AM

    Posted on May 29, 2016 at 09:50

    I m using cubemx for stacks. In files i foundassert_param is used for parameter checking. I don’t understand how it is work. Here is syntax and use of that.

    #define assert_param(expr) ((void)0) assert_param(IS_UART_WORD_LENGTH(huart->Init.WordLength));

    what will happen in both cases like

    IS_UART_WORD_LENGTH returns 0 and 1.

    #printf #assert #!cubemx #cubemx #debug

    • All forum topics
    • Previous Topic
    • Next Topic
    • Mark as New
    • Bookmark
    • Subscribe
    • Mute
    • Subscribe to RSS Feed
    • Permalink
    • Print
    • Email to a Friend
    • Report Inappropriate Content

    ‎2016-05-30 04:02 AM

    Posted on May 30, 2016 at 13:02

    assert_param is used to check that a parameter that is passed to a firmware library function is in the valid range of values. Each call to assert_param uses another macro that checks the parameter is in that range. If it is in range, then assert_param does nothing. If it is a bad value, then assert_failed is called with the filename and line number.

    The assert_param function is disabled by default, and can be enabled by uncommenting the correspondent macro ( for example in Hal library , in stm32fxx_hal_conf.h :

    /* #define USE_FULL_ASSERT 1 */ )

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *