由于STM8单片机本身内存比较小,而系统自带的printf()函数又比较占据空间,所以在稍微大一点的工程中有时候一使用 printf() 函数就会导致单片机内存不足,于是想着能不能自己写一个比较小的函数来实现类似printf()函数的功能。经过网上查找资料和总结终于找到了一个占用内存比较小,又能实现串口打印功能的方法。

  现在将自己的方法分享出来,这里使用 STM8S003F3P6单片机测试。
  首先新建一个工程,专门用来测试串口功能。

  串口部分相关代码如下:

//串口
void Uart1_IO_Init( void )
{PD_DDR |= ( 1 << 5 );                       //输出模式 TXDPD_CR1 |= ( 1 << 5 );                       //推挽输出PD_DDR &= ~( 1 << 6 );                      //输入模式 RXDPD_CR1 &= ~( 1 << 6 );                      //浮空输入
}
//波特率最大可以设置为38400
void Uart1_Init( unsigned int baudrate )
{unsigned int baud;baud = 16000000 / baudrate;Uart1_IO_Init();UART1_CR1 = 0;      //禁止发送和接收UART1_CR2 = 0;      //8 bitUART1_CR3 = 0;      //1 stopUART1_BRR2 = ( unsigned char )( ( baud & 0xf000 ) >> 8 ) | ( ( unsigned char )( baud & 0x000f ) );UART1_BRR1 = ( ( unsigned char )( ( baud & 0x0ff0 ) >> 4 ) );UART1_CR2_bit.REN = 1;                      //接收使能UART1_CR2_bit.TEN = 1;                      //发送使能UART1_CR2_bit.RIEN = 1;                     //接收中断使能
}
//阻塞式发送函数
void SendChar( unsigned char dat )
{while( ( UART1_SR & 0x80 ) == 0x00 );       //发送数据寄存器空UART1_DR = dat;
}
//发送字符串
void SendString( unsigned char* s )
{while( 0 != *s ){SendChar( *s );s++;}
}
//接收中断函数 中断号18
#pragma vector = 20                             // IAR中的中断号,要在STVD中的中断号上加2
__interrupt void UART1_Handle( void )
{unsigned char res = 0;UART1_SR &= ~( 1 << 5 );                    //RXNE 清零res = UART1_DR;
}

  主程序代码如下:

void main( void )
{__asm( "sim" );                             //禁止中断SysClkInit();delay_init( 16 );LED_GPIO_Init();Uart1_IO_Init();Uart1_Init( 9600 );__asm( "rim" );                             //开启中断while( 1 ){}
}

  编译代码之后,在map文件中查看代码大小。


  可以看到此时串口占用代码大小只有100个字节左右,占用代码量很小,接下来在主程序中使用printf()函数打印数据。

  在main()函数中使用printf()函数打印字符串,这里要注意一点,要使用printf()函数时,需要在串口中实现 putchar( ) 函数,因为printf( )函数最终会调用这个putchar( ) 函数打印字符。putchar( ) 函数和SendChar( )函数的功能其实是一样的。

int putchar( int ch )
{while( !( UART1_SR & 0X80 ) );              //循环发送,直到发送完毕UART1_DR = ( u8 ) ch;return ch;
}

  通过串口助手可以看到字符串可以正常打印了,此时再次查看map文件。

  串口文件的字节数变成了110多个,比原来多了10个,但是总的字节数由六百多个变成了两千多个,翻了好几倍。这里只使用了一条打印语句,内存占用多了2K多。

  下面不使用系统自带的这个printf( )函数了,重新自己实现一个printf( ) 函数。在网上找到一个使用最多的方法。

#include "stdarg.h"
#include "stdio.h"
#include "string.h"//自定义串口 的printf 函数
char uart_buf[50];
void uart_printf( char* fmt, ... )  //无法列出传递函数的所有实参的类型和数目时,可以用省略号指定参数表
{u16 i, j;va_list ap;          //va_list 是一个字符指针,可以理解为指向当前参数的一个指针,取参必须通过这个指针进行。va_start( ap, fmt ); //va_start函数来获取参数列表中的参数,使用完毕后调用va_end()结束vsprintf( ( char* )uart_buf, fmt, ap );   // 把生成的格式化的字符串存放在这里va_end( ap );i = strlen( ( const char* )uart_buf );          //此次发送数据的长度for( j = 0; j < i; j++ )                                                  //循环发送数据{while( ( UART1_SR & 0x80 ) == 0x00 );       //发送数据寄存器空UART1_DR = uart_buf[j];}
}

  使用可变参数的原理,通过调用系统 vsprintf() 函数来打印。

  通过调用自定义的 uart_printf()函数来打印字符串,通过串口助手可以看到,字符串也能正常打印。下面通过map文件查看文件占用内存大小。

  通过map文件中的内容可以看到,这个方法比直接调用系统的printf()函数占用的空间还大。可以网上的这个方法也不好。于是又重新找了一个方法。

#include "stdarg.h"
#include "stdio.h"
/*功能:将int型数据转为2,8,10,16进制字符串参数:value --- 输入的int整型数str --- 存储转换的字符串radix --- 进制类型选择注意:8位单片机int字节只占2个字节
*/
char *int2char( int value, char *str, unsigned int radix )
{char list[] = "0123456789ABCDEF";unsigned int tmp_value;int i = 0, j, k = 0;if ( NULL == str ){return NULL;}if ( 2 != radix && 8 != radix && 10 != radix && 16 != radix ){return NULL;}if ( radix == 10 && value < 0 ){//十进制且为负数tmp_value = ( unsigned int )( 0 - value );str[i++] = '-';k = 1;}else{tmp_value = ( unsigned int )value;}//数据转换为字符串,逆序存储do{str[i ++] = list[tmp_value % radix];tmp_value /= radix;}while( tmp_value );str[i] = '\0';//将逆序字符串转换为正序char tmp;for ( j = k; j < ( i + k ) / 2; j++ ){tmp = str[j];str[j] = str[i - j - 1 + k];str[i - j - 1 + k] = tmp;}return str;
}/*功能:将double型数据转为字符串参数:value --- 输入的double浮点数str --- 存储转换的字符串eps --- 保留小数位选择,至少保留一个小数位,至多保留4个小数位注意:8位单片机int字节只占2个字节
*/
void flaot2char( double value, char *str, unsigned int eps )
{unsigned int integer;double decimal;char list[] = "0123456789";int i = 0, j, k = 0;//将整数及小数部分提取出来if ( value < 0 ){decimal = ( double )( ( ( int )value ) - value );integer = ( unsigned int )( 0 - value );str[i ++] = '-';k = 1;}else{integer = ( unsigned int )( value );decimal = ( double )( value - integer );}//整数部分数据转换为字符串,逆序存储do{str[i ++] = list[integer % 10];integer /= 10;}while( integer );str[i] = '\0';//将逆序字符串转换为正序char tmp;for ( j = k; j < ( i + k ) / 2; j++ ){tmp = str[j];str[j] = str[i - j - 1 + k];str[i - j - 1 + k] = tmp;}//处理小数部分if ( eps < 1 || eps > 4 ){eps = 4;}//精度问题,防止输入1.2输出1.19等情况double pp = 0.1;for ( j = 0; j <= eps; j++ ){pp *= 0.1;}decimal += pp;while ( eps ){decimal *= 10;eps --;}int tmp_decimal = ( int )decimal;str[i ++] = '.';k = i;//整数部分数据转换为字符串,逆序存储do{str[i ++] = list[tmp_decimal % 10];tmp_decimal /= 10;}while( tmp_decimal );str[i] = '\0';//将逆序字符串转换为正序for ( j = k; j < ( i + k ) / 2; j++ ){tmp = str[j];str[j] = str[i - j - 1 + k];str[i - j - 1 + k] = tmp;}str[i] = '\0';
}void uart_printf( char * Data, ... )
{const char *s;int d;char buf[16];unsigned char txdata;va_list ap;va_start( ap, Data );while ( * Data != 0 ){if ( * Data == 0x5c ){switch ( *++Data ){case 'r':txdata = 0x0d;SendChar( txdata );Data ++;break;case 'n':txdata = 0x0a;SendChar( txdata );Data ++;break;default:Data ++;break;}}else if ( * Data == '%' ){switch ( *++Data ){case 's':s = va_arg( ap, const char * );for ( ; *s; s++ ){SendChar( *( ( unsigned char * )s ) );}Data++;break;case 'd':d = va_arg( ap, int );int2char( d, buf, 10 );for ( s = buf; *s; s++ ){SendChar( *( ( unsigned char * )s ) );}Data++;break;case 'x':{d = va_arg( ap, int );int2char( d, buf, 16 );for ( s = buf; *s; s++ ){SendChar( *( ( unsigned char * )s ) );}Data++;break;}case 'f':{double num = va_arg(ap, double);flaot2char(num, buf, 4);for (s = buf; *s; s++){SendChar(*((unsigned char *)s));}Data++;break;}default:Data++;break;}}else{SendChar( *( ( unsigned char * )Data ) );Data++;}}
}

  这个方法的实现原理是分别将整形数据和浮点型数据转换为字符,然后再通过串口将字符打印出来。转换整形和字符型数据时,分别使用了两个函数来实现。

  下面测试这个方法
  串口数据可以正常输出。接下来在map文件中查看占用内存大小。

  可以看到这个方法总的占空空间也很大,其中串口文件的占用大小也多了1K多。那么能不能优化一下串口中的代码。

  通过对上面的代码分析可以看出,串口打印函数主要调用了两个函数,一个而是整形数据转字符串,一个是浮点型数据转字符串,而在平时调试中使用浮点数比较少,那么就可以将浮点数相关的代码屏蔽掉。

   在 uart_printf( )函数中将浮点打印相关代码屏掉,这样只需要使用一个整形转字符串的函数就可以了。

   继续测试打印功能,可以看到串口打印功能正常,此时查看map文件中占用空间大小。


   在map文件中可以看到串口的代码占用空间缩小了一半,代码总的占用空间1K多,是不使用打印功能的2倍,相当于这次自定义的 printf()函数占用空间大概 600多个字节。

   通过map文件中可以看出,主要是uart.c这个文件占用的空间变大了,那么想要减小代码的空间占用,只需要优化uart.c这个文件中的代码就行了。通过对代码分析,可以优化部分基本就是switch 语句的分支了,由于自己在调试代码的时候大多数使用的只是整数打印功能,也就是基本只使用到 “%d”,其他的很少用,所以可以将switch 语句其他的分支都注释掉。优化后的代码为:

void uart_printf( char * Data, ... )
{const char *s;int d;char buf[16];unsigned char txdata;va_list ap;va_start( ap, Data );while ( * Data != 0 ){if ( * Data == 0x5c ){switch ( *++Data ){case 'r':txdata = 0x0d;SendChar( txdata );Data ++;break;case 'n':txdata = 0x0a;SendChar( txdata );Data ++;break;default:Data ++;break;}}else if ( * Data == '%' ){switch ( *++Data ){/*              case 's':s = va_arg( ap, const char * );for ( ; *s; s++ ){SendChar( *( ( unsigned char * )s ) );}Data++;break;
*/case 'd':d = va_arg( ap, int );int2char( d, buf, 10 );for ( s = buf; *s; s++ ){SendChar( *( ( unsigned char * )s ) );}Data++;break;
/*                case 'x':{d = va_arg( ap, int );int2char( d, buf, 16 );for ( s = buf; *s; s++ ){SendChar( *( ( unsigned char * )s ) );}Data++;break;}case 'f':{double num = va_arg(ap, double);flaot2char(num, buf, 4);for (s = buf; *s; s++){SendChar(*((unsigned char *)s));}Data++;break;}
*/default:Data++;break;}}else{SendChar( *( ( unsigned char * )Data ) );Data++;}}
}

   优化代码之后,测试一下字符串打印和整形打印的功能。

   通过串口助手可以看到,字符串和整数打印功能都正常,下面看一下map文件中占用空间大小。

   在map文件中可以看到,注释掉switch 语句的几个分支之后,代码少了一百多个字节。现在总代码量是1K多。可见这个代码基本已经达到最优了,没有多大的优化空间了。但是和前面的方法相比这个方法占用代码量小多了。这个方法相对来说还是比较好的。

   在以后工程中就可以使用这个函数进行串口打印了,对于资源比较少的单片机来说,每一片内存都是寸土寸金,能少占用就尽量少占用。

代码工程下载地址: https://download.csdn.net/download/qq_20222919/54824408

在STM8单片机中自己实现 printf()函数功能相关推荐

  1. 延时作用是什么意思c语言,单片机中C语言延时函数

    原标题:单片机中C语言延时函数 延时程序计算2009-11-02 22:15单片机C语言延时程序用C语言写出来程序非常的简练,它是一种模块化的语言,一种比汇编更高级的语言,但是就是这样一种语言也还是有 ...

  2. replace c语言,C中如何实现replace函数功能

    C中如何实现replace函数功能 (2012-04-10 04:59:49) 标签: replace 如何 杂谈 C中如何实现replace函数功能各位大侠,由于数据库中无replace函数,因此要 ...

  3. 【C语言】之实现 printf 函数功能

    /************************************************ 文件名: myPrintf.c* 文件功能: 使用putchar函数模拟printf函数的功能* 编 ...

  4. 51单片机实现类似于Arduino中micros()、millis()函数功能并用于接收ppm信号

    最近想要给小车添加航模遥控器遥控功能,毕竟航模遥控器拿在手里还是很有质感,手感很不错的.选择使用ppm信号,ppm信号相关参考:PPM相关 Arduino中micros()函数返回一个单位为无符号长整 ...

  5. 单片机中C语言延时函数

    单片机C语言延时程序计算2009-11-02 22:15单片机C语言延时程序用C语言写出来程序非常的简练,它是一种模块化的语言,一种比汇编更高级的语言,但是就是这样一种语言也还是有它不足之处:它的延时 ...

  6. 单片机c语言中延时函数的作用,单片机中C语言延时函数

    单片机C语言延时程序计算2009-11-02 22:15单片机C语言延时程序用C语言写出来程序非常的简练,它是一种模块化的语言,一种比汇编更高级的语言,但是就是这样一种语言也还是有它不足之处:它的延时 ...

  7. c语言程序代码中的间隔,printf()函数输出后 默认的间隔是多少

    7楼那么输出应该就可以了,不过楼主显然不是这个意思,问题的关键在于普通输出都是直接%d而它用%2d是不是这个2搞的!? ----------------解决方案-------------------- ...

  8. STM8学习笔记---串口printf函数的实现

    在使用单片机的时候,串口是要经常使用的功能,特别是在调试代码的过程中,经常需要使用串口打印出某些变量值,来判断程序执行流程是否正常.但是单片机默认情况下没有printf函数,如果需要使用printf函 ...

  9. stm8单片机例程下载链接

    在这篇文章中将自己上传的资源做一个下载链接的目录方便查找下载 STM8单片机编码器使用示例 STM8单片机ADC连续扫描模式并开启模拟看门狗功能 STM8单片机ADC单次扫描模式并开启模拟看门狗功能 ...

最新文章

  1. php phpqueey内存泄露,phpQuery 占用内存过多的处理方法
  2. 阅读《Android 从入门到精通》(29)——四大布局
  3. 中国楼宇自控系统发展规划现状及未来前景预测报告2022-2028年版
  4. discuz php如何开发,Discuzx2开发标准流程
  5. zone.js在bootstrap阶段对window对象里一些标准方法的注入逻辑
  6. excel 表格导入 - java 实现
  7. Mongodb使用总结
  8. 章节十一、9-操作隐藏元素
  9. [引用]关于C#操作INI文件的总结
  10. 【优化分类】基于matlab粒子群算法优化支持向量机分类(多输入多分类)【含Matlab源码 1559期】
  11. linux 怎么添加文件类型,如何在Linux/Unix上添加基于文件类型的文件扩展名?
  12. 4款最好的Android设备HTML编辑器
  13. 【笔记】Robust High-Resolution Video Matting with Temporal Guidance
  14. JAVA实现成绩统计之及格率和优秀率
  15. linux下解压rar和7z压缩文件
  16. Cadence License破解失败解决办法
  17. 信管117115李静JSP期末报告
  18. Comet OJ - Contest #10 沉鱼落雁
  19. SAP PP配置详解之六:物料需求计划
  20. 逻辑备份数据导入原分区表遇到的那些事儿

热门文章

  1. 变量定义类型长度的理解
  2. {{jQuery源码分析}}jQuery对象初始化的多种传参数形式
  3. JavaScript遇到浏览器不兼容与解决方案
  4. ADO.NET远程访问Access数据库的连接字符串
  5. 如何让 MSN 与应用系统紧密集成起来?
  6. JavaScript基础(二)-类
  7. 01-python进阶-拾遗
  8. WebService学习笔记系列(四)
  9. Java中的锁(转)
  10. 唯品会高级副总裁 唐倚智:电商精细化运营