单片机学习笔记————用关中断和互斥量来保护多线程共享的全局变量

一、使用proteus绘制简单的电路图,用于后续仿真

二、编写程序

/********************************************************************************************************************
----	@Project:	Mutex
----	@File:	main.c
----	@Edit:	ZHQ
----	@Version:	V1.0
----	@CreationTime:	20200810
----	@ModifiedTime:	20200810
----	@Description:	LED闪烁,蜂鸣器报警
----	让蜂鸣器在前面3秒发生一次短叫报警,在后面6秒发生一次长叫报警,如此反复循环。 
----	单片机:AT89C52
********************************************************************************************************************/
#include "reg52.h"

/*——————宏定义——————*/
#define FOSC 11059200L
#define T1MS (65536-FOSC/12/1000)   /*1ms timer calculation method in 12Tmode*/

#define const_time_05s 400   /*0.5秒钟的时间需要的定时中断次数*/
#define const_time_1s 800   /*1秒钟的时间需要的定时中断次数*/
#define const_time_3s 2400   /*3秒钟的时间需要的定时中断次数*/
#define const_time_6s 4800  /*6秒钟的时间需要的定时中断次数*/

#define const_voice_short  100   /*蜂鸣器短叫的持续时间*/
#define const_voice_long   800  /*蜂鸣器长叫的持续时间*/

/*——————变量函数定义及声明——————*/
/*定义LED口*/
sbit LED = P3^5;
/*定义蜂鸣器口*/
sbit BUZZER = P2^7;

/*LED步骤变量*/
unsigned char ucLedStep=0; 
/*LED统计定时中断次数的延时计数器*/
unsigned int  uiTimeLedCnt=0;

/*报警步骤变量*/
unsigned char ucAlarmStep=0; 
/*报警统计定时中断次数的延时计数器*/
unsigned int  uiTimeAlarmCnt=0;

/*蜂鸣器鸣叫的持续时间计数器*/
unsigned int  uiVoiceCnt=0;

unsigned char ucLock = 0;	/* 互斥量,俗称原子锁 */

/**
* @brief  定时器0初始化函数
* @param  无
* @retval 初始化T0
**/
void Init_T0(void)
{
	TMOD = 0x01;                    /*set timer0 as mode1 (16-bit)*/
	TL0 = T1MS;                     /*initial timer0 low byte*/
	TH0 = T1MS >> 8;                /*initial timer0 high byte*/
}
/**
* @brief  定时器0中断函数
* @param  无
* @retval 无
**/
void ISR_T0(void)	interrupt 1
{
	TF0 = 0;  /*清除中断标志*/
  TR0 = 0; /*关中断*/
  if(uiTimeLedCnt < 0xffff)  /*设定这个条件,防止uiTimeCnt超范围*/
  {
      uiTimeLedCnt ++;  /*累加定时中断的次数*/
  }
  if(ucLock == 0)	/* 互斥量判断 */
  {	
	if(uiTimeAlarmCnt < 0xffff)  /*设定这个条件,防止uiTimeCnt超范围*/
	{
		uiTimeAlarmCnt ++;  /*累加定时中断的次数*/
	}
	if(0 != uiVoiceCnt)
	{
		uiVoiceCnt --;
		BUZZER = 0;
	}
	else
	{
		BUZZER = 1;
	}
  }	
	TL0 = T1MS;                     /*initial timer0 low byte*/
	TH0 = T1MS >> 8;                /*initial timer0 high byte*/
  TR0 = 1; /*开中断*/	
}
/**
* @brief  外围初始化函数
* @param  无
* @retval 初始化外围
**/
void Init_Peripheral(void)
{
	ET0 = 1;/*允许定时中断*/
	TR0 = 1;/*启动定时中断*/
	EA = 1;/*开总中断*/

}

/**
* @brief  初始化函数
* @param  无
* @retval 初始化单片机
**/
void	Init(void)
{
	Init_T0();
	LED = 0;
	BUZZER = 1;
}
/**
* @brief  LED闪烁函数
* @param  无
* @retval 控制LED闪烁
**/
void Led_Flicker(void)
{
	switch(ucLedStep)
	{
		case 0:
			if(uiTimeLedCnt >= const_time_05s) /*时间到,灯亮*/
			{
				ET0 = 0; /*禁止定时中断*/
				uiTimeLedCnt = 0; 
				LED = 1;
				ucLedStep = 1;/*切换到下一步*/
				ET0 = 1; /*允许定时中断*/
			}
		break;
		case 1:
			if(uiTimeLedCnt >= const_time_05s) /*时间到,灯灭*/
			{
				ET0 = 0; /*禁止定时中断*/
				uiTimeLedCnt = 0; 
				LED = 0;
				ucLedStep = 0;/*返回到上一步*/
				ET0 = 1; /*允许定时中断*/
			}
		break;			
	}
}
/**
* @brief  报警器报警函数
* @param  无
* @retval 报警器报警.
* 保护多线程共享全局变量的原理:
* 多个线程同时访问同一个全局变量,如果都是读取操作,则不会出现问题。如果一个线程负责改变此变量的值,
* 而其他线程负责同时读取变量内容,则不能保证读取到的数据是经过写线程修改后的。
**/
void Alarm_Run(void)
{
	switch(ucAlarmStep)
	{
		case 0:
			if(uiTimeAlarmCnt >= const_time_3s) /*时间到*/
			{
/* 
* 用关中断来保护多线程共享的全局变量:
* 因为uiTimeAlarmCnt和uiVoiceCnt都是unsigned int类型,本质上是由两个字节组成。
* 在C语言中uiTimeAlarmCnt=0和uiVoiceCnt=const_voice_short看似一条指令,
* 实际上经过编译之后它不只一条汇编指令。由于另外一个定时中断线程里也会对这个变量
* 进行判断和操作,如果不禁止定时中断或者采取其它措施,定时函数往往会在主函数还没有
* 结束操作共享变量前就去访问或处理这个共享变量,这就会引起冲突,导致系统运行异常。
*/
				ET0 = 0; /*禁止定时中断*/
				uiTimeAlarmCnt = 0; 
				uiVoiceCnt = const_voice_short;  /*蜂鸣器短叫*/
				ucAlarmStep = 1;/*切换到下一步*/
				ET0 = 1; /*允许定时中断*/
			}
		break;
		case 1:
			if(uiTimeAlarmCnt >= const_time_6s) /*时间到*/
			{
/* 
* 用互斥量来保护多线程共享的全局变量:
* 我觉得,在这种场合,用互斥量比前面用关中断的方法更加好。
* 因为一旦关闭了定时中断,整个中断函数就会在那一刻停止运行了,
* 而加一个互斥量,既能保护全局变量,又能让定时中断函数正常运行,
* 真是一举两得。 
*/
				ucLock = 0; /*互斥量加锁。 俗称原子锁*/
				uiTimeAlarmCnt = 0; 
				uiVoiceCnt = const_voice_long;  /*蜂鸣器长叫*/
				ucAlarmStep = 0;/*返回到上一步*/
				ucLock = 0; /*互斥量解锁*/
			}
		break;			
	}
}
/**
* @brief  延时函数
* @param  无
* @retval 无
**/
void Delay_Long(unsigned int uiDelayLong)
{
   unsigned int i;
   unsigned int j;
   for(i=0;i

/* 注释一:

* 如何知道1秒钟需要多少个定时中断?

* 这个需要编写一段小程序测试,得到测试的结果后再按比例修正。

* 步骤:

* 第一步:在程序代码上先写入1秒钟大概需要200个定时中断。

* 第二步:基于以上1秒钟的基准,编写一个60秒的简单测试程序(如果编写超过

* 60秒的时间,这个精度还会更高)。比如,编写一个用蜂鸣器的声音来识别计时的

* 起始和终止的测试程序。

* 第三步:把程序烧录进单片机后,上电开始测试,手上同步打开手机里的秒表。

*         如果单片机仅仅跑了27秒。

* 第四步:那么最终得出1秒钟需要的定时中断次数是:const_time_1s=(200*60)/27=444

*/

三、仿真实现

更改蜂鸣器短叫和长叫的时间的宏,可修改相应时间。(proteus蜂鸣器默认12V,可修改为5V,否则不报警)

 

你可能感兴趣的:(单片机,C,proteus,单片机,proteus,互斥量)