当前位置: 首页 > news >正文

单片机如何用C语言重写vfprintf

在单片机中,标准库函数 vfprintf 可能不可用或占用过多资源(如内存或代码空间),因此我们可以通过自定义实现一个轻量级的 vfprintf 函数,专门用于单片机的串口输出。以下是一个用 C 语言重写 vfprintf 的示例,假设我们需要将格式化字符串通过串口发送。


1. 目标

  • 实现一个轻量级的 vfprintf 函数,支持基本的格式化输出(如 %d%s%c%x 等)。

  • 将格式化后的字符串通过串口发送。


2. 实现代码

#include <stdarg.h>
#include <stdint.h>

// 假设 USART_SendChar 是一个发送单个字符到串口的函数
void USART_SendChar(char ch) {
    usart_data_transmit(USART0, (uint8_t)ch);
	while (RESET == usart_flag_get(USART0, USART_FLAG_TBE));
    // 例如,STM32 中可以使用 HAL_UART_Transmit
}

// 自定义的 vfprintf 函数
void my_vfprintf(const char *format, va_list args) {
    char buffer[20];  // 用于存储转换后的数字
    const char *p = format;

    while (*p != '\0') {
        if (*p == '%') {
            p++;  // 跳过 '%'
            switch (*p) {
                case 'd': {
                    int value = va_arg(args, int);
                    int i = 0;
                    if (value < 0) {
                        USART_SendChar('-');
                        value = -value;
                    }
                    do {
                        buffer[i++] = (value % 10) + '0';
                        value /= 10;
                    } while (value > 0);
                    while (i > 0) {
                        USART_SendChar(buffer[--i]);
                    }
                    break;
                }
                case 's': {
                    char *str = va_arg(args, char *);
                    while (*str != '\0') {
                        USART_SendChar(*str++);
                    }
                    break;
                }
                case 'c': {
                    char ch = (char)va_arg(args, int);
                    USART_SendChar(ch);
                    break;
                }
                case 'x': {
                    unsigned int value = va_arg(args, unsigned int);
                    int i = 0;
                    do {
                        int digit = value % 16;
                        buffer[i++] = (digit < 10) ? (digit + '0') : (digit - 10 + 'A');
                        value /= 16;
                    } while (value > 0);
                    while (i > 0) {
                        USART_SendChar(buffer[--i]);
                    }
                    break;
                }
                case '%': {
                    USART_SendChar('%');
                    break;
                }
                default: {
                    // 不支持的格式,直接输出字符
                    USART_SendChar('%');
                    USART_SendChar(*p);
                    break;
                }
            }
        } else {
            USART_SendChar(*p);
        }
        p++;
    }
}

// 自定义的 printf 函数
void my_printf(const char *format, ...) {
    va_list args;
    va_start(args, format);
    my_vfprintf(format, args);
    va_end(args);
}

3. 代码解析

(1)USART_SendChar 函数
  • 这是一个假设的函数,用于将单个字符发送到串口。

  • 在实际项目中,需要根据具体的硬件平台实现该函数。例如,在 STM32 中可以使用 HAL_UART_Transmit

(2)my_vfprintf 函数
  • 这是自定义的 vfprintf 函数,支持以下格式化输出:

    • %d:输出整数。

    • %s:输出字符串。

    • %c:输出字符。

    • %x:输出十六进制数。

    • %%:输出百分号 %

  • 使用 va_list 和 va_arg 处理可变参数。

(3)my_printf 函数
  • 这是一个封装函数,用于调用 my_vfprintf

  • 通过 va_start 和 va_end 处理可变参数列表。


4. 使用示例

int main() {
    int num = -1234;
    char *str = "Hello, World!";
    char ch = 'A';
    unsigned int hex = 0xABCD;

    my_printf("Number: %d\n", num);
    my_printf("String: %s\n", str);
    my_printf("Character: %c\n", ch);
    my_printf("Hexadecimal: %x\n", hex);
    my_printf("Percent: %%\n");

    return 0;
}
输出结果:
Number: -1234
String: Hello, World!
Character: A
Hexadecimal: ABCD
Percent: %

5. 另外一种printf重映射方式

// 将库函数的Printf重定向到串口
int fputc(int ch, FILE* f)
{
	#ifdef USART1_OPEN
	usart_data_transmit(USART1, (uint8_t)ch);
	while (RESET == usart_flag_get(USART1, USART_FLAG_TBE));
	#else
	usart_data_transmit(USART0, (uint8_t)ch);
	while (RESET == usart_flag_get(USART0, USART_FLAG_TBE));
	#endif
	return ch;
}


 /* @brief 重定向c库函数getchar,scanf到USARTx*/
int fgetc(FILE *f)
{
    uint8_t ch = 0;
	#ifdef USART1_OPEN
    ch = usart_data_receive(USART1);
	#else
	ch = usart_data_receive(USART0);
	#endif
    return ch;
}

6. 优化与扩展

  • 如果需要支持更多格式化选项(如 %f%u%o 等),可以在 my_vfprintf 中添加相应的处理逻辑。

  • 如果单片机的 RAM 有限,可以减少 buffer 的大小,或者直接使用循环发送字符,避免使用缓冲区。

  • 如果需要更高的性能,可以将 USART_SendChar 替换为直接操作串口寄存器的代码。


7. 总结

  • 通过自定义 my_vfprintf 和 my_printf 函数,可以在单片机中实现轻量级的格式化输出。

  • 该实现避免了使用标准库的 vfprintf,减少了代码大小和内存占用,适合资源受限的单片机环境。

  • 根据具体需求,可以进一步扩展和优化该实现。

相关文章:

  • npm 执行安装报错
  • Final Cut Pro X for Mac fcpx音视频剪辑编辑
  • 项目设计之用户注册与登录
  • Unity HDR颜色、基础颜色、强度强度、HDR面板Intensity之间的相互转换
  • UDP协议 TCP协议(格式 超时重传 滑动窗口 拥塞控制...)
  • 如何收集 Kubernetes 集群的日志
  • 7V 至 30V 的超宽 VIN 输入范围,转换效率高达 96%的WD5030
  • Java中lombok的@Data注解【布尔类型】字段定义方式
  • 在使用 router-link 进行路由跳转时,A页面跳转到A页面,资源要重新加载吗
  • eBay日本站猫咪用品卖家数量激增60%,新机遇显现
  • Spring 无法解决循环依赖的 5 种场景
  • 嵌入式 ARM Linux 系统构成(3):根文件系统(Root File System)
  • Magento2根据图片文件包导入产品图片
  • 解决火绒启动时,报安全服务异常,无法保障计算机安全
  • 工具介绍《netcat》
  • 电脑如何拦截端口号,实现阻断访问?
  • 物联网设备数据割裂难题:基于OAuth2.0的分布式用户画像系统设计!格行代理是不是套路?2025有什么比较好的副业?低成本的创业好项目有哪些?
  • 股票交易所官方api接口有哪些?获取和使用需要满足什么条件
  • segment-anything分割万物python环境部署和实现
  • Seata:分布式事务的终极解决方案
  • 婚礼策划公司/关键词排名优化如何
  • 台州seo网站管理/百度入口提交
  • 男人和女人做羞羞的免费网站/灰色广告投放平台
  • 网站开发市场现在怎么样/国内十大搜索引擎
  • 沈阳网站定制开发/google play商店
  • 广州建站模板平台/怎样创建一个网站