原始版本
This commit is contained in:
18
stm32f407vet6_v2/applications/SConscript
Normal file
18
stm32f407vet6_v2/applications/SConscript
Normal 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')
|
||||
31
stm32f407vet6_v2/applications/app.c
Normal file
31
stm32f407vet6_v2/applications/app.c
Normal file
@ -0,0 +1,31 @@
|
||||
#include <sys/time.h>
|
||||
#include <rtthread.h>
|
||||
|
||||
/**
|
||||
* @brief 测试 timegm 函数,将指定时间转换为时间戳
|
||||
*
|
||||
* 需要注意 tm 结构体的各字段含义:
|
||||
* tm_year: 年份 = 实际年份 - 1900
|
||||
* tm_mon : 月份 = 0~11(1月为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-11,5月为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);
|
||||
24
stm32f407vet6_v2/applications/arduino_main.cpp
Normal file
24
stm32f407vet6_v2/applications/arduino_main.cpp
Normal 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);
|
||||
}
|
||||
69
stm32f407vet6_v2/applications/arduino_pinout/README.md
Normal file
69
stm32f407vet6_v2/applications/arduino_pinout/README.md
Normal 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)。
|
||||
|
||||

|
||||
|
||||
| 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 |
9
stm32f407vet6_v2/applications/arduino_pinout/SConscript
Normal file
9
stm32f407vet6_v2/applications/arduino_pinout/SConscript
Normal 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')
|
||||
69
stm32f407vet6_v2/applications/arduino_pinout/pins_arduino.c
Normal file
69
stm32f407vet6_v2/applications/arduino_pinout/pins_arduino.c
Normal 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 */
|
||||
};
|
||||
77
stm32f407vet6_v2/applications/arduino_pinout/pins_arduino.h
Normal file
77
stm32f407vet6_v2/applications/arduino_pinout/pins_arduino.h
Normal 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 */
|
||||
682
stm32f407vet6_v2/applications/eat.html
Normal file
682
stm32f407vet6_v2/applications/eat.html
Normal 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>
|
||||
29
stm32f407vet6_v2/applications/main.c
Normal file
29
stm32f407vet6_v2/applications/main.c
Normal 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();
|
||||
}
|
||||
}
|
||||
50
stm32f407vet6_v2/applications/pid.c
Normal file
50
stm32f407vet6_v2/applications/pid.c
Normal 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);
|
||||
|
||||
}
|
||||
21
stm32f407vet6_v2/applications/pid.h
Normal file
21
stm32f407vet6_v2/applications/pid.h
Normal 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__
|
||||
Reference in New Issue
Block a user