STM32單片機實現DMA+ADC+UART功能

這是我參與更文挑戰的第1天,活動詳情查看: 更文挑戰數組

        忽然想測試一下STM32單片機ADC採樣速率問題,按照常規方法,能夠經過ADC採樣,而後將採樣值打印出來。可是這種方法在處理和打印數據的時候會佔用不少時間,致使處理數據的時間超過了ADC的採樣時間。因而想到了ADC採樣的數據用DMA功能存儲,並經過串口打印。可是串口打印依然要佔用單片機時間,那能不能串口數據的輸出也採用 DMA功能呢?這樣ADC採樣的數據經過DMA直接存儲,而後串口經過DMA功能直接輸出採樣到的數據。這樣速度程序執行速度不就極大的提高了嗎?說幹就幹,使用STM32F103C8T6單片機,標準庫函數,keil5軟件,編寫一個測試程序。緩存

首先實現ADC採樣並經過DMA存儲markdown

#ifndef __ADC_H
#define	__ADC_H

#include "stm32f10x.h"
// 注意:用做ADC採集的IO必須沒有複用,不然採集電壓會有影響
/********************ADC1輸入通道(引腳)配置**************************/
#define    ADC_APBxClock_FUN             RCC_APB2PeriphClockCmd
#define    ADC_CLK                       RCC_APB2Periph_ADC1

#define    ADC_GPIO_APBxClock_FUN        RCC_APB2PeriphClockCmd
#define    ADC_GPIO_CLK                  RCC_APB2Periph_GPIOA
#define    ADC_PORT                      GPIOA

#define    NOFCHANEL					 1							//使用一個通道測試

#define    ADC_PIN1                      GPIO_Pin_0
#define    ADC_CHANNEL1                  ADC_Channel_0

// ADC1 對應 DMA1通道1,ADC3對應DMA2通道5,ADC2沒有DMA功能
#define    ADC_x                         ADC1
#define    ADC_DMA_CHANNEL               DMA1_Channel1
#define    ADC_DMA_CLK                   RCC_AHBPeriph_DMA1

// ADC1轉換的電壓值經過MDA方式傳到SRAM
extern __IO uint16_t ADC_ConvertedValue[NOFCHANEL];

/**************************函數聲明********************************/
void               ADCx_Init                               ( void );
#endif /* __ADC_H */
複製代碼

首先在頭文件中定義用到的時鐘和端口,若是要修改採樣的AD口時,直接在頭文件中修改就行,程序中就不須要修改了,方便代碼的移植。下面編寫ADC代碼。函數

#include "bsp_adc.h"
__IO uint16_t ADC_ConvertedValue[NOFCHANEL] = {1000};
/**
  * @brief  ADC GPIO 初始化
  * @param  無
  * @retval 無
  */
static void ADCx_GPIO_Config( void )
{
    GPIO_InitTypeDef GPIO_InitStructure;
    // 打開 ADC IO端口時鐘
    ADC_GPIO_APBxClock_FUN ( ADC_GPIO_CLK, ENABLE );
    // 配置 ADC IO 引腳模式
    GPIO_InitStructure.GPIO_Pin = 	ADC_PIN1;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    // 初始化 ADC IO
    GPIO_Init( ADC_PORT, &GPIO_InitStructure );
}
/**
  * @brief  配置ADC工做模式
  * @param  無
  * @retval 無
  */
static void ADCx_Mode_Config( void )
{
    DMA_InitTypeDef DMA_InitStructure;
    ADC_InitTypeDef ADC_InitStructure;

    // 打開DMA時鐘
    RCC_AHBPeriphClockCmd( ADC_DMA_CLK, ENABLE );
    // 打開ADC時鐘
    ADC_APBxClock_FUN ( ADC_CLK, ENABLE );
    // 復位DMA控制器
    DMA_DeInit( ADC_DMA_CHANNEL );
    // 配置 DMA 初始化結構體
    // 外設基址爲:ADC 數據寄存器地址
    DMA_InitStructure.DMA_PeripheralBaseAddr = ( u32 ) ( & ( ADC_x->DR ) );
    // 存儲器地址
    DMA_InitStructure.DMA_MemoryBaseAddr = ( u32 )ADC_ConvertedValue;
    // 數據源來自外設
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    // 緩衝區大小,應該等於數據目的地的大小
    DMA_InitStructure.DMA_BufferSize = NOFCHANEL;
    // 外設寄存器只有一個,地址不用遞增
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    // 存儲器地址遞增
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    // 外設數據大小爲半字,即兩個字節
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
    // 內存數據大小也爲半字,跟外設數據大小相同
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
    // 循環傳輸模式
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
    // DMA 傳輸通道優先級爲高,當使用一個DMA通道時,優先級設置不影響
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    // 禁止存儲器到存儲器模式,由於是從外設到存儲器
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    // 初始化DMA
    DMA_Init( ADC_DMA_CHANNEL, &DMA_InitStructure );
    // 使能 DMA 通道
    DMA_Cmd( ADC_DMA_CHANNEL, ENABLE );
    // ADC 模式配置
    // 只使用一個ADC,屬於單模式
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
    // 掃描模式
    ADC_InitStructure.ADC_ScanConvMode = ENABLE ;
    // 連續轉換模式
    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
    // 不用外部觸發轉換,軟件開啓便可
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
    // 轉換結果右對齊
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    // 轉換通道個數
    ADC_InitStructure.ADC_NbrOfChannel = NOFCHANEL;
    // 初始化ADC
    ADC_Init( ADC_x, &ADC_InitStructure );
    // 配置ADC時鐘N狿CLK2的8分頻,即9MHz
    RCC_ADCCLKConfig( RCC_PCLK2_Div8 );
    // 配置ADC 通道的轉換順序和採樣時間
    ADC_RegularChannelConfig( ADC_x, ADC_CHANNEL1, 1, ADC_SampleTime_55Cycles5 );
    // 使能ADC DMA 請求
    ADC_DMACmd( ADC_x, ENABLE );
    // 開啓ADC ,並開始轉換
    ADC_Cmd( ADC_x, ENABLE );
    // 初始化ADC 校準寄存器
    ADC_ResetCalibration( ADC_x );
    // 等待校準寄存器初始化完成
    while( ADC_GetResetCalibrationStatus( ADC_x ) );
    // ADC開始校準
    ADC_StartCalibration( ADC_x );
    // 等待校準完成
    while( ADC_GetCalibrationStatus( ADC_x ) );
    // 因爲沒有采用外部觸發,因此使用軟件觸發ADC轉換
    ADC_SoftwareStartConvCmd( ADC_x, ENABLE );
}
/**
  * @brief  ADC初始化
  * @param  無
  * @retval 無
  */
void ADCx_Init( void )
{
    ADCx_GPIO_Config();
    ADCx_Mode_Config();
}
/*********************************************END OF FILE**********************/
複製代碼

設置ADC爲DMA傳輸,將採樣的數據由DMA自動存儲到 ADC_ConvertedValue[ ] 數組中,這裏雖然只使用了一個ADC採樣通道,可是定義了一個數組來存放採樣結果,若是想要實現多通道採樣值,只須要將其餘通道的初始化代碼添加上,同時將數組長度,也就是通道數修改一下就可使用了。初始化ADC和DMA後,ADC採樣並經過 DMA傳輸的功能就可以使用了。而後串口輸出數據的時候直接從ADC的採樣結果的數組中取值就能夠了。post

下面編寫串口相關代碼測試

#ifndef __USART_H
#define	__USART_H

#include "stm32f10x.h"
#include <stdio.h>
// 串口1-USART1
#define  DEBUG_USARTx                   USART1
#define  DEBUG_USART_CLK                RCC_APB2Periph_USART1
#define  DEBUG_USART_APBxClkCmd         RCC_APB2PeriphClockCmd
#define  DEBUG_USART_BAUDRATE           921600

// USART GPIO 引腳宏定義
#define  DEBUG_USART_GPIO_CLK           (RCC_APB2Periph_GPIOA)
#define  DEBUG_USART_GPIO_APBxClkCmd    RCC_APB2PeriphClockCmd

#define  DEBUG_USART_TX_GPIO_PORT       GPIOA
#define  DEBUG_USART_TX_GPIO_PIN        GPIO_Pin_9
#define  DEBUG_USART_RX_GPIO_PORT       GPIOA
#define  DEBUG_USART_RX_GPIO_PIN        GPIO_Pin_10
複製代碼

將串口的端口和時鐘也使用宏定義的方式,若是要改成其餘串口輸出時,直接修改頭文件就行。下來初始化串口。ui

void USART_Config( void )
{
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;

    // 打開串口GPIO的時鐘
    DEBUG_USART_GPIO_APBxClkCmd( DEBUG_USART_GPIO_CLK, ENABLE );

    // 打開串口外設的時鐘
    DEBUG_USART_APBxClkCmd( DEBUG_USART_CLK, ENABLE );

    // 將USART Tx的GPIO配置爲推輓複用模式
    GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init( DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure );

    // 將USART Rx的GPIO配置爲浮空輸入模式
    GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init( DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure );

    // 配置串口的工做參數
    // 配置波特率
    USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
    // 配置 針數據字長
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    // 配置中止位
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    // 配置校驗位
    USART_InitStructure.USART_Parity = USART_Parity_No ;
    // 配置硬件流控制
    USART_InitStructure.USART_HardwareFlowControl =
        USART_HardwareFlowControl_None;
    // 配置工做模式,收發一塊兒
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    // 完成串口的初始化配置
    USART_Init( DEBUG_USARTx, &USART_InitStructure );

    // 使能串口
    USART_Cmd( DEBUG_USARTx, ENABLE );
}
複製代碼

下來設置串口爲DMA輸出es5

#ifndef __DMA_H
#define __DMA_H
#include "stm32f10x.h"
// 串口對應的DMA請求通道
#define  USART_TX_DMA_CHANNEL     DMA1_Channel4
// 外設寄存器地址
//#define  USART_DR_ADDRESS        (USART1_BASE+0x04)			//使用地址偏移值
#define  USART_DR_ADDRESS        ((u32)&USART1->DR)				//直接使用寄存器地址
// 一次發送的數據量
#define  SENDBUFF_SIZE            6
extern  uint8_t SendBuff[SENDBUFF_SIZE];
void USARTx_DMA_Config(void);
void USARTx_DMA_Restart( void );
#endif
複製代碼

在頭文件中定義串口發送端的 DMA通道,將串口數據寄存器做爲DMA源地址,將SendBuff[ ]數組中的內容做爲內存地址,數據方向爲內存到源,這樣就直接將SendBuff[ ]數組中的數據經過DMA直接傳輸到了串口中。spa

void USARTx_DMA_Config(void)
{
		DMA_InitTypeDef DMA_InitStructure;	
		// 開啓DMA時鐘
		RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
		// 設置DMA源地址:串口數據寄存器地址*/
        DMA_InitStructure.DMA_PeripheralBaseAddr = USART_DR_ADDRESS;
		// 內存地址(要傳輸的變量的指針)
		DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff;
		// 方向:從內存到外設	
		DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
		// 傳輸大小	 
		DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE ;
		// 外設地址不增	    
		DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
		// 內存地址自增
		DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
		// 外設數據單位	
		DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
		// 內存數據單位
		DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;	 
		// DMA模式,一次或者循環模式
		DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ;							//發送一次數據
		//DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;						//循環發送數據
		// 優先級:中	
		DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; 
		// 禁止內存到內存的傳輸
		DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
		// 配置DMA通道		   
		DMA_Init(USART_TX_DMA_CHANNEL, &DMA_InitStructure);		
		// 使能DMA
		DMA_Cmd (USART_TX_DMA_CHANNEL,ENABLE);
}
//從新啓動串口 DMA
void USARTx_DMA_Restart( void )
{
    //從新開啓DMA傳輸
    DMA_Cmd( USART_TX_DMA_CHANNEL, DISABLE );
    DMA_SetCurrDataCounter( USART_TX_DMA_CHANNEL, SENDBUFF_SIZE ); //從新設置傳輸的數據數量
    DMA_Cmd( USART_TX_DMA_CHANNEL, ENABLE );                       //開啓DMA傳輸
}
複製代碼

將DMA傳輸設置爲單次傳輸模式,由於轉換後的數據爲16進制數據,爲了方便在串口助手上觀看數據,須要將16進制轉換爲字符串打印出來,因此每次每次在串口DMA傳輸數據的前,將數據轉換爲字符串,而後再打開 DMA傳輸功能,將數據經過串口發送出去。因此DMA傳輸時不能設置爲循環發送,須要每次將數據格式轉換完成後才能發送。發送完一次數據後,須要從新設置DMA傳輸數據的數量,而後從新啓動一次 DMA傳輸。指針

int main( void )
{
    char str[5];
    /* LED 端口初始化 */
    LED_GPIO_Config();
    /*初始化USART 配置模式爲 115200 8-N-1,中斷接收*/
    USART_Config();
    ADCx_Init();
    /* DMA傳輸配置 */
    USARTx_DMA_Config();

    /* USART1 向 DMA發出TX請求  單次發送模式 */
    USART_DMACmd( DEBUG_USARTx, USART_DMAReq_Tx, ENABLE );
    //串口發送緩衝區後面添加回車換行符
    SendBuff[4] = '\r';
    SendBuff[5] = '\n';
    while ( 1 )
    {
        //填充數據 只打印通道1數據
        sprintf( str, "%d", ADC_ConvertedValue[0] );		//將通道1採樣值轉換爲字符串類型
        SendBuff[0] = str[0];														//將字符串數據存入串口DMA發送緩存區
        SendBuff[1] = str[1];
        SendBuff[2] = str[2];
        SendBuff[3] = str[3];
        USARTx_DMA_Restart();

        SysTick_Delay_Us( 45 );
    }
}
複製代碼

STM32F103單片機的AD爲12位,採樣值的範圍爲0---4095,轉換爲字符串後最多爲4位,再加上回車換行符,總共6個字符。這樣串口DMA傳輸數據的長度就爲6位。

爲了測試 DMA傳輸的最大速度,將串口波特率設置爲921600。下面開始測試代碼,用函數發生器產生一個0--2V的正弦波,頻率爲100HZ。而後經過串口助手觀察採樣的數據。

經過串口波形顯示助手觀察輸出的數據波形以下

經過上面測試能夠看出,在STM32F103C8T6單片機上成功實現了ADC的 DMA採集,和串口DMA發送功能。

相關文章
相關標籤/搜索