找回密码
 立即注册
查看: 743|回复: 3

【串口收发框架】【printf重定向】【圆周率计算】

[复制链接]
  • TA的每日心情
    开心
    7 小时前
  • 签到天数: 112 天

    [LV.6]常住居民II

    25

    主题

    304

    回帖

    1037

    积分

    荣誉版主

    Cyber Hamster

    积分
    1037
    发表于 2023-10-10 23:53:58 | 显示全部楼层 |阅读模式
    本开源程序实现如题目所示的三个功能:

    1、串口收发框架
    使用硬件UART中断方式实现串口收发,中断方式相对查询方式和GPIO软件模拟的优点在此就不多赘述了;使用定时器实现串口接收超时复位,避免接收到错误数据或数据未发完就终止时,错误的状态始终保留,导致再发送正确的数据,正确的数据又和之前错误的数据拼接成一个错误的数据;收发缓存为全局变量,使用指针在中断函数中实现自动处理,不需要在主程序中轮询某些状态和标志,中断程序简单高效,全状态机控制,不会耽误大量时间,不使用软件延时。

    2、printf重定向:使用标准库stdio.h、stdarg.h实现变量格式化,printf重定向到UART输出,然后就可以愉快的使用printf了,缺点是和直接写数组相比会消耗更多code空间。

    3、圆周率计算:使用莱布尼茨法计算圆周率,用于printf输出变量,由于莱布尼茨法是收敛的无穷级数且偶数项为正奇数项为负,程序中保留上一次迭代结果并和本次迭代结果求平均值以加速收敛,实测454次迭代结果为3.141592,如不求相邻两次平均值,则需上万次迭代,且由于浮点数精度的限制,几万次迭代后误差会越来越大。

    单片机STC8G1K08A-8PIN,时钟频率11.0592MHz,UART1 9600bps 8N1,为便于观察,主程序中延迟50ms,串口默认为阻塞模式,即上一个数据包未发完又发新的数据包时会等待串口状态复位,即发送长度为0(Uart_Send_Lenth=0),该变量为全局变量,数据包发送完毕后在中断程序中清零表示数据包发送完毕,该模式在软件发送频率大于硬件发送频率时,软件发送频率会被拖慢,但不会发送错误的数据,注释掉while(Uart_Send_Lenth)即可改为覆盖模式,该模式上一个数据包未发完又发新的数据包时,新的数据包会覆盖掉原数据包且串口状态被重置,发送的数据包会被打断,但软件发送不会被拖慢,如在阻塞模式下没有达到预期速度或在覆盖模式下发送数据错误,请降低发送频率或提高波特率,该串口收发框架100%自行编写,无抄袭。

    如不想使用printf函数,直接写发送缓存,以下为范例程序:

                    T_Buffer[0]='T';
                    T_Buffer[1]='=';
                    T_Buffer[2]=Hex_to_Ascii[t%10000/1000];
                    T_Buffer[3]=Hex_to_Ascii[t%1000/100];
                    T_Buffer[4]='.';
                    T_Buffer[5]=Hex_to_Ascii[t%100/10];
                    T_Buffer[6]=Hex_to_Ascii[t%10];
                    T_Buffer[7]='C';
                    T_Buffer[8]=' ';
                    T_Buffer[9]='P';
                    T_Buffer[10]='=';
                    T_Buffer[11]=Hex_to_Ascii[p%1000000/100000];
                    T_Buffer[12]=Hex_to_Ascii[p%100000/10000];
                    T_Buffer[13]=Hex_to_Ascii[p%10000/1000];
                    T_Buffer[14]='.';
                    T_Buffer[15]=Hex_to_Ascii[p%1000/100];
                    T_Buffer[16]=Hex_to_Ascii[p%100/10];
                    T_Buffer[17]=Hex_to_Ascii[p%10];
                    T_Buffer[18]='K';
                    T_Buffer[19]='P';
                    T_Buffer[20]='a';
                    T_Buffer[21]=0x0d;
                    T_Buffer[22]=0x0a;
                    UART_Send(23);


    对发送缓存数组赋值后调用UART_Send(x)即可,x为发送长度。

    因没有使用串口接收功能,串口接收程序是个半成品,需用户按照具体协议编写解析程序,以下为范例程序:

    void Uart_Isr(void) interrupt 4
    {
            static unsigned int tp;
            unsigned int temp,lenth,i;
            if(RI)
            {
                    RI=0;
                    Uart_Start();
                    R_Buffer[RP]=SBUF;
                    if(RP==1)
                    {
                            temp=(R_Buffer[0]<<8)+R_Buffer[1];
                            if(temp>0x0200)
                            {
                                    R_Buffer[1]&=0x07;
                                    switch(R_Buffer[0])
                                    {
                                            case 'R':
                                                    Uart_Send_Iap(R_Buffer[1]);
                                                    Sector=0xFF;
                                            break;
                                            case 'W':
                                                    Sector=R_Buffer[1];
                                                    Iap_Erase_Sector(Sector*0x0200);
                                                    Uart_Printf("扇区%bd已擦除\xFD,请切换到HEX模式写入数\xFD据\r\n",Sector);
                                            break;
                                    }
                                    Uart_Stop();
                            }
                            else if(temp>2&&temp<=512)
                            {
                                    lenth=temp;
                            }
                    }
                    if(RP==lenth-1&&lenth>2&&Sector<8)
                    {
                            for(i=0;i<lenth;i++)
                            {
                                    Iap_Program_Byte(Sector*0x0200+i,R_Buffer);
                                    Uart_Start();
                            }
                            Uart_Stop();
                            Uart_Printf("扇区%bd写入%d字节\r\n",Sector,lenth);
                            Sector=0xFF;
                    }
                    else if(RP==R_Buffer_Len-1)
                    {
                            Uart_Stop();
                    }
                    else if(TR0)
                    {
                            RP++;
                    }
            }
            if(TI)
            {
                    TI=0;
                    if(Uart_Send_Lenth!=0)
                    {
                            SBUF=(T_Buffer[TP]);
                            TP++;
                    }
                    if(TP==Uart_Send_Lenth)
                    {
                            TP=0;
                            Uart_Send_Lenth=0;
                    }
            }
    }

    这是一个接收不固定长度数据包的程序,前两个字节为长度,最大长度512字节,如您也需要接收这么长的数据包,则需要修改接收缓存长度R_Buffer_Len大于数据包长度,注意接收到除了包尾的每个字节后都要调用Uart_Start()函数以重置超时定时器,在接收到期望的数据包(正确数据包或错误数据包)后调用Uart_Stop()函数立即重置状态即可,如不立即重置状态,在超时后也会自动重置状态,但在此期间又收到数据会发生数据接收错误。
    该例程包含汉字0xFD问题的解决方法供参考。

    以下是完整程序:

    /*----------------------------分割线----------------------------*/

    #include <STC8G.H>
    #include "define.h"
    #include <intrins.h>
    #include <string.h>
    #include <stdio.h>
    #include <stdarg.h>
    #define                RXD                P30
    #define                TXD                P31
    #define                FOSC                11059200UL
    #define                BAUD                9600UL
    #define                BRT                        (0x10000-FOSC/BAUD/4)
    #define                R_Buffer_Len        64        //Uart1接收缓存长度
    #define                T_Buffer_Len        64        //Uart1发送缓存长度

    unsigned char                RP;                                                        //Uart1接收指针
    unsigned char                TP;                                                        //Uart1发送指针
    unsigned char                Uart_Send_Lenth;                        //Uart1发送长度
    unsigned char xdata        R_Buffer[R_Buffer_Len];                //Uart1接收缓存
    unsigned char xdata        T_Buffer[T_Buffer_Len];                //Uart1发送缓存

    ///*----------------------------延时10us@STC-Y6@11.0592MHz----------------------------*/
    //void Delay_10us(void)
    //{
    //        unsigned char i;
    //        i=35;
    //        while(--i);
    //}

    ///*----------------------------延时x10us----------------------------*/
    //void Delay_x10us(unsigned char x)
    //{
    //        while(x--)
    //                Delay_10us();
    //}

    /*----------------------------延时10ms@STC-Y6@11.0592MHz----------------------------*/
    void Delay_10ms(void)
    {
            unsigned char i,j;
            _nop_();
            _nop_();
            i=144;
            j=157;
            do
            {
                    while(--j);
            }while(--i);
    }

    /*----------------------------延时x10ms----------------------------*/
    void Delay_x10ms(unsigned char x)
    {
            while(x--)
                    Delay_10ms();
    }

    void UART_Send(unsigned int x)
    {
            TP=0;
            Uart_Send_Lenth=x;
            TI=1;
    }

    void Uart_Printf(unsigned char *v,...)
    {
            va_list ap;
            va_start(ap,v);
            while(Uart_Send_Lenth);
            UART_Send(vsprintf(T_Buffer,v,ap));
            va_end(ap);
    }

    void Init(void)
    {
            P_SW2|=EAXFR;
            
            P3M0=0x00;
            P3M1=0x00;
            
            AUXR=0x40;                //设置定时器0时钟为12T模式,设置定时器1为1T模式,设置定时器1为波特率发生器
            TMOD=0x01;                //设置定时器0为16位不自动重装载模式,设置定时器1为16位自动重装载模式
            TL0=0x00;                //设置定时器0初始值(5ms)
            TH0=0xEE;                //设置定时器0初始值(5ms)
            TF0=0;                        //清除TF0中断标志位
            ET0=1;                        //启用定时器0中断
            
            SCON=0x50;                //设置UART1模式为8位数据可变波特率
            TL1=BRT;                //设置UART1波特率
        TH1=BRT>>8;                //设置UART1波特率
            TR1=1;                        //打开定时器1
            ES=1;                        //启用UART1中断
            
            EA=1;                        //启用总中断
    }

    void main(void)
    {
            unsigned int x;
            float pi,pi_last;
            Init();
            x=0;
            pi=0.0F;
            while(1)
            {
                    x%2?(pi-=4.0F/((float)x*2.0F+1.0F)):(pi+=4.0F/((float)x*2.0F+1.0F));
                    Uart_Printf("x=%U pi=%1.6F\r\n",x,(pi+pi_last)/2);
                    pi_last=pi;
                    if(x==454)
                            while(1);
                    x++;
                    Delay_x10ms(5);
            }
    }

    void Uart_Start(void)
    {
            TL0=0x00;
            TH0=0xEE;
            TR0=1;
    }

    void Uart_Stop(void)
    {
            TR0=0;
            TL0=0x00;
            TH0=0xEE;
            RP=0;
            memset(R_Buffer,0x00,sizeof R_Buffer);
    }

    void Timer0_Isr(void) interrupt 1
    {
            Uart_Stop();
    }

    void Uart_Isr(void) interrupt 4
    {
            if(RI)
            {
                    RI=0;
                    Uart_Start();
                    R_Buffer[RP]=SBUF;
                    if(RP==R_Buffer_Len-1)
                    {
                            Uart_Stop();
                    }
                    else if(TR0)
                    {
                            RP++;
                    }
            }
            if(TI)
            {
                    TI=0;
                    if(Uart_Send_Lenth!=0)
                    {
                            SBUF=(T_Buffer[TP]);
                            TP++;
                    }
                    if(TP==Uart_Send_Lenth)
                    {
                            TP=0;
                            Uart_Send_Lenth=0;
                    }
            }
    }

    /*----------------------------分割线----------------------------*/

    完整工程见附件:


    printf.zip (13.51 KB, 下载次数: 46)


    1 喜欢他/她就送朵鲜花吧,赠人玫瑰,手有余香!
    (=・ω・=)
    回复 送花

    使用道具 举报

  • TA的每日心情
    开心
    7 小时前
  • 签到天数: 112 天

    [LV.6]常住居民II

    25

    主题

    304

    回帖

    1037

    积分

    荣誉版主

    Cyber Hamster

    积分
    1037
     楼主| 发表于 2023-10-11 00:02:04 | 显示全部楼层
    截图202310110002018071.jpg
    (=・ω・=)
    回复 支持 反对 送花

    使用道具 举报

  • TA的每日心情
    开心
    7 小时前
  • 签到天数: 112 天

    [LV.6]常住居民II

    25

    主题

    304

    回帖

    1037

    积分

    荣誉版主

    Cyber Hamster

    积分
    1037
     楼主| 发表于 2023-10-11 00:27:23 来自手机 | 显示全部楼层
    本帖最后由 DebugLab 于 2023-10-11 19:45 编辑

    注意:实测定时器在16位自动重载模式时,关闭定时器再打开定时器不会自动重载,只有溢出时才会自动重载,且写入重载值并不会更新计数器,而是写入影子寄存器,所以串口接收超时定时器要使用16位不自动重载模式,写入值就会立即更新计数器。
    (=・ω・=)
    回复 支持 反对 送花

    使用道具 举报

  • TA的每日心情
    开心
    6 小时前
  • 签到天数: 160 天

    [LV.7]常住居民III

    17

    主题

    370

    回帖

    1313

    积分

    荣誉版主

    积分
    1313
    发表于 2023-11-26 10:12:46 | 显示全部楼层
    准备用STC32F54的FPMU硬件进行万次迭代,看看误差是否越来越大。验证FPMU硬件的计算精度。
    回复 支持 反对 送花

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

    GMT+8, 2024-5-5 18:01 , Processed in 0.062256 second(s), 44 queries .

    Powered by Discuz! X3.5

    © 2001-2024 Discuz! Team.

    快速回复 返回顶部 返回列表