Русский
Русский
English
Статистика
Реклама

Cortex-m4

Из песочницы Как подключить АЦП HX711 к NRF52832

29.09.2020 18:09:04 | Автор: admin

1. Введение


На повестке дня стояла задача разработать протокол общения микроконтролера nrf52832 с двумя полумостовыми китайскими тензодатчиками.

Задача оказалась не простой, так как столкнулся с отсутствием какой либо внятной информации. Вероятнее, что корень зла находится в самом SDK от Nordic Semiconductor это постоянное обновления версий, некоторая избыточность и запутанность функционала. Пришлось писать все с нуля.


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


2. Описание проекта


image

Железо:


  • Adafruit Feather nRF52 Bluefruit LE (то что оказалось под рукой)
  • АЦП HX711
  • Китайские тензодатчики 2 шт. (50х2 кг)
  • Программатор ST-LINK V2

Софт:


  • IDE VSCODE
  • NRF SDK 16
  • OpenOCD
  • Программатор ST-LINK V2


Все находится в одном проекте, придется только подшаманить Makefile (указать расположение вашего SDK).


3. Описание кода


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



ret_code_t err_code;   err_code = nrf_drv_gpiote_out_init(PD_SCK, &config);//настраеваем на выход   nrf_drv_gpiote_out_config_t config = GPIOTE_CONFIG_OUT_TASK_TOGGLE(false);//будем передергивать пин для импульса   err_code = nrf_drv_gpiote_out_init(PD_SCK, &config);//настраеваем на выход

Настраиваем линию синхронизации PD_SCL на выход для генерации импульсов длительностью 10 мкс.

   nrf_drv_gpiote_in_config_t  gpiote_config = GPIOTE_CONFIG_IN_SENSE_HITOLO(false);// переход уровня с высокого на низкий   nrf_gpio_cfg_input(DOUT, NRF_GPIO_PIN_NOPULL);// на вход без подтяжки   err_code = nrf_drv_gpiote_in_init(DOUT, &gpiote_config, gpiote_evt_handler); 

static void gpiote_evt_handler(nrf_drv_gpiote_pin_t pin, nrf_gpiote_polarity_t action){    nrf_drv_gpiote_in_event_disable(DOUT);//отключаем прерывание    nrf_drv_timer_enable(&m_timer0);//включаем таймер} 

Настраиваем линию данных DOUT для считывания состояния готовности HX711, при наличии низкого уровня срабатывает обработчик в котором отключаем прерывание и запускаем таймер для генерации синхронизирующих импульсов на выходе PD_SCL .


 err_code = nrf_drv_ppi_channel_alloc(&m_ppi_channel1);   APP_ERROR_CHECK(err_code);   err_code = nrf_drv_ppi_channel_assign(m_ppi_channel1,                                         nrf_drv_timer_event_address_get(&m_timer0, NRF_TIMER_EVENT_COMPARE0),                                           nrf_drv_gpiote_out_task_addr_get(PD_SCK));// подключаем таймер к выходу   APP_ERROR_CHECK(err_code);   err_code = nrf_drv_ppi_channel_enable(m_ppi_channel1);// включаем канал   APP_ERROR_CHECK(err_code);   nrf_drv_gpiote_out_task_enable(PD_SCK); 
// включаем gpiote

После чего инициализируем PPI модуль и коммутируем наш таймер к выходу PD_SCL, для генерирования импульсов длительность 10мкс при наступление события сравнения, а также включаем GPIOTE модуль.


nrf_drv_timer_config_t timer_cfg = NRF_DRV_TIMER_DEFAULT_CONFIG;// по умолчанию   timer_cfg.frequency = NRF_TIMER_FREQ_1MHz;// тактируем на частоте 1Мгц   ret_code_t err_code = nrf_drv_timer_init(&m_timer0, &timer_cfg, timer0_event_handler);   APP_ERROR_CHECK(err_code);   nrf_drv_timer_extended_compare(&m_timer0,                                  NRF_TIMER_CC_CHANNEL0,                                  nrf_drv_timer_us_to_ticks(&m_timer0,                                                            10),                                  NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK,                                  true);// срабатывает по сравнению

Инициализируем нулевой таймер и его обработчик.



  if(m_counter%2 != 0 && m_counter<=48){       buffer <<= 1;// переменная считанных даных        c_counter++;// счетчик положительных  импульсов           if(nrf_gpio_pin_read(DOUT))buffer++;//считываем состояние входа   }

Самое интересное происходит в обработчике таймера. Период импульсов составляет 20 мкс. Нас интересуют нечетные импульсы (по восходящему фронту) и при условии, что их количество не более 24, а событий 48. При каждом нечетном событии происходит считывание DOUT


Из даташита следует, что количество импульсов должно быть не менее 25, что соответствует коэффициенту усиления 128 (в коде я использовал 25 импульсов), это эквивалентно 50 событиям таймера, что указывает на окончание фрейма данных.


 ++m_counter;// счетчик событийif(m_counter==50){      nrf_drv_timer_disable(&m_timer0);// отключаем таймер       m_simple_timer_state = SIMPLE_TIMER_STATE_STOPPED;//       buffer = buffer ^ 0x800000;       hx711_stop();//jотключаем hx711       }   

После этого отключаем таймер и обрабатываем данные (по даташиту) и переводим HX711 в режим низкого энергопотребления.


static void repeated_timer_handler(void * p_context){   nrf_drv_gpiote_out_toggle(LED_2);   if(m_simple_timer_state == SIMPLE_TIMER_STATE_STOPPED){      hx711_start();// включаем hx711       nrf_drv_gpiote_out_toggle(LED_1);       m_simple_timer_state = SIMPLE_TIMER_STATE_STARTED;   }  }/**@brief Create timers.*/static void create_timers(){   ret_code_t err_code;    // Create timers   err_code = app_timer_create(&m_repeated_timer_id,                               APP_TIMER_MODE_REPEATED,                               repeated_timer_handler);   APP_ERROR_CHECK(err_code);}

Ожидаем события от RTC таймера с интервалом в 10 с (эту уже на ваше усмотрение) в обработчике запускаем HX711, вызывая прерывание по линии DOUT.

Есть еще один момент, логи выводятся через UART (baud rate 115200, TX 6 пин, RX 8 пин) все настройки находятся в sdk_config.h

image

Выводы


Спасибо всем за внимание, надеюсь эта статья будет полезной и сократит драгоценное время на поиск решения для разработчиков. Хочу сказать, что технический подход который использует Nordic в своих платформах довольно интересен с точки зрения энергоэффективности.


P.S.


Проект еще в процессе разработки, поэтому если будет интересна эта тема в следующей статье я постараюсь описать алгоритм калибровки датчиков веса, а также подключения BLE стека.


Материалы


Подробнее..

Что делать, если поймал HardFault?

22.07.2020 14:19:29 | Автор: admin
Что делать, если поймал HardFault? Как понять, каким событием он был вызван? Как определить строчку кода, которая привела к этому? Давайте разбираться.

Всем привет! Сложно найти программиста микроконтроллеров, который ни разу не сталкивался с тяжелым отказом. Очень часто он никак не обрабатывается, а просто остаётся висеть в бесконечном цикле обработчика, предусмотренном в startup файле производителя. В то же время программист пытается интуитивно найти причину отказа. На мой взгляд это не самый оптимальный путь решения проблемы.

В данной статье я хочу описать методику анализа тяжелых отказов популярных микроконтроллеров с ядром Cortex M3/M4. Хотя, пожалуй, методика слишком громкое слово. Скорее, я просто разберу на примере то, как я анализирую возникновение тяжелых отказов, и покажу, что можно сделать в подобной ситуации. Я буду использовать программное обеспечение от IAR и отладочную плату STM32F4DISCOVERY, так как эти инструменты есть у многих начинающих программистов. Однако это совершенно не принципиально, данный пример можно адаптировать под любой процессор семейства и любую среду разработки.




Падение в HardFault


Перед тем, как пытаться анализировать HatdFault, нужно в него попасть. Есть много способов это сделать. Мне сразу же пришло на ум попытаться переключить процессор из состояния Thumb в состояние ARM, путем задания адреса инструкции безусловного перехода четным числом.

Небольшое отступление. Как известно, микроконтроллеры семейства Cortex M3/M4 используют набор ассемблерных инструкций Thumb-2 и всегда работают в режиме Thumb. Режим ARM не поддерживается. Если попытаться задать значение адреса безусловного перехода(BX reg) со сброшенным младшим битом, то произойдет исключение UsageFault, так как процессор будет пытаться переключить своё состояние в ARM. Подробнее об этом можно почитать в [1](пункты 2.8 THE INSTRUCTION SET; 4.3.4 Assembler Language: Call and Unconditional Branch).

Для начала, я предлагаю смоделировать безусловный переход по четному адресу на языке C/C++. Для этого я создам функцию func_hard_fault, затем попытаюсь вызвать ее по указателю, предварительно уменьшив адрес указателя на единицу. Сделать это можно следующим образом:

void func_hard_fault(void);void main(void){  void (*ptr_hard_fault_func) (void); //Объявляем указатель на функцию отказа  ptr_hard_fault_func = reinterpret_cast<void(*)()>(reinterpret_cast<uint8_t *>(func_hard_fault) - 1); //Присваиваем ему значение со сброшенным младшим битом  ptr_hard_fault_func(); //Вызов функции отказа    while(1) continue;}void func_hard_fault(void) //Функция, вызов которой приведет к отказу{  while(1) continue;}


Посмотрим с отладчиком, что же у меня получилось.



Красным я выделил текущую инструкцию перехода по адресу в РОН R1, который содержит чётный адрес перехода. Как результат:



Еще проще данную операцию можно выполнить с помощью ассемблерных вставок:

void main(void){  //Переходим на свой же адрес  asm("LDR R1, =0x0800029A"); //Псевдо-инструкция для записи значения переходf  asm("BX r1"); //Переход по адресу R1    while(1) continue;}


Ура, мы попали в HardFault, миссия выполнена!

Анализ HardFault


Откуда мы попали в HardFault?


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

extern "C"{void HardFault_Handler(void){  }}


Теперь поговорим о том, как выяснить, как мы здесь оказались. В процессорном ядре Cortex M3/M4 есть такая замечательная вещь, как сохранение контекста [1](пункт 9.1.1 Stacking). Если говорить простым языком, при возникновении любого исключения, содержимое регистров R0-R3, R12, LR, PC, PSR сохраняется в стеке.



Здесь для нас самым важным будет регистр PC, который содержит информацию о текущей выполняемой инструкции. Так как значение регистра было сохранено в стек во время возникновения исключительной ситуации, то там будет содержаться адрес последней выполняемой инструкции. Остальные регистры менее важны для анализа, но нечто полезное можно выцепить и из них. LR адрес возврата последнего перехода, R0-R3, R12 значения, которые могут подсказать в каком направлении двигаться, PSR просто общий регистр состояния программы.

Предлагаю выяснить значения регистров в обработчике. Для этого был написал такой код (подобный код я встречал в одном из файлов производителя):

extern "C"{void HardFault_Handler(void){  struct   {    uint32_t r0;    uint32_t r1;    uint32_t r2;    uint32_t r3;    uint32_t r12;    uint32_t lr;    uint32_t pc;    uint32_t psr;  }*stack_ptr; //Указатель на текущее значение стека(SP)   asm(      "TST lr, #4 \n" //Тестируем 3ий бит указателя стека(побитовое И)      "ITE EQ \n"   //Значение указателя стека имеет бит 3?      "MRSEQ %[ptr], MSP  \n"  //Да, сохраняем основной указатель стека      "MRSNE %[ptr], PSP  \n"  //Нет, сохраняем указатель стека процесса      : [ptr] "=r" (stack_ptr)      );  while(1) continue;}}


Как результат, имеем значения всех сохраняемых регистров:



Что же здесь произошло? Сначала мы завели указатель стека stack_ptr, тут все понятно. Сложности возникают с ассемблерной вставкой (если есть потребность в понимании ассемблерных инструкций для Cortex, то рекомендую [2]).

Почему мы просто не сохранили стек через MRS stack_ptr, MSP? Дело в том, что ядра Cortex M3/M4 имеют два указателя стека [1](пункт 3.1.3 Stack Pointer R13) основной указатель стека MSP и указатель стека процесса PSP. Они используются для разных режимов работы процессора. Не буду подробно углубляться в то, для чего это сделано и как это работает, однако дам небольшое пояснение.

Для выяснения режима работы процессора(используется в данный MSP или PSP), нужно проверить третий бит регистра связи. Этот бит определяет то, какой указатель стека используется для возвращения из исключения. Если этот бит установлен, то это MSP, если нет, то PSP. В целом, большинство приложений, написанных на C/C++ используют только MSP, и эту проверку можно и не делать.

Так что же по итогу? Имея список сохраняемых регистров, мы можем без труда определить то, откуда программа упала в HardFault по регистру PC. PC указывает на адрес 0x0800029A, который и является адресом нашей ломающей инструкции. Не стоит также забывать о важности значений остальных регистров.

Причина возникновения HardFault


На самом деле, мы можем также выяснить причину возникновения HardFault. В этом нам помогут два регистра. Hard fault status register (HFSR) и Configurable fault status register (CFSR; UFSR+BFSR+MMFSR). Регистр CFSR состоит из трёх регистров: Usage fault status register (UFSR), Bus fault status register (BFSR), Memory management fault address register (MMFSR). Почитать про них можно, например, в [1] и [3].

Предлагаю посмотреть, что эти регистры выдают в моём случае:



Во-первых, установлен бит HFSR FORCED. Значит произошёл отказ, который не может быть обработан. Для дальнейшей диагностики следует изучить остальные регистры статуса отказов.

Во-вторых, выставлен бит CFSR INVSTATE. Это значит, что произошёл UsageFault, так как процессор попытался выполнить инструкцию, которая незаконно использует EPSR.

Что такое EPSR? EPSR Execution program status register. Это внутренний регистр PSR специального регистра состояния программы(который, как мы помним, сохраняется в стеке). Двадцать четвертый бит этого регистра указывает на текущее состояние процессора (Thumb или ARM). Это как раз может определять нашу причину возникновения отказа. Давайте попробуем его считать:

volatile uint32_t EPSR = 0xFFFFFFFF;    asm(    "MRS %[epsr], PSP  \n"    : [epsr] "=r" (EPSR)      );


В результате выполнения получаем значение EPSR = 0.



Получается, что регистр показывает состояние ARM и мы нашли причину возникновения отказа? На самом деле нет. Ведь согласно [3](стр. 23), считывание этого регистра с помощью специальной команды MSR всегда возвращает нуль. Мне не очень понятно почему это работает именно так, ведь этот регистр и так только для чтения, а тут его полностью и считать нельзя(можно только некоторые биты через xPSR). Возможно это некие ограничения архитектуры.

По итогу, к сожалению, вся эта информация практически ничего не дает рядовому программисту МК. Именно поэтому я рассматриваю все данные регистры только как дополнение к анализу сохраненного контекста.

Однако, например, если отказ был вызван делением на нуль(данный отказ разрешается установкой бита DIV_0_TRP регистра CCR), то в регистре CFSR будет выставлен бит DIVBYZERO, что укажет нам на причину возникновения этого самого отказа.

Что дальше?


Что же можно сделать после того, как мы проанализировали причину возникновения отказа? Мне кажется хорошим вариантом такой порядок действий:
1) Вывести в отладочную консоль(printf) значения всех анализируемых регистров. Это можно сделать только при наличии отладчика JTAG.
2) Сохранить информацию об отказе во внутреннюю или внешнюю(если имеется) flash. Также можно вывести значение регистра PC на экран устройства(если имеется).
3) Перезагрузить процессор NVIC_SystemReset().



Источники


1) Joseph Yiu. The definitive guide to the ARM Cortex-M3.
2) Cortex-M3 Devices Generic User Guide.
3) STM32 Cortex-M4 MCUs and MPUs programming manual.
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru