历史上的今天
返回首页

历史上的今天

今天是:2024年12月27日(星期五)

正在发生

2021年12月27日 | 在STM8单片机中自己实现 printf()函数功能

2021-12-27 来源:eefocus

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


  现在将自己的方法分享出来,这里使用 STM8S003F3P6单片机测试。

  首先新建一个工程,专门用来测试串口功能。


  串口部分相关代码如下:


//串口

void Uart1_IO_Init( void )

{

    PD_DDR |= ( 1 << 5 );                       //输出模式 TXD

    PD_CR1 |= ( 1 << 5 );                       //推挽输出

    

    PD_DDR &= ~( 1 << 6 );                      //输入模式 RXD

    PD_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 bit

    UART1_CR3 = 0;      //1 stop

    UART1_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] = '';

    //将逆序字符串转换为正序

    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] = '';

    //将逆序字符串转换为正序

    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] = '';

    //将逆序字符串转换为正序

    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] = '';

}


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 ++;

推荐阅读

史海拾趣

AUK Contractors Co Ltd公司的发展小趣事

近年来,电子行业面临着原材料成本上升、环保要求提高等多重挑战。AUK Contractors Co Ltd积极应对这些挑战,通过优化生产流程、采用环保材料等方式降低成本、提高效益。同时,公司还加大了对新能源、智能制造等领域的研发投入,实现了从传统电子制造向高科技领域的转型升级。

ELESTA GmbH公司的发展小趣事

2013年,ELESTA公司更名为ELESTA GmbH,这一品牌重塑标志着公司进入了新的发展阶段。同时,ELESTA也开始实施全球化战略,积极拓展国际市场,通过不断提升产品质量和服务水平,赢得了全球客户的信赖和认可。

DB Lectro Inc公司的发展小趣事

作为一家有社会责任感的企业,DB Lectro Inc始终关注绿色制造和可持续发展。公司在生产过程中采用了环保材料和节能技术,降低了对环境的影响。同时,公司还积极参与公益活动和慈善事业,为社会做出了积极贡献。这些举措不仅提升了公司的品牌形象和社会声誉,还为公司赢得了更多客户和合作伙伴的信任和支持。

ESPROS [Espros Photonics corp]公司的发展小趣事

随着技术的不断进步和市场需求的增长,ESPROS不断扩展其产品线。例如,公司发布了160 x 60像素的epc635 3D ToF传感器芯片,进一步完善了其3D ToF传感器产品线。此外,ESPROS还发布了320 x 240像素的epc660 3D ToF传感器,满足了市场对QVGA分辨率的需求。这些新产品的推出,不仅丰富了ESPROS的产品线,也进一步巩固了其在光电传感器领域的领先地位。

Gigavac LLC公司的发展小趣事

ESPROS的ToF传感器技术已成功应用于多个领域,包括无人机飞行避障及定高、人机交互和手势识别、机器人定位和智能导航、人体检测和物体探测、工业自动化以及汽车自动驾驶等。这些应用不仅展示了ESPROS技术的广泛适用性,也证明了其在光电传感器领域的强大实力。

DIOTECH公司的发展小趣事

随着全球环保意识的不断提高,DIOTECH公司也开始关注绿色环保和可持续发展问题。公司投入大量研发资源,成功开发出了一系列绿色电子产品,这些产品在生产过程中采用了环保材料和生产工艺,并且具有低功耗、长寿命等特点。这些产品的推出不仅满足了市场对环保产品的需求,也提升了公司的社会责任感和品牌形象。

问答坊 | AI 解惑

什么是FTTH(光纤到户)?

说到FTTH,首先就必须谈到光纤接入。光纤接入是指局端与用户之间完全以光纤作为传输媒体。光纤接入可以分为有源光接入和无源光接入。光纤用户网的主要技术是光波传输技术。目前光纤传输的复用技术发展相当快,多数已处于实用化。根据光纤深入用户的 ...…

查看全部问答>

ARM_操作指令学习(仅供参考)

介绍了ARM—操作指令集及汇编…

查看全部问答>

十万火急!

要做一个程序,用c编程,要实现对服务器上面的共享文件夹的某一个具体文件的访问,读取数据给主程序使用。。。。 怎么实现啊,给个思路啊。。。 关键: 怎么实现对目标的访问: 用什么传输数据。 怎么读取需要的数据。…

查看全部问答>

无线电发射设备型号核准检测的检验依据(含参考标准)

无线电发射设备型号核准检测的检验依据(含参考标准)…

查看全部问答>

新闻早班车:最小级别的肖特基二极管

Torex推出了世界上最小级别的 0603 尺寸超小型肖特基二极管。 XBS013V1DR-G和XBS013R1DR-G是超小型封装USP-2B01 (0.6 x 0.3 x h0.3mm)的肖特基二极管。超小型封装有利于缩小实装面积、节省空间。用于便携式仪器等小功率电路中,特别是反向漏电流 ( ...…

查看全部问答>

请问如何获取打印spool的文件名

    jobID好像并不是文件名,请问如何获取打印spool的文件名呢,请各位大侠帮忙…

查看全部问答>

求教LPC214X的ISP处理器命令问题

本人在LPC2148的ISP模式下,PC机通过串口向LPC2148发送ISP命令,返回乱码,而发送\"?\"就会返回\"ynchronized\"。哪位能说一下哪个地方出问题了?ISP模式下控制编写Flash需要注意什么? [ 本帖最后由 hd1006 于 2012-8-30 18:03 编辑 ]…

查看全部问答>

触摸键盘的演示

对于前期发表的触摸按键的视频演示…

查看全部问答>

QC-12864B

有没有QC-12864B相关程序,我在单片机就是不能出现字,不知道怎么写:写的都不行:谁有程序发给我一份;谢谢!…

查看全部问答>