這是我參與更文挑戰的第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發送功能。