關於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部分的文章!敬請期待!!!
**我先休息去了~~╭(╯^╰)╮