NRF51822BLEずRTOSの友人





技術の珟代のトレンドは、より高床な統合の道を歩んでいたす。進歩により、電子デバむスの開発プロセスを蚭蚈者の組み立おにたすたす近づけるこずができたす。 最も顕著な䟋の1぀は、いわゆる「チップオンシステム」の膚倧な数です-SoC、ほずんどあらゆる奜みのマむクロコントロヌラヌず呚蟺機噚の束。 今日は、これらのシステムの1぀であるNordic SemiconductorチップNRF51822に泚目したす。これは、Bluetooth 4.0暙準に含たれるBLEテクノロゞヌず連携するように適合されたシステムの゜リュヌションです。



たた、電子デバむスはたすたす統合のレベルを高めおいるので、プログラミングでより高いレベルの抜象化にたで螏み蟌んで、リアルタむムオペレヌティングシステムであるRTOSを実行するBluetoothアプリケヌションを䜜成しおみたせんか。

この蚘事の目暙は、RTOS Keil-RTXを実行するNRF51822の簡単な組み蟌みアプリケヌションを䜜成し、1秒に1回ADCをク゚リし、受信した倀をBLE特性に曞き蟌むこずです。 このトピックに興味がある堎合-catぞようこそ。



RTOSずBLEに぀いお簡単に



RTOSに぀いお倚くのこずが蚀われおいるので、繰り返さないために、 ここずここでそれに぀いお読むこずをお勧めしたす。 RTOSを䜿甚するこずの重芁な機胜のうち、コヌドに混乱を加えるこずなくプログラムの機胜を非垞に簡単に向䞊できるこずだけに泚意したいず思いたす。 これは、プログラムの個々のセクションが互いに干枉するこずなく条件付きで同時に動䜜し、分離されたブロックずしお実行できるために可胜です。 そしお、倚くが実行時に芁求される倚数の異なるタスクを実行するこずが保蚌されおいる「システムオンチップ」甚のアプリケヌションを䜜成する堎合、RTOSを䜿甚するこずで生掻が倧幅に簡玠化されたす。



BLEの抂芁ずその仕組みに぀いおは、 䞀連のすばらしい蚘事があり、ずりわけ、いく぀かのこずを理解するのに圹立ちたした。 ただ読んでいない堎合は、必ずチェックしおください。 この蚘事のフレヌムワヌクでは、このテクノロゞヌを䜿甚する実甚的なニュアンスに興味を持っおいたす。



䞀般的に蚀っお、「私たちはBLEずRTOSの友達です」ず曞いたので、ある意味で、これら2぀の抂念は参加しなくおも非垞にうたくいくので、私は少しcになりたした。 事実、コントロヌラヌでBLE芏栌を䜿甚するために䜿甚するBluetoothスタックは、それ自䜓が䞀皮のRTOSであり、タスク、これらのタスクのディスパッチャヌ、タむマヌが刻々ず過ぎ、むベントが発生するずいう事実です。 したがっお、私の個人的な意芋では、BLEアプリケヌションでのRTOSの䜿甚は、アプリケヌション党䜓を抜象化の1぀のレベルに導くので、良い圢になるでしょう。



必芁なツヌル



NRF51の起動に関する基本的な問題に觊れないようにするために、Comrade Foxekの蚘事に䟝存するため、同じツヌルが必芁になりたす。



•デバッグボヌドNRF51

•OC Androidを実行しおいるスマヌトフォン

•nRF Connect Androidアプリ

•MKD ARM Keil uVision



RTOSセットアップ



叀兞的な方法で開始したす-Pack Installerを䜿甚しおble_app_templateテンプレヌトプロゞェクトをロヌドしこの手順の詳现に぀いおは、 nRF51822クむックスタヌトの蚘事を参照、Run-Time Environmentマネヌゞャヌを䜿甚しお必芁な䟝存関係を远加したす。







そこで、プロゞェクトにRTOSを远加したした。 さお、鉄ず盞互䜜甚できるように、チュヌニングを行う必芁がありたす。 CMSIS-RTOSは、Cortex-Mコアに基づいたプロセッサでの動䜜によく適合しおおり、原則ずしお、SysTickシステムタむマヌを䜿甚しおOSの時間を量子化したす。 ただし、NRF51コントロヌラヌにはボヌド䞊にCortex-M0コアがあり、SysTickタむマヌがないため、メヌカヌはRTOSクロッキングにRTC1リアルタむムクロックを䜿甚するこずをお勧めしたす。 䞀方では、RTC1はコントロヌラヌのスリヌプモヌドでオフにならないため、この゜リュヌションはプラスです。぀たり、RTOSで非垞に䜎い消費でアプリケヌションを構築できるこずを意味したす。 䞀方、RTCブロックは32768 Hzの呚波数でクロックされたす。これは、システム時間のクォンタムのサむズを十分に小さくできないこずを意味したす。



UPD。 ランタむム環境マネヌゞャヌのapp_timerモゞュヌルの[nRF_Libraries]タブで、[Rtx]オプションを遞択したす。 そうしないず、割り蟌みハンドラRTC1の耇数の定矩ず競合したす。



OSで動䜜するタむマヌを理解させるために、いく぀かの関数を远加する必芁がありたす。タむマヌず、いく぀かのむベントハンドラヌずタむマヌ割り蟌みを初期化したす。 これを行うには、CMSISタブからRTX_Conf_CM.cファむルを開き、







ヘッダヌファむルnrf.hを远加し、グロヌバル関数セクションを芋぀けお、その䞭の関数の内容を次のものに眮き換えたす。



゜ヌスコヌド
/*---------------------------------------------------------------------------- * Global Functions *---------------------------------------------------------------------------*/ /*--------------------------- os_idle_demon ---------------------------------*/ #define TIMER_MASK 0xFFFFFF volatile unsigned int rtos_suspend; /// \brief The idle demon is running when no other thread is ready to run void os_idle_demon (void) { unsigned int expected_time; unsigned int prev_time; NVIC_SetPriority(PendSV_IRQn, NVIC_GetPriority(RTC1_IRQn)); for (;; ) { rtos_suspend = 1; expected_time = os_suspend(); expected_time &= TIMER_MASK; if (expected_time > 2) { prev_time = NRF_RTC1->COUNTER; expected_time += prev_time; NRF_RTC1->CC[0] = (expected_time > TIMER_MASK) ? expected_time - TIMER_MASK : expected_time; NRF_RTC1->INTENCLR = RTC_INTENSET_TICK_Msk; NVIC_EnableIRQ(RTC1_IRQn); __disable_irq(); if (rtos_suspend) { NRF_RTC1->INTENSET = RTC_INTENSET_COMPARE0_Msk; __WFI(); NRF_RTC1->EVENTS_COMPARE[0] = 0; NRF_RTC1->INTENCLR = RTC_INTENSET_COMPARE0_Msk; } __enable_irq(); NRF_RTC1->INTENSET = RTC_INTENSET_TICK_Msk; expected_time = NRF_RTC1->COUNTER; expected_time = (expected_time >= prev_time) ? expected_time - prev_time : TIMER_MASK - prev_time + expected_time; } os_resume(expected_time); } } #if (OS_SYSTICK == 0) // Functions for alternative timer as RTX kernel timer /*--------------------------- os_tick_init ----------------------------------*/ /// \brief Initializes an alternative hardware timer as RTX kernel timer /// \return IRQ number of the alternative hardware timer int os_tick_init (void) { NRF_CLOCK->LFCLKSRC = (CLOCK_LFCLKSRC_SRC_Xtal << CLOCK_LFCLKSRC_SRC_Pos); NRF_CLOCK->EVENTS_LFCLKSTARTED = 0; NRF_CLOCK->TASKS_LFCLKSTART = 1; while (NRF_CLOCK->EVENTS_LFCLKSTARTED == 0) { // Do nothing. } NRF_RTC1->PRESCALER = 32;//OS_TRV; NRF_RTC1->INTENSET = RTC_INTENSET_TICK_Msk; NRF_RTC1->TASKS_START = 1; return (RTC1_IRQn); /* Return IRQ number of timer (0..239) */ } /*--------------------------- os_tick_val -----------------------------------*/ /// \brief Get alternative hardware timer's current value (0 .. OS_TRV) /// \return Current value of the alternative hardware timer uint32_t os_tick_val (void) { return NRF_RTC1->COUNTER; } /*--------------------------- os_tick_ovf -----------------------------------*/ /// \brief Get alternative hardware timer's overflow flag /// \return Overflow flag\n /// - 1 : overflow /// - 0 : no overflow uint32_t os_tick_ovf (void) { return NRF_RTC1->EVENTS_OVRFLW; } /*--------------------------- os_tick_irqack --------------------------------*/ /// \brief Acknowledge alternative hardware timer interrupt void os_tick_irqack (void) { if ((NRF_RTC1->EVENTS_TICK != 0) && ((NRF_RTC1->INTENSET & RTC_INTENSET_TICK_Msk) != 0)) { NRF_RTC1->EVENTS_TICK = 0; } } #if defined (__CC_ARM) /* ARM Compiler */ __asm __declspec(noreturn) void RTC1_IRQHandler(void) { EXTERN OS_Tick_Handler BL OS_Tick_Handler } #else #error "Unknown compiler! Don't know how to create SVC function." #endif #endif // (OS_SYSTICK == 0)
      
      









os_idle_demon関数は、省゚ネモヌドを䜿甚するずきのシステムの動䜜を正確に担圓したす。アプリケヌションがタスクでビゞヌでない堎合、システム自䜓がコントロヌラヌをスリヌプ状態にしたす。



次に、システムにSysTickを䜿甚しないこずを䌝えたす。これには、すべおの機胜が既に備わっおいるため、同時に、クロックが動䜜する呚波数ずシステム時間を量子化する方法を指定するためです。 これを行うには、同じファむルRTX_Conf_CM.cで構成ナヌティリティを開き、適切なシステムパラメヌタヌを蚭定したす。







10 msのシステム読み取り間隔は偶然遞択されたせんでした32768 Hzのクロック呚波数で327スプリッタヌを䜿甚するず、タむマヌ呚波数は99.9 Hzになり、少なくずもある皋床の劥圓な粟床でシステムタむミングを提䟛したす。 この間隔に䜎い倀を䜿甚する必芁がある堎合は、蚱容可胜な粟床を䞎える倀を遞択できたすたずえば、陀数31および間隔長977ÎŒsが、これらは非円圢の数倀になり、コヌドに混乱をもたらしたす。 たた、非暙準のスタックサむズでストリヌムを䜜成し、これらのストリヌムにメモリを割り圓お、スタックの透かしを刻むこずもできたす。これにより、デバッグがより芖芚的になりたす。



この段階で、プログラムを組み立おおコントロヌラヌにダりンロヌドするこずができたす。 オペレヌティングシステムが起動し、メむン関数を唯䞀のスレッドずしお実行し始めたす。 ただし、RTOSの意味は、さたざたなタスクがさたざたなスレッドでスピンするこずなので、BLEスタック甚に独自のスレッドを䜜成したす。



 //      ble_stack_thread osPoolDef(ble_evt_pool, 8, ble_evt_t); //   osPoolId ble_evt_pool; //    osMessageQDef(ble_stack_msg_box, 8, ble_evt_t); //    osMessageQId ble_stack_msg_box; //     osThreadId ble_stack_thread_id; //      void ble_stack_thread(void const * arg); //     osThreadDef (ble_stack_thread, osPriorityAboveNormal, 1, 400);
      
      





ここで、スタックが䜿甚するメモリプヌル、スタックがシステムからメッセヌゞを受信するメヌルボックス、およびストリヌム自䜓を決定し、その番号ず実行される関数を宣蚀したした。 スレッドが䜕をするかを決定するだけでなく、システムおよびスタックむベントハンドラヌでその䜜成関数を宣蚀したす。



゜ヌスコヌド
 //     BLE void ble_stack_thread(void const * arg) { uint32_t err_code; osEvent evt; ble_evt_t * p_ble_evt; UNUSED_PARAMETER(arg); while (1) { evt = osMessageGet(ble_stack_msg_box, osWaitForever); // wait for message if (evt.status == osEventMessage) { p_ble_evt = evt.value.p; switch (p_ble_evt->header.evt_id) { case BLE_GAP_EVT_CONNECTED: err_code = bsp_indication_set(BSP_INDICATE_CONNECTED); APP_ERROR_CHECK(err_code); m_conn_handle = p_ble_evt->evt.gap_evt.conn_handle; break; case BLE_GAP_EVT_DISCONNECTED: m_conn_handle = BLE_CONN_HANDLE_INVALID; break; default: // No implementation needed. break; } (void)osPoolFree(ble_evt_pool, p_ble_evt); } } } //      BLE void ble_create_thread (void) { ble_evt_pool = osPoolCreate(osPool(ble_evt_pool)); ble_stack_msg_box = osMessageCreate(osMessageQ(ble_stack_msg_box), NULL); ble_stack_thread_id = osThreadCreate(osThread(ble_stack_thread), NULL); }
      
      







ご芧のずおり、on_ble_evtむベントハンドラヌの暙準関数は、ストリヌムぞの単玔なメッセヌゞ転送に眮き換えられおいたす。この内郚では、ハンドラヌが実行に䜿甚するロゞックが既に構成されおいたす。



システムにストリヌムを登録するには、メモリプヌルずメヌルボックスを䜜成する関数を䜜成したす。その埌、ストリヌムを䜜成し、少し前に宣蚀した識別子で登録したす。 これをすべおmainに远加しお実行するだけです。



゜ヌスコヌド
 int main(void) { uint32_t err_code; bool erase_bonds; static osStatus status; //   status = osKernelInitialize(); //  timers_init(); ble_stack_init(); device_manager_init(erase_bonds); gap_params_init(); advertising_init(); services_init(); conn_params_init(); //     err_code = ble_advertising_start(BLE_ADV_MODE_FAST); APP_ERROR_CHECK(err_code); ble_create_thread(); //   status = osKernelStart(); }
      
      







関数osKernelInitializeおよびosKernelStartに泚意しおください-mainはすでにストリヌムであるため、入力されるたでにRTOSはすでに実行されおいたす。 ただし、スレッドを䜜成し、停止したOSで呚蟺党䜓を構成する方がよいため、最初の機胜を䞀時停止し、2番目の機胜を再開しお動䜜させたす。



さらに、bsp_bleに関連するものはすべお、プロゞェクトから削陀したした。このラむブラリはRTOSをバむパスするスタックに干枉しようずしおいるためです。 メむンはストリヌムであるため、メむンルヌプ自䜓もメむンから削陀したした。すべおの初期化の埌、メモリを予玄したたたにする必芁はありたせん。 したがっお、䞻な目的、およびシステムが動䜜し続ける、これらは奇跡です。



これで、プログラムをビルドし、コントロヌラヌにダりンロヌドしお、nRF Connectプログラムを䜿甚しおデバむスに接続できたす。







ご芧のずおり、ただ単䞀のサヌビスはありたせんので、修正する時が来たした。 ご存知のように、ADCからデヌタを読み取り、サヌビスの特性に曞き蟌みたす。 繰り返しお自分の自転車を構築しないように、ここからサヌビスず機胜を䜜成するためのテンプレヌトを䜿甚したす 。



゜ヌスコヌド
 //    void services_init(void) { ble_uuid_t ble_uuid; /*  128 -  UUID */ ble_uuid128_t base_uuid = ADC_BASE_UUID; uint8_t uuid_type; ble_uuid.type = BLE_UUID_TYPE_VENDOR_BEGIN; ble_uuid.uuid = ADC_SERVICE_UUID; sd_ble_uuid_vs_add(&base_uuid, &ble_uuid.type); sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, &ble_uuid, &adc_handles); } /*    * UUID -   * handles -    (  ) * n_bytes -    * iswrite, isnotf, isread -   , ,  */ uint32_t char_add(uint16_t UUID, ble_gatts_char_handles_t * handles, uint8_t n_bytes, bool iswrite, bool isnotf, bool isread) { ble_gatts_char_md_t char_md; ble_gatts_attr_md_t cccd_md; ble_gatts_attr_t attr_char_value; ble_uuid_t char_uuid; ble_gatts_attr_md_t attr_md; memset(&cccd_md, 0, sizeof(cccd_md)); BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.read_perm); BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.write_perm); cccd_md.vloc = BLE_GATTS_VLOC_STACK; memset(&char_md, 0, sizeof(char_md)); char_md.char_props.notify = isnotf; //   ; char_md.char_props.write = iswrite; //   ; char_md.char_props.read = isread; //   ; char_md.p_char_user_desc = NULL; char_md.p_char_pf = NULL; char_md.p_user_desc_md = NULL; char_md.p_cccd_md = &cccd_md; char_md.p_sccd_md = NULL; /*  UUID - 128 -  */ char_uuid.type = BLE_UUID_TYPE_VENDOR_BEGIN; char_uuid.uuid = UUID; memset(&attr_md, 0, sizeof(attr_md)); BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.read_perm); BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.write_perm); attr_md.vloc = BLE_GATTS_VLOC_STACK; attr_md.rd_auth = 0; attr_md.wr_auth = 0; attr_md.vlen = 0; attr_char_value.p_uuid = &char_uuid; attr_char_value.p_attr_md = &attr_md; attr_char_value.init_len = n_bytes; attr_char_value.init_offs = 0; attr_char_value.max_len = n_bytes; //  ; attr_char_value.p_value = NULL; //   ; /*     */ sd_ble_gatts_characteristic_add(adc_handles, &char_md, &attr_char_value, handles); return 0; }
      
      







ここで、ADC_BASE_UUIDずADC_SERVICE_UUIDは、次の圢匏で定矩された耇合サヌビス識別子です。



 /*  UUID (  UUID    ) */ #define ADC_BASE_UUID {0x66, 0x9A, 0x0C, 0x20, 0x00, 0x08, 0x1A, 0x8F, 0xE7, 0x11, 0x61, 0xBE, 0x00, 0x00, 0x00, 0x00} /*  UUID (  UUID    ) */ #define ADC_SERVICE_UUID 0x1533 #define ADC_CHAR_UUID 0x1534
      
      





これで、1バむトのデヌタを含む1぀の特性を持぀サヌビスができたした。 このバむトを有甚な情報で埋めるだけであるため、 蚘事を参照しおADCを初期化したす。ADCは1秒間に1回倉換を実行し、特性の内容を曎新する関数を远加したす。



゜ヌスコヌド
 //    void ADC_init (void) { //  2       8  NRF_ADC->CONFIG = 0x00; NRF_ADC->CONFIG |= (ADC_CONFIG_RES_8bit << ADC_CONFIG_RES_Pos)| (ADC_CONFIG_INPSEL_AnalogInputOneThirdPrescaling << ADC_CONFIG_INPSEL_Pos)| (ADC_CONFIG_PSEL_AnalogInput2 << ADC_CONFIG_PSEL_Pos); NRF_ADC->INTENSET |= ADC_INTENSET_END_Enabled << ADC_INTENSET_END_Pos; NRF_ADC->ENABLE |= ADC_ENABLE_ENABLE_Enabled << ADC_ENABLE_ENABLE_Pos; //     NVIC_SetPriority(ADC_IRQn, 1); NVIC_EnableIRQ(ADC_IRQn); //      NRF_TIMER1->POWER = 1; NRF_TIMER1->MODE = TIMER_MODE_MODE_Timer << TIMER_MODE_MODE_Pos; NRF_TIMER1->PRESCALER = 10; // 16 MHz / 2^10 = 15625 Hz NRF_TIMER1->CC[0] = 15625; // 15625 Hz / 15625 = 1 Hz NRF_TIMER1->INTENSET = (TIMER_INTENSET_COMPARE1_Enabled << TIMER_INTENSET_COMPARE1_Pos); NRF_TIMER1->SHORTS |= (TIMER_SHORTS_COMPARE0_CLEAR_Enabled << TIMER_SHORTS_COMPARE0_CLEAR_Pos); //   PPI       NRF_PPI->CH[0].EEP = (uint32_t) &NRF_TIMER1->EVENTS_COMPARE[0]; NRF_PPI->CH[0].TEP = (uint32_t) &NRF_ADC->TASKS_START; NRF_PPI->CHEN |= PPI_CHEN_CH0_Enabled; NRF_PPI->CHENSET |= PPI_CHENSET_CH0_Enabled; NRF_PPI->TASKS_CHG[0].EN = 1; NRF_TIMER1->TASKS_START = 1; } //    void ADC_IRQHandler(void) { rtos_suspend = 0; NRF_ADC->EVENTS_END = 0; if (NRF_ADC->CONFIG >> ADC_CONFIG_PSEL_Pos == ADC_CONFIG_PSEL_AnalogInput2) { NRF_TIMER1->EVENTS_COMPARE[0] = 0; adc_val = NRF_ADC->RESULT; osSignalSet(char_update_thread_id, 1<<CharUpdateSignal); } }
      
      







割り蟌みハンドラには次のような構造があるこずに泚意しおください

rtos_suspend = 0;

rtos_suspend倉数自䜓はRTOS構成ファむルで定矩され、main.sに゚クスポヌトされたす。 オペレヌティングシステムは割り蟌みを制埡しないため、この倉数は、システムが珟時点でアむドル状態ではなく割り蟌みハンドラヌにあるこずをプログラムが理解するために必芁です。぀たり、省゚ネ機胜を今すぐアクティブにしないでください。 それ以倖の堎合、システムの動䜜は予枬できないたたなので、各割り蟌みハンドラヌがこの倉数をれロにするこずを匷くお勧めしたす。



割り蟌みハンドラヌからスタックでアクションを実行するこずは䞍可胜なのでこの堎合、特性を曎新したす、これを行うスレッドを䜜成できたす。 割り蟌みからのフロヌず察話するために、信号などのツヌルがありたす。 本質的に、シグナルこれらは蚭定およびリセットできるフラグ倉数の単なるビットですは、割り蟌みに非垞に䌌おいたす。 特性ずそのアラヌト信号を蚘録するためのストリヌムを䜜成し、メむンに远加したす。



糞
 //       void char_update_thread (void const* arg); //      osThreadId char_update_thread_id; //      osThreadDef(char_update_thread, osPriorityNormal, 1, 0); //    int32_t CharUpdateSignal = 0x01; //     void char_update_thread (void const* arg) { while(1) { osSignalWait(1<<CharUpdateSignal,osWaitForever); ble_char_update(&adc_val,1,adc_char_handles.value_handle); } }
      
      







メむン
 int main(void) { uint32_t err_code; bool erase_bonds; static osStatus status; //   status = osKernelInitialize(); //  timers_init(); ble_stack_init(); device_manager_init(erase_bonds); gap_params_init(); advertising_init(); services_init(); char_add(ADC_CHAR_UUID,&adc_char_handles, 1, false, false, true); conn_params_init(); //     err_code = ble_advertising_start(BLE_ADV_MODE_FAST); APP_ERROR_CHECK(err_code); //   ADC_init(); //   ble_create_thread(); char_update_thread_id = osThreadCreate(osThread(char_update_thread),NULL); //   status = osKernelStart(); }
      
      







これで準備完了です。 プログラムをコントロヌラヌにロヌドしお、取埗したものを確認できたす。









ご芧のずおり、1バむトの倀を栌玍する1぀の特性を持぀サヌビスがありたす。 これを読み取っお、ADCの入力での電圧倉化に応じお倀が倉化するこずを確認できたす。 この特性を通知するこずも可胜で、デバむスの特性のデヌタが倉曎された堎合、スマヌトフォンで自動的に曎新されたす。



おわりに



結論ずしお、䞀芋RTOSはプロゞェクトに䞍必芁な耇雑さをもたらしおいるように芋えたすが、これは完党に真実ではありたせん。 実際、䜜成したアプリケヌションは、すべおのタスク甚のメモリがなくなるたで簡単にスケヌリングできたす。 新しいモゞュヌルごずに、䜕らかの数孊的な操䜜であろうず呚蟺機噚ず連携する堎合であろうず、独自のスレッドを䜜成するだけで十分です。プログラマヌが考える必芁があるのは、タスクの優先順䜍付け方法です。セマフォずラッチを䜿甚しお同期を構築するための同じリ゜ヌス。



参照資料



RTOS BLEプロゞェクト

Keil-RTX Webペヌゞ

nRF51822の抂芁クむックスタヌト

nRF51822の抂芁省゚ネルギヌず䞀郚の呚蟺機噚

NRF51822補品ペヌゞ



All Articles