教你手寫DMA傳輸數據(看完這篇你就會手動寫啦,保姆級講解)---- 2020.3.31

關於DMA與串口原理方面的文章:

嵌入式stm32 複習(工作用)— USART(串口)通信原理知識 2020.3.23
添加鏈接描述

教你手寫串口收發數據(看完這篇你就會手動寫啦,保姆級講解)---- 2020.3.28
添加鏈接描述

嵌入式stm32 複習(工作用)— DMA控制器知識 2020.3.30
添加鏈接描述

先上完整DMA串口收發部分代碼!!!

#define CR1_OVER8_Set             ((u16)0x8000)  /* USART OVER8 mode Enable Mask */

void USART_DMA_Init(void) {

	//1.開啓時鐘
	RCC->AHBENR |= 1 << 0;//DMA1時鐘

	//2.配置DMA
	DMA1_Channel5->CCR = 0;//先清零
	DMA1_Channel5->CCR |= 1 << 7;//執行存儲器地址增量操作
	DMA1_Channel5->CCR |= 1 << 12;//中等優先級

	//設置DMA數據接收大小
	DMA1_Channel5->CNDTR = USART1_REC_MAX - 1;//允許最大接收數據緩存大小,注意,必須在DMA停止時設置
	//設置外設數據地址
	DMA1_Channel5->CPAR = (u32) &USART1->DR;
	//設置內存緩存器的地址
	DMA1_Channel5->CMAR = (u32) USART1_REC;

	//3.使能DMA
	DMA1_Channel5->CCR |= 1 << 0;

}

u16 USART_GetBound(USART_TypeDef* USARTx, int bound) {
	uint32_t integerdivider = 0x00, apbclock = 0x00;
	uint32_t tmpreg = 0x00;
	uint32_t fractionaldivider = 0x00;

	apbclock = 72000000; //如是USART1是72M,否則是36M

	/* Determine the integer part */
	if ((USARTx->CR1 & CR1_OVER8_Set) != 0) {
		/* Integer part computing in case Oversampling mode is 8 Samples */
		integerdivider = ((25 * apbclock) / (2 * bound));
	} else /* if ((USARTx->CR1 & CR1_OVER8_Set) == 0) */
	{
		/* Integer part computing in case Oversampling mode is 16 Samples */
		integerdivider = ((25 * apbclock) / (4 * bound));
	}
	tmpreg = (integerdivider / 100) << 4;

	/* Determine the fractional part */
	fractionaldivider = integerdivider - (100 * (tmpreg >> 4));

	/* Implement the fractional part in the register */
	if ((USARTx->CR1 & CR1_OVER8_Set) != 0) {
		tmpreg |= ((((fractionaldivider * 8) + 50) / 100)) & ((uint8_t) 0x07);
	} else /* if ((USARTx->CR1 & CR1_OVER8_Set) == 0) */
	{
		tmpreg |= ((((fractionaldivider * 16) + 50) / 100)) & ((uint8_t) 0x0F);
	}
	return tmpreg;

}

void USART1_Init(int bound) {

	//1.使能時鐘
	RCC->APB2ENR |= 1 << 2;//GPIOA 時鐘使能
	RCC->APB2ENR |= 1 << 14;//USART1 時鐘使能

	//2.初始化GPIO
	GPIOA->CRH &= ~(0x0F << 4);
	GPIOA->CRH |= 0x0B << 4;//設置GPIOA.9 -> 50MHz,複用推免

	GPIOA->CRH &= ~(0x0F << 8);
	GPIOA->CRH |= 0x04 << 8;//設置GPIOA.10 -> 浮空輸入模式

	//3.初始化USART1
	//3.1 設置波特率
	USART1->BRR = USART_GetBound(USART1, bound);// 0x1D4 << 4 | 0x4B;//設置波特率9600
	//3.2 設置校驗位
	USART1->CR1 &= ~(1 << 10);//不使用校驗位
	//3.3 數據位
	USART1->CR1 &= ~(1 << 12);//8位長度
	//3.4 停止位
	USART1->CR2 &= ~(0x02 << 12);//1個停止位
	//4.使能
	USART1->CR1 |= 1 << 3;//使能TX
	USART1->CR1 |= 1 << 2;//使能RX
	USART1->CR1 |= 1 << 13;//使能USART1

	USART1->CR3 |= 1 << 6;//開啓USART1的RX DMA

	//配置數據接收時候,需要用到中斷
	//5.配置NVIC
	//5.1 先分組
	NVIC_SetPriorityGrouping(5);
	NVIC_EnableIRQ(USART1_IRQn);//使能USART1的全局中斷
	NVIC_SetPriority(USART1_IRQn, 10);//設置USART1的中斷優先級是10

	//6 使能接受數據中斷寄存器
//	USART1->CR1 |= 1 << 5;	,如果使用DMA方式,需要開啓數據接收中斷
	USART1->CR1 |= 1 << 4;//開啓IDLE中斷

	USART_DMA_Init();
}

u8 USART1_REC[USART1_REC_MAX] = { 0 };
//USART1_STA[15] 標識是否接收完成狀態位,1:標識接收完成;0:未接收完成;
//USART1_STA[14] 標識是否爲第一個數據,1:非第一個數據;0:表示第一個數據;
//USART1_STA[13:0] 標識有效的數據接收長度
u16 USART1_STA = 0;

void USART1_IRQHandler(void) {

	if (USART1->SR & 0x10) {	//總線空閒
		USART1_STA = (USART1_REC_MAX - 1) - DMA1_Channel5->CNDTR;//實際接收的數據大小

		USART1_REC[USART1_STA & 0x3FFF] = '\0';//保證有效字符串

		USART1_STA |= 0x8000;//標識接收成功

		USART1->DR;//清空狀態

		DMA1_Channel5->CCR &= ~(1 << 0);//先關閉DMA
		DMA1_Channel5->CNDTR = USART1_REC_MAX - 1;//還原允許的數據緩衝區的大小
		DMA1_Channel5->CCR |= (1 << 0);//啓動DMA
	}

}

void USART1_SendData(u8 *data, u8 len) {
	u8 i = 0;

	for (i = 0; i < len; i++) {
		if (*(data + i) == '\0')	//空白符號無需發送
			return;
		//判斷是否允許發送數據
		while ((USART1->SR & 0x40) == 0)
			;
		USART1->DR = *(data + i);	//等效於下面的函數
//		USART_SendData(USART1, *(data + i));
	}
}

int fputc(int ch, FILE* f) {
	while ((USART1->SR & 0x40) == 0)
		;
	USART1->DR = (u8) ch;	//等效於下面的函數
	return ch;
}

好!按照老樣子,接下來開始詳細講解每行代碼的用處,以及爲什麼這樣寫!

串口DMA初始化部分

//1.開啓時鐘
RCC->AHBENR |= 1 << 0;//DMA1時鐘

//
在這裏插入圖片描述//由上圖可知DMA是在AHB總線下的,所以需要開啓AHB時鐘,並且我們這裏使用的是DMA1。
//
在這裏插入圖片描述//
在這裏插入圖片描述

//2.配置DMA
DMA1_Channel5->CCR = 0;//先清零
DMA1_Channel5->CCR |= 1 << 7;//執行存儲器地址增量操作
DMA1_Channel5->CCR |= 1 << 12;//中等優先級

//
在這裏插入圖片描述//咦?這裏爲啥是反的?大家湊合看吧~
在這裏插入圖片描述
//
在這裏插入圖片描述//因爲這裏使用過的就是USART1的讀取,因爲我們今天使用的過程就是通過RX讀取外設上的數據,然後再通過DMA通道5來發送數據到存儲器中。

//在這裏插入圖片描述
在這裏插入圖片描述//所以這裏我們先清零,然後才能將我們需要寫入的數據放到該寄存器中。其實另一方面也相當於設置成我們想要的配置了,大家往下看就知道了。
//
在這裏插入圖片描述//因爲我們這裏是外設到存儲器,所以該位爲0。

//在這裏插入圖片描述//串口傳輸的長度是8位,所以11:10位也爲0。
//
在這裏插入圖片描述//這裏我們外設地址不加一,所以該位爲0。
//
在這裏插入圖片描述
//這裏我們不執行循環操作,因爲如果是循環操作的話,那麼DMA寫數據到存儲器中,是不會管存儲器緩存空間是否溢出,所以我們等等需要設置的就是得自己認爲關閉掉DMA,然後再清空緩存區裏面的數據,最後再開啓,相當於是人爲進行循環了。

//在這裏插入圖片描述//因爲我們這裏就是從外設讀數據到存儲器中,所以該位爲0。

//由上圖我們可以得知,存儲器地址是加一的,外設地址是不加一的。
//
在這裏插入圖片描述//在這裏插入圖片描述//這裏我們設置爲中等優先級,如果我們在用DMA傳輸的數據比較重要的話,在這裏可以改成高或者最高優先級。

//設置DMA數據接收大小
DMA1_Channel5->CNDTR = USART1_REC_MAX - 1;//允許最大接收數據緩存大小,
//注意,必須在DMA停止時設置

//
在這裏插入圖片描述//在前幾篇關於串口收發數據的文章中我們可以得知USART1_REC_MAX是可以接收的最大數據長度,但是由於最後我們要表示該數據爲有效數據,往往在最後加一個“/0”停止符。所以我們這裏還是減一,留出一個空間來存放“/0”。

//設置外設數據地址
DMA1_Channel5->CPAR = (u32) &USART1->DR;

//在這裏插入圖片描述//這裏就是將串口的數據賦值給這個寄存器,然後DMA再進行傳輸。

//設置內存緩存器的地址
DMA1_Channel5->CMAR = (u32) USART1_REC;

//
在這裏插入圖片描述//最後存儲器中的緩衝區爲USART1_REC。

//3.使能DMA
DMA1_Channel5->CCR |= 1 << 0;

//在這裏插入圖片描述//這裏就是開啓DMA對應的通道,也相當於是使能。

串口DMA讀取數據中斷服務函數部分

if (USART1->SR & 0x10) {	//總線空閒

//
在這裏插入圖片描述//當檢測到串口總線空閒,那麼就代表數據傳輸完成。
//
在這裏插入圖片描述

USART1_STA = (USART1_REC_MAX - 1) - DMA1_Channel5->CNDTR;//實際接收的數據大小

//
在這裏插入圖片描述//由上圖我們可以得知,該寄存器代表的是現在的緩存區中還有多少個空餘的緩存區。
那麼我們想知道具體到底傳輸了多少個數據,那就可以用我們自己設置的最大可接受數據長度減掉這個寄存器值。

USART1_REC[USART1_STA & 0x3FFF] = '\0';//保證有效字符串

//由於上一篇文章中也講了,所以這裏就不再贅述了。

USART1_STA |= 0x8000;//標識接收成功
USART1->DR;//清空狀態

//同理不再贅述。

DMA1_Channel5->CCR &= ~(1 << 0);//先關閉DMA
DMA1_Channel5->CNDTR = USART1_REC_MAX - 1;//還原允許的數據緩衝區的大小
DMA1_Channel5->CCR |= (1 << 0);//啓動DMA

//在前面我們也說過我們設置的不是循環模式,所以如果想要源源不斷的傳輸數據並且保證最終的存儲區不溢出,那麼我們就需要先關閉DMA,然後還原允許的數據緩衝區的大小,最後再啓動DMA即可。

結束語

個人認爲大家如果細心看完這篇文章,並且結合上一篇文章一起看(在文章的剛開始會將前幾篇關於USART和DMA原理部分的文章鏈接發出來),我相信大家會徹底掌握DMA了!!!如果覺得這篇文章還不錯的話,記得點贊 ,支持下!!!

以後我會繼續推出關於嵌入式(stm32)的協議方面的講解,下一講會推出PWM部分的文章!敬請期待!!!

**我先休息去了~~╭(╯^╰)╮

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章