【二代示波器教程】第12章 示波器設計—DAC信號發生器的實現

 

第12章      示波器設計—DAC信號發生器的實現

本章節爲你們講解二代示波器中信號發生器的實現。這個功能仍是比較實用的,方便爲二代示波器提供測試信號。實現了正弦波,方波和三角波的頻率,幅度以及佔空比設置。php

12.1   DAC的輸出阻抗和使能緩衝問題數組

12.2   DAC驅動實現函數

12.3   信號發生器配置界面設計測試

12.4   信號發生器波形顯示效果優化

12.5   總結ui

 

 

12.1  DAC的輸出阻抗和使能緩衝問題

咱們這裏把F429的輸出阻抗和使能緩衝問題放在最前面說。spa

使能了多緩衝後發現有失真問題,即滿幅輸出的時候有削頂和削底,而禁止了輸出緩衝會致使輸出阻抗僅有10KΩ左右,外接負載很容易形成分壓(能夠根據實際狀況,外接運放輸出)。設計

F429的手冊中對於DAC的幾個關鍵特性說明以下:code

一、開啓緩衝的時候,外接的負載阻抗最小得是5KΩ。blog

二、禁止緩衝的時候,DAC輸出阻抗最大可達15KΩ,好比要實現1%精度的輸出,外接負載阻抗至少得是1.5MΩ。

三、開啓緩衝的時候,最小輸出電壓0.2V,最大Vdda - 0.2V,這個應該是形成削頂問題的根本緣由。

四、禁止緩衝的時候,最小輸出電壓的典型值是0.5mV,最大輸出是Vref - 1LSB。基本正好滿幅輸出,因此效果比較好。

 

F429數據手冊中幾個關鍵參數的截圖:

 

緩衝和外接負載時的框圖:

 

禁止緩衝時,滿幅輸出效果比較漂亮:

 

使能緩衝時,滿幅輸出效果,出現削頂問題:

 

有了上面的感性認識後,下面爲你們講解DAC的驅動實現和相應的GUI界面實現。

 

12.2  DAC驅動實現

F429帶有兩個DAC,分別是DAC1和DAC2,咱們這裏使用了DAC1,驅動中還須要用到TIM6和DMA,方便咱們配置不一樣的的頻率,佔空比和幅值。

 

12.2.1  第1步:引腳配置和DAC配置

配置代碼以下,使用的PA4引腳作輸出:

/* ********************************************************************************************************* * 函 數 名: bsp_InitDAC1 * 功能說明: 配置PA4/DAC1 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */

void bsp_InitDAC1(void) { /* 配置GPIO */ { GPIO_InitTypeDef GPIO_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); /* 配置DAC引腳爲模擬模式 PA4 / DAC_OUT1 */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ; GPIO_Init(GPIOA, &GPIO_InitStructure); } /* DAC通道1配置 */ { DAC_InitTypeDef DAC_InitStructure; /* 使能DAC時鐘 */ RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE); DAC_InitStructure.DAC_Trigger = DAC_Trigger_None;  /* 選擇軟件觸發, 軟件修改DAC數據寄存器 */ DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None; DAC_InitStructure.DAC_LFSRUnmask_TriangleAmplitude = DAC_LFSRUnmask_Bit0; //DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable;
 DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Disable; DAC_Init(DAC_Channel_1, &DAC_InitStructure); DAC_Cmd(DAC_Channel_1, ENABLE); } }

特別注意。程序中關閉了DAC輸出緩衝,即DAC參數成員DAC_InitStructure.DAC_OutputBuffer。關於DAC的緩衝問題,看前面12.1小節說明便可。

 

12.2.2 第2步:DAC的定時器觸發和DMA配置

DAC的定時器觸發和DMA配置以下:

/* ********************************************************************************************************* * 函 數 名: dac1_InitForDMA * 功能說明: 配置PA4 爲DAC_OUT1, 啓用DMA2 * 形 參: _BufAddr : DMA數據緩衝區地址 * _Count : 緩衝區樣本個數 * _DacFreq : DAC樣本更新頻率 * 返 回 值: 無 ********************************************************************************************************* */

void dac1_InitForDMA(uint32_t _BufAddr, uint32_t _Count, uint32_t _DacFreq) { uint16_t usPeriod; uint16_t usPrescaler; uint32_t uiTIMxCLK; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; DMA_Cmd(DMA1_Stream5, DISABLE); DAC_DMACmd(DAC_Channel_1, DISABLE); TIM_Cmd(TIM6, DISABLE); /* TIM6配置 */ { RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE); uiTIMxCLK = SystemCoreClock / 2; if (_DacFreq < 100) { usPrescaler = 10000 - 1;                         /* 分頻比 = 10000 */ usPeriod =  (uiTIMxCLK / 10000) / _DacFreq  - 1; /* 自動重裝的值 */ } else if (_DacFreq < 3000) { usPrescaler = 100 - 1;                         /* 分頻比 = 100 */ usPeriod =  (uiTIMxCLK / 100) / _DacFreq  - 1; /* 自動重裝的值 */ } else /* 大於4K的頻率,無需分頻 */ { usPrescaler = 0;                     /* 分頻比 = 1 */ usPeriod = uiTIMxCLK / _DacFreq - 1; /* 自動重裝的值 */ } TIM_TimeBaseStructure.TIM_Period = usPeriod; TIM_TimeBaseStructure.TIM_Prescaler = usPrescaler; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseStructure.TIM_RepetitionCounter = 0x0000;        /* TIM1 和 TIM8 必須設置 */ TIM_TimeBaseInit(TIM6, &TIM_TimeBaseStructure); /* 選擇TIM6作DAC的觸發時鐘 */ TIM_SelectOutputTrigger(TIM6, TIM_TRGOSource_Update); } /* DAC通道1配置 */ { DAC_InitTypeDef DAC_InitStructure; /* 使能DAC時鐘 */ RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE); DAC_InitStructure.DAC_Trigger = DAC_Trigger_T6_TRGO; DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None; DAC_InitStructure.DAC_LFSRUnmask_TriangleAmplitude = DAC_LFSRUnmask_Bit0; //DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable;
 DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Disable; DAC_Init(DAC_Channel_1, &DAC_InitStructure); DAC_Cmd(DAC_Channel_1, ENABLE); } /* DMA1_Stream5配置 */ { DMA_InitTypeDef DMA_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE); /* 配置DMA1 Stream 5 channel 7用於DAC1 */ DMA_InitStructure.DMA_Channel = DMA_Channel_7; DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&DAC->DHR12R1; DMA_InitStructure.DMA_Memory0BaseAddr = _BufAddr; DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral; DMA_InitStructure.DMA_BufferSize = _Count; 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_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull; DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; DMA_Init(DMA1_Stream5, &DMA_InitStructure); DMA_Cmd(DMA1_Stream5, ENABLE); /* 使能DAC通道1的DMA */ DAC_DMACmd(DAC_Channel_1, ENABLE); } /* 使能定時器 */ TIM_Cmd(TIM6, ENABLE); }

 

經過這個函數能夠方便的設置DAC的輸出波形頻率。計算方法是:

配置的定時器觸發頻率 / DMA的緩衝個數 = 輸出波形頻率

其中DMA緩衝數據的個數就是輸出波形一個週期的採樣點數。程序中統一將其配置爲128個點表明一個週期的波形,你們實際應用中配置的點數不要太少,不然波形不夠漂亮。好比咱們要出10KHz的波形,這個函數的配置就是:dac1_InitForDMA((uint32_t)&g_Wave1, 128, 10000 * 128); 數組g_Wave1裏面是128個波形採樣點。

關於這個驅動代碼,要注意TIM6的配置。F429的定時器從TIM1到TIM14的主頻以下:

/* ******************************************************************************** system_stm32f4xx.c 文件中 void SetSysClock(void) 函數對時鐘的配置以下: HCLK = SYSCLK / 1 (AHB1Periph) PCLK2 = HCLK / 2 (APB2Periph) PCLK1 = HCLK / 4 (APB1Periph) 由於APB1 prescaler != 1, 因此 APB1上的TIMxCLK = PCLK1 x 2 = SystemCoreClock / 2; 由於APB2 prescaler != 1, 因此 APB2上的TIMxCLK = PCLK2 x 2 = SystemCoreClock; APB1 定時器有 TIM2, TIM3 ,TIM4, TIM5, TIM6, TIM7, TIM12, TIM13, TIM14 APB2 定時器有 TIM1, TIM8 ,TIM9, TIM10, TIM11 TIM 更新週期是 = TIMCLK / (TIM_Period + 1)/(TIM_Prescaler + 1) ******************************************************************************** */

由此可知,TIM6的主頻是SystemCoreClock / 2。當主頻是168MHz時,TIM6的時鐘就是84MHz,TIM6更新週期 = TIM6CLK / (TIM_Period + 1)/(TIM_Prescaler + 1),其中

TIM_Period就是定時器結構體成員TIM_TimeBaseStructure.TIM_Period。

TIM_Prescaler就是定時器結構體成員TIM_TimeBaseStructure.TIM_Prescaler。

另外還有很是重要的一點,TIM6是16位定時器,這兩個參範圍是0-65535,切不要超過65535。正是由於這個緣由,程序中對不一樣的輸出頻率作了範圍區分。

 

12.2.3 第3步:正弦波輸出配置

正弦波的輸出配置以下:

/* ********************************************************************************************************* * 函 數 名: dac1_SetSinWave * 功能說明: DAC1輸出正弦波 * 形 參: _vpp : 幅度 0-4095; * _freq : 頻率 * 返 回 值: 無 ********************************************************************************************************* */

void dac1_SetSinWave(uint16_t _vpp, uint32_t _freq) { uint32_t i; uint32_t dac; TIM_Cmd(TIM6, DISABLE); /* 調整正弦波幅度 */       

     for (i = 0; i < 128; i++) { dac = (g_SineWave128[i] * _vpp) / 4095; if (dac > 4095) { dac = 4095; } g_Wave1[i] = dac; } dac1_InitForDMA((uint32_t)&g_Wave1, 128, _freq * 128); }

正弦波輸出128個採樣點表明一個週期,同時程序裏面增長了一個幅值設置功能,範圍0到4095。實際DAC輸出的波形頻率由前面第2步函數dac1_InitForDMA實現。好比咱們要實現頻率10KHz,幅值4095正弦波,那麼配置就是:dac1_SetSinWave(4095, 10000)。

 

12.2.4 第4步:方波輸出配置

方波的輸出配置以下:

/* ********************************************************************************************************* * 函 數 名: dac1_SetRectWave * 功能說明: DAC1輸出方波 * 形 參: _low : 低電平時DAC, * _high : 高電平時DAC * _freq : 頻率 Hz * _duty : 佔空比 2% - 98%, 調節步數 1% * 返 回 值: 無 ********************************************************************************************************* */

void dac1_SetRectWave(uint16_t _low, uint16_t _high, uint32_t _freq, uint16_t _duty) { uint16_t i; TIM_Cmd(TIM6, DISABLE); for (i = 0; i < (_duty * 128) / 100; i++) { g_Wave1[i] = _high; } for (; i < 128; i++) { g_Wave1[i] = _low; } dac1_InitForDMA((uint32_t)&g_Wave1, 128, _freq * 128); }

方波也是輸出128個採樣點表明一個週期,同時支持幅值和佔空比的配置,其中佔空比能夠配置2%到98%,直接填數值2到98就能夠了。實際DAC輸出的波形頻率由前面第2步函數dac1_InitForDMA實現。好比咱們要實現頻率10KHz,幅值4095,佔空比50%的方波,那麼配置就是:dac1_SetRectWave (0, 4095, 10000, 50)。

 

12.2.5 第5步:三角波輸出配置

三角波的輸出配置以下:

/* ********************************************************************************************************* * 函 數 名: dac1_SetTriWave * 功能說明: DAC1輸出三角波 * 形 參: _low : 低電平時DAC, * _high : 高電平時DAC * _freq : 頻率 Hz * _duty : 佔空比 * 返 回 值: 無 ********************************************************************************************************* */

void dac1_SetTriWave(uint16_t _low, uint16_t _high, uint32_t _freq, uint16_t _duty) { uint32_t i; uint16_t dac; uint16_t m; TIM_Cmd(TIM6, DISABLE); /* 構造三角波數組,128個樣本,從 _low 到 _high */ m = (_duty * 128) / 100; if (m == 0) { m = 1; } if (m > 127) { m = 127; } for (i = 0; i < m; i++) { dac = _low + ((_high - _low) * i) / m; g_Wave1[i] = dac; } for (; i < 128; i++) { dac = _high - ((_high - _low) * (i - m)) / (128 - m); g_Wave1[i] = dac; } dac1_InitForDMA((uint32_t)&g_Wave1, 128, _freq * 128); }

三角波也是輸出128個採樣點表明一個週期,同時支持幅值和佔空比的配置,其中佔空比能夠配置0%到100%,不過程序中對0%和100%作了一個特殊處理。實際DAC輸出的波形頻率由前面第2步函數dac1_InitForDMA實現。好比咱們要實現頻率10KHz,幅值4095,佔空比50%的三角波,那麼配置就是:dac1_SetTriWave (0, 4095, 10000, 50)。

 

12.3 信號發生器配置界面設計

信號發生器的界面設計以下:

 

這個操做界面簡單易用,支持正弦波,方波和三角波的切換,支持佔空比設置,支持幅值設置,同時也支持頻率設置,限制頻率範圍1Hz到50KHz。超過50KHz的話,波形效果會變的愈來愈差。

關於這個對話框的代碼實現就不在教程裏面作講解了,咱們這裏主要講解下對話框上的小鍵盤實現。這裏小鍵盤是一個獨立的窗口,父窗口是信號發生器主窗口,經過函數WM_SendMessageNoPara發自定義消息給父窗口,在父窗口裏面更新Graph控件的波形和波形信息,同時DAC的波形輸出也獲得更新。瞭解了這知識點後,再看代碼就比較容易了。

 

知識點拓展:

新版emWin教程第51章:實用的官方小鍵盤實例講解:

http://forum.armfly.com/forum.php?mod=viewthread&tid=19834

另外還有emWin提升篇例子的第一期ATM機裏面也有用到小鍵盤。

http://forum.armfly.com/forum.php?mod=viewthread&tid=23687

 

12.4 信號發生器波形顯示效果

下面爲你們展現信號發生器輸出波形效果:

方波:

 

正弦波:

 

三角波:

 

12.5 總結

本章節爲你們講解的信號發生器仍是比較實用的,建議實際動手操做下,有興趣的話,還能夠進一步優化升級。

相關文章
相關標籤/搜索