文章目录
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;
}
测试结果和分析的是一致的,说明还是没有问题的。
如有错误还请指出~