新聞中心

        EEPW首頁 > 嵌入式系統 > 設計應用 > STM32 ADC結合DMA數據采樣與軟件濾波處理

        STM32 ADC結合DMA數據采樣與軟件濾波處理

        作者: 時間:2016-11-17 來源:網絡 收藏
        作為一個偏向工控的芯片,ADC采樣是一個十分重要的外設。STM32集成三個12位精度18通道的內部ADC,最高速度1微秒,結合DMA可以解放CPU進行更好的處理。
        ADC接口上的其它邏輯功能包括:
        ●同步的采樣和保持
        ●交叉的采樣和保持
        ●單次采樣
        模擬看門狗功能允許非常精準地監視一路、多路或所有選中的通道,當被監視的信號超出預置的閥值時,將產生中斷。
        由標準定時器(TIMx)和高級控制定時器(TIM1和TIM8)產生的事件,可以分別內部級聯到ADC的開始觸發和注入觸發,應用程序能使AD轉換與時鐘同步。

        12位ADC是一種逐次逼近型模擬數字數字轉換器。它有多達18個通道,可測量16個外部和2個內部信號源。

        ADC的輸入時鐘不得超過14MHZ,它是由PCLK2經分頻產生。

        如果被ADC轉換的模擬電壓低于低閥值或高于高閥值,AWD模擬看門狗狀態位被設置。

        關于ADC采樣與DMA關系,引用網上一段解釋:

        STM32 的優點在哪里?
        除去宣傳環節,細細分析。
        STM32 時鐘不算快,72MHZ,
        也不能擴展大容量的RAM FLASH,
        同樣沒有 DSP 那樣強大的指令集。
        它的優勢在哪里呢?
        ---就在快速采集數據,快速處理上。
        ARM 的特點就是方便。
        這個快速采集,高性能的ADC 就是一個很好的體現,
        12 位精度,最快1uS 的轉換速度,通常具備2 個以上獨立的ADC 控制器,
        這意味著,
        STM32 可以同時對多個模擬量進行快速采集,
        這個特性不是一般的MCU具有的。
        以上高性能的 ADC,配合相對比較塊的指令集和一些特色的算法支持,
        就構成了STM32 在電機控制上的強大特性。
        好了,正題,怎末做一個簡單的ADC,注意是簡單的,
        ADC 是個復雜的問題,涉及硬件設計,電源質量,參考電壓,信號預處理等等問題。
        我們只就如何在MCU內完成一次ADC 作討論。
        談到 ADC,我們還要第一次引入另外一個重要的設備DMA.
        DMA是什么東西呢。
        通常在 8 位單片機時代,很少有這個概念。
        在外置資源越來越多以后,
        我們把一個MCU內部分為主處理器和 外設兩個部分。
        主處理器當然是執行我們指令的主要部分,
        外設則是串口 I2C ADC 等等用來實現特定功能的設備
        回憶一下,8 位時代,我們的主處理器最常干的事情是什么?
        邏輯判斷?不是。那才幾個指令
        計算算法?不是。大部分時候算法都很簡單。
        事實上,主處理器就是作個搬運工,
        把 USART 的數據接收下來,存起來
        把 ADC 的數據接收下來,存起來
        把要發送的數據,存起來,一個個的往USART 里放。
        …………
        為了解決這個矛盾,
        人們想到一個辦法,讓外設和內存間建立一個通道,
        在主處理器允許下,
        讓外設和內存直接讀寫,這樣就釋放了主處理器,
        這個東西就是DMA。
        打個比方:
        一個MCU是個公司。
        老板就是主處理器
        員工是外設
        倉庫就是內存
        從前 倉庫的東西都是老板管的。
        員工需要原料工作,就一個個報給老板,老板去倉庫里一個一個拿。
        員工作好的東西,一個個給老板,老板一個個放進倉庫里。
        老板很累,雖然老板是超人,也受不了越來越多的員工和單子。
        最后老板雇了一個倉庫保管員,它就是DMA
        他專門負責入庫和出庫,
        只需要把出庫和入庫計劃給老板過目
        老板說 OK,就不管了。
        后面的入庫和出庫過程,
        員工只需要和這個倉庫保管員打交道就可以了。
        --------閑話,馬七時常想,讓設備與設備之間開DMA,豈不更牛X
        比喻完成。
        ADC 是個高速設備,前面提到。
        而且 ADC 采集到的數據是不能直接用的。即使你再小心的設計外圍電路,測的離譜的數據總會出現。
        那么通常來說,是采集一批數據,然后進行處理,這個過程就是軟件濾波。
        DMA用到這里就很合適。讓ADC 高速采集,把數據填充到RAM 中,填充一定數量,比如32 個,64 個MCU再來使用。
        -----多一句,也可以說,單次ADC 毫無意義。
        下面我們來具體介紹,如何使用DMA來進行ADC 操作。
        初始化函數包括兩部分,DMA 初始化和ADC 初始化
        我們有多個管理員--DMA
        一個管理員當然不止管一個DMA 操作。所以DMA有多個Channel

        以下是程序分析:
        程序基于STM32F103VET6,庫函數實現
        RCC部分:(忽略系統時鐘配置)
        //啟動DMA時鐘
        RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
        //啟動ADC1時鐘
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
        GPIO部分:(ADC引腳參見上表)
        //ADC_CH10--> PC0
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//模擬輸入
        GPIO_Init(GPIOC, &GPIO_InitStructure);
        // PC2
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
        GPIO_Init(GPIOC, &GPIO_InitStructure);
        ADC1配置:(兩外部輸入,另采樣內部溫度傳感器
        void ADC1_Configuration(void)
        {
        ADC_InitTypeDef ADC_InitStructure;

        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 = 3; //設置轉換序列長度為3,三通道
        ADC_Init(ADC1, &ADC_InitStructure);

        //ADC內置溫度傳感器使能(要使用片內溫度傳感器,切忌要開啟它)
        ADC_TempSensorVrefintCmd(ENABLE);

        //常規轉換序列1:通道10
        ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 1, ADC_SampleTime_239Cycles5);
        //常規轉換序列2:通道16(內部溫度傳感器),采樣時間>2.2us,(239cycles)
        ADC_RegularChannelConfig(ADC1, ADC_Channel_16, 2, ADC_SampleTime_239Cycles5);
        ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 3, ADC_SampleTime_239Cycles5);
        //輸入參數:ADC外設,ADC通道,轉換序列順序,采樣時間
        // Enable ADC1
        ADC_Cmd(ADC1, ENABLE);
        // 開啟ADC的DMA支持(要實現DMA功能,還需獨立配置DMA通道等參數)
        ADC_DMACmd(ADC1, ENABLE);

        // 下面是ADC自動校準,開機后需執行一次,保證精度
        // Enable ADC1 reset calibaration register
        ADC_ResetCalibration(ADC1);
        // Check the end of ADC1 reset calibration register
        while(ADC_GetResetCalibrationStatus(ADC1));

        // Start ADC1 calibaration
        ADC_StartCalibration(ADC1);
        // Check the end of ADC1 calibration
        while(ADC_GetCalibrationStatus(ADC1));
        // ADC自動校準結束---------------
        ADC_SoftwareStartConvCmd(ADC1, ENABLE); //ADC啟動
        }
        DMA配置:(無軟件濾波)
        void DMA_Configuration(void)
        {
        DMA_InitTypeDef DMA_InitStructure;

        DMA_DeInit(DMA1_Channel1);
        DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address; //DMA外設地址,在頭部定義
        DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&AD_Value; //內存地址
        DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //外設至內存模式
        //BufferSize=2,因為ADC轉換序列有2個通道
        //如此設置,使序列1結果放在AD_Value[0],序列2結果放在AD_Value[1]
        DMA_InitStructure.DMA_BufferSize = 3; //一次轉換三個
        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;
        //循環模式開啟,Buffer寫滿后,自動回到初始地址開始傳輸
        DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
        DMA_InitStructure.DMA_Priority = DMA_Priority_High;
        DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
        DMA_Init(DMA1_Channel1, &DMA_InitStructure);
        //配置完成后,啟動DMA通道
        DMA_Cmd(DMA1_Channel1, ENABLE);
        }
        此DMA例程用于單次ADC轉換,配合軟件濾波可做如下改動:
        全局聲明:
        vu16 AD_Value[30][3]; //AD采樣值
        vu16 After_filter[3]; //AD濾波后
        DMA部分:(帶中斷濾波)
        void DMA_Configuration(void)
        {
        DMA_InitTypeDef DMA_InitStructure;

        DMA_DeInit(DMA1_Channel1);
        DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;
        DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&AD_Value;
        DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
        //BufferSize=2,因為ADC轉換序列有2個通道
        //如此設置,使序列1結果放在AD_Value[0],序列2結果放在AD_Value[1]
        DMA_InitStructure.DMA_BufferSize = 90;
        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;
        //循環模式開啟,Buffer寫滿后,自動回到初始地址開始傳輸
        DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
        DMA_InitStructure.DMA_Priority = DMA_Priority_High;
        DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
        DMA_Init(DMA1_Channel1, &DMA_InitStructure);
        //配置完成后,啟動DMA通道
        DMA_Cmd(DMA1_Channel1, ENABLE);
        DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE); //使能DMA傳輸完成中斷

        }
        NVIC部分:
        NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQChannel;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&NVIC_InitStructure); // Enable the DMA Interrupt
        stm32f10x_it.c文件:
        void DMA1_Channel1_IRQHandler(void)
        {
        if(DMA_GetITStatus(DMA1_IT_TC1) != RESET)
        {
        filter();
        DMA_ClearITPendingBit(DMA1_IT_TC1);
        }
        }
        濾波部分:(均值濾波)
        #define N 30
        void filter(void)
        {
        intsum = 0;
        u8 count,i;
        for(i=0;i<2;i++)
        {
        for ( count=0;count {
        sum += AD_Value[count][i];
        }
        After_filter[i]=sum/N;
        sum=0;
        }

        }
        采樣數據與實際電壓/溫度轉換:
        u16 GetTemp(u16 advalue)
        {
        u32 Vtemp_sensor;
        s32 Current_Temp;

        // ADC轉換結束以后,讀取ADC_DR寄存器中的結果,轉換溫度值計算公式如下:
        // V25 - VSENSE
        //T(℃) = ------------+ 25
        // Avg_Slope
        // V25:溫度傳感器在25℃時 的輸出電壓,典型值1.43 V。
        //VSENSE:溫度傳感器的當前輸出電壓,與ADC_DR 寄存器中的結果ADC_ConvertedValue之間的轉換關系為:
        // ADC_ConvertedValue * Vdd
        //VSENSE = --------------------------
        // Vdd_convert_value(0xFFF)
        //Avg_Slope:溫度傳感器輸出電壓和溫度的關聯參數,典型值4.3 mV/℃。

        Vtemp_sensor = advalue * 330 / 4096;
        Current_Temp = (s32)(143 - Vtemp_sensor)*10000/43 + 2500;
        return (s16)Current_Temp;
        }

        u16 GetVolt(u16 advalue)
        {

        return (u16)(advalue * 330 / 4096);
        }
        濾波部分思路為:ADC正常連續采樣三個通道,由DMA進行搬運,一次搬運90個數據,即為1-2-3-1-2-3循環,每個通道各30次,存在 AD_Value[30][3]中,30為每通道30個數據,3為三個通道,根據二維數組存儲方式此過程自動完成。而每當一次DMA過程結束后,觸發 DMA完成中斷,進入濾波函數將30個數據均值成一個, 存入After_filter[3]。整個過程濾波計算需要CPU參與,而在程序中采樣結果值隨時均為最新,盡力解決程序復雜性和CPU負載。 x=GetVolt(After_filter[0]);即可得到即時電壓值。


        評論


        技術專區

        關閉
        主站蜘蛛池模板: 共和县| 台中县| 新宾| 沧州市| 洛阳市| 武清区| 新巴尔虎右旗| 于都县| 四会市| 昭觉县| 古蔺县| 揭阳市| 弋阳县| 余江县| 鸡西市| 仙居县| 柏乡县| 项城市| 葫芦岛市| 桐庐县| 土默特左旗| 岳阳市| 富裕县| 扎赉特旗| 寿阳县| 徐闻县| 民和| 兴国县| 盐源县| 赤城县| 惠东县| 刚察县| 固阳县| 永新县| 汉寿县| 石阡县| 垫江县| 昌都县| 萨嘎县| 尼玛县| 安仁县|