(非)プロトスレッドのファン専用:1-Wireを操作するための高レベル機能

ベアメタル用のファームウェアを作成することが理解されています。 そうしないと、プロトスレッドを使用しても意味がありません。 マルチタスクはOSツールによって提供される必要があります。 また、入出力操作に関連する多少複雑なアルゴリズムを実装する必要があることも理解されています。 さて、そして、いつものように、マイクロコントローラーでは、RAMと消費電力を節約するための明らかな要件があります。



例として、1-Wireバス上のデバイスを保守するタスクを考えます。 非同期プリミティブの実装については、以前の記事とこちらご覧いただけます



PnP実装の場合、プログラムは、最大許容交換レート、現在接続されているデバイスの識別子のリスト、電源の状態など、バスの特性を独立して決定できる必要があります。

その後、可能な限り迅速に高速デバイスと通信するために、最大許容為替レートを定義します。 同時に、低速のデバイスはこの交換によって「認識」されず、当社に干渉しません。



測定を実行する(またはEEPROMをプログラミングする)コマンドを発行した後、(必要に応じて)アクティブプルアップモードを有効にするには、電源の状態を知る必要があります。 そうしないと、寄生電力を使用すると、測定結果を読み取ろうとするときにエラーが発生します(おそらく、低インピーダンスのプルアップ抵抗を配置する必要がありますが、これはおそらく美しい解決策ではありません)。



さて、現在接続されているデバイスの識別子のリストは、私たちにとって非常に重要です。 それ以外の場合、どのように対処しますか?



バスの特性を決定するアルゴリズム:

  1. OVERDRIVEモードでRESET手順を実行してみましょう。 PRESENCEが検出された場合、接続されたデバイスの少なくとも一部は高速で動作できます。 この場合、手順3に進みます。
  2. 通常モードでRESETプロシージャを実行してみましょう。 PRESENCEが検出された場合、1-Wireバス上に少なくとも1つの接続されたデバイスがあり、ステップ3に進みます。 それ以外の場合、現在バスに接続されているデバイスはありません。
  3. 「すべてのデバイスのアドレス指定」コマンドをバスに送信してから、「電力条件の読み取り」コマンドを送信します。 接続されたデバイスの少なくとも1つが寄生電力モードを使用している場合、

    対応するフラグを設定します。


しかし、接続されたデバイスの識別子を決定するアルゴリズムは非常に面倒です。 同期実装はアプリケーションノート187に記載されていますが、非同期に作り直しただけです。



アルゴリズムの実行のすべての段階で、1-Wireバス上の干渉の出現により発生する可能性のあるエラーを監視することが望ましいです。 エラーの発生ポイントに応じて、進行中の操作を自動的に繰り返すか、否定的な完了ステータスを返すことができます。



さらに、読者はプロトスレッドに関する最小限の知識しか持っていないことが想定されています。 英語のテキストを理解するのが難しいと思う人は誰でも、最初にここここを読むことができます



スポイラーの下には、バスパラメータを決定し、メインプログラムから接続されたデバイスを検出するためのプロシージャを呼び出す例があります。



主なプログラム
PT_INIT(&ptSearchContext.pt); /*          */ while(PT_SCHEDULE(c = ptOneWireProbeBus(&ptSearchContext.pt, &nested))) { if(PT_WAITING == c) { /*    ,   -  */ waitComplete(); continue; } } /*      */ ptOneWireInitWalkROM(&ptSearchContext); /*      1-Wire  */ while(PT_SCHEDULE(c = ptOneWireWalkROM(&ptSearchContext))) { if(PT_WAITING == c) { /*    ,   -  */ waitComplete(); continue; } /*   1-Wire   . *  S/N    ptSearchContext.romid */ __no_operation(); } /*    */ __no_operation();
      
      







これはデモです。 実際のプログラムでは、waitComplete()関数を呼び出す代わりに、他のプロトスレッドのサービスに切り替えることができます(そうでない場合は、低電力モードに入ります)。



実装で使用されるマクロ
 #define OVERDRIVE() \ drv_onewire_context.overdrive #define PRESENCE_DETECTED() \ drv_onewire_context.presence #define PARASITE_POWER \ drv_onewire_context.parasite #define STATUS \ drv_onewire_context.status #define PT_WAIT_IO_COMPLETE() \ PT_WAIT_WHILE(TASK_CONTEXT, ONEWIRE_STATUS_PROGRESS == (dummy = drvOneWireStatus())) #define IO_SUCCESS() \ (ONEWIRE_STATUS_COMPLETE == dummy) #define PT_TX_BITS(_v,_n) do { \ if(drvOneWireTxBits((_v),(_n))) { \ PT_WAIT_IO_COMPLETE(); } else { \ dummy = ONEWIRE_STATUS_ERROR; } } while(0) #define PT_TX_BYTE(_v) \ PT_TX_BITS((_v), 8) #define PT_RX_BITS(_n) \ PT_TX_BITS(~0,(_n)) #define PT_TX_BYTE_CONST(_v) do { \ PT_TX_BYTE((_v)); \ if(!IO_SUCCESS() || ((_v) != drvOneWireRxBits(8))) { \ STATUS = ONEWIRE_STATUS_ERROR; } } while(0)
      
      







短い説明:



PT_WAIT_IO_COMPLETE()

I / O操作が完了するのを待っています。 プロトスレッド内でのみ使用するように設計されています。



PT_TX_BITS(_v、_n)

_vの値から_nビットをバスに転送し、I / O操作が完了するのを待ちます。 プロトスレッド内でのみ使用するように設計されています。



PT_TX_BYTE(_v)

_vバイトをバスに転送して、I / O操作が完了するのを待ちます。 プロトスレッド内でのみ使用するように設計されています。



PT_RX_BITS(_n)

I / O操作が完了するまで待機する_nビットを受信します。 プロトスレッド内でのみ使用するように設計されています。



PT_TX_BYTE_CONST(_v)

コマンドバイト(定数)をバスに送信し、送信データの歪みをチェックし、I / O操作の完了を待ちます。 プロトスレッド内でのみ使用するように設計されています。



この場合の「入力入力の完了を待機する」とは、何らかの条件のチェックを伴う鈍いサイクルではなく、ステータスPT_WAITINGの現在のプロトスレッドの中断を意味することに注意してください。 これにより、アクティブ化されたI / O操作が終了するまで電流を定期的にチェックして、他のプロトスレッドを実行できます。



すべてのデバイスのアドレス指定コマンドの送信
 PT_THREAD(ptOneWireTargetAll(struct pt * _pt)) { uint8_t dummy; PT_BEGIN(TASK_CONTEXT); PT_TX_BYTE_CONST(OP_SKIP_ROM); PT_END(TASK_CONTEXT); }
      
      







すべてのデバイスをアドレス指定する操作は、1-Wireバスの他のコマンドで使用できるため、独立したプロトスレッドとしてフレーム化されました。



タイヤ定義手順
 PT_THREAD(ptOneWireProbeBus(struct pt * _pt, struct pt * _nested)) { uint8_t dummy; PT_BEGIN(TASK_CONTEXT); /* Parasite power not detected */ PARASITE_POWER = 0; /* Try overdrive procedure first */ if(drvOneWireReset(1)) { PT_WAIT_IO_COMPLETE(); if(!IO_SUCCESS() || !PRESENCE_DETECTED()) { /* Overdrive RESET procedure failed */ if(drvOneWireReset(0)) { PT_WAIT_IO_COMPLETE(); if(!IO_SUCCESS() || !PRESENCE_DETECTED()) { /* No devices on the bus */ PT_EXIT(TASK_CONTEXT); } } else { /* Hardware BUSY */ PT_EXIT(TASK_CONTEXT); } } } else { /* Hardware BUSY */ PT_EXIT(TASK_CONTEXT); } PT_SPAWN(TASK_CONTEXT, _nested, ptOneWireTargetAll(_nested)); if(ONEWIRE_STATUS_COMPLETE == STATUS) { PT_TX_BYTE_CONST(OP_READ_POWER_SUPPLY); if(IO_SUCCESS()) { /* Read one bit after command */ PT_RX_BITS(1); if(IO_SUCCESS()) { /* Fetch bit value */ int16_t value = drvOneWireRxBits(1); if(value < 0) { /* Rx bit decode failed */ STATUS = ONEWIRE_STATUS_ERROR; } else { /* If any device sent "0" then it used parasite power */ PARASITE_POWER = value ? 0 : 1; } } } } PT_END(TASK_CONTEXT); }
      
      







コードは非常にシンプルで、上記のアルゴリズムを実装しています



接続されたデバイスの識別子を決定する手順
 PT_THREAD(ptOneWireWalkROM(pt_onewire_search_context_t * _ctx)) { PT_BEGIN(TASK_CONTEXT); while(!LAST_DEVICE_FLAG) { int16_t dummy; /* initialize for search */ ID_BIT_NUMBER = 1; LAST_ZERO = 0; ROM_BYTE_NUMBER = 0; ROM_BYTE_MASK = 1; /* 1-Wire reset (dependent on OVERDRIVE flag) */ PT_ONEWIRE_RESET(); if(!IO_SUCCESS() || !PRESENCE_DETECTED()) { // reset the search LAST_DISCREPANCY = 0; LAST_DEVICE_FLAG = 0; LAST_FAMILY_DISCREPANCY = 0; /* If presence not detected then no devices on the bus */ PT_EXIT(TASK_CONTEXT); } /* issue the search command */ PT_TX_BYTE(OP_SEARCH_ROM); if(!IO_SUCCESS() || (OP_SEARCH_ROM != drvOneWireRxBits(8))) { /* Send command error, repeat procedure from RESET point */ /* Other solution is abort search procedure */ continue; } // loop to do the search do { // read a bit and its complement PT_RX_BITS(2); if(!IO_SUCCESS()) { /* Error while receiving 2 bits. * As ID_BIT_NUMBER less than 65 search procedure * resumed from state such as original task entry. */ break; } if((RX_VALUE = drvOneWireRxBits(2)) < 0) { __no_operation(); break; } uint8_t id_bit = (RX_VALUE & 0x01) ? 1 : 0; uint8_t cmp_id_bit = (RX_VALUE & 0x02) ? 1 : 0; uint8_t search_direction; /* check for no devices on 1-wire */ if ((id_bit == 1) && (cmp_id_bit == 1)) { /* Same bit values equ "1" indicate no devices on the bus */ break; } else { /* all devices coupled have 0 or 1 */ if (id_bit != cmp_id_bit) { search_direction = id_bit; /* bit write value for search */ } else { /* if this discrepancy if before the LAST_DISCREPANCY on a previous next then pick the same as last time */ if (ID_BIT_NUMBER < LAST_DISCREPANCY) { search_direction = (ROMID_BYTE_REF(ROM_BYTE_NUMBER) & ROM_BYTE_MASK) ? 1 : 0; } else { /* if equal to last pick 1, if not then pick 0 */ search_direction = (ID_BIT_NUMBER == LAST_DISCREPANCY) ? 1 : 0; } /* if 0 was picked then record its position in LAST_ZERO */ if (search_direction == 0) { LAST_ZERO = ID_BIT_NUMBER; } /* check for LAST_FAMILY_DISCREPANCY in family */ if (LAST_ZERO < 9) { LAST_FAMILY_DISCREPANCY = LAST_ZERO; } } } /* set or clear the bit in the ROM byte ROM_BYTE_NUMBER with mask rom_byte_mask */ if (search_direction == 1) { ROMID_BYTE_REF(ROM_BYTE_NUMBER) |= ROM_BYTE_MASK; } else { ROMID_BYTE_REF(ROM_BYTE_NUMBER) &= ~ROM_BYTE_MASK; } /* serial number search direction write bit */ PT_TX_BITS(search_direction, 1); /* search_direction not stored, therefore we can't check echo */ if(!IO_SUCCESS()) { /* Sending direction failed. * As ID_BIT_NUMBER less than 65 search procedure * resumed from state such as original task entry. */ break; } /* increment the byte counter ID_BIT_NUMBER and shift the mask rom_byte_mask */ ID_BIT_NUMBER++; ROM_BYTE_MASK <<= 1; /* if the mask is 0 then go to new SerialNum byte ROM_BYTE_NUMBER and reset mask */ if (ROM_BYTE_MASK == 0) { ROM_BYTE_NUMBER++; ROM_BYTE_MASK = 1; } } while(ROM_BYTE_NUMBER < 8); /* loop until through all ROM bytes 0-7 */ /* if the search was successful then */ if(!(ID_BIT_NUMBER < 65)) { uint8_t i; /* Calculate CRC */ uint8_t crc = 0; for(i = 0;i < sizeof(ROMID);i++) { crc = modOneWireUpdateCRC(crc, ROMID_BYTE_REF(i)); } if(crc) { /* CRC error. * Repeat procedure from original point */ continue; } /* search successful so set LAST_DISCREPANCY and LAST_DEVICE_FLAG */ LAST_DISCREPANCY = LAST_ZERO; // check for last device if (LAST_DISCREPANCY == 0) { LAST_DEVICE_FLAG = 1; } /* Next device detection complete */ } else { /* I/O error. * Retry procedure from original point */ continue; } if(!ROMID.id.familyCode) { /* familyCode     0! */ break; } /* Return detected device S/N */ PT_YIELD(TASK_CONTEXT); } /* Reset state for next scan loop (if need) */ ptOneWireInitWalkROM(CONTEXT); PT_END(TASK_CONTEXT); } /* * Initialize device search procedure */ void ptOneWireInitWalkROM(pt_onewire_search_context_t * _ctx) { /* Prepare ptOneWireWalkROM() for first call */ LAST_DISCREPANCY = 0; LAST_DEVICE_FLAG = 0; LAST_FAMILY_DISCREPANCY = 0; /* Initialize protothreads data */ PT_INIT(TASK_CONTEXT); }
      
      







マキシムから同期実装を取得し、I / Oルーチンを非同期マクロに置き換えました。 このすべてと、実行中のテストケースには、約30分かかりました。 プロトスレッドラッパーを使用しないと、どれだけ混乱する必要があるのでしょうか。



STM8L-Discoveryボードの完全なプロジェクトソースコードはgithubで入手できます。 コンパイル時に上記の例を使用してアセンブリを作成するには、HIGH_LEVELシンボルを定義する必要があります。



参照:



  1. Githubプロジェクトコード
  2. STM8LおよびSTM32用のPWMおよびICPを使用して1-Wireマスターを実装するためのプリミティブ
  3. AVR AtMegaマイクロコントローラーでPWMおよびICPを使用して1-Wireマスターを実装するためのプリミティブ
  4. アダムダンケルのプロトスレッド
  5. 連続性ベースのマイクロコントローラーでの ldir マルチタスクからのHDR
  6. LifeV Protothread Habrと協調マルチタスク
  7. アプリケーションノート187。1-Wire検索アルゴリズム



All Articles