黄鹤楼中吹玉笛,江城五月落梅花

原文发表于 CSDN(作者:hjz0810),发布日期 2026-04-15。本文为转载备份,已征得作者同意。

刷到MarkWorld上作者wannawow建模的黄鹤楼,

这里给出了拓竹各类机器都能一体打印的方案从评论区看大家的反馈都相当好看

那首先下载项目用拓竹编辑器打开,这是一个380克重的模型,使用拓竹A1mini打印几乎要占满整个盘面,而且模型地盘厚,身材高,其打印时间也是来到了28个小时,如果为了追求打印完美,在仅开启50%的速度下继续把空驶,外墙,内墙的速度和加速度降到更低,同时把由原来的50,130改为30,50,同时开启延时摄影,为了保证质量开启擦拭塔,切片后其打印时间更是来到了2天15小时,848层,

打印完实物图如下,

由于是用PLA Basic材料打的,模型稳定但是会缺少光泽,如果为了极致的美观可以使用PLA Silk材料打印,

以下是打印完后连续两昼夜的延时摄影视频:https://v.douyin.com/VBJO5h7FICA/

黄鹤楼

黄鹤楼的内部是中空的,内径为4.5mm,正好可以塞下一些小型LED灯,那么控制器呢,当然也可以就用一个开关,那既然都用上控制器了,刚好看到身边有一块,其尺寸也是刚好能放入楼内,于是算法和程序就加在了黄鹤楼上

text
ESP32-S3

ASRPRO天问模块

bash

关于ASRPRO天问的语音模块代码,(配图形化配置)

text
#include "asr.h"

extern "C"{ void * __dso_handle = 0 ;}

#include "setup.h"

#include "HardwareSerial.h"

#include "asr_math.h"

#include "myLib/asr_event.h"

uint32_t snid;

uint8_t suijishu = 0;

bool LED_station = 0;

void app();

uint8_t suiji();

void ASR_CODE();

//{speak:小蝶-清新女声,vol:6,speed:8,platform:haohaodada}

//{playid:10001,voice:江边黄鹤古时楼,劳置华筵待我游。楚思淼茫云水冷,商声清脆管弦秋。}

//{playid:10002,voice:我退下了,用黄鹤楼唤醒我}

void app(){

 String serialBuf; // 串口接收缓冲区

  while (1) {

    // 非阻塞串口接收,按换行符解析指令

    if(Serial.available() > 0){

      char c = Serial.read();

      if(c == '\n'){ // 遇到换行符,解析完整指令

 serialBuf.trim(); // 去掉多余空格/换行

        if(serialBuf == "on"){

          digitalWrite(4, 1);

 LED_station = 1;

 }

        else if(serialBuf == "off"){

          digitalWrite(4, 0);

 LED_station = 0;

 }

 serialBuf = ""; // 清空缓冲区

 }else{

 serialBuf += c; // 拼接字符

 }

 }

    delay(2);

 }

  vTaskDelete(NULL);

}

/*描述该功能...

*/

uint8_t suiji(){

  return random(1, 5+1);

}

/*描述该功能...

*/

void ASR_CODE(){

  set_state_enter_wakeup(10000);

  if((snid) <= 4){

    // 开灯逻辑

    digitalWrite(4, 1);

 LED_station = 1;

 Serial.println("on"); // 给ESP32发开灯指令

 suijishu = suiji();

    switch (suijishu) {

     case 1:

      //{playid:10500,voice:一为迁客去长沙,西望长安不见家。黄鹤楼中吹玉笛,江城五月落梅花。}

      play_audio(10500);

      break;

     case 2:

      //{playid:10501,voice:东望黄鹤山,雄雄半空出。四面生白云,中峰倚红日。}

      play_audio(10501);

      break;

     case 3:

      //{playid:10502,voice:城下沧江水,江边黄鹤楼。 朱阑将粉堞,江水映悠悠。}

      play_audio(10502);

      break;

     case 4:

      //{playid:10503,voice:江边黄鹤古时楼,劳置华筵待我游。 楚思淼茫云水冷,商声清脆管弦秋。}

      play_audio(10503);

      break;

     case 5:

      //{playid:10504,voice:枫林霜叶净江烟,锦石游鱼清可怜。 贾客帆樯云里见,仙人楼阁镜中悬。}

      play_audio(10504);

      break;

 }

 }

  if((snid) >= 6){

    // 关灯逻辑

    digitalWrite(4, 0);

 LED_station = 0;

 Serial.println("off"); // 给ESP32发关灯指令

 suijishu = suiji();

    switch (suijishu) {

     case 1:

      //{playid:10505,voice:日暮乡关何处是?烟波江上使人愁。}

      play_audio(10505);

      break;

     case 2:

      //{playid:10506,voice:孤帆远影碧空尽,唯见长江天际流。}

      play_audio(10506);

      break;

     case 3:

      //{playid:10507,voice:黄鹤西楼月,长江万里情。}

      play_audio(10507);

      break;

     case 4:

      //{playid:10508,voice:总是平生未行处,醉来堪赏醒堪愁。}

      play_audio(10508);

      break;

     case 5:

      //{playid:10509,voice:定知羽客无因见,空使含情对落晖}

      play_audio(10509);

      break;

 }

 }

}

void hardware_init(){

  //需要操作系统启动后初始化的内容

  //音量范围1-7

  vol_set(1);

  xTaskCreate(app,"app",128,NULL,4,NULL);

  vTaskDelete(NULL);

}

void setup()

{

  //需要操作系统启动前初始化的内容

  //欢迎词指开机提示音,可以为空

  //{ID:5,keyword:"唤醒词",ASR:"黄鹤楼",ASRTO:"我在"}

  //开灯命令词

  //{ID:1,keyword:"命令词",ASR:"开灯",ASRTO:"已开灯"}

  //{ID:2,keyword:"命令词",ASR:"我回来了",ASRTO:"已开灯"}

  //{ID:3,keyword:"命令词",ASR:"起床了",ASRTO:"已开灯"}

  //{ID:4,keyword:"命令词",ASR:"天黑了",ASRTO:"已开灯"}

  //关灯命令词

  //{ID:6,keyword:"命令词",ASR:"关灯",ASRTO:"已关灯"}

  //{ID:7,keyword:"命令词",ASR:"我出去了",ASRTO:"已关灯"}

  //{ID:8,keyword:"命令词",ASR:"睡觉了",ASRTO:"已关灯"}

  //{ID:9,keyword:"命令词",ASR:"天亮了",ASRTO:"已关灯"}

  setPinFun(4,FIRST_FUNCTION);

  pinMode(4,OUTPUT);

  digitalWrite(4, 0); // 初始关灯

  setPinFun(13,SECOND_FUNCTION);

  setPinFun(14,SECOND_FUNCTION);

 Serial.begin(9600);

}

cpp

使用ESP32连接WIFI网络,可以发布一个局域网的网页,同时该网页也可以访问外部网站的资源或直接跳转至外部网站,由于局域网的网页网址一般为192.168.xxx.xxx,且在不同网络下(如手机的热点网络)地址随时变化,采用mdns将ip转为一个自定义的固定的页面:http://520.local

关于ESP32-S3的代码

text
#include <WiFi.h>

#include <WebServer.h>

#include <ESPmDNS.h>

#include <HardwareSerial.h>

// ==================== 配置区域(可根据需求修改)====================

// WiFi配置

const char* ssid = "hjz";

const char* password = "12345678";

// LED配置

const int ledPin = 2; // ESP32-S3板载LED引脚,不是GPIO4!

// mDNS配置

const char* hostName = "esp32s3"; // 访问地址:http://520.local

// 串口配置(和语音模块通信)

HardwareSerial ASR_Serial(1); // 使用ESP32-S3的Serial1

const int asrTX = 17; // 接语音模块RX(PB.6)

const int asrRX = 18; // 接语音模块TX(PB.5)

const long asrBaud = 9600; // 和语音模块波特率一致

// ==================== 全局变量 ====================

WebServer server(80);

bool ledState = LOW; // LED全局状态,网页和串口共用

String serialBuf = ""; // 串口接收缓冲区

// ==================== 网页HTML生成 ====================

String getHtml() {

 String stateText = ledState ? "已开启" : "已关闭";

 String html = R"(

<!DOCTYPE html>

<html>

<head>

 <meta charset="UTF-8">

 <title>ESP32-S3 灯光控制</title>

 <meta name="viewport" content="width=device-width, initial-scale=1">

 <style>

 body { text-align: center; font-family: Arial; margin-top: 50px; }

 h1 { color: #333; }

 .btn {

 font-size: 24px; padding: 20px 40px; margin: 20px;

 border: none; border-radius: 10px; cursor: pointer;

 color: white;

 }

 .btn-on { background-color: #4CAF50; }

 .btn-off { background-color: #f44336; }

 .state { font-size: 20px; margin-top: 30px; color: #555; }

 </style>

</head>

<body>

 <h1>ESP32-S3 灯光控制台</h1>

 <a href="/on"><button class="btn btn-on">开灯</button></a>

 <a href="/off"><button class="btn btn-off">关灯</button></a>

 <div class="state">当前灯光状态: )" + stateText + R"(</div>

</body>

</html>

 )";

  return html;

}

// ==================== 网页路由处理 ====================

// 主页

void handleRoot() {

 server.send(200, "text/html", getHtml());

}

// 开灯处理

void handleOn() {

 ledState = HIGH;

  digitalWrite(ledPin, ledState);

 ASR_Serial.println("on"); // 给语音模块发开灯指令

 Serial.println("网页触发开灯,已同步发送指令到语音模块");

 server.send(200, "text/html", getHtml());

}

// 关灯处理

void handleOff() {

 ledState = LOW;

  digitalWrite(ledPin, ledState);

 ASR_Serial.println("off"); // 给语音模块发关灯指令

 Serial.println("网页触发关灯,已同步发送指令到语音模块");

 server.send(200, "text/html", getHtml());

}

// ==================== 串口指令解析(处理语音模块发来的指令)====================

void parseASRSerial() {

  while (ASR_Serial.available() > 0) {

    char c = ASR_Serial.read();

    if (c == '\n') { // 换行符为指令结束

 serialBuf.trim();

      if (serialBuf == "on") {

 ledState = HIGH;

        digitalWrite(ledPin, ledState);

 Serial.println("语音模块触发开灯,已同步ESP32灯光");

 } 

      else if (serialBuf == "off") {

 ledState = LOW;

        digitalWrite(ledPin, ledState);

 Serial.println("语音模块触发关灯,已同步ESP32灯光");

 }

 serialBuf = ""; // 清空缓冲区

 } else {

 serialBuf += c;

 }

 }

}

// ==================== 初始化setup ====================

void setup() {

 Serial.begin(115200);

  delay(100);

  // 1. 初始化LED引脚

  pinMode(ledPin, OUTPUT);

  digitalWrite(ledPin, ledState); // 初始关灯

  // 2. 初始化和语音模块通信的串口

 ASR_Serial.begin(asrBaud, SERIAL_8N1, asrRX, asrTX);

 Serial.println("ASR串口已初始化,波特率9600");

  // 3. 连接WiFi

 Serial.print("正在连接WiFi: ");

 Serial.println(ssid);

 WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {

    delay(500);

 Serial.print(".");

 }

 Serial.println("");

 Serial.println("WiFi连接成功!");

 Serial.print("本地IP地址: ");

 Serial.println(WiFi.localIP());

  // 4. 启动mDNS服务

  if (MDNS.begin(hostName)) {

 Serial.println("mDNS服务启动成功!");

 Serial.print("网页访问地址: http://");

 Serial.print(hostName);

 Serial.println(".local");

 } else {

 Serial.println("mDNS服务启动失败!");

 }

  // 5. 配置Web服务器路由

 server.on("/", handleRoot);

 server.on("/on", handleOn);

 server.on("/off", handleOff);

  // 404处理

 server.onNotFound([]() {

 server.send(404, "text/plain", "页面不存在");

 });

  // 6. 启动Web服务器

 server.begin();

 Serial.println("Web服务器已启动");

}

// ==================== 主循环loop ====================

void loop() {

 server.handleClient(); // 处理网页客户端请求

  parseASRSerial(); // 处理语音模块的串口指令

  delay(10);

}

cpp

这里,ESP32和天问通过串口实现双通信,但是几乎所有人都遇到过,从天问的5V向3V的ESP32传串口数据虽然能传,但是ESP32总是莫名的被击穿或烧毁,而从3V的ESP32向天问传则怎么也传不过去,而两个模块独立使用又都可以确认是能发送,能接收,但接在一起就会水火不容

解决这个玄学一样的问题前我们可以不必考虑复杂的电路和编程知识,我们只聊自然规律,从广义相对论来讲,宇宙间一切物体的运动一切事件的发生都和且只都和质量有关,大质量的物体会压弯时空形成引力,使其他小质量的物体有了朝它运动的趋势,

没错,引力 和 趋势,这样问题就得到了解决,就像水会收到引力的作用总是保持向低处流的趋势,因此,ESP32串口的3.3V无论怎样直接接入到天问时天问都不能正确接收串口消息,同理,天问对ESP32由着近2V的压差,使得数据(电流)就像洪水一样涌进ESP32,虽然能接收到,但也容易因此而烧毁

那这两个模块天生就永远不能通信吗,那既然如此不同电压的模块间又是怎么直接低延迟通信的呢,这时候就需要用到外力,“水电站”——双向四路逻辑电平转换模块

通过此模块,就实现了多电压模块间的通信

然而,如此多的模块,引脚带来了复杂的接线,杂乱的摆放,各种线路黑色白色红色绿色错综复杂,蜘蛛网在它面前都是有序美观的,而且这些杂乱的线即使有一根接触不良,有一根接入错误,都将造成整个模块的不工作或烧毁

采用集成电路

未完待续,时间2026-3-31