Что такое callback ты понимаешь или нет? Callack иначе "обратный вызов", с английского "перезвонить". Когда ты пишешь какую-то полезную библиотеку для людей у тебя рано или поздно возникает необходимость в ответ на какое-то происшествие позвать код пользователя. Ну и как ты собрался это сделать, если переписывать код твоей библиотеки нельзя и если у пользователя не будет кода твоей библиотеки?
Делается это так.
У себя в библиотеке ты заводишь одну void* переменную-указатель с адресом пользовательской функции (пользовательского кода).
Если она 0 (NULL, nullptr) код твоей библиотеки это понимает н ничего не происходит. Но если проверка показывает что там что-то есть (не-0), то значит это адрес который пользователь установил.
И тогда когда нужно (допустим, по сети приходит пакет) ты вызываешь функцию по этому адресу.
Обрати внимание. На момент написания своей библиотеки ты ничего не знаешь о пользователе и его коде. Но он (пользователь, пользовательский код) используя твою библиотеку может в любой момент задать адрес обратного вызова, таким образом "подставив" себя, подставив свой код.
И таким образом, ты сможешь вызвать даже то, о чем ты сейчас не знаешь что. Вот для этого нужны колбэки (callbacks).
Обрати внимание, что устанавливать колбэки можно не при компиляции (* см ниже про weak), а динамически, в run-time, прямо во время выполнения.
callback можно установить или снять. А можно подменить уже кем-то установленный:
Сохраняешь адрес у себя. Вписываешь себя, свой адрес (своей функции). А дальше это дает тебе возможность решать. Когда библиотека "позовёт" callback, то вызовается твоя функция. (Ты же подменил адрес callback'а).
И ты можешь решать что делать: вызвать обработку сначала свою, а затем ту что была по адресу до тебя записана в переменной сохраняющей адрес callback'а. Или наоборот.
Дальнейшим расширением callback'ов является "придумка" (называемая также идиомой, паттерном) "publisher-subscriber".
Это когда callback представляет собой не одну переменную с адресом, а управляемый список. И ты типа "подписываешься" или отписываешься на события-обратные вызовы (это в точности такой же callback, библиотека тебя позовет). И можешь прописываться в начало или в конец. И так далее. Суть в том что подписантов может быть много (т.е. список).
Конкретный ответ. ЗАЧЕМ.
Затем, что подобный механизм позволяет какому-то коду (библиотеке, втч HAL) ничего не знать о твоем коде, но вызывать его.
Затем, что подобный механизм поключаемых callback'ов работает как "интерфейс'" (что-то типа разъёма между платами или блоками аппаратуры) и позволяет не сваливать всё в одну кучу, а разрабатывать систему отдельными модулями. Которые ты затем "подключишь" друг к другу. А может перключишь. Или выключишь.
И наконец самый главный ответ почему HAL сделано именно так.
Потому что HAL сидит на прерывании и обрабатывает МНОГО ЧЕГО ЕЩЕ, о чем ты даже и не догадываешься.
Недавно например возникал спор о обработке ошибок UART (втч при DMA). Вот HAL например обрабатывает ошибки "у тебя за спиной". Для этого он должен "сесть" на прерывания.
Ну и твою функцию, получается, если уж он сам сел на прерывания вызывать уже чисто как "callback", а как еще-то?
Еще один ответ про HAL.
Дело в том что в HAL даже для одного прерывания множество обработок на самом деле.
И соответственно, множество и Callback'ов в свою очередь. Если ты заинтересован - прописываешь callback.
Не заинтересован - ну и шут с ним, не прописываешь, тогда оно само там предпринимает усилия как и что делать. "Как-то делает"
weak callback'и
В HAL Через #define можно выбрать способ работы с колбэками.
Callback'и можно сделать динамические, ровно такие, как я описал выше (адреса вызываемых функция у них хранятся в структуре). Но благодаря компилятору GCC есть и другой способ, более простой, быстрый и компактный.
В GCC существуют так называемые "weak" (слабые) функции. Работает это так. В коде может быть определена функция помеченная как weak. Она будет использоваться как бы по-умолчанию.
Обрати внимание что в коде HAL заданы пустые функции-колбэки по умолчанию (заглушки), но помеченые как weak.
Но когда ты определишь самую обычную свою функцию, weak-функция по умолчанию уже не будет использоваться (отрубится).
Поэтому как только ты задал функцию с такой же сигнатурой как её предполагает callback, твоя функция будет использована.
Это делает линкер, поэтому свой колбэк ты можешь определять где угодно в коде.
От обработки прерываний механизмами HAL и вызова механизма callback'ов можно полностью (а можно и не полностью, а чуть-чуть) отказаться.
Иди в свой файл обработки прерываний, он всегда называется че-то там ***_it.c
Видишь там одиночные функции прерываний? (Адреса этих функций прописываются прямо в ARM вектор прерываний).
Вписывай между пользовательским /* USER CODE BEGIN */
и
/* USER CODE END */ любой свой код.
Ставишь например return до вызова HAL. И всё, HAL функция обработки прерываний за твоим return конечно уже не вызовется.
Есть выбор, вписать свой код до, HAL после HAL и вместо HAL, полностью свой. Т.е. пишешь всё сам, всю обработку в прерывании (я часто так делаю).
Надеюсь это было полезным.