基礎(chǔ)知識(shí)點(diǎn)
DMA
DMA(Direct Memory Access),即直接內(nèi)存存儲(chǔ),在一些數(shù)據(jù)的傳輸中,如串口、SPI等,采用DMA方式,傳輸過(guò)程不需要CPU參與,可用讓CPU有更多的時(shí)間處理其他的事情。
STM32F4的DMA通道選擇如下:

接下來(lái)的程序思路如下:

編程要點(diǎn)
DMA發(fā)送
串口DMA發(fā)送配置
由于是發(fā)送不定長(zhǎng)的數(shù)據(jù),先不需要配置發(fā)送的長(zhǎng)度,在每次的發(fā)送時(shí),再配置。
//=======================================
//串口DMA發(fā)送配置
//=======================================
void dma_uart_tx_init()
{
    DMA_InitTypeDef  DMA_InitStructure;
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2時(shí)鐘使能 
    
    DMA_DeInit(Uart_Tx_DMAStream);//使用----->DMA2_Stream7
    while (DMA_GetCmdStatus(Uart_Tx_DMAStream) != DISABLE){}//等待DMA可配置 
    /* 配置 DMA Stream */
    DMA_InitStructure.DMA_Channel            = DMA_Channel_4;              //通道選擇
    DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART1->DR;           //目的:DMA外設(shè)地址
    DMA_InitStructure.DMA_Memory0BaseAddr    = (u32)SendBuff;              //源:DMA存儲(chǔ)器0地址
    DMA_InitStructure.DMA_DIR                = DMA_DIR_MemoryToPeripheral; //方向:存儲(chǔ)器到外設(shè)模式
    //DMA_InitStructure.DMA_BufferSize       = BUF_SIZE;                   //長(zhǎng)度:數(shù)據(jù)傳輸量(先不配置)
    DMA_InitStructure.DMA_PeripheralInc      = DMA_PeripheralInc_Disable;  //外設(shè)非增量模式
    DMA_InitStructure.DMA_MemoryInc          = DMA_MemoryInc_Enable;       //存儲(chǔ)器增量模式
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外設(shè)數(shù)據(jù)長(zhǎng)度:8位
    DMA_InitStructure.DMA_MemoryDataSize     = DMA_MemoryDataSize_Byte;    //存儲(chǔ)器數(shù)據(jù)長(zhǎng)度:8位
    DMA_InitStructure.DMA_Mode               = DMA_Mode_Normal;            //使用普通模式 
    DMA_InitStructure.DMA_Priority           = DMA_Priority_Medium;        //DMA優(yōu)先級(jí):中等優(yōu)先級(jí)
    DMA_InitStructure.DMA_FIFOMode           = DMA_FIFOMode_Disable;       //FIFO模式 
    DMA_InitStructure.DMA_FIFOThreshold      = DMA_FIFOThreshold_Full;     //FIFO大小
    DMA_InitStructure.DMA_MemoryBurst        = DMA_MemoryBurst_Single;     //存儲(chǔ)器單次傳輸
    DMA_InitStructure.DMA_PeripheralBurst    = DMA_PeripheralBurst_Single; //外設(shè)單次傳輸
    DMA_Init(Uart_Tx_DMAStream, &DMA_InitStructure);//初始化DMA Stream
    
    //中斷配置
    DMA_ITConfig(Uart_Tx_DMAStream,DMA_IT_TC,ENABLE);  //配置DMA發(fā)送完成后產(chǎn)生中斷
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream7_IRQn;//
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=7;//搶占優(yōu)先級(jí)8
    NVIC_InitStructure.NVIC_IRQChannelSubPriority =0;       //子優(yōu)先級(jí)0
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;         //IRQ通道使能
    NVIC_Init(&NVIC_InitStructure); //根據(jù)指定的參數(shù)初始化VIC寄存器
    
    USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);  //使能串口1的DMA發(fā)送
    DMA_Cmd (Uart_Tx_DMAStream,DISABLE);//先不要使能DMA!           
}
DMA發(fā)送完成中斷
DMA發(fā)送完成后,觸發(fā)DMA發(fā)送完成中斷,這里可用釋放自定義的DMA發(fā)送完成信號(hào)量,表明下次的DMA傳輸可用進(jìn)行。
//=======================================
//DMA發(fā)送完成中斷服務(wù)程序
//=======================================
void DMA2_Stream7_IRQHandler(void)
{
    BaseType_t xHigherPriorityTaskWoken;
    //printf("ooooooorn");
    if(DMA_GetITStatus(Uart_Tx_DMAStream,DMA_IT_TCIF7)!= RESET) //檢查DMA傳輸完成中斷 DMA_IT_TCIF7
    {
        DMA_ClearITPendingBit(Uart_Tx_DMAStream,DMA_IT_TCIF7); 
        //printf("dma tx okrn");
        if(uartDMATCSemaphore!=NULL)
        {
            //釋放二值信號(hào)量
            xSemaphoreGiveFromISR(uartDMATCSemaphore,&xHigherPriorityTaskWoken);    //釋放DMA傳輸完成二值信號(hào)量
        }
        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);//如果需要的話進(jìn)行一次任務(wù)切換
    }
}
 
DMA發(fā)送函數(shù)接口
//=======================================
//串口DMA發(fā)送函數(shù)
//======================================= 
void uart_DMA_send(u8 *str,u16 ndtr)
{
    u8 i;
    u8 *p=str;
    
    while(xSemaphoreTake(uartDMATCSemaphore,2)!=pdTRUE);//獲取信號(hào)量,等待DMA發(fā)送可用
    
    DMA_Cmd(Uart_Tx_DMAStream, DISABLE);                      //關(guān)閉DMA傳輸 
    while (DMA_GetCmdStatus(Uart_Tx_DMAStream) != DISABLE){}    //確保DMA可以被設(shè)置  
    DMA_SetCurrDataCounter(Uart_Tx_DMAStream,ndtr);          //數(shù)據(jù)傳輸量 
    for(i=0;i;i++)>
	DMA接收
	串口DMA接收配置
	需要配置一個(gè)接收地址和一個(gè)接收長(zhǎng)度,用于DMA接收數(shù)據(jù)的暫存。
//=======================================
//串口DMA接收配置
//=======================================
void dma_uart_rx_init()
{
    DMA_InitTypeDef  DMA_InitStructure;
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2時(shí)鐘使能 
    
    DMA_DeInit(Uart_Rx_DMAStream);//使用----->DMA2_Stream5
    while (DMA_GetCmdStatus(Uart_Rx_DMAStream) != DISABLE){}//等待DMA可配置 
    /* 配置 DMA Stream */
    DMA_InitStructure.DMA_Channel            = DMA_Channel_4;              //通道選擇
    DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART1->DR;           //源:DMA外設(shè)地址
    DMA_InitStructure.DMA_Memory0BaseAddr    = (u32)ReceiveBuff;           //目的:DMA存儲(chǔ)器0地址
    DMA_InitStructure.DMA_DIR                = DMA_DIR_PeripheralToMemory; //方向:外設(shè)到存儲(chǔ)器模式
    DMA_InitStructure.DMA_BufferSize         = BUF_SIZE;                   //長(zhǎng)度:數(shù)據(jù)傳輸量
    DMA_InitStructure.DMA_PeripheralInc      = DMA_PeripheralInc_Disable;  //外設(shè)非增量模式
    DMA_InitStructure.DMA_MemoryInc          = DMA_MemoryInc_Enable;       //存儲(chǔ)器增量模式
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外設(shè)數(shù)據(jù)長(zhǎng)度:8位
    DMA_InitStructure.DMA_MemoryDataSize     = DMA_MemoryDataSize_Byte;    //存儲(chǔ)器數(shù)據(jù)長(zhǎng)度:8位
    DMA_InitStructure.DMA_Mode               = DMA_Mode_Normal;            //使用普通模式 
    DMA_InitStructure.DMA_Priority           = DMA_Priority_Medium;        //DMA優(yōu)先級(jí):中等優(yōu)先級(jí)
    DMA_InitStructure.DMA_FIFOMode           = DMA_FIFOMode_Disable;       //FIFO模式 
    DMA_InitStructure.DMA_FIFOThreshold      = DMA_FIFOThreshold_Full;     //FIFO大小
    DMA_InitStructure.DMA_MemoryBurst        = DMA_MemoryBurst_Single;     //存儲(chǔ)器單次傳輸
    DMA_InitStructure.DMA_PeripheralBurst    = DMA_PeripheralBurst_Single; //外設(shè)單次傳輸
    DMA_Init(Uart_Rx_DMAStream, &DMA_InitStructure);//初始化DMA Stream
    
    USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);  //使能串口1的DMA接收
    DMA_Cmd (Uart_Rx_DMAStream,ENABLE);//使能          
}
	串口空閑中斷
	串口空閑中斷的作用與上一篇介紹的一樣,都是在發(fā)送完一串字符后被觸發(fā),這次由于使用了DMA接收,所以接收的數(shù)據(jù)在DMA緩沖區(qū),且接收的數(shù)據(jù)長(zhǎng)度可用根DMA接收通道的總長(zhǎng)度與剩余長(zhǎng)度的差值來(lái)計(jì)算,將接收的數(shù)據(jù)復(fù)制出來(lái)使用即可,同時(shí)釋放自定義的串口空閑信號(hào)量,以便其它任務(wù)可用及時(shí)獲取串口接收到的數(shù)據(jù)。
//=======================================
//串口1空閑中斷服務(wù)程序,用于DMA接收
//=======================================
void USART1_IRQHandler(void)                    
{
    uint8_t data;//接收數(shù)據(jù)暫存變量
    BaseType_t xHigherPriorityTaskWoken;
    
    if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)//空閑中斷
    {
        data = USART1->SR;
        data = USART1->DR;
        
        DMA_Cmd(Uart_Rx_DMAStream,DISABLE);//關(guān)閉DMA接收
        while (DMA_GetCmdStatus(Uart_Rx_DMAStream) != DISABLE){}    //確保DMA可以被設(shè)置 
        rx_cnt = BUF_SIZE - DMA_GetCurrDataCounter(Uart_Rx_DMAStream);//得到真正接收數(shù)據(jù)個(gè)數(shù)  
        DMA_SetCurrDataCounter(Uart_Rx_DMAStream,BUF_SIZE);//重新設(shè)置接收數(shù)據(jù)個(gè)數(shù)    
        //printf("rx_cnt:%drn",rx_cnt);
        memcpy(rxbuf,ReceiveBuff,rx_cnt);//先復(fù)制出來(lái),防止下次的數(shù)據(jù)來(lái)了之后將其覆蓋
        DMA_ClearFlag(Uart_Rx_DMAStream,DMA_FLAG_TCIF5 | DMA_FLAG_FEIF5 | DMA_FLAG_DMEIF5 | DMA_FLAG_TEIF5 | DMA_FLAG_HTIF5);//這里的各種標(biāo)志還沒(méi)搞懂
        DMA_Cmd(Uart_Rx_DMAStream,ENABLE); //開啟DMA接收
            
        if(uartRxIDLESemaphore!=NULL)
        {
            //printf("nnnnnnnrn");
            //釋放二值信號(hào)量
            xSemaphoreGiveFromISR(uartRxIDLESemaphore,&xHigherPriorityTaskWoken);//釋放串口空閑中斷二值信號(hào)量
        }
        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);//如果需要的話進(jìn)行一次任務(wù)切換
    }
} 
	串口配置與測(cè)試任務(wù)
	串口配置
	基礎(chǔ)的GPIO配置,以及串口空閑中斷配置,并調(diào)用上面的串口DMA發(fā)送與接收配置。
//=======================================
//串口配置
//=======================================
void uart_init(u32 bound)
{
    //GPIO端口設(shè)置
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOA時(shí)鐘
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//使能USART1時(shí)鐘
    //串口1對(duì)應(yīng)引腳復(fù)用映射
    GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //GPIOA9復(fù)用為USART1
    GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); //GPIOA10復(fù)用為USART1
    //USART1端口配置
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_9 | GPIO_Pin_10; //GPIOA9與GPIOA10
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;             //復(fù)用功能
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;         //速度50MHz
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;            //推挽復(fù)用輸出
    GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;             //上拉
    GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA9,PA10
    //USART1 初始化設(shè)置
    USART_InitStructure.USART_BaudRate            = bound;                         //波特率設(shè)置
    USART_InitStructure.USART_WordLength          = USART_WordLength_8b;           //字長(zhǎng)為8位數(shù)據(jù)格式
    USART_InitStructure.USART_StopBits            = USART_StopBits_1;              //一個(gè)停止位
    USART_InitStructure.USART_Parity              = USART_Parity_No;               //無(wú)奇偶校驗(yàn)位
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//無(wú)硬件數(shù)據(jù)流控制
    USART_InitStructure.USART_Mode                = USART_Mode_Rx | USART_Mode_Tx; //收發(fā)模式
    USART_Init(USART1, &USART_InitStructure); //初始化串口1
    //DMA Config
    dma_uart_tx_init();//串口DMA發(fā)送配置
    dma_uart_rx_init();//串口DMA接收配置
    
    USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);//開啟串口空閑中斷
    //Usart1 NVIC 配置
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;       //串口1中斷通道
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=6; //搶占優(yōu)先級(jí)8
    NVIC_InitStructure.NVIC_IRQChannelSubPriority =0;       //子優(yōu)先級(jí)0
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;         //IRQ通道使能
    NVIC_Init(&NVIC_InitStructure); //根據(jù)指定的參數(shù)初始化VIC寄存器
    
    USART_Cmd(USART1, ENABLE);  //使能串口1 
}
	測(cè)試任務(wù)
	創(chuàng)建DMA發(fā)送完成信號(hào)量和串口空閑信號(hào)量,并先釋放DMA發(fā)送完成信號(hào)量,用于第一次DMA發(fā)送時(shí)獲取信號(hào)量。然后測(cè)試兩條DMA發(fā)送不定長(zhǎng)字符串,最后測(cè)試DMA接收不定長(zhǎng)字符串。
//打印任務(wù)函數(shù)(測(cè)試任務(wù))
void print_task(void *pvParameters)
{
    //創(chuàng)建二值信號(hào)量
    uartDMATCSemaphore = xSemaphoreCreateBinary();
    uartRxIDLESemaphore = xSemaphoreCreateBinary();
    xSemaphoreGive(uartDMATCSemaphore);
    
    u8 str1[]="ma nong ai xue xirn";
    uart_DMA_send(str1,sizeof(str1));
    
    u8 str2[]="xxpcb.github.iorn";
    uart_DMA_send(str2,sizeof(str2));
    
    BaseType_t err = pdFALSE;
    while(1)
    {
        err=xSemaphoreTake(uartRxIDLESemaphore,5);  //獲取信號(hào)量
        if(err==pdTRUE)                         //獲取信號(hào)量成功
        {  
            uart_DMA_send("receive:",sizeof("receive:"));
            uart_DMA_send(rxbuf,rx_cnt);
            uart_DMA_send("rn",sizeof("rn"));
            
            rx_cnt=0;
        }
    }
}
	實(shí)驗(yàn)結(jié)果
	通過(guò)串口助手,可以先接收到DMA發(fā)送的兩個(gè)字符串(第一條hello是測(cè)試串口的,不是DMA發(fā)的),然后通過(guò)串口調(diào)試助手發(fā)送兩次nice to meet you,測(cè)試DMA接收。
hello
ma nong ai xue xi
xxpcb.github.io
receive:nice to meet you
receive:nice to meet you
審核編輯:湯梓紅
                                        - 
                                串口
                                +關(guān)注
關(guān)注
15文章
1603瀏覽量
81655 - 
                                dma
                                +關(guān)注
關(guān)注
3文章
577瀏覽量
105101 - 
                                RTOS
                                +關(guān)注
關(guān)注
24文章
858瀏覽量
122423 - 
                                FreeRTOS
                                +關(guān)注
關(guān)注
14文章
496瀏覽量
66105 
發(fā)布評(píng)論請(qǐng)先 登錄
FreeRTOS串口中斷接收不定長(zhǎng)的數(shù)據(jù)與二值信號(hào)量的使用
    
如何使用DMA進(jìn)行USART不定長(zhǎng)度接收
    
stm32串口怎么用DMA接收不定長(zhǎng)數(shù)據(jù)?
不定長(zhǎng)數(shù)據(jù)接收的原理是什么?怎么實(shí)現(xiàn)串口數(shù)據(jù)的不定長(zhǎng)接收?
請(qǐng)問(wèn)串口DMA+環(huán)形緩沖區(qū)如何實(shí)現(xiàn)不定長(zhǎng)度的數(shù)據(jù)收發(fā)?
如何去實(shí)現(xiàn)stm32f405串口DMA+空閑中斷不定長(zhǎng)數(shù)據(jù)收發(fā)代碼
HAL庫(kù)的DMA+CobeMx方式下的不定長(zhǎng)收發(fā)
請(qǐng)問(wèn)STM32 DMA串口接收不定長(zhǎng)數(shù)據(jù)的過(guò)程是怎樣的?
stm32 串口接收不定長(zhǎng)度數(shù)據(jù)及黏包處理 + 串口DMA接收
    
STM32之串口DMA接收不定長(zhǎng)數(shù)據(jù)
    
STM32F429 標(biāo)準(zhǔn)庫(kù) 串口完成中斷+DMA 接收不定長(zhǎng)數(shù)據(jù)
    
STM32CubeMX之串口接收不定長(zhǎng)數(shù)據(jù)
    
          
        
        
FreeRTOS串口DMA收發(fā)不定長(zhǎng)數(shù)據(jù)
                
 
           
            
            
                
            
評(píng)論