作为学习stm32f103c8t6阶段的收官项目,这里做下总结,源码放在了最后。
一、功能描述
- 1、跟随功能
- 2、循迹功能
- 3、避障功能
- 4、测速功能
- 5、温湿度常显
- 6、oled显示
- 7、语音或蓝牙进行功能切换
- 8、手势功能
二、主要代码解析
2.1、main
main函数主要展示while里的功能,具体的实现在car_function文件内。
int main(void)
{
HAL_Init();
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_TIM2_Init();
MX_TIM4_Init();
MX_TIM1_Init();
MX_TIM3_Init();
MX_I2C1_Init();
init();
while (1)
{
get_mode();
reset();
switch(runMode) {
case tracingMode:
traceing();
break;
case followMode:
follow();
break;
case avoidMode:
avoid();
break;
case stopMode:
stop_car();
break;
}
display_temp_humi();
}
}
2.2、init
初始化一个是要开启串口中断,另外要把电机旋转90让超声波正对前方,再者就是显示空数据。
这里没有开启测速的中断TIM3,因为开启会影响DHT11的时序,导致其卡死到检测温湿度的while循环里。
void init() {
//开启串口中断蓝牙在用
HAL_UART_Receive_IT(&huart1, &buf, 1);
//开启pwm,并旋转至最前方
sg90_init();
//初始化oled
oled_init();
oled_clear_all();
oled_show_string(1,2,"mode : ready");
oled_show_string(2,2, "speed: 0cm/s");
oled_show_string(3,2, "Temp :--.--");
oled_show_string(4,2, "Temp :--.--");
}
2.3、display_temp_humi
停止模式时,温湿度正常在main 函数里正常检测,在小车的其它模式,要限制温湿度检测的频率,否则会影响小车的运行。小车的其它模式要比温湿度优先级高。在非停止模式时,这里会计数,当计数到50w次时才会进行一次检测。
void display_temp_humi() {
// 停止模式时正常检测湿度,非停止模式,计数50w检测一次
if(runMode != stopMode) {
count ++;
if(count <= 500000) { return; }
count = 0;
}
count = 0;
//记得关中断,否则会影响DHT11采集数据
HAL_TIM_Base_Stop_IT(&htim3);
char msg[16];
uint8_t result = trig_dht();
receive_data();
oled_clear(4, 8, 56, 128);
sprintf(msg, "Temp : %d.%d C", datas[2], datas[3]);
oled_show_string(3,2,msg);
sprintf(msg, "Humi : %d.%d %%", datas[0], datas[1]);
oled_show_string(4,2,msg);;
HAL_Delay(500);
}
2.4、changeMode
切换电机的模式,PWM 是变速模式,主要用在寻迹模式,NORMAL是正常模式,用在跟随和避障模式。L0110s 部分引脚要接入到stm32具体pwm功能的引脚上。模式的切换要重新初始化相应的引脚。
void changeMode(uint8_t m) {
mode = m;
if(mode == NORMAL) {
HAL_TIM_PWM_Stop(&htim2,TIM_CHANNEL_1);
HAL_TIM_PWM_Stop(&htim2,TIM_CHANNEL_2);
init_port();
} else {
MX_TIM2_Init();
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_2);
HAL_Delay(500);
}
}
2.5、测速
定时器1s,查看外部中断进入了多少次。即可算出速度(当前测速模块有问题,速度偏大)
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
// 展示速度
//oled部分清屏
sprintf(speedString, "speed:%4dcm/s", speedCnt);
//old__clear_bottom_half();
oled_show_string(2,2,speedString);
speedCnt = 0;
}
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
if(GPIO_Pin != GPIO_PIN_10) {
return ;
}
if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_10) == GPIO_PIN_RESET)
speedCnt++;
}
2.6、循迹
循迹用的是pwm模式,原理是利用红外发射的光线有没有反射回来。
void traceing() {
if(runMode != lastMode) {
lastMode = runMode;
changeMode(PWM);
HAL_Delay(500);
// 处理oled
oled_clear_1_line();
oled_show_string(1,2,"mode : trace");
}
if(leftTraceValue() == GPIO_PIN_RESET && rightTraceValue() == GPIO_PIN_RESET) {
forward();
}
if(leftTraceValue() == GPIO_PIN_RESET && rightTraceValue() == GPIO_PIN_SET) {
leftward();
}
if(leftTraceValue() == GPIO_PIN_SET && rightTraceValue() == GPIO_PIN_RESET) {
rightward();
}
if(leftTraceValue() == GPIO_PIN_SET && rightTraceValue() == GPIO_PIN_SET) {
stop();
}
}
2.7、避障
主要是利用超声波检测左前右的障碍物的距离来决定如何前进。
void avoid() {
if(runMode != lastMode) {
lastMode = runMode;
changeMode(NORMAL);
// 处理oled
oled_clear_1_line();
oled_show_string(1,2,"mode : avoid");
HAL_Delay(500);
}
if(dir != MIDDLE) {
dir = MIDDLE;
turn_90_degree();
HAL_Delay(300);
}
disMiddle = get_distance();
if(disMiddle > 35) {
forward();
} else if(disMiddle < 10) {
backward();
} else {
stop();
turn_180_degree();
HAL_Delay(300);
disLeft = get_distance();
turn_90_degree();
HAL_Delay(300);
turn_0_degree();
dir = RIGHT;
HAL_Delay(300);
disRight = get_distance();
if(disLeft < disRight) {
rightward();
HAL_Delay(150);
stop();
}
if(disLeft > disRight){
leftward();
HAL_Delay(150);
stop();
}
}
}
2023-10-2 13点 超声波中断
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
//脉冲测速
if(GPIO_Pin == GPIO_PIN_10) {
if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_10) == GPIO_PIN_RESET)
speedCnt++;
}
//超声波echo
if(GPIO_Pin == GPIO_PIN_12) {
//while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_12) == GPIO_PIN_RESET);
HAL_TIM_Base_Start(&htim1);
__HAL_TIM_SetCounter(&htim1, 0);
while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_12) == GPIO_PIN_SET);
HAL_TIM_Base_Stop(&htim1);
int cnt = __HAL_TIM_GetCounter(&htim1);
distance = 340 * 0.000001 * cnt * 100 / 2;
}
}
2.8、跟随
原理同循迹,so esay。
void follow() {
if(runMode != lastMode) {
lastMode = runMode;
changeMode(NORMAL);
// 处理oled
oled_clear_1_line();
oled_show_string(1,2,"mode : follow");
HAL_Delay(100);
}
if(leftFollowValue() == GPIO_PIN_RESET && rightFollowValue() == GPIO_PIN_RESET) {
forward();
}
if(leftFollowValue() == GPIO_PIN_RESET && rightFollowValue() == GPIO_PIN_SET) {
leftward();
}
if(leftFollowValue() == GPIO_PIN_SET && rightFollowValue() == GPIO_PIN_RESET) {
rightward();
}
if(leftFollowValue() == GPIO_PIN_SET && rightFollowValue() == GPIO_PIN_SET) {
stop();
}
}
2.9、手势
添加手势模块paj7620, 手势不可与DHT11共存,会影响手势的获取。
void gesture() {
if(runMode != lastMode) {
lastMode = runMode;
changeMode(NORMAL);
// 处理oled
oled_clear_1_line();
oled_show_string(1,2,"mode : gesture");
HAL_Delay(500);
}
paj7620_action();
}
void paj7620_action(void) {
switch (status) {
case 0x01: // up
paj7620_up();
break;
case 0x02: // down
paj7620_down();
break;
case 0x04: // left
paj7620_left();
break;
case 0x08: // right
paj7620_right();
break;
case 0x10: // push
paj7620_push();
break;
case 0x20: // pop
paj7620_pop();
break;
case 0x40: // rotate right
paj7620_rotate_right();
break;
case 0x80: // rotate left
paj7620_rotate_left();
break;
case 0x100:// wave
paj7620_wave();
break;
case 0x00: // nothing
paj7620_nothing();
break;
default:
paj7620_error();
}
}
三、效果
stm32小车
四、问题记录
1、计数定时器(TIM3)中断影响DHT11单总线的时序,导致会卡死到检测温度的while里,导致执行不了main函数里while的其它功能(小车模式切换),但是定时器中断还是可以运行的。
2、同理也会卡超声波while,所以开启避障模式时也要把TIM3关了。(利用外部中断,不用再关TIM)
3、PWM模式和GPIO的功能切换,起初以为只能在初始化里使用一种模式,但在调试之后发现是可以进行转换的。
4、oled是可以进行局部清屏的,oled清屏函数记录-CSDN博客。
5、修改蓝牙波特率,用错了指令,导致一直未成功。HC08 AT指令
6、触发DHT11和获取数据之间不能插入printf函数,否则会卡死到 while 循环里,效果同1
7、小车变速PWM模式时,在使用CubeMx配置引脚时,要把所有引脚输出低电平,否则PWM会不起作用。
8、面板包接线容易松动,导致语音模式引脚输出的电平乱跳。
9、单片机和直流电机单独供电,防止因为电机电流的问题导致单片机复位。
10、paj7620刚开始上电之后需要复位才能正常运行,经调试在初始化的时,write时添加了300us的延时上电即可正常运行
五、配置
跟随功能:
PB5 : 左红外传感器,输入模式
PA15: 右红外传感器,输入模式
循迹功能(原理和跟随一样):
PB3 : 左红外传感器,输入模式
PB4: 左红外传感器,输入模式
避障功能:
超声波
PA11 : sr04中的trig引脚,输出模式
PA12: sr04中的echo引脚,
输入模式(外部中断模式)TIM1: 用来超声波的计时
舵机
TIM4(PB9): channel 4用来控制舵机的旋转角度
温湿度功能(DHT11):
PA8 : dht11中的Data引脚,(初始化时先配置成输入模式!!!,在需要的时候再切换成输出模式)
Oled
PB6(SCL) : 使用stm32 IIC1接口中的SCL
PB7(SDA): 使用stm32 IIC1接口中的SDA
蓝牙(hc04)
PA9(USART1_TX) : 使用stm32 串口1中的输出
PA10(USART1_RX): 使用stm32 串口1中的输入
语音(su03)
PB11: 对应su03的a25引脚,输入模式
PB12: 对应su03的a26引脚,输入模式
PB13: 对应su03的a27引脚,输入模式
直流电机 和 L9110S模块
PA0: 对应L9110s的B-1B引脚,TIM2中channle 1 的PWM模式
PA1: 对应L9110s的A-1B引脚,TIM2中channle 2 的PWM模式
PB0: 对应L9110s的B-1A引脚
PB2: 对应L9110s的A-1A引脚
l9110s中接线会影响电机的前进和后退,根据实际情况来处理引脚的配置,这里PA0 和 PA1 是控制前面的速度,PB0和PB2没有进行控制
测速
PB10: 对应模块的out引脚,外部中断
手势(paj7620)
PA5: 对应paj7620的scl引脚,输出模式
PA6: 对应paj7620的sda引脚,输出模式
PA7: 对应paj7620的int引脚,外部下降沿中断
六、修订
2023-10-2 13点:将超声波回应利用外部中断方式,这样在避障模式下,也可以进行测速
2023-10-4 :添加手势模块