一度気象ステーションを建設することに決めました。 センサーは、I2Cバスを含め、そこで異なります。 そして、通常は最初はどれだけ良い結果が期待の旗にすべてをしました。 しかし、実際のジェダイの進路は異なり、中断のためにすべてを停止することにしました。 これがhemoの始まりです。 私が遭遇した問題は、いくつかの連続したリクエストを処理していることです。 たとえば、
BMP085圧力
センサーをさらに使用するには、EEPROMから11個のキャリブレーション定数を
取得する必要があります。
決定に至った経緯と一連の考えを以下に概説します。
待機フラグ
関数
get_AC1、get_AC2、...、get_MDを定義します。 それらはそれぞれ、I2Cバスを介してEEPROMセンサーから対応する定数を受け取ります。 データシートからの小包形式:
get_AC1関数のサンプルコード:
get_AC1int get_AC1(void) { union { unsigned int Word; unsigned char Byte[2]; } AC1;
そして、彼女の波形は次のとおりです。
AC1の共用体の使用は、バイト順の違いによるものです。 TWIではビッグエンディアン、AVRではリトルエンディアンです。
get_AC2関数は、
SLA + Wコマンドの後に
0xAAではなくバイト
0xAC (データシートを参照)を送信する点のみが異なります。 他のすべての点で、機能はまったく同じです。 したがって、1つの
get_Dataを定義できます。これは、データシートに従って、パラメーターとして同じレジスタアドレスを受け入れます。
get_Data int get_Data(unsigned char Register_adress) { union { unsigned int Word; unsigned char Byte[2]; } Data;
そして、ちょうど:
struct { short AC1; short AC2; short AC3; unsigned short AC4; unsigned short AC5; unsigned short AC6; short B1; short B2; short MB; short MC; short MD; } BMP085_EEPROM; ... static void get_EEPROM(void) { BMP085_EEPROM.AC1 = get_Data(0xAA); BMP085_EEPROM.AC2 = get_Data(0xAC); BMP085_EEPROM.AC3 = get_Data(0xAE); BMP085_EEPROM.AC4 = get_Data(0xB0); BMP085_EEPROM.AC5 = get_Data(0xB2); BMP085_EEPROM.AC6 = get_Data(0xB4); BMP085_EEPROM.B1 = get_Data(0xB6); BMP085_EEPROM.B2 = get_Data(0xB8); BMP085_EEPROM.MB = get_Data(0xBA); BMP085_EEPROM.MC = get_Data(0xBC); BMP085_EEPROM.MD = get_Data(0xBE); }
結果は次のとおりです。
中断作業
まえがき
上の図からわかるように、1つの動作の継続時間は、バス転送周波数が
100 kHzの場合、
488μsであり、これは
8 MHzで
3094プロセッサ
クロック速度
です 。 まあ、これはみんなじゃない。 もちろん、ターゲットデバイスで許可されている場合は、頻度を増やすことができます。 たとえば、
400 kHzの場合、持続時間は
128μsまたは
1024サイクルですフラグが立ち上がるのを待つことは、まったく役に立ちません。 プロセッサは数千クロックサイクルにわたって、たとえば浮動小数点数を除算する操作を実行するなど、多くの有用な作業を実行できます。 したがって、この状況から抜け出す唯一の適切な方法は、割り込みを使用することです。
中断作業
構造を定義します。
struct { unsigned char SLA;
そして今、
get_AC1関数:
unsigned char Register_address; ... void get_AC1(void) { Register_address = 0xAA;
割り込みハンドラーについて一言。
nW = n 、
nR = 0の場合、フレーム形式は次のとおりです。
nW = 0の場合、
nR = m :
nW = nの場合 、
nR = m :
ところで、割り込みハンドラは何でも構いません。 ステートマシンロジックはさまざまな方法で実装できます。 私の例を以下に示します。
割り込みハンドラー ISR(TWI_vect) { static const void * const twi_list[] PROGMEM = {&&TWI_00, &&TWI_08, &&TWI_10, &&TWI_18, &&TWI_20, &&TWI_28, &&TWI_30, &&TWI_38, &&TWI_40, &&TWI_48, &&TWI_50, &&TWI_58, &&TWI_60, &&TWI_68, &&TWI_70, &&TWI_78, &&TWI_80, &&TWI_88, &&TWI_90, &&TWI_98, &&TWI_A0, &&TWI_A8, &&TWI_B0, &&TWI_B8, &&TWI_C0, &&TWI_C8, &&TWI_F8}; goto *(pgm_read_word(&(twi_list[TWSR>>3]))); TWI_00:
さて、仕事の結果:
ご覧のとおり、ランタイムは
488μsから
538μsに増加しました。 これは、ハンドラへの遷移とハンドラからの戻り、およびルックアップテーブルのジャンプアドレスの計算によるものです。 しかし、最も重要なことは、伝送全体がハードウェアで行われることです。 したがって、
3.5μsまたは
28クロックサイクルしか続かない小さな
get_AC1関数を実行した後、他のことを安全に行うことができ、待機サイクルでスタックすることはありません。
get_AC1 、
get_AC2 、...、
get_MD関数を順番に呼び出すとどうなるか見てみましょう。
get_MDを
実行するのは1つだけで、すべての理由は次のとおりです。
最後の関数は、SLA + Wが完了する前に構造にデータをロードします。したがって、Register_addressはそれに対応し、
0xBEと等しくなります。 実際、これはおそらく最も無害なシナリオです。 結局、転送がRegister_addressよりも少し速い場合、たとえば
get_AC5関数に対応し、
BMP085_EEPROM.MDにそれをまったく書き込んだでしょう。 つまり、
AC1を期待し、
MDに AC5を保存し
ます 。 また、
get_AC5の代わりに、独自のSLAを持つ別のアドレスがある場合、一方のアドレスの半分と他方のアドレスを送信してNACKを取得すると、割り込みハンドラーからのロジックはプログラムをループするか、STOPをスローします。 この状況から抜け出すための明確な方法が1つあります。 前の機能が終了するまで、次の機能を実行しないでください。 つまり、
get_AC1を実行することでフラグを1に設定し、STOPを完了したハンドラーはそれをリセットします。 また、このフラグがクリアされるまで
get_AC2は開始されません。 はい、これは解決方法ですが、中断の魅力はすべて失われます。この場合、0%も使用されます。 より良いフラッグマシン。 しかし、別の美しいソリューションがあります。
ミューテックスアナログ
get_AC1 、...、
get_MD関数はデータを直接送信しません。 それらはキューにそれらを置く別の関数を呼び出し、キューが空でない場合、それ自体が転送の開始を初期化します。 構造体の配列を作成して、構造体を書き換えます。
#define size 8 ... typedef struct { unsigned char SLA;
get_AC1関数
がどのように見えるかを見てみましょう。
unsigned char buf[size]; ... void get_AC1(void) { volatile twi *pl;
実際には、START状態を初期化する代わりに、TWIフレームに必要なすべてのデータを含む
twi- type
構造体へのポインターを渡す
Scheduler関数を呼び出します。 スケジューラーの基本機能とハンドラーへのいくつかの変更を考慮してください。
- ポインターを取得すると、関数はすべてのデータをTWI [i]構造体にコピーします。ここで、iは構造体配列の要素の現在の番号で、その後1ずつ増加します。 iが配列の次元から外れると、リセットされます。 したがって、構造体の配列を循環バッファーに変換します。
- j変数は、割り込みハンドラーで処理される要素を示し、その後1ずつ増加します。 jが配列の次元の境界外にある場合、リセットされます。
- 私がjに追いつく場合(キューオーバーフロー)-構造体の配列への書き込みは不可能です;
- フラグ変数は0に初期化されます。
- iがjと等しくない(キューが空ではない)場合、flag = 0の場合、flag = 1の場合、START状態を開始します。
- ハンドラーでは、フレームを処理した後、jを1増やして、次のフレームを示します。
- ハンドラーで、フレームを処理してjを変更した後、iがjと等しくない場合はSTOP-START状態になり、そうでない場合はSTOPおよびflag = 0になります。
上記の内容を考慮する必要はありません。 上記のいくつかのポイントを明確にする小さなプレゼンテーションを見るのが良いでしょう:
スケジューラースケジューラー
機能 :
void Scheduler(twi *pl) { if (tail-head !=1 && head-tail != size-1)
さて、ハンドラー自体:
再設計された割り込みハンドラー ISR(TWI_vect) { twi *p = &TWI[tail]; static const void * const twi_list[] PROGMEM = {&&TWI_00, &&TWI_08, &&TWI_10, &&TWI_18, &&TWI_20, &&TWI_28, &&TWI_30, &&TWI_38, &&TWI_40, &&TWI_48, &&TWI_50, &&TWI_58, &&TWI_60, &&TWI_68, &&TWI_70, &&TWI_78, &&TWI_80, &&TWI_88, &&TWI_90, &&TWI_98, &&TWI_A0, &&TWI_A8, &&TWI_B0, &&TWI_B8, &&TWI_C0, &&TWI_C8, &&TWI_F8}; goto *(pgm_read_word(&(twi_list[TWSR>>3]))); TWI_00:
さて、
get_AC1の結果:
あとがき
実際、
get_AC1と同様に、
get_AC2を作成し、2つの関数を連続して実行すると、最後の関数のみが2回実行されます。 これは、送信用のデータをbuf [0]に常に保存しているためです。 これを避けるために、buf [1]にデータを書き込むことができ、すべてが正常に機能します。 しかし、これは行われていません。 データを取得する場所と量からポインターを転送するのは正しいことです。一部のサードパーティ関数は、これをTWIのバッファーに入れ、ハンドラーでデータを取得する場所からポインターを返します。 一般に、たとえば次のコード:
コード unsigned char bufTx[size]; unsigned char pos = 0; ... void get_AC1(void) { volatile twi *pl;
写真を見てください:類推により、受信バッファに対して同じ機能を実行する必要があります。あなたは彼女にいくら教えて、彼女はあなたをどこに送ります。TWIの構造を変更することもできます。たとえば、ポインターではなくインデックスを送信して、メモリを最適化します。リセットにコードが使用される理由について質問がある場合: head = (head+1)&(size-1); tail = (tail+1)&(size-1); pos &= size-1;
私はこれをより速く、より少ないコードと言います。唯一の条件は、値が2 nであることです。機能のうち、TWIの最大サイズサイズに注意する価値もあります。連続したリクエストの最大可能数よりも少ない場合、それらの一部が確実に失われます。したがって、これを強調することが重要です。同時要求の最大数より少なくとも1大きいサイズ値を常に使用してください。関数を実行した後、受信したデータのハンドラーを追加することが可能です。この例では、これは受信したAC1とAC2の処理であり、ビッグエンディアンで受信し、リトルエンディアンに変換してから適切な場所に保存する必要があります。これを行うには、TWI構造で、関数ハンドラーへのポインターを保存し、ワークアウト後に呼び出す方法を見つけます。プロセスについて言及する価値のある言葉。私のスキームに関連して、TWIバスには2つの混乱、圧力、1つのタイムチップ、1つのメモリチップのセンサーがあります。そして、それらへのアクセスは、たとえば圧力センサーの場合、アトミックと見なすことができます。非圧縮圧力で伝送が失われた場合、取得した非圧縮温度の値は不要であり、無効なデータを取得できるため、最初からやり直す必要があります。また、TWIを介して送信するときに障害が発生することはありません。ハンドラーで元のTWI [i]を変更しているため、転送を再開できません。これを行うには、twi型のグローバル構造を宣言し、各開始前に、TWI [tail]からデータをコピーして、プロセッサにコピーを台無しにさせます。障害が発生した場合、元のデータを復元できます。おわりに
多くの人が尋ねます:「なぜ?実際、構造の16個の要素を保存するには、16 *(1 + 2 + 1 + 2 + 1)= 112バイトのSRAMが必要です!RTOSを使用しないのはなぜですか?1つの要素を持つ構造があり、それに収まらない場合は、nミリ秒待機します。」このようなソリューションは、オペレーティングシステムに非常に役立つと思います。この転送にかかる時間について考えてください。キューに入れると、次の各送信はnミリ秒になり、全体はn * mミリ秒で終了します。そして、本当に重要なタスクを見逃す可能性があるため、不必要なm命令でキューイングするのはなぜですか。また、OSカーネルはアンロードされ、m *(m + 1)/ 2タスクをキューに入れません。