单片机Delay延时缺点
对于单片机裸机和单片机移植操作系统来说,延时函数是一个不一样的作用,在操作系统重,任务的延时cpu会使这个任务挂起,从而去执行其他任务,当任务到时间之后会继续执行,但是在大多数的嵌入式开发芯片中,大多数的芯片是不支持移植操作系统的。
如果在单片机中调用延时函数,在执行延时函数的期间,单片机是不能执行其他任务的,如果单片机在这期间外界有条件发生,单片机不能及时进行相应,得等到单片机延时执行结束,这个就是单片机使用系统提供的Delay的坏处。
对于单片机不能及时响应外界的情况,对于一些产品是很致命的对于功能来说,当使用Delay延时的时候,有外界条件发生需要执行一些功能的时候,怎么办,总不能说,我知道你很急,但你先别急!
void delay_us(uint32_t us)
{
uint32_t systickCount;
uint32_t count = us * systick_us; // 计算延时所需的计数值,systick_us为每微秒所需的计数周期
if (us == 1) // 如果延时为1微秒
{
SysTick->LOAD = count - 8; // 调整LOAD寄存器的值,微调延时,使其接近1微秒
}
else
{
SysTick->LOAD = count - 10; // 加载计数器的值,稍微调整以准确控制延时时间
}
SysTick->VAL = 0x00; // 清除当前计数器值,使其从0开始计数
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; // 启动SysTick定时器(使能SysTick计数器)
// 等待SysTick计数器达到设定的延时时间
do
{
systickCount = SysTick->CTRL; // 读取SysTick控制寄存器
} while ((systickCount & 0x01) && !(systickCount & (1 << 16))); // 检查计数器是否完成并且没有溢出
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; // 禁用SysTick定时器(关闭计数器)
SysTick->VAL = 0x00; // 清除计数器值,准备下一次使用
}
延时函数库函数定义
如果使用这个delay函数,单片机就是在while(){}循环里面硬等,在等待的期间不会做任何事情。
定时器中断基本思路
这里需要知道单片机自身时钟,假设单片机自身时钟为24Mhz,以为着单片机在1s,以内能够产生 24000 000 次时钟脉冲周期,单片机时钟在经过预分频系数,还有自动重装载值的,分频之后就是定时器时钟,意味着定时器能够在,1s内产生的时钟脉冲周期是多少。
同时使能定时器开启中断,1s内定时器开启中断的次数,就是定时器时钟频率的次数,这里中断函数因为自身的特殊性,切记不需要做太多的事情,里面就放变量++就好。
void TIM1_BRK_UP_TRG_COM_IRQHandler(void)
{
//检查中断出发条件
if((LL_TIM_ReadReg(TIM1,SR) & LL_TIM_SR_UIF) == 1 && LL_TIM_IsEnabledIT_UPDATE(TIM1))
{
//清楚中断标志位
LL_TIM_ClearFlag_UPDATE(TIM1);
//调用自己定义的中断回调函数
APP_Updatefallback();
}
}
//tiemr 频率是4khz 每秒产生 4000 次中断 1s Flag_NVIC =4000
void APP_Updatefallback(void)
{
Flag_NVIC ++;
Flag_NVIC = Flag_NVIC%12001;
}
这里切记因为是中断所以中断里面不要放太多的东西,同时给大家看一下使能定时器,开启中断的函数配置,这里需要注意的是,单片机自身的时钟是多少,以及定时器的时钟分频系数是多少。
void Beer_Using_Set(uint32_t State)
{
//开启时钟
LL_APB1_GRP2_EnableClock(LL_APB1_GRP2_PERIPH_TIM1);
LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOA);
/*配置TIM1*/
LL_TIM_InitTypeDef TIM1CountInit = {0};
//结构体变量声明
LL_GPIO_InitTypeDef TIM1CH1MapInit = {0};
LL_TIM_OC_InitTypeDef TIM_OC_Initstruct = {0};
//GPIOA引脚设置成pwm模式 引脚搞成 af2模式
TIM1CH1MapInit.Pin = LL_GPIO_PIN_9;
TIM1CH1MapInit.Mode = LL_GPIO_MODE_ALTERNATE;
TIM1CH1MapInit.Alternate = LL_GPIO_AF_2;
TIM1CH1MapInit.Speed = LL_GPIO_SPEED_FREQ_VERY_HIGH;
TIM1CH1MapInit.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
TIM1CH1MapInit.Pull = LL_GPIO_PULL_UP;
LL_GPIO_Init(GPIOA,&TIM1CH1MapInit);
/*配置PWM通道*/
TIM_OC_Initstruct.OCMode = LL_TIM_OCMODE_PWM2; /* 通道模式:PWM2 */
TIM_OC_Initstruct.OCState = LL_TIM_OCSTATE_ENABLE; /* 通道状态:开启 */
TIM_OC_Initstruct.OCPolarity = LL_TIM_OCPOLARITY_HIGH; /* 通道有效极性:高电平 */
TIM_OC_Initstruct.OCIdleState = LL_TIM_OCIDLESTATE_LOW; /* 通道空闲极性:低电平 */
//这里输出1v
TIM_OC_Initstruct.CompareValue = State;
/*配置通道2*/
LL_TIM_OC_Init(TIM1,LL_TIM_CHANNEL_CH2,&TIM_OC_Initstruct);
TIM1CountInit.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1;/* 不分频 */
TIM1CountInit.CounterMode = LL_TIM_COUNTERMODE_UP; /* 计数模式:向上计数 */
TIM1CountInit.Prescaler = 3-1; //预分频系数参数
TIM1CountInit.Autoreload = 2000-1; //这里频率是4khz
TIM1CountInit.RepetitionCounter = 0; /* 重复计数值:0 */
/*初始化TIM1*/
LL_TIM_Init(TIM1,&TIM1CountInit);
//清除中断标志位
LL_TIM_ClearFlag_UPDATE(TIM1);
//开启自动预装载
LL_TIM_EnableARRPreload(TIM1);
//开启中断标志位
LL_TIM_EnableIT_UPDATE(TIM1);
/*主输出使能*/
LL_TIM_EnableAllOutputs(TIM1);
/*使能TIM1计数器*/
LL_TIM_EnableCounter(TIM1);
//开启TIM1中断函数,同时分配变量
NVIC_EnableIRQ(TIM1_BRK_UP_TRG_COM_IRQn);
NVIC_SetPriority(TIM1_BRK_UP_TRG_COM_IRQn,0);
}
到了这里已经完成对,基本定时器代码初始化的编写,最重要的是完成开始的目的,在完成延时功能的同时,保证单片机没有卡死,同时执行单片机的其他任务。
使能定时器中断代码理解
这里就不放真正的程序代码了,只给大家讲解怎么调用延时,同时讲解一些关键的点, 下面这个程序写的是延时3s,同时在延时的同时,又能保证程序没有卡死。
Beer_Using_Set(0);
//读这个变量之前一定要把这个变量给赋值为 0
//因为在真正的程序执行到这里之前不知道这个变量的值到底是多少
//如果直接读取这个全局变量的值,延时计算是不准的
Flag_NVIC = 0;
//在延时3s内 while循环里有执行函数cpu在这里没有卡死
while (!(Flag_NVIC == 12000) && !exitFlag) {exitFlag = Detection_Up();}
如果在自己的程序里面使用了这种,延时方法只需要,在while循环体里面,添加自己工程的其他功能就能保证,在延时的同时,程序其他功能能够正常执行,同时程序能够正常响应 ,外界发生的变化,也就是说程序并不会被卡死。
编写不易,请勿搬运,仅供学习,感谢理解。