STM32实现声控灯以及jiffies解决时间回绕问题(对宏time_after理解)


1- 配置

因为前面实现了声音传感器和光强传感器,又可以控制LED灯的量灭,所以尝试着做一下声控灯的小测试。
在黑暗状态下声音大了之后就会触发LED灯亮,到一定时间就会自动熄灭。
主要配置可以看一下,但是实际配置过程可以查看我前面写的,比较详细都有的。
在这里插入图片描述


2- 代码实现

gpio.c中添加代码:

/* USER CODE BEGIN 2 */
typedef struct gpio_s
{
	  GPIO_TypeDef   *group;
	  uint16_t       pin;
} gpio_t;

/*这里定义了一个结构体数组,group指向第一个,pin指向第二个*/
gpio_t      leds[LedMax] =
{
		{RedLed_GPIO_Port,  BlueLed_Pin},
		{RedLed_GPIO_Port,  RedLed_Pin},
		{RedLed_GPIO_Port,  GreenLed_Pin},
};
/*which代表选择哪一个灯,int类型的,每个灯都有一个数字代替,去enum去看;ststus控制灯的亮灭,对应参数是OFF 和 ON*/
void turn_led(int which, int status)
{
	GPIO_PinState       level;
	if( which<0 || which>=LedMax)
	{
		return ;
	}
	level = status == OFF ? GPIO_PIN_SET : GPIO_PIN_RESET;

	HAL_GPIO_WritePin(leds[which].group, leds[which].pin, level);
}


/* USER CODE END 2 */

gpio.h中添加声明:

/* USER CODE BEGIN Prototypes */
/*枚举,SysLed默认为0,后面依次增长*/
enum
{
	BlueLed,
	RedLed,
	GreenLed,
	LedMax,
};

#define OFF 0
#define ON 1

extern void turn_led(int which, int status);

/* USER CODE END Prototypes */

tim.c中添加蜂鸣器的函数,如果没有就不需要添加了:

/* USER CODE BEGIN 1 */
void beep_start(uint8_t times, uint16_t interval)
{
	while(times--)
	{
		if(HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2) != HAL_OK)
		{
			Error_Handler();
		}
		HAL_Delay(interval);
		if(HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_2) != HAL_OK)
		{
			Error_Handler();
		}
		HAL_Delay(interval);
	}
}
/* USER CODE END 1 */

tim.c中添加函数声明:

/* USER CODE BEGIN Includes */
void beep_start(uint8_t times, uint16_t interval);
/* USER CODE END Includes */

usart.c中添加printf()代码实现函数:

/* USER CODE BEGIN 1 */
#ifdef __GNUC__
  #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)/*char类型是以int类型存储的,�?以可以用int*/
#else
  #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */

PUTCHAR_PROTOTYPE
{
  HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);

  return ch;
}
/* USER CODE END 1 */

usart.h中添加头文件:

/* USER CODE BEGIN Includes */
#include <stdio.h>
/* USER CODE END Includes */

主要是main.c的中的代码实现:

  /* USER CODE BEGIN 1 */
uint32_t lux, noisy;
uint32_t start = 0;
uint32_t light_status = 0;
  /* USER CODE END 1 */
/* USER CODE BEGIN WHILE */
  beep_start(2, 300);/*蜂鸣器叫两声,间隔300ms*/
  printf("Start voice-controlled light control test\t\n");
  while (1)
  {
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
	  if( OFF == light_status )
	  {
		  adc_sample_lux_noisy(&lux, &noisy);
		  printf("Lux:[%lu] Noisy[%lu]\r\n", lux, noisy);

		  /*在黑暗状态下声音超过900就会亮*/
		  if(lux > 30 && noisy > 800)
		  {
			  printf("Turn light on\t\n");
			  turn_led(RedLed, ON);
			  light_status = ON;

			  start = HAL_GetTick();
		  }
	  }

	  else
	  {
		  if( time_after(HAL_GetTick(), start+15000) )
		  {
			  printf("Turn light OFF\t\n");
			  turn_led(RedLed, OFF);

			  light_status = OFF;
		  }
	  }

	  HAL_Delay(10);
  }
  /* USER CODE END 3 */
}

main.h中添加定义宏,这个就是今天的主角,防止时间回绕解决的措施,后面会细讲:

/* USER CODE BEGIN Private defines */

#define time_after(a,b)  ( (int32_t)(b) - (int32_t)(a) < 0)
#define time_before(a,b)  time_after(b,a)

#define time_after_eq(a,b)  ( (int32_t)(a) - (int32_t)(b) >= 0)
#define time_before_eq(a,b)  time_after_eq(b,a)

/* USER CODE END Private defines */

3- 防止时间回绕问题解决

(1)HAL_Delay()延迟

第一次写的时候我想要延迟,是直接判断之后用的HAL_Delay(15000)实现的,但是在这15秒内,cpu只是停在那里发呆,是不能去做其他的事情的,这样的程序是不好的,如果想要cpu去做其他的事情怎么办呢?

(2)while(HAL_GetTick() - Start_time > 15000)延迟(存在时间回绕)

我又想到了一种方法,就是先获取一个时间Start_time,然后利用while(HAL_GetTick() - Start_time > 15000)来判断,但是这样存在一个问题,时间回绕问题。
分析:
uint32_t太大了,我们用uint8_t来简单分析一下。uint8_t范围为 0-255
假设我获取到的开始的时间Start_time为254,假设我要延迟 5 ,那么过了5 之后,时间回绕获取到的新的HAL_GetTick()为 255 0 1 2 3,获取到的为3。
3 - 255 > 5 ?,肯定是不对的。
所以这个办法也是不可取的。

(3)jiffies中的宏time_after()解决时间回绕及分析

后来就利用Linux内核中jiffies回绕解决方案,使用time_after()这个宏来解决这个问题。
我们来细讲一下这个jiffies中的宏time_after()怎么实现的:

全局变量jiffies用来记录自启动以来产生的节拍的总数。系统启动时会将该变量初始化为0,此后,每当时钟中断产生时就会增加该变量的值。这个和我们stm32中自增的uwTick类似。
其实就是将我们的uint32_t类型的转换为int类型的来进行比较。

#define time_after(a,b)  ( (int32_t)(b) - (int32_t)(a) < 0)

time-after就是判断过去的时间有没有在我们想要的时间后面,一到了后面就说明时间到了。假设我们想要5ms,获取的时间是10ms,那么真正的时间在(5+10)ms之后的话就说明5ms时间到了。也就是16秒,这样会比正常的时间多1ms。

要分析就需要在复习一下原码和补码:
正数的补码即为原码,负数的补码为原码除符号位外其他各位取反再加1
[+1]补码 = [+1]原码 = 0000 0001
[- 1]补码 = [- 1]原码取反+1 = 1111 1110 + 1 = 1111 1111

错误分析结果:
回到代码中,我们还是假设我们的HAL_GetTick()获取到的时间为uint8_t类型的,范围也就是0-255;
假设我们刚开始获取到的Start_time为254,无符号类型补码:1111 1110
过了5ms之后我们获取到的新的时间HAL_GetTick()为255 0 1 2 3,为3,无符号补码形式为0000 0011
我们需要暂停时间为5ms,也就是0000 0101
来比较一下:0000 0011 - 1111 1110 > 0000 0101?,显然是错误的。

正确分析结果:
但是使用宏time_after就成为了int类型的。
Start_time:按照有符号数读取,首先从补码转换成原码:10000010,转换成十进制为-2;
HAL_GetTick():按照有符号读取,首先从补码转换成原码:0000 0011,转换成十进制:3;
5ms:按照有符号读取,首先从补码转换成源码:0000 0011,转换成十进制:5;

刚好3 - (-2) = 5,也就是说刚好过了五秒,完美。宏time_after()就是需要往后在读一秒判断HAL_GetTick()获取到的时间有没有在5+(-2)之后,也就是第4秒的时候时间到了。


4- 代码验证3(3)

因为在实际情况中我们的main函数中 HAL_GetTick() 获取到的 uwtick 是 uint32_t 类型的,而且宏time_after() 中的类型也是int类型32位的,所以是匹配的。
测试中我用的是 uint8_t 类型的,所以对应的也只能使用 int8_t 类型的。
代码如下:

/*************************************************************************
    > File Name: a.c
    > Author: WangDengtao
    > Mail: 1799055460@qq.com 
    > Created Time: 2022年10月26日 星期三 10时22分24秒
 ************************************************************************/

#include <stdio.h>
#include <stdint.h>

int main(int argc, char *argv[])
{
	uint8_t Start_time1 = 254;
	printf("Start_time1:    %d\n", Start_time1);
	uint8_t HAL_GetTick1 = 254+5;
	printf("HAL_GetTick1:   %d\n", HAL_GetTick1);
	printf("HAL_GetTick1 - Start_time1 = %d\n", HAL_GetTick1 - Start_time1);
	
	printf("\n");

	int8_t Start_time2 = 254;
	printf("Start_time2:    %d\n", Start_time2);
	int8_t HAL_GetTick2 = Start_time2 + 5;
	printf("HAL_GetTick2:   %d\n", HAL_GetTick2);
	printf("HAL_GetTick2 - Start_time2 = %d\n", HAL_GetTick2 - Start_time2);

	return 0;
}

测试结果和分析的是一致的,说明还是没有问题的。
在这里插入图片描述


如有错误还请指出~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值