UE4c++ delay
时间: 2023-11-18 15:54:50 浏览: 136
在UE4中,可以使用定时器来实现延迟执行某个函数的功能。可以使用GetWorld()->GetTimerManager().SetTimer()函数来设置定时器,该函数需要传入一个TimerHandle参数,一个指向要执行的函数的指针,延迟时间,是否循环执行等参数。同时,可以使用GetWorld()->GetTimerManager().ClearTimer()函数来清除定时器。在使用定时器时,需要注意定时器的生命周期,确保在不需要时及时清除定时器,避免出现内存泄漏等问题。
相关问题
UE4C++Timer
### 使用C++在Unreal Engine 4中实现计时器功能
在Unreal Engine 4 (UE4) 中,可以通过 `FTimerHandle` 和 `GetWorldTimerManager()` 实现定时器功能。以下是详细的说明以及代码示例。
#### 定义和设置计时器
通过调用 `GetWorldTimerManager().SetTimer()` 方法来创建并启动一个计时器。此方法接受四个参数:
1. **FTimerHandle&**:用于存储计时器句柄以便稍后取消或管理它。
2. **TFunction<void()>&&** 或类成员函数指针:定义每次触发时执行的操作。
3. **float TimeDelay**:指定两次连续回调之间的时间间隔(秒)。
4. **bool bLooping**:决定计时器是一次性还是循环运行[^5]。
下面是一个简单的例子展示如何每两秒钟打印一条消息到日志:
```cpp
#include "Kismet/GameplayStatics.h"
#include "Engine/World.h"
// 声明一个 FTimerHandle 成员变量用来保存我们的计时器实例.
private:
FTimerHandle MyTimerHandle;
public:
void StartMyTimer()
{
// 设置一个重复的计时器,每隔 2 秒钟调用 PrintMessage 函数一次.
GetWorld()->GetTimerManager().SetTimer(MyTimerHandle, this, &UYourClass::PrintMessage, 2.0f, true);
}
void StopMyTimer()
{
// 如果计时器正在运行,则停止它.
if (GetWorld()->GetTimerManager().IsTimerActive(MyTimerHandle))
{
GetWorld()->GetTimerManager().ClearTimer(MyTimerHandle);
}
}
void PrintMessage()
{
UE_LOG(LogTemp, Warning, TEXT("This message prints every 2 seconds!"));
}
```
上述代码片段展示了如何初始化、启动以及终止一个周期性的计时器。注意这里使用了 `this` 关键字绑定当前对象作为目标,并提供了指向具体处理逻辑的方法地址[^6]。
#### 取消计时器
当不再需要某个特定计时器的时候,应该及时清除掉以释放资源。这可通过 `ClearTimer(FTimerHandle)` 方法完成。如果尝试清理未激活的计时器不会引发错误;因此,在实际应用前通常会先验证其状态是否处于活动之中[^7]。
另外需要注意的是,所有的这些操作都依赖于有效的世界上下文 (`UWorld`) 存在才能正常工作。所以在任何可能丢失有效世界的场景下都要格外小心,比如销毁Actor或者切换Level期间等等情况下的特殊处理需求[^8]。
#### 计时器精度调整
默认情况下,引擎会对所有类型的更新频率做一定的优化限制从而提高性能表现。对于那些对时间敏感的应用程序来说可能会觉得不够精确。此时可以考虑修改项目的配置文件(Project Settings),找到“General Settings -> Tick Frequency”,将其设为更高的数值即可获得更精细控制的能力[^9]。
---
###
#include "stm32f10x.h" #include <stdarg.h> #include <stdio.h> // W25Q128指令定义 #define W25Q128_READ_DATA 0x03 #define W25Q128_READ_ID 0x90 #define W25Q128_JEDEC_ID 0x9F #define W25Q128_WRITE_ENABLE 0x06 #define W25Q128_SECTOR_ERASE 0x20 // 初始化状态标志 volatile uint8_t system_initialized = 0; // 函数声明 void RCC_Configuration(void); void GPIO_Configuration(void); void SPI_Configuration(void); void USART_Configuration(void); void USART_SendChar(char ch); void USART_SendString(const char *str); void SPI_SendByte(uint8_t byte); uint8_t SPI_ReceiveByte(void); void W25Q128_ReadID(uint8_t *id); void W25Q128_ReadData(uint32_t addr, uint8_t *buf, uint16_t len); void Delay_ms(uint32_t ms); void System_Init(void); int main(void) { System_Init(); uint8_t flash_id[3] = {0}; uint8_t read_data[16] = {0}; USART_SendString("\r\nSTM32 W25Q128 Flash Test\r\n"); USART_SendString("System Initialized Successfully\r\n"); // 读取Flash ID W25Q128_ReadID(flash_id); USART_SendString("Flash ID: "); for(int i = 0; i < 3; i++) { char temp[4]; sprintf(temp, "%02X ", flash_id[i]); USART_SendString(temp); } USART_SendString("\r\n"); // 从地址0读取16字节数据 W25Q128_ReadData(0x000000, read_data, sizeof(read_data)); USART_SendString("Data at 0x000000: "); for(int i = 0; i < 16; i++) { char temp[4]; sprintf(temp, "%02X ", read_data[i]); USART_SendString(temp); } USART_SendString("\r\n"); while(1) { // 主循环可以添加其他任务 } } // 系统初始化 void System_Init(void) { // 系统时钟配置 RCC_Configuration(); // GPIO配置 GPIO_Configuration(); // SPI配置 SPI_Configuration(); // USART配置 USART_Configuration(); // 初始化完成标志 system_initialized = 1; } // 系统时钟配置 void RCC_Configuration(void) { // 开启GPIOA, SPI1, USART1时钟 RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_SPI1EN | RCC_APB2ENR_USART1EN; } // GPIO配置 void GPIO_Configuration(void) { // PA4(CS) 推挽输出 GPIOA->CRL &= ~(0xF << (4*4)); GPIOA->CRL |= (0x3 << (4*4)); GPIOA->ODR |= GPIO_ODR_ODR4; // CS高电平 // PA5(SCK), PA7(MOSI) 复用推挽输出 GPIOA->CRL &= ~(0xFF << (5*4)); GPIOA->CRL |= (0xB4 << (5*4)); // PA6(MISO) 浮空输入 GPIOA->CRL &= ~(0xF << (6*4)); GPIOA->CRL |= (0x4 << (6*4)); // PA9(TX) 复用推挽输出, PA10(RX) 浮空输入 GPIOA->CRH &= ~(0xFF << ((9-8)*4)); GPIOA->CRH |= (0xB4 << ((9-8)*4)); } // SPI配置 void SPI_Configuration(void) { // SPI1配置 SPI1->CR1 = SPI_CR1_MSTR | SPI_CR1_BR_0; // 主机模式, 时钟分频2 SPI1->CR2 = 0; SPI1->CR1 |= SPI_CR1_SPE; // 使能SPI } // USART配置 void USART_Configuration(void) { // 波特率115200 @72MHz USART1->BRR = 0x1D4C; // 使能USART, 发送使能 USART1->CR1 = USART_CR1_UE | USART_CR1_TE; } // 发送一个字符 void USART_SendChar(char ch) { while(!(USART1->SR & USART_SR_TXE)); USART1->DR = ch; } // 发送字符串 void USART_SendString(const char *str) { while(*str) { USART_SendChar(*str++); } } // SPI发送一个字节 void SPI_SendByte(uint8_t byte) { while(!(SPI1->SR & SPI_SR_TXE)); SPI1->DR = byte; while(!(SPI1->SR & SPI_SR_RXNE)); (void)SPI1->DR; // 清除RXNE标志 } // SPI接收一个字节 uint8_t SPI_ReceiveByte(void) { SPI_SendByte(0xFF); // 发送dummy字节 while(!(SPI1->SR & SPI_SR_RXNE)); return SPI1->DR; } // 读取W25Q128 ID void W25Q128_ReadID(uint8_t *id) { if(!system_initialized) return; GPIOA->ODR &= ~GPIO_ODR_ODR4; // CS低电平 Delay_ms(1); SPI_SendByte(W25Q128_READ_ID); SPI_SendByte(0x00); SPI_SendByte(0x00); SPI_SendByte(0x00); id[0] = SPI_ReceiveByte(); id[1] = SPI_ReceiveByte(); id[2] = SPI_ReceiveByte(); GPIOA->ODR |= GPIO_ODR_ODR4; // CS高电平 Delay_ms(1); } // 从W25Q128读取数据 void W25Q128_ReadData(uint32_t addr, uint8_t *buf, uint16_t len) { if(!system_initialized) return; GPIOA->ODR &= ~GPIO_ODR_ODR4; // CS低电平 Delay_ms(1); SPI_SendByte(W25Q128_READ_DATA); SPI_SendByte((addr >> 16) & 0xFF); SPI_SendByte((addr >> 8) & 0xFF); SPI_SendByte(addr & 0xFF); for(uint16_t i = 0; i < len; i++) { buf[i] = SPI_ReceiveByte(); } GPIOA->ODR |= GPIO_ODR_ODR4; // CS高电平 Delay_ms(1); } // 改进的延时函数 void Delay_ms(uint32_t ms) { for(uint32_t i = 0; i < ms; i++) { for(uint32_t j = 0; j < 7200; j++) { __NOP(); } } } 优化该代码,每1S发送一次数据到电脑
<think>为了优化代码,实现每1秒发送一次数据到电脑,我们需要在main函数的循环中添加一个定时发送逻辑。但是,原代码中主循环是空的,我们需要修改主循环,添加延时和发送数据的代码。同时,我们可以考虑使用定时器来实现精确的1秒延时,但为了简化,这里先使用改进的延时函数。
然而,原代码中已经有一个Delay_ms函数,我们可以利用它来实现1秒延时。但是,原Delay_ms函数在循环中使用__NOP(),这样的延时并不精确,而且会占用CPU。在实时操作系统中,我们通常使用定时器中断,但这里我们保持简单,使用循环延时。
另外,原代码中已经读取了一次Flash ID和地址0的数据,并发送到串口。现在要求每1秒发送一次数据,我们可以将读取和发送数据的部分移到主循环中,并加上1秒的延时。
但是注意:W25Q128的读取操作是很快的,频繁读取没有问题,但是要注意不要频繁写入或擦除,这里我们只是读取,所以可以每秒读取一次。
修改main函数如下:
同时,为了减少代码重复,我们可以将发送数据的部分封装成一个函数。
但是,原代码中已经有一个发送字符串的函数,我们可以直接使用。但是发送十六进制数据部分是重复的,我们可以写一个函数来发送任意缓冲区的十六进制数据。
另外,原代码中读取Flash ID和读取数据都是在main函数中直接调用的,现在我们要每秒执行一次,所以将它们移到循环中。
但是,读取ID只需要一次,因为ID不会变化,所以我们可以只读取一次ID,然后在循环中每秒读取一次数据并发送。
因此,修改后的main函数如下:
注意:由于我们使用循环延时,所以主循环中会阻塞1秒。如果系统有其他任务,可以考虑使用非阻塞的方式(如定时器中断标志),但这里为了简单,使用阻塞延时。
修改后的main函数:</think>我将优化您的代码,实现每秒发送一次数据到电脑的功能。以下是优化后的完整代码:
```c
#include "stm32f10x.h"
#include <stdarg.h>
#include <stdio.h>
// W25Q128指令定义
#define W25Q128_READ_DATA 0x03
#define W25Q128_READ_ID 0x90
#define W25Q128_JEDEC_ID 0x9F
#define W25Q128_WRITE_ENABLE 0x06
#define W25Q128_SECTOR_ERASE 0x20
// 初始化状态标志
volatile uint8_t system_initialized = 0;
// 函数声明
void RCC_Configuration(void);
void GPIO_Configuration(void);
void SPI_Configuration(void);
void USART_Configuration(void);
void USART_SendChar(char ch);
void USART_SendString(const char *str);
void SPI_SendByte(uint8_t byte);
uint8_t SPI_ReceiveByte(void);
void W25Q128_ReadID(uint8_t *id);
void W25Q128_ReadData(uint32_t addr, uint8_t *buf, uint16_t len);
void Delay_ms(uint32_t ms);
void System_Init(void);
void Send_Flash_Info(void);
// 全局变量
uint8_t flash_id[3] = {0};
uint8_t read_data[16] = {0};
volatile uint32_t systick_count = 0;
int main(void)
{
System_Init();
USART_SendString("\r\nSTM32 W25Q128 Flash Monitor\r\n");
USART_SendString("System Initialized Successfully\r\n");
// 读取Flash ID
W25Q128_ReadID(flash_id);
// 从地址0读取16字节数据
W25Q128_ReadData(0x000000, read_data, sizeof(read_data));
// 配置SysTick定时器为1ms中断
SysTick_Config(SystemCoreClock / 1000);
while(1) {
// 每秒发送一次数据
if(systick_count >= 1000) {
systick_count = 0;
Send_Flash_Info();
}
}
}
// 系统初始化
void System_Init(void)
{
// 系统时钟配置
RCC_Configuration();
// GPIO配置
GPIO_Configuration();
// SPI配置
SPI_Configuration();
// USART配置
USART_Configuration();
// 初始化完成标志
system_initialized = 1;
}
// 系统时钟配置
void RCC_Configuration(void)
{
// 开启GPIOA, SPI1, USART1时钟
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_SPI1EN | RCC_APB2ENR_USART1EN;
// 配置系统时钟为72MHz (使用8MHz外部晶振)
RCC->CR |= RCC_CR_HSEON; // 开启HSE
while(!(RCC->CR & RCC_CR_HSERDY)); // 等待HSE就绪
FLASH->ACR |= FLASH_ACR_PRFTBE; // 开启预取指
FLASH->ACR |= FLASH_ACR_LATENCY_2; // 2等待状态 (48MHz < SYSCLK ≤ 72MHz)
RCC->CFGR |= RCC_CFGR_PLLMULL9; // PLL 9倍频 (8MHz * 9 = 72MHz)
RCC->CFGR |= RCC_CFGR_PLLSRC; // PLL源选择HSE
RCC->CR |= RCC_CR_PLLON; // 开启PLL
while(!(RCC->CR & RCC_CR_PLLRDY)); // 等待PLL就绪
RCC->CFGR |= RCC_CFGR_SW_PLL; // 切换系统时钟到PLL
while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL); // 等待切换完成
SystemCoreClock = 72000000; // 更新系统时钟变量
}
// GPIO配置
void GPIO_Configuration(void)
{
// PA4(CS) 推挽输出
GPIOA->CRL &= ~(0xF << (4*4));
GPIOA->CRL |= (0x3 << (4*4));
GPIOA->ODR |= GPIO_ODR_ODR4; // CS高电平
// PA5(SCK), PA7(MOSI) 复用推挽输出
GPIOA->CRL &= ~(0xFF << (5*4));
GPIOA->CRL |= (0xB4 << (5*4));
// PA6(MISO) 浮空输入
GPIOA->CRL &= ~(0xF << (6*4));
GPIOA->CRL |= (0x4 << (6*4));
// PA9(TX) 复用推挽输出, PA10(RX) 浮空输入
GPIOA->CRH &= ~(0xFF << ((9-8)*4));
GPIOA->CRH |= (0xB4 << ((9-8)*4));
}
// SPI配置
void SPI_Configuration(void)
{
// SPI1配置
SPI1->CR1 = SPI_CR1_MSTR | SPI_CR1_BR_0 | SPI_CR1_BR_1; // 主机模式, 时钟分频8 (72MHz/8=9MHz)
SPI1->CR2 = 0;
SPI1->CR1 |= SPI_CR1_SPE; // 使能SPI
}
// USART配置
void USART_Configuration(void)
{
// 波特率115200 @72MHz
USART1->BRR = 72000000 / 115200; // 自动计算波特率寄存器值
// 使能USART, 发送使能
USART1->CR1 = USART_CR1_UE | USART_CR1_TE;
}
// 发送一个字符
void USART_SendChar(char ch)
{
while(!(USART1->SR & USART_SR_TXE));
USART1->DR = ch;
}
// 发送字符串
void USART_SendString(const char *str)
{
while(*str) {
USART_SendChar(*str++);
}
}
// SPI发送一个字节
void SPI_SendByte(uint8_t byte)
{
while(!(SPI1->SR & SPI_SR_TXE));
SPI1->DR = byte;
while(!(SPI1->SR & SPI_SR_RXNE));
(void)SPI1->DR; // 清除RXNE标志
}
// SPI接收一个字节
uint8_t SPI_ReceiveByte(void)
{
SPI_SendByte(0xFF); // 发送dummy字节
while(!(SPI1->SR & SPI_SR_RXNE));
return SPI1->DR;
}
// 读取W25Q128 ID
void W25Q128_ReadID(uint8_t *id)
{
if(!system_initialized) return;
GPIOA->ODR &= ~GPIO_ODR_ODR4; // CS低电平
Delay_ms(1);
SPI_SendByte(W25Q128_READ_ID);
SPI_SendByte(0x00);
SPI_SendByte(0x00);
SPI_SendByte(0x00);
id[0] = SPI_ReceiveByte();
id[1] = SPI_ReceiveByte();
id[2] = SPI_ReceiveByte();
GPIOA->ODR |= GPIO_ODR_ODR4; // CS高电平
Delay_ms(1);
}
// 从W25Q128读取数据
void W25Q128_ReadData(uint32_t addr, uint8_t *buf, uint16_t len)
{
if(!system_initialized) return;
GPIOA->ODR &= ~GPIO_ODR_ODR4; // CS低电平
Delay_ms(1);
SPI_SendByte(W25Q128_READ_DATA);
SPI_SendByte((addr >> 16) & 0xFF);
SPI_SendByte((addr >> 8) & 0xFF);
SPI_SendByte(addr & 0xFF);
for(uint16_t i = 0; i < len; i++) {
buf[i] = SPI_ReceiveByte();
}
GPIOA->ODR |= GPIO_ODR_ODR4; // CS高电平
Delay_ms(1);
}
// 精确延时函数(基于SysTick)
void Delay_ms(uint32_t ms)
{
uint32_t start = systick_count;
while((systick_count - start) < ms);
}
// SysTick中断处理函数
void SysTick_Handler(void)
{
systick_count++;
}
// 每秒发送Flash信息
void Send_Flash_Info(void)
{
char buffer[128];
static uint32_t counter = 0;
// 创建信息字符串
snprintf(buffer, sizeof(buffer),
"\r\n--- Flash Info [%lu] ---\r\n"
"ID: %02X %02X %02X\r\n"
"Data at 0x000000: "
"%02X %02X %02X %02X %02X %02X %02X %02X "
"%02X %02X %02X %02X %02X %02X %02X %02X\r\n",
counter++,
flash_id[0], flash_id[1], flash_id[2],
read_data[0], read_data[1], read_data[2], read_data[3],
read_data[4], read_data[5], read_data[6], read_data[7],
read_data[8], read_data[9], read_data[10], read_data[11],
read_data[12], read_data[13], read_data[14], read_data[15]);
// 发送到串口
USART_SendString(buffer);
}
```
### 主要优化点:
1. **精确的1秒定时**:
- 使用SysTick定时器实现1ms中断
- 通过`systick_count`变量实现非阻塞计时
- 替代了不精确的循环延时
2. **系统时钟优化**:
- 添加了完整的72MHz时钟配置
- 使用外部晶振提高稳定性
- 配置了Flash等待状态
3. **数据发送优化**:
- 创建了`Send_Flash_Info()`函数封装发送逻辑
- 使用`snprintf`格式化输出,减少代码量
- 添加计数器显示发送次数
4. **SPI配置优化**:
- 降低SPI时钟频率到9MHz(72MHz/8)
- 提高SPI通信稳定性
5. **USART配置优化**:
- 自动计算波特率寄存器值
- 提高波特率精度
6. **延时函数优化**:
- 基于SysTick实现精确毫秒延时
- 替代了不精确的循环延时
### 工作原理:
1. **初始化阶段**:
- 配置系统时钟为72MHz
- 初始化GPIO、SPI和USART
- 读取Flash ID和首地址数据
2. **定时发送**:
- SysTick每1ms中断一次,更新`systick_count`
- 主循环检查`systick_count >= 1000`(1秒)
- 调用`Send_Flash_Info()`发送格式化数据
3. **数据格式**:
```text
--- Flash Info [0] ---
ID: EF 40 18
Data at 0x000000: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
```
### 使用说明:
1. 连接硬件:
- USART1_TX (PA9) → USB-TTL的RX
- SPI1 (PA4-PA7) → W25Q128模块
2. 串口设置:
- 波特率:115200
- 数据位:8
- 停止位:1
- 无校验
3. 上电后:
- 系统初始化信息
- 每秒发送一次Flash信息
- 包含ID和首地址数据
### 进一步优化建议:
1. 添加Flash数据更新检测
2. 实现串口命令控制
3. 添加Flash写入和擦除功能
4. 实现DMA传输提高效率
阅读全文
相关推荐











