2023年11月30日 星期四

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

首先,請先參考如以下IAR提供的"在IAR Embedded Workbench中实现打印输出技術資料"

https://www.iar.com/cn/knowledge/support/technical-notes/general/implementing-printf-output/

本文要說明的是重新定向到MCU的周邊(UART)



STM32在IAR9.2以前的版本printf時,在main.c的主程式之前通常會加入如以下的程式

#ifdef __GNUC__

/* With GCC, small printf (option LD Linker->Libraries->Small printf

   set to 'Yes') calls __io_putchar() */

#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)

#else

#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)

#endif /* __GNUC__ */


並執行以下宣告如於主程式之後

/**

  * @brief  Retargets the C library printf function to the USART.

  * @param  None

  * @retval None

  */

PUTCHAR_PROTOTYPE

{

  /* Place your implementation of fputc here */

  /* e.g. write a character to the USART2 and Loop until the end of transmission */

  HAL_UART_Transmit(&UartHandle, (uint8_t *)&ch, 1, 0xFFFF);

  return ch;

}

但是在IAR9.32以後,已經不支援此方式了,因此在這邊大致上來記錄一下我查到的資訊與方法跟同樣遇到問題的人分享,

1.先到IAR安裝後的資料夾找出write.c,下圖是write.c原始的內容


2.COPY write.c到自行設定的專案資料夾內

3.在您的IAR專案內加入write.c

4.修改write.c以下內容,把main.h這個head file include進來,然後把您的UART底層驅動的函式放在MyLowLevelPutChar內,大致上就可以了,記住您的main.h裡面必須include stdio.h進來,才會有printf相關的函式,由於我用的底層選擇為STM32K的LL,您也可以換成STM32 HAL或是Standard Peripheral Libraries的底層函式








2023年11月20日 星期一

如何透過示波器存下來的波形檔案進行類比電路模擬

在設計電路的時候,常常會遇到雜訊問題,於是我們就會在電路板上進行跳線修改,增加元件增加電容之類的工作,但如果是要增加一個n階的濾波器,有可能因為手工難以修改或增加,而且也無法確定增加的電路是否能正常工作,因此我會藉由電路模擬軟體來協助分析,但是特定的雜訊可能無法在模擬軟體中生成,怎麼辦??

我們可以透過在示波器錄下來波形的圖檔,轉存成.csv/.xls/.txt/.dat或是ASCII files, 然後存在電腦另存.txt file, 其實裡面的內容不外乎是time(second)與amplitude(volt),也就是時域裡的時間跟振福!

有了這個波形檔案我們就可以透過模擬軟體來實現雜訊濾波器電路的模擬了,首先將示波器存好的檔案轉到電腦中存成.txt,去除檔案中的文字,例如time(second)與amplitude(volt)之類的相關文字,只留示波器X/Y兩軸所錄下來的數字,該數字可能有科學符號,例如E-02,代表是10^-2次方,這必須保留以免,數值不正確!

我所使用的模擬軟體是Microchip授權給使用者免費使用的MINDI, 屬於Simetrix/Simplis這類的模擬軟體,首先需要設計濾波器,這部分有兩種方法,一是照濾波器公式計算,二是用類比濾波器軟體幫你設計,建議可以使用Analog Filter Wizard網頁版,假設預計設計的是High Pass Filter,就選擇如下圖對應的Filter type


然後進入Specification之後選擇填入Passband與Stopband,並選擇Filter Response, Filter Response與電路複雜度跟濾波器效果有很大的關係,當然成本也會有所增加



接下來進入Components選擇電壓,就會跳出濾波器電路+參考電壓的電路,然後把電路記下來繪製到慣用的模擬軟體,可以是CADENCE ORCAD PSPICE / SIMETRIX / ADI LTSPICE / MICROCHIP MINDI / TI TINA等等之類的軟體,我用的是MICROCHIP MINDI, 因為免費且容易使用,但有元件數量的限制,使用者自行斟酌!



以下是我的波形.txt檔, 用來放進去MINDI模擬軟體的PWLFILE



設定好模擬的選項(暫態Transient),我的雜訊加訊號是100ms這麼長,因此時間設100ms,就可以開始模擬了!
如下圖可以看到紅色波形是示波器從實際的電路板電路中錄下來的波形加雜訊,經過濾波之後是綠色的波形,雖然訊號變小了,但很明顯的SNR變得好很多,若再經過下一級放大就可以得到足夠大的訊號了!
整個電路方塊大約是這樣:
Input --> HPF --> Amplifier output







2020年9月4日 星期五

ADC two point calibration

使用MCU時常會有不準確的問題造成量測不精準的狀況可以透過兩點式校正ADC的方法ADCGain errorOffset error校正回來,取得需要的精確度。

要進行ADC兩點校正方法不難,首先在MCU的部分需要有VREF輸入可以使用,在不透過人工使用Calibration Source的情況下,利用ATL431B Grade(0.5%)輸出2.5V(VREF)ADC參考電壓,然後再利用ATL431輸出的2.5V電壓做90%*VREF10%*VREF兩個電壓點分別進入AN0AN1,利用AN0AN1兩點電壓做校正,假設ADC的解析度為12 bit2點校正實際方式如下:

/*

VREF = 2.5V

The two calibration points are chosen at V1 = 2.5*10k/(10k+100k); V2 = 2.5*100k/(10k+100k)

The ideal ADC output values at V1 and V2 are represented as Ci1 and Ci2.

Ci1 = V1 * 4096 / 2.5 = 372; Ci2 = V2 * 4096 / 2.5 = 3724

Assume Ca1 = 404, Ca2 = 3847

The actual ADC output values at V1 and V2 are represented as Ca1 and Ca2.

The gain and offset error will be calculated using the equation of a straight line     y = mx + b, Where m is the slope of the line and b is the offset.

The gain error can be calculated as the slope of the actual ADC output divided by the slope of the ideal ADC output.

Gain Error = (Ca2 – Ca1) / (Ci2 – Ci1) = (3847 – 404) / (3724 – 372) = 1.027(positive gain error)

b = y – mx

Offset error = Cal – (Gain error * Ci1) = 404 – (1.027 * 372) = 22(positive offset error)

GAINCORR(Gain correction) = 4096 / Gain error = 4096 / 1.027 = 3988

OFFSETCORR = offset error = 22

Adc result = (Conversion value – OFFSETCORR) * (GAINCORR/4096)

Ca1 = (404 – 22) * (3988 / 4096) = 372

Ca2 = (3847 – 22) * (3988 / 4096) = 3724

*/    

#define CI1 372

#define CI2 3724

 unsigned long int Ca1, Ca2;

long int Ci1 = CI1, Ci2 = CI2;

unsigned int GainError;

int OffsetError;

       

Ca1 = Adc_Read(ACS_AN1, VBG_DIS);

Ca2 = Adc_Read(ACS_AN2, VBG_DIS); 

       

/* y = mx + b, m = slope = (y2-y1) / (x2-x1), y2 = Ca2, y1 = Ca1, x2 = Ci2, x1 =  Ci1*/

GainError = (unsigned int)(((Ca2-Ca1) << 12) / (Ci2 - Ci1)); // scale to 4096

/* b = y - mx */

OffsetError = (int)(Ca1 - (Ci1 * GainError >> 12));

OffsetCorr = OffsetError;

       

/* GainCorr = 4096 * (1/GainError) = 4096 * 4096 / GainError */

GainCorr = (unsigned int)(4096ul * 4096 / GainError);


void MeasureVoltage(void)

{

        static unsigned int SumOfMovAvg = 0;

        static unsigned int MovAvg_Buffer[8] = {0};  // Moving average buffer

        static unsigned char Index = 0;                       // Moving average index

        unsigned char cnt = 0;

        unsigned long int Temp;

    unsigned int result;

               

    MovAvg_Buffer[Index++] = Adc_Read(ACS_AN0, VBG_DIS);

        if(Index == 8)

        {

                Index = 0;

                FLAGbits.MovingAvgOutput = 1;

        }

       

        SumOfMovAvg = 0;

        if(FLAGbits.MovingAvgOutput)

        {

                for(cnt = 0; cnt < 8; cnt++)

                        SumOfMovAvg += MovAvg_Buffer[cnt];                                       

                Temp = (SumOfMovAvg >> 3);

        }

        else

        {

                for(cnt = 0; cnt < Index; cnt++)

                        SumOfMovAvg += MovAvg_Buffer[cnt];                                       

                Temp =  MovAvg_Buffer[Index-1]; //Temp = (SumOfMovAvg / Index);     

        }      

        result = (unsigned int)((unsigned long int)(Temp - OffsetCorr) * GainCorr >> 12);

        // result is the ADC output after two point calibration

}

2019年7月5日 星期五

如何透過校正提升SAR ADC量測電壓的精確度

通常做量測產品的開發時,很多人沒有校正的習慣,這種問題對於做電壓的量測工具來說尤其重要,以開發電瓶量測工具來說,電壓精準度很重要,誤差0.1V對量測電池內阻來說差異就很大了,所以我來提供幾個需要注意的地方,讓你可以使用SAR ADC也可以精準!

撇開硬體的濾波電容等設計不說,要準確的使用ADC,有幾件事要注意:
1.精準的參考電壓源:
假設使用12-bit ADC,可以使用5V4.096V2.5V2.048V的參考電壓,參考電壓越小時ADC的輸入就可能需要有分壓電阻,建議盡量使用跟ADC解析度可以整除的參考電壓,例如12-bit = 2^12 = 4096, 4.096 / 4096 = 1mV = 1LSB,這樣在計算電壓上可以整除,所以誤差較小,選擇參考電壓時要注意精度,盡量選擇0.5%的參考電壓源,例如
2.控制分壓電阻的精度:
     ✮假設電壓輸入12V,分壓電阻使用91k(+-1%)及13k(+-1%),最大的誤差量會發生在91k(+1%)搭配13k(-1%)及91k(-1%)搭配13k(+1%)這兩個組合,12*(12870/(91910+12870))=1.4739及12*(13130/(90090+13130)) = 1.5264,乘上分壓比例8則分別為11.79及12.21,假設在板子上發生這電壓不準確的時候,我們可以透過電壓校正將電壓補償回來,例如12.00/11.79 = 1.0178(GAIN,分壓增益),假電壓為14V,則14*(12870/91910+12870))= 1.7196,反推分壓後為13.75V,且13.75*1.0178 = 14.00V
      ✭但是,非理想的ADC具有INL及DNL誤差,因此分壓增益越大,帶有INL及DNL誤差的ADC Code計算成實際電壓之後再乘上增益會使誤差加大,也就是說(IDEAL ADC+ERR)*GAIN=(IDEAL ADC*GAIN) +(ERR*GAIN), ERR*GAIN會把誤差放大,且通常發生在電壓不是在校正點的地方,解決方法就是將分壓增益縮小,實際的方法就是控制分壓電阻的精度,如91k(+-0.1%)與13k(+-0.1%), 12*(12987/(91091+12987))=1.4972, 1.4972*8 = 11,977, 12/11.977=1.0019,也就是ADC本身誤差*GAIN最多會與理想誤差值相差0.0019倍



     DNL = |[(VD+1- VD)/VLSB-IDEAL - 1] | , where 0 < D < 2- 2.
      INL = | [(VD - VZERO)/VLSB-IDEAL] - D | , where 0 < D < 2N-1.



      3.選擇適合的校正電壓點:
      將校正電壓點移到最常量測的電壓點,以一般12V電瓶來說是13~12V之間,因此將校正點由12V移到12.5V

2019年5月12日 星期日

STM32 LCD Display


常常在手持量測工具的開發中,會使用到LCD來顯示量測資訊,如電壓等,在這邊就簡單的介紹我之前用來開發某量測工具使用LCD的控制方式!
由於並非所有STM32 MCU都有LCD顯示的功能,因此這邊選擇STM32L100C6U6A為例,假設要來點亮88.8V這樣字符的LCD88.8V需要23個點,假設選擇4COMx6SEG共有24 pixels可以滿足23個點的需求,所以會占用STM32L100C6U6A 10 pin I/O(4COM + 6SEG)LCD設計圖如下:















#define LCD_SEG0_Pin            GPIO_Pin_1
#define LCD_SEG0_GPIO_Port      GPIOA
#define LCD_SEG1_Pin            GPIO_Pin_2
#define LCD_SEG1_GPIO_Port      GPIOA
#define LCD_SEG2_Pin            GPIO_Pin_3
#define LCD_SEG2_GPIO_Port      GPIOA
#define LCD_SEG3_Pin            GPIO_Pin_6
#define LCD_SEG3_GPIO_Port      GPIOA
#define LCD_SEG4_Pin            GPIO_Pin_7
#define LCD_SEG4_GPIO_Port      GPIOA
#define LCD_SEG5_Pin            GPIO_Pin_0
#define LCD_SEG5_GPIO_Port      GPIOB    
#define LCD_COM0_Pin            GPIO_Pin_8
#define LCD_COM0_GPIO_Port      GPIOA
#define LCD_COM1_Pin            GPIO_Pin_9
#define LCD_COM1_GPIO_Port      GPIOA
#define LCD_COM2_Pin            GPIO_Pin_10
#define LCD_COM2_GPIO_Port      GPIOA   
#define LCD_COM3_Pin            GPIO_Pin_9
#define LCD_COM3_GPIO_Port      GPIOB  

void LCD_Config(void)
{
     LCD_InitTypeDef LCD_InitStructure;
     GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA | RCC_AHBPeriph_GPIOB, ENABLE);
     GPIO_PinAFConfig(GPIOA, GPIO_PinSource8, GPIO_AF_LCD);  // com0
GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_LCD);  // com1
GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_LCD); // com2
     GPIO_PinAFConfig(GPIOB, GPIO_PinSource9, GPIO_AF_LCD);  // com3
GPIO_PinAFConfig(GPIOA, GPIO_PinSource1, GPIO_AF_LCD);  // seg0
GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_LCD);  // seg1       
GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_LCD);  // seg2
GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_LCD);  // seg3
GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_LCD);  // seg4
GPIO_PinAFConfig(GPIOB, GPIO_PinSource0, GPIO_AF_LCD);  // seg5
     GPIO_InitStructure.GPIO_Pin = LCD_SEG0_Pin | LCD_SEG1_Pin |   
     LCD_SEG2_Pin | LCD_SEG3_Pin | LCD_SEG4_Pin | LCD_COM0_Pin |  
     LCD_COM1_Pin | LCD_COM2_Pin;                                                                                                     
     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
     GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
     GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
     GPIO_Init(GPIOA, &GPIO_InitStructure);
     GPIO_InitStructure.GPIO_Pin = LCD_SEG5_Pin | LCD_COM3_Pin;
     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
     GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
     GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
     GPIO_Init(GPIOB, &GPIO_InitStructure);    
  /*!< Configure the LCD interface */
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_LCD, ENABLE);
  LCD_BlinkConfig(LCD_BlinkMode_Off, LCD_BlinkFrequency_Div8);
  LCD_InitStructure.LCD_Prescaler = LCD_Prescaler_4;
  LCD_InitStructure.LCD_Divider = LCD_Divider_16;
  LCD_InitStructure.LCD_Duty = LCD_Duty_1_4;
}

由於一個com會對應到44 seg,一個comseg共需要2個32 bit(S00~S43,其餘空白)的大小來存放顯示資料,但LCD圖面只用到4COMx6SEG,因此只須宣告一個32bit變數來對應S00~S31,由於使用了4COM所以必須宣告四個變數如下:
uint32_t LCD_COM0_SEGx = 0;
uint32_t LCD_COM1_SEGx = 0;
uint32_t LCD_COM2_SEGx = 0;
uint32_t LCD_COM3_SEGx = 0;
LCD register map and reset values
















#define COMx_S00        0x00000001 << 0
#define COMx_S01        0X00000001 << 1  
#define COMx_S02        0X00000001 << 2 
#define COMx_S03        0X00000001 << 3 
#define COMx_S04        0X00000001 << 4 
#define COMx_S05        0X00000001 << 5 
#define COM0_S0_S31     0       // COM0-S0~S31
#define COM1_S0_S31     2       // COM1-S0~S31
#define COM2_S0_S31     4       // COM2-S0~S31
#define COM3_S0_S31     6       // COM3-S0~S31
enum
{
     CHAR_0 = 0,
     CHAR_1 = 1,
     CHAR_2 = 2,
     CHAR_3 = 3,
     CHAR_4 = 4,
     CHAR_5 = 5,
     CHAR_6 = 6,
     CHAR_7 = 7,
     CHAR_8 = 8,
     CHAR_9 = 9,
     CHAR_A = 10,
     CHAR_a = 10, 
     CHAR_B = 11,
     CHAR_b = 11,
     CHAR_C = 12,
     CHAR_c = 12,
     CHAR_D = 13,
     CHAR_d = 13,
     CHAR_E = 14,
     CHAR_e = 14,
     CHAR_F = 15,
     CHAR_f = 15,
     CHAR_H = 16,
     CHAR_h = 16,
     CHAR_I = 17,
     CHAR_i = 17,
     CHAR_J = 18,
     CHAR_j = 18,
     CHAR_L = 19,
     CHAR_l = 19,
     CHAR_N = 20,
     CHAR_n = 20,
     CHAR_O = 21,
     CHAR_o = 21,
     CHAR_P = 22,
     CHAR_p = 22,
     CHAR_R = 23,
     CHAR_r = 23,
     CHAR_S = 24,
     CHAR_s = 24,
     CHAR_T = 25,
     CHAR_t = 25,
     CHAR_U = 26,
     CHAR_u = 26,
     CHAR_Y = 27,
     CHAR_y = 27,
     CHAR_DESH = 28,
     CHAR_NULL = 29
};
#define DIG1 0
#define DIG2 1
#define DIG3 2
#define DIG4 3
另外我會撰寫LCDTABLE如下:
// 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, b, C, d, E, F, H, I, J, L, n, O, P, r, S, t, U, Y, -, NULL
const uint8_t LCD_Digit_Table[30] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F,0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71, 0x76, 0x06, 0x0E, 0x38, 0x54, 0x3F, 0x73, 0x50, 0x6D, 0x78, 0X3e, 0x6E, 0x40, 0x00};
由於TABLE屬於常數資料,因此需宣告為const另外再寫一個用來顯示控制的七結構變數
union LCD_Digit
{
    uint8_t Byte;
    struct
    {
        unsigned a : 1;
        unsigned b : 1;
        unsigned c : 1;
        unsigned d : 1;
        unsigned e : 1;
        unsigned f : 1;
        unsigned g : 1;
        unsigned p_v : 1;
    };
}Dig[3] = {0, 0, 0};

void LCD_COM0_SEGx_Update(void)
{
    LCD_COM0_SEGx = 0;
    if(Dig[0].d)
        LCD_COM0_SEGx |= COMx_S00;   // COM0-SEG0
    if(Dig[2].d)
        LCD_COM0_SEGx |= COMx_S02;   // COM0-SEG2
    if(Dig[3].p_v)
        LCD_COM0_SEGx |= COMx_S03;   // COM0-SEG3
    if(Dig[4].d)
        LCD_COM0_SEGx |= COMx_S04;   // COM0-SEG4
    if(Dig[5].p_v)
        LCD_COM0_SEGx |= COMx_S05;   // COM0-SEG5 
    return;
}

void LCD_COM1_SEGx_Update(void)
{
    LCD_COM0_SEGx = 0;
    if(Dig[0].e)
        LCD_COM0_SEGx |= COMx_S00;   // COM1-SEG0
    if(Dig[0].c)
        LCD_COM0_SEGx |= COMx_S01;   // COM1-SEG1
    if(Dig[1].e)
        LCD_COM0_SEGx |= COMx_S02;   // COM1-SEG2
    if(Dig[1].c)
        LCD_COM0_SEGx |= COMx_S03;   // COM1-SEG3
    if(Dig[2].e)
        LCD_COM0_SEGx |= COMx_S04;   // COM1-SEG4
    if(Dig[2].c)
        LCD_COM0_SEGx |= COMx_S05;   // COM1-SEG5 
    return;
}

void LCD_COM2_SEGx_Update(void)
{
    LCD_COM0_SEGx = 0;
    if(Dig[0].g)
        LCD_COM0_SEGx |= COMx_S00;   // COM2-SEG0
    if(Dig[0].b)
        LCD_COM0_SEGx |= COMx_S01;   // COM2-SEG1
    if(Dig[1].g)
        LCD_COM0_SEGx |= COMx_S02;   // COM2-SEG2
    if(Dig[1].b)
        LCD_COM0_SEGx |= COMx_S03;   // COM2-SEG3
    if(Dig[2].g)
        LCD_COM0_SEGx |= COMx_S04;   // COM2-SEG4
    if(Dig[2].b)
        LCD_COM0_SEGx |= COMx_S05;   // COM2-SEG5 
    return;
}

void LCD_COM3_SEGx_Update(void)
{
    LCD_COM0_SEGx = 0;
    if(Dig[0].f)
        LCD_COM0_SEGx |= COMx_S00;   // COM3-SEG0
    if(Dig[0].a)
        LCD_COM0_SEGx |= COMx_S01;   // COM3-SEG1
    if(Dig[1].f)
        LCD_COM0_SEGx |= COMx_S02;   // COM3-SEG2
    if(Dig[1].a)
        LCD_COM0_SEGx |= COMx_S03;   // COM3-SEG3
    if(Dig[2].f)
        LCD_COM0_SEGx |= COMx_S04;   // COM3-SEG4
    if(Dig[2].a)
        LCD_COM0_SEGx |= COMx_S05;   // COM3-SEG5 
    return;
}


void LCD_Digit_Display(uint8_t Digit, const unsigned char Table)
{  
    switch(Digit)
    { 
        case 0 :  Dig[0].Byte = Table; break;
        case 1 :  Dig[1].Byte = Table; break;
        case 2 :  Dig[2].Byte = Table; break;
        case 3 :  Dig[3].Byte = Table; break;
        default : break;   
    }
    return;

}

void LCD_Char_Display(const uint8_t *ptr)
{
     uint8_t cnt = 0;
     uint8_t LCD_Digit[4] = {CHAR_NULL, CHAR_NULL, CHAR_NULL, CHAR_NULL};
     while(*ptr != 0x00)
     {
          switch(*ptr)
          {
              case '0':       
                   LCD_Digit[cnt] = CHAR_0; break;
              case '1':
                   LCD_Digit[cnt] = CHAR_1; break;                          
              case '2':
                   LCD_Digit[cnt] = CHAR_2; break;
              case '3':
                   LCD_Digit[cnt] = CHAR_3; break;                                        
              case '4':
                   LCD_Digit[cnt] = CHAR_4; break;
              case '5':
                   LCD_Digit[cnt] = CHAR_5; break;                          
              case '6':    
                   LCD_Digit[cnt] = CHAR_6; break;
              case '7':
                   LCD_Digit[cnt] = CHAR_7; break;                               
              case '8':
                   LCD_Digit[cnt] = CHAR_8; break;
              case '9':
                   LCD_Digit[cnt] = CHAR_9; break;                               
              case 'A':
                   case 'a': LCD_Digit[cnt] = CHAR_A; break;
              case 'B':
                   case 'b': LCD_Digit[cnt] = CHAR_B; break;
              case 'C':
                   case 'c': LCD_Digit[cnt] = CHAR_C; break;
              case 'D':
                   case 'd': LCD_Digit[cnt] = CHAR_D; break;
              case 'E':
                   case 'e': LCD_Digit[cnt] = CHAR_E; break;
              case 'F':
                   case 'f': LCD_Digit[cnt] = CHAR_F; break;           
              case 'H':
                   case 'h': LCD_Digit[cnt] = CHAR_H; break;                                   
              case 'I':
                   case 'i': LCD_Digit[cnt] = CHAR_I; break; 
              case 'J':
                   case 'j': LCD_Digit[cnt] = CHAR_J; break; 
              case 'L':
                   case 'l': LCD_Digit[cnt] = CHAR_L; break;                           
              case 'N':
                   case 'n': LCD_Digit[cnt] = CHAR_N; break;                           
              case 'O':
                   case 'o': LCD_Digit[cnt] = CHAR_O; break;
              case 'P':
                   case 'p': LCD_Digit[cnt] = CHAR_P; break;                           
              case 'R':
                   case 'r': LCD_Digit[cnt] = CHAR_R; break;
              case 'S':
                   case 's': LCD_Digit[cnt] = CHAR_S; break;                           
              case 'T':
                   case 't': LCD_Digit[cnt] = CHAR_T; break;
              case 'U':
                   case 'u': LCD_Digit[cnt] = CHAR_U; break;
              case 'Y':
                   case 'y': LCD_Digit[cnt] = CHAR_Y; break;
              case '-': LCD_Digit[cnt] = CHAR_DESH;  break;
              default : LCD_Digit[cnt] = CHAR_NULL;  break;
          }
          ptr++;
          cnt++;
     }
     LCD_Digit_Display(DIG1, LCD_Digit_Table[LCD_Digit[0]]);
     LCD_Digit_Display(DIG2, LCD_Digit_Table[LCD_Digit[1]]);
     LCD_Digit_Display(DIG3, LCD_Digit_Table[LCD_Digit[2]]);
     LCD_Digit_Display(DIG4, LCD_Digit_Table[LCD_Digit[3]]);  
     }
}

void LCD_Integer_Display(int16_t Number)
{
    uint8_t LCD_Digit[3] = {CHAR_NULL, CHAR_NULL, CHAR_NULL, CHAR_NULL};
   
    LCD_Digit[0] = Number / 100;
    LCD_Digit[1] = (Number / 10) % 10;
    LCD_Digit[2] = Number  % 10;
   
    if(LCD_Digit[0] == 0)
    {
        LCD_Digit[0] = CHAR_NULL;
    }
    else
        LeadZero = 0;

     LCD_Digit_Display(DIG1, LCD_Digit_Table[LCD_Digit[0]]);
     LCD_Digit_Display(DIG2, LCD_Digit_Table[LCD_Digit[1]]);
     LCD_Digit_Display(DIG3, LCD_Digit_Table[LCD_Digit[2]]);
     LCD_Digit_Display(DIG4, LCD_Digit_Table[LCD_Digit[3]]);  
}

void LCD_Handler(void)
{
    LCD_COM0_SEGx_Update();
    LCD_COM1_SEGx_Update();
    LCD_COM2_SEGx_Update();
    LCD_COM3_SEGx_Update();
                      
    LCD_Write(LCD_RAMRegister_0, LCD_COM0_SEGx);     // COM0SEG0~COM0SEG43
    LCD_Write(LCD_RAMRegister_2, LCD_COM1_SEGx);     // COM1SEG0~COM1SEG43
    LCD_Write(LCD_RAMRegister_4, LCD_COM2_SEGx);     // COM2SEG0~COM2SEG43
    LCD_Write(LCD_RAMRegister_6, LCD_COM3_SEGx);     // COM3SEG0~COM3SEG43
    LCD_UpdateDisplayRequest();
 
  /* Wait until the update display is finished */
  while(LCD_GetFlagStatus(LCD_FLAG_UDD) == RESET)
  {}
}

假設要顯示12.0V請先將12.0放大10倍成120然後套用以下程式
LCD_Integer_Display(120);
Dig[DIG2].p_v = 1; // “.”
Dig[DIG3].p_v = 1; // “V”









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

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