原始版本

This commit is contained in:
冯佳
2025-06-19 21:56:46 +08:00
parent fe98e5f010
commit a4841450cf
4152 changed files with 1910684 additions and 0 deletions

View File

@ -0,0 +1,18 @@
from building import *
import os
cwd = GetCurrentDir()
src = Glob('*.c')
CPPPATH = [cwd]
if GetDepend(['PKG_USING_RTDUINO']) and not GetDepend(['RTDUINO_NO_SETUP_LOOP']):
src += ['arduino_main.cpp']
group = DefineGroup('Applications', src, depend = [''], CPPPATH = CPPPATH)
list = os.listdir(cwd)
for item in list:
if os.path.isfile(os.path.join(cwd, item, 'SConscript')):
group = group + SConscript(os.path.join(item, 'SConscript'))
Return('group')

View File

@ -0,0 +1,31 @@
#include <sys/time.h>
#include <rtthread.h>
/**
* @brief 测试 timegm 函数,将指定时间转换为时间戳
*
* 需要注意 tm 结构体的各字段含义:
* tm_year: 年份 = 实际年份 - 1900
* tm_mon : 月份 = 0~111月为0
* tm_mday: 日
* tm_hour: 时
* tm_min : 分
* tm_sec : 秒
* tm_isdst: 夏令时标志0表示不使用夏令时
*/
void test_timegm(void)
{
struct tm tm = {0};
time_t timestamp;
tm.tm_year = 2025 - 1900; // 年份从1900开始
tm.tm_mon = 5 - 1; // 月份0-115月为4
tm.tm_mday = 23;
tm.tm_hour = 9;
tm.tm_min = 56;
tm.tm_sec = 50;
tm.tm_isdst = 0; // 不使用夏令时
timestamp = mktime(&tm);
//rt_kprintf("时间戳: %ld\n", (long)timestamp);
}
MSH_CMD_EXPORT(test_timegm, test timegm function);

View File

@ -0,0 +1,24 @@
/*
* Copyright (c) 2006-2024, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2024-04-08 Li ZhenHong first version
*/
#include <Arduino.h>
void setup(void)
{
/* put your setup code here, to run once: */
Serial.begin();
}
void loop(void)
{
/* put your main code here, to run repeatedly: */
Serial.println("Hello Arduino!");
delay(800);
}

View File

@ -0,0 +1,69 @@
# stm32f407-rt-spark 开发板的Arduino生态兼容说明
## 1 RTduino - RT-Thread的Arduino生态兼容层
stm32f407-rt-spark 开发板已经完整适配了[RTduino软件包](https://github.com/RTduino/RTduino)即RT-Thread的Arduino生态兼容层。用户可以按照Arduino的编程习惯来操作该BSP并且可以使用大量Arduino社区丰富的库是对RT-Thread生态的极大增强。更多信息请参见[RTduino软件包说明文档](https://github.com/RTduino/RTduino)。
### 1.1 如何开启针对本BSP的Arduino生态兼容层
Env 工具下敲入 menuconfig 命令,或者 RT-Thread Studio IDE 下选择 RT-Thread Settings
```Kconfig
Hardware Drivers Config --->
Onboard Peripheral Drivers --->
[*] Compatible with Arduino Ecosystem (RTduino)
```
## 2 Arduino引脚排布
更多引脚布局相关信息参见 [pins_arduino.c](pins_arduino.c) 和 [pins_arduino.h](pins_arduino.h)。
![Rt-spark_Rtduino_Pin_Map.drawio](Rt-spark_Rtduino_Pin_Map.drawio.png)
| Arduino引脚编号 | STM32引脚编号 | 5V容忍 | 备注 |
| ------------------- | --------- | ---- | ------------------------------------------------------------------------- |
| 0 (D0) | GET_PIN(A, 10) | 是 | Serial-RX,默认被RT-Thread的UART设备框架uart1接管 |
| 1 (D1) | GET_PIN(A, 9) | 是 | Serial-TX,默认被RT-Thread的UART设备框架uart1接管 |
| 2 (D2) | GET_PIN(A, 8) | 是 | |
| 3 (D3) | GET_PIN(B, 10) | 是 | PWM2-CH3,默认被RT-Thread的PWM设备框架pwm2接管 |
| 4 (D4) | GET_PIN(A, 1) | 是 | |
| 5 (D5) | GET_PIN(B, 14) | 是 | |
| 6 (D6) | GET_PIN(B, 11) | 是 | PWM2-CH4,默认被RT-Thread的PWM设备框架pwm2接管 |
| 7 (D7) | GET_PIN(B, 15) | 是 | |
| 8 (D8) | GET_PIN(F, 15) | 是 | |
| 9 (D9) | GET_PIN(E, 11) | 是 | PWM1-CH2,默认被RT-Thread的PWM设备框架pwm1接管 |
| 10 (D10) | GET_PIN(E, 13) | 是 | PWM1-CH3,默认被RT-Thread的PWM设备框架pwm1接管 |
| 11 (D11) | GET_PIN(D, 12) | 是 | PWM4-CH1,默认被RT-Thread的PWM设备框架pwm4接管 |
| 12 (D12) | GET_PIN(D, 10) | 是 | |
| 13 (D13) | GET_PIN(D, 8) | 是 | |
| 14 (D14) | GET_PIN(E, 14) | 是 | |
| 15 (D15) | GET_PIN(E, 12) | 是 | |
| 16 (D16) | GET_PIN(E, 15) | 是 | |
| 17 (D17) | GET_PIN(D, 9) | 是 | |
| 18 (D18) | GET_PIN(G, 2) | 是 | |
| 19 (D19) | GET_PIN(B, 2) | 是 | |
| 20 (D20) | GET_PIN(G, 0) | 是 | |
| 21 (D21) | GET_PIN(A, 0) | 是 | |
| 22 (D22) | GET_PIN(G, 5) | 是 | SSPI1-SCK,默认被RT-Thread的SPI设备框架sspi1接管|
| 23 (D23) | GET_PIN(G, 3) | 是 | SSPI1-MISO,默认被RT-Thread的SPI设备框架sspi1接管 |
| 24 (D24) | GET_PIN(G, 1) | 是 | SSPI1-MOSI,默认被RT-Thread的SPI设备框架sspi1接管 |
| 25 (D25) | GET_PIN(G, 7) | 是 | I2C1-SCL,默认被RT-Thread的I2C设备框架i2c1接管 |
| 26 (D26) | GET_PIN(D, 7) | 是 | I2C1-SDA,默认被RT-Thread的I2C设备框架i2c1接管 |
| 27 (D27) | GET_PIN(B, 6) | 是 | I2C2-SCL,默认被RT-Thread的I2C设备框架i2c2接管 |
| 28 (D28) | GET_PIN(B, 7) | 是 | I2C2-SDA,默认被RT-Thread的I2C设备框架i2c2接管 |
| 29 (D29) | GET_PIN(G, 6) | 是 | |
| 30 (D30) | GET_PIN(G, 4) | 是 | |
| 31 (D31) | GET_PIN(A, 2) | 是 | Serial2-TX,默认被RT-Thread的UART设备框架uart2接管 |
| 32 (D32) | GET_PIN(A, 3) | 是 | Serial2-RX,默认被RT-Thread的UART设备框架uart2接管 |
| 33 (D33) | GET_PIN(F, 12) | 是 | 板载用户R_LED |
| 34 (D34) | GET_PIN(F, 11) | 是 | 板载用户B_LED |
| 35 (D35) | GET_PIN(B, 0) | 是 | 板载蜂鸣器 |
| 36 (D36) | GET_PIN(C, 5) | 是 | 板载按键KEY_UP |
| 37 (D37) | GET_PIN(C, 1) | 是 | 板载按键KEY_DOWM |
| 38 (D38) | GET_PIN(C, 0) | 是 | 板载按键KEY_LEFT |
| 39 (D39) | GET_PIN(C, 4) | 是 | 板载按键KEY_RIGHT |
| 40 (A0) | GET_PIN(F, 6) | 是 | ADC3-CH4,默认被RT-Thread的ADC设备框架adc3接管 |
| 41 (A1) | GET_PIN(F, 7) | 是 | ADC3-CH5,默认被RT-Thread的ADC设备框架adc3接管 |
| 42 (A2) | GET_PIN(F, 4) | 是 | ADC3-CH14,默认被RT-Thread的ADC设备框架adc3接管 |
| 43 (A3) | GET_PIN(F, 5) | 是 | ADC3-CH15,默认被RT-Thread的ADC设备框架adc3接管 |
| 44 (DAC0) | GET_PIN(A, 4) | 否 | DAC1-CH1,默认被RT-Thread的DAC设备框架dac1接管 |

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

View File

@ -0,0 +1,9 @@
from building import *
cwd = GetCurrentDir()
src = Glob('*.c') + Glob('*.cpp')
inc = [cwd]
group = DefineGroup('RTduino', src, depend = ['PKG_USING_RTDUINO'], CPPPATH = inc)
Return('group')

View File

@ -0,0 +1,69 @@
/*
* Copyright (c) 2006-2024, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2024-04-10 Li ZhenHong first version
*/
#include <Arduino.h>
#include "pins_arduino.h"
#include <drv_gpio.h>
/*
* {Arduino Pin, RT-Thread Pin [, Device Name, Channel]}
* [] means optional
* Digital pins must NOT give the device name and channel.
* Analog pins MUST give the device name and channel(ADC, PWM or DAC).
* Arduino Pin must keep in sequence.
*/
const pin_map_t pin_map_table[]=
{
{D0, GET_PIN(A, 10), "uart1"}, /* Serial-RX */
{D1, GET_PIN(A, 9), "uart1"}, /* Serial-TX */
{D2, GET_PIN(A, 8)},
{D3, GET_PIN(B, 10), "pwm2", 3}, /* PWM */
{D4, GET_PIN(A, 1)},
{D5, GET_PIN(B, 14)},
{D6, GET_PIN(B, 11), "pwm2", 4}, /* PWM */
{D7, GET_PIN(B, 15)},
{D8, GET_PIN(F, 15)},
{D9, GET_PIN(E, 11), "pwm1", 2}, /* PWM */
{D10, GET_PIN(E, 13), "pwm1", 3}, /* PWM */
{D11, GET_PIN(D, 12), "pwm4", 1}, /* PWM */
{D12, GET_PIN(D, 10)},
{D13, GET_PIN(D, 8)},
{D14, GET_PIN(E, 14)},
{D15, GET_PIN(E, 12)},
{D16, GET_PIN(E, 15)},
{D17, GET_PIN(D, 9)},
{D18, GET_PIN(G, 2)},
{D19, GET_PIN(B, 2)},
{D20, GET_PIN(G, 0)},
{D21, GET_PIN(A, 0)},
{D22, GET_PIN(G, 5), "sspi1"}, /* SOFT-SPI-SCK */
{D23, GET_PIN(G, 3), "sspi1"}, /* SOFT-SPI-SCK */
{D24, GET_PIN(G, 1), "sspi1"}, /* SOFT-SPI-SCK */
{D25, GET_PIN(G, 7), "i2c4"}, /* I2C-SCL (Wire) */
{D26, GET_PIN(D, 7), "i2c4"}, /* I2C-SDA (Wire) */
{D27, GET_PIN(B, 6), "i2c5"}, /* I2C-SCL (Wire) */
{D28, GET_PIN(B, 7), "i2c5"}, /* I2C-SDA (Wire) */
{D29, GET_PIN(G, 6)}, /* SPI-SS */
{D30, GET_PIN(G, 4)},
{D31, GET_PIN(A, 2), "uart2"}, /* Serial2-TX */
{D32, GET_PIN(A, 3), "uart2"}, /* Serial2-RX */
{D33, GET_PIN(F, 12)}, /* On-Board R_LED */
{D34, GET_PIN(F, 11)}, /* On-Board B_LED */
{D35, GET_PIN(B, 0)}, /* On_Board Buzzer */
{D36, GET_PIN(C, 5)}, /* On-Board KEY_UP */
{D37, GET_PIN(C, 1)}, /* On-Board KEY_DOWM */
{D38, GET_PIN(C, 0)}, /* On-Board KEY_LEFT */
{D39, GET_PIN(C, 4)}, /* On-Board KEY_RIGHT */
{A0, GET_PIN(F, 6), "adc3", 4}, /* ADC */
{A1, GET_PIN(F, 7), "adc3", 5}, /* ADC */
{A2, GET_PIN(F, 4), "adc3", 14}, /* ADC */
{A3, GET_PIN(F, 5), "adc3", 15}, /* ADC */
{DAC0, GET_PIN(A, 4), "dac1", 1}, /* DAC */
};

View File

@ -0,0 +1,77 @@
/*
* Copyright (c) 2006-2024, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2024-04-07 Li ZhenHong first version
*/
#ifndef Pins_Arduino_h
#define Pins_Arduino_h
/* pins alias. Must keep in sequence */
#define D0 (0)
#define D1 (1)
#define D2 (2)
#define D3 (3)
#define D4 (4)
#define D5 (5)
#define D6 (6)
#define D7 (7)
#define D8 (8)
#define D9 (9)
#define D10 (10)
#define D11 (11)
#define D12 (12)
#define D13 (13)
#define D14 (14)
#define D15 (15)
#define D16 (16)
#define D17 (17)
#define D18 (18)
#define D19 (19)
#define D20 (20)
#define D21 (21)
#define D22 (22)
#define D23 (23)
#define D24 (24)
#define D25 (25)
#define D26 (26)
#define D27 (27)
#define D28 (28)
#define D29 (29)
#define D30 (30)
#define D31 (31)
#define D32 (32)
#define D33 (33)
#define D34 (34)
#define D35 (35)
#define D36 (36)
#define D37 (37)
#define D38 (38)
#define D39 (39)
#define A0 (40)
#define A1 (41)
#define A2 (42)
#define A3 (43)
#define DAC0 (44)
#define RTDUINO_PIN_MAX_LIMIT DAC0 /* pin number max limit check */
#define F_CPU 168000000L /* CPU:168MHz */
#define LED_BUILTIN D33 /* Default Built-in LED */
/* i2c4 : PD.7-SDA PG.7-SCL */
#define RTDUINO_DEFAULT_IIC_BUS_NAME "i2c4"
#define SS D32 /* Chip select pin of default spi */
/* sspi1 : PG.5-SCK PG.3-MISO PG.1-MOSI */
#define RTDUINO_DEFAULT_SPI_BUS_NAME "sspi1"
/* Serial2(uart2) : PA.2-TX PA.3-RX */
#define RTDUINO_SERIAL2_DEVICE_NAME "uart2"
#endif /* Pins_Arduino_h */

View File

@ -0,0 +1,682 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>随机点餐转盘</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" rel="stylesheet">
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#FF6B6B',
secondary: '#4ECDC4',
accent: '#FFD166',
dark: '#2A2D34',
light: '#F7FFF7'
},
fontFamily: {
inter: ['Inter', 'system-ui', 'sans-serif'],
},
}
}
}
</script>
<style type="text/tailwindcss">
@layer utilities {
.content-auto {
content-visibility: auto;
}
.spinning {
animation: spin 3s cubic-bezier(0.17, 0.67, 0.83, 0.67);
}
.result-appear {
animation: fadeIn 0.5s ease-in-out;
}
.button-press {
animation: press 0.2s ease-in-out;
}
.heart-fall {
animation: heartFall 3s linear forwards;
}
.confetti {
animation: confettiFall 3s ease-in-out forwards;
}
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(calc(360deg * 5 + var(--random-rotation, 0deg))); }
}
@keyframes fadeIn {
0% { opacity: 0; transform: translateY(10px); }
100% { opacity: 1; transform: translateY(0); }
}
@keyframes press {
0% { transform: scale(1); }
50% { transform: scale(0.95); }
100% { transform: scale(1); }
}
@keyframes heartFall {
0% { transform: translateY(-10vh) rotate(0deg); opacity: 1; }
100% { transform: translateY(110vh) rotate(720deg); opacity: 0; }
}
@keyframes confettiFall {
0% { transform: translateY(-10vh) rotate(0deg); opacity: 1; }
100% { transform: translateY(110vh) rotate(360deg); opacity: 0; }
}
</style>
</head>
<body class="bg-gradient-to-br from-light to-secondary/10 min-h-screen font-inter text-dark flex flex-col relative overflow-x-hidden">
<header class="bg-white/80 backdrop-blur-md shadow-sm sticky top-0 z-50">
<div class="container mx-auto px-4 py-4 flex justify-between items-center">
<h1 class="text-[clamp(1.5rem,3vw,2.5rem)] font-bold text-primary flex items-center">
<i class="fa-solid fa-cutlery mr-3"></i>
<span>我可爱的老婆今天吃什么呀?(还有隐藏彩蛋等你呦!)</span>
</h1>
<button id="helpBtn" class="p-2 rounded-full hover:bg-gray-100 transition-all duration-300">
<i class="fa-solid fa-question text-dark/70"></i>
</button>
</div>
</header>
<main class="flex-grow container mx-auto px-4 py-8">
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- 左侧:食物列表 -->
<div class="bg-white rounded-xl shadow-md p-6 order-2 lg:order-1">
<h2 class="text-xl font-bold mb-4 flex items-center">
<i class="fa-solid fa-list-ul mr-2 text-secondary"></i>
食物列表
</h2>
<div id="foodList" class="space-y-2 max-h-[400px] overflow-y-auto pr-2">
<div class="food-item flex items-center justify-between p-3 bg-gray-50 rounded-lg hover:bg-gray-100 transition-all">
<span>烤鸭</span>
<button class="delete-btn text-red-400 hover:text-red-600 transition-colors">
<i class="fa-solid fa-trash"></i>
</button>
</div>
<div class="food-item flex items-center justify-between p-3 bg-gray-50 rounded-lg hover:bg-gray-100 transition-all">
<span>红烧肉</span>
<button class="delete-btn text-red-400 hover:text-red-600 transition-colors">
<i class="fa-solid fa-trash"></i>
</button>
</div>
<div class="food-item flex items-center justify-between p-3 bg-gray-50 rounded-lg hover:bg-gray-100 transition-all">
<span>炒茄子</span>
<button class="delete-btn text-red-400 hover:text-red-600 transition-colors">
<i class="fa-solid fa-trash"></i>
</button>
</div>
<div class="food-item flex items-center justify-between p-3 bg-gray-50 rounded-lg hover:bg-gray-100 transition-all">
<span>汉堡</span>
<button class="delete-btn text-red-400 hover:text-red-600 transition-colors">
<i class="fa-solid fa-trash"></i>
</button>
</div>
<div class="food-item flex items-center justify-between p-3 bg-gray-50 rounded-lg hover:bg-gray-100 transition-all">
<span>奶茶</span>
<button class="delete-btn text-red-400 hover:text-red-600 transition-colors">
<i class="fa-solid fa-trash"></i>
</button>
</div>
</div>
<div class="mt-6">
<h3 class="text-lg font-medium mb-2">添加食物</h3>
<div class="flex">
<input type="text" id="newFoodInput" placeholder="输入食物名称" class="flex-grow px-4 py-2 rounded-l-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-secondary/50 focus:border-secondary transition-all">
<button id="addFoodBtn" class="bg-secondary text-white px-4 py-2 rounded-r-lg hover:bg-secondary/90 transition-colors">
<i class="fa-solid fa-plus"></i>
</button>
</div>
</div>
</div>
<!-- 中间:转盘 -->
<div class="flex flex-col items-center justify-center order-1 lg:order-2">
<div class="relative">
<!-- 转盘 -->
<div id="wheelContainer" class="relative w-[min(80vw,400px)] h-[min(80vw,400px)]">
<canvas id="wheelCanvas" class="w-full h-full rounded-full shadow-lg"></canvas>
<!-- 指针 -->
<div class="absolute top-0 left-1/2 transform -translate-x-1/2 -translate-y-4 z-10">
<div class="w-8 h-12 bg-primary rounded-t-lg flex items-start justify-center shadow-md">
<div class="w-0 h-0 border-l-4 border-r-4 border-b-6 border-l-transparent border-r-transparent border-b-primary transform translate-y-[-10px]"></div>
</div>
</div>
<!-- 中心按钮 -->
<button id="spinBtn" class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-16 h-16 bg-white rounded-full shadow-lg flex items-center justify-center z-10 hover:bg-gray-50 transition-all duration-300 active:button-press">
<span class="text-primary font-bold text-lg"></span>
</button>
</div>
<!-- 结果显示 -->
<div id="resultDisplay" class="mt-8 text-center opacity-0">
<div class="bg-white p-4 rounded-xl shadow-md inline-block transform transition-all">
<h3 class="text-xl font-bold text-dark mb-1">今天就吃</h3>
<p id="resultText" class="text-3xl font-bold text-primary"></p>
</div>
</div>
</div>
</div>
<!-- 右侧:历史记录 -->
<div class="bg-white rounded-xl shadow-md p-6 order-3">
<h2 class="text-xl font-bold mb-4 flex items-center">
<i class="fa-solid fa-history mr-2 text-accent"></i>
历史记录
</h2>
<div id="historyList" class="space-y-3 max-h-[400px] overflow-y-auto pr-2">
<!-- 历史记录将通过JS动态添加 -->
<div class="text-center text-gray-500 italic">暂无记录</div>
</div>
<div class="mt-6">
<button id="clearHistoryBtn" class="w-full py-2 px-4 bg-gray-100 hover:bg-gray-200 rounded-lg text-gray-700 transition-colors flex items-center justify-center">
<i class="fa-solid fa-trash mr-2"></i>
清空历史
</button>
</div>
</div>
</div>
</main>
<footer class="bg-dark text-white py-6 mt-8">
<div class="container mx-auto px-4 text-center">
<p>© 2025 我可爱的老婆今天吃点什么呢?长按转盘随机点餐哦❤</p>
<p class="text-sm mt-2 text-gray-400">做出选择,告别纠结</p>
</div>
</footer>
<!-- 帮助模态框 -->
<div id="helpModal" class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 hidden flex items-center justify-center">
<div class="bg-white rounded-xl shadow-xl max-w-md w-full mx-4 overflow-hidden transform transition-all">
<div class="bg-primary text-white p-4 flex justify-between items-center">
<h3 class="text-xl font-bold">使用帮助</h3>
<button id="closeHelpBtn" class="text-white hover:text-gray-200 transition-colors">
<i class="fa-solid fa-times"></i>
</button>
</div>
<div class="p-6">
<ul class="space-y-4">
<li class="flex">
<div class="bg-primary/10 rounded-full p-2 mr-3">
<i class="fa-solid fa-list-ul text-primary"></i>
</div>
<div>
<h4 class="font-bold">管理食物列表</h4>
<p class="text-gray-600">在左侧输入框添加食物,点击垃圾桶图标删除食物</p>
</div>
</li>
<li class="flex">
<div class="bg-primary/10 rounded-full p-2 mr-3">
<i class="fa-solid fa-play text-primary"></i>
</div>
<div>
<h4 class="font-bold">开始转盘</h4>
<p class="text-gray-600">点击转盘中心的"转"按钮开始随机选择</p>
</div>
</li>
<li class="flex">
<div class="bg-primary/10 rounded-full p-2 mr-3">
<i class="fa-solid fa-history text-primary"></i>
</div>
<div>
<h4 class="font-bold">查看历史</h4>
<p class="text-gray-600">右侧面板会记录最近的选择结果</p>
</div>
</li>
<li class="flex">
<div class="bg-primary/10 rounded-full p-2 mr-3">
<i class="fa-solid fa-star text-primary"></i>
</div>
<div>
<h4 class="font-bold">隐藏彩蛋</h4>
<p class="text-gray-600">连续两次选中同一食物会触发爱心特效,连续三次选中同一食物会触发超级彩蛋!</p>
</div>
</li>
</ul>
</div>
<div class="bg-gray-50 p-4 text-center">
<button id="gotItBtn" class="bg-primary text-white px-6 py-2 rounded-lg hover:bg-primary/90 transition-colors">
知道了
</button>
</div>
</div>
</div>
<!-- 爱心容器 -->
<div id="heartsContainer" class="fixed inset-0 pointer-events-none z-40 hidden"></div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// 获取元素
const canvas = document.getElementById('wheelCanvas');
const ctx = canvas.getContext('2d');
const spinBtn = document.getElementById('spinBtn');
const foodList = document.getElementById('foodList');
const newFoodInput = document.getElementById('newFoodInput');
const addFoodBtn = document.getElementById('addFoodBtn');
const resultDisplay = document.getElementById('resultDisplay');
const resultText = document.getElementById('resultText');
const historyList = document.getElementById('historyList');
const clearHistoryBtn = document.getElementById('clearHistoryBtn');
const helpBtn = document.getElementById('helpBtn');
const helpModal = document.getElementById('helpModal');
const closeHelpBtn = document.getElementById('closeHelpBtn');
const gotItBtn = document.getElementById('gotItBtn');
const heartsContainer = document.getElementById('heartsContainer');
// 设置Canvas尺寸
const setCanvasSize = () => {
const container = canvas.parentElement;
const size = Math.min(container.clientWidth, container.clientHeight);
canvas.width = size;
canvas.height = size;
};
setCanvasSize();
window.addEventListener('resize', setCanvasSize);
// 食物数据
let foods = ['烤鸭', '红烧肉', '炒茄子', '汉堡', '奶茶']; // 修改了食物列表
let isSpinning = false;
let rotation = 0;
let resultIndex = 0;
let animationId = null;
// 历史记录
let history = [];
// 连续选择记录
let consecutiveResults = [];
// 绘制转盘
const drawWheel = () => {
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const radius = Math.min(centerX, centerY) - 20;
// 清除画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 如果没有食物,显示提示
if (foods.length === 0) {
ctx.fillStyle = '#f0f0f0';
ctx.beginPath();
ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#999';
ctx.font = 'bold 16px Inter';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('请添加食物', centerX, centerY);
return;
}
// 计算每个扇形的角度
const sliceAngle = (Math.PI * 2) / foods.length;
// 颜色数组
const colors = [
'#FF6B6B', '#4ECDC4', '#FFD166', '#6A0572', '#1A936F',
'#F7B801', '#E63946', '#457B9D', '#1D3557', '#A8DADC'
];
// 绘制每个扇形
foods.forEach((food, index) => {
const color = colors[index % colors.length];
const startAngle = sliceAngle * index + rotation;
const endAngle = startAngle + sliceAngle;
// 绘制扇形
ctx.fillStyle = color;
ctx.beginPath();
ctx.moveTo(centerX, centerY);
ctx.arc(centerX, centerY, radius, startAngle, endAngle);
ctx.closePath();
ctx.fill();
// 添加边框
ctx.strokeStyle = '#fff';
ctx.lineWidth = 2;
ctx.stroke();
// 绘制文字
ctx.save();
ctx.translate(centerX, centerY);
ctx.rotate(startAngle + sliceAngle / 2);
ctx.fillStyle = '#fff';
ctx.font = 'bold 14px Inter';
ctx.textAlign = 'center';
ctx.fillText(food, radius * 0.7, 0);
ctx.restore();
});
// 绘制中心圆
ctx.fillStyle = '#fff';
ctx.beginPath();
ctx.arc(centerX, centerY, 30, 0, Math.PI * 2);
ctx.fill();
};
// 旋转转盘
const spinWheel = () => {
// 修复检查动画ID并取消现有动画
if (animationId) {
cancelAnimationFrame(animationId);
}
if (isSpinning || foods.length < 2) return;
isSpinning = true;
resultDisplay.style.opacity = '0';
// 优化:根据食物数量调整旋转速度和圈数
const baseSpins = 3;
const spinVariation = Math.floor(Math.random() * 4) + 2;
const totalSpins = baseSpins + spinVariation;
// 优化:根据食物数量调整动画持续时间
const baseDuration = 2000;
const durationVariation = 1000;
const spinDuration = baseDuration + (foods.length * 200) + Math.random() * durationVariation;
const randomRotation = Math.random() * Math.PI * 2;
const targetRotation = rotation + totalSpins * Math.PI * 2 + randomRotation;
// 计算结果索引
const normalizedRotation = targetRotation % (Math.PI * 2);
const sliceAngle = (Math.PI * 2) / foods.length;
resultIndex = Math.floor((Math.PI * 2 - normalizedRotation) / sliceAngle) % foods.length;
// 动画旋转
let startTime = null;
const animateSpin = (timestamp) => {
if (!startTime) startTime = timestamp;
const progress = (timestamp - startTime) / spinDuration;
if (progress < 1) {
// 使用缓动函数使旋转减速
const easeOut = 1 - Math.pow(1 - progress, 3);
rotation = rotation + (targetRotation - rotation) * easeOut * (timestamp - startTime) / 16;
drawWheel();
animationId = requestAnimationFrame(animateSpin);
} else {
rotation = targetRotation;
drawWheel();
isSpinning = false;
animationId = null;
// 显示结果
setTimeout(() => {
resultText.textContent = foods[resultIndex];
resultDisplay.classList.add('result-appear');
resultDisplay.style.opacity = '1';
// 添加到历史记录
addToHistory(foods[resultIndex]);
// 检查连续选择
checkConsecutiveResults(foods[resultIndex]);
}, 500);
}
};
animationId = requestAnimationFrame(animateSpin);
};
// 添加食物
const addFood = () => {
const foodName = newFoodInput.value.trim();
if (foodName && !foods.includes(foodName)) {
foods.push(foodName);
newFoodInput.value = '';
updateFoodList();
drawWheel();
} else if (foods.includes(foodName)) {
newFoodInput.classList.add('border-red-500');
setTimeout(() => {
newFoodInput.classList.remove('border-red-500');
}, 1000);
}
};
// 更新食物列表
const updateFoodList = () => {
foodList.innerHTML = '';
if (foods.length === 0) {
const emptyState = document.createElement('div');
emptyState.className = 'text-center text-gray-500 italic py-4';
emptyState.textContent = '食物列表为空';
foodList.appendChild(emptyState);
return;
}
foods.forEach((food, index) => {
const foodItem = document.createElement('div');
foodItem.className = 'food-item flex items-center justify-between p-3 bg-gray-50 rounded-lg hover:bg-gray-100 transition-all';
const foodName = document.createElement('span');
foodName.textContent = food;
const deleteBtn = document.createElement('button');
deleteBtn.className = 'delete-btn text-red-400 hover:text-red-600 transition-colors';
deleteBtn.innerHTML = '<i class="fa-solid fa-trash"></i>';
deleteBtn.addEventListener('click', () => {
// 如果删除的是当前结果,重置结果显示
if (index === resultIndex) {
resultDisplay.style.opacity = '0';
}
foods.splice(index, 1);
updateFoodList();
drawWheel();
});
foodItem.appendChild(foodName);
foodItem.appendChild(deleteBtn);
foodList.appendChild(foodItem);
});
};
// 添加到历史记录
const addToHistory = (food) => {
const now = new Date();
const timeString = now.toLocaleTimeString();
history.unshift({
food,
time: timeString
});
// 限制历史记录数量
if (history.length > 10) {
history.pop();
}
updateHistoryList();
};
// 更新历史记录列表
const updateHistoryList = () => {
historyList.innerHTML = '';
if (history.length === 0) {
const emptyState = document.createElement('div');
emptyState.className = 'text-center text-gray-500 italic';
emptyState.textContent = '暂无记录';
historyList.appendChild(emptyState);
return;
}
history.forEach((item) => {
const historyItem = document.createElement('div');
historyItem.className = 'flex items-center justify-between p-3 border-b border-gray-100 last:border-0';
const foodContainer = document.createElement('div');
const foodName = document.createElement('p');
foodName.className = 'font-medium';
foodName.textContent = item.food;
const time = document.createElement('p');
time.className = 'text-sm text-gray-500';
time.textContent = item.time;
foodContainer.appendChild(foodName);
foodContainer.appendChild(time);
historyItem.appendChild(foodContainer);
historyList.appendChild(historyItem);
});
};
// 清空历史记录
const clearHistory = () => {
history = [];
updateHistoryList();
};
// 打开帮助模态框
const openHelpModal = () => {
helpModal.classList.remove('hidden');
helpModal.classList.add('flex');
document.body.style.overflow = 'hidden';
};
// 关闭帮助模态框
const closeHelpModal = () => {
helpModal.classList.add('hidden');
helpModal.classList.remove('flex');
document.body.style.overflow = '';
};
// 检查连续选择
const checkConsecutiveResults = (food) => {
// 添加到连续选择数组
consecutiveResults.push(food);
// 限制数组长度为3
if (consecutiveResults.length > 3) {
consecutiveResults.shift();
}
// 检查是否连续两次相同
if (consecutiveResults.length >= 2 &&
consecutiveResults[consecutiveResults.length - 1] === consecutiveResults[consecutiveResults.length - 2]) {
// 触发爱心特效
createHearts(food);
// 如果连续三次相同,触发彩蛋
if (consecutiveResults.length === 3 &&
consecutiveResults[0] === consecutiveResults[1] &&
consecutiveResults[1] === consecutiveResults[2]) {
triggerEasterEgg(food);
}
}
};
// 创建爱心特效
const createHearts = (food) => {
heartsContainer.innerHTML = '';
heartsContainer.classList.remove('hidden');
// 创建100个爱心
for (let i = 0; i < 100; i++) {
setTimeout(() => {
const heart = document.createElement('div');
heart.className = 'heart-fall absolute text-primary';
heart.style.left = `${Math.random() * 100}vw`;
heart.style.fontSize = `${16 + Math.random() * 24}px`;
heart.style.animationDuration = `${2 + Math.random() * 3}s`;
heart.style.opacity = `${0.5 + Math.random() * 0.5}`;
// 50%概率是爱心50%概率是食物名称
if (Math.random() > 0.5) {
heart.innerHTML = '❤️';
} else {
heart.innerHTML = food;
}
heartsContainer.appendChild(heart);
// 动画结束后移除
setTimeout(() => {
heart.remove();
}, 5000);
}, i * 30);
}
// 3秒后隐藏容器
setTimeout(() => {
heartsContainer.classList.add('hidden');
}, 5000);
};
// 触发彩蛋
const triggerEasterEgg = (food) => {
// 显示提示
alert(`恭喜触发彩蛋!看来你真的很想吃${food},那就别犹豫啦!`);
// 把转盘上所有选项都变成这个食物
const originalFoods = [...foods];
foods = Array(foods.length).fill(food);
// 重新绘制转盘
drawWheel();
// 3秒后恢复原来的食物列表
setTimeout(() => {
foods = originalFoods;
drawWheel();
}, 3000);
};
// 事件监听
spinBtn.addEventListener('click', spinWheel);
addFoodBtn.addEventListener('click', addFood);
newFoodInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') addFood();
});
clearHistoryBtn.addEventListener('click', clearHistory);
helpBtn.addEventListener('click', openHelpModal);
closeHelpBtn.addEventListener('click', closeHelpModal);
gotItBtn.addEventListener('click', closeHelpModal);
// 点击模态框外部关闭
helpModal.addEventListener('click', (e) => {
if (e.target === helpModal) {
closeHelpModal();
}
});
// 初始化
updateFoodList();
updateHistoryList();
drawWheel();
// 修复添加resize事件处理
window.addEventListener('resize', () => {
setCanvasSize();
drawWheel();
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,29 @@
/*
* Copyright (c) 2023, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2023-07-06 Supperthomas first version
* 2023-12-03 Meco Man support nano version
* 2024-04-13 yuanzihao adaptation for SkyStar STM32F407 version
*/
#include <board.h>
#include <rtthread.h>
//#include <drv_gpio.h>
#ifndef RT_USING_NANO
#include <rtdevice.h>
#endif /* RT_USING_NANO */
void led_loop(void);
int main(void)
{
while (1)
{
led_loop();
}
}

View File

@ -0,0 +1,50 @@
#include <sys/time.h>
#include <rtthread.h>
#include "pid.h"
void PID_Init(PID_TypeDef *pid, float kp, float ki, float kd)
{
pid->kp = kp;
pid->ki = ki;
pid->kd = kd;
pid->setpoint = 0;
pid->integral = 0;
pid->prev_error = 0;
pid->output = 0;
pid->last_time = 0;
}
float PID_Calculate(PID_TypeDef *pid, float measured, unsigned long now_time)
{
float error = pid->setpoint - measured;
float delta_time = (now_time - pid->last_time) / 1000.0f; // 转换为秒
if (delta_time <= 0)
{
return pid->output; // 如果时间间隔无效,返回上次输出
}
pid->integral += error * delta_time;
float derivative = (error - pid->prev_error) / delta_time;
pid->output = pid->kp * error + pid->ki * pid->integral + pid->kd * derivative;
pid->prev_error = error;
pid->last_time = now_time;
return pid->output;
}
extern unsigned long get_timestamp(void);
void pid_demo(void)
{
PID_TypeDef pid;
PID_Init(&pid, 1.0f, 0.5f, 0.1f);
pid.setpoint = 100.0f; // 设置目标值
// unsigned long now_time = get_timestamp(); // 获取当前时间戳
// float output = PID_Calcuate(&pid, measured, now);
}

View File

@ -0,0 +1,21 @@
#ifndef __PID_H__
#define __PID_H__
typedef struct
{
float kp;
float ki;
float kd;
float setpoint; // 目标值
float integral; // 积分项
float prev_error; // 上一次误差
float output; // 输出
unsigned long last_time; // 上次计算的时间戳单位ms
} PID_TypeDef;
void PID_Init(PID_TypeDef *pid, float kp,float ki, float ka);
float PID_Calculate(PID_TypeDef *pid,float measured,unsinged long now_time);
#endif // __PID_H__