2017年11月22日 星期三

FloatToString

/***************************************************************************//**
 * @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年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資料夾內


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.記得所有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後的結果會比較缺乏可靠度,建議是在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量測電壓都有問題,架構圖如下:

後來詢問打線廠時,將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採樣,當中含有一些數位雜訊,可以透過這樣的方式做一些基礎的濾波,若用在交流訊號的取樣,會因為要增加取樣數,且取樣速度要夠快,否則會導致不符合取樣定理,有時會反效果,造成訊號混疊的情況發生!

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只是一個結構變數,因此需要用取址運算元"&"去指向它,相關細節可能要更加了解硬體底層的工作才能了解,能了解最好,如都不懂的話只要懂得指標相關原理也是可以寫得出來!







2017年8月31日 星期四

定時灑水器

由於曾經開發過紅外線沖水產品,因此此類的用水控制大同小異,也將經驗分享一下給大家!
定時灑水器電路設計及軟體設計都比較單純,大致上的架構如下為使用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;
}







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)





光接收器(光二極體)




 光放大電路很簡單,下圖的建議電路為反向型的,也就是說當有光接收時輸出為LOW,而放大原理也很簡單,光接收器接受光時產生逆向光電流,由於OP輸入阻抗無限大,因此電流不會流入OP的反向輸入端,而會流向RF, V = Id * RF,因此有一個輸出電壓,若邏輯上不習慣可以將IF D91的A(+)接地然後K(-)接OP的反向輸入端,這樣輸出邏輯就會是正的,放大電路完成之後必須考量若要傳送的是數位訊號,就要將訊號變成Full swing,因此OP輸出之後再接比較器去做電壓得比較,就可以完成數位01010101的輸出了!
使用上可以直接將光發射器利用MCU的UART TX去控制訊號的發送,然後接收器接到另外一個MCU的UART RX,利用MCU的UART Module很容易可以解出所送出的資料!
對於光的傳送也可以進行調變,例如使用ASK搭配OOK(ON/OFF Keying)協定,訊號進去OP放大之後會如下圖的正弦波,再透過Logarithmic amplifier將訊號整形(類似低通),然後經由比較器將訊號轉數位01訊號,經過調變之後的訊號在資料的錯誤率會比較低!

Logarithmic Amplifier(對數放大器)訊號處理示意圖如下,INPUT為ASK調變的訊號,經過對數放大器後為OUTPUT,若再經過比較器之後就會變成漂亮的數位輸出!


STM32於IAR 9.3以後的版本將uart導向至printf

首先,請先參考如以下IAR提供的"在IAR Embedded Workbench中实现打印输出技術資料" https://www.iar.com/cn/knowledge/support/technical-notes/general/implementing-printf-ou...