【串口收发框架】【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 19:45 编辑
注意:实测定时器在16位自动重载模式时,关闭定时器再打开定时器不会自动重载,只有溢出时才会自动重载,且写入重载值并不会更新计数器,而是写入影子寄存器,所以串口接收超时定时器要使用16位不自动重载模式,写入值就会立即更新计数器。 准备用STC32F54的FPMU硬件进行万次迭代,看看误差是否越来越大。验证FPMU硬件的计算精度。 感谢分享 下载一个学习学习 已拜读,容我好好学习学习,融会贯通一下{:4_166:} 学习学习{:4_174:}{:4_174:}{:4_174:}
页:
[1]