主题
🔴 ARD.C.1 — 上手完成第一台遥控车
🔴 从点亮第一颗 LED 到通过蓝牙遥控小车,共 9 节内容。
在本章结束时,你将得到一辆可以遥控的灵活电动小车,或许可以开始你们的碰碰车大赛。
📦 物资清单
| 类别 | 物品 | 数量 | 备注 |
|---|---|---|---|
| 第 1 章 | Arduino Uno R3 开发板 | × 1 | 主控板 |
| USB 数据线(USB-A 转 USB-B) | × 1 | 烧录与供电 | |
| 面包板 | × 1 | 搭建电路 | |
| 公对公杜邦线 | 若干 | 电路连接 | |
| LED(任意颜色) | × 1 | GPIO 输出实验 | |
| 电阻(220Ω~510Ω) | × 1 | LED 限流 | |
| 轻触按键 | × 1 | GPIO 输入实验 | |
| TB6612FNG 电机驱动模块 | × 1 | 驱动直流电机 | |
| 带编码器的 TT 马达 | × 2 | 左右轮动力 + 测速 | |
| 两节 18650 电池 + 电池盒 | × 1 | 小车供电 | |
| 5V DC-DC 降压模块(MP1584 等) | × 1 | 降压稳压 | |
| HC-05 蓝牙模块 | × 1 | 无线遥控通信 | |
| 公对母杜邦线 | 若干 | 模块连接 | |
| 第 2 章新增 | HC-SR04 超声波测距模块 | × 1 | 避障测距 |
| SG90(或类似)舵机 | × 1 | 雷达扫描 | |
| 第 3 章新增 | Arduino Uno R3 开发板 | × 1 | 遥控器端主控 |
| MPU6050 六轴陀螺仪/加速度计 | × 1 | 姿态检测 | |
| SSD1306 OLED 128×64 显示屏 | × 1 | 遥控器显示 | |
| 双轴模拟摇杆(含按键) | × 1 | 遥控器控制 | |
| HC-05 蓝牙模块 | × 1 | 与第 1 章组成主从对 | |
| 轻触按键 | × 4 | 遥控器功能按键 | |
| PCB 打样 | (可选) | 第 3.6 节制作遥控器 PCB |
ARD.C.1.0 — 万事开头难:项目介绍与环境搭建
本节我们将做:
- 了解如何将程序编译链接烧录到板子上并运行,选择最官方最现代化最AI化的开发方案
- 下载并安装 VS Code
- 安装 PlatformIO 插件
- 测试编译与烧录
- 完成 Deepseek API Key 接入 Chat聊天 工具
核心知识点:
- 开发流程:编写代码 → 编译 → 烧录 → 运行

相关资源:
ARD.C.1.1 — 从一束光开始:GPIO 推挽输出
本节我们将做:
- 编写第一个 Arduino 程序,点亮板载 LED(D13)
- 控制 LED 以 1 秒间隔闪烁
核心知识点:
- 硬件外设、寄存器:CPU执行程序时,会与硬件外设的寄存器交流。
- GPIO通用输入输出-推挽输出:控制该引脚连接到GND或VCC。

技能点:
- 函数调用、while循环
cpp
//******** ARD.C.1.1 *********//
#include <Arduino.h>
void init_led()
{
// 初始化 GPIO 13 引脚为输出模式
pinMode(13, OUTPUT); // 初始化 GPIO 13 引脚
digitalWrite(13, LOW); // 将 GPIO 13 引脚设置为低电平(LED 关闭)
return;
}
int main()
{
// 初始化整块板子
init();
/*
- 函数调用:通过调用函数来执行特定的任务,这里我们定义了一个函数来初始化 GPIO 13 引脚。
*/
init_led();
/*
- while 循环:无限循环,程序将一直在此运行。若想退出循环并向下运行,需要break;。
*/
while (true)
{
digitalWrite(13, HIGH); // 将 GPIO 13 引脚设置为高电平(LED 点亮)
delay(1000); // 亮灯持续 1000 毫秒(1 秒)
digitalWrite(13, LOW); // 将 GPIO 13 引脚设置为低电平(LED 关闭)
delay(1000); // 灭灯持续 1000 毫秒(1 秒)
}
}ARD.C.1.2 — 相信光,成为光:GPIO 上拉输入
本节我们将做:
- 连接按键到 D4,通过按键控制 LED 的开关
- 每次按下按键切换 LED 状态
核心知识点:
- 引脚复用:一个引脚可以连接至不同的硬件外设,通过切换实现不同的功能。
- GPIO通用输入输出-上拉输入:可用检测该引脚是否通过按键连接到了 GND。
- 上升沿和下降沿:当电平出现从低到高的变化或从高到低的变化时,执行相应的操作。
- 面包板与杜邦线:快速搭建电路的工具,便于实验和调试。

连接图

参考:引脚定义图

参考:电路原理图

技能点:
- 宏定义、变量、flag标志位、作用域、条件语句、static变量
cpp
//******** ARD.C.1.2 *********//
/*
宏定义:使用 #define 定义一个常量或宏,以便在代码中使用更具描述性的名称来代替具体的值。
*/
#define BUTTON_PIN 4
#define BOARD_LED_PIN 13
void init_button()
{
pinMode(BUTTON_PIN, INPUT_PULLUP); // 初始化 BUTTON_PIN 引脚 启用 BUTTON_PIN 引脚的上拉电阻
return;
}
int main()
{
// 初始化整块板子
init();
// 初始化 BOARD_LED_PIN 引脚为输出模式
init_led();
// 初始化 BUTTON_PIN 引脚为输入模式,并启用上拉电阻
init_button();
/*
变量:在程序中用于存储数据的容器,这里使用 bool 类型的变量 last_button_state 来记录上一次按钮的状态。
*/
/*
flag 标志位: 用于记录系统的某项状态
*/
bool last_button_state = true; // 记录上一次按钮的状态,初始为未按下(高电平)
while (true)
{
/*
作用域:
在 while 循环内定义的变量 current_button_state 只在循环体内有效,每次循环都会重新定义和赋值。
而 last_button_state 定义在循环外部,因此它的值在整个程序运行期间都保持不变,直到被更新为当前按钮状态。
*/
bool current_button_state = digitalRead(BUTTON_PIN); // 读取当前按钮状态
//
/*
if() 条件语句:根据括号内的条件判断是否执行大括号内的代码块,经常包含&&(逻辑与)或||(逻辑或)等逻辑运算符来组合多个条件。
*/
if (last_button_state && !current_button_state)
{
// 检测到按钮从未按下变为按下的状态变化(下降沿)
/*
static 变量:在函数内定义的 static 变量在程序周期内只初始化一次、不被销毁。但作用域仍然限制。
*/
static bool led_on = false; // 记录 LED 当前状态
led_on = !led_on; // 切换 LED 状态
digitalWrite(BOARD_LED_PIN, led_on); // 根据 led_on 的值设置 BOARD_LED_PIN 引脚
}
last_button_state = current_button_state; // 将当前按钮状态保存为上一次状态,以便下一次循环比较
delay(10); // 延迟
}
}ARD.C.1.3 — 呼吸的光才有灵魂:PWM 与 TIM 定时器
本节我们将做:
- 使用 PWM 实现 LED 呼吸灯效果(亮度从暗到亮再到暗循环)
- 通过按键切换呼吸 / 常亮模式
核心知识点:
- PWM脉宽调制:通过快速改变高低电平,用高低电平的比例模拟输出的强度。

连接图:

技能点:
- include头文件、PWM配置、if-else条件语句、const常量、全局变量、边界条件控制
cpp
//******** ARD.C.1.3 *********//
#define PWM_LED_PIN 10 // pin11 与 MsTimer2 共用 Timer2 冲突,改到 pin10(OC1B/Timer1)
void init_pwm_led()
{
// 初始化 PWM 功能
pinMode(PWM_LED_PIN, OUTPUT); // 初始化 PWM_LED_PIN 引脚
// Arduino analogWrite 使用内置 PWM
// PWM_LED_PIN=10 使用 Timer1/OC1B,与 Servo(pin9/OC1A) 共用 Timer1
// Timer1 默认配置:prescaler=64, phase-correct PWM, TOP=255
// Phase-correct PWM:计数器 0→255→0 为一个周期,共 512 tick
// PWM 频率 = 16MHz / 64 / 512 ≈ 488Hz
analogWrite(PWM_LED_PIN, 0); // 初始占空比为 0(LED 关闭)
}
/*
const变量:在函数内定义的 const 变量在程序周期内值不可修改,且只初始化一次。适用于常量值的定义。
全局变量:在函数外部定义的变量,在整个程序中都可以访问和修改。
*/
const int brightness_step = 2; // 亮度变化步长(8位 PWM 0~255)
int main()
{
// 初始化整块板子
init();
// 初始化 BOARD_LED_PIN 引脚为输出模式
init_led();
// 初始化 BUTTON_PIN 引脚为输入模式,并启用上拉电阻
init_button();
// 初始化 PWM LED
/*
PWM:通过 analogWrite 输出 PWM 信号控制 LED 亮度。
*/
init_pwm_led();
bool last_button_state = true; // 记录上一次按钮的状态,初始为未按下(高电平)
while (true)
{
static bool breathing = true; // 新增:记录当前呼吸状态
static int brightness = 0; // 记录当前亮度
static bool direction = true; // 记录亮度变化方向
bool current_button_state = digitalRead(BUTTON_PIN); // 读取当前按钮状态
if (last_button_state && !current_button_state)
{
// 检测到按钮从未按下变为按下的状态变化(下降沿)
static bool led_on = false; // 记录 LED 当前状态
led_on = !led_on; // 切换 LED 状态
digitalWrite(BOARD_LED_PIN, led_on); // 根据 led_on 的值设置 BOARD_LED_PIN 引脚
breathing = !breathing; // 新增:切换呼吸模式状态
}
/*
if-else 条件语句:根据括号内的条件判断执行不同的代码块。
*/
if (breathing)
{
if (direction)
{
brightness += brightness_step; // 增加亮度
/*
边界条件控制:当亮度值达到上限(255)时停止增加并翻转方向,实现亮度从暗到亮再到暗的循环变化。
*/
if (brightness >= 255)
{ // 达到最大亮度
brightness = 255;
direction = false; // 切换为减少亮度
}
}
else
{
brightness -= brightness_step; // 减少亮度
/*
边界条件控制:当亮度值达到下限(0)时停止减少并翻转方向,形成完整的呼吸周期。
*/
if (brightness <= 0)
{ // 达到最小亮度
brightness = 0;
direction = true; // 切换为增加亮度
}
}
}
else
{
brightness = 0; // 关闭 LED
}
analogWrite(PWM_LED_PIN, brightness); // 设置 PWM 占空比以调整 LED 亮度(Arduino 范围 0-255)
last_button_state = current_button_state; // 将当前按钮状态保存为上一次状态,以便下一次循环比较
delay(10); // 延迟
}
}ARD.C.1.4 — 才第四节就这么复杂了?:参数传入返回与 USB 通信
本节我们将做:
- 将之前的逻辑封装成函数,提高代码复用性和可读性
- 实现
check_button_pressed()和update_breathing_led()两个新函数
核心知识点:
- 函数定义与调用:将代码封装成函数,提高代码的复用性和可读性。
- Serial调试输出:通过在代码中添加打印语句,将变量值和程序状态输出到串口进行调试和分析。
- USB通信:通过 USB 将数据发送到电脑进行调试和分析。

提示:淘宝上大多数 17~30 块的 Arduino Uno R3 克隆板使用的是 CH340 或 CP2102 作为 USB 转串口芯片,Windows 不自带这两者的驱动,需要额外安装。如果连接板子后设备管理器中没有出现 COM 口,请根据芯片型号下载对应驱动:CH340 驱动下载 / CP210x 驱动下载。
相关链接:
技能点:
- 函数返回值、函数传入参数、Serial调试输出
cpp
//******** ARD.C.1.4 *********//
/*
函数返回值:
函数可以返回一个值,表示函数执行的结果或状态。
check_button_pressed 函数用于检测按钮是否被按下,并返回一个布尔值表示按钮状态。
*/
bool check_button_pressed()
{
static bool last_button_state = true; // 记录上一次按钮的状态,初始为未按下(高电平)
bool current_button_state = digitalRead(BUTTON_PIN); // 读取当前按钮状态
if (last_button_state && !current_button_state)
{
last_button_state = current_button_state; // 更新 last_button_state
return true; // 检测到按钮按下,返回 true
}
last_button_state = current_button_state; // 更新 last_button_state
return false; // 没有检测到按钮按下,返回 false
}
/*
函数传入参数:
函数可以接受参数,这些参数在函数内部使用来执行特定的操作。
update_breathing_led 函数接受一个布尔参数 breathing,表示当前是否处于呼吸模式,根据该参数来更新 LED 的状态。
*/
int update_breathing_led(bool breathing)
{
if (!breathing)
{
analogWrite(PWM_LED_PIN, 0); // 关闭 LED
return -1;
}
else
{
static int brightness = 0; // 记录当前亮度
static bool direction = true; // 记录亮度变化方向
if (direction)
{
brightness += brightness_step; // 增加亮度
if (brightness >= 255)
{ // 达到最大亮度
brightness = 255;
direction = false; // 切换为减少亮度
}
}
else
{
brightness -= brightness_step; // 减少亮度
if (brightness <= 0)
{ // 达到最小亮度
brightness = 0;
direction = true; // 切换为增加亮度
}
}
analogWrite(PWM_LED_PIN, brightness); // 设置 PWM 占空比以调整 LED 亮度(Arduino 范围 0-255)
return brightness; // 返回当前亮度值
}
}
int main()
{
// 初始化整块板子
init();
// 初始化串口通信,设置波特率为 115200
Serial.begin(115200);
// 初始化 BOARD_LED_PIN 引脚为输出模式
init_led();
// 初始化 BUTTON_PIN 引脚为输入模式,并启用上拉电阻
init_button();
// 初始化 PWM LED
init_pwm_led();
bool breathing = true; // 记录当前呼吸状态
while (true)
{
if (check_button_pressed())
{
static bool led_on = false; // 记录 LED 当前状态
led_on = !led_on; // 切换 LED 状态
digitalWrite(BOARD_LED_PIN, led_on); // 根据 led_on 的值设置 BOARD_LED_PIN 引脚
breathing = !breathing; // 切换呼吸模式状态
}
int now_brightness = update_breathing_led(breathing); // 更新呼吸灯状态
/*
printf() 函数:用于将信息输出到串口进行调试和分析,这里我们可以输出当前的亮度值来观察呼吸灯的变化。
*/
Serial.print("Brightness: "); Serial.println(now_brightness); // 打印当前亮度值到串口进行调试
delay(10); // 延迟
}
}ARD.C.1.5 — 我要用 GPIO 和 PWM 干票大的:TB6612 与电机控制
本节我们将做:
- 使用 TB6612FNG 电机驱动芯片驱动两个直流电机
- 通过按键循环切换前进 → 后退 → 停止
核心知识点:
- 直流电机原理:通过改变电机两端的电压极性来控制转动方向。
- 电机驱动芯片:TB6612FNG 是一个常用的电机驱动芯片,可以控制两个直流电机的转动方向和速度。
- 电压与共地:务必!务必!务必!共地连接以保证电路的正常工作。


连接图:

技能点:
- 引脚资源分配、累加取模
cpp
//******** ARD.C.1.5 *********//
//引脚资源分配:单片机的引脚支持不同的功能,因此要合理分配引脚资源,避免冲突。这里我们为两个电机的控制引脚和 PWM 引脚进行了定义。
#define MOTOR_A_IN1 7
#define MOTOR_A_IN2 8
#define MOTOR_B_IN1 A0
#define MOTOR_B_IN2 A1
#define MOTOR_PWM_A 5
#define MOTOR_PWM_B 6
void init_motor()
{
// 初始化电机控制引脚
pinMode(MOTOR_A_IN1, OUTPUT);
pinMode(MOTOR_A_IN2, OUTPUT);
pinMode(MOTOR_B_IN1, OUTPUT);
pinMode(MOTOR_B_IN2, OUTPUT);
// 初始化 PWM 功能(Arduino analogWrite 自动配置 PWM 引脚)
pinMode(MOTOR_PWM_A, OUTPUT);
pinMode(MOTOR_PWM_B, OUTPUT);
// Arduino analogWrite 使用内置 PWM,无需手动配置分频和周期
analogWrite(MOTOR_PWM_A, 0); // 初始占空比为 0(电机停止)
analogWrite(MOTOR_PWM_B, 0); // 初始占空比为 0(电机停止)
}
int main()
{
// 初始化整块板子
init();
// 初始化 BOARD_LED_PIN 引脚为输出模式
init_led();
// 初始化 BUTTON_PIN 引脚为输入模式,并启用上拉电阻
init_button();
// 初始化 PWM LED
init_pwm_led();
// 初始化电机控制
/*
引脚资源分配:单片机的引脚支持不同的功能,因此要合理分配引脚资源,避免冲突。
Arduino Uno 的 PWM 引脚为 3,5,6,9,10,11,我们将电机 PWM 分配在 5,6。
*/
init_motor();
int motor_state_step = 0; // 0: 前进, 1: 后退, 2: 停止
while (true)
{
if (check_button_pressed())
{
/*
累加去模:通过对 motor_state_step 进行累加并取模来循环切换电机状态,实现前进、后退和停止的功能。
*/
motor_state_step = (motor_state_step + 1) % 3; // 循环切换状态
}
if (motor_state_step == 0)
{
// 前进
digitalWrite(MOTOR_A_IN1, HIGH);
digitalWrite(MOTOR_A_IN2, LOW);
digitalWrite(MOTOR_B_IN1, LOW);
digitalWrite(MOTOR_B_IN2, HIGH);
analogWrite(MOTOR_PWM_A, 125); // ~50% 占空比(Arduino 范围 0-255)
analogWrite(MOTOR_PWM_B, 125); // ~50% 占空比
// 呼吸 + 常亮
update_breathing_led(true); // 启用呼吸灯
digitalWrite(BOARD_LED_PIN, HIGH); // 启用常亮
}
else if (motor_state_step == 1)
{
// 后退
digitalWrite(MOTOR_A_IN1, LOW);
digitalWrite(MOTOR_A_IN2, HIGH);
digitalWrite(MOTOR_B_IN1, HIGH);
digitalWrite(MOTOR_B_IN2, LOW);
analogWrite(MOTOR_PWM_A, 125); // ~50% 占空比
analogWrite(MOTOR_PWM_B, 125); // ~50% 占空比
// 呼吸 + 关闭
update_breathing_led(true); // 启用呼吸灯
digitalWrite(BOARD_LED_PIN, LOW); // 关闭常亮
}
else
{
// 停止
digitalWrite(MOTOR_A_IN1, HIGH);
digitalWrite(MOTOR_A_IN2, HIGH);
digitalWrite(MOTOR_B_IN1, HIGH);
digitalWrite(MOTOR_B_IN2, HIGH);
analogWrite(MOTOR_PWM_A, 0); // 停止电机 A
analogWrite(MOTOR_PWM_B, 0); // 停止电机 B
// 关闭呼吸灯和常亮
update_breathing_led(false); // 关闭呼吸灯
digitalWrite(BOARD_LED_PIN, LOW); // 关闭常亮
}
delay(10); // 延迟
}
}ARD.C.1.6 — 恭喜你,你已经会做多级 API 了:多层 API 设计
本节我们将做:
- 封装底层电机控制逻辑,提供
set_motor_speed()和drive()高级 API - 实现差速转向
核心知识点:
- 多层API设计:通过分层封装实现代码的模块化和复用。
- 小车运动控制:差速转向 = 前进/后退速度 + 左转/右转速度。

技能点:
- 差速转向叠加原理、速度限幅
cpp
//******** ARD.C.1.6 *********//
#define MAX_SPEED 255
// 第一层 API,传入电机ID与速度,直接控制电机转动
void set_motor_speed(int motor_id, int speed)
{
// motor_id: 0 - 左轮, 1 - 右轮
int in1, in2, pwm_pin;
if (motor_id == 0)
{
// 左轮 ← 物理 Motor A 的引脚
in1 = MOTOR_A_IN1;
in2 = MOTOR_A_IN2;
pwm_pin = MOTOR_PWM_A;
}
else
{
// 右轮 ← 物理 Motor B 的引脚
in1 = MOTOR_B_IN2;
in2 = MOTOR_B_IN1;
pwm_pin = MOTOR_PWM_B;
}
if (speed > 0)
{
digitalWrite(in1, HIGH); // 接线反相,IN1/IN2 对调
digitalWrite(in2, LOW);
analogWrite(pwm_pin, speed);
}
else if (speed < 0)
{
digitalWrite(in1, LOW);
digitalWrite(in2, HIGH);
analogWrite(pwm_pin, -speed);
}
else
{
// 停止
digitalWrite(in1, HIGH);
digitalWrite(in2, HIGH);
analogWrite(pwm_pin, 0);
}
}
// 第二层 API,传入正向速度和转向速度,控制两轮速度实现差速转向
void drive(int forward_speed, int turn_speed)
{
// forward_speed: -255 到 255,负数表示后退,正数表示前进
// turn_speed: -255 到 255,负数表示左转,正数表示右转
int left_motor_speed = forward_speed + turn_speed; // 左轮速度 = 前进速度 + 转向速度
int right_motor_speed = forward_speed - turn_speed; // 右轮速度 = 前进速度 - 转向速度
/*
速度限幅:通过检查计算后的速度值是否超过 MAX_SPEED 阈值,
将左右轮速度限制在 [-255, 255] 范围内,防止电机驱动芯片过载。
*/
if (left_motor_speed > MAX_SPEED)
left_motor_speed = MAX_SPEED;
if (left_motor_speed < -MAX_SPEED)
left_motor_speed = -MAX_SPEED;
if (right_motor_speed > MAX_SPEED)
right_motor_speed = MAX_SPEED;
if (right_motor_speed < -MAX_SPEED)
right_motor_speed = -MAX_SPEED;
set_motor_speed(0, left_motor_speed); // 控制左轮
set_motor_speed(1, right_motor_speed); // 控制右轮
}
int main()
{
// 初始化整块板子
init();
// 初始化 BOARD_LED_PIN 引脚为输出模式
init_led();
// 初始化 BUTTON_PIN 引脚为输入模式,并启用上拉电阻
init_button();
// 初始化电机控制
/*
引脚资源分配:单片机的引脚支持不同的功能,因此要合理分配引脚资源,避免冲突。
Arduino Uno 的 PWM 引脚为 3,5,6,9,10,11,我们将电机 PWM 分配在 5,6。
*/
init_motor();
int drive_state_step = 0; // 0: 前进, 1: 后退, 2: 左转, 3: 右转, 4: 停止
while (true)
{
if (check_button_pressed())
{
drive_state_step = (drive_state_step + 1) % 5; // 循环切换状态
}
if (drive_state_step == 0)
{
drive(150, 0); // 前进
digitalWrite(BOARD_LED_PIN, HIGH);
}
else if (drive_state_step == 1)
{
drive(-150, 0); // 后退
digitalWrite(BOARD_LED_PIN, LOW);
}
else if (drive_state_step == 2)
{
drive(0, -150); // 左转
digitalWrite(BOARD_LED_PIN, HIGH);
}
else if (drive_state_step == 3)
{
drive(0, 150); // 右转
digitalWrite(BOARD_LED_PIN, LOW);
}
else
{
drive(0, 0); // 停止
digitalWrite(BOARD_LED_PIN, LOW);
}
update_breathing_led(true); // 呼吸灯
delay(10); // 延迟
}
}ARD.C.1.7 — 追着车跑一定很累吧:UART 串口与蓝牙模块
本节我们将做:
- 配置 HC-05 蓝牙模块的 UART 通信
- 通过蓝牙遥控器发送指令控制小车
核心知识点:
- UART串口通信:一种异步串行通信协议,通过 TX/RX 两根数据线实现双向通信。
- HC-05蓝牙模块:将 UART 有线信号转换为蓝牙无线信号。


连接图:

技能点:
- Serial.available 可读判断、Serial.write/Seria.read、指针遍历字符串
相关资源:
使用外部 USB 转串口模块(如 CH340 / CP2102)时,Windows 不自带驱动,需要额外安装。请根据模块芯片型号下载对应驱动:
cpp
//******** ARD.C.1.7 *********//
int main()
{
init(); // 初始化 Arduino 定时器(millis/delay/analogWrite 需要)
// 初始化 UART 串口通信
Serial.begin(115200);
init_button();
// 接收到的数据再通过 UART 发送回去,形成回显
while (true)
{
/*
Serial.available 可读判断:当串口接收缓冲区有数据时,返回 true,否则返回 false。
*/
if (Serial.available())
{
int c = Serial.read(); // 从串口接收一个字符
/*
Serial.write:将数据发送到串口。
*/
Serial.write(c); // 将接收到的字符通过串口发送回去
}
/*
指针遍历字符串:通过指针 msg 遍历字符串中的每个字符,
逐个发送直到遇到字符串结束符 \0。
*/
if (check_button_pressed())
{
const char *msg = "Hello, world!\r\n";
while (*msg)
{
Serial.write(*msg++); // 发送字符串
}
}
}
}ARD.C.1.8 — 遥控车!启动!:状态机通信协议
本节我们将做:
- 使用状态机解析蓝牙控制指令
- 通过差速转向控制小车运动
核心知识点:
- 状态机设计:通过 IDLE → GET_FORWARD → GET_TURN → GET_CHECKSUM → GET_TAIL 五个状态解析协议帧。
- 校验和验证:通过 (byte1 + byte2) & 0xFF 计算校验和,确保数据传输的准确性。

技能点:
- 枚举状态机、缓冲区轮询、switch条件语句、校验和验证
cpp
//******** ARD.C.1.8 *********//
typedef enum {
IDLE,
GET_FORWARD,
GET_TURN,
GET_CHECKSUM,
GET_TAIL
} uart_state_t;
static uart_state_t uart_state = IDLE;
// 解析字符
static int16_t forward_speed = 0, turn_speed = 0;
void uart_handler()
{
/*
缓冲区轮询:使用 while 循环一次性处理完当前缓冲区的所有字符,防止因主循环延迟导致的数据堆积。
*/
while (Serial.available())
{
uint8_t c = (uint8_t)Serial.read();
static int8_t byte1 = 0, byte2 = 0;
/*
switch 条件语句:根据 uart_state 的不同值执行不同的代码块,实现状态机的逻辑。
*/
switch (uart_state)
{
case IDLE:
if (c == 0xA5)
{
uart_state = GET_FORWARD;
byte1 = 0;
byte2 = 0;
}
break;
case GET_FORWARD:
byte1 = (int8_t)c; // 转换为有符号,支持负数(后退)
uart_state = GET_TURN;
break;
case GET_TURN:
byte2 = (int8_t)c;
uart_state = GET_CHECKSUM;
break;
case GET_CHECKSUM:
/*
校验和验证:通过 (byte1 + byte2) & 0xFF 计算校验和,
与接收到的校验字节比较,确保数据传输的准确性。
*/
if (((byte1 + byte2) & 0xFF) == c)
{
uart_state = GET_TAIL; // 校验通过,等待帧尾
}
else
{
uart_state = IDLE; // 校验失败,复位
}
break;
case GET_TAIL:
if (c == 0x5A) // 帧尾校验
{
forward_speed = byte1 * 2; // 前进速度
turn_speed = byte2 * 2; // 转向速度
}
uart_state = IDLE;
break;
}
}
}
int main()
{
init(); // 初始化 Arduino 定时器(millis/delay/analogWrite 需要)
// 初始化 UART 串口通信
Serial.begin(115200);
// 初始化各模块
init_led();
init_button();
init_pwm_led();
init_motor();
while (true)
{
/*
状态机协议解析:通过 uart_handler 函数中定义的状态机,逐字节解析蓝牙接收到的控制指令,
提取 forward_speed 和 turn_speed 两个控制量。
*/
uart_handler(); // UART → byte1, byte2 → forward_speed, turn_speed
/*
控制指令应用:将解析出的 forward_speed 和 turn_speed 传入 drive 函数,
drive 内部通过差速转向算法控制左右轮电机,实现小车运动。
*/
drive(forward_speed, turn_speed); // 根据解析的指令控制小车运动
update_breathing_led(true);
delay(10);
}
}恭喜你,你已经完成了 ARD.C.1 课程的所有内容!驾驶你的小遥控车,能忍住不来一场碰碰车比赛吗?