DebugLab 发表于 2023-10-10 23:53:58

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

本开源程序实现如题目所示的三个功能:

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='T';
                T_Buffer='=';
                T_Buffer=Hex_to_Ascii;
                T_Buffer=Hex_to_Ascii;
                T_Buffer='.';
                T_Buffer=Hex_to_Ascii;
                T_Buffer=Hex_to_Ascii;
                T_Buffer='C';
                T_Buffer=' ';
                T_Buffer='P';
                T_Buffer='=';
                T_Buffer=Hex_to_Ascii;
                T_Buffer=Hex_to_Ascii;
                T_Buffer=Hex_to_Ascii;
                T_Buffer='.';
                T_Buffer=Hex_to_Ascii;
                T_Buffer=Hex_to_Ascii;
                T_Buffer=Hex_to_Ascii;
                T_Buffer='K';
                T_Buffer='P';
                T_Buffer='a';
                T_Buffer=0x0d;
                T_Buffer=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=SBUF;
                if(RP==1)
                {
                        temp=(R_Buffer<<8)+R_Buffer;
                        if(temp>0x0200)
                        {
                              R_Buffer&=0x07;
                              switch(R_Buffer)
                              {
                                        case 'R':
                                                Uart_Send_Iap(R_Buffer);
                                                Sector=0xFF;
                                        break;
                                        case 'W':
                                                Sector=R_Buffer;
                                                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++;
                }
                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;                //Uart1接收缓存
unsigned char xdata      T_Buffer;                //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=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++;
                }
                if(TP==Uart_Send_Lenth)
                {
                        TP=0;
                        Uart_Send_Lenth=0;
                }
      }
}

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

完整工程见附件:



DebugLab 发表于 2023-10-11 00:02:04

DebugLab 发表于 2023-10-11 00:27:23

本帖最后由 DebugLab 于 2023-10-11 19:45 编辑

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

社区闲人 发表于 2023-11-26 10:12:46

准备用STC32F54的FPMU硬件进行万次迭代,看看误差是否越来越大。验证FPMU硬件的计算精度。

小涵子爸爸 发表于 2024-8-18 09:53:02

感谢分享

向日葵男人 发表于 2025-1-16 00:25:44

下载一个学习学习

jackduan 发表于 2025-2-21 11:46:51

已拜读,容我好好学习学习,融会贯通一下{:4_166:}

mingliang 发表于 2025-4-21 09:47:04

学习学习{:4_174:}{:4_174:}{:4_174:}
页: [1]
查看完整版本: 【串口收发框架】【printf重定向】【圆周率计算】