项目背景

         大模型的浪潮还在汹涌的向前,伴随着大模型的浪潮,很多基于大模型的各种玩具类也迎来白热化的阶段,小编今天推荐一款低成本的核心板,可以基于这款开发版快速的打造一台包含智能对话的玩具小车。

硬件介绍

        这款开发板包含:一路数字MIC,一路扬声器喇叭输出,可以同时控制四路直流马达,支持OV2610摄像头,供电支持18650 3.7V供电,电池充电保护,主控采用的是ESP32S3-N16R8所有的GPIO都已经引出,可以基于这款开发版,进行多种个性化应用的开发。https://item.taobao.com/item.htm?ft=t&id=965144824762

功能流程图

整体的控制流程
上位机

        上位机使用GO语言Fyne框架开发,Fyne,这是一款基于 Go 语言开发的开源 GUI 框架。Fyne 旨在为开发者提供一种简单、现代且一致的用户界面体验,支持在 WindowsmacOSLinux移动设备(iOS/Android 上运行。 与其他 GUI 框架相比,Fyne 的最大特点是完全使用 Go 语言开发,结合了 Go 的简洁和高效特性,非常适合快速构建跨平台桌面和移动应用。

TB6612驱动

        TB6612核心PWM的产生,使用的ESP-IDF LEDC模块,可以高精度的产生不同频率的PWM波形,通过不同频率的PWM,控制马达的转速,驱动的代码如下:

static void drv_gpio_init(int gpio, int level)
{
    gpio_config_t gpio_conf = {
        .pin_bit_mask = (1ULL << gpio), // 选择 GPIO
        .mode = GPIO_MODE_OUTPUT,                 // 配置为输出模式
        .pull_up_en = GPIO_PULLUP_DISABLE,        // 关闭上拉
        .pull_down_en = GPIO_PULLDOWN_DISABLE,    // 关闭下拉
        .intr_type = GPIO_INTR_DISABLE            // 关闭中断
    };
    gpio_config(&gpio_conf);
    gpio_set_level(gpio, level);
}

void esp32s3_bsp_motor_init(void)
{
    ledc_timer_config_t motor_timer = {
        .speed_mode       = LEDC_LOW_SPEED_MODE,
        .duty_resolution  = MOTOR_PWM_DUTY_RES,
        .timer_num        = MOTOR_PWM_TIMER,
        .freq_hz          = MOTOR_PWM_FREQUENCY_HZ,  // Set output frequency at 4 kHz
        .clk_cfg          = LEDC_AUTO_CLK
    };
    ESP_ERROR_CHECK(ledc_timer_config(&motor_timer));
    ledc_channel_config_t motor_channel_0 = {
        .speed_mode     = MOTOR_PWM_MODE,
        .channel        = MOTOR_PWM_A_CHANNEL,
        .timer_sel      = MOTOR_PWM_TIMER,
        .intr_type      = LEDC_INTR_DISABLE,
        .gpio_num       = MOTOR_PWM_A_GPIO,
        .duty           = 0, // Set duty to 0%
        .hpoint         = 0
    };
    ESP_ERROR_CHECK(ledc_channel_config(&motor_channel_0));
    ledc_channel_config_t motor_channel_1 = {
        .speed_mode     = MOTOR_PWM_MODE,
        .channel        = MOTOR_PWM_B_CHANNEL,
        .timer_sel      = MOTOR_PWM_TIMER,
        .intr_type      = LEDC_INTR_DISABLE,
        .gpio_num       = MOTOR_PWM_B_GPIO,
        .duty           = 0, // Set duty to 0%
        .hpoint         = 0
    };
    ESP_ERROR_CHECK(ledc_channel_config(&motor_channel_1));
    drv_gpio_init(MOTOR_A_CTL_GPIO_1, GPIO_LEVEL_LOW);
    drv_gpio_init(MOTOR_A_CTL_GPIO_2, GPIO_LEVEL_LOW);
    drv_gpio_init(MOTOR_B_CTL_GPIO_1, GPIO_LEVEL_LOW);
    drv_gpio_init(MOTOR_B_CTL_GPIO_2, GPIO_LEVEL_LOW);
    drv_gpio_init(MOTOR_C_CTL_GPIO_1, GPIO_LEVEL_LOW);
    drv_gpio_init(MOTOR_C_CTL_GPIO_2, GPIO_LEVEL_LOW);
    drv_gpio_init(MOTOR_D_CTL_GPIO_1, GPIO_LEVEL_LOW);
    drv_gpio_init(MOTOR_D_CTL_GPIO_2, GPIO_LEVEL_LOW);
}

void esp32s3_bsp_set_gpio_level(int gpio, int level)
{
    gpio_set_level(gpio, level);
}

void esp32s3_bsp_set_pwm_duty(int channel, int duty)
{
    ESP_ERROR_CHECK(ledc_set_duty(MOTOR_PWM_MODE, channel, duty));
    ESP_ERROR_CHECK(ledc_update_duty(MOTOR_PWM_MODE, channel));
}
Websoket客户端

        通过websocket协议与上位机进行通信,解析上位机发送的指令,控制直流电机的速度,以及小车的方向,客户端的代码如下:

 static void handle_motor_event(const char *event)
{
    if (0 == strcmp(event,"stop")) {
        app_motor_stop();
    }
    if (0 == strcmp(event,"left")) {
        app_motor_left();
    }
    if (0 == strcmp(event,"right")) {
        app_motor_right();
    }
    if (0 == strcmp(event,"forward")) {
        app_motor_forward();
    }
    if (0 == strcmp(event,"backward")) {
        app_motor_backward();
    }
    if (0 == strcmp(event,"break")) {
        app_motor_break();
    }
    if (0 == strcmp(event,"forwardUp")) {
        app_motor_forward_arm_up();
    }
    if (0 == strcmp(event,"forwardStop")) {
        app_motor_forward_arm_stop();
    }
    if (0 == strcmp(event,"backwardUp")) {
        app_motor_forward_arm_down();
    }
    if (0 == strcmp(event,"backwardStop")) {
        app_motor_forward_arm_stop();
    }
}

static void handle_pan_tilt_event(const char *event)
{
    if (0 == strcmp(event,"up")) {
        app_motor_backward_arm_up();
    }
    if (0 == strcmp(event,"down")) {
        app_motor_backward_arm_down();
    }
    if (0 == strcmp(event,"stop")) {
        app_motor_backward_arm_stop();
    }
}

static void phrase_ws_recv_buffer(const char *buf)
{
    char method[32] ={0};
    char value[32] ={0};
    cJSON *root = cJSON_Parse(buf);
    if (NULL == root) {
        ESP_LOGE(TAG, "Phrase mqtt recv buffer error");
        return;
    }
    cJSON *filed_obj = cJSON_GetObjectItem(root,"method");
    if (NULL != filed_obj) {
        strncpy(method, filed_obj->valuestring, sizeof(method)-1);
    }
    filed_obj = cJSON_GetObjectItem(root,"value");
    if (NULL != filed_obj) {
        strncpy(value, filed_obj->valuestring, sizeof(value)-1);
    }
    if (0 == strcmp(method,"dirCtl")) {
        handle_motor_event(value);
    }
    if (0 == strcmp(method,"ptzCtl")) {
        handle_pan_tilt_event(value);
    }
    if (0 == strcmp(method,"fileSave")) {
        strncpy(s_file_name,value,sizeof(s_file_name)-1);
    }
    if (0 == strcmp(method,"fileEnd")) {
        memset(s_file_name, 0, sizeof(s_file_name));
         if (NULL != s_save_file_fp) {
             fclose(s_save_file_fp);
         }
        s_save_file_fp = NULL;
    }
    cJSON_Delete(root);
}


static void ws_tools_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
{
    esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data;
    switch (event_id) {
        case WEBSOCKET_EVENT_BEGIN:
            ESP_LOGI(TAG, "WEBSOCKET_EVENT_BEGIN");
            break;
        case WEBSOCKET_EVENT_CONNECTED:
            s_camera_send_flag = 1;
            ESP_LOGI(TAG, "WEBSOCKET_EVENT_CONNECTED");
            break;
        case WEBSOCKET_EVENT_DISCONNECTED:
            ESP_LOGI(TAG, "WEBSOCKET_EVENT_DISCONNECTED");
            break;
        case WEBSOCKET_EVENT_DATA:
            if (data->op_code == 0x08 && data->data_len == 2) {
                ESP_LOGW(TAG, "Received closed message with code=%d", 256 * data->data_ptr[0] + data->data_ptr[1]);
            }else {
                if (data->data_len > 0) {
                    if (data->op_code == 0x1) {
                        memset(s_test_buf, 0, sizeof(s_test_buf));
                        memcpy(s_test_buf, data->data_ptr, data->data_len);
                        ESP_LOGI(TAG,"Receive Data %s",s_test_buf);
                        phrase_ws_recv_buffer(s_test_buf);
                    }
                    if (data->op_code == 0x2) {
                        save_data_file_to_spiffs(data->data_ptr,data->data_len);
                    }
                }
            }
            break;
        case WEBSOCKET_EVENT_ERROR:
            ESP_LOGI(TAG, "WEBSOCKET_EVENT_ERROR");
            break;
        case WEBSOCKET_EVENT_FINISH:
            ESP_LOGI(TAG, "WEBSOCKET_EVENT_FINISH");
            break;
        case  WEBSOCKET_EVENT_CLOSED:
            ESP_LOGI(TAG, "WEBSOCKET_EVENT_CLOSED");
            break;
    }
}


void init_toos_ws_client()
{
    s_send_pic = (struct camera_detach_pic_t *)malloc(sizeof(struct camera_detach_pic_t));
    assert(s_send_pic);
    esp_websocket_client_config_t websocket_cfg = {};
    //websocket_cfg.uri = s_app_toos.ws_addr;
    websocket_cfg.uri = APP_TOOLS_WS_ADDR;
    websocket_cfg.reconnect_timeout_ms = 5000;
    s_app_toos.ws_client = esp_websocket_client_init(&websocket_cfg);
    esp_websocket_register_events(s_app_toos.ws_client, WEBSOCKET_EVENT_ANY, ws_tools_event_handler, (void *)s_app_toos.ws_client);
    esp_websocket_client_start(s_app_toos.ws_client);
    xTaskCreatePinnedToCore(camera_frame_send_task,"Camera Task", 4 * 1024, NULL, 5, NULL, 0);
}
代码仓库

https://gitee.com/acccode/smart-car

运行效果代码

智能小车

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐