引言
寒假練有一款白色的、非常美觀的、雙通道輸入的基于STM32G031的板卡,它可以實現哪些功能呢?示波器、DDS信號發生器、頻譜分析儀、失真度測量儀等等。 今天我們來看一位來自南京大學的【電子卷卷怪】同學所做的雙通道簡易示波器項目,這位同學還幫助多個參加寒假練的同學親自解決了他們的問題。項目成果概述
本項目使用硬禾課堂STM32G031開發板卡以及STM32CubeIDE開發工具,實現了一個簡易的示波器。示波器的各項參數或功能概述如下:
1. 外觀(1)有主界面、副界面兩個界面,并可以相互切換;
(2)主界面包含波形模式和FFT模式,分別顯示被測信號的波形和頻譜;
(3)波形模式包含:垂直尺度調整、水平時基調整、屏幕中心電平調整、模擬觸發電平調整、負時間調整、平均值顯示、頻率測量顯示、峰峰值顯示;FFT模式包含:垂直尺度顯示、采樣率顯示、屏幕中心電平顯示、模擬觸發電平顯示、頻譜最大分量(歸一化值)顯示、頻標調整、頻標對應分量顯示。
(4)副界面包含5個其他功能:通道選擇、波形/FFT模式切換、開啟AUTO、模擬觸發電平極性、開啟單次(Single)模式。
2. 操作
(1)位于主界面的任意模式時,單擊左右鍵可以使光標在該模式下可調整的功能間移動,轉動旋鈕調整被光標選中的參數;
(2)位于副界面時,單擊左右鍵可以使光標在5個其他功能間移動,轉動旋鈕可以調整被選中的功能;
(3)按住旋鈕的情況下:單擊左鍵進入主界面、單擊右鍵進入副界面。
(4)開啟AUTO后,自適應調整只會在切回主界面后被執行一次;對新波形的自適應調整需要切到副界面——開啟AUTO——切回主界面。
(5)開啟Single后,無觸發時,正常顯示波形;觸發一次后,波形與頻譜均固定,并不會更新,但可以調整負時間和頻標;在觸發后,調整垂直尺度、水平時基、屏幕中心電平、模擬觸發電平、采樣率中的任意一者,都會導致下一次觸發的捕捉。



項目需求分析
總的來說,本項目可以分為兩個大的模塊:GUI模塊、采樣處理模塊。其中,相對于程序的主循環而言,采樣處理模塊是高速的、“同步”的,GUI模塊是慢速的、“異步”的。兩個模塊間既需要并行不悖,又需要互相交換數據。 對于采樣處理模塊,主要考慮以下4個需求問題:1. ADC可控采樣率與切換通道的實現;2. 觸發電平的實現,以及負時間顯示的實現;3. 如何對頻率進行較高精度的測定;4. 如何計算信號頻譜; 對于GUI模塊,主要考慮以下3個需求問題:1. 如何以盡可能低的誤判率獲取按鍵與旋鈕的信息;2. 中斷服務函數所應干涉的范圍;3. 如何以盡可能簡潔的方式實現按鍵對GUI的改變 對于兩個模塊而言,最核心的問題是:如何在兩者之間進行高效的數據傳輸的同時,避免數據的誤判或漏判。核心技術路線
針對“二”中提出的需求,以下同樣分兩個模塊,對項目的技術路線進行完備的論述。鑒于HAL庫過于龐大,且本人對項目的理解更偏重于硬件底層,除了HAL_Init,SystemClock_Config,以及與NVIC有關的3個最底層的函數(Priority, Enable, ClearPending)外,其他所有的外設配置代碼,均為本人閱讀器件手冊后編寫的寄存器代碼。1. ADC可控采樣率與通道切換
在ADC連續模式下,雖然可以通過調整采樣時間來調整采樣率,但這樣做顯然并不好。一方面,這樣得到的轉換周期(Tsamp + 12.5ADC_Cycle)的倒數,即頻率,往往是不規律的非整數,這樣做不利于功能調整的層次化與統一化;另一方面,即使采用16MHz主頻,在12位分辨率下,ADC最小轉化頻率也有16MHz / (160.5 + 12.5) ≈92.5kHz,有效測量范圍太小。 定時器觸發的方式是最好的選擇。一方面,只需控制轉換時間不大于采樣率的倒數,就能獲得完全可控的轉換率;另一方面,這樣有利于定時器觸發DMA傳輸的引入。由于在32MHz主頻下,即使是最簡單的中斷服務函數,頻率也只能到150kHz左右,因此,DMA傳輸既可以提供較高的采樣率,又可以使“采樣——處理”分離的結構更加清晰。配置的方法: 對ADC端:
模擬看門狗的配置將在后面說明。這里最關鍵的,一是必須配置為非連續模式、外部上升沿觸發,選擇TIM2的TRGO為觸發源,并且不能選擇ADC為DMA觸發源,否則ADC的overwritten特性會迫使軟件屢屢清除標志位,以保證DMA Request的持續產生;二是在外部觸發時,必須先start。void ADC_init(void)
{
uint32_t temp;
RCC->IOPENR |= 0X1UL;//打開PortA時鐘
temp=RCC->IOPENR;//時鐘使能需等2個周期
UNUSED(temp);//避免Warning
//由于GPIOA->MODER對應位默認為0X3,即模擬輸入
//因此不需要再額外配置PortA
RCC->APBENR2 |= (0X1UL<<20UL);//打開ADC1時鐘
temp=RCC->APBENR2;
UNUSED(temp);
ADC1->CR |= (0X1UL<<28UL);//使能內部參考電壓
//自己寫的延時,用TIM17的OPM模式
TIM17_Delay(1000-1,32-1);//等待參考電壓有效
ADC1->CR |= (0X1UL<<31UL);
do
{
temp=ADC1->CR;//開始校正指令
}while(temp & (0X1UL<<31));//等待校正結束
ADC1->CFGR1 |= (0X1UL<<16 | 0X1UL<<12 | 0X2UL<<10 | 0X2UL<<6 | 0X0UL);
//(discontinuous,overwritten,ext rising edge,TRG2,DMA disabled);
ADC1->TR1 &= ~(0X0FFF0000);
ADC1->TR1 |= (0X0FFF0800);
//模擬看門狗的高低閾值
ADC1->CFGR1 |= (0X1<<26 | 0X1<<22 | 0X1UL<<23);
//AWD1 configuration
ADC1->CFGR2 |= (0X3UL<<30); //PCLK as ADC_CLK
ADC1->CHSELR |= (0X1UL << 1 | 0X0UL<<7);//選擇通道一
do
{
temp=ADC1->ISR;
}while(!(temp & (0X1UL<<13)));//等待通道配置有效
ADC1->CR |= 0X1UL;//enabling ADC1
do
{
temp = ADC1->ISR;
}while(!(temp & 0X1UL));//ADC Ready
ADC1->CR |= 0X1UL<<2;//ADC Start
return;
}


傳輸數據使用的是通道一。相比于F407等系列,G031引入了DMAMUX的概念,使得幾乎所有的外設和一些事件都可以在任意一個DMA通道上產生請求。由于DMAMUX的0~4對應DMA的1~5,查閱用戶指南后,得知設置DMAMUX的CCR的低7位為31(0X1F)表示TIM2的Update。 對TIM端:void ADC_DMA_init(void)
{
uint32_t temp;
RCC->AHBENR |= 0X1UL;
temp=RCC->AHBENR;//時鐘使能需2個周期
UNUSED(temp);//避免Warning
DMA1_Channel1->CPAR = (uint32_t)(ADC1_BASE+0X40);
DMA1_Channel1->CMAR = (uint32_t)(&dat_buf);
DMA1_Channel1->CNDTR = ADC_MAX * 2;
DMA1_Channel1->CCR |= (0X2UL<<12 | 0X1UL<<10 | 0X2UL<<8 | 0X1UL<<7 |
0X0UL<<3 | 0X1UL<<1 | 0X1 << 5);
//v-high priority, m-size=16,p-size=16,m-increase,
//error and complete interrupt, circular mode;
DMAMUX1_Channel0->CCR &= ~(0X7FUL);
DMAMUX1_Channel0->CCR |= (0X1FUL);//tim2 as request source
__NVIC_SetPriority(DMA1_Channel1_IRQn,0);
__NVIC_EnableIRQ(DMA1_Channel1_IRQn);
DMA1_Channel1->CCR |= 0X1UL;//enable DMA channel
return;
}
通過CR2的主模式位MMS[6:4]配置TIM2的Update為TRGO,否則無法正確觸發ADC;使能更新事件的DMA請求。 在上述框架下,DMA只要開啟單次模式,等待全傳輸中斷函數置標志位就可以了。需要注意的是,在清除中斷標志的時候,需要同時清除NVIC端和外設端的標志位,否則會陷入無限的中斷循環。 若開啟了上述外設配置,則上述架構在DMA One shot模式下就能完成采樣率可調的循環數據傳輸。而我們最終開啟的是DMA Circular模式,這將在后面說明。void TIM2_Init(unsigned int priority)
{
uint32_t temp;
RCC->APBENR1 |= 0X1UL;//使能TIM2時鐘
temp=RCC->APBENR1;
UNUSED(temp);
//TIM2->DIER |= 0X1UL;//允許更新中斷
TIM2->CR1 |= 0X1UL<<2UL;//手動更新不觸發中斷
TIM2->CR2 |= 0X2<<4;//update as TRGO
TIM2->SMCR |= 0X1UL<<7;
TIM2->DIER |= 0X1UL<<8;
TIM2->ARR = 16-1;
TIM2->PSC = 0;
temp=TIM2->ARR;
TIM2->EGR |= 0X1UL;//手動更新寄存器值
temp=TIM2->PSC;
UNUSED(temp);
}
2. 觸發電平的實現,以及負時間的實現
觸發電平,即以被測信號越過某個閾值電壓為起算點,采集后面的若干個數據。該方法可以使波形穩定地顯示在屏幕上。 負時間,即可以顯示觸發電平前一定時間內的波形。當觸發電平用于異常信號的單次捕捉(Single模式)時,負時間可以顯示異常信號前的波形。 有同學在無條件采樣后計算一組數據的均值(中值),并顯示從中值樣點開始的數據,從而通過軟件實現觸發電平。這種方案在實現AUTO時不失為一個好的啟發,但在此面臨兩個問題:第一,單純的中值判斷無法控制觸發的極性,即無法選擇上升沿還是下降沿觸發。若增加前后值判斷,則將增加軟件運算量;第二,這種算法下不可能出現“無觸發”的、波形亂晃的現象,與真實的數字示波器存在差異。從本質上講,這種方法沒有充分利用硬件底層。 G031的ADC自帶一個模擬看門狗,即Analog Window Watchdog的特性。即當采樣值超出規定范圍(窗口)時,輸出AWD_OUT將持續拉高,直至電壓落回窗口內,延遲為一個轉換周期。


配置TIM1從模式為Trigger Mode(上升沿觸發啟動)、選擇觸發源為外部觸發ETR,再連接AWD1至ETR,就可以在DMA One Shot模式下,實現基于硬件的、真正的觸發電平功能。通過ADC的TR1設置閾值,假設TIM1為上升沿啟動,則當窗口為(x , 0x0FFF)時,為下降沿觸發;當窗口為(0x0000 , x)時,為上升沿觸發。

然而在這樣的結構下,是無法實現負時間功能的。由于AWD_OUT的上升沿是不可預知的隨機事件,因此應該對程序結構進行微調:改用DMA Circular模式,AWD_OUT作為采樣停止——而不是開始——的信號。 假如我們希望采集觸發后的256個數據(為方便FFT運算),又希望顯示負時間的128個數據,則應該配置TIM2為ADC觸發源,令TIM1的溢出周期為TIM2的256倍。在TIM1的中斷服務函數中關掉(Disable)TIM2,就能實現上述功能。與此同時,DMA1_Channel1的CNDTR中將保存一個循環中剩余待傳輸的數據個數,據此可以定位連同負時間在內的整段有效數據在DMA目標數組內的起止位置。

若目標數組大小為512,當TIM2停止時,CNDTR的值為CH1_CNDTR,則觸發點下標應為(512 - CH1_CNDTR - 256) % 512= (512 - CH1_CNDTR + 256) % 512= (768 - CH1_CNDTR) % 512 然而這樣的設計存在一個問題:模擬觸發事件具有隨機性,如果它在重新開啟TIM2后的幾個周期內就發生,那么當新一段數據被存儲完成后,負時間位置的數據還是上次采樣的數據,這就會導致負時間顯示錯誤。

為了避免上述情況,在新一輪開啟后,必須先等待一次全傳輸中斷再開啟TIM1。事實上,只要一次全傳輸中斷后,無論TIM1隔多久開啟,數組中的時間軸都是連續的。用dat_buf_ready的bit0表示全傳輸中斷、bit7表示TIM1中斷。
3. 信號頻率的測定if((!(cursor_buf & (0X1 << 7))) || ((cursor_buf & (0X1 << 7)) && (single_flag == 0)))
{
TIM1->ARR = TIM2->ARR;
TIM1->PSC = (TIM2->PSC + 1) * 256 - 1;
TIM1->EGR |= 0X1;
{
DMA1_Channel1->CNDTR = ADC_MAX * 2;
DMA1_Channel1->CCR |= 0X1UL;
TIM1->SR &= ~(0X1UL);
TIM2->CR1 |= 0X1UL;
}
while(!(dat_buf_ready & 0X01))
{
}
TIM1->DIER |= (0X1UL);
TIM1->SMCR |= (0X0UL<<16 | 0X6UL);
dat_buf_ready &= ~(0X1);
}
void TIM1_BRK_UP_TRG_COM_IRQHandler(void)
{
CH1_CNDTR = DMA1_Channel1->CNDTR;//賦值了不一定用,但這樣最準確
if(TIM1->SR & 0X1UL)
{
{
TIM2->CR1 &= ~(0X1UL);
TIM1->CR1 &= ~(0X1UL);
//Stop tim2 and consequently stop DMA
TIM2->CNT = 0;//resetting TIM2
dat_buf_ready |= 0X1 << 7;//setting complement flag
}
TIM1->SR &= ~(0X1UL);
__NVIC_ClearPendingIRQ(TIM1_BRK_UP_TRG_COM_IRQn);
}
}
void DMA1_Channel1_IRQHandler(void)//中斷服務函數
{
DMA1->IFCR |=0X1UL;
dat_buf_ready |= 0X1;
__NVIC_ClearPendingIRQ(DMA1_Channel1_IRQn);
}
數字測定頻率的方法,一般是先整形再測量。即通過施密特觸發器(比如TLV3501)先把信號整形成脈沖,再對脈沖進行測定。對脈沖的測定也有兩種思路:一是直接同步采樣后計算脈沖個數,適用于較高頻率;二是計算脈沖高低電平的周期個數,適用于較低頻率。兩種方法均受限于系統最高主頻。這也是本項目至今為止兩個尚為得出最優解的難點之一。 本項目從脈沖整形到計數均采用硬件特性為主、軟件程序為輔的思路。根據前面的討論可知,ADC的AWD在一定頻率以下等效于一個極其理想的脈沖整形器。相較于模擬施密特觸發器,其最大的特點在于脈沖整形的響應特性與信號峰峰值的絕對值無關,而僅受到信道噪聲和量化噪聲的干擾。因此,測量頻率最基本的方法,也是本項目采用的方法,就是對AWD的輸出信號AWD_OUT在一定時間內進行計數。此方法實現起來最為簡單,但面臨兩個很大的問題:第一,相比于FPGA廣泛采用的雙閘門法,此方法會把閘門時間的前后沿漏掉,引入一定的誤差,但這并非主要矛盾。

第二,實測表明,在測定較低頻率的正弦波或三角波時,頻率將出現較大誤差,只有對方波的測定最為準確。這種誤差只有在200Hz以上才可以忽略不計。究其本質,是因為信號的噪聲抖動所致。AWD_OUT的靈敏度帶來了一個致命的缺點:沒有任何的滯回特性,這就導致在過觸發點附近的任何噪聲都可能被極大地放大,只有邊沿極抖的方波才能“幸免”。反觀模擬脈沖整形電路,由于人為設計滯回電路以及電路本身輸入輸出電容的存在,對輸入信號總有一定的消抖能力。當然,用于傳輸測試信號的信道本身也存在問題。一方面,用于輸出測試信號的手持信號源輸出的信號可能質量欠佳;另一方面,相比于“BNC——同軸線——SMA”信道,“鱷魚夾——杜邦線——排針”信道的明顯劣勢也是不言而喻的。 一定程度上減弱抖動影響的措施,唯有通過定時器自帶的數字濾波器,對AWD輸出信號進行數字濾波。但實驗證明,若用2Msps的速率采集峰峰值3.0V的正弦波,即使采用最大濾波長度,依然會將10Hz誤測成100Hz左右,而在濾波前,誤測值高達2kHz左右。由于后續AUTO功能的需要,測量頻率和數據采集是分開的。也即頻率測量與時基無關。
4. 如何計算信號頻譜if((!(cursor_buf & (0X1 << 7))) || ((cursor_buf & (0X1 << 7)) && (single_flag == 0)))
//測量頻率
{
//配置參數
//保存TIM2原參數,并設為2MHz采樣率
arr = TIM2->ARR;
TIM2->ARR = 16 - 1;
smp = (ADC1->SMPR) & 0X7;
ADC1->SMPR &= ~(0X7);
ADC1->SMPR |= 0X1;
psc = TIM2->PSC;
TIM2->PSC = 0;
//將TIM1的從模式更改為External Clock 1
//并打開數字濾波
TIM1->SMCR &= ~((0X1 << 16) | 0X7);
TIM1->SMCR |= 0X7;
TIM1->SMCR |= 0XF << 8;
TIM1->PSC = 0;
TIM1->ARR = 65535;
TIM1->CNT = 0;
TIM1->EGR |= 0X1;
//配置SysTick
SysTick->VAL = 0;
SysTick->LOAD = 16000000 -1;
//開啟測量
TIM2->CR1 |= 0X1;
TIM1->CR1 |= 0X1;
SysTick->CTRL |= 0X1;
while(!(SysTick_UE_FLAG & 0X1))
{
}
//結束測量,恢復TIM1參數
TIM1->CR1 &= ~(0X1);
TIM2->CR1 &= ~(0X1);
SysTick_UE_FLAG &= ~(0X1);
TIM1->SMCR &= ~((0X1 << 16) | 0X7);
TIM1->SMCR &= ~(0XF << 8);
}
本項目的FFT算法沒有調用任何除C++標準庫以外的庫,這一方面是考慮到RAM空間的緊張,另一方面則是起到鍛煉的作用。 本項目的FFT算法就是最簡單的256點基-2 FFT算法,將復數乘法拆分為實虛部進行同址運算,并將FFT因子存儲為const型常量。

基-2 FFT的蝶形算子概念在DSP教材中均有解釋,本項目完全依照其定義與原理編寫。以上完成了采樣處理模塊的論述,下面將進行GUI模塊的論述。 鑒于本項目具有一定的復雜性,我們將GUI模塊又分為兩部分:一是用戶交互部分,即按鍵和旋鈕及與之相關的中斷服務函數,二是顯示部分,即OLED屏驅動以及主循環。為了避免使程序過于復雜,用戶交互部分并不能直接、即時地改變顯示部分,用戶的操作將被保存在由幾個變量模擬成的寄存器的各個位里,并被主循環的固定部分重復讀取、刷新。各寄存器及其各位的定義如下。

各個位的含義及位置,均以宏定義的形式在頭文件中聲明。這樣,就可以在刷新函數中通過位運算的方式獲取各個參數。
這樣做的顯著好處就是極大地節省了RAM空間。因為最小的變量也是8位,卻沒有任何參數達到256檔之多,尤其是那些只有一位的標志位,完全沒有必要用8位變量表示。當然,這又是一對用時間換空間的矛盾。因為位運算的操作量是直接賦值運算的3倍,這是在內存空間緊張的情況下最好的選擇。//macros for register ui_buf
5. 如何以盡可能低的誤判率獲取按鍵和旋鈕的信息
由硬件電路可知,旋鈕的AB相、旋鈕按鍵、左右按鍵,分別連接在PB4,PA15,PB3,PA4,PA5上。其中,三個按鍵只要用外部中斷+延時消抖就能很好地判斷,而旋鈕則具有一定的復雜性。

我們判斷旋鈕不應選擇上升沿,這是由旋鈕的硬件特性決定的。出于簡化考慮,本項目只對PA15的下降沿做了外部中斷,即:根據下降沿時PB4的電平高低來判斷左旋或右旋,但這帶來的問題也很明顯:如果旋鈕被誤轉了一半,那么即使松開復原了,也會被判定為一次轉動——這往往發生在用戶完成一次有效轉動之后,由于慣性而導致的誤觸。 事實上,正確的做法應該是:用TIM3的CC1來捕捉PB4(以此避開與PA4在EXTI Line4上的沖突),用EXTI Line15來捕捉PA15。只要兩個中斷服務函數共享一個全局變量,就可以解決誤觸的問題。 由下圖(在下一頁)可以看出,除了切換主副界面以外,按鍵和旋鈕并不會直接去動那6個全局變量寄存器。而主副界面的“切換”也只是動了一個位M_S_FLAG,真正的顯示更新在主循環中完成。除此之外,按鍵和旋鈕的加、減被記錄在變量add_buf和min_buf中,而因為按鍵和旋鈕都可以進行加減操作,因此用flag寄存器的0位和7位來表示究竟是按鍵按下,還是旋鈕轉動。為了避免抖動,在PA15外部中斷時,add_buf和min_buf只有一個能被置位,而置位它的同時將強行清零另一個,也算是一個簡單的軟件消抖。這其實也回答了需求中提出的第二個問題:中斷服務函數只改變加減標志位,而不改變全局寄存器,否則整個服務函數將因充斥各種邏輯判斷而變得十分冗長與龐大,以至于喧賓奪主。
6. 如何以盡可能簡潔的方式實現按鍵對GUI的改變void EXTI4_15_IRQHandler(void)
{
if(EXTI->FPR1 & (0X1 << 15))
{
flag |= 0X1 << 7;
if(!(GPIOB->IDR & (0X1 << 4)))
{
add_buf ++;
min_buf = 0;
}
else
{
min_buf ++;
add_buf = 0;
}
TIM17_Delay(5000-1,320-1);
EXTI->FPR1 |= 0X1 << 15;
}
if(EXTI->FPR1 & (0X1 << 4))//left key down,--, or switch to main ui
{
TIM17_Delay(5000-1,320-1);
if(!(GPIOA->IDR & (0X1 << 4)))
{
flag |= 0X1;
if(GPIOB->IDR & (0X1 << 3))//PB3 not down
{
if(!(M_S_FLAG & cursor_buf))//main ui
{
if(!(ui_buf & FFT_ON_BIT))
{
if((cursor_buf & M_UI_BITS) > 0)
cursor_buf -= 0X1 << M_UI_BITS_OFFSET;
}
else
{
fft_col |= 0X1 << 7;//變量標志位
}
}
else//sub ui
{
if((cursor_buf & S_UI_BITS) > 0)
cursor_buf -= 0X1 << S_UI_BITS_OFFSET;
}
}
else//PB3 down
{
cursor_buf &= ~(M_S_FLAG);
}
}
EXTI->FPR1 |= 0X1 << 4;
}
if(EXTI->FPR1 & (0X1 << 5))//right key down,++, or switch to sub ui
{
TIM17_Delay(5000-1,320-1);
if(!(GPIOA->IDR & (0X1 << 5)))
{
flag |= 0X1;
if(GPIOB->IDR & (0X1 << 3))//PB3 not down
{
if(!(M_S_FLAG & cursor_buf))//main ui
{
if(!(ui_buf & FFT_ON_BIT))
{
if((cursor_buf & M_UI_BITS) < (0X4 << M_UI_BITS_OFFSET))
cursor_buf += 0X1 << M_UI_BITS_OFFSET;
}
else
{
fft_col &= ~(0X1 << 7);
}
}
else//sub ui
{
if((cursor_buf & S_UI_BITS) < (0X4 << S_UI_BITS_OFFSET))
cursor_buf += 0X1 << S_UI_BITS_OFFSET;
}
}
else//PB3 down
{
cursor_buf |= M_S_FLAG;
}
}
EXTI->FPR1 |= 0X1 << 5;
}
__NVIC_ClearPendingIRQ(EXTI4_15_IRQn);
}
由上述討論可以看出,最簡潔的方式就是在每次進入主循環后的固定位置,根據6個全局寄存器的值,共同決定本次循環應該在屏幕上顯示什么,并清除所有的標志位。由于實現該功能的UI_Refresh函數太長,這里僅以一個switch-case分支作為示例。
目至今沒有完全得出優化解的另一個難點。雖然這樣的結構很簡潔,但我們后續就將看到:這種完全“同步”于主循環,而屏蔽任何“異步”帶來的后果,就是當水平時基很大時,整個程序也會變得非常緩慢,以至于幾乎進入了一種“假死”狀態。因為即使按下了按鍵,至少也要等一次主循環結束。而在以低的采樣率采集數十Hz信號時,連同等待觸發加256個采樣點在內的時間,是相當可觀的。這啟示我們,中斷服務函數應該真的具有“中斷”的作用,而不僅僅是完成一個硬件電路就可以實現的狀態機。至于采樣處理模塊的更新,則與GUI的更新如出一轍:同樣是根據6個全局寄存器的值來更新,這樣保證了顯示與實際相符。只不過這一次更新的是模擬開關檔位、TIM2溢出頻率,TIM14與TIM16的PWM波占空比等參數。case (0X1 << M_UI_BITS_OFFSET)://水平分格
{
flag |= 0X1 << 2;
&& ((ui_buf & TIME_BASE_BITS) < (0XF << TIME_BASE_BITS_OFFSET)))
{
add_buf = 0;
ui_buf += (0X1 << TIME_BASE_BITS_OFFSET);
}
else if(min_buf && ((ui_buf & TIME_BASE_BITS) > (0X1 << TIME_BASE_BITS_OFFSET)))
{
min_buf = 0;
ui_buf -= (0X1 << TIME_BASE_BITS_OFFSET);
}
break;
}
事實上,這是本項
其他功能簡述
在核心部分以外,以下將對AUTO,Single以及波形顯示函數作簡要的論述。1. AUTO功能
所謂的AUTO功能,是指示波器根據當前被采信號的直流偏置、峰峰值、頻率等特點,自動調節顯示時基、觸發電平、垂直尺度等參數,使得整個波形盡可能以最大的完整度和占滿率顯示在屏幕上。 在本程序中,頻率的測定與采樣時基無關,這對AUTO的實現無疑是有利的。而由于輸入端采用了反相放大(衰減)器加同相端直流偏置的方式,而不是在同一端接成加法器,因此直流偏置的概念本身變得模糊。

上圖為輸入端電路。其中Vi為真實輸入值,Vo為ADC實際采到的值。據此,我們可以得出如下映射關系:

根據這個關系,就能根據ADC采樣值反推出真實的電壓。在AUTO時,我們首先將觸發電平選在屏幕中心(即Xadc = 2048,Vo = 1.65V),然后求出真實輸入電壓的中值(而不是均值,因為,如果輸入的是90%占空的方波,那么中值作為觸發的效果顯然比均值要好),最后,通過解方程的方式,反推出TIM16或TIM14應該輸出的PWM波占空比,就能使波形以中值附近為中心顯示在屏幕上。 AUTO模式不能和FFT模式以及Single模式一起開啟。 每次在副界面打開AUTO后,AUTO指令只會被執行一次。在AUTO后,任何除查看負時間(波形模式)和頻標(FFT模式)以外的操作均會解除AUTO。每次要執行新一次AUTO,需要切換副界面——保證AUTO處于OFF——再將AUTO調至ON。
2. Single單次模式
在打開Single模式時,示波器會在一次觸發之后將波形凍結。此時可以切換主副界面,在頻譜和波形顯示之間切換、查看負時間(波形模式),以及調整頻標查看各分量大小(FFT模式)。除此之外的任何操作都會解除凍結,并自動等待與捕捉下一次觸發。Single模式不能和AUTO模式一起開啟。
3. 波形顯示函數
與大多數人不同,本項目的波形顯示函數沒有調用DrawLine,而是用了自己編寫的另一個基于底層的方法。這樣做的初衷是為了進一步驗證自己對OLED底層驅動的理解,并試圖通過自己編寫的顯示函數來避免移植庫中顯存的使用。然而事實證明,顯存的存在有其優勢,且自己建立一套字模就好比天方夜譚。 盡管在8KB RAM的開發板上,2KB的顯存不免奢侈,但顯存的概念本身——尤其是在緩存以避免頻閃上——是很重要的。對于一些更高階的開發板(如F407)系列,顯存將被外擴SRAM硬件實現。一個典型的例子就是EMWIN庫。 本程序采用的函數,主要是討論一種底層驅動的方法。 繪制波形的確可以用DrawLine,然而也可以采用不同的思路。 因為波形一定是以相鄰兩個點為步進,一個一個點繪制的,也就是說,這本質上不是一個通用的DrawLine問題,而是一個x軸步進固定為1的特殊的DrawLine問題,那么這個問題就可以有不同的解法。我們可以認為:第i點與第i+1點的數值,共同決定了第i+1列的顯示。但它們不能影響第i列的顯示。 這要從我們調用的底層講起。板載的這款OLED有兩種尋址模式:一是寫入0X20指令后的列自增尋址,即選定頁地址和列地址后連續寫入,頁地址固定而行地址自增;二是寫入0X21指令后的頁自增尋址,即選定頁地址和列地址后連續寫入,頁地址自增而行地址固定。波形繪制使用的就是不同于常規的0X21指令。 可以想象,每次更新波形時,是一列一列進行的。先清除一列上已有的波形,再顯示新的波形(注意這是直接寫進OLED里,而不是顯存里的,因此無法進行“ |= ”運算)。如果第i點和第i+1點共同決定第i列和第i+1列的顯示,那么同理,第i-1點和第i點也將共同決定第i-1列和第i列的顯示。這樣就會導致第i列在顯示上出現矛盾:后面的會把前面的沖掉。一個典型的例子就是:在顯示方波時,這種方法會導致所有的沿顯示為空白。因此,要想達到顯示波形的效果,只需要簡單地在第i+1列上,填充第i點的行與第i+1點的行之間的全部行就可以了。而在后續顯示示波器分格的虛線、觸發電平虛線,以及負時間或頻標虛線時,只要通過簡單的位運算和或運算,在恰當的行與列將虛線的每個像素點與波形數據進行“或”運算即可。
總結與思考
原文標題:如何使用STM32G031開發板實現雙通道示波器-2022年寒假在家練STM32平臺項目分享(一)
文章出處:【微信公眾號:電子森林】歡迎添加關注!文章轉載請注明出處。
聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。
舉報投訴
-
示波器
+關注
關注
113文章
6516瀏覽量
188094 -
參數
+關注
關注
11文章
1865瀏覽量
32847 -
開發板
+關注
關注
25文章
5481瀏覽量
101923
原文標題:如何使用STM32G031開發板實現雙通道示波器-2022年寒假在家練STM32平臺項目分享(一)
文章出處:【微信號:xiaojiaoyafpga,微信公眾號:電子森林】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
熱點推薦
stm32G031串口外部不接上拉電阻,導致stm32進入到了硬件中斷怎么解決?
stm32G031使用串口和另一其他芯片交互,外部直連,未接上拉電阻,導致stm32進入到了硬件中斷
發表于 03-13 07:59
為什么Stm32g031芯片無法進入bootloader狀態呢
為什么Stm32g031芯片無法進入bootloader狀態呢?為何新的Stm32g031芯片智能使用一次ISP燒寫呢?
發表于 11-25 06:03
STM32G031無線溫濕度儀開源
STM32G031無線溫濕度儀開源項目關鍵詞:CubeMX,CubeIDE,STM32G031C8T6,AHT10,DRF1609H1、項目任務本項目MCU使用STM32G031C8T6,單片機讀取
發表于 01-07 07:57
是否可以將14.7456MHz晶體與STM32G031 (LQFP32) 一起使用?
您好,是否可以將 14.7456MHz 晶體與 STM32G031 (LQFP32) 一起使用。如果我是對的,在數據??表中是不可能的,這個包沒有 OSC_OUT。還是數據表中缺少該選項?我們需要這個晶體用于 IO-Link 應用。
發表于 12-30 07:25
基于FPGA的雙通道簡易可存儲示波器設計
基于FPGA的雙通道簡易可存儲示波器設計:本文介紹了一種基于FPGA的采樣速度60Mbit/s的雙通道簡易數字
發表于 09-29 10:45
?110次下載
評論