一、硬件介绍
1、产品特点
XIAO ESP32C6基于ESP32-C6芯片(两个32位RISC-V 处理器)构建,其中包含一个运行频率高达160 MHz的高性能处理器;
以及一个时钟频率高达20 MHz的低功耗32 位 RISC-V处理器,同时具有512KB SRAM 和 4 MB Flash等;


2、功能引脚示意图 / 原理图
板载LED灯:GPIO15 引脚控制(黄色LED)

充电指示灯
XIAO ESP32C6 具有电池充电红色指示灯
当未连接电池时:连接Type-C线缆时红灯亮起,30秒后熄灭;
当连接电池并插入Type-C线缆充电时:红灯闪烁;
当电池通过Type-C连接完全充满时:红灯熄灭;
板载按键
BOOT按键:当程序无法烧录时,按住该按键重新上电时,即可进入BootLoader模式,重新进行烧录;
RESET按键:用于开发板复位;


主要引脚图


主要原理图

3、外部硬件
1、摇杆模块
摇杆模块,随着摇杆方向的变化,阻值也会随着变化,从而改变输出的值;
初始状态下X,Y读出电压为供电电压的1/2左右( 1.65V / 2.5V );
当随着X、Y方向移动时,读出电压值减小(0V) / 增大(VCC);
其中X、Y方向输出的数据为模拟量,而Z轴输出的为数字量 (0 / 1);

模块引脚介绍:

2、8*8LED模块【WS2812B】
WS2812B是一个集控制电路与发光电路于一体的智能外控LED光源其外型与一个5050LED灯珠相同, 每个元件即为一个像素点;
数据协议采用单线归零码的通讯方式, 像素点在上电复位以后, DIN端接受从控制器传输过来的数据;


主要特点:
● IC控制电路与LED点光源共用一个电源。
● 控制电路与RGB芯片集成在一个5050封装的元器件中, 构成一个完整的外控像素点。
● 每个像素点的三基色颜色可实现256级亮度显示, 完成16777216种颜色的全真色彩显示。
● 端口扫描频率2KHz/s。
● 当刷新速率30帧/秒时, 级联数不小于1024点。
● 数据发送速度可达800Kbps。
3、喇叭模块【GSPK2307P-8R1W】


二、硬件连接
模块与开发板引脚之间的连接方式如下:

实物效果搭建如下

三、项目实现思想
【摇杆控制LED贪吃蛇游戏】
实现效果:通过摇杆模块控制8*8 LED模块以实现贪吃蛇游戏;
1、通过控制摇杆上、下、左、右分别控制LED移动方向;
2、按下按钮时,实现LED清屏复位(既游戏重启);
3、游玩过程中,游戏运行速度会随着蛇身长度逐渐加速;
4、当蛇碰到墙壁或自身时,喇叭模块发生声音,提示游戏结束(此时需按下按钮,重新开启);
通过ADC功能获取摇杆模块数据;
通过PWM功能控制喇叭模块、LED模块;
四、功能实现
安装FastLED 库;

主要相关代码
#include <FastLED.h>
//#define DEBUG //调试 获取摇杆数值
#ifdef DEBUG
unsigned long lastDebugPrint = 0;
#endif
//喇叭引脚
#define Sound_Pin D7
// 摇杆引脚
#define VRx A0
#define VRy A1
#define SW D9
//摇杆XY初始值
#define X_POS 408
#define Y_POS 416
#define LED_PIN D8 // LED_IN 引脚
#define LED_COLS 8 // 列
#define LED_ROWS 8 // 行
#define NUM_LEDS (LED_COLS * LED_ROWS) // 8*8
#define LED_TYPE WS2812B
#define COLOR_ORDER GRB
#define UP 0
#define DOWN 1
#define LEFT 2
#define RIGHT 3
CRGB HeadColor(255,0,0); //蛇头颜色(R)
CRGB BodyColor(0,255,0); //蛇身颜色(G)
CRGB FoodColor(0,0,255); //食物颜色(B)
CRGB leds[NUM_LEDS];
unsigned long lastUpdate = 0;
int gameSpeed = 900; // 游戏运行初始速度ms
static byte lastDir = UP; //默认方向向上
bool buttonPressed = false;
unsigned long lastTime = 0;
const int Delay = 50;
bool collisionsState = false;
// 蛇结构体
struct {
int x, y; // 蛇头坐标
int tail[65][2]; // 蛇身坐标数组 (x,y)
int length = 3; // 初始长度
int dir = 0; // 运动方向(0上,1下,2左,3右)
} snake;
// 食物结构体
struct {
int x, y;
bool eaten = true; //食物状态
} food;
//移动蛇身体
void moveSnake() {
for(int i=snake.length-1; i>0; i--){
snake.tail[i][0] = snake.tail[i-1][0]; //x
snake.tail[i][1] = snake.tail[i-1][1]; //y
}
//更新蛇头位置
snake.tail[0][0] = snake.x;
snake.tail[0][1] = snake.y;
// 移动蛇头 (坐标系)
switch(snake.dir){
case UP: snake.y++; break; // 上
case DOWN: snake.y--; break; // 下
case LEFT: snake.x++; break; // 左
case RIGHT: snake.x--; break; // 右
}
}
//生成食物
void spawnFood() {
if(food.eaten){
bool valid = false;
while(!valid){
//生成食物位置
valid = true;
food.x = random(LED_COLS);
food.y = random(LED_ROWS);
// 检查是否与蛇重叠
if(food.x == snake.x && food.y == snake.y) valid = false;
for(int i=0; i<snake.length; i++){
if(food.x == snake.tail[i][0] && food.y == snake.tail[i][1]){
valid = false;
break;
}
}
}
food.eaten = false;
}
}
//蛇碰撞检测
void checkCollisions() {
// 吃食物检测
if(snake.x == food.x && snake.y == food.y){
snake.length++;
food.eaten = true;
gameSpeed = max(100, gameSpeed-50); // 游戏加速
spawnFood();
}
// 撞墙检测
if(snake.x<0 || snake.x>=LED_COLS || snake.y<0 || snake.y>=LED_ROWS){
playSound();
collisionsState = true;
}
// 撞自身检测
for(int i=0; i<snake.length; i++){
if(snake.x == snake.tail[i][0] && snake.y == snake.tail[i][1]){
playSound();
collisionsState = true;
}
}
}
//刷新显示
void updateDisplay() {
if(!collisionsState){ //发生碰撞 暂停显示
FastLED.clear();
// 绘制食物
leds[food.y * LED_COLS + food.x] = FoodColor;
// 绘制蛇头
leds[snake.y * LED_COLS + snake.x] = HeadColor;
// 绘制蛇身
for(int i=0; i<snake.length; i++){
int index = snake.tail[i][1] * LED_COLS + snake.tail[i][0];
leds[index] = BodyColor;
}
FastLED.show();
}
}
//重新开始
void resetGame(){
collisionsState = false;
gameSpeed = 500;
snake.x = LED_COLS/2;
snake.y = LED_ROWS/2;
snake.length = 3;
// 用当前时间重置随机种子 重新生成食物
randomSeed(millis());
food.eaten = true;
spawnFood();
updateDisplay();
}
//摇杆按下
void buttonSet() {
int buttonState = digitalRead(SW);
if (buttonState == LOW && !buttonPressed && (millis() - lastTime) > Delay) {
buttonPressed = true;
lastTime = millis();
resetGame();
}else if (buttonState == HIGH && buttonPressed) {
buttonPressed = false;
lastTime = millis();
}
}
//摇杆XY数据读取
void controllerRead() {
byte newDir = lastDir;
int xVal = analogRead(VRx) - X_POS;
int yVal = analogRead(VRy) - Y_POS;
//方向保持上一次
if(abs(xVal)< 100 && abs(yVal)<100)
{
xVal = 0;
yVal = 0;
snake.dir = lastDir;
}else{
// 计算角度方向 (对应摇杆X / Y)
float angle = atan2(yVal, xVal) * 180/PI;
if (angle >= -45 && angle < 45) {
newDir = LEFT; // 左
} else if (angle >= 45 && angle < 135) {
newDir = UP; // 上
} else if (angle >= 135 || angle < -135) {
newDir = RIGHT; // 右
} else if (angle >= -135 && angle < -45) {
newDir = DOWN; // 下
}
}
// 防止直接反向180°移动
if (!(snake.dir == RIGHT && newDir == LEFT) &&
!(snake.dir == LEFT && newDir == RIGHT) &&
!(snake.dir == UP && newDir == DOWN) &&
!(snake.dir == DOWN && newDir == UP)) {
snake.dir = newDir; //更新方向
lastDir = newDir;
}
}
//喇叭播放声音
void playSound(){
tone(Sound_Pin, 3000, 1000); //3Khz 1s
}
void setup() {
//串口初始化
Serial.begin(115200);
while (!Serial);
analogReadResolution(10); // 10位ADC
//摇杆初始化
pinMode(VRx, INPUT);
pinMode(VRy, INPUT);
pinMode(SW, INPUT_PULLUP);
// 初始化snake LED
FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS);
FastLED.setBrightness(15);
snake.x = LED_COLS/2;
snake.y = LED_ROWS/2;
for(int i=0; i<snake.length; i++){
snake.tail[i][0] = snake.x - i;
snake.tail[i][1] = snake.y;
}
randomSeed(millis()); // 食物随机数种子
}
void loop() {
//控制游戏速度
if(millis() - lastUpdate > gameSpeed){
lastUpdate = millis();
//摇杆数据读取
controllerRead();
//摇杆按下
buttonSet();
//移动蛇位置
moveSnake();
//检测碰撞
checkCollisions();
//更新显示
updateDisplay();
}
#ifdef DEBUG // 调试(获取实际摇杆X / Y值)
if (millis() - lastDebugPrint > 500) {
lastDebugPrint = millis();
int xRaw = analogRead(VRx); //X_POS
int yRaw = analogRead(VRy); //Y_POS
int xVal = xRaw - X_POS;
int yVal = yRaw - Y_POS;
Serial.print("X="); Serial.print(xRaw);
Serial.print(", Y="); Serial.print(yRaw);
Serial.print(" | Δx="); Serial.print(xVal);
Serial.print(", Δy="); Serial.println(yVal);
}
#endif
}
五、效果演示
1、通过控制摇杆上、下、左、右分别控制LED移动方向;
2、按下按钮时,实现LED清屏复位(既游戏重启);
3、游玩过程中,游戏运行速度会随着蛇身长度逐渐加速;
4、当蛇碰到墙壁或自身时,喇叭模块发生声音,提示游戏结束(此时需按下按钮,重新开启);

六、总结
相关注意事项
1、关于角度方向
float angle = atan2(yVal, xVal) * 180/PI;
if (angle >= -45 && angle < 45) {
newDir = LEFT; // 左
} else if (angle >= 45 && angle < 135) {
newDir = UP; // 上
} else if (angle >= 135 || angle < -135) {
newDir = RIGHT; // 右
} else if (angle >= -135 && angle < -45) {
newDir = DOWN; // 下
}摇杆实际的XY坐标系与常规的XY坐标系是镜像关系;
因此左右方向是相反的;

2、每次重新开始游戏时,需要重新生成食物位置,不然食物开始时永远都会在同一地方;
void resetGame(){
...
// 用当前时间重置随机种子 重新生成食物
randomSeed(millis());
food.eaten = true;
spawnFood();
updateDisplay();
}3、开启#define DEBUG 用于调试以及获取实际摇杆的X / Y初始值变化等,以适配各种硬件不同的摇杆;
我要赚赏金
