問題とドライバーの要件
経験豊富なマイクロプロセッサプログラマはすべて、ドライバの作成経験があります。 小さなプロジェクトを実装するとき、または既製のデバッグ済みコードを別のプロセッサーに転送するとき、ドライバーの作成とデバッグには50%以上の開発時間がかかることがあります。 さらに、新しいプロセッサ用のドライバを作成し、既存のコードと一致させるプロセスは、ドライバの構造と一般性の欠如により、非常に不快な場合があります。 プログラマーにとって、これは神経質なルーチンになります。 ドライバーを作成する際に重要な問題を特定します。
- ドライバー構造の欠如。 この問題の解決策により、ドライバーの操作性が向上するため、デバッグが容易になります。
- ドライバーの共通性の欠如。 つまり、ドライバーインターフェイスは、少なくともプロセッサのラインで同じであり、理想的には異なる企業の類似のプロセッサである必要があります。 これにより、ドライバーに関連付けられたコードを、変更なしで、または最小限の変更で転送できます。
- ドライバーの実行速度に関して効率を維持します。
- 最適なメモリ使用量。
この投稿では、例としてC ++で実装された2つのUARTおよびDMAドライバーを使用して、これらの問題を解消し、ドライバーの作成プロセスをより楽しいタスクにする方法を示します。 このために、ドライバーの要件を策定しました。
- ドライバーは構造化されている必要があります。 ドライバーはC ++クラスとして実装され、ドライバーオブジェクトを作成できるようになります。 これは便利で論理的です。たとえば、プロセッサに3つのUARTチャネルと4つのDMAチャネルがある場合、各UARTに3つのドライバーオブジェクトを作成し、各DMAチャネルに4つのドライバーオブジェクトを作成できます。 プロジェクトが1つのUARTのみを使用する場合、対応するUARTなどに対して1つのオブジェクトのみを作成できます。
- ドライバは、複数のプロセッサに対して同じインターフェイスを持っている必要があります。 この要件を満たすことは非常に困難です。多くのプロセッサーにとって便利な共通インターフェースを選択するには、この多くのプロセッサーを解決する必要があり、多くの時間がかかります。 したがって、STM8L051F3に便利なインターフェイスを作成し、新しいプロセッサにプロジェクトが表示されたら、このインターフェイスを調整します。 したがって、時間の経過とともにすべてのプロセッサに最も一般的で最適なインターフェイスを選択できます。
- ドライバーは可能な限り効率的でなければなりません。 理想的には、実行の速度と占有メモリ量は、このドライバを直接レジスタにアクセスするように記述した場合と同じである必要があります。 重要なことは、C ++テンプレートを使用することで、ドライバーの実行速度を上げることができることです。 C ++テンプレートを使用すると、メモリに直接アクセスできるため、逆参照に時間がかかるポインターを置き換えることができます。 ただし、ドライバーインターフェイス関数を呼び出すコストもあります。 インライン演算子を使用すると、関数呼び出しを削除できませんでした。これはアセンブラーコードで確認できます。 コンパイラオプティマイザーをオンにすると、この演算子が影響する可能性があります...ドライバーは1回または低速で構成されているため、関数を呼び出すコストは重要ではありません。
ドライバーの使用
そのため、まず、DMAドライバーの使用を検討してから、実装を検討してください。 DMAを使用して、RAMからシフトレジスタUARTにデータを転送し、シフトレジスタUARTからRAMにデータを受信します。
#include "iostm8l051f3.h" #include "Driver_DMA.hpp" char UartBuffer[128];
前のリストからわかるように、コードはより理解しやすく、読みやすく、自己文書化されます。
ドライバーインターフェース
インターフェイスは、ドライバーメソッドへの引数として使用される列挙を定義するEnumDMA構造で定義されます。
struct EnumDMA { enum DMASel{ DMA1 = 0x5070 }; enum ChannelSel{ CHANNEL0 = 0x05, CHANNEL1 = 0x0F, CHANNEL2 = 0x19, CHANNEL3 = 0x23 }; enum DataBlock{ DATABLOCK_8bit, DATABLOCK_16bit }; enum ChannelPriority{ PRIORITY_LOW, PRIORITY_MEDIUM, PRIORITY_HIGH, PRIORITY_VERYHIGH }; enum MemoryPointerMode{ MEMPNT_DECREMENT, MEMPNT_INCREMENT }; enum CircularBufferMode{ CIRCULAR_DISABLE, CIRCULAR_ENABLE }; enum TransferType{
DMASel列挙により、DMAモジュールを選択でき、ChannelSel列挙により、DMAチャネル間のメモリオフセットが決まります。 STM8L051F3プロセッサにはDMAモジュールが1つあるため、選択肢は大きくありません。 DMA1列挙には、DMA1モジュールのアドレスが割り当てられます。
DriverDMAクラスの設計:
template <EnumDMA::DMASel DMA, EnumDMA::ChannelSel DMAChannel> class DriverDMA { private: ... struct DMA_struct // DMA { volatile GCSR_REG GCSR; volatile GIR_REG GIR1; }; ... struct DMA_Channel_struct // DMA { volatile CCR_REG CCR; volatile CSPR_REG CSPR; volatile unsigned char CNDTR; volatile unsigned char CPARH; volatile unsigned char CPARL; volatile unsigned char CM0EAR; volatile unsigned char CM0ARH; volatile unsigned char CM0ARL; }; u8 number_of_transfers; u8 NumChannel; public: DriverDMA(); void operator= (EnumDMA::DataBlock); void operator= (EnumDMA::ChannelPriority); void operator= (EnumDMA::MemoryPointerMode); void operator= (EnumDMA::CircularBufferMode); void operator= (EnumDMA::TransferDirection); void operator= (EnumDMA::TransferType);
DMAモジュールレジスタは、C ++テンプレートを使用してプライベートセクションで定義され、DMAモジュールとモジュールチャネルが選択されます。
ドライバー実装の説明
クラスコンストラクターと2つのメソッドの実装:
__DMAおよび__DMACHANNELは次のように定義されます。
#define __DMA ((DMA_struct*) DMA) #define __DMACHANNEL ((DMA_Channel_struct*) ((u32)DMA + (u32)DMAChannel))
一見、複雑な設計:
__DMACHANNEL->CSPR.bit.TSIZE = db;
コンパイラーによって3つのアセンブラーコマンドとして解釈され、コマンドは次のとおりです。
__DMA->GCSR.bit.GEN = 1;
1つのアセンブラーコマンドを占有します。
中断
割り込みを作成するには、割り込みを有効にし、確認する割り込み関数を作成する必要があります。 たとえば、DMAトランザクションの終了時に割り込みを作成します。
void func() {
DMAドライバーを使用したUARTドライバーの実装
UARTドライバーもクラスを使用して同様の方法で実装されますが、このプロセッサにはUARTモジュールが1つしかないため、C ++テンプレートを使用しません。 UARTドライバーは2つのDMAチャネルを使用してデータを送受信します。 ドライバーユーザーはDMAにアクセスする必要がないため、プライベートセクションでDMAを初期化します。
class DriverUART { private: ... u8 UartBuffer[128]; DriverDMA<EnumDMA::DMA1,EnumDMA::CHANNEL1> DMAChannelTX; DriverDMA<EnumDMA::DMA1,EnumDMA::CHANNEL2> DMAChannelRX; ...
DMA構成はUARTドライバーコンストラクターで行うことができるため、UARTオブジェクトを作成すると、DMAドライバーはすぐに初期化され、動作する準備が整います。
データ転送の実装:
void DriverUART::transmit(u8 * source, u16 size) { while(DMAChannelTX.is_busy()) ; select_direction(TRANSMITION); __disable_interrupt(); DMAChannelTX.global_disable(); DMAChannelTX.channel_disable(); DMAChannelTX.set_number_of_transfers(size); DMAChannelTX.set_memory0_addr(source); DMAChannelTX.global_enable(); DMAChannelTX.channel_enable(); __enable_interrupt(); }
使用例:
u8 buffer[] = "hello world!" Uart1.transmit(buffer, sizeof(buffer));
受信すると、受信DMAチャネルをリセットする必要がある割り込みが生成されます。
void DriverUART::reception_handshake() { __disable_interrupt(); DMAChannelRX.global_disable(); DMAChannelRX.channel_disable(); received_size = DMAChannelRX.get_amount_of_last_transation(); DMAChannelRX.set_number_of_transfers(sizeof(UartBuffer)); DMAChannelRX.global_enable(); DMAChannelRX.channel_enable(); __enable_interrupt(); }
割り込みでは、受信データが配置されている内部バッファへのポインタと、受信パケットのサイズを取得できます。 この場合、割り込みはクラス内で作成されるため、ハンドラーは次のように実装する必要があります。
おわりに
これらのドライバーは、うまくナビゲートできる明確な構造を持ち、そのおかげでドライバーの記憶がよくなります。 ドライバーは、構成可能なオブジェクトとして認識されるようになりました。ドライバーを使用して、何かを送信し、そこから何かを取得できます。
インターフェイスはC ++リストの形式で作成されており、ドライバーのプロパティと機能をよりよく理解し、プロセッサーのデータシートとの通信を最小限に抑えるのに役立ちます。 このインターフェイスのおかげで、コードは自己文書化されます。 これにより、ドライバーの初心者をすばやく理解し、経験豊富なプログラマーに自分のコードの本質を思い出させることができます。
ドライバーコードは、インターフェイスを大幅に変更することなく、この一連のドライバーまたは他のメーカーの別のプロセッサ用の同様のドライバーを記述するためのテンプレートとして使用できます。
C ++テンプレートの使用により、ドライバーの速度を大幅に向上させることができましたが、関数の呼び出しにはコストがかかりますが、実行速度は重要ではありません。
DMAおよびUARTドライバーファイルは、このリンク
STM8L051F3_Driversからダウンロードできます。
ファイル「Init_UART.cpp」には、UARTドライバーの使用例が含まれています。