江科大学のビデオと CSDN の著名人のブログを研究して、DMA についての理解を以下に記録しました。
記事ディレクトリ
1. メモリ、レジスタ
メモリについては、前回の記事でROMとRAM、特にSRAM、フラッシュ、周辺レジスタについて書きましたが、ここでJiang Keda氏が知識ポイントを述べており、理解に役立ちます。
まず、たとえば、16 進数を定義します:
uint8_t a=0x66;
次に、コンパイル後、SRAM で a のアドレス空間が開かれます。たとえば、アドレスは 0x20000000 で、a は変数であるため、a に対応します。スラムです。
キーワード const を使用すると、変数が定数
const uint8_t a=0x66 になる場合、
a はフラッシュに保存され、アドレスは 0x080000FF になります。フラッシュ ストレージは読み取り専用であるため、定数は変更できず、読み取り専用属性になります。したがって、一部のフォントまたはルックアップ テーブルについては、const を追加してフラッシュに保存し、SRAM スペースを解放できます。
さらに、ペリフェラル レジスタのアドレスをクエリする場合は、まずデータシートでメモリ イメージを検索し、USART2 の開始アドレスを見つけてから、USART2 の章で特定のレジスタ イメージを見つけます。オフセット、開始アドレス + オフセット = 特定のレジスタ アドレス。コードから見つけたい場合:
例: USART3 の DATAR データ レジスタ アドレスを見つけたい場合
は、USART2 の開始アドレスとオフセットを見つける必要があります。以下の図に示すように:
USART3 のベース アドレスは、APB1 ペリフェラルのベース アドレス + オフセット 0x4800 です。
また、フラッシュ、SRAM、バス ペリフェラルのベース アドレスも確認でき、APB1 ペリフェラルのベース アドレスが 0x40000000 であることがわかります。 、USART3アドレスのベースアドレスはわかりますが、USART3のオフセットは何ですか? ここでは、構造変数がオフセットを参照するという賢い方法が使用されています。
このとき、USARTの構造体メンバ変数はアドレス上の実際のレジスタと同じ順序になっており、構造体の各メンバが実際のレジスタにマッピングされているだけであり、実際には指定した構造体メンバのアドレスとアドレスは一致しています。対応する周辺レジスタの。これによりオフセットの問題が解決されます。&USART3->DATAR は USART3 の構造体ポインタを指定し、DATAR メンバを指すことはオフセットアドレスを追加します。
ここでもう 1 つの知識ポイントがあります。
組み込みシステムでは、一般的に使用されるデータ型には uint8_t、uint16_t、uint32_t などが含まれます。これは、それらのビット数が少なく、メモリ領域を節約できるためです。データ送信中に、実際のニーズに応じて適切なデータ タイプを選択して送信できます。
uint8_t: 8 ビットの符号なし整数を表し、値の範囲は 0 ~ 255 です;
uint16_t: 16 ビットの符号なし整数を表します、値の範囲は 0 ~ 65535 です;
uint32_t: 32 ビットの符号なし整数を表します、値の範囲は0~4294967295 。
同様に、int8_t は符号付き 8 ビット整数型で、値の範囲は -128 ~ 127 です。
u8 と uint8_t の機能は同じで、どちらも符号なし 8 ビット整数を表すために使用されます。ただし、u8 は通常、特定のシナリオ (組み込みコンパイラなど) ではカスタム型であり、uint8_t は C 言語の組み込み型です。つまり、たとえば、最大データが 255 を超えない場合
、配列を uint8_t data[100] として設定すると、メモリ領域を大幅に節約できますが、同時にシリアル ポートは 1 バイト単位でデータを送受信するため、8 ビット整数を設定することもできます。シリアルポートの機能に影響します。
2、特定のコード
ここではデュアル開発ボードを使用し、1 つは STM32F103RCT6 開発ボード、もう 1 つは Qinheng CH32V307 開発ボードです。実現する機能は次のとおりです: STM32 を送信側として DMA+シリアル ポートを使用し、温度と湿度の値を生成する 2 つの関数を作成し
ますそれを配列に格納し、配列のデータをシリアルポート+DMAにより受信側に送信します。
DMA+シリアル ポートを使用するレシーバーとして、CH32V307 は受信した配列内のデータを識別し、シリアル ポート デバッグ アシスタントを使用して出力します。
1. STM32 (送信側) DMA 構成
(1)送信側としては、当然メモリからシリアルポートのデータレジスタにデータを送信することになるので、配列を定義し、その配列の要素として温度と湿度の値をそれぞれ返す関数を2つ書きます。
uint8_t data[2];
uint8_t get_temperature(){
data[0]=rand() % 126 ;
return data[0];// 限定温度在-40到85摄氏度之间
}
uint8_t get_humi(){
data[1]=rand() % 101; // 限定湿度在0到100%之间
return data[1];
}
(2) DMA を設定します。方向はメモリから周辺レジスタです。
ここではシリアルポート 2 が使用されます。データシートを確認すると、USART2 の TX 機能が DMA1 のチャネル 7 に対応していることがわかりますので、設定する場合は注意してください。
まずシリアル ポート 2 を通常どおりに設定します。ボー レートは 9600 です。
コードは以下のように表示されます。
void Init_USART2(){
GPIO_InitTypeDef GPIO_InitStructure;//声明一个结构体对象
USART_InitTypeDef USART_InitStructure;
//NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);//USART2挂载APB1总线
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//GPIOA挂载APB2总线
//对于哪个应用挂载哪个APB总线,可以根据代码自动补全功能快捷判断
//TX端口-PA2
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;//这个对象的成员变量GPIO_Pin取值为pin2
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//模式为复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//50MHZ速度
GPIO_Init(GPIOA,&GPIO_InitStructure);
//RX端口-PA3
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;//这个对象的成员变量GPIO_Pin取值为pin3
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//模式为浮空输入模式
GPIO_Init(GPIOA,&GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = 9600;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;//收发模式并存
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
USART_InitStructure.USART_StopBits = USART_StopBits_1;//1位停止位
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//八位数据位
USART_Init(USART2,&USART_InitStructure);
//USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);//开启串口2的中断接收
USART_Cmd(USART2,ENABLE);
}
(3) DMA 初期化の設定
void USART2_DMA_Tx_Configuration(void)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2 , ENABLE); //DMA2时钟使能
DMA_DeInit(DMA1_Channel7);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART2->DR; //DMA外设地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)USART2_DMA_TX_Buffer; //发送缓存指针
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //传输方向,从内存到外设
DMA_InitStructure.DMA_BufferSize = USART2_DMA_TX_BUFFER_MAX_LENGTH; //传输长度
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设递增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存递增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设数据宽度:BYTE
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //内存数据宽度:BYTE
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //循环模式:否//(注:DMA_Mode_Normal为正常模式,DMA_Mode_Circular为循环模式)
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; //优先级:高
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //内存:内存(都)
DMA_Init(DMA1_Channel7 , &DMA_InitStructure); //初始化DMA1_Channel4
//DMA_ClearFlag(DMA1_FLAG_GL4);
DMA_ClearFlag(DMA1_FLAG_GL7);
DMA_Cmd(DMA1_Channel7 , DISABLE); //禁用DMA通道传输
USART_DMACmd(USART2, USART_DMAReq_Tx, ENABLE); //开启串口DMA发送
}
(4) DMA が伝達機能をオンにする
void DMA_send(){
//开启计数器,在传输过程中,DMA控制器会持续地递减该计数器的值,直到计数器为0,表示数据传输完成。
int len = sizeof(data);
memcpy(USART2_DMA_TX_Buffer, (uint8_t*)data, len);
DMA_SetCurrDataCounter(DMA1_Channel7,USART2_DMA_TX_BUFFER_MAX_LENGTH);
DMA_Cmd(DMA1_Channel7, ENABLE);//开启DMA传输
while(DMA_GetFlagStatus(DMA1_FLAG_TC7) != SET);
DMA_Cmd(DMA1_Channel7, DISABLE);//关闭DMA传输
DMA_ClearFlag(DMA1_FLAG_TC7);
}
(5) メインプログラム
int main(void)
{
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
uart_init(9600);
Init_USART2();
USART2_DMA_Tx_Configuration();
printf("666");
while(1)
{
get_humi();
get_temperature();
DMA_send();
delay_ms(1000);
}
}
2. CH32V307(受信機)は通常のシリアルポート割り込み受信を採用
まず、一般的なシリアル ポート受信方法を使用します。
void USART2_IRQHandler(void)
{
u8 Res;
int i;
if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
{
static uint8_t idx=0;//当前接收到的字节数
static uint8_t* ptr=(uint8_t*)res_data;//将数据转化为字节数组
Res=USART_ReceiveData(USART2);//读取接收到的字节
if(idx<data_length*sizeof(int)){
//如果数据还未接受完
ptr[idx++]=Res;//将接收到的数据存储到数组中
}
if (idx==data_length*sizeof(int)) {
//如果数据接收完毕
idx=0;
for ( i = 0; i < data_length;i++) {
res_data[i]=*((int*)(ptr+i*sizeof(int)));
}
}
USART_ClearITPendingBit(USART2, USART_IT_RXNE); // 清除接收中断标志位
}
}
この方法は比較的一般的であり、CPU リソースを消費します。たとえば、100 バイトのデータを送信すると、CPU は頻繁に 100 回の割り込みを入力しますが、これは明らかに、すべてのデータがパッケージ化されてから 1 回だけ割り込みを行う DMA より劣ります。送信済。
3. CH32V307 (レシーバー) DMA 構成
また、CH32V307 のシリアルポート 2 を使用して DMA 受信を設定します。
(1) 受信側として、自然データをシリアル ポート データ レジスタからメモリに送信する必要があるため、DMA 構成を変更する必要があります。
//DMA1的通道6对应USART2的RX
void DMA_RX_init(){
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1 , ENABLE); //DMA2时钟使能
DMA_DeInit(DMA1_Channel6);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART2->DATAR; //DMA外设地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)USART2_RxBuf; //发送缓存指针
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //传输方向,从外设到内存
DMA_InitStructure.DMA_BufferSize = USART_MAX_LEN; //传输长度
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设递增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存递增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设数据宽度:BYTE
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //内存数据宽度:BYTE
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //循环模式:否//(注:DMA_Mode_Normal为正常模式,DMA_Mode_Circular为循环模式)
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; //优先级:高
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //内存:内存(都)
DMA_Init(DMA1_Channel6 , &DMA_InitStructure); //初始化DMA1_Channel4
//DMA_ClearFlag(DMA1_FLAG_GL4);
//DMA_ClearFlag(DMA1_FLAG_GL6);
DMA_Cmd(DMA1_Channel6 , DISABLE); //禁用DMA通道传输
USART_DMACmd(USART2, USART_DMAReq_Rx, ENABLE); //开启串口DMA接收
USART_Cmd(USART2, ENABLE); //使能串口
}
(2) DMA起動プログラム
void USART2_Server(){
uint16_t i,len;
// len = USART_MAX_LEN - DMA_GetCurrDataCounter(DMA1_Channel6); // 获取接收到的数据长度 单位为字节
//DMA_SetCurrDataCounter(DMA1_Channel6,USART_MAX_LEN); // 重新赋值计数值,必须大于等于最大可能接收到的数据帧数目
DMA_Cmd(DMA1_Channel6 , ENABLE);
DMA_SetCurrDataCounter(DMA1_Channel6,USART_MAX_LEN); // 重新赋值计数值,必须大于等于最大可能接收到的数据帧数目
//USART_ReceiveData(USART2); // 清除空闲中断标志位(接收函数有清标志位的作用)
//printf("data=%d\r\n",USART_ReceiveData(USART2));
// printf("data1=%d\r\n",USART2_RxBuf[0]);
//DMA_Cmd(DMA1_Channel6, DISABLE); // 关闭DMA1_Channel6不再接收数据
while(DMA_GetFlagStatus(DMA1_FLAG_TC6)==RESET);
DMA_Cmd(DMA1_Channel6, DISABLE);
DMA_ClearFlag(DMA1_FLAG_TC6); // 清DMA1_Channel6接收完成标志位
//DMA_SetCurrDataCounter(DMA1_Channel6,USART_MAX_LEN); // 重新赋值计数值,必须大于等于最大可能接收到的数据帧数目
//for (i = 0; i < len; ++i) { // 把接收到的数据转移到发送数组
// res_data[i] = USART2_RxBuf[i];
// printf("res%d=%d\r\n",i,res_data[i]);
}
(3) メインプログラム
extern uint8_t USART2_RxBuf[USART_MAX_LEN]; //接收缓存
extern uint8_t res_data[2];
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
SystemCoreClockUpdate();
Delay_Init();
USART_Printf_Init(115200);
printf("SystemClk:%d\r\n", SystemCoreClock);
//printf( "ChipID:%08x\r\n", DBGMCU_GetCHIPID() );
printf("RTC Test\r\n");
// USART3_INIT(9600);
uart2_init(9600);
DMA_RX_init();
printf(" Test\r\n");
while(1)
{
USART2_Server();
printf("res_data[0]=%d\r\n",USART2_RxBuf[0]);
printf("res_data[1]=%d\r\n",USART2_RxBuf[1]);
Delay_Ms(1000);
}
}
3. 結果:
送信側がランダムデータを生成すると、そのデータはメモリからシリアルポート2のデータレジスタに転送され、受信側に送信されます。受信側のデータレジスタはDMAにより指定されたメモリに転送されます。図に示すように、実験結果は正しいです。
要約する
DMAは本当に便利です。ただし、DMA+ 割り込みはまだ使用したことがないので、後で試してみるかもしれません。