合泰单片机 | HT66F3195 | 个人库开发过程 | 【4】时基

时基

理解成一个带中断触发的定时器会好一点,定时器有非常多复杂的配置,如果只用来计时很多东西用不上,用这个时基模块就可以满足开发需求了。
由于目前的功力有限,还没有研究过RTOS那种嵌入式系统,另外一方面就是这种性能不高的芯片,用时间轮询的伪任务系统也可以满足大部分开发需求,我个人习惯把这个时间轮询称作时基碎片功能
该款MCU拥有2个独立的时基模块,对应的属性也是非常简单的,可以大致理解成:

  1. 模块时钟
  2. 溢出周期
  3. 模块使能
  4. 中断使能

程序配置

Timebase.h

#ifndef _TIMEBASE_H_
#define	_TIMEBASE_H_

/*-------------------变量声明------------------- */
typedef struct
{
	unsigned int FragmentCnt;
	
	unsigned char Cnt1s;
	unsigned char Cnt100ms;
	unsigned char Cnt10ms;
	unsigned char Cnt2ms;
	
	unsigned char FragmentFlag;

}Timebase_Fragment_Task;

#define	Fragment_1ms_Mask		0x01
#define	Fragment_2ms_Mask		0x02
#define	Fragment_10ms_Mask		0x04
#define	Fragment_100ms_Mask		0x08
#define	Fragment_1s_Mask		0x10

typedef struct
{
	unsigned int PWM_Cnt;
	unsigned int Duty;
	unsigned int Period;
	unsigned char Status;

}Timebase_PWM_Task;

#define	TIMEBASE_PWM_PERIOD			250
#define	TIMEBASE_PWM_DUTY			100


extern Timebase_Fragment_Task TB0;
extern Timebase_PWM_Task TB1;

/*-------------------函数声明------------------- */
void Timebase_0_Init();
void Timebase_1_Init();
void Timebase_PWM_Output();
void Timebase_PWM_Duty_Update();
void Timebase_PWM_Parameter_Update(unsigned int duty, unsigned int period);


/*-------------------宏封装------------------- */
#define	Timebase_0_IntAddress			0x1C
#define	Timebase_1_IntAddress			0x20


#define	Timebase_0_Cmd(x)				_tb0on = x
#define	Timebase_0_InterruptCmd(x)		_tb0e = x
#define	Timebase_0_Clock(x)				_psc0r = (_psc0r&(~(REG_2_BIT<<0)))|(x<<0)
#define	Timebase_0_Period(x)			_tb0c = (_tb0c&(~(REG_3_BIT<<0)))|(x<<0)
#define	Timebase_0_FlagReset()			_tb0f = 0


#define	Timebase_1_Cmd(x)				_tb1on = x
#define	Timebase_1_InterruptCmd(x)		_tb1e = x
#define	Timebase_1_Clock(x)				_psc1r = (_psc1r&(~(REG_2_BIT<<0)))|(x<<0)
#define	Timebase_1_Period(x)			_tb1c = (_tb1c&(~(REG_3_BIT<<0)))|(x<<0)
#define	Timebase_1_FlagReset()			_tb1f = 0


typedef enum
{
	Timebase_Clock_Fsys = 0,
	Timebase_Clock_Fsys_Div4,
	Timebase_Clock_Sub,
	
}Timebase_Clock_T;


typedef enum
{
	Timebase_Period_2e8 = 0,
	Timebase_Period_2e9,
	Timebase_Period_2e10,
	Timebase_Period_2e11,
	Timebase_Period_2e12,
	Timebase_Period_2e13,
	Timebase_Period_2e14,
	Timebase_Period_2e15,
	
}Timebase_Period_T;


#endif

时基0相关程序配置

时基0初始化

前面说了,时基0用来做那个时基碎片功能,对应的任务结构体就是这个Timebase_Fragment_Task,在对应的C文件中通过这个结构体类型新建一个变量Timebase_Fragment_Task TB0,结构体里面的变量用法如下,主要有两部分,一部分体现在中断里面,另一部分体现在主函数里面。

void Timebase_0_Init()
{
	Timebase_0_Cmd(Disable);
	Timebase_0_InterruptCmd(Disable);
	
	Timebase_0_Clock(Timebase_Clock_Fsys);

	if((Sys.Clock == HXT_8M)||(Sys.Clock == HIRC_8M))
	{
		Timebase_0_Period(Timebase_Period_2e13);
	}
	else if((Sys.Clock == HXT_16M)||(Sys.Clock == HIRC_16M))
	{
		Timebase_0_Period(Timebase_Period_2e14);
	}

	Timebase_0_Cmd(Enable);
	Timebase_0_InterruptCmd(Enable);

	TB0.FragmentCnt = 0;
	TB0.Cnt1s = 0;
	TB0.Cnt100ms = 0;
	TB0.Cnt10ms = 0;
	TB0.Cnt2ms = 0;
	TB0.FragmentFlag = 0;
}

先看时基0的初始化部分,一般我是把系统时钟直接给到时基0这边不用分频。
至于Sys.Clock这个结构体是保存一些系统信息的结构体,目前还没有构思好要放哪些东西,先放置一个变量记录当前的时钟频率,后续会单独写一篇介绍这个结构体。
这个变量只要记录,系统时钟频率,以及是内部时钟还是外部时钟这种。之所以要保存时钟的信息,是因为如果时基0的溢出周期固定的话,但是不同工程选择的时钟可能不一样,就导致进入中断的时间就不一致了,整个时基碎片功能就出错了。
所以这里设置溢出周期之前要先判断当前的时钟频率是多少。

时基0中断

DEFINE_ISR(timebase_0_int, Timebase_0_IntAddress)
{	
	Timebase_0_FlagReset();
	TB0.FragmentFlag |= Fragment_1ms_Mask;
	TB0.FragmentCnt += 1;
	if(TB0.FragmentCnt >= 2)
	{
		TB0.FragmentCnt = 0;
		TB0.FragmentFlag |= Fragment_2ms_Mask;
		TB0.Cnt2ms += 1;
		if(TB0.Cnt2ms >= 5)
		{
			TB0.Cnt2ms = 0;
			TB0.FragmentFlag |= Fragment_10ms_Mask;
			TB0.Cnt10ms += 1;
			if(TB0.Cnt10ms >= 10)
			{
				TB0.Cnt10ms = 0;
				TB0.FragmentFlag |= Fragment_100ms_Mask;
				TB0.Cnt100ms += 1;
				if(TB0.Cnt100ms >= 10)
				{
					TB0.Cnt100ms = 0;
					TB0.FragmentFlag |= Fragment_1s_Mask;
				}
			}
		}
	}
}

这个逻辑也是比较简单的,每次中断进来就是1ms,累计2次就是2ms,然后2ms累计5次就是10ms,这样层层递进下来。
我预设的时钟是1ms,但是时基的溢出周期没有办法像时钟那样设定得非常精准,所以只能把溢出周期配置成最接近的1.024ms,8M和16M的时钟都能配置出来,但是12M的时钟就有点尴尬了。
因为这个是1.024ms的中断,所以后续累加上来的时间比如2ms,10ms,100ms,1s这种都是有一定误差在里面的,但是这个功能后续对接的不应该是那种实时性特别强的开发任务。

时基0新写法降低误差思路

如果强迫症犯了非得要把这个误差降下来也不是不行,换一种思路就好了。不要按照上面那种层层递进的关系,从把所有标志位都绑在一起的关系变成,2ms绑定1ms,10ms绑定1ms,100ms绑定1ms,1s绑定1ms这种,单独跟1ms对应。
就是每次进入中断之后,每个级别都自加一次,2ms这种累加2次,2ms自己的计数变量清零,拉起对应标志位。
10ms这种累加10次1ms,但是实际上应该是1.024 x 10 = 10.24ms,如果累加成9次再清零的话,时间应该是1.024 x 9 = 9.216秒。按照误差来算肯定是累加10次比较合理的。
同理,100ms的话累加100次的时间是102.4ms,99次是101.376,98次是100.352,97次是99.328,这里取98次比较合理。
1s的标志位呢,需要注意一个点,unsigned char是8位变量,最大值为255,要换成unsigned int才能记录到1000,剩下的自己算。
如果强迫症达到丧心病狂的地步的话,那个时基溢出周期选择更低一些,按照自己喜欢然后计算,尽可能把每一个级别的误差降到最低🤡太过频繁进入中断并不是特别合适,后续有空的话也可以举个例子讲讲。

时基0主函数操作

void main()
{
    System_Init();
    Timebase_0_Init();

    while(1)
    {
        if(TB0.FragmentFlag & Fragment_1ms_Mask)
        {
            TB0.FragmentFlag ^= Fragment_1ms_Mask;
        }

        if(TB0.FragmentFlag & Fragment_2ms_Mask)
        {
            TB0.FragmentFlag ^= Fragment_2ms_Mask;
        }

        if(TB0.FragmentFlag & Fragment_10ms_Mask)
        {
            TB0.FragmentFlag ^= Fragment_10ms_Mask;
        }

        if(TB0.FragmentFlag & Fragment_100ms_Mask)
        {
            TB0.FragmentFlag ^= Fragment_100ms_Mask;

        }

        if(TB0.FragmentFlag & Fragment_1s_Mask)
        {
            TB0.FragmentFlag ^= Fragment_1s_Mask;
        }

        Feed_Dog();
    };
}

这里就是按照那种常见任务轮询来写的。看需求然后把要操作的程序放置到对应的时间里面。
关于标志位清零的操作,只要能进入对应的分支就证明对应的标志位一定是1,直接通过异或进行清零运算。

时基1配置

之前写的时候预留了时基1作为模拟PWM控制呼吸灯,随便写了点东西程序不是很完善,这里就不展开写了,后面涉及到GPIO的时候再展开写清楚。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值