STM32 は、レジスタを使用して低レベルのドライバー学習 (USART+DMA) を開発します。

教材ダウンロード

以下のドキュメントは、この記事の学習に使用されます。お持ちでない方は、最初にダウンロードしてください。

工程模板
Cortex M3权威指南(中文)			:本文简称为《指南》
STM32F10x Cortex -M3编程手册  	:本文简称为《编程手册》
STM32F10x Cortex -M3编程手册(译文)
STM32中文参考手册_V10				:本文简称为《手册》

STM32F10Xレジスタ学習教材

タスク

主にレジスタを使ってDMAや割り込みの設定を行う方法を学び、以下の3つの実験に分かれています。

1、使用USART的DMA功能发送数据。
2、使用DMA非中断模式接收USART数据。
3、使用DMA中断模式接收USART数据。

データを送信する USART の DMA 機能

DMA 機能の詳細については、ここでは説明していませんが、理解できない場合は、まずDMAについて学習してください。

1. テンプレートはpunctual atomの register プロジェクト テンプレートを使用します. プロジェクトではdelay.c, sys.c,usart.cはいくつかの基本的な機能で構成されているので, シリアル ポートの構成を再構成しませんが, 一部を変更する必要があります.

2. 「マニュアル」には、USART レジスタの説明があります: 制御レジスタ 3 (USART_CR3)。
ここに画像の説明を挿入
このレジスタのビット 6 と 7CR3は、DMA トランシーバ機能を有効にします。
ここに画像の説明を挿入
この表では、各ビットの機能を明確に理解できます。

3.では、プログラミングするときはどのように書くのでしょうか? 次のステートメントをファイルに追加します
usart.c

	//使能DMA发送和接收
	USART1->CR3|=1<<7;
	USART1->CR3|=1<<6;

usart.cファイルに次の変更を加える必要があります。DMA イネーブルを追加し、シリアル ポート 1 受信の割り込み構成をコメント アウトします。

ここに画像の説明を挿入

ペリフェラル レジスタを構成するときは、ペリフェラルの名前を入力するだけで、->ドロップダウン ボックスが表示されます.これらのドロップダウン ボックスは、ペリフェラルのレジスタです.
ここに画像の説明を挿入
4.プロジェクト内のペリフェラルの名前は?
ファイルには、背後にある DMA の各チャネルの名前を含む、stm32f10x.hすべてのペリフェラルの名前があります。
ここに画像の説明を挿入
5.USART1->CR3|=1<<7;この声明はどういう意味ですか?
AND または OR 演算は、このレジスタの 7 番目の位置を 1 に設定することですUSART1これは、レジスタ構成中に頻繁に使用および操作されます。使い始めは間違いなく多少の論理的混乱がありますが、心配する必要はありません。このステートメントがレジスタ設定を完了する方法については、次の論理演算を含めて詳細には触れません。CR3
|=&=

6. USART1 の DMA 送信機能がオンになり、DMA レジスタの設定を開始します。
同様に、DMA レジスタの説明も「マニュアル」に含まれていますが、NVIC によって構成されたレジスタの説明はこのファイルには含まれておらず、それ以外はすべてこのマニュアルに記載されています。ですから、レジスターを学ぶ上で、このマニュアルは非常に重要です。

7. 最初に 2 つのファイルdma.cと を作成しますdma.h
dma.h書類

#ifndef __DMA_H
#define __DMA_H

#include "sys.h"	//引用相关头文件

void DMA_UsartTx_Init(void);	//发送初始化
void DMA_UsartRX_Init(u32 Rx_Buff, u16 CNDTR);	//接收初始化

#endif

この関数をファイルにdma.c書き込みvoid DMA_UsartTx_Init(void);、DMA を初期化してシリアル データ構成を送信します。
(1)ペリフェラル クロックを
オンにする
(2) ペリフェラル レジスタを構成する
(3) 割り込み優先順位を設定
する (1)ペリフェラル
クロック 1 をオンにします
。STM32 には 3 つのクロック ラインがあり、3 つのレジスタで構成されていますレジスタは特にこれらのクロックラインの下にありますか? 「マニュアル」の 25 ページの図 1 システム構造では、クロック ラインと含まれるペリフェラルを確認できます. これら 2 つのクロック ラインに記載されているペリフェラルを除いて、他のペリフェラルはこのクロック ラインにあります. したがって、DMA クロックを設定する場合は、レジスタで設定する必要があります。2. 「マニュアル」の 69 ページの6.3.6 AHB ペリフェラル クロック イネーブル レジスタ (RCC_AHBENR)を見つけます。ここでは、このレジスターについて説明します。このレジスタのビット 0 とビット 1 は DMA1/2 クロックによって有効になりますが、どのレジスタを使用すればよいでしょうか? 3. 「マニュアル」の 147 ページの10.3.7 DMA 要求イメージに詳細な説明があります。シリアル ポート 1TX および RX の DMA 要求は、それぞれ DMA1 のチャネル 4 およびチャネル 5 にあります。4. これで、シリアル ポート 1 の DMA 要求は DMA1 を使用する必要があり、DMA1 のチャネル 4 と 5 であることがわかりました。AHBENRAPB1ENRAPB2ENRAPB1APB2AHBAHBENR
ここに画像の説明を挿入
ここに画像の説明を挿入


ここに画像の説明を挿入

void DMA_UsartTx_Init(void){
    
    

	RCC->AHBENR|=1<<0;							//开启DMA1时钟

}

5. DMA レジスタの設定を開始する「マニュアル」の 150 ページの10.4.3 DMA チャネル x 設定レジスタ (DMA_CCRx) (x = 1...7)
には、ここに DMA 制御の設定レジスタがあります。見てください。
ここに画像の説明を挿入
これも非常に単純です。1 つずつ見ていきます。0 番目のビットはチャネルが開いていることを示します。つまり、このビットを 1 に設定すると、チャネルが動作し始めます。したがって、通常は、ワーク イネーブルや割り込みイネーブルなどのこれらのチャネルを最後にオンにします。最初に他のものを構成する必要があり、これは最後です。
4 桁目の前に割り込みとワーク イネーブルがあるので、4 桁目から見ていきます。

各ビットの機能は表の説明でよく分かるので、簡単に説明します。

(ビット 4) DIR: データ転送方向
段落を送信する場合は、まず送信する内容を文字列変数に格納し、次に文字列変数の内容を DMA 経由でシリアル ポート 1 の送信バッファに転送する必要があります。このプロセスでは、メモリは文字列変数を参照し、シリアル ポート 1 の送信バッファ レジスタはペリフェラル アドレスです。明らかに、メモリからデータを読み取る必要があるため、ビット 4 を 1 に設定する必要があります。
	DMA1_Channel4->CCR=0x00000000;	//复位
	DMA1_Channel4->CCR|=1<<4;				//从存储器读	

DMA1このペリフェラル名が使用されていないのに使用されているのはなぜですかDMA1_Channel4?
振り返って、このレジスターの名前を見てみましょう。
ここに画像の説明を挿入
DMAの割り込みフラグクリアレジスタを見てみましょう。
ここに画像の説明を挿入
これで違いがわかります。1 つは最初の数チャネルに固有である必要があること、もう 1 つは DMA1 または DMA2 全体の構成です。DMA は複数のチャネルに分割されており、チャネルごとに管理されるペリフェラルが異なるため、それらのレジスタも分離する必要があります。
先ほど、DMA の各チャネルの名前を含め、プロジェクト ファイル全体の各ペリフェラルの名前について説明しました。
ここに画像の説明を挿入

(ビット5) CIRC: サーキュラーモード (サーキュラーモード)
これはあなたのニーズに応じて提供されます。必要なのは、DMA を有効にしてデータを送信するたびに、一度だけ送信することです。次に、ループ操作を実行しないことを選択します。次に、この位置はゼロです。先ほどリセットしたときは全ビットをゼロにしたので、位置がゼロという文は書けないのでしょうか?もちろん、それを書くだけでもかまいません。他の人があなたのプログラムを簡単に理解できるように、コメントを付けたほうがよいでしょう。
	//如果你前面没有将所有位置零的话,不能使用下面的语句  需要使能这句	DMA1_Channel4->CCR&=~(1<<5);
	DMA1_Channel4->CCR|=0<<5;				//不执行循环操作
(ビット 6) PINC: ペリフェラル インクリメント モード
ペリフェラル アドレスを増やしてはなりません.シリアル ポート 1 の送信バッファの固定アドレスは USART1->DR
DMA1_Channel4->CCR|=0<<6;				//不执行外设地址增量操作
(ビット7) MINC:メモリインクリメントモード(メモリインクリメントモード)
メモリのアドレスを増やす必要があります.文字列変数は単なる文字の配列です.データストレージのアドレスはビットごとに増加します.
(ビット 9:8) PSIZE[1:0]: 周辺サイズ、(ビット 11:10) MSIZE[1:0]: メモリ データ幅 (メモリ サイズ)
文字データ形式は 8 ビットです。したがって、どちらも同じです。
(ビット 13:12) PL[1:0]: チャネル優先レベル (チャネル優先レベル)
これは、DMA1 の各チャネルでのこのチャネルの実行の優先順位であり、要件に基づいています。
(ビット14) MEM2MEM: メモリtoメモリモード (メモリtoメモリモード)
ペリフェラル シリアル ポート 1 を使用する必要があります。
	DMA1_Channel4->CCR|=1<<7;				//执行存储器地址增量操作
	DMA1_Channel4->CCR|=0<<8;				//8位
	DMA1_Channel4->CCR|=0<<10;				//8位
	DMA1_Channel4->CCR|=1<<12;				//优先级中
	DMA1_Channel4->CCR|=0<<14;				//非存储器到存储器模式;

5. 「マニュアル」の 144 ページに DMA チャネルの構成プロセスがあります. 全部で 6 つのステップがあります. 4 と 5 を完了したところです. 6 番が最後です。1、2、3 を見てみましょう。
ここに画像の説明を挿入

6. 「マニュアル」152 ページ、10.4.4 DMA チャネル x 転送数レジスタ (DMA_CNDTRx) (x = 1...7)
ここに画像の説明を挿入
7. 「マニュアル」152 ページに、周辺アドレスの記述があります。レジスタとメモリ アドレス メモリ。
ここに画像の説明を挿入
8. これら 3 つのレジスタの使用は比較的単純で、値を割り当てるだけです。注意すべきことの 1 つは、アドレスの割り当てです. コードの形式は後で見てください。最後はチャネルを有効にすることです。この送信を中断する必要がないため、ビット 1 ~ 3 を見る必要はありません。

	DMA1_Channel4->CNDTR = 14;			//数据长度14
	DMA1_Channel4->CMAR = (u32)Send_Buff;		//存储器地址
	DMA1_Channel4->CPAR = (u32)&USART1->DR;		//外设地址
	
	DMA1_Channel4->CCR|=1<<0;				//开启DMA1通道4

Send_Buff文字列を格納するために定義した変数です。

ファイルdma.hの完全なコード:

#include "dma.h"

u8 Send_Buff[14] = {
    
    "2022年10月21日"};

void DMA_UsartTx_Init(void){
    
    
	
	RCC->AHBENR|=1<<0;						//开启DMA1时钟
	DMA1_Channel4->CCR=0x00000000;			//复位
	DMA1_Channel4->CCR|=1<<4;				//从存储器读
	
	//如果你前面没有将所有为置零的话,不能使用下面的语句  需要使能这句DMA1_Channel4->CCR&=~(1<<5);
	DMA1_Channel4->CCR|=0<<5;				//不执行循环操作
	DMA1_Channel4->CCR|=0<<6;				//不执行外设地址增量操作
	DMA1_Channel4->CCR|=1<<7;				//执行存储器地址增量操作
	DMA1_Channel4->CCR|=0<<8;				//8位
	DMA1_Channel4->CCR|=0<<10;				//8位
	DMA1_Channel4->CCR|=1<<12;				//优先级中
	DMA1_Channel4->CCR|=0<<14;				//非存储器到存储器模式
	DMA1_Channel4->CNDTR = 14;				//数据长度14
	DMA1_Channel4->CMAR = (u32)Send_Buff;	//存储器地址
	DMA1_Channel4->CPAR = (u32)&USART1->DR;	//外设地址
	
	DMA1_Channel4->CCR|=1<<0;				//开启DMA1通道4
	
}

ファイルの完全なコードtest.c:

#include "sys.h"
#include "usart.h"		
#include "delay.h"	 
#include "dma.h"

int main(void)
{
    
    				 

	Stm32_Clock_Init(9);		//时钟初始化
	uart_init(72, 115200);		//串口1初始化
	delay_init(72);				//延时函数初始化
	
	DMA_UsartTx_Init();			//使能一次DMA1发送串口1数据
	while(1){
    
    
		
		
		
	}
	
} 

9.
データを一度だけ送信して実行します。
ここに画像の説明を挿入

USART データを受信するための DMA 非割り込​​みモード。

今後の実験では素材の場所を書き出すことはしませんが、自分で素材を見つける方法を学ぶことも非常に重要です。

1. 中断のない受信データの構成は、送信の構成と大差ありません。私はコードを最初に置きます:

//Rx_Buff存储器地址
//CNDTR数据长度
void DMA_UsartRX_Init(u32 Rx_Buff, u16 CNDTR){
    
    
	
	RCC->AHBENR|=1<<0;							//开启DMA1时钟
	DMA1_Channel5->CCR=0x00000000;	//复位
	DMA1_Channel5->CCR|=0<<4;				//从外设读
	
	DMA1_Channel5->CCR|=0<<5;				//不执行循环操作
	DMA1_Channel5->CCR|=0<<6;				//不执行外设地址增量操作
	DMA1_Channel5->CCR|=1<<7;				//执行存储器地址增量操作
	DMA1_Channel5->CCR|=0<<8;				//8位
	DMA1_Channel5->CCR|=0<<10;				//8位
	DMA1_Channel5->CCR|=0<<12;				//优先级低
	DMA1_Channel5->CCR|=0<<14;				//非存储器到存储器模式
	DMA1_Channel5->CNDTR = CNDTR;			//数据长度
	DMA1_Channel5->CMAR = Rx_Buff;			//存储器地址
	DMA1_Channel5->CPAR = (u32)&USART1->DR;	//外设地址
	
	DMA1_Channel5->CCR|=1<<0;				//开启DMA1通道5
	
}

コードは基本的に同じで、すべてを2 にDMA1_Channel4変更しDMA1_Channel5
、次に違い:
1、データの読み取り方向、ビット 4、2
、. . .

優先度は変更してもしなくても構いません次に、使いやすいように、データ長とメモリのアドレスを入力パラメータとして使用します。

ファイルtest.cコード:

#include "sys.h"
#include "usart.h"		
#include "delay.h"	 
#include "dma.h"

u8 Rx_Buff[14];		//接收缓冲区

int main(void)
{
    
    				 

	Stm32_Clock_Init(9);		//时钟初始化
	uart_init(72, 115200);		//串口1初始化
	delay_init(72);				//延时函数初始化
	
//	DMA_UsartTx_Init();			//使能一次DMA1发送串口1数据
	DMA_UsartRX_Init((u32)Rx_Buff, 14);		//初始化DMA接收
	while(DMA1_Channel5->CNDTR != 0);		//等待接收完成
	printf("%s\r\n", Rx_Buff);				//输出接收内容
	while(1){
    
    
		
		
	}
	
} 

3.ダウンロードして実行します。
ここに画像の説明を挿入

USART データを受信するための DMA 割り込みモード

1. 割り込みモードと非割り込みモードの違いは、割り込みを有効にし、NVIC 関連のレジスタを構成することです。DMA 受信の割り込みを有効にするのは非常に簡単ですが、NVIC 関連のレジスタを設定するのは困難です。

2. NVIC に関する知識については、次の記事をお勧めします: [STM32] NVIC 割り込み優先度管理 (割り込みベクトル テーブル)

3. 上記の記事を読めば、NVIC の設定について多くの知識が得られると思います。ここでは、NVIC関連のレジスタの構成について学習します。

4.割り込み優先度のグループ化:
グループ化の構成は、SCB->AIRCRレジスタ (ビット 10:8 )によって定義されます。このレジスタの詳細な説明は、ガイドの 285 ページの表D.13 アプリケーション割り込みおよびリセット制御レジスタ (AIRCR) 0xE000_ED0Cにあります。このレジスタでは、(ビット 31:16 ) と (ビット 10:8 )
のみを考慮します。(ビット 31:16 ) はアクセス キーで、次のコードに反映されます。(ビット 10:8 ) は優先グループであり、別のレジスタ ( NVIC_IP ) と組み合わせて使用​​されます。ですから、レジスター ( NVIC_IP ) を理解するのを待ってから、一緒に構成してください。

5. 127 ページの「プログラミング マニュアル」(翻訳エラーがある場合は、英語版と照合して使用できます)、4.3.1 Cortex®-M3 NVIC レジスタの CMSIS マッピングに、NVICの説明があります。登録。

core_cm3.hNVIC を構成するために使用される構造体 NVIC_Type は、ファイルに記述されています。
ここに画像の説明を挿入

割り込みイネーブル レジスタ (NVIC_ISERx)
ISER[0] と ISER[1] の各レジスタは 32 ビットで、各ビットは対応する割り込みソースの割り込みイネーブルを制御します。 各割り込みソースが列挙されているファイル
では 、このリストで各割り込みソースの名前を見つけることができます。これは後で使用されます。 stm32f10x.h
ここに画像の説明を挿入
割り込み優先度レジスタ (NVIC_IPRx)
このレジスタは NVIC_Type 構造体に記述されていませんか? IP[240] だけな ので、これら 2 つのレジスタの関係は何ですか?
「プログラミング マニュアル」のこれら 2 つのレジスタの説明には、次のように書かれています。 IPR0 ~ IPR16 レジスタは、各割り込みに 4 ビットの優先度フィールドを提供します。これらのレジスタはバイトアクセス可能です。各レジスタには 4 つの優先度フィールドがあり、CMSIS 割り込み優先度配列 IP[0] から IP[67] の 4 つの要素にマップされます。
ここに画像の説明を挿入
簡単に言えば、32 ビットの NVIC_IPR[17] を8 ビットのIP[68] にマップすることです。
また、8 ビットの IP[x] のみが使用されます (ビット 7:4)。

6. この時点で、 stm32 割り込み優先度のグループ化と、各割り込みソースの割り込み優先度の構成レジスタについて学習しました。

SCB->AIRCRNVIC->IP[x]をどのように組み合わせて優先度を設定しますか?

AIRCR 割り込みグルーピング設定表:

グループ AIRCR[10:8] IP ビット[7:4] 割り当て 課題結果
0 111 0:4 プリエンプション優先度0ビット、レスポンス優先度4ビット
1 110 1:3 1 ビットのプリエンプティブ優先度、3 ビットの応答優先度
2 101 2:2 2 ビットのプリエンプション優先度、2 ビットの応答優先度
3 100 3:1 3 ビットのプリエンプション優先度、1 ビットの応答優先度
4 011 4:0 4 ビットのプリエンプション優先度、0 ビットの応答優先度

ここにはさらに 2 つの知識ポイントがあります:プリエンプションの優先順位と応答の優先順位ですよくわからない場合は、プリエンプション優先度とレスポンス優先度を見てください.
次に、AIRCR[10:8]の内容の書き込みに注意する必要があります.割り込みグループを0グループに設定する必要がある場合、AIRCR [10:8]と書く必要があります Input111ではなく、000否定演算を行ったようで、他のグループの設定も同様です。IP bit[7:4]の振り分け
について:例えば、 AIRCR[10:8]をGroup 3に設定するとIP bit[7:4]が2分割されてIP bit[7:が分割されます。 5]およびIPビット[4]IPビット[7:5]はプリエンプション優先度を設定するために使用され、残りのビットIPビット[4]は応答優先度を設定するために使用される。100

7. 多くのことを知っているので、実際には、コードは 4 ~ 5 文で実行できます。
関数の最後にvoid DMA_UsartRX_Init(u32 Rx_Buff, u16 CNDTR)次のコードを追加します

	
	DMA1_Channel5->CCR|=1<<1;		//打开完成传输中断
	temp = SCB->AIRCR;				//读SCB->AIRCR数据
	temp&=0x0000F8FF;				//将[31:16][10:8]置零,其他位我们不改动
	temp|=0x05FA0400;				//写入钥匙和设置分组为3
	SCB->AIRCR = temp;				//数据写入
	// 注意:上面这段代码在整个工程中只能出现一次,因为改变分组后可能导致之前设置的优先级改变,可能造成不可预测的错误!!!
	
	//DMA1_Channel5_IRQn/32 先找出该中断是在寄存器ISER[0]还是ISER[1]
	//|=(1<<DMA1_Channel5_IRQn%32) 将对应位置一,使能中断
	NVIC->ISER[DMA1_Channel5_IRQn/32]|=(1<<DMA1_Channel5_IRQn%32);

	//写入0101 抢占优先级2  响应优先级1
	NVIC->IP[DMA1_Channel5_IRQn]|=5<<4; 
	
	//最后开启通道
	DMA1_Channel5->CCR|=1<<0;				//开启DMA1通道5
	

DMA1 チャネル 5 の割り込みサービス関数を記述します void DMA1_Channel5_IRQHandler(){}

スタートアップ ファイルstartup_stm32f10x_hd.sには、割り込みソースごとに割り込みサービス関数名があります。次のコードをファイル
ここに画像の説明を挿入
に追加します。dma.c

u8 flag;
void DMA1_Channel5_IRQHandler(){
    
    
	
	flag = 1;			//信号量置一
	
	DMA1->IFCR|=1<<17;	//清除中断标志位
	//在《手册》150页有DMA中断标志清除寄存器(DMA_IFCR)的描述
	
}

セマフォflagはグローバル変数なので、dma.hヘッダー ファイルで宣言する必要があります。キーワードを使用しますextern
ここに画像の説明を挿入
main主な機能コード:

int main(void)
{
    
    				 

	Stm32_Clock_Init(9);		//时钟初始化
	uart_init(72, 115200);		//串口1初始化
	delay_init(72);				//延时函数初始化
	
//	DMA_UsartTx_Init();			//使能一次DMA1发送串口1数据
	
	DMA_UsartRX_Init((u32)Rx_Buff, 14);		//初始化DMA接收
	
//	while(DMA1_Channel5->CNDTR != 0);		//等待接收完成
//	printf("%s\r\n", Rx_Buff);				//输出接收内容
	
	while(1){
    
    
		
		//如果完成一次数据传输
		if(flag){
    
    	
			
			flag = 0;					//清零
			printf("%s\r\n", Rx_Buff);	//打印信息
			
		}
		
	}
	
} 

8. ダウンロードして実行:
ここに画像の説明を挿入
実験結果: 一度送信すると、再度送信をクリックしても反応しません。
どうしてこれなの?
ループ送信を有効にしていないためです。DMA が 14 ビットのデータを受信した後、(DMA_CNDTRx) レジスタ カウントは 0 になります。ループ モードを使用せず、(DMA_CNDTRx) レジスタ カウントを 14 に戻さない場合、DMA はデータの送信を続行しません。これは
( DMA_CNDTRx)レジスタに明示的に記述されています。レジスタの内容が 0 の場合、チャネルがオンになっているかどうかに関係なく、データ転送は行われません。
ここに画像の説明を挿入
したがって、引き続きデータを受信したい場合は、次の 2 つの方法があります。
1. チャネルのワーク イネーブルをオフにし (チャネルがオンになると (DMA_CNDTRx) レジスタは読み取り専用になるため、データを書き込むことはできません)、次に書き込みます。受信するデータ, 最後にチャネルを開く

	while(1){
    
    
		
		//如果完成一次数据传输
		if(flag){
    
    	
			
			flag = 0;					//清零
			
			DMA1_Channel5->CCR&=~(1<<0);	//关闭通道
			DMA1_Channel5->CNDTR = 14;		//写入计数
			DMA1_Channel5->CCR|=1<<0;		//开启通道
		
			printf("%s\r\n", Rx_Buff);	//打印信息
			
		}
		
	}

2. ループモードを使う
ここに画像の説明を挿入

要約する

実際、レジスタを使用して低レベルのドライバーを開発することには、多くの優れた機能があります。

  • コードは簡潔で明快ですが、もちろん、よくコメントする必要があります。
  • 標準ライブラリのように各関数や各パラメータの機能を覚えて探す必要がなく、すべてのレジスタを参照するための「マニュアル」があれば便利で高速です。

おすすめ

転載: blog.csdn.net/weixin_45915259/article/details/127342431