您的位置:首页 > 其它

限时定数法——简易高精度转速表,曾经的课程设计

2016-10-06 03:37 239 查看

大学二年级时的一个课程作业,怀念那些年岁月匆匆,贴出来怀缅

有程序和程序流程图,高转速时信号调理电路对边沿的处理较为重要,硬件需注意,信号须同时传到单片机INT0和T1计数IO口

简述:基于限时定数法,51平台所谓“单时钟周期”12MHz,C语言源码非汇编,传感器6脉冲每转,5转/分钟至300000转/分钟范围内,相对误差小于0.0125%,LCD1602显示

//崔廷佐 转速范围:5RPM-300000RPM 相对误差小于0.0125% 传感转盘每转脉冲数:6
//单片机平台:STC12C5A60S2 1T @12MHz
//2014.5.25 原创
//信号调理由实验台完成,输入接INT0、T1引脚,注意使用外围电路对输入电平箝位,防止外部相对电势差过大损坏IO引脚
//要求单片机要有一定的处理速度,外部中断后的250μs内要完成本程序的一次中断子程序
//为求较高精度,读数变更较慢,在新的计算周期,第一个脉冲到来时刻起,经两秒再过一个脉冲“立即”暂停所有计数,采集圈数及时间值进行换算,更新一次读数
//大约两秒没有信号接入,显示“No Access”(任何仪器计数频率都无法做到无穷趋近零,即周期无法是无穷大,通常也不现实,本课程设计为简易设计,作此侧重)

#include <reg52.h>
#include <stdio.h>

typedef unsigned int uint;
typedef unsigned char uchar;

sbit RS=P0^2;    //LCD1602并行,6800总线
sbit RW=P0^1;
sbit E=P0^0;

#define Data_Port P1  //6800总线数据口使用P1口

volatile bit data Flag_2s=0;  //两秒标志
volatile bit data Flag_Read=0;  //读取标志
volatile bit data Flag_again=1;  //开始计时、计数标志
volatile bit data Flag_No_Access=0;  //“无接入”标志

uchar data Turn_Cnt_M8b=0;  //转速计数高八位
uchar data Turn_Cnt_L8b=0;  //转速计数低八位
uchar data Oth_time_Cnt_50ms=0;  //额外时间50ms计数,缓冲单元
uchar data Oth_time_Cnt_250us=0;     //额外时间,250us计数,缓冲单元

uint data Cnt_No_Access1=0;  //“无接入”计数
uint data Cnt_No_Access2=0;  //“无接入”计数

uchar data Cnt_50ms=0;  //50ms计数值
uchar data Cnt_250us=0;  //250us计数值
uchar data Time0_50ms=0;  //定时器50ms计数值
uchar data Time0_250us=0;  //定时器250us计数值

float data Min_Turn_Speed=0;  //分转速

uchar data num_of_turns_show[]={"T:\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20"};
uchar data student[]={"CUI   YourName  "};
uchar data Turn_String[]={"0.00RPM\x20\x20\x20\x20\x20\x20\x20"};
uchar code Prompt_String[]={"No Access\x20\x20\x20\x20\x20"};
uchar code Turn_String_Space[10][10]={
{""},
{"\x20"},
{"\x20\x20"},
{"\x20\x20\x20"},
{"\x20\x20\x20\x20"},
{"\x20\x20\x20\x20\x20"},
{"\x20\x20\x20\x20\x20\x20"},
{"\x20\x20\x20\x20\x20\x20\x20"},
{"\x20\x20\x20\x20\x20\x20\x20\x20"},
{"\x20\x20\x20\x20\x20\x20\x20\x20\x20"}
};

void Init_Time0(void);   //函数声明
void Init_Counter1(void);
void Init_Int0(void);
void delay40us(void);
void write(uchar del);
void enable(uchar del);
void L1602_init(void);
void L1602_char(uchar hang,uchar lie,char sign);
void L1602_string(uchar hang,uchar lie,uchar *p);

void Init_Time0(void)
{
TMOD=(TMOD&0xf0)|0x02;  //T0工作在定时器方式二,自动重装,最大程度确保定时精度(没有方式一严重的总填数时间隙)
TL0=0x06;    //初值,STC12C5A60S2@12MHz,定时器12分频,250μs
TH0=0x06;
PT0=0;   //优先级为低
ET0=1;   //开定时器0中断
TR0=0;   //暂不开定时器0
}

void Init_Counter1(void)
{
TMOD=(TMOD&0x0f)|0x50;  //T1工作在计数器方式一
TR1=0;   //暂不开计数器1
}

void Init_Int0(void)
{
IT0=1;  //下降沿中断
PX0=1;  //优先级为高
EX0=1;  //开外部中断
}

int main (void)
{
uchar String_len=0;
Init_Time0();   //初始化定时器0
Init_Counter1();  //初始化计数器1
Init_Int0();   //初始化外部中断0
L1602_init();  //初始化1602
EA=1;      //开总中断
L1602_string(1,1,num_of_turns_show);  //界面
L1602_string(1,3,Turn_String);
L1602_string(2,1,student);
while(1)
{
if(Cnt_No_Access1++>=1000)  //超
d4cb
时计数
{
Cnt_No_Access1=0;
Cnt_No_Access2++;
if(Cnt_No_Access2>=1000)
{
Cnt_No_Access2=0;
Flag_No_Access=1;
}
}
if(Flag_No_Access)
{
L1602_string(1,3,Prompt_String);
}
else if(Flag_Read)  //可以读取
{
TR0=0;  //关定时器0
TR1=0;  //关计数器1
Min_Turn_Speed=10000.0*(((((uint)Turn_Cnt_M8b<<8)|Turn_Cnt_L8b))/(2000.0+(Oth_time_Cnt_250us*0.25+Oth_time_Cnt_50ms*50.0)));  //换算
String_len=sprintf(Turn_String,"%-0.2f""RPM",Min_Turn_Speed);   //打印并获取字符串长度
L1602_string(1,3+String_len,Turn_String_Space[13-String_len]);  //先补空格,盖字^_^
L1602_string(1,3,Turn_String);  //显示转速
Flag_Read=0;  //清读取标志
Flag_again=1;  //置可开始计数计时标志
}
else;
}
return 0;
}

void Int_0 () interrupt 0
{
Cnt_No_Access1=0;   //清所有超时标志
Cnt_No_Access2=0;
Flag_No_Access=0;
if(Flag_again==1)   //重入
{
TH1=0;  //清计数值
TL1=0;
Cnt_50ms=0;  //清时间积累值
Cnt_250us=0;
TL0=0x06;    //初值,STC12C5A60S2@12MHz,定时器12分频,250μs
TH0=0x06;
TR0=1;   //开定时器0
TR1=1;   //开计数器1,舍弃第一个下降沿
Flag_again=0;  //清开始计时计数标志
}
if(Flag_2s)  //两秒时或两秒后的下降沿到
{
TR1=0;  //关计数器1
TR0=0;  //关计时器
Turn_Cnt_M8b=TH1;  //装入两秒内计数值高八位
Turn_Cnt_L8b=TL1;  //装入两秒内计数值低八位
Oth_time_Cnt_50ms=Cnt_50ms;  //装入其余50ms累计数
Oth_time_Cnt_250us=Cnt_250us;  //装入其余250us累计数
Flag_Read=1;  //置读取标志
Flag_2s=0;   //清两秒标志
TH1=0;  //清计数值
TL1=0;
Cnt_50ms=0;  //清时间积累值
Cnt_250us=0;
}
}

void Timer_0_Int(void) interrupt 1
{
Cnt_250us++;      //250μs计数自加,250μs为计时精度
if(Cnt_250us==200)  //50ms到
{
Cnt_250us=0;   //清250μs计时值
Cnt_50ms++;   //50ms计时值自加
if(Cnt_50ms==40)
{
Cnt_50ms=0;  //清50ms计时值
Flag_2s=1;  //置两秒标志,计时误差来自最后一次中断后的处理时间,中间的计时误差不积累
}
}
}

void delay40us(void)   //误差 0us,STC12C5A60S2@12MHZ,时钟无分频
{
unsigned char a;
for(a=117;a>0;a--);
}

void enable(uchar del)
{
Data_Port = del;
RS = 0;
RW = 0;
E = 0;
delay40us();
E = 1;
delay40us();
}

void write(uchar del)
{
Data_Port = del;
RS = 1;
RW = 0;
E = 0;
delay40us();
E = 1;
delay40us();
}

void L1602_init(void)
{
enable(0x01);
enable(0x38);
enable(0x0c);
enable(0x06);
enable(0xd0);
}

void L1602_string(uchar hang,uchar lie,uchar *p)
{
uchar a;
if(hang == 1) a = 0x80;
if(hang == 2) a = 0xc0;
a = a + lie - 1;
enable(a);
while(1)
{
if(*p !='\0')
write(*p);
else break;
p++;
}
}


作业上的方案选型和要求(固定格式要求,感觉老师们萌萌哒)

方案一:使用“定时记数法”,在固定的时间T内,利用计数器记录转速传感器发出的脉冲数N,从而算出转速n=N/T。

方案优缺点:此方案简单易行。但当固定时间T时刻到来,有可能计数器即将记录到下一个脉冲,因此该方案最大的误差是一个脉冲,一般只适合高转速测量场合。

方案二:使用“记数查时法”,是指用计数器记录固定个脉冲数K,查询记录K个脉冲所花费的时间T,从而计算出转速n=K/T。

方案优缺点:此方案简单易行。但由于定时器最小时间单位往往没有设计中期望的小,当记录K个脉冲所花费的时间T不是足够大时,可能出现测量超差。因此,此方法一般只适用于低转速测量场合。

方案三:采用新的“限时定数法”。

方案优缺点:新的软件算法结合记数查时法和定时计数法的特点,回避了两种算法的缺点。与定时计数法相比,它不是在固定的时间T到后立即取出计数器中的计数来计算转速,而是等到下一个脉冲的下降沿到来时,再进行N/t计算,此举解决了定时计数法可能有一个脉冲误差的问题。与计数查时法相比较,该算法不是记录固定个脉冲后立即进行K/t运算,而是等待t大于某个时间阈值再进行转速运算,解决了记数查时法当转速过高时,记录K个脉冲所用的时间太短而造成的测量超差问题。

方案选择:综合速度和精度考虑,选择方案三为设计方案。具体方案的设计…





CSDN没办法贴Visio框图,直接PNG图片~


![主函数框图" title="">

主函数框图

 


![定时器中断框图" title="">

定时器中断框图

 

 


![计数框图" title="">

计数框图

 

 


![外部中断框图" title="">

外部中断框图

 

  

————————☺————————
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息