1. Вступ
На порядку денному стояло завдання розробити протокол спілкування мікроконтролера nrf52832 із двома напівмостовими китайськими тензодатчиками.
Завдання виявилося не простим, оскільки зіткнувся з відсутністю якоїсь виразної інформації. Імовірніше, що "корінь зла" знаходиться в самому SDK від Nordic Semiconductor - це постійне оновлення версій, деяка надмірність та заплутаність функціоналу. Довелося писати з нуля.
Я думаю ця тема досить актуальна виходячи з того, що даний чіп має BLE стеком і цілим набором "смак" режиму енергозбереження. Але в технічну частину я сильно заглиблюватися не буду, тому що на цю тему написано чимало статей.
2. Опис проекту
Залізо:
- 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
Висновки
Дякую всім за увагу, сподіваюся, ця стаття буде корисною і скоротить дорогоцінний час на пошук рішення для розробників. Хочу сказати, що технічний підхід, який використовує Nordic у своїх платформах, досить цікавий з точки зору енергоефективності.
PS
Проект ще в процесі розробки, тому якщо буде цікава ця тема в наступній статті, я постараюся описати алгоритм калібрування датчиків ваги, а також підключення BLE стека.
Матеріали
Джерело: habr.com