1. Wprowadzenie
Na agendzie było zadanie opracowania protokołu komunikacji mikrokontrolera nrf52832 z dwoma chińskimi czujnikami tensometrycznymi półmostkowymi.
Zadanie okazało się trudne, ponieważ natknąłem się na brak jakiejkolwiek jasnej informacji. Prawdopodobnie źródło problemu tkwi w samym SDK od Nordic Semiconductor — ciągłych aktualizacjach wersji, pewnej nadmiarowości i skomplikowanej funkcjonalności. Musiałem wszystko pisać od zera.
Uważam, że ten temat jest dość aktualny, biorąc pod uwagę, że ten chip ma stos BLE i cały zestaw funkcji oszczędzania energii. Ale nie zagłębię się zbytnio w część techniczną, ponieważ na ten temat napisano już wiele artykułów.
2. Opis projektu

Sprzęt:
- Adafruit Feather nRF52 Bluefruit LE (to, co miało pod ręką)
- ADC HX711
- Chińskie czujniki tensometryczne 2 szt. (50x2 kg)
- Programator ST-LINK V2
Oprogramowanie:
- IDE VSCODE
- NRF SDK 16
- OpenOCD
- Programator ST-LINK V2
Wszystko znajduje się w jednym projekcie, trzeba tylko nieco dostosować Makefile (wskazać lokalizację swojego SDK).
3. Opis kodu
Będziemy używać modułu GPIOTE do pracy z peryferiami w oparciu o przypisanie zadań i zdarzeń, a także modułu PPI do przesyłania danych z jednego peryferyjnego urządzenia do drugiego bez udziału procesora.
ret_code_t err_code;
err_code = nrf_drv_gpiote_out_init(PD_SCK, &config); // ustawiamy na wyjście
nrf_drv_gpiote_out_config_t config = GPIOTE_CONFIG_OUT_TASK_TOGGLE(false); // będziemy przełączać pin na impuls
err_code = nrf_drv_gpiote_out_init(PD_SCK, &config); // ustawiamy na wyjście
Ustawiamy linię synchronizacji PD_SCL na wyjście do generowania impulsów o długości 10 µs.
nrf_drv_gpiote_in_config_t gpiote_config = GPIOTE_CONFIG_IN_SENSE_HITOLO(false); // przeskok poziomu z wysokiego na niski
nrf_gpio_cfg_input(DOUT, NRF_GPIO_PIN_NOPULL); // na wejściu bez podciągania
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); // wyłączamy przerwanie
nrf_drv_timer_enable(&m_timer0); // włączamy timer
}
Ustawiamy linię danych DOUT do odczytu stanu gotowości HX711, przy niskim poziomie wyzwala się handler, w którym wyłączamy przerwanie i uruchamiamy timer do generowania synchronizujących impulsów na wyjściu 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)); // podłączamy timer do wyjścia
APP_ERROR_CHECK(err_code);
err_code = nrf_drv_ppi_channel_enable(m_ppi_channel1); // włączamy kanał
APP_ERROR_CHECK(err_code);
nrf_drv_gpiote_out_task_enable(PD_SCK); // включаем gpiote
Następnie inicjalizujemy moduł PPI i łączymy nasz timer z wyjściem PD_SCL, aby generować impulsy o długości 10 μs w momencie wystąpienia zdarzenia porównania, a także włączamy moduł GPIOTE.
nrf_drv_timer_config_t timer_cfg = NRF_DRV_TIMER_DEFAULT_CONFIG; // domyślnie
timer_cfg.frequency = NRF_TIMER_FREQ_1MHz; // taktujemy na częstotliwości 1 MHz
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); // aktywacja na porównaniuInicjalizujemy timer zerowy i jego obsługę.
if(m_counter%2 != 0 && m_counter<=48){
buffer <<= 1; // zmienna z odczytanymi danymi
c_counter++; // licznik pozytywnych impulsów
if(nrf_gpio_pin_read(DOUT)) buffer++; // odczytujemy stan wejścia
}
Najciekawsze dzieje się w obsłudze timera. Okres impulsów wynosi 20 μs. Interesują nas nieparzyste impulsy (na narastającym zboczu) i pod warunkiem, że ich liczba nie przekracza 24, a zdarzeń - 48. Przy każdym nieparzystym zdarzeniu następuje odczyt DOUT.
Z karty danych wynika, że liczba impulsów powinna wynosić co najmniej 25, co odpowiada wzmocnieniu 128 (w kodzie użyłem 25 impulsów), to równoważne 50 zdarzeniom timera, co wskazuje na zakończenie ramki danych.
++m_counter; // licznik zdarzeń
if(m_counter==50){
nrf_drv_timer_disable(&m_timer0); // wyłączamy timer
m_simple_timer_state = SIMPLE_TIMER_STATE_STOPPED; //
buffer = buffer ^ 0x800000;
hx711_stop(); // wyłączamy hx711
}
Następnie wyłączamy timer i przetwarzamy dane (zgodnie z kartą danych) oraz przełączamy HX711 w tryb niskiego zużycia energii.
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();// włączamy hx711
nrf_drv_gpiote_out_toggle(LED_1);
m_simple_timer_state = SIMPLE_TIMER_STATE_STARTED;
}
}
/**@brief Tworzenie timerów.
*\/
static void create_timers()
{
ret_code_t err_code;
// Tworzenie timerów
err_code = app_timer_create(&m_repeated_timer_id,
APP_TIMER_MODE_REPEATED,
repeated_timer_handler);
APP_ERROR_CHECK(err_code);
}
Czekamy na zdarzenie od zegara RTC co 10 s (to już od Was zależy) w obsłudze uruchamiamy HX711, wywołując przerwanie na linii DOUT.
Jest jeszcze jeden aspekt, logi są wyprowadzane przez UART (baud rate 115200, TX — pin 6, RX — pin 8) wszystkie ustawienia znajdują się w sdk_config.h.

Wnioski
Dziękuję wszystkim za uwagę, mam nadzieję, że ten artykuł będzie przydatny i skróci cenny czas potrzebny na znalezienie rozwiązania dla programistów. Chcę powiedzieć, że techniczne podejście, które wykorzystuje Nordic w swoich platformach, jest dość interesujące z punktu widzenia efektywności energetycznej.
P.S.
Projekt jest jeszcze w fazie rozwoju, dlatego jeśli ta tematyka będzie interesująca, w następnym artykule postaram się opisać algorytm kalibracji czujników wagi, a także podłączenia stosu BLE.
Materiały
Źródło: habr.com
