天天看點

嵌入式單片機實作printf函數的原理

為什麼需要printf函數

标準庫的 printf函數 能友善的輸出格式化的字元串

而單片機隻有序列槽,隻能列印int型數。如果能借用标準庫 stdio.h 檔案裡的 printf函數,那麼單片機輸出格式化字元串就友善了。

printf函數原理

printf函數 的底層會調用字元輸出函數,我們隻需要把這個字元輸出函數給重新實作為我們的單片機序列槽輸出函數,就可以實作 printf函數 功能了。

stdio.h有兩種:

keil用的是armcc編譯器(隻支援stm32)

​MDK\ARM\ARMCC\include​

​​這個目錄下的​

​stdio.h​

armcc這個stdio.h裡的 printf函數 的底層調用的 字元輸出函數 是fputc(int ch, FILE *f)函數,是以我們重新實作該函數即可。(見附錄中的方案2的解釋)

//加入以下代碼,支援printf函數,而不需要選擇use MicroLIB    
#if 1
#pragma import(__use_no_semihosting)             
//标準庫需要的支援函數                 
struct __FILE 
{ 
  int handle; 
}; 

FILE __stdout;       
//定義_sys_exit()以避免使用半主機模式    
void _sys_exit(int x) 
{ 
  x = x; 
} 
//重定義fputc函數 
int fputc(int ch, FILE *f)
{  
  while((USART1->SR&0X40)==0);//循環發送,直到發送完畢   
  USART1->DR = (u8) ch;      
  return ch;
}
#endif      

關于半主機,參考附錄的解決方案2

其它IDE用的是gcc編譯器

​GNU-Tools-ARM-Embedded\arm-none-eabi\include​

​​裡面的​

​stdio.h​

gcc編譯器這個stdio.h裡的 printf函數 的底層調用的 字元輸出函數 是print_char(char c)函數,是以我們重新實作該函數即可

#include <stdio.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdarg.h>

#undef errno
extern int errno;
extern int _end;
//extern void print_char(char);

//這樣重新定義一下即可
void print_char(char c)
{
    HAL_UART_Transmit(&handle, (uint8_t *) (&c), 1, 1); //我們單片機的序列槽輸出函數
}


caddr_t _sbrk(int incr)
{
    static unsigned char *heap = NULL;
    unsigned char *prev_heap;

    if (heap == NULL)
    {
        heap = (unsigned char *) &_end;
    }
    prev_heap = heap;

    heap += incr;

    return (caddr_t) prev_heap;
}

int link(char *old, char *new)
{
    return -1;
}

int _close(int file)
{
    return -1;
}

int _fstat(int file, struct stat *st)
{
    st->st_mode = S_IFCHR;
    return 0;
}

int _isatty(int file)
{
    return 1;
}

int _lseek(int file, int ptr, int dir)
{
    return 0;
}

int _read(int file, char *ptr, int len)
{
    return 0;
}

void abort(void)
{
    /* Abort called */
    while (1)
        ;
}


int _write(int fd, char *pBuffer, int size)
{
    for (int i = 0; i < size; i++)
    {
        if (pBuffer[i] == '\n')
        {
            print_char('\r');
        }
        print_char(pBuffer[i]);
    }
    return size;
}      

附錄

關于微庫,半主機的解釋:

半主機:專門用arm的,比如我們的開發闆沒有 鍵盤和螢幕 ,但是,使用半主機模式後,我們就可以利用仿真器或其他連接配接到電腦(主機),使用電腦(即主機)的螢幕和鍵盤通過printf() 和 scanf() 來與開發闆互動。

單片機使用 printf() 和 scanf() 函數時 ,隻是希望通過自身硬體帶有的序列槽,列印或接收資料。是以此時的單片機并不是工作在半主機模式的。

而我們通常使用的C庫 中 printf() 和 scanf() 函數 是需要工作在半主機模式。如何解決這個沖突?

方案1:在KEIL中勾選 Use MicroLIB . 即使用微庫. (因為微庫是一個壓縮庫,而微庫中的printf() 和 scanf() 函數 就不是工作在半主機模式下的),其實keil就是在編譯參數裡傳入了一個

-D__MICROLIB 這個宏

方案2:繼續使用标準的C庫,在代碼中聲明不使用半主機模式。是以就有了上面keil中stm32程式那段代碼了。而gcc的stdio.h中的printf是不需要指明不使用半主機的。

參考部落格:

​​配置CLion用于STM32開發【優雅の嵌入式開發】 - 知乎​​