總網頁瀏覽量

2020年2月26日 星期三

【ADC應用】ADC之Step size(LSB size) voltage的計算: Vref該除以2^n還是該除以2^n – 1之簡略探討

歡迎透過合法的方式分享此文內容,若要轉載/轉貼,請明確貼出此原始連結並標示作者基本資訊請勿抄襲及非法轉貼(例如擷取內文但並未註明出處)

  • 前言
小弟不才,本身並不是ADCIC/IP設計專家。長期以來在嵌入式系統韌體與硬體整合應用設計系統的開發方面使用到ADC做各種感測器類比訊號擷取及轉換的應用,常在許多資料看到關於step size/LSB value/LSB size/LSB的實際公式算法有些微不同,故提出此討論。雖然這表面上看起來是個不起眼的小細節,但事實上,許多同行的資深工程師或資深教授都未必相當清楚 (除非過去是有ADC的相關開發經驗)

本文會以偏向軟韌體背景人員而非ADC IC設計工程人員的角度來作探討


: 在許多ADC IC或內建ADCsensor chipdatasheet所稱的step size/LSB value/LSB size/LSB通常指的都是step size voltage(階層電壓值)的意思,也就是輸入的類比電壓如果超過達到一倍的step size階層電壓值,ADC即輸出數位值1,以此類推,不熟的朋友請自行了解ADC基本應用基礎觀念,例如: https://en.wikipedia.org/wiki/Analog-to-digital_converter,參考內文的: “The change in voltage required to guarantee a change in the output code level is called the least significant bit (LSB) voltage”

平常我們單講LSB(Least Significant Bit)本身通常是數位二進制值的最低有效位的意思,而用在ADCdatasheet簡單來說單位數位值所對應的類比電壓值。例如一個8位元的ADC而LSB voltage(或者稱step size voltage)1V,要讓數位值從0b00000000變成0b00000001就是輸入的待轉換類比電壓要達1V以上。另外,step size voltage/LSB voltage常在datasheet常被簡稱為step sizeLSB(常見的敘述形式多種,可能會是這些: step size/LSB value/LSB size/LSB)


------

  • 本文重點

而關於本文要探討的主要部分為: 常見的step size之公式通常就是ADC之參考電壓Vref去除以量化位階,而這公式的常見形式有兩種,如下(暫時撇除量化誤差等誤差條件情況,單純探討原理層面):

Step size = Vref /(2^n)   第一種公式

Step size = Vref /(2^n - 1)   第二種公式

VrefADC的參考電壓
nADC的解析度位元數(文章以下內容皆同此)
而公式的分母項目為量化階層

裡所當然地,數位值的表示範圍是從0到2^n-1,這沒有疑慮。且當位元數多的時候,實際上使用第一種或第二種,都沒有太大的落差(除了這方面需要極度嚴謹的應用系統開發之外)。但這邊要探討的是step size較嚴謹的算法

成大資工wiki與一些市面上的MCU書籍即認為第二種公式才是正確,其內容主張: ADCstep size(LSB value)Vref/(2^n - 1)

圖片來源: 成大資工wiki網頁擷取畫面(http://wiki.csie.ncku.edu.tw/embedded/ADC)



但是,我們也可以在許多書籍或IC晶片廠的datasheetdatasheet裡面的看到ADCstep size(LSB value)的公式,是本文前面所歸類的第一種公式,也就是Vref/(2^n) ,如下:



可以從Analog devicesdatasheet看到數位滿格度的0b11111111是對應到(Vref減去1LSB/Step size電壓值)的類比電壓,而不是對應到Vref





Step size(LSB size)Vref/(2^10)也就是Vref/1024,

右邊是的Digital Output Code指的是ADC轉換並輸出的數位值 ,而1024/Vref等於Step size(LSB size)電壓值的倒數(1/LSB Size)。所以上面公式為

ADC轉換出來的數位值 =  VIN / Step size

1024可以看出仍然屬於第一種公式(沒有做本文章討論的所謂1”的動作)






其中,1024/Vref等於Step size(LSB size)電壓值的倒數(1/LSB Size),也就是每一階的電壓是多少的倒數

公式中的ADC是指ADC轉換並輸出的數位值所以上面公式為(意思與上述MCP3004/3008的部分一樣): 

ADC轉換出來的數位值 =  VIN / Step size

那從1024可以看出仍然屬於第一種公式(2^10 = 1024)








Arduino UNO板子的MCU晶片是上面提及過的Atmega328P,可看出這裡的寫法一樣是屬於第一種公式,也就是5/1024 (題外話: 它後面寫的4.9 mV僅是一個大約的數值,是不論用第一種或第二種公式去套用,答案都是4.88mV)






基本上雖然這章節在講ADCErrors,但框起來的這邊主要仍是在描述STM32 MCUADCLSB(即上述各datasheetStep sizeLSB size)的定義,基本上從4096(指的是STM32 12bits ADC2^12 = 4096)看起來仍然屬於第一種公式

但是在datasheet的某些部分(3.2 Errors due to the ADC environment)就用已經減14095在做一些該章節討論的相關計算,目前對這部分尚未清楚(或許這邊探討的狀況不同,尚未詳細研究)






這裡的LSB(即上述各datasheetStep sizeLSB size)
的定義,基本上從4096(STM32 ADC基本上是12bits ADC2^12 = 4096),仍然沒有做本文章討論的所謂"1"的動作,所以仍是屬於第一種公式



-        80X86 IBM PC and Compatible Computers: Assembly Language, Design, and Interfacing Volumes I & II (4th Edition), Author: Muhammad Ali Mazidi and Janice Gillispie-Mazidi
這是小弟大學時代的微算機原理科目課本,課本裡的4.4節以8位元的ADC0848 chip為講解範例,Step size一樣是Vref/256,仍然屬於第一種公式






上圖畫紅線的部分,說明1LSB(1個LSB voltage)是怎麼來的,12.5V tied to the Vref/2代表Vref為5V,而19.53mV是套用第一種公式而來(5/256,而不是5/255)


上圖畫紅線的部分,這裡又提到1個LSB(1個LSB voltage)的算法 ,也是屬於第一種公式的類型

-----------


這邊再假設一個極端的例子作為範例來思考,假設今天有一顆Vref5V、解析度為1bitADC(當然不可能有解析度這麼差的ADC產品,這邊只是故意舉極端的例子)。並且在圖表的部分仿照Analog devicedatasheet的方式來表示(橫軸為輸入的待轉換類比電壓,縱軸為在許多datasheet也稱為ADC code的已轉換出的數位值)

-> 若是套用第一種公式step size = 5/(2^1)= 2.5V




-> 若是套用第二種公式step size = 5/((2^1) – 1) = 5V


補充: 當然了,本例的上面這兩個圖不一定就是完全精確或完全實際情況的ADC transfer function畫法,僅按照前述Analog devicesdatasheet的在ADC transfer function方面的繪畫方式與邏輯來表現出這個例子的狀況

從這例子來看,第二種公式似乎不太合理,因為step size居然等於Vref的5V,而這邊的5V就是ADC的可輸入的類比full scale range(FSR)電壓值(題外話: 多數ADC允許差動輸入的方式輸入參考電壓),而5V以下的類比電壓就完全都區分不出來。基本上以此例來看,套用第二種公式的情況,似乎就失去了透過有限的數位位階去進行取樣/模擬/轉換那連續(無限多個值)的類比電壓的Analog to Digital的初衷和意義。



另外也從上面許多datasheet的內容做為結果論來看,在step size的計算方面或許應該是第一種公式才是比較正確的( Step size = Vref/(2^n) )

還有一些國外的相關議題討論文章:
> TI E2E™ support forums - Trying to find ADC non-linearity? Look under the carpet
mastering electronics design - An ADC and DAC Integral Non-Linearity (INL)

可以從上列這幾篇文章內容列出的的ADC transfer function圖看出,與ADC規格位元相對應的數位最大值,基本上對應到的類比輸入電壓,並不是對應到Vref/FSR電壓值(單從這點來說,與前述Analog devicesdatasheet的在ADC transfer function圖相同)。而從這點也可以推斷第一種公式較為正確



當然,若有ADC專家或高手能進一步對此說明或討論,則非常歡迎&感謝

------


  • 後記&補充

小弟過去的工程師專職工作都是在IC設計公司負責軟韌體開發,在某間公司時期也需要碰到一些電路的整合設計考量。但剛好都沒有負責到ADC相關的軟韌體,所以當時沒想到要去特別請教負責ADC相關的IC /IP的designer工程師這方面的問題。而最近透過朋友詢問相關專長的工程師,目前也都是得到第一種公式才是正確的公式的回覆。

【若需要嵌入式系統技術輔導課程 可來信洽談合作方式: iws6645@gmail.com,亦可先點擊參考這篇介紹文章

2020年2月18日 星期二

【使用STM32CubeIDE進行STM32 MCU軟韌體開發】STM32 Timer之計數器功能簡易使用教學與Demo

歡迎透過合法的方式分享此文內容,若要轉載/轉貼,請明確貼出此原始連結並標示作者基本資訊請勿抄襲及非法轉貼(例如擷取內文但並未註明出處)

  • 前言
有接觸過8051 MCU的朋友可能知道,在8051中,能夠使用其內部Timer來達成計數器(counter)的功能。對相關的暫存器設定完成無誤後,從8051晶片外部輸入負緣訊號(falling edge)到8051的T1或T0(對應Timer1和Timer0)腳位,即可實現計數功能。每當輸入一個負緣訊號,就會看到timer中的計數暫存器(THx/TLx)的值+1,也就是進行上數(count up)的動作

而STM32系列MCU內部的Timer當然也有這種功能,只是又更加的複雜且多元(畢竟8051是30幾年被開發的MCU晶片)

近期因為收到朋友的緊急協助需求,希望使用STM32 MCU上面的計數器的功能(由MCU外部輸入訊號達成計數)

起初使用網路上許多文章所介紹使用ETR的方式(數年前曾用過),但在此IDE上使用STM32CubeMX tool作相對應的設定後沒有成功(原因尚不清楚,或許有些小地方沒注意到)。於是花了些時間查資料,後來在網路上有看到外國朋友所做的影片,是透過STM32CubeMX tool去做對應的設定,並產生出相對應的code。這篇文章會介紹參考這個影片的設定方式,並在STM32CubeIDE開發環境上面重現實驗和功能測試的過程,過程中會有一些與上述影片中不同的地方

所以這篇文章就僅當成一個實驗紀錄供參考,暫時不分析講解STM32內部Timer的各種模式或較複雜的細節

對於細節有興趣的朋友可自行參考ST官方文件,例如:



----------------

  • 本文重點
我們用的實驗平台是STM32F407G-DISC1 (STM32 discovery系列的板子MB997D),與上述國外網友的影片中所用的平台型號不同


- STM32F405xx, STM32F407xx MCU官方datasheet: 
STM32F407G-DISC1開發板

而從上述官方資料中的電路圖可得知,板子上面的藍色按鈕(代號B1)是被接到STM32F407VG MCU晶片的PA0這根pin腳,且平常是pull down(透過編號R39的220K電阻)的狀況,而當按鈕按下時會是一個high level訊號輸入PA0。如下圖:


STM32F407G-DISC1開發板上的藍色小按鈕對應電路圖
(圖片來源: 
ST官方資料 UM1472 User manual Discovery kit with STM32F407VG MCU)

而我們可以從STM32F407G datasheet的pin out & pin description中看到,PA0這隻腳位的Alternate functions欄位中,是有包含TIM2_CH1_ETR的。基本上這些資訊就提示了我們可以使用這塊開發板上面的藍色按鈕來進行計數器的MCU晶片外部訊號的產生來源(demo驗證功能用)


STM32F407G datasheet中的PA0的pin out & pin description


接下來開始關於軟體的部分。在STM32Cube IDE開設一個關於你自己的STM32平台所屬的專案(看是針對你的MCU型號或者你所使用的開發板型號)

並且使用STM32CubeMX tool開啟專案內的.ioc檔案,在左方的Timers裡面的TIM2的Slave Mode設定為External Clock Mode 1 並將Mode(深灰色項目)中Trigger Source選擇為TI1FP1。此時會在右方MCU對應腳位圖看到PA0這根腳位被設為TIM2_CH1用途 (眾多alternate functions之一)

STM32Cube IDE相關設定(Mode)


再來接著進行Configuration(深灰色項目)中的設定,首先是Parameter Settings(深藍色選項,被選到時會變成比較淺的藍色)。注意: Counter Period一定要設定,且不能設為0。例如你設定100,那Timer最多計數到100,再到下一個tick時,計數值(CNT register的值)就會變成0,簡單來說這個就是設定上限。而因為這顆STM32F407的Timer2(TIM2)的長度是32bits,所以這個Counter Period在這顆MCU晶片上最多可以設定到2的32次方),Trigger polarity設為Rising Edge(正緣觸發)。設定畫面如下:

STM32Cube IDE相關設定(Configuration-Parameter Settings)


而關於計數器從外部輸入訊號所需對應的GPIO Settings(深藍色選項,被選到時會變成比較淺的藍色)如下圖,基本上在我這次的應用需求上沒什麼需要更動的

STM32Cube IDE相關設定(Configuration-GPIO Settings)

接下來就是按下Ctrl + S或者IDE上面的Project選項內的Generate Code,以產生相關的程式碼



接著,在程式中加入實際執行時須呼叫的函式HAL_TIM_Base_Start(&htim2)以啟動TIM2 ,否則TIM2是不會動的。

再來就是燒錄&執行程式(若不清楚操作步驟的朋友可參考網路上其他教學),這邊是用Debug模式進行測試 (透過板子上的stlink進行線上除錯模式觀看暫存器的值)。


進入Debug模式後透過右上方的tool觀察TIM2之CNT暫存器的值

執行結果如下影片(特別觀察上圖中下方紅色圈起來的地方的CNT(counter register)值的變化,每按一下板子上了藍色按鈕,其值即會+1(上數)。而用滑鼠點擊IDE畫面上的Debug tool的RD按鈕圖示是為了更新畫面上顯示的暫存器值



另外,如果要用軟體的方式取得本例中的TIM2的CNT值,可以使用__HAL_TIM_GET_COUNTER這個macro function,其內容在stm32f4xx_hal_tim.h內



__HAL_TIM_GET_COUNTER macro function

而像本例是用TIM2,所以加上參數後的函式呼叫就是長這樣子: __HAL_TIM_GET_COUNTER(&htim2);


【若需要嵌入式系統技術輔導課程 可來信洽談合作方式: iws6645@gmail.com,亦可先點擊參考這篇介紹文章

2020年2月15日 星期六

【使用STM32CubeIDE進行STM32 MCU軟韌體開發】CMSIS-OS wrapping layer和FreeRTOS之thread優先權號碼之對應與互轉



  • 簡介:

當我們使用STM32Cube IDE進行STM32 MCU的軟韌體開發時


若有透過STM32CubeMX工具勾選FreeRTOS


STM32Cube IDE將會自動產生(長出)結合了ARM CMSIS介面層與third party RTOS(我們目前的狀況下就是FreeRTOS)的code出來,加入到我們的MCU開發專案檔中

本文將要介紹的code基本上都是由STM32Cube IDE所自動生成的code,將會追蹤一下這篇文章主題所要追蹤的一個小小部分的source code


而關於這方面的一些較詳細的部分,例如何謂CMSIS,
與third party RTOS之間的關係為何? 未來應該會再另寫文章作介紹,若有興趣的讀者可先參考ST官方文件: UM1722 User manual- Developing applications on STM32Cube with RTOS  以及下方於此ST官方文件中的架構圖


CMSIS-RTOS Architecture(圖片來源: ST官方文件)



而這篇文章的重點會放在有關於CMSIS-OS wrapping layer(以下簡稱CMSIS)和FreeRTOS的task優先權號碼之對應與互轉相關的部分

基本上內容簡單,所以適合在BLOG將追蹤過程大致寫出來(技術過程太複雜的文章不太適合用BLOG寫)


註: 本例是用CMSIS-RTOS V1來實驗的 (現在已經有V2了)

---------------------



  • 本文重點:


當我們在屬於應用程式層的main.c裡面,要藉由CMSIS API創建thread(在FreeRTOS裡面稱作task)時,會需要指定優先權。例如osPriorityHigh或osPriorityNormal即為列舉(enum)內的常數元素項目的名稱(而此數這個常數就是優先權號碼)



osThreadDef 的第三個參數即為優先權號碼

而實際關於這部分的列舉型態宣告之內容如下圖:


osPriority enum declaration in cmsis_os.h

也就是關於我們在STM32Cube with CMSIS & FreeRTOS環境下的CMSIS創建thread時所指定的 -3到3這樣的7個優先權號碼,是CMSIS_OS這層介面所宣告制定的

這時有或許有些接觸過FreeRTOS的人就會有疑問,FreeRTOS的優先權號碼,不是從0(the lowest priority)到configMAX_PRIORITIES - 1 嗎(the highest priority, 預設是6)嗎? 這之間如何對應呢?

FreeRTOSConfig.h

實際上,CMSIS的優先權號碼(-33),是對應到FreeRTOS06(configMAX_PRIORITIE-1,而在FreeRTOSConfig.h內的configMAX_PRIORITIES預設是7,所以號碼就是06)

關於這方面的互轉實作,可以參考cmsis_os.c makeFreeRtosPriority()makeCmsisPriority() 這兩隻函數即可看出來(前者的作用就是就是將CMSISthread優先權號碼轉為FreeRTOS的優先權號碼。而後者的作用就是與前者相反)

makeFreeRtosPriority和makeFreeRtosPriority API

這邊舉個實際例子例如CMSIS層的優先權3,在makeFreeRtosPriority函式裡面就會去減掉-3 (由程式碼裡面的priority - osPriorityIdle可得知,這邊的priority3,而osPriorityIdle從上面介紹過的cmsis_os.h中的宣告是-3),而3-3會得到6,可以簡單地從這些資訊判斷CMSIS層的優先權號碼3,其實是對應到FreeRTOS的優先權號碼6

再舉另一個例子如果CMSIS層的優先權號碼-3,就會對應到FreeRTOS的優先權號碼0,因為-3減-3會得到0。其餘號碼皆以此類推


而理所當然地,osThreadCreate()的內容中,會去呼叫到makeFreeRtosPriority(),以此完成CMSISthread優先權號碼與FreeRTOS thread優先權號碼的轉換對應


osThreadCreate API


所以總結一下,下表為CMSISFreeRTOS優先權號碼對照表






另外,最後也將osThreadDefosThread以及 os_thread_def struct貼出來供參考

osThreadDef




os_thread


 os_thread_def  struct


【若需要嵌入式系統技術輔導課程 可來信洽談合作方式: iws6645@gmail.com,亦可先點擊參考這篇介紹文章