deliahouse887 发表于 2023-12-23 14:42

对printf和scanf的实现

1.printf函数在格式化输出时,向下调用了char putchar(char c);这个函数,在“stdio.h”里可以发现有这个函数,所以我们需要自己构造一个这样的函数,即通过串口putchar(),代码如下:

char putchar(char c)

{

    hal_uart_putchar(c);

    return c;

}

      其中hal_uart_putchar(c);函数是我们比较熟悉的了,是51单片机通过串口发送一个字节的函数,具体代码如下:

void hal_uart_putchar(char i)

{



}

      有了这两个函数,在单片机启动后,首先进行串口初始化,接着就可以使用printf了……是不是很简单……

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

      2.下面再看scanf的具体实现方法:

      scanf函数在格式化输入时,向下调用了char getkey(void);这个函数,在“stdio.h”里可以发现有这个函数,所以我们需要自己构造一个这样的函数,即通过串口getkey(),代码如下:

char _getkey (void)   

{

    return hal_uart_getchar();

}

其中hal_uart_getchar(); 稍稍复杂,但也很好理解,代码如下:

char hal_uart_getchar(void)

{

    uchar ch;

    //Wait until a character is available:   

    while(uart_rx_cnt == 0);

    ES = 0;

    ch = uart_rx;

    uart_rx_rp = (uart_rx_rp + 1) % UART_BUF_SIZE;

    uart_rx_cnt--;

    ES = 1;

    return ch;

}

这个函数是从串口接收队列中取出队尾的一个字节。uart_rx_cnt 表示现在串口队列中的已有字节数,uart_rx_rp 指向队尾。

最后要介绍的一个函数是串口接收中断函数,代码如下:

void UART1InterruptReceive(void) interrupt 4

{

    ES=0;//关串行口中断   

    if(RI)

    {

      RI=0;//接收中断信号清零,表示将继续接收   

      if(uart_rx_cnt < UART_BUF_SIZE)

      {

            uart_rx = SBUF;

            uart_rx_wp = (uart_rx_wp + 1) % UART_BUF_SIZE;

            uart_rx_cnt++;

      }

    }   

    ES=1;//开串行口中断   

}

该函数实现了串口的中断接收,收到的新的字节存放在队首,即uart_rx_wp指向队列的首地址,每次收到一个新的字节,uart_rx_cnt增1。

至此,scanf函数也可以实现了。

华大MCU代理 发表于 2023-12-25 10:12

感谢分享

xdvca 发表于 2024-7-31 22:46

你的描述详细地介绍了如何实现printf和scanf函数,通过UART在51单片机上进行串口通信。这是一个很好的例子,如何使用自定义的putchar和getkey函数,配合UART中断实现输入输出。

sdlls 发表于 2024-8-4 13:20

在单片机中实现printf和scanf函数通常涉及到对标准输入输出库的替换或者自定义实现,因为单片机的资源有限,尤其是内存和存储空间,所以不能直接使用像PC上的标准库那样庞大的代码。

deliahouse887 发表于 2024-8-4 16:50

标准的scanf函数是从标准输入设备(通常是键盘)接收输入,但在单片机环境中,需要将scanf函数的输入重定向到串行通信接口。这通常通过定义一个新的_read函数来实现,该函数从串行通信接口读取字符。

gouguoccc 发表于 2024-8-4 21:41

在单片机中用scanf函数意义不大

suncat0504 发表于 2024-8-5 08:47

很实用的知识。调试时,基本都用串口输出信息。

adolphcocker 发表于 2024-8-5 11:00

内存使用:由于单片机内存有限,需要优化内存使用,避免内存溢出。
缓冲区:printf和scanf通常使用缓冲区来提高效率。需要实现缓冲区管理,并在必要时刷新缓冲区。
硬件限制:需要考虑单片机的硬件限制,如波特率、I/O口数量等。

cemaj 发表于 2024-8-5 12:14

printf函数通常用于输出格式化的数据到串口。为了在单片机中使用printf,需要重定向标准输出流stdout到串口。这通常涉及到重定义fputc函数,因为printf底层是通过调用fputc来输出每个字符的。

everyrobin 发表于 2024-8-5 16:13

这两个函数的实现通常依赖于硬件支持的串行通信接口

chenci2013 发表于 2024-8-5 18:08

由于单片机的内存限制,需要对printf函数的代码进行优化,去除不必要的功能和冗余代码。

hilahope 发表于 2024-8-5 20:27

#include "stm32f1xx_hal.h"

// 输出函数,用于发送字符到USART
void USART_PutChar(char c)
{
    HAL_StatusTypeDef status;

    status = HAL_UART_Transmit(&huart1, (uint8_t*)&c, 1, HAL_MAX_DELAY);
    if (status != HAL_OK)
    {
      // 错误处理
    }
}

// 输出字符串到USART
void USART_Puts(const char *str)
{
    while (*str)
    {
      USART_PutChar(*str++);
    }
}

// 自定义printf函数
int printf(const char *format, ...)
{
    va_list args;
    char buffer; // 假设输出缓冲区大小
    int len = 0;

    va_start(args, format);
    len = vsnprintf(buffer, sizeof(buffer), format, args);
    va_end(args);

    USART_Puts(buffer);
    return len;
}

beacherblack 发表于 2024-8-5 22:14

// 输入函数,用于从USART接收字符
char USART_GetChar(void)
{
    char c;
    HAL_StatusTypeDef status;

    status = HAL_UART_Receive(&huart1, (uint8_t*)&c, 1, HAL_MAX_DELAY);
    if (status != HAL_OK)
    {
      // 错误处理
    }
    return c;
}

// 自定义scanf函数
int scanf(const char *format, ...)
{
    va_list args;
    char input; // 假设输入缓冲区大小
    int len = 0;
    int pos = 0;

    va_start(args, format);

    while (*format && pos < sizeof(input) - 1)
    {
      if (*format == '%')
      {
            format++; // 跳过'%'
            switch (*format)
            {
                case 'd':
                  // 读取整数
                  while (isdigit((c = USART_GetChar())))
                  {
                        input = c;
                  }
                  sscanf(input, "%d", va_arg(args, int *));
                  break;
                case 's':
                  // 读取字符串
                  while ((c = USART_GetChar()) != '\n' && c != EOF)
                  {
                        input = c;
                  }
                  input = '\0';
                  va_arg(args, char *);
                  break;
                default:
                  // 忽略未知格式符
                  break;
            }
      }
      else
      {
            // 普通字符
            input = USART_GetChar();
      }
      format++;
    }

    input = '\0';
    va_end(args);

    return pos;
}

mattlincoln 发表于 2024-8-6 11:41

#include <stdio.h>
#include <stdarg.h>

// 假设UART_HandleTypeDef huart1是你的串口句柄
extern UART_HandleTypeDef huart1;

// 重定义fputc函数
int fputc(int ch, FILE *f) {
    // 将字符通过串口发送出去
    HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
    return ch;
}

int main(void) {
    // 初始化串口
    // ...

    // 使用printf输出信息
    printf("Hello, World!\r\n");

    while (1) {
      // 主循环
    }
}

ulystronglll 发表于 2024-8-6 13:17

#include <stdarg.h>

// 发送一个字符到指定的输出设备(例如串口)
void uart_putchar(char c) {
    // 在这里实现将字符发送到串口的代码
}

// 简化版的 printf 函数
void my_printf(const char *format,...) {
    va_list args;
    va_start(args, format);

    while (*format) {
      if (*format == '%') {
            format++;
            switch (*format) {
                case 'd':// 整数
                {
                  int num = va_arg(args, int);
                  char str;
                  int len = sprintf(str, "%d", num);
                  for (int i = 0; i < len; i++) {
                        uart_putchar(str);
                  }
                  break;
                }
                case 's':// 字符串
                {
                  char *str = va_arg(args, char *);
                  while (*str) {
                        uart_putchar(*str++);
                  }
                  break;
                }
                // 可以根据需要添加更多的格式支持
            }
      } else {
            uart_putchar(*format);
      }
      format++;
    }

    va_end(args);
}

int main() {
    my_printf("Hello, %d!\n", 123);
    return 0;
}

plsbackup 发表于 2024-8-6 15:45

将printf函数的输出重定向到单片机的某个外设,比如UART(串口),这样可以通过串口将数据发送到PC或其他设备。

linfelix 发表于 2024-8-6 17:22

#include <stdarg.h>
#include "uart.h" // 假设有一个UART库用于发送数据

void my_putchar(char c) {
    uart_send_byte(c); // 将字符发送到UART
}

void my_printf(const char *format, ...) {
    va_list args;
    va_start(args, format);

    while (*format) {
      if (*format == '%') {
            format++;
            switch (*format) {
                case 's': {
                  char *str = va_arg(args, char*);
                  while (*str) {
                        my_putchar(*str++);
                  }
                  break;
                }
                case 'c': {
                  char c = (char)va_arg(args, int);
                  my_putchar(c);
                  break;
                }
                default:
                  my_putchar('%');
                  my_putchar(*format);
                  break;
            }
      } else {
            my_putchar(*format);
      }
      format++;
    }

    va_end(args);
}

mnynt121 发表于 2024-8-6 19:50

通常不直接提供标准C库中的printf和scanf函数。

janewood 发表于 2024-8-6 21:33

printf和scanf函数的实现可能需要占用较多的内存和CPU资源,因此在资源受限的单片机上使用时需要谨慎。

pixhw 发表于 2024-8-8 09:48

在嵌入式系统中,特别是单片机环境中,并没有标准的控制台,因此需要对这些函数进行特殊的实现,以便通过串口或其他通信接口与外部设备进行交互。
页: [1] 2
查看完整版本: 对printf和scanf的实现