找回密码
 立即注册
查看: 2810|回复: 7

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

[复制链接]
  • 打卡等级:以坛为家II
  • 打卡总天数:423
  • 最近打卡:2025-05-01 07:10:25
已绑定手机

76

主题

4833

回帖

8343

积分

超级版主

DebugLab

积分
8343
发表于 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, 下载次数: 248)


2 喜欢他/她就送朵鲜花吧,赠人玫瑰,手有余香!
DebugLab
回复

使用道具 举报 送花

  • 打卡等级:以坛为家II
  • 打卡总天数:423
  • 最近打卡:2025-05-01 07:10:25
已绑定手机

76

主题

4833

回帖

8343

积分

超级版主

DebugLab

积分
8343
发表于 2023-10-11 00:02:04 | 显示全部楼层
截图202310110002018071.jpg
DebugLab
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:以坛为家II
  • 打卡总天数:423
  • 最近打卡:2025-05-01 07:10:25
已绑定手机

76

主题

4833

回帖

8343

积分

超级版主

DebugLab

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

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

使用道具 举报 送花

  • 打卡等级:以坛为家II
  • 打卡总天数:513
  • 最近打卡:2025-05-01 04:31:49

24

主题

515

回帖

986

积分

荣誉版主

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

使用道具 举报 送花

  • 打卡等级:以坛为家II
  • 打卡总天数:502
  • 最近打卡:2025-05-01 08:01:38
已绑定手机

1

主题

833

回帖

1441

积分

金牌会员

积分
1441
发表于 2024-8-18 09:53:02 | 显示全部楼层
感谢分享
靡不有初,鲜克有终
回复

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:55
  • 最近打卡:2025-04-23 13:29:53

9

主题

42

回帖

390

积分

中级会员

积分
390
发表于 2025-1-16 00:25:44 | 显示全部楼层
下载一个学习学习
爱玩单片机的老头
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:常住居民II
  • 打卡总天数:87
  • 最近打卡:2025-04-30 09:11:54
已绑定手机

12

主题

56

回帖

367

积分

中级会员

积分
367
发表于 2025-2-21 11:46:51 | 显示全部楼层
已拜读,容我好好学习学习,融会贯通一下
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:51
  • 最近打卡:2025-04-29 10:45:42
已绑定手机

0

主题

28

回帖

260

积分

中级会员

积分
260
发表于 2025-4-21 09:47:04 | 显示全部楼层
学习学习
回复

使用道具 举报 送花

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

本版积分规则

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

GMT+8, 2025-5-1 19:57 , Processed in 0.226272 second(s), 102 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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