一、项目介绍
一直想在办公桌上摆个精致的小车,工作疲倦时,通过语音控制小车移动,用来舒缓疲倦。对小车的要求,体积不能太大,不能太占地方。运动起来噪音不能太大,不然会太吵。语音识别需要准确、灵敏。
二、硬件介绍
小车的整体功能,一直没能做出一个完善的规划。于是将小车做模块化设计,这次主要是设计小车的运动底盘,对小车的控制部分以后设计完善了,再进行设计。

小车的动力部分使用两个N20减速电机。电机驱动使用TB6612芯片。小车底盘使用一节锂电池供电,使用TP5400对锂电池进行充放电管理,并将锂电池升压到5V,做为电机驱动的动力电源,并通过接口,给主控提供电源。
小车底板还使用了一颗ADI厂家的ADP122AUJZ-3.3-R7芯片,给板子提供3.3V电压,用来给TB6612和两颗逻辑芯片供电。
小车的主控部分使用了XIAO Esp32s3 sense。主控部分暂时没有做电路板,直接使用杜邦线与底板连接。这个板子上自带了PDM麦克风,用来拾音。


底板PCB同时也是小车底盘的结构件。N20电机通过支架直接固定在PCB板子上。采用3轮设计,除了两个N20橡胶轮外,还是用了一个万向轮做为支撑。

三、系统实现
小车底盘使用PWM驱动,左右轮各自独立,所以使用两路PWM方波控制。两个电机的运动方向由两个管脚控制。分别使用高、低来决定电机的运动方向。底板上两个逻辑芯片将主控送来的0、1电平,分成(1,0)(0,1)信号传送给TB6612芯片,就实现了电机的正反转驱动。编程使用了Vscode+ESP-IDF5.5实现。这里使用了乐鑫提供的esp-sr组件来实现,命令字的唤醒和语音命令的识别。

ESP32S3使用了FreeRtos操作系统,创建了三个进程,其中两个进程优先级一样高,一个负责来监听麦克风,一个用来实现语音唤醒和语音命令识别。还有一个进程优先级较低,用来驱动小车底盘。

XIAO Esp32s3 sense开发板自带PDM麦克风,使用I2S进行读取,这里乐鑫ESP-IDF有点奇怪,已经制定了I2S是单通道音频,但是在读取时依然会以双声道音频方式读取,但是读到的数据确只有一半(只读到单声道音频信号),只好手工处理将单声道音频拓展到双声道。
esp_err_t bsp_get_feed_data(bool is_get_raw_channel, int16_t *buffer, int buffer_len)
{
// afe入口是双声道,但是这里获取的声音是单声道的。
esp_err_t ret = ESP_OK;
size_t bytes_read;
int audio_chunksize = buffer_len / (sizeof(int32_t));
// ret = i2s_channel_read(rx_handle, buffer, audio_chunksize*sizeof(int16_t), &bytes_read, portMAX_DELAY);
ret = i2s_channel_read(rx_handle, buffer, audio_chunksize*sizeof(int16_t), &bytes_read, portMAX_DELAY);
//扩展到双声道
for(int i = audio_chunksize-1; i >=0; i--){
buffer[i*2] = buffer[i];
buffer[i*2+1] = 0;
}
return ret;
}
乐鑫提供的组件esp-sr可以挑选唤醒词,可以设置2个唤醒词,这里我选择的是:“小鸭小鸭”和“喵喵同学”。

系统通过上述两个关键词唤醒,然后就可以发布语音命令了。通过语音命令来控制小车的动作。这里我设置了前、后、左、右四个动作,对小车进行控制。
esp_mn_commands_clear();
esp_mn_commands_add(1, (char *)"qian jin"); //前进
esp_mn_commands_add(1, (char *)"xiang qian jin");
esp_mn_commands_add(2, (char *)"hou tui"); //后退
esp_mn_commands_add(2, (char *)"xiang hou tui");
esp_mn_commands_add(3, (char *)"zuo zhuan"); //左转
esp_mn_commands_add(3, (char *)"xiang zuo zhuan");
esp_mn_commands_add(4, (char *)"you zhuan"); //右转
esp_mn_commands_add(4, (char *)"xiang you zhuan");
esp_mn_commands_update();
考虑到这是个桌面小车,桌面空间有限,所以每个动作都只执行1秒就停止。因为电机没有码盘,系统也没有安装陀螺仪,电机驱动没有做到闭环处理,现在只是给定了30%的PWM比例方波进行动作,这个比例是实际测试获得的,速度不快,但又能带得动小车运动。
// 添加小车运动控制任务
void car_Task(void *arg)
{
motor_init(); // 初始化电机
bool action = false;
while (task_flag)
{
// ESP_LOGI(Tag, "Car action: %d", car_action);
switch (car_action)
{
case 1:
ESP_LOGI(Tag, "Car: Forward");
motor_set_speed(MOTOR_LEFT, -30);
motor_set_speed(MOTOR_RIGHT, -30);
action = true;
vTaskDelay(1000 / portTICK_PERIOD_MS); // 动作持续 1 秒
break;
case 2:
ESP_LOGI(Tag, "Car: Backward");
motor_set_speed(MOTOR_LEFT, 30);
motor_set_speed(MOTOR_RIGHT, 30);
action = true;
vTaskDelay(1000 / portTICK_PERIOD_MS); // 动作持续 1 秒
break;
case 3:
ESP_LOGI(Tag, "Car: Turn Left");
motor_set_speed(MOTOR_LEFT, -20);
motor_set_speed(MOTOR_RIGHT, 20);
action = true;
vTaskDelay(1000 / portTICK_PERIOD_MS); // 动作持续 1 秒
break;
case 4:
ESP_LOGI(Tag, "Car: Turn Right");
motor_set_speed(MOTOR_LEFT, 20);
motor_set_speed(MOTOR_RIGHT, -20);
action = true;
vTaskDelay(1000 / portTICK_PERIOD_MS); // 动作持续 1 秒
break;
default:
// ESP_LOGI(Tag, "Car: Stop");
motor_set_speed(MOTOR_LEFT, 0);
motor_set_speed(MOTOR_RIGHT, 0);
break;
}
vTaskDelay(20 / portTICK_PERIOD_MS);
if(action){
car_action = 0;
action = false;
}
}
motor_set_speed(MOTOR_LEFT, 0);
motor_set_speed(MOTOR_RIGHT, 0);
vTaskDelete(NULL);
}
四、效果展示
小车充电状态。

待机状态,随时听从命令。

唤醒状态,唤醒后XIAO Esp32s3 sense开发板上的黄色LED灯会闪烁。

运动状态

五、心得体会:
感谢得捷和硬禾科技共同举办的FastBond活动,平时心头的一点小想法,借助这次活动,将它变为现实。虽然还有很多不完美,但是却给了自己满满的成就感!