/***************************************************************************//**
* @brief Converts a float value to a character array with 3 digits of accuracy.
*
* @param *buf - returns the converterd value
* @param val - value to be converted
*
* @return None.
*******************************************************************************/
void FloatToString(char * buf, double val)
{
long intPart = 0;
short fracPart = 0;
short charPos = 0;
char localBuf[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
short i = sizeof(localBuf) - 1;
intPart = (long)val;
fracPart = (short)((val - intPart) * 1000 + 0.5);
while(i > sizeof(localBuf) - 4)
{
localBuf[i] = (fracPart % 10) + 0x30;
fracPart /= 10;
i--;
}
localBuf[i] = '.';
if(intPart == 0)
{
i --;
localBuf[i] = '0';
}
while(intPart)
{
i --;
localBuf[i] =(intPart % 10) + 0x30;
intPart /= 10;
}
for(charPos = i; charPos < sizeof(localBuf); charPos ++)
{
*buf = localBuf[charPos];
buf ++;
}
*buf = 0;
}
2017年11月22日 星期三
2017年9月27日 星期三
IAR Embedded Workbench建立STM32 ARM MCU專案
使用過幾種ARM的IDE開發工具後,覺得其實IAR是不錯的選擇,操作簡潔好用,寫MCU程式的人對於IDE的設定繁雜也是蠻苦惱的,今天要介紹如何設定IAR來完成STM32L1XX專案設定,順便幫自己做一下筆記,以免常常花很多時間在開發硬體上,一段時間之後要寫程式又忘了專案的設定!
1.安裝IAR Embedded Workbench(可以去官網下載)
2.開啟IAR Embedded Workbench
3.下載STM32L1xx standard peripherals library(STSW-STM32077), Libraries裡面有CMSIS及STM32L1xx_StdPeriph_Driver是撰寫STM32L1XX所需要的檔案,下載網址路徑如下:
http://www.st.com/en/embedded-software/stsw-stm32077.html
4.在C:\跟目錄底下建立專案資料夾IAR_STM32L1(資料夾名稱隨你喜歡即可),其實資料夾要放哪隨便你,本文說明為了方便直接放在C槽
5.在IAR_IAR_STM32L1資料夾中貼上STSW-STM32077 Libraries裡面的CMSIS及STM32L1xx_StdPeriph_Driver,並新增資料夾Inc及Src, Inc及Src分別用來放置使用者自行撰寫的*.c及*.h檔案
6.建立IAR專案及設定IAR-->Project-->Create New Project, 如以下選擇案OK後會跳出儲存專案*.ewp的視窗,將專案命名並存在IAR_STM32L1資料夾內
1.安裝IAR Embedded Workbench(可以去官網下載)
2.開啟IAR Embedded Workbench
3.下載STM32L1xx standard peripherals library(STSW-STM32077), Libraries裡面有CMSIS及STM32L1xx_StdPeriph_Driver是撰寫STM32L1XX所需要的檔案,下載網址路徑如下:
http://www.st.com/en/embedded-software/stsw-stm32077.html
4.在C:\跟目錄底下建立專案資料夾IAR_STM32L1(資料夾名稱隨你喜歡即可),其實資料夾要放哪隨便你,本文說明為了方便直接放在C槽
5.在IAR_IAR_STM32L1資料夾中貼上STSW-STM32077 Libraries裡面的CMSIS及STM32L1xx_StdPeriph_Driver,並新增資料夾Inc及Src, Inc及Src分別用來放置使用者自行撰寫的*.c及*.h檔案
7.儲存STM32L1.ewp專案後如下圖,在左邊的STM32L1-Debug欄位案右鍵選擇Add Group...,
新增一些Group : (1)CMSIS (2)STM32L1xx_StdPeriph_Driver (3)User, 用來放置要撰寫的程式及要引入的Libraries,
8.到STSW-STM32077裡面的Project資料夾內的STM32L1xx_StdPeriph_Templates資料夾中,
複製以下(1)stm32l1xx_it.c (2)stm32l1xx_conf.h (3)system_stm32l1xx.c (4)stm32l1_it.h檔案到所建立的專案資料夾中
9.將一些需要用到的Libraries及相關STM32L1需要用到的檔案加入到對應的Group裡面
9.新增Group,命名為EWARM用來放置startup_stm32l1xx_md.s, startup_stm32l1xx_md.s可於所下載的STSW-STM32077檔案中搜尋startup_stm32l1xx_md.s並複製貼到EWARM,STM32L1資料夾,startup有分(1)startup_stm32l1xx_hd.s (2)startup_stm32l1xx_md.s (3)startup_stm32l1xx_mdp.s (4)startup_stm32l1xx_xl.s由於選擇的MCU是STM32L100R8T6A, 因此可以找一下stm32l1xx.h裡面有以下關於density的資訊:
/* #define STM32L1XX_MD */ /*!< - Ultra Low Power Medium-density devices: STM32L151x6xx, STM32L151x8xx,
STM32L151xBxx, STM32L152x6xx, STM32L152x8xx, STM32L152xBxx,
STM32L151x6xxA, STM32L151x8xxA, STM32L151xBxxA, STM32L152x6xxA,
STM32L152x8xxA and STM32L152xBxxA.
- Ultra Low Power Medium-density Value Line devices: STM32L100x6xx,
STM32L100x8xx and STM32L100xBxx. */
/* #define STM32L1XX_MDP */ /*!< - Ultra Low Power Medium-density Plus devices: STM32L151xCxx, STM32L152xCxx and STM32L162xCxx
- Ultra Low Power Medium-density Plus Value Line devices: STM32L100xCxx */
/* #define STM32L1XX_HD */ /*!< Ultra Low Power High-density devices: STM32L151xDxx, STM32L152xDxx and STM32L162xDxx */
/* #define STM32L1XX_XL */ /*!< Ultra Low Power XL-density devices: STM32L151xExx, STM32L152xExx and STM32L162xExx */
10.設定Options,在Workspace專案名稱案右鍵選擇Options,設定如下
11.按存檔會跳出視窗要你存*.eww
12.編譯看看是否有錯,通常有錯都是其中的步驟有設錯,尤其是步驟10的Additional include directories有缺路徑沒有選擇,無法找到邊譯器無法找到某個.h檔
2017年9月26日 星期二
STM32初始化程式流程(initialization code)
很多人在做設計MCU程式時,都會遇到一些需要寫底層Driver的問題,偏偏產品開發時程可能無法讓自己做許多的study來了解底層如何撰寫,本篇文章將簡單的介紹STM32如何利用STM32CubeMX軟體來開發底層的程式,讓STM32快速地動起來,加速開發時間!
步驟如下:
1.下載STM32CubeMX軟體
http://www.st.com/content/st_com/en/products/development-tools/software-development-tools/stm32-software-development-tools/stm32-configurators-and-code-generators/stm32cubemx.html
2.安裝STM32CubeMX軟體
3.安裝對應之STM32 MCU所需的Firmware package
Help --> Install New Libraries
4.在STM32CubeMX開啟畫面選擇New Project後,會跳出一個New Project視窗,接著在Part Number Search搜尋所使用的STM32 MCU,右下角會出現選擇的型號,連點兩下左鍵會進入所選取之MCU的畫面
5.利用左邊tab中的選項設定你的MCU,此時的設定必須依據你的板子電路來規劃,也可以直接點選MCU pin來設定
6.選擇Project-->Project Settings, 設定Project Name/Project Location/Toolchain Folder Location/Toolchain IDE等資訊
7.設定完後按存檔*.ioc
8.選擇Project-->Generate Code,程式就會產生在步驟6指定的Location,並會自動詢問是否要開啟專案如EWARM or MDK-ARM V5等專案來開啟程式
9.開始撰寫自己的應用程式
步驟如下:
1.下載STM32CubeMX軟體
http://www.st.com/content/st_com/en/products/development-tools/software-development-tools/stm32-software-development-tools/stm32-configurators-and-code-generators/stm32cubemx.html
2.安裝STM32CubeMX軟體
3.安裝對應之STM32 MCU所需的Firmware package
Help --> Install New Libraries
4.在STM32CubeMX開啟畫面選擇New Project後,會跳出一個New Project視窗,接著在Part Number Search搜尋所使用的STM32 MCU,右下角會出現選擇的型號,連點兩下左鍵會進入所選取之MCU的畫面
5.利用左邊tab中的選項設定你的MCU,此時的設定必須依據你的板子電路來規劃,也可以直接點選MCU pin來設定
6.記得所有tab如Pinout/Clock Configurations/ Configurations都要設定
6.選擇Project-->Project Settings, 設定Project Name/Project Location/Toolchain Folder Location/Toolchain IDE等資訊
7.設定完後按存檔*.ioc
8.選擇Project-->Generate Code,程式就會產生在步驟6指定的Location,並會自動詢問是否要開啟專案如EWARM or MDK-ARM V5等專案來開啟程式
9.開始撰寫自己的應用程式
2017年9月11日 星期一
Frequency Domain Using Excel
通常硬體工程師在量測雜訊或者電壓漣波時,習慣性使用Time Domain來分析,卻忽略的一點,就是Time Domain看不到頻率的成分存在,這樣會很容易忽略掉一些訊號成分,導致找不出雜訊到底如何來的,由於之前在待在觸控IC設計廠的經驗多少有養成這些觀念,因此將此經驗分享出來!
FFT是一種可以將時域信號轉頻域分析的方法,快速傅立葉轉換(英語:Fast Fourier Transform, FFT),是計算序列的離散傅立葉轉換(DFT)或其逆轉換的一種演算法。傅立葉分析將訊號從原始域(通常是時間或空間)轉換到頻域的表示或者逆過來轉換。
至於太理論的東西我也不熟,這邊是要告訴你除了可以用示波器直接開啟Math做FFT量測訊號,並可以透過Frequency Domain Using Excel這份文件將Time Domain的資料成Frequency Domain,
在這之前我們要了解如下圖所示時域與頻域訊號的關係,Time Domain很重要的兩個資訊是Time及Amplitude(Magnitude),可以透過FFT轉成Frequency Domain,而Frequency Domain則可以看出頻帶及能量大小(dB)!
再來就需要參考San Francisco State University School of Engineering的Frequency Domain Using Excel by Larry Klingenberg April 2005,這份文件說明了如何將示波器的Timer Domain資料轉成Frequency Domain,如下圖可以看出FFT轉換過程,要注意的是假設你是波器可以直接printf,就可以直接將FFT的結果直接Printf成圖片存到隨身碟,就不太需要做以下的動作;若否,則需要先儲存Time Domain訊號成*.dat或者*.txt然後再透過以下方式轉換,整個動作對學習的人有個優點,會對FFT大概怎麼轉換的會比較有概念,縱使不懂數學原理,但對整體觀念會比較能建立!
FFT是一種可以將時域信號轉頻域分析的方法,快速傅立葉轉換(英語:Fast Fourier Transform, FFT),是計算序列的離散傅立葉轉換(DFT)或其逆轉換的一種演算法。傅立葉分析將訊號從原始域(通常是時間或空間)轉換到頻域的表示或者逆過來轉換。
至於太理論的東西我也不熟,這邊是要告訴你除了可以用示波器直接開啟Math做FFT量測訊號,並可以透過Frequency Domain Using Excel這份文件將Time Domain的資料成Frequency Domain,
在這之前我們要了解如下圖所示時域與頻域訊號的關係,Time Domain很重要的兩個資訊是Time及Amplitude(Magnitude),可以透過FFT轉成Frequency Domain,而Frequency Domain則可以看出頻帶及能量大小(dB)!
再來就需要參考San Francisco State University School of Engineering的Frequency Domain Using Excel by Larry Klingenberg April 2005,這份文件說明了如何將示波器的Timer Domain資料轉成Frequency Domain,如下圖可以看出FFT轉換過程,要注意的是假設你是波器可以直接printf,就可以直接將FFT的結果直接Printf成圖片存到隨身碟,就不太需要做以下的動作;若否,則需要先儲存Time Domain訊號成*.dat或者*.txt然後再透過以下方式轉換,整個動作對學習的人有個優點,會對FFT大概怎麼轉換的會比較有概念,縱使不懂數學原理,但對整體觀念會比較能建立!
通常一次轉換FFT後的結果會比較缺乏可靠度,建議是在EXCEL內將好幾次的結果利用作圖疊起來,這樣就會有比較準確的判定,假設一個工程師在分析雜訊的同時可以將Time Domain及Frequency Domain同時放在分析報告,這樣會對整個報告的內容更加有說服力!
2017年9月7日 星期四
如何簡單的分析MCU I/O是否損壞
曾經在一個產品的試產階段,發現IC Chip die(裸晶)做bonding(打線)完後回來的產品有一堆故障,因為當時是冬天,也不確定IC打線製程定沒問題,這時候想到IC的I/O port都有設計ESD protection diode,這時候電表的功用來了,直接用電表二極體檔位量測一下ESD Diode是否電壓約為0.65V左右,發現產品故障的,大部分diode量測電壓都有問題,架構圖如下:
如要正確性高一點的話,可以用半導體分析儀直接量測二極體的IV Curve,Diode從負電壓掃描到正電壓,自動會跑出來Current Curve,教科書上有很多類似的如圖,曾經用來分析I2C的CLK/DATA pin故障情況,由於單純用電表量沒辦法百分之百正確,畢竟有些I/O要死不死的就比較難用二極體電壓判定,用分析儀來跑IV Curve判定會比較正確,只不過一台很昂貴,並非一般廠商會購買的!
後來詢問打線廠時,將IC封膠去除用顯微鏡觀察I/OPAD狀況發現有破裂,是因為IC的PAD鋁墊層過薄,導致超音波熔接Wire時把鋁墊層打裂,導致IC有故障的狀況,可能是打線的克數過重,導致破裂也有可能,這時觀察號電流也會增加,都可以當作參考數據!
如要正確性高一點的話,可以用半導體分析儀直接量測二極體的IV Curve,Diode從負電壓掃描到正電壓,自動會跑出來Current Curve,教科書上有很多類似的如圖,曾經用來分析I2C的CLK/DATA pin故障情況,由於單純用電表量沒辦法百分之百正確,畢竟有些I/O要死不死的就比較難用二極體電壓判定,用分析儀來跑IV Curve判定會比較正確,只不過一台很昂貴,並非一般廠商會購買的!
補充: 有網友表示這要看 ESD的設計了,很多IC會用更複雜的MOSFET 開關來做ESD保護電路,這時特性可能就不會完全與diode相同,雖然MOSFET到S/D和sub還是存在巨大的PN junction,但又多了控制!
在我的經驗來說之前用半導體分析儀時有時會分析不出來,去跟IC設計人員詢問,因為保護電路是用MOSFET設計的,如果遇到這種情況就會比較棘手,不過基本上若是一般MCU的I/O應該都是判定得出來的!
2017年9月4日 星期一
簡單的中值平均濾波器
有時寫ADC做平均時,可能因為訊號本身雜訊的影響,導致平均演算後出來的訊號不太滿意,因此會想一些其他平均的方式來試試看,效果也還可以,這邊就分享一下方式給大家中值!
假設ADC快速取樣16筆,當然取樣的頻率必須符合取樣定理如下連結http://www.ni.com/white-paper/2709/zht/,如這部分沒問題了就繼續了解中值平均濾波器,所謂中值,就是數值大小介於大跟小之間的數值,如何取得中值?要取得中值就必須要先將取樣的訊號做排序,排序之後再將比較大的跟比較小的數值踢掉,其餘的數值再進行加總做平均,排序的方式有很多種,可以是氣泡排序或者選擇排序亦或插入排序法等方式,不同排序的方式跟時間複雜度有關,資料結構或演算法課程當中會提到這些內容,需要的人自己可以去了解!
這邊將取樣後的數值存在buffer裡面,等存滿之後,用Selection sort的方式做排序,程式如下:
#define SWAP(x, y, t) ((t) = (x), (x) = (y), (y) = (t))
unsigned int ADC_Result[8] = {512, 510, 520, 513, 509, 505, 511, 512};
void main(void)
{
unsigned char i = 0;
unsigned int Sum = 0
unsigned int ADC_Avg = 0;
SelectionSort(ADC_Result, 8);
for(i = 2; i < 6; i++) // 去除最高最低的數值共四筆數值
{
Sum += ADC_Result[i];
}
ADC_Avg = Sum >> 2; // 總和值/4做平均
return;
}
void SelectionSort(unsigned int list[], unsigned char length)
{
unsigned char i, j, min;
unsigned int temp;
for(i = 0; i < (length - 1); i++)
{
min = i;
for(j = i + 1; j < length; j++)
{
if(list[j] < list[min])
min = j;
}
SWAP(list[i], list[min], temp);
}
return;
}
上述方式建議適用在偏向直流訊號的ADC採樣,當中含有一些數位雜訊,可以透過這樣的方式做一些基礎的濾波,若用在交流訊號的取樣,會因為要增加取樣數,且取樣速度要夠快,否則會導致不符合取樣定理,有時會反效果,造成訊號混疊的情況發生!
假設ADC快速取樣16筆,當然取樣的頻率必須符合取樣定理如下連結http://www.ni.com/white-paper/2709/zht/,如這部分沒問題了就繼續了解中值平均濾波器,所謂中值,就是數值大小介於大跟小之間的數值,如何取得中值?要取得中值就必須要先將取樣的訊號做排序,排序之後再將比較大的跟比較小的數值踢掉,其餘的數值再進行加總做平均,排序的方式有很多種,可以是氣泡排序或者選擇排序亦或插入排序法等方式,不同排序的方式跟時間複雜度有關,資料結構或演算法課程當中會提到這些內容,需要的人自己可以去了解!
這邊將取樣後的數值存在buffer裡面,等存滿之後,用Selection sort的方式做排序,程式如下:
#define SWAP(x, y, t) ((t) = (x), (x) = (y), (y) = (t))
void main(void)
{
unsigned char i = 0;
unsigned int Sum = 0
unsigned int ADC_Avg = 0;
SelectionSort(ADC_Result, 8);
for(i = 2; i < 6; i++) // 去除最高最低的數值共四筆數值
{
Sum += ADC_Result[i];
}
ADC_Avg = Sum >> 2; // 總和值/4做平均
return;
}
void SelectionSort(unsigned int list[], unsigned char length)
{
unsigned char i, j, min;
unsigned int temp;
for(i = 0; i < (length - 1); i++)
{
min = i;
for(j = i + 1; j < length; j++)
{
if(list[j] < list[min])
min = j;
}
SWAP(list[i], list[min], temp);
}
return;
}
上述方式建議適用在偏向直流訊號的ADC採樣,當中含有一些數位雜訊,可以透過這樣的方式做一些基礎的濾波,若用在交流訊號的取樣,會因為要增加取樣數,且取樣速度要夠快,否則會導致不符合取樣定理,有時會反效果,造成訊號混疊的情況發生!
2017年9月1日 星期五
ARM結構指標的操作~以STM32L1XX為例
使用ARM CPU時,通常很常使用CMSIS已經包好的Library,至於其內容如何操作不得而知,這邊要來說明一下其中如何運作,現在以GPIO_Init(GPIOA, &GPIO_InitStructure); 這樣的程式為範例來trace code如何運作,主要會針對指標或結構的操作來說明!
首先先去了解一下GPIO的Register map
GPIO_Init函式實作如下
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
{
uint32_t pinpos = 0x00, pos = 0x00 , currentpin = 0x00;
uint32_t tmpreg = 0x00;
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin));
assert_param(IS_GPIO_MODE(GPIO_InitStruct->GPIO_Mode));
assert_param(IS_GPIO_PUPD(GPIO_InitStruct->GPIO_PuPd));
/* -------------------------Configure the port pins---------------- */
/*-- GPIO Mode Configuration --*/
for (pinpos = 0x00; pinpos < 0x10; pinpos++)
{
pos = ((uint32_t)0x01) << pinpos;
/* Get the port pins position */
currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;
if (currentpin == pos)
{
/* Use temporary variable to update MODER register configuration, to avoid
unexpected transition in the GPIO pin configuration. */
tmpreg = GPIOx->MODER;
tmpreg &= ~(GPIO_MODER_MODER0 << (pinpos * 2));
tmpreg |= (((uint32_t)GPIO_InitStruct->GPIO_Mode) << (pinpos * 2));
GPIOx->MODER = tmpreg;
if ((GPIO_InitStruct->GPIO_Mode == GPIO_Mode_OUT) || (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_AF))
{
/* Check Speed mode parameters */
assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed));
/* Use temporary variable to update OSPEEDR register configuration, to avoid
unexpected transition in the GPIO pin configuration. */
tmpreg = GPIOx->OSPEEDR;
tmpreg &= ~(GPIO_OSPEEDER_OSPEEDR0 << (pinpos * 2));
tmpreg |= ((uint32_t)(GPIO_InitStruct->GPIO_Speed) << (pinpos * 2));
GPIOx->OSPEEDR = tmpreg;
/*Check Output mode parameters */
assert_param(IS_GPIO_OTYPE(GPIO_InitStruct->GPIO_OType));
/* Use temporary variable to update OTYPER register configuration, to avoid
unexpected transition in the GPIO pin configuration. */
tmpreg = GPIOx->OTYPER;
tmpreg &= ~((GPIO_OTYPER_OT_0) << ((uint16_t)pinpos));
tmpreg |= (uint16_t)(((uint16_t)GPIO_InitStruct->GPIO_OType) << ((uint16_t)pinpos));
GPIOx->OTYPER = tmpreg;
}
/* Use temporary variable to update PUPDR register configuration, to avoid
unexpected transition in the GPIO pin configuration. */
tmpreg = GPIOx->PUPDR;
tmpreg &= ~(GPIO_PUPDR_PUPDR0 << ((uint16_t)pinpos * 2));
tmpreg |= (((uint32_t)GPIO_InitStruct->GPIO_PuPd) << (pinpos * 2));
GPIOx->PUPDR = tmpreg;
}
}
}
重點是必須了解為何要宣告兩個指標GPIO_TypeDef* GPIOx及GPIO_InitTypeDef* GPIO_InitStruct做傳入參數來使用,因此找出GPIO_TypeDef及GPIO_InitTypeDef結構定義如下,
typedef struct
{
__IO uint32_t MODER; /*!< GPIO port mode register, Address offset: 0x00 */
__IO uint16_t OTYPER; /*!< GPIO port output type register, Address offset: 0x04 */
uint16_t RESERVED0; /*!< Reserved, 0x06 */
__IO uint32_t OSPEEDR; /*!< GPIO port output speed register, Address offset: 0x08 */
__IO uint32_t PUPDR; /*!< GPIO port pull-up/pull-down register, Address offset: 0x0C */
__IO uint16_t IDR; /*!< GPIO port input data register, Address offset: 0x10 */
uint16_t RESERVED1; /*!< Reserved, 0x12 */
__IO uint16_t ODR; /*!< GPIO port output data register, Address offset: 0x14 */
uint16_t RESERVED2; /*!< Reserved, 0x16 */
__IO uint16_t BSRRL; /*!< GPIO port bit set/reset low registerBSRR, Address offset: 0x18 */
__IO uint16_t BSRRH; /*!< GPIO port bit set/reset high registerBSRR, Address offset: 0x1A */
__IO uint32_t LCKR; /*!< GPIO port configuration lock register, Address offset: 0x1C */
__IO uint32_t AFR[2]; /*!< GPIO alternate function low register, Address offset: 0x20-0x24 */
#if defined (STM32L1XX_HD) || defined (STM32L1XX_XL)
__IO uint16_t BRR; /*!< GPIO bit reset register, Address offset: 0x28 */
uint16_t RESERVED3; /*!< Reserved, 0x2A */
#endif
} GPIO_TypeDef;
typedef struct
{
uint32_t GPIO_Pin; /*!< Specifies the GPIO pins to be configured.
This parameter can be any value of @ref GPIO_pins_define */
GPIOMode_TypeDef GPIO_Mode; /*!< Specifies the operating mode for the selected pins.
This parameter can be a value of @ref GPIOMode_TypeDef */
GPIOSpeed_TypeDef GPIO_Speed; /*!< Specifies the speed for the selected pins.
This parameter can be a value of @ref GPIOSpeed_TypeDef */
GPIOOType_TypeDef GPIO_OType; /*!< Specifies the operating output type for the selected pins.
This parameter can be a value of @ref GPIOOType_TypeDef */
GPIOPuPd_TypeDef GPIO_PuPd; /*!< Specifies the operating Pull-up/Pull down for the selected pins.
This parameter can be a value of @ref GPIOPuPd_TypeDef */
}GPIO_InitTypeDef;
而仔細觀察GPIO_TypeDef及GPIO_InitTypeDef會發現結構內宣告的內容全部都在上述GPIO register map圖片當中,
實際在執行GPIO的規劃程式如下,GPIO_Init(GPIOA, &GPIO_InitStructure);就是最後做GPIO設定的程式,
void GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA | RCC_AHBPeriph_GPIOB |\
RCC_AHBPeriph_GPIOC | RCC_AHBPeriph_GPIOD | RCC_AHBPeriph_GPIOH ,ENABLE);
/* Configure GPIOA pins */
GPIO_InitStructure.GPIO_Pin = BKL_BTN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
返回去看一下void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)這個初始化GPIO的Library,如前述宣告兩個指標GPIO_TypeDef* GPIOx及GPIO_InitTypeDef* GPIO_InitStruct做傳入參數來使用,在
GPIO_Init(GPIOA, &GPIO_InitStructure)執行時GPIO_TypeDef*會指向GPIOA及GPIO_InitTypeDef*會指向GPIO_InitStructure,因此必須繼續挖出GPIOA是什麼,接著找出相關定義如下:
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
由於GPIO屬於AHB Bus,因此找到定義如下,並查看上述圖片table 5.可以了解PERIPH_BASE的address是0x40000000,而GPIOA在AHBPERIPH的offset是0x20000,
#define GPIOA_BASE (AHBPERIPH_BASE + 0x0000)
#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)
#define PERIPH_BASE ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */
總結
GPIO_Init(GPIOA, &GPIO_InitStructure)的使用就是GPIO_TypeDef*指標指向GPIOA的位址,然後透過底層的Driver去設定GPIOA的硬體,而GPIO_InitTypeDef*指向GPIO_InitStructure,GPIO_InitStructure則會設定IO相關工作模式,例如輸出入等等,由於GPIO_InitStructure只是一個結構變數,因此需要用取址運算元"&"去指向它,相關細節可能要更加了解硬體底層的工作才能了解,能了解最好,如都不懂的話只要懂得指標相關原理也是可以寫得出來!
首先先去了解一下GPIO的Register map
GPIO_Init函式實作如下
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
{
uint32_t pinpos = 0x00, pos = 0x00 , currentpin = 0x00;
uint32_t tmpreg = 0x00;
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin));
assert_param(IS_GPIO_MODE(GPIO_InitStruct->GPIO_Mode));
assert_param(IS_GPIO_PUPD(GPIO_InitStruct->GPIO_PuPd));
/* -------------------------Configure the port pins---------------- */
/*-- GPIO Mode Configuration --*/
for (pinpos = 0x00; pinpos < 0x10; pinpos++)
{
pos = ((uint32_t)0x01) << pinpos;
/* Get the port pins position */
currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;
if (currentpin == pos)
{
/* Use temporary variable to update MODER register configuration, to avoid
unexpected transition in the GPIO pin configuration. */
tmpreg = GPIOx->MODER;
tmpreg &= ~(GPIO_MODER_MODER0 << (pinpos * 2));
tmpreg |= (((uint32_t)GPIO_InitStruct->GPIO_Mode) << (pinpos * 2));
GPIOx->MODER = tmpreg;
if ((GPIO_InitStruct->GPIO_Mode == GPIO_Mode_OUT) || (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_AF))
{
/* Check Speed mode parameters */
assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed));
/* Use temporary variable to update OSPEEDR register configuration, to avoid
unexpected transition in the GPIO pin configuration. */
tmpreg = GPIOx->OSPEEDR;
tmpreg &= ~(GPIO_OSPEEDER_OSPEEDR0 << (pinpos * 2));
tmpreg |= ((uint32_t)(GPIO_InitStruct->GPIO_Speed) << (pinpos * 2));
GPIOx->OSPEEDR = tmpreg;
/*Check Output mode parameters */
assert_param(IS_GPIO_OTYPE(GPIO_InitStruct->GPIO_OType));
/* Use temporary variable to update OTYPER register configuration, to avoid
unexpected transition in the GPIO pin configuration. */
tmpreg = GPIOx->OTYPER;
tmpreg &= ~((GPIO_OTYPER_OT_0) << ((uint16_t)pinpos));
tmpreg |= (uint16_t)(((uint16_t)GPIO_InitStruct->GPIO_OType) << ((uint16_t)pinpos));
GPIOx->OTYPER = tmpreg;
}
/* Use temporary variable to update PUPDR register configuration, to avoid
unexpected transition in the GPIO pin configuration. */
tmpreg = GPIOx->PUPDR;
tmpreg &= ~(GPIO_PUPDR_PUPDR0 << ((uint16_t)pinpos * 2));
tmpreg |= (((uint32_t)GPIO_InitStruct->GPIO_PuPd) << (pinpos * 2));
GPIOx->PUPDR = tmpreg;
}
}
}
重點是必須了解為何要宣告兩個指標GPIO_TypeDef* GPIOx及GPIO_InitTypeDef* GPIO_InitStruct做傳入參數來使用,因此找出GPIO_TypeDef及GPIO_InitTypeDef結構定義如下,
typedef struct
{
__IO uint32_t MODER; /*!< GPIO port mode register, Address offset: 0x00 */
__IO uint16_t OTYPER; /*!< GPIO port output type register, Address offset: 0x04 */
uint16_t RESERVED0; /*!< Reserved, 0x06 */
__IO uint32_t OSPEEDR; /*!< GPIO port output speed register, Address offset: 0x08 */
__IO uint32_t PUPDR; /*!< GPIO port pull-up/pull-down register, Address offset: 0x0C */
__IO uint16_t IDR; /*!< GPIO port input data register, Address offset: 0x10 */
uint16_t RESERVED1; /*!< Reserved, 0x12 */
__IO uint16_t ODR; /*!< GPIO port output data register, Address offset: 0x14 */
uint16_t RESERVED2; /*!< Reserved, 0x16 */
__IO uint16_t BSRRL; /*!< GPIO port bit set/reset low registerBSRR, Address offset: 0x18 */
__IO uint16_t BSRRH; /*!< GPIO port bit set/reset high registerBSRR, Address offset: 0x1A */
__IO uint32_t LCKR; /*!< GPIO port configuration lock register, Address offset: 0x1C */
__IO uint32_t AFR[2]; /*!< GPIO alternate function low register, Address offset: 0x20-0x24 */
#if defined (STM32L1XX_HD) || defined (STM32L1XX_XL)
__IO uint16_t BRR; /*!< GPIO bit reset register, Address offset: 0x28 */
uint16_t RESERVED3; /*!< Reserved, 0x2A */
#endif
} GPIO_TypeDef;
typedef struct
{
uint32_t GPIO_Pin; /*!< Specifies the GPIO pins to be configured.
This parameter can be any value of @ref GPIO_pins_define */
GPIOMode_TypeDef GPIO_Mode; /*!< Specifies the operating mode for the selected pins.
This parameter can be a value of @ref GPIOMode_TypeDef */
GPIOSpeed_TypeDef GPIO_Speed; /*!< Specifies the speed for the selected pins.
This parameter can be a value of @ref GPIOSpeed_TypeDef */
GPIOOType_TypeDef GPIO_OType; /*!< Specifies the operating output type for the selected pins.
This parameter can be a value of @ref GPIOOType_TypeDef */
GPIOPuPd_TypeDef GPIO_PuPd; /*!< Specifies the operating Pull-up/Pull down for the selected pins.
This parameter can be a value of @ref GPIOPuPd_TypeDef */
}GPIO_InitTypeDef;
而仔細觀察GPIO_TypeDef及GPIO_InitTypeDef會發現結構內宣告的內容全部都在上述GPIO register map圖片當中,
實際在執行GPIO的規劃程式如下,GPIO_Init(GPIOA, &GPIO_InitStructure);就是最後做GPIO設定的程式,
void GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA | RCC_AHBPeriph_GPIOB |\
RCC_AHBPeriph_GPIOC | RCC_AHBPeriph_GPIOD | RCC_AHBPeriph_GPIOH ,ENABLE);
/* Configure GPIOA pins */
GPIO_InitStructure.GPIO_Pin = BKL_BTN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
返回去看一下void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)這個初始化GPIO的Library,如前述宣告兩個指標GPIO_TypeDef* GPIOx及GPIO_InitTypeDef* GPIO_InitStruct做傳入參數來使用,在
GPIO_Init(GPIOA, &GPIO_InitStructure)執行時GPIO_TypeDef*會指向GPIOA及GPIO_InitTypeDef*會指向GPIO_InitStructure,因此必須繼續挖出GPIOA是什麼,接著找出相關定義如下:
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
由於GPIO屬於AHB Bus,因此找到定義如下,並查看上述圖片table 5.可以了解PERIPH_BASE的address是0x40000000,而GPIOA在AHBPERIPH的offset是0x20000,
#define GPIOA_BASE (AHBPERIPH_BASE + 0x0000)
#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)
#define PERIPH_BASE ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */
總結
GPIO_Init(GPIOA, &GPIO_InitStructure)的使用就是GPIO_TypeDef*指標指向GPIOA的位址,然後透過底層的Driver去設定GPIOA的硬體,而GPIO_InitTypeDef*指向GPIO_InitStructure,GPIO_InitStructure則會設定IO相關工作模式,例如輸出入等等,由於GPIO_InitStructure只是一個結構變數,因此需要用取址運算元"&"去指向它,相關細節可能要更加了解硬體底層的工作才能了解,能了解最好,如都不懂的話只要懂得指標相關原理也是可以寫得出來!
2017年8月31日 星期四
定時灑水器
由於曾經開發過紅外線沖水產品,因此此類的用水控制大同小異,也將經驗分享一下給大家!
定時灑水器電路設計及軟體設計都比較單純,大致上的架構如下為使用MCU控制H bridge來驅動脈衝電磁閥,其實這種電磁閥就是一個電磁鐵的裝置,用脈波控制水閥開啟或關閉,控制脈波大概幾十ms的方波就可以了,也可以搭配紅外線測距完成紅外線小便斗沖水器及紅外線水龍頭!
水用電磁閥圖片
定時灑水器電路設計及軟體設計都比較單純,大致上的架構如下為使用MCU控制H bridge來驅動脈衝電磁閥,其實這種電磁閥就是一個電磁鐵的裝置,用脈波控制水閥開啟或關閉,控制脈波大概幾十ms的方波就可以了,也可以搭配紅外線測距完成紅外線小便斗沖水器及紅外線水龍頭!
水用電磁閥圖片
控制電路 :
動作原理如下,圖中負載為電磁閥開啟或關閉分別為不同方向的電流,簡單的控制方式為
Direction 1 :
P1B = 0;
P1A = 1;
delay_ms(50);
P1A = 0;
Direction 2:
P1A = 0;
P1B = 1;
delay_ms(50);
P1B = 0;
MCU可以隨便選擇一個Microchip pic16 8bit的MCU即可, Program memory 1k or 2k word, Data memory 128~256 bytes, 可以搭配Timer1 32.768k振盪器,做計時器的功能,宣告三個變數Hour / Minute / Second,每秒產生一次timer中斷,然後將利用定時器寫一個可以設定時間的時鐘,然後看要隔多久自動沖水灑水一次即可,定時器的精準度要靠軟體去撰寫,盡量少除頻,然後不要去更新Timer的數值,讓timer自動跑到溢位發生中斷,32.768kHz crystal也可以選擇精度較高的(20ppm或以下),另外需要使用七段顯示器或者TN/STN LCD來顯示時間,並預留按鍵調整時間!
Microchip台灣的論壇有相關的討論:http://www.microchip.com.tw/modules/newbb/viewtopic.php?topic_id=177
Timer程式虛擬碼:
volatile unsigned char Hour = 0, Minute = 0, Second = 0;
void interrupt ISR(void)
{
if(TMR1IF && TMR1IE)
{
TMR1IF = 0;
if(++Second == 60)
{
Second = 0;
if(++Minute == 60)
{
Minute = 0;
if(++Hour == 24)
{
Hour = 0;
}
}
}
}
}
利用GPIO達到Software UART TX
有時在開發過程中撰寫程式時,礙於成本考量,使用Low pin count MCU,因此對於IC的開發工具不支援Low pin count的Debug模式,要如何偵錯就是一個課題,在這邊將簡單的介紹利用GPIO pin將UART的資料送出,經由UART轉USB連接線接到電腦去觀看數據資料,因此需了解UART傳送格式如以下 :
Idle : High
Start bit : Low
Data : LSB first
也可以透過電腦終端機軟體Tera Term了解以下資訊 :
Port : 跟PC的哪個COM port連線
Baud rate : 傳送速度(每秒多少位元,常見9600 bit per second)
Data : 8bit or 9bit(常見為8bit)
Parity : 同位元檢查(錯誤檢查用,若只是用來做開發的Debug用途,可以選none)
Stop : 1 / 1.5 / 2 bit(通常為1bit)
Flow control : 流量控制(基本上MCU不需要使用到)
廢話不多說,假設要使用最常見的9600/8/N/1, 這邊會說明以下幾個步驟 :
(1)設定GPIO為輸出
(2)設定timer中斷或者使用delay,我最常用是使用timer中斷,設定方法為先知道傳送速度,假設為9600bps,也就是說每104us送一個bit,因此timer interval先設定為104us,當要傳送時,開啟timer計數等待中斷發生之後傳送第一個Start bit,接著等下一次中斷傳送LSB,繼續等待下次中斷直到LSB送完之後並且送完Stop bit;一個bit一個bit緊接著傳送的方式要有移位的觀念,假設MCU使用的是PIC16系列程式如下:
volatile unsigned char TX_Data;
volatile unsigned char Cnt = 0;
#define TX LATCbits.LATC7
// Cnt = 0 : Start bit
// Cnt = 1~8 : Data bit
// Cnt = 9 : Stop bit
void interrupt ISR(void)
{
if(TMR2IF && TMR2IE)
{
TMR2IF = 0;
if(Cnt == 0)
{
TX = 0; // Start bit
}
else if(Cnt < 9) // Data bit : 1 ~ 8
{
if ((TX_Data >> (Cnt - 1)) & 0x01) /* bit to send */
TX = 1;
else
TX = 0;
}
else
{
TX = 1; // Stop bit
TMR2ON = 0;
Cnt = 0;
return;
}
Cnt++;
}
void main(void)
{
Init_MCU();
Init_Timer2(); // 104us interval
TX_Data = 0x55;
TMR2ON = 1; // Start UART TX
PEIE = 1;
GIE = 1;
while(1);
return;
}
Idle : High
Start bit : Low
Data : LSB first
也可以透過電腦終端機軟體Tera Term了解以下資訊 :
Port : 跟PC的哪個COM port連線
Baud rate : 傳送速度(每秒多少位元,常見9600 bit per second)
Data : 8bit or 9bit(常見為8bit)
Parity : 同位元檢查(錯誤檢查用,若只是用來做開發的Debug用途,可以選none)
Stop : 1 / 1.5 / 2 bit(通常為1bit)
Flow control : 流量控制(基本上MCU不需要使用到)
(1)設定GPIO為輸出
(2)設定timer中斷或者使用delay,我最常用是使用timer中斷,設定方法為先知道傳送速度,假設為9600bps,也就是說每104us送一個bit,因此timer interval先設定為104us,當要傳送時,開啟timer計數等待中斷發生之後傳送第一個Start bit,接著等下一次中斷傳送LSB,繼續等待下次中斷直到LSB送完之後並且送完Stop bit;一個bit一個bit緊接著傳送的方式要有移位的觀念,假設MCU使用的是PIC16系列程式如下:
volatile unsigned char TX_Data;
volatile unsigned char Cnt = 0;
#define TX LATCbits.LATC7
// Cnt = 0 : Start bit
// Cnt = 1~8 : Data bit
// Cnt = 9 : Stop bit
void interrupt ISR(void)
{
if(TMR2IF && TMR2IE)
{
TMR2IF = 0;
if(Cnt == 0)
{
TX = 0; // Start bit
}
else if(Cnt < 9) // Data bit : 1 ~ 8
{
if ((TX_Data >> (Cnt - 1)) & 0x01) /* bit to send */
TX = 1;
else
TX = 0;
}
else
{
TX = 1; // Stop bit
TMR2ON = 0;
Cnt = 0;
return;
}
Cnt++;
}
void main(void)
{
Init_MCU();
Init_Timer2(); // 104us interval
TX_Data = 0x55;
TMR2ON = 1; // Start UART TX
PEIE = 1;
GIE = 1;
while(1);
return;
}
2017年8月30日 星期三
光纖通訊~Fiber Optic
光纖通訊(Fiber-optic communication)也作光纖通訊,是指一種利用光與光纖(optical fiber)傳遞資訊的一種方式,屬於有線通訊的一種。光經過調變(modulation)後便能攜帶資訊。
利用光纖做為通訊之用通常需經過下列幾個步驟:
·
以發射器(transmitter)產生光訊號。
·
以光纖傳遞訊號,同時必須確保光訊號在光纖中不會衰減或嚴重變形。
·
以接收器(receiver)接收光訊號,並且轉換成電訊號。
至於發射器及接收器與光纖可以參考Industrial optic的產品 http://i-fiberoptics.com/fiber-optic-leds.php, 要注意的是發射器與接收器光譜波長盡量要相同,這樣做響應才會匹配!
之前曾經在學校中使用IF E91D及IF D91用來做電池平衡板的通訊,由於電池是串連的,多串之後電壓非常高,採四串一片BMS做管理,假設電池有16串,那共有四片BMS,如果將四片BMS共地通訊的話,會因為電池短路產生燒毀,因此必須採用隔離通訊,而光通訊就是一個很好的方式!
光纖Cable
光發射器(IF E91D)
光接收器(光二極體)
使用上可以直接將光發射器利用MCU的UART TX去控制訊號的發送,然後接收器接到另外一個MCU的UART RX,利用MCU的UART Module很容易可以解出所送出的資料!
對於光的傳送也可以進行調變,例如使用ASK搭配OOK(ON/OFF Keying)協定,訊號進去OP放大之後會如下圖的正弦波,再透過Logarithmic amplifier將訊號整形(類似低通),然後經由比較器將訊號轉數位01訊號,經過調變之後的訊號在資料的錯誤率會比較低!
Logarithmic Amplifier(對數放大器)訊號處理示意圖如下,INPUT為ASK調變的訊號,經過對數放大器後為OUTPUT,若再經過比較器之後就會變成漂亮的數位輸出!
訂閱:
文章 (Atom)
-
按鍵反彈跳設計 按鍵開關在MCU的程式設計中初學者通常會遇到一個問題,就是按鍵彈跳現象,原因由於按鍵屬於機械結構,按下時會有機械彈跳的狀況出現,通常會讓判定按鍵的動作出現BUG,因此在程式當中或者硬體上須作處理才能消除彈跳的現象,下圖為量測按鍵按下時的彈跳現象,可以看出在10...
-
你想用熱敏電阻偵測溫度嗎?你想用微控制器做熱敏電阻的讀取並顯示溫度嗎? 由於Edison在出社會第一份從事電子的工作的公司就是專門生產體溫計的公司,既然了解其中一點東西,出社會這麼久了,也該分享一些東西讓有需要的人可以取得資訊的來源,加速學習過程,本篇文章幫大家介紹如何使用微控...
-
在使用ADC過程當中,解析度是一個用來表示ADC性能的指標,其計算方式可以透過如下方程式表示 : 1LSB = VREF / (2^n - 1), VREF為ADC的參考電壓, n為表示解析度的位元數,假設使用10-bits ADC, ADC參考電壓為5V, 可以計算1LSB =...