单片机用关中断和互斥量来保护多线程共享的全局变量
程序员文章站
2022-03-04 15:53:21
一、使用proteus绘制简单的电路图,用于后续仿真二、编写程序/********************************************************************************************************************----@Project:Mutex----@File:main.c----@Edit:ZHQ----@Version:V1.0----@CreationTime:202...
一、使用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<uiDelayLong;i++)
{
for(j=0;j<500;j++) /*内嵌循环的空指令数量*/
{
; /*一个分号相当于执行一条空语句*/
}
}
}
/*——————主函数——————*/
/**
* @brief 主函数
* @param 无
* @retval 实现LED灯闪烁
**/
void main()
{
/*单片机初始化*/
Init();
/*延时,延时时间一般是0.3秒到2秒之间,等待外围芯片和模块上电稳定*/
Delay_Long(100);
/*单片机外围初始化*/
Init_Peripheral();
while(1)
{
/*第一个任务,实现LED闪烁*/
Led_Flicker();
/*第二个任务,实现报警器定时报警*/
Alarm_Run();
}
}
/* 注释一:
* 如何知道1秒钟需要多少个定时中断?
* 这个需要编写一段小程序测试,得到测试的结果后再按比例修正。
* 步骤:
* 第一步:在程序代码上先写入1秒钟大概需要200个定时中断。
* 第二步:基于以上1秒钟的基准,编写一个60秒的简单测试程序(如果编写超过
* 60秒的时间,这个精度还会更高)。比如,编写一个用蜂鸣器的声音来识别计时的
* 起始和终止的测试程序。
* 第三步:把程序烧录进单片机后,上电开始测试,手上同步打开手机里的秒表。
* 如果单片机仅仅跑了27秒。
* 第四步:那么最终得出1秒钟需要的定时中断次数是:const_time_1s=(200*60)/27=444
*/
三、仿真实现
更改蜂鸣器短叫和长叫的时间的宏,可修改相应时间。(proteus蜂鸣器默认12V,可修改为5V,否则不报警)
本文地址:https://blog.csdn.net/DJDN426611/article/details/107924362