一、项目需求
- 检测靠近时,垃圾桶自动开盖并伴随滴一声,2秒后关盖
- 发生震动时,垃圾桶自动开盖并伴随滴一声,2秒后关盖
- 按下按键时,垃圾桶自动开盖并伴随滴一声,2秒后关盖
硬件介绍
sg90舵机
PWM 波的频率不能太高,大约 50HZ ,即周期 =1/ 频率 =1/50=0.02s , 20ms 左右。角度控制PWM的有效电平为高电平0.5ms-------------0 度; 2.5% 对应函数中 CCRx 为5, 对应函数中占空比为5%1.0ms------------45 度; 5.0% 对应函数中 CCRx 为10, 对应函数中占空比为10%1.5ms------------90 度; 7.5% 对应函数中 CCRx 为15, 对应函数中占空比为15%2.0ms-----------135 度; 10.0% 对应函数中 CCRx 为20, 对应函数中占空比为20%2.5ms-----------180 度; 12.5% 对应函数中 CCRx 为25, 对应函数中占空比为25%
震动传感器
产生震动时,会输出低电平,绿色指示灯亮(开关信号指示灯)
蜂鸣器
低电平触发
超声波传感器
- 怎么让它发送波
Trig ,给 Trig 端口至少 10us 的高电平
- 怎么知道它开始发了
Echo信号,由低电平跳转到高电平,表示开始发送波
- 怎么知道接收了返回波
Echo,由高电平跳转回低电平,表示波回来了
- 怎么算时间
Echo引脚维持高电平的时间!
- 波发出去的那一下,开始启动定时器
波回来的拿一下,我们开始停止定时器,计算出中间经过多少时间
- 怎么算距离
距离 = 速度 ( 340m/s ) * 时间 /2
二、使用STM32控制舵机SG90
已知,舵机的驱动要使用周期20ms左右的PWM波,并通过调整占空比x来控制舵机的角度:
那如果我现在想要让舵机每隔1S转动一个角度,使得角度为0,45,90,135,180,0度。
那就可以使用我上节实现呼吸灯的CubeMX项目,对其进行修改:
1. 由于舵机中角度的计算是和高电平占周期的比例来换算的,所以要将CH Polarity改为High
2. 由于舵机需要的PWM的周期是20ms,则可以设置PSC = 7199, ARR = 199,这样当Tclk = 72M时,周期正好是0.02s,即20ms。
3. 这就设置好了,更新项目并打开Keil,修改main.c中的main函数,根据公式:
我选用的时钟源为 72 MHz,因此将预分频系数(PSC)设置为7200-1 = 7199,分频后为 10000Hz,将装载值设置为(ARR) 200-1 = 199,所以得到 50Hz 的频率,即20ms的周期。
占空比计算:CCRx / ARR
HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_3);
while (1)
{
HAL_Delay(1000);
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_3, 5);//设置CCRx为5,占空比为2.5 * 20ms = 0.5s
HAL_Delay(1000);
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_3, 10);//设置CCRx为10,占空比为5% * 20ms =1s
HAL_Delay(1000);
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_3, 15);//设置CCRx为15,占空比为7.5% * 20ms =1.5s
HAL_Delay(1000);
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_3, 20);//设置CCRx为20,占空比为10% * 20ms =2s
HAL_Delay(1000);
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_3, 25);//设置CCRx为25,占空比为12.5% * 20ms = 2.5s
}
三、使用STM32控制无线测距模块HC-SR04
需求:使用超声波测距,当手离传感器距离小于 5cm 时, LED1 点亮,否则保持不亮状态。
接线:Trig --- PB6 设置成GPIO_output( Trig是单片机发给HCSR04的信号)Echo --- PB7 设置成GPIO_input( Echo是HCSR04发回单片机的信号)LED1 --- PB8
1.由于要测距离,根据超声波传感器的原理,需要用到一个定时器来算距离,故需要使用一个TIM2,并且只用来作为计数功能,并软件控制何时停止计数,因此只需要设置TIM2的PSC而不需要设置ARR
计数一次经过的时间是 (PSC + 1) / Tclk , 因此如果我想要计数1微秒,即0.000001s, 已知Tclk = 72 MHz, 那么PSC就应该设置为 71。然后在main.c中就可以定义出一个实现微秒级延时的函数:
//使用TIM2来做us级延时函数 void TIM2_Delay_us(uint16_t n_us) { /* 使能定时器2计数 */ __HAL_TIM_ENABLE(&htim2); __HAL_TIM_SetCounter(&htim2, 0); while(__HAL_TIM_GetCounter(&htim2) < ((1 * n_us)-1) ); /* 关闭定时器2计数 */ __HAL_TIM_DISABLE(&htim2); }
然后根据echo和trig的状态设置GPIO口的引脚模式即可(Trig是单片机发给HCSR04的信号,Echo是HCSR04发回单片机的信号)
2.代码实现
//使用TIM2来做us级延时函数
void TIM2_Delay_us(uint16_t n_us)
{
/* 使能定时器2计数 */
__HAL_TIM_ENABLE(&htim2);
__HAL_TIM_SetCounter(&htim2, 0);
while(__HAL_TIM_GetCounter(&htim2) < ((1 * n_us)-1) );
/* 关闭定时器2计数 */
__HAL_TIM_DISABLE(&htim2);
}
int main(void)
{
int cnt = 0;
float distance = 0;
while (1)
{
//1. Trig ,给Trig端口至少10us的高电平
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET);//Trig拉低
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET);//Trig拉高
TIM2_Delay_us(20);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET);//Trig拉低
//2. echo由低电平跳转到高电平,表示开始发送波
//波发出去的那一下,开始启动定时器
while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7) == GPIO_PIN_RESET);//等待输入电平拉高
HAL_TIM_Base_Start(&htim2);
__HAL_TIM_SetCounter(&htim2,0); //将TIM2的计数器置0
//3. 由高电平跳转回低电平,表示波回来了
while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7) == GPIO_PIN_SET);//等待输入电平变低
//波回来的那一下,我们开始停止定时器
HAL_TIM_Base_Stop(&htim2);
//4. 计算出中间经过多少时间
cnt = __HAL_TIM_GetCounter(&htim2);//求出计了多少次,由于计数一次经过的时间是1us
//5. 距离 = 速度 (340m/s)* 时间/2(计数1次表示1us)
distance = cnt*340/2*0.000001*100; //单位:cm
if(distance < 5)
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);
else
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);
//每500毫秒测试一次距离
HAL_Delay(500);
}
四、将SG90与HC-SR04合并
由于HC-SR04为测距离已经使用了一个定时器Timer2,而SG90的PWM波的生成也需要使用定时器,所以只能从STM32的其他未使用的定时器查找,使用产品手册查询得知
Timer3的4个通道的默认引脚为PA6、PA7、PB0、PB1,复用引脚为PC6-9(此开发板没有这几个引脚,故无法使用)
Timer4同理
int cnt = 0;
float distance = 0;
//使用TIM2来做us级延时函数
void TIM2_Delay_us(uint16_t n_us)
{
/* 使能定时器2计数 */
__HAL_TIM_ENABLE(&htim2);
__HAL_TIM_SetCounter(&htim2, 0);
while(__HAL_TIM_GetCounter(&htim2) < ((1 * n_us)-1) );
/* 关闭定时器2计数 */
__HAL_TIM_DISABLE(&htim2);
}
void init_SG90()
{
HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_4); //打开Timer4的4号Channe
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_4, 5); //将舵机置为0度
}
void Open_dustbin()
{
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_4, 15); //将舵机置为90度
HAL_Delay(2000);
}
void Close_dustbin()
{
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_4, 5); //将舵机置为0度
HAL_Delay(150);
}
void deal_distance()
{
//1. Trig ,给Trig端口至少10us的高电平
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET);//Trig拉低
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET);//Trig拉高
TIM2_Delay_us(20);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET);//Trig拉低
//2. echo由低电平跳转到高电平,表示开始发送波
//波发出去的那一下,开始启动定时器
while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7) == GPIO_PIN_RESET);//等待输入电平拉高
HAL_TIM_Base_Start(&htim2);
__HAL_TIM_SetCounter(&htim2,0); //将TIM2的计数器置0
//3. 由高电平跳转回低电平,表示波回来了
while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7) == GPIO_PIN_SET);//等待输入电平变低
//波回来的那一下,我们开始停止定时器
HAL_TIM_Base_Stop(&htim2);
//4. 计算出中间经过多少时间
cnt = __HAL_TIM_GetCounter(&htim2);//求出计了多少次,由于计数一次经过的时间是1us
//5. 距离 = 速度 (340m/s)* 时间/2(计数1次表示1us)
distance = cnt*340/2*0.000001*100; //单位:cm
if(distance < 5)
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);//点亮LED
Open_dustbin();//开盖
}
else
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);//熄灭LED
Close_dustbin();//关盖
}
}
int main(void)
{
init_SG90();
while (1)
{
deal_distance();
}
}
上述代码在逻辑上没啥问题,只是电机因为超声波传感器检测不当而引起的旋转异常,还有可能因为开关盖时冲突而导致的快速旋转或者抽搐,将在下文寻找解决办法(最终发现是时钟未设置好,要设置成72MHz的频率)
五、感应开关盖垃圾桶实现
超声波模块:Trig -- PB6 outputEcho -- PB7 inputsg90 舵机: PWM -- PB9 Timer4channel4PWM控制按键:KEY1 -- PA0 开盖exti0KEY2 -- PA1 关盖exti1LED 灯:LED1 -- PB8震动传感器:D0 -- PB5 exti5VCC -- 5V蜂鸣器:IO -- PB4 outputVCC -- 3V3
按键和振动传感器设置为下降沿触发
打开中端
#define OPEN 1
#define CLOSE 0
char flag = CLOSE;
void TIM2_Delay_us(uint16_t n_us)
{
/* 使能定时器2计数 */
__HAL_TIM_ENABLE(&htim2);
__HAL_TIM_SetCounter(&htim2, 0);
while(__HAL_TIM_GetCounter(&htim2) < ((1 * n_us)-1) );
/* 关闭定时器2计数 */
__HAL_TIM_DISABLE(&htim2);
}
void init_SG90()
{
HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_4); //打开Timer4的4号Channe
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_4, 5); //将舵机置为0度
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);//熄灭LED
}
void Open_dustbin()
{
if(flag == CLOSE)
{
flag = OPEN;
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_4, 15); //将舵机置为90度
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);//打开LED
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_4, GPIO_PIN_RESET);//蜂鸣器响应
HAL_Delay(100);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_4, GPIO_PIN_SET);//蜂鸣器关闭
}
HAL_Delay(2000);
}
void Close_dustbin()
{
flag = CLOSE;
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_4, 5); //将舵机置为0度
HAL_Delay(150);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);//熄灭LED
}
double get_distance()
{
int cnt=0;
//1. Trig ,给Trig端口至少10us的高电平
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET);//Trig拉高
TIM2_Delay_us(20);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET);//Trig拉低
//2. echo由低电平跳转到高电平,表示开始发送波
//波发出去的那一下,开始启动定时器
while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7) == GPIO_PIN_RESET);//等待输入电平拉高
HAL_TIM_Base_Start(&htim2);
__HAL_TIM_SetCounter(&htim2,0); //将TIM2的计数器置0
//3. 由高电平跳转回低电平,表示波回来了
while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7) == GPIO_PIN_SET);//等待输入电平变低
//波回来的那一下,我们开始停止定时器
HAL_TIM_Base_Stop(&htim2);
//4. 计算出中间经过多少时间
cnt = __HAL_TIM_GetCounter(&htim2);//求出计了多少次,由于计数一次经过的时间是1us
//5. 距离 = 速度 (340m/s)* 时间/2(计数1次表示1us)
return (cnt*340/2*0.000001*100); //单位:cm
}
int main(void)
{
float distance = 0;
init_SG90();
HAL_NVIC_SetPriority(SysTick_IRQn,0,0);
while (1)
{
get_distance();
if(distance < 5)
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);//点亮LED
Open_dustbin();//开盖
}
else
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);//熄灭LED
Close_dustbin();//关盖
}
}
}