STM32からロシアのマイクロコントローラーK1986BE92QIに渡します。 音を生成して再現します。 パート2:DMAをマスターする

前回の記事では、なんとか音を得ることができましたが、非常に高価でした。 まず、コントローラーを最大速度にオーバークロックしました。 第二に、サウンドの生成に加えて、コントローラーは何もできません。これは、プロセッサー時間の大部分がDACの値を絶えず更新するために費やされるためです。 これは良くありません。 現在、DMAの使用の問題は深刻です。

DMA、またはダイレクトメモリアクセス-中央プロセッサをバイパスして、メモリに直接アクセスする技術。
-(c) ここから





小さな余談。



DMAを使用するというアイデアから最初の結果を得るまで、1週間の苦労がありました。 最初の3日間は自分でマスターしようとしましたが、少なくとも結果を得ることができませんでした。 公式フォーラムでほぼ同じタスクのDMA構成の例を与えられて初めて、すべてが判明しました。 彼の詳細な研究と文書の詳細な分析の4日後、DMA作業の構造の明確な画像が私の頭に現れました。



第一印象。



MDR_DMAメモリダイレクトコントローラー.................. 410


ドキュメントを開いた後、私は困惑しました... DMAをマスターする初期段階の主なタスクは、DACに値を転送することです。 解決します。 DMAにはいわゆる「チャネル」があります。 それらは、受信機と送信機の間のバンドルです。 この場合、メモリと周辺機器(DAC)の間。 表に表示できる靭帯。

表に表示できる靭帯。








表からわかるように、一部のチャネルは特定の周辺機器用に予約されています。 この周辺機器にはDACはありません。 残りのチャネルは、意図した目的に使用できます。 最初の無料チャンネルはチャンネル8です。設定します。 しかし、どのように? ドキュメントには、 データ交換ルールに関するセクションがあります。

次のものが含まれます。
データ交換ルール

コントローラーは、以下の条件に従って、以下の表376にリストされているデータ交換規則を使用します。

-DMAチャネルがオンになります。これは、chnl_enable_set [C]およびmaster_enableを制御ビットの論理ユニットの状態に設定することにより行われます。

-要求フラグdma_req [C]およびdma_sreq [C]はマスクされません。これは、制御放電chnl_req_mask_set [C]を論理ゼロ状態に設定することによって行われます。

-コントローラーはテストモードではありません。テストモードは、int_test_enビット[C]を制御ビットの論理ゼロ状態に設定することで実行されます。


これらのビットが属するレジスタをすぐに見つけてください。 ただし、最初にクロック信号をDMAに送信する必要があります。
これは、DMA構成関数で行います。
#define PCLK_EN_DMA (1<<5) //   DMA. void DMA_Init_DAC (void) // DMA  DAC. { RST_CLK->PER_CLOCK|=PCLK_EN_DMA; //  DMA. }
      
      





クロックを適用した後、DMAを有効にする必要があります。

レジスタDMA-> CFGがこれを担当します。
 #define CFG_master_enable (1<<0) //   . DMA->CFG = CFG_master_enable; //  DMA.
      
      





次のステップはchnl_enable_set [C]ビットを有効にすることです。 ここで、Cはゼロからのチャネル番号を示します。

レジスタDMA-> CHNL_ENABLE_SETにあります。
  DMA->CHNL_ENABLE_SET = 1<<8; //   DMA 8.
      
      





その後、 chnl_req_mask_set [0]を「0」に設定する必要があります。
このビットはレジスタDMA-> CHNL_REQ_MASK_SETにあります。






すべてがうまくいくだろう、0を書くだろうとそれだけです...

放電[C] = 0は効果がありません。 使用する必要があります

chnl_req_mask_clr許可のレジスタ

セットアップの要求。


わかった
レジスタDMA-> CHNL_REQ_MASK_CLRを確認します。






ここで、必要なチャネルにユニットを設定する必要があります。
 DMA->CHNL_REQ_MASK_CLR = 1<<8; //      DMA,  dma_sreq[]  dma_req[].
      
      





さて、最後のステップはint_test_en bit [8] bitにゼロを書き込むことです 。 しかし、このビットの存在はどこにも書かれていません。 だから-スキップ。

さらに、チャンネルの優先度を高くします。
このために、レジスタDMA-> CHNL_PRIORITY_SETがあります。


 DMA->CHNL_PRIORITY_SET = 1<<8; // .
      
      







チャネルをオンにした後、DMA動作モードを決定する必要があります。

それらの6つがあります。
-無効。

-メイン;

-自動リクエスト。

-「ピンポン」。

-「設定変更を伴う実行」モードでメモリを操作します。

-「設定変更を伴う実行」モードで周辺機器を操作します。
すべてを分析して、初期段階では「メイン」モードで十分だと判断しました。
ここに彼の説明があります。
メイン

このモードでは、コントローラーはチャンネルのメインまたは代替制御データでのみ動作します。 チャネルが有効になり、コントローラーが処理の要求を受信すると、DMAサイクルは次のようになります。

1.コントローラーは2 ^ Rギアを実行します。 残りのギアの数が0の場合、コントローラーは手順3に進みます。

2.仲裁:

-優先度の高いチャネルが処理要求を発行すると、コントローラーはこのチャネルのサービスを開始します。

-周辺装置またはソフトウェアが処理の要求(チャネルでの処理の繰り返し要求)を発行した場合、コントローラーはステップ1に進みます。

3.コントローラは、hclk信号のクロックサイクルごとにdma_done [C]を状態1に設定します。 これは、中央処理装置にDMAサイクルを完了するように指示します。
DMAチャネル構成構造に記入するときに、より詳細に理解します。



DMAの構造。



結局のところ、レジスタに加えて、DMAにも構成構造があります。 正直なところ、私はこれらの構造を非常に長い間使用するという原則を掘り下げました。 以前、STM32の期間中、ドキュメントを読むのに十分な言語の知識がなかったため、既製のライブラリを使用しました。 現在、多少の困難はありますが、DMAの原則全体を低レベルで実現できます。
チャンネルのチャンネルごとに、独自の構造を設定する必要があります。 4つの32ビットセルで構成されます。




ドキュメントによると、構造はこの順序で作成する必要があります。

-ソースデータの末尾へのポインタ。

-受信者データの終わりへのポインタ。

-制御ビット;

-アドレスの計算。




DMAチャネル構造の塗りつぶし



レジスタ設定セルの入力から始めることをお勧めします。

受信者アドレスオフセット(DAC)を選択します。
私たちにとっては変わりません。 ソースとレシーバーには、ハーフワード容量(16ビット)があります。 私たちの場合:

ソースデータのビット深度=ハーフワード:

b11 =増分なし。 アドレスは、dst_data_end_ptrメモリ領域の値と同じままです。


ソースデータとレシーバデータの次元を選択します。
ここでは、ハーフワードを選択します。 配列はuint16_t(16ビット)なので。 ここでは、同じハーフワードを選択します。


仲裁手続きを許可します。




この点は私を非常に長い間無知のままにしていました。 実際には、DMAはパッケージ全体を一度に送信することはできませんが、部分的に送信することができます。 たとえば、1024個の要素の配列があります。 ただし、1秒あたり128個の要素を送信する必要があります。 これを行うには、b0111を設定し、128個の要素を送信した後、プロセッサまたは周辺機器が再起動するまで転送を中断します。 これは、DMAをタイマーに関連付けるときに役立ちます。 この場合、ゼロのままにします。 厳密に定義された瞬間に各要素を転送する必要があるため。 アレイ全体を単純に転送するのは適切ではありません。


パッケージの長さを設定します。


前回の記事では、100要素の配列を渡しました。 したがって、ここでは100-1個の要素を選択します(0 = 1個の要素なので)。


残念ながら、なぜこれが必要なのか理解できませんでした。 そのままにしておきます。
今のところは変更しません。


モードを選択するだけです。




「メイン」モードを選択します。


チャネル構成セルを構成しました。

次のものがあります。
 //   . #define dst_src (3<<30) // - 16  (). #define src_inc (1<<26) //   16    . #define src_size (1<<24) //  16 . #define dst_size (1<<28) //  16 . (      ). #define dst_prot_ctrl //     (,  , ) #define R_power (0<<14) // (    ,   )   . #define n_minus_1 (99<<4) //100  DMA. #define next_useburst (0<<3) //    ,  ... #define cycle_ctrl (1<<0) // . // . #define ST_DMA_DAC_STRYKT dst_src|src_inc|src_size|dst_size|R_power|n_minus_1|next_useburst|cycle_ctrl
      
      





次に、構造体の配列を作成し、そこにセットアップを書き込む必要があります。
 struct DAC_ST { uint32_t Destination_end_pointer; //   . uint32_t Source_end_pointer; //    uint32_t channel_cfg; // . uint32_t NULL; // . }
      
      





しかし、次のステップには約4日かかりました。 実際には、各構造のアドレスは厳密に固定されており、キロバイト単位のオフセットでのみ変更できます。
構造の配列を見てください。




各チャネルには2つの構造があります。 プライマリおよび代替。 代替はまだ私たちには関係ありません(他の動作モードに必要です)。 プライマリ(右の列)のみに関心があります。 コントローラーが8番目のチャネルの構成を確認するには、0x20000080、0x20000280、または0x20000480などに配置する必要があります。このレコードでは、構造がRAMにあり、1024の境界に配置する必要があることを示したいと思います。バイト。 この構造について説明します。

 __align(1024) DAC_ST; struct DAC_ST DAC_ST_ADC[8] ;
      
      





もう少し説明。 主なことは、指定されたアドレスに目的の構造が存在することです。 7番目のチャネルまたは9番目のDMAの構造のデータは関係ありません。 そうではないかもしれません。 技術的には、指定されたアドレスのRAMに4つの32ビットセルを書き込んで使用できます。 ただし、プログラムの実行中にコントローラーがそれらを変更するリスクがあります。 プログラムに記入してください。

  DAC_ST_ADC[7].Destination_end_pointer = (uint32_t)C_4 + sizeof(C_4) - 1; //     (C_4 -      100 ). DAC_ST_ADC[7].Source_end_pointer = (uint32_t)&(DAC->DAC2_DATA); //   ( )   (  DAC) DAC_ST_ADC[7].channel_cfg = (uint32_t)(ST_DMA_DAC_STRYKT); //  . DAC_ST_ADC[7].NULL = (uint32_t)0; // .
      
      





レジスタDMA-> CTRL_BASE_PTRの構造体の配列の開始アドレスを示すためだけに残ります。

 DMA -> CTRL_BASE_PTR = (uint32_t)&DAC_ST_ADC;
      
      





セットアップの結果はそうでした。
 #define CFG_master_enable (1<<0) //   . #define PCLK_EN_DMA (1<<5) //   DMA. //   . #define dst_src (3<<30) // - 16  (). #define src_inc (1<<26) //   16    . #define src_size (1<<24) //  16 . #define dst_size (1<<28) //  16 . (      ). #define dst_prot_ctrl //     (,  , ) #define R_power (0<<14) // (    ,   )   . #define n_minus_1 (99<<4) //100  DMA. #define next_useburst (0<<3) //    ,  ... #define cycle_ctrl (1<<0) // . // . #define ST_DMA_DAC_STRYKT dst_src|src_inc|src_size|dst_size|R_power|n_minus_1|next_useburst|cycle_ctrl struct DAC_ST { uint32_t Destination_end_pointer; //   . uint32_t Source_end_pointer; //    uint32_t channel_cfg; // . uint32_t NULL; // . } __align(1024) DAC_ST; struct DAC_ST DAC_ST_ADC[8] ; void DMA_and_DAC (void) { DAC_ST_ADC[7].Destination_end_pointer = (uint32_t)C_4 + sizeof(C_4) - 1; //     (C_4 -      100 ). DAC_ST_ADC[7].Source_end_pointer = (uint32_t)&(DAC->DAC2_DATA); //   ( )   (  DAC) DAC_ST_ADC[7].channel_cfg = (uint32_t)(ST_DMA_DAC_STRYKT); //  . DAC_ST_ADC[7].NULL = (uint32_t)0; // . RST_CLK->PER_CLOCK|=PCLK_EN_DMA; //  DMA. DMA->CTRL_BASE_PTR = (uint32_t)&DAC_ST_ADC; //   . DMA->CFG = CFG_master_enable; //  DMA. }
      
      







DMAを使用して正弦波信号を取得します。



思い出すと、DMAは各送信後に停止するように設定します。 次に、システムタイマーを使用して、次のデータブロックのDACへの転送を有効にする必要があります。

タイマーを構成します。
 void Init_SysTick (void) { SysTick->LOAD = 80000000/261.63/100-1; SysTick->CTRL |= CLKSOURCE|TCKINT|ENABLE; }
      
      





次に、システムタイマーの割り込みで、DMAがすべてを送信したかどうかを確認する必要があります。 その場合、その構造を再度構成する必要があります。 実際には、DMAは各送信後に、ユニットごとの転送数を独立して取り去ります。 したがって、すべての転送後-正弦波の送信の元の値を再度復元する必要があります。 その後、チャネルが再び機能するようにし(送信後、チャネルは禁止されます)、送信を再開する必要があります。

 volatile uint16_t Loop = 0; volatile uint32_t Delay_dec = 0; void SysTick_Handler (void) { if ((DAC_ST_ADC[7].channel_cfg & (0x3FF<<4)) == 0) { DAC_ST_ADC[7].channel_cfg = (uint32_t)(ST_DMA_DAC_STRYKT); } // DMA. DMA->CHNL_ENABLE_SET = 1<<8; //   DMA 8. DMA->CHNL_SW_REQUEST = 1<<8; //  . }
      
      







結論の代わりに。



DMAの操作方法を学べましたが、プロセッサをアンロードできませんでした。 次の記事では、タイマーの動作を分析し、DMAからタイマーに作業を移行し、必要に応じてプロセッサーのパワーを残します。

Yurockに感謝します。Yurock は公式の公式フォーラムでDACのDMA構成コードの例を共有しました。 最初は、この例の解析に関する記事を書く予定でした。 私は約3日間彼に対処したからです。 私には複雑すぎました。 タイマーとさまざまな構造の使用。



githubのコードとオーディオ






All Articles