Что такое void error



Void error(); / функция обработки ошибок /

printf(«SUCCESS . \n»); exit(0);

О применимости метода рекурсивного спуска

Метод рекурсивного спуска применим в том случае, если каждое правило грамматики имеет вид:

либо A  , где   (VT  VN) * и это единственное правило вывода для этого нетерминала;

либо A  a11 | a22 | . | ann, где ai  VT для всех i = 1,2. n; ai  aj для i  j; i  (VT  VN) * , т. е. если для нетерминала А правил вывода несколько, то они должны начинаться с терминалов, причем все эти терминалы должны быть различными.

Ясно, что если правила вывода имеют такой вид, то рекурсивный спуск может быть реализован по выше изложенной схеме.

Естественно, возникает вопрос: если грамматика не удовлетворяет этим условиям, то существует ли эквивалентная КС-грамматика, для которой метод рекурсивного спуска применим? К сожалению, нет алгоритма, отвечающего на поставленный вопрос, т.е. это алгоритмически неразрешимая проблема.

Изложенные выше ограничения являются достаточными, но не необходимыми. Попытаемся ослабить требования на вид правил грамматики:

(1) при описании синтаксиса языков программирования часто встречаются правила, описывающие последовательность однотипных конструкций, отделенных друг от друга каким-либо знаком-разделителем (например, список идентификаторов при описании переменных, список параметров при вызове процедур и функций и т.п.).

Общий вид этих правил:

L  a | a,L ( либо в сокращенной форме L  a <,a>)

Формально здесь не выполняются условия применимости метода рекурсивного спуска, т.к. две альтернативы начинаются одинаковыми терминальными символами.

Действительно, в цепочке a,a,a,a,a из нетерминала L может выводиться и подцепочка a , и подцепочка a,a , и вся цепочка a,a,a,a,a. Неясно, какую из них выбрать в качестве подцепочки, выводимой из L. Если принять решение, что в таких случаях будем выбирать самую длинную подцепочку (что и требуется при разборе реальных языков), то разбор становится детерминированным.

Тогда для метода рекурсивного спуска процедура L будет такой:

if ((c = fgetc(fp)) != ‘a’) ERROR();

Важно, чтобы подцепочки, следующие за цепочкой символов, выводимых из L, не начинались с разделителя (в нашем примере — с запятой), иначе процедура L попытается считать часть исходной цепочки, которая не выводится из L. Например, она может порождаться нетерминалом B — ”соседом” L в сентенциальной форме, как в грамматике

Если для этой грамматики написать анализатор, действующий РС-методом, то цепочка а,а,а,b будет признана им ошибочной, хотя в действительности это не так.

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

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

a) если в грамматике есть нетерминалы, правила вывода которых леворекурсивны, т.е. имеют вид

где i  (VT  VN) + , j  (VT  VN) * , i = 1, 2, . n; j =1, 2 . m, то непосредственно применять РС-метод нельзя.

Левую рекурсию всегда можно заменить правой:

Будет получена грамматика, эквивалентная данной, т.к. из нетерминала A по-прежнему выводятся цепочки вида j <i>, где i = 1,2. n; j = 1,2. m.

b) если в грамматике есть нетерминал, у которого несколько правил вывода начинаются одинаковыми терминальными символами, т.е. имеют вид

где a  VT; i, j  (VT  VN) * , то непосредственно применять РС-метод нельзя. Можно преобразовать правила вывода данного нетерминала, объединив правила с общими началами в одно правило:

Будет получена грамматика, эквивалентная данной.

c) если в грамматике есть нетерминал, у которого несколько правил вывода, и среди них есть правила, начинающиеся нетерминальными символами, т.е. имеют вид

где Bi  VN; aj  VT; i, j, ij  (VT  VN) * , то можно заменить вхождения нетерминалов Bi их правилами вывода в надежде, что правило нетерминала A станет удовлетворять требованиям метода рекурсивного спуска:

d) если допустить в правилах вывода грамматики пустую альтернативу, т.е. правила вида

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

Например, для грамматики G = (, , P, S), где

РС-анализатор, реализованный по обычной схеме, будет таким:

Тогда при анализе цепочки baaa функция A() будет вызвана три раза; она прочитает подцепочку ааа, хотя третий символ а — это часть подцепочки, выводимой из S. В результате окажется, что baaa не принадлежит языку, порождаемому грамматикой, хотя в действительности это не так.

Проблема заключается в том, что подцепочка, следующая за цепочкой, выводимой из A, начинается таким же символом, как и цепочка, выводимая из А.

Однако в грамматике G = (, , P, S), где

нет проблем с применением метода рекурсивного спуска.

Выпишем условие, при котором -правило вывода делает неприменимым РС-метод.

Определение: множество FIRST(A) — это множество терминальных символов, которыми начинаются цепочки, выводимые из А в грамматике G = (VT, VN, P, S), т.е. FIRST(A) = < a  VT | A  a, A  VN,   (VT  VN) * >.

Определение: множество FOLLOW(A) -это множество терминальных символов, которые следуют за цепочками, выводимыми из А в грамматике G = (VT, VN, P, S), т.е. FOLLOW(A) = < a  VT | S  A,   a, A  VN, , ,   (VT  VN) * >.

Тогда, если FIRST(A) FOLLOW(A) , то метод рекурсивного спуска неприменим к данной грамматике.

и FIRST(A)  FOLLOW(A)  (из-за вхождения А в правила вывода для В), то можно попытаться преобразовать такую грамматику:

Полученная грамматика будет эквивалентна исходной, т.к. из B по-прежнему выводятся цепочки вида  <i> j  либо  <i> .

Однако правило вывода для нетерминального символа A’ будет иметь альтернативы, начинающиеся одинаковыми терминальными символами, следовательно, потребуются дальнейшие преобразования, и успех не гарантирован.

Метод рекурсивного спуска применим к достаточно узкому подклассу КС-грамматик. Известны более широкие подклассы КС-грамматик, для которых существуют эффективные анализаторы, обладающие тем же свойством, что и анализатор, написанный методом рекурсивного спуска, — входная цепочка считывается один раз слева направо и процесс разбора полностью детерминирован, в результате на обработку цепочки длины n расходуется время cn. К таким грамматикам относятся LL(k)-грамматики, LR(k)-грамматики, грамматики предшествования и некоторые другие (см., например, [2], [3]).

Источник

Как исправить ошибку Java VOID 0

Номер ошибки: Ошибка VOID 0
Название ошибки: Java Error Void 0
Описание ошибки: Ошибка VOID 0: Возникла ошибка в приложении Java. Приложение будет закрыто. Приносим извинения за неудобства.
Разработчик: Oracle Corporation
Программное обеспечение: Java
Относится к: Windows XP, Vista, 7, 8, 10, 11

Сводка «Java Error Void 0

«Java Error Void 0» — это стандартная ошибка времени выполнения. Разработчики программного обеспечения, такие как SoftwareDeveloper, обычно работают через несколько этапов отладки, чтобы предотвратить и исправить ошибки, обнаруженные в конечном продукте до выпуска программного обеспечения для общественности. Как и во всем в жизни, иногда такие проблемы, как ошибка VOID 0, упускаются из виду.

Читайте также:  Microsoft com error codes

Ошибка VOID 0, рассматриваемая как «Java Error Void 0», может возникнуть пользователями Java в результате нормального использования программы. После возникновения ошибки VOID 0 пользователь программного обеспечения имеет возможность сообщить разработчику об этой проблеме. Затем Oracle Corporation исправит ошибки и подготовит файл обновления для загрузки. Таким образом, в этих случаях разработчик выпустит обновление программы Java, чтобы исправить отображаемое сообщение об ошибке (и другие сообщенные проблемы).

В чем причина ошибки VOID 0?

У вас будет сбой во время выполнения Java, если вы столкнетесь с «Java Error Void 0» во время выполнения. Вот три наиболее распространенные причины, по которым происходят ошибки во время выполнения ошибки VOID 0:

Ошибка VOID 0 Crash — Ошибка VOID 0 может привести к полному замораживанию программы, что не позволяет вам что-либо делать. Это возникает, когда Java не реагирует на ввод должным образом или не знает, какой вывод требуется взамен.

Утечка памяти «Java Error Void 0» — при утечке памяти Java это может привести к медленной работе устройства из-за нехватки системных ресурсов. Возможные провокации включают отсутствие девыделения памяти и ссылку на плохой код, такой как бесконечные циклы.

Ошибка VOID 0 Logic Error — Логическая ошибка вызывает неправильный вывод, даже если пользователь дал действительные входные данные. Когда точность исходного кода Oracle Corporation низкая, он обычно становится источником ошибок.

Такие проблемы Java Error Void 0 обычно вызваны повреждением файла, связанного с Java, или, в некоторых случаях, его случайным или намеренным удалением. Большую часть проблем, связанных с данными файлами, можно решить посредством скачивания и установки последней версии файла Oracle Corporation. Кроме того, регулярная очистка и оптимизация реестра Windows предотвратит создание неправильных ссылок на пути к файлам Oracle Corporation, поэтому мы настоятельно рекомендуем регулярно выполнять сканирование реестра.

Типичные ошибки Java Error Void 0

Усложнения Java с Java Error Void 0 состоят из:

  • «Ошибка программного обеспечения Java Error Void 0. «
  • «Недопустимая программа Win32: Java Error Void 0»
  • «Извините за неудобства — Java Error Void 0 имеет проблему. «
  • «К сожалению, мы не можем найти Java Error Void 0. «
  • «Отсутствует файл Java Error Void 0.»
  • «Ошибка запуска в приложении: Java Error Void 0. «
  • «Файл Java Error Void 0 не запущен.»
  • «Отказ Java Error Void 0.»
  • «Неверный путь к приложению: Java Error Void 0.»

Ошибки Java Error Void 0 EXE возникают во время установки Java, при запуске приложений, связанных с Java Error Void 0 (Java), во время запуска или завершения работы или во время установки ОС Windows. Важно отметить, когда возникают проблемы Java Error Void 0, так как это помогает устранять проблемы Java (и сообщать в Oracle Corporation).

Причины ошибок в файле Java Error Void 0

Проблемы Java Error Void 0 могут быть отнесены к поврежденным или отсутствующим файлам, содержащим ошибки записям реестра, связанным с Java Error Void 0, или к вирусам / вредоносному ПО.

В частности, проблемы Java Error Void 0 возникают через:

  • Поврежденные ключи реестра Windows, связанные с Java Error Void 0 / Java.
  • Вредоносные программы заразили Java Error Void 0, создавая повреждение.
  • Другая программа злонамеренно или по ошибке удалила файлы, связанные с Java Error Void 0.
  • Java Error Void 0 конфликтует с другой программой (общим файлом).
  • Java (Java Error Void 0) поврежден во время загрузки или установки.

Совместима с Windows 2000, XP, Vista, 7, 8, 10 и 11

Источник

HAL stm32

HAL (Hardware Abstraction Layer) — это библиотека для создания приложений на stm32, разработанная компанией ST в 2014 году. HAL пришёл на смену SPL.

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

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

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

То есть нам не нужно знать какие регистры отвечают за это, и что в них записывать. Более того, эта функция будет работать на любых микроконтроллерах серии 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 assert_failed(uint8_t *file, uint32_t line) . Если макрос assert_param (упомянутый в начале статьи) возвращает ошибку, то в эту функцию прилетает имя файла в котором произошла ошибка, и номер строки.

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

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

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


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 говорит о том, что устройство еще не инициализировано или отключено.

Если статус удовлетворяет, то снимается блокировка (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, поэтому можно назвать её низкоуровневой) работающая напрямую с регистрами…

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

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

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

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

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

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

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

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

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

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

Функция устанавливает бит разрешающий прерывания по переполнению — __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) содержится вот такая конструкция…

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

В первую очередь происходит проверка — занят 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…

Тогда после включения глобального прерывания 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 бита), то есть копироваться будет по четыре байта за один такт.

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

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

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


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

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

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

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

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

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

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

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

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

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

Всем спасибо

Источник

Оцените статью
toolgir.ru
Adblock
detector