首次初始化

This commit is contained in:
zhy
2026-07-08 10:30:07 +08:00
commit b4423fcfa2
1963 changed files with 898669 additions and 0 deletions
+715
View File
@@ -0,0 +1,715 @@
#include "ili9341.h"
#include "cmsis_os.h"
#include "uart4.h"
#include <stdarg.h>
#include <stdio.h>
/*
* ILI9341 TFT LCD 驱动
* STM32H750 + SPI1 + DMA
*
* 本版本相对上一版的主要改动:
* 1. DMA buffer 不再用裸指针指向硬编码地址,改用链接器 section,
* 避免和其他默认分配(heap/栈/其他静态变量)物理重叠。
* —— 需要在 .ld 文件里配合添加一个 section,见文件末尾说明。
* 2. DMA 完成/错误的等待方式从"HAL_GetTick() 忙等"改成
* "RTOS 信号量 + 超时",真正让出 CPU,而不是空转。
* 3. ISR 回调只做最少的事(释放信号量/记录错误码),所有
* 恢复逻辑、日志打印都放在任务上下文里做,符合
* "中断里越短越好"的原则。
* 4. 增加了 HAL_SPI_ErrorCallback,之前只处理了 TxCplt
* 一旦发生 SPI 总线错误(比如 CRC/Overrun/ModeFault),
* 原来的代码只能傻等 100ms 超时,现在能立刻被唤醒并记录。
* 5. 调试日志走 Print_DMA(非阻塞 UART DMA 发送),不占用
* 本来就紧张的 SPI/DMA 时间片。
*/
extern SPI_HandleTypeDef hspi1;
static uint16_t lcd_width = ILI9341_WIDTH;
static uint16_t lcd_height = ILI9341_HEIGHT;
/* LCD GPIO 控制 */
#define LCD_CS_LOW() HAL_GPIO_WritePin(LCD_CS_PORT, LCD_CS_PIN, GPIO_PIN_RESET)
#define LCD_CS_HIGH() HAL_GPIO_WritePin(LCD_CS_PORT, LCD_CS_PIN, GPIO_PIN_SET)
#define LCD_DC_CMD() HAL_GPIO_WritePin(LCD_DC_PORT, LCD_DC_PIN, GPIO_PIN_RESET)
#define LCD_DC_DATA() HAL_GPIO_WritePin(LCD_DC_PORT, LCD_DC_PIN, GPIO_PIN_SET)
#define LCD_RST_LOW() HAL_GPIO_WritePin(LCD_RST_PORT, LCD_RST_PIN, GPIO_PIN_RESET)
#define LCD_RST_HIGH() HAL_GPIO_WritePin(LCD_RST_PORT, LCD_RST_PIN, GPIO_PIN_SET)
#define LCD_BL_ON() HAL_GPIO_WritePin(LCD_BL_PORT, LCD_BL_PIN, GPIO_PIN_SET)
#define LCD_BL_OFF() HAL_GPIO_WritePin(LCD_BL_PORT, LCD_BL_PIN, GPIO_PIN_RESET)
/*
* LVGL buffer 使用 20 行:
* 320 * 20 * 2 = 12800 bytes
*/
#define LCD_DMA_MAX_PIXELS (320U * 20U)
#define LCD_DMA_BUF_SIZE (LCD_DMA_MAX_PIXELS * 2U)
/*
* DMA buffer 放到链接器专属 section 里,而不是裸写绝对地址。
* 需要在 .ld 文件的 MEMORY 区里确认 D2 SRAM0x30000000 起)
* 已经声明为一个可用 region(很多 CubeIDE 模板里叫 RAM_D2),
* 并在 SECTIONS 里加:
*
* .dma_buffer (NOLOAD) :
* {
* . = ALIGN(32);
* *(.dma_buffer)
* . = ALIGN(32);
* } >RAM_D2
*
* 这样链接器会知道这块内存已经被占用,不会再把 heap/栈/其他
* 全局变量分配到这里,从根本上排除"覆盖冲突"的可能性。
* 32 字节对齐是为了配合 D-Cache clean-by-address 的对齐要求。
*/
#if defined(__GNUC__)
#define LCD_DMA_BUF_ATTR __attribute__((section(".dma_buffer"), aligned(32)))
#else
#define LCD_DMA_BUF_ATTR
#warning "非 GNU 工具链,请自行确认 lcd_dma_buf 被放置在 DMA 可访问、且不会被其他数据覆盖的内存区域"
#endif
LCD_DMA_BUF_ATTR
static uint8_t lcd_dma_buf[LCD_DMA_BUF_SIZE];
/* ---- 状态与同步 ---- */
static volatile uint8_t lcd_dma_busy = 0;
static volatile uint32_t lcd_dma_start_tick = 0;
static volatile uint32_t lcd_spi_last_error = 0;
static osSemaphoreId_t lcd_dma_sem = NULL;
/* ---- 非阻塞调试日志:走 Print_DMA,忙的时候直接丢弃这条日志 ---- */
static char lcd_dbg_line[96];
static void LCD_DebugLogf(const char *fmt, ...)
{
if (UART_IsBusy())
{
return; /* 调试串口正忙,宁可丢日志也不要阻塞关键路径 */
}
va_list args;
va_start(args, fmt);
vsnprintf(lcd_dbg_line, sizeof(lcd_dbg_line), fmt, args);
va_end(args);
Print_DMA(lcd_dbg_line);
}
static void LCD_DMA_EnsureSemaphore(void)
{
if (lcd_dma_sem == NULL)
{
lcd_dma_sem = osSemaphoreNew(1, 0, NULL);
}
}
/* ========================= 基础 SPI 写命令/数据(阻塞,保持不变) ========================= */
static void ILI9341_WriteCommand(uint8_t cmd)
{
LCD_CS_LOW();
LCD_DC_CMD();
HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY);
LCD_CS_HIGH();
}
static void ILI9341_WriteData(uint8_t *data, uint16_t size)
{
LCD_CS_LOW();
LCD_DC_DATA();
HAL_SPI_Transmit(&hspi1, data, size, HAL_MAX_DELAY);
LCD_CS_HIGH();
}
static void ILI9341_WriteData8(uint8_t data)
{
LCD_CS_LOW();
LCD_DC_DATA();
HAL_SPI_Transmit(&hspi1, &data, 1, HAL_MAX_DELAY);
LCD_CS_HIGH();
}
static void ILI9341_WriteData16(uint16_t data)
{
uint8_t buf[2];
buf[0] = (uint8_t)(data >> 8);
buf[1] = (uint8_t)(data & 0xFF);
LCD_CS_LOW();
LCD_DC_DATA();
HAL_SPI_Transmit(&hspi1, buf, 2, HAL_MAX_DELAY);
LCD_CS_HIGH();
}
static void ILI9341_Reset(void)
{
LCD_RST_HIGH();
HAL_Delay(20);
LCD_RST_LOW();
HAL_Delay(20);
LCD_RST_HIGH();
HAL_Delay(120);
}
static void ILI9341_SetAddressWindow(uint16_t x0,
uint16_t y0,
uint16_t x1,
uint16_t y1)
{
uint8_t data[4];
ILI9341_WriteCommand(0x2A);
data[0] = (uint8_t)(x0 >> 8);
data[1] = (uint8_t)(x0 & 0xFF);
data[2] = (uint8_t)(x1 >> 8);
data[3] = (uint8_t)(x1 & 0xFF);
ILI9341_WriteData(data, 4);
ILI9341_WriteCommand(0x2B);
data[0] = (uint8_t)(y0 >> 8);
data[1] = (uint8_t)(y0 & 0xFF);
data[2] = (uint8_t)(y1 >> 8);
data[3] = (uint8_t)(y1 & 0xFF);
ILI9341_WriteData(data, 4);
ILI9341_WriteCommand(0x2C);
}
/* ========================= 初始化 ========================= */
void ILI9341_Init(void)
{
LCD_CS_HIGH();
LCD_BL_ON();
LCD_DMA_EnsureSemaphore();
ILI9341_Reset();
ILI9341_WriteCommand(0x01);
HAL_Delay(120);
ILI9341_WriteCommand(0xEF);
{
uint8_t data[] = {0x03, 0x80, 0x02};
ILI9341_WriteData(data, sizeof(data));
}
ILI9341_WriteCommand(0xCF);
{
uint8_t data[] = {0x00, 0xC1, 0x30};
ILI9341_WriteData(data, sizeof(data));
}
ILI9341_WriteCommand(0xED);
{
uint8_t data[] = {0x64, 0x03, 0x12, 0x81};
ILI9341_WriteData(data, sizeof(data));
}
ILI9341_WriteCommand(0xE8);
{
uint8_t data[] = {0x85, 0x00, 0x78};
ILI9341_WriteData(data, sizeof(data));
}
ILI9341_WriteCommand(0xCB);
{
uint8_t data[] = {0x39, 0x2C, 0x00, 0x34, 0x02};
ILI9341_WriteData(data, sizeof(data));
}
ILI9341_WriteCommand(0xF7);
ILI9341_WriteData8(0x20);
ILI9341_WriteCommand(0xEA);
{
uint8_t data[] = {0x00, 0x00};
ILI9341_WriteData(data, sizeof(data));
}
ILI9341_WriteCommand(0xC0);
ILI9341_WriteData8(0x23);
ILI9341_WriteCommand(0xC1);
ILI9341_WriteData8(0x10);
ILI9341_WriteCommand(0xC5);
{
uint8_t data[] = {0x3E, 0x28};
ILI9341_WriteData(data, sizeof(data));
}
ILI9341_WriteCommand(0xC7);
ILI9341_WriteData8(0x86);
ILI9341_WriteCommand(0x36);
ILI9341_WriteData8(0x48);
ILI9341_WriteCommand(0x3A);
ILI9341_WriteData8(0x55);
ILI9341_WriteCommand(0xB1);
{
uint8_t data[] = {0x00, 0x18};
ILI9341_WriteData(data, sizeof(data));
}
ILI9341_WriteCommand(0xB6);
{
uint8_t data[] = {0x08, 0x82, 0x27};
ILI9341_WriteData(data, sizeof(data));
}
ILI9341_WriteCommand(0xF2);
ILI9341_WriteData8(0x00);
ILI9341_WriteCommand(0x26);
ILI9341_WriteData8(0x01);
ILI9341_WriteCommand(0xE0);
{
uint8_t data[] = {
0x0F, 0x31, 0x2B, 0x0C, 0x0E,
0x08, 0x4E, 0xF1, 0x37, 0x07,
0x10, 0x03, 0x0E, 0x09, 0x00
};
ILI9341_WriteData(data, sizeof(data));
}
ILI9341_WriteCommand(0xE1);
{
uint8_t data[] = {
0x00, 0x0E, 0x14, 0x03, 0x11,
0x07, 0x31, 0xC1, 0x48, 0x08,
0x0F, 0x0C, 0x31, 0x36, 0x0F
};
ILI9341_WriteData(data, sizeof(data));
}
ILI9341_WriteCommand(0x11);
HAL_Delay(120);
ILI9341_WriteCommand(0x29);
HAL_Delay(20);
ILI9341_SetRotation(0);
}
/* ========================= 方向设置 ========================= */
void ILI9341_SetRotation(uint8_t rotation)
{
uint8_t madctl;
rotation %= 4;
switch (rotation)
{
case 0:
madctl = 0x48;
lcd_width = 240;
lcd_height = 320;
break;
case 1:
madctl = 0x28;
lcd_width = 320;
lcd_height = 240;
break;
case 2:
madctl = 0x88;
lcd_width = 240;
lcd_height = 320;
break;
default:
madctl = 0xE8;
lcd_width = 320;
lcd_height = 240;
break;
}
ILI9341_WriteCommand(0x36);
ILI9341_WriteData8(madctl);
}
/* ========================= 基础绘图(阻塞,保持不变) ========================= */
void ILI9341_DrawPixel(uint16_t x, uint16_t y, uint16_t color)
{
if (x >= lcd_width || y >= lcd_height)
{
return;
}
ILI9341_SetAddressWindow(x, y, x, y);
ILI9341_WriteData16(color);
}
void ILI9341_FillRect(uint16_t x,
uint16_t y,
uint16_t w,
uint16_t h,
uint16_t color)
{
uint32_t pixels;
uint8_t buffer[128];
uint16_t chunk_pixels;
if (x >= lcd_width || y >= lcd_height)
{
return;
}
if ((x + w) > lcd_width)
{
w = lcd_width - x;
}
if ((y + h) > lcd_height)
{
h = lcd_height - y;
}
pixels = (uint32_t)w * h;
for (uint16_t i = 0; i < sizeof(buffer); i += 2)
{
buffer[i] = (uint8_t)(color >> 8);
buffer[i + 1] = (uint8_t)(color & 0xFF);
}
ILI9341_SetAddressWindow(x, y, x + w - 1, y + h - 1);
LCD_CS_LOW();
LCD_DC_DATA();
while (pixels > 0)
{
chunk_pixels = pixels > 64U ? 64U : (uint16_t)pixels;
HAL_SPI_Transmit(&hspi1,
buffer,
chunk_pixels * 2U,
HAL_MAX_DELAY);
pixels -= chunk_pixels;
}
LCD_CS_HIGH();
}
void ILI9341_FillScreen(uint16_t color)
{
ILI9341_FillRect(0, 0, lcd_width, lcd_height, color);
}
uint16_t ILI9341_Color565(uint8_t r, uint8_t g, uint8_t b)
{
return ((r & 0xF8U) << 8) |
((g & 0xFCU) << 3) |
(b >> 3);
}
/* ========================= LVGL 阻塞区域刷新(保持不变) ========================= */
void ILI9341_WriteArea(uint16_t x,
uint16_t y,
uint16_t w,
uint16_t h,
const uint16_t *pixels)
{
uint32_t total_pixels;
uint32_t sent_pixels = 0;
static uint8_t tx_buf[512];
if (w == 0 || h == 0 || pixels == NULL)
{
return;
}
total_pixels = (uint32_t)w * h;
ILI9341_SetAddressWindow(x, y, x + w - 1, y + h - 1);
LCD_CS_LOW();
LCD_DC_DATA();
while (sent_pixels < total_pixels)
{
uint32_t remain = total_pixels - sent_pixels;
uint32_t chunk_pixels = remain > 256U ? 256U : remain;
for (uint32_t i = 0; i < chunk_pixels; i++)
{
uint16_t color = pixels[sent_pixels + i];
tx_buf[i * 2U] = (uint8_t)(color >> 8);
tx_buf[i * 2U + 1] = (uint8_t)(color & 0xFF);
}
HAL_SPI_Transmit(&hspi1,
tx_buf,
chunk_pixels * 2U,
HAL_MAX_DELAY);
sent_pixels += chunk_pixels;
}
LCD_CS_HIGH();
}
/* ========================= LVGL DMA 区域刷新:重写版 ========================= */
static void LCD_CleanDCache(void *addr, uint32_t size)
{
#if (__DCACHE_PRESENT == 1U)
if ((SCB->CCR & SCB_CCR_DC_Msk) != 0U)
{
uint32_t start_addr = (uint32_t)addr & ~31U;
uint32_t end_addr = ((uint32_t)addr + size + 31U) & ~31U;
SCB_CleanDCache_by_Addr((uint32_t *)start_addr, end_addr - start_addr);
}
#else
(void)addr;
(void)size;
#endif
}
static void ILI9341_SPI_WaitIdle(void)
{
uint32_t tick_start;
#if defined(SPI_FLAG_TXC)
tick_start = HAL_GetTick();
while (__HAL_SPI_GET_FLAG(&hspi1, SPI_FLAG_TXC) == RESET)
{
if ((HAL_GetTick() - tick_start) > 20U)
{
break;
}
}
#elif defined(SPI_FLAG_EOT)
tick_start = HAL_GetTick();
while (__HAL_SPI_GET_FLAG(&hspi1, SPI_FLAG_EOT) == RESET)
{
if ((HAL_GetTick() - tick_start) > 20U)
{
break;
}
}
#else
for (volatile uint32_t i = 0; i < 1000U; i++)
{
__NOP();
}
#endif
__DSB();
}
void ILI9341_WriteArea_DMA(uint16_t x,
uint16_t y,
uint16_t w,
uint16_t h,
const uint16_t *pixels,
ILI9341_DmaDoneCallback done_cb,
void *user_data)
{
uint32_t total_pixels;
uint32_t byte_len;
if (w == 0 || h == 0 || pixels == NULL)
{
if (done_cb != NULL)
{
done_cb(user_data);
}
return;
}
total_pixels = (uint32_t)w * h;
if (total_pixels > LCD_DMA_MAX_PIXELS)
{
/* 超出单次 DMA buffer 容量,退化为阻塞传输 */
ILI9341_WriteArea(x, y, w, h, pixels);
if (done_cb != NULL)
{
done_cb(user_data);
}
return;
}
LCD_DMA_EnsureSemaphore();
if (lcd_dma_busy)
{
/*
* 正常情况下,LVGL 在上一次 flush 没有调用 flush_ready
* 之前不会发起新的 flush,所以走到这里说明上一次传输
* 大概率已经出了问题(信号量没有被正确释放)。
* 这里做一次强制恢复,而不是继续等待,避免连锁卡死。
*/
LCD_DebugLogf("[LCD] WARN busy-reenter, force recover\r\n");
HAL_SPI_Abort(&hspi1);
LCD_CS_HIGH();
lcd_dma_busy = 0;
}
lcd_dma_busy = 1;
lcd_spi_last_error = 0;
/* 组包到 DMA buffer */
for (uint32_t i = 0; i < total_pixels; i++)
{
uint16_t color = pixels[i];
lcd_dma_buf[i * 2U] = (uint8_t)(color >> 8);
lcd_dma_buf[i * 2U + 1] = (uint8_t)(color & 0xFF);
}
byte_len = total_pixels * 2U;
LCD_CleanDCache(lcd_dma_buf, byte_len);
ILI9341_SetAddressWindow(x, y, x + w - 1U, y + h - 1U);
lcd_dma_start_tick = HAL_GetTick();
LCD_CS_LOW();
LCD_DC_DATA();
if (HAL_SPI_Transmit_DMA(&hspi1, lcd_dma_buf, (uint16_t)byte_len) != HAL_OK)
{
LCD_CS_HIGH();
lcd_dma_busy = 0;
LCD_DebugLogf("[LCD] ERR DMA start failed\r\n");
if (done_cb != NULL)
{
done_cb(user_data);
}
return;
}
/*
* 真正的关键改动:这里不再用 HAL_GetTick() 忙等,而是让任务
* 通过信号量挂起,把 CPU 让给调度器;DMA 完成中断
* HAL_SPI_TxCpltCallback)或错误中断(HAL_SPI_ErrorCallback
* 会释放这个信号量把任务唤醒。100ms 超时依旧作为兜底。
*/
if (osSemaphoreAcquire(lcd_dma_sem, 100) != osOK)
{
LCD_DebugLogf("[LCD] ERR DMA timeout, aborting\r\n");
HAL_SPI_Abort(&hspi1);
}
else if (lcd_spi_last_error != 0U)
{
LCD_DebugLogf("[LCD] ERR SPI err=0x%08lX\r\n",
(unsigned long)lcd_spi_last_error);
lcd_spi_last_error = 0U;
}
ILI9341_SPI_WaitIdle();
LCD_CS_HIGH();
lcd_dma_busy = 0;
if (done_cb != NULL)
{
done_cb(user_data);
}
}
void ILI9341_DMA_Watchdog(uint32_t timeout_ms)
{
if (!lcd_dma_busy)
{
return;
}
uint32_t elapsed = HAL_GetTick() - lcd_dma_start_tick;
if (elapsed > timeout_ms)
{
LCD_DebugLogf("[LCD] WDG force recover after %lums\r\n",
(unsigned long)elapsed);
HAL_SPI_Abort(&hspi1);
LCD_CS_HIGH();
lcd_dma_busy = 0;
/* 如果此时正好有任务卡在 WriteArea_DMA 里等信号量,唤醒它 */
if (lcd_dma_sem != NULL)
{
osSemaphoreRelease(lcd_dma_sem);
}
}
}
/*
* SPI1 TX DMA 完成回调(ISR 上下文)。
* 只做最必要的事:释放信号量。不打印日志、不调用 LVGL。
*/
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
if (hspi->Instance == SPI1)
{
if (lcd_dma_sem != NULL)
{
osSemaphoreRelease(lcd_dma_sem);
}
}
}
/*
* SPI 错误回调(ISR 上下文)。
* 之前的版本没有实现这个回调,一旦发生总线错误(Overrun/
* ModeFault/CRC 等),只能傻等 100ms 超时才能恢复。
* 现在记录错误码并立即释放信号量唤醒等待的任务,
* 真正的日志打印和处理放到任务上下文(WriteArea_DMA 里)去做。
*/
void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi)
{
if (hspi->Instance == SPI1)
{
lcd_spi_last_error = hspi->ErrorCode;
if (lcd_dma_sem != NULL)
{
osSemaphoreRelease(lcd_dma_sem);
}
}
}
+99
View File
@@ -0,0 +1,99 @@
#ifndef __ILI9341_H__
#define __ILI9341_H__
#ifdef __cplusplus
extern "C" {
#endif
#include "main.h"
#include "spi.h"
/* 默认竖屏分辨率 */
#define ILI9341_WIDTH 240
#define ILI9341_HEIGHT 320
/* ============================================================
* LCD 控制引脚
*
* !!! 重要 !!!
* 下面的 LCD_CS_PIN 目前是 GPIO_PIN_12,但 gpio.c 的
* MX_GPIO_Init() 里只把 GPIO_PIN_6 配置成了输出(Output_PP)。
* GPIO_PIN_12 复位后是 Analog 模式,不是输出,直接
* HAL_GPIO_WritePin() 不会对物理引脚产生任何效果。
*
* 请确认实际硬件接线:
* - 如果 CS 确实接在 PB12,必须在 MX_GPIO_Init() 里把
* GPIO_PIN_12 加入 output push-pull 初始化列表;
* - 如果实际接线仍是 PB6,请把下面的宏改回 GPIO_PIN_6。
* 在确认并修复之前,CS 时序不可信,任何"偶发花屏/白屏"的
* 排查都应该先排除这个问题。
* ============================================================ */
#define LCD_CS_PORT GPIOB
#define LCD_CS_PIN GPIO_PIN_12
#define LCD_DC_PORT GPIOB
#define LCD_DC_PIN GPIO_PIN_7
#define LCD_RST_PORT GPIOB
#define LCD_RST_PIN GPIO_PIN_8
#define LCD_BL_PORT GPIOB
#define LCD_BL_PIN GPIO_PIN_9
/* RGB565 常用颜色 */
#define ILI9341_BLACK 0x0000
#define ILI9341_WHITE 0xFFFF
#define ILI9341_RED 0xF800
#define ILI9341_GREEN 0x07E0
#define ILI9341_BLUE 0x001F
#define ILI9341_YELLOW 0xFFE0
#define ILI9341_CYAN 0x07FF
#define ILI9341_MAGENTA 0xF81F
#define ILI9341_GRAY 0x8410
typedef void (*ILI9341_DmaDoneCallback)(void *user_data);
void ILI9341_Init(void);
void ILI9341_SetRotation(uint8_t rotation);
void ILI9341_FillScreen(uint16_t color);
void ILI9341_DrawPixel(uint16_t x, uint16_t y, uint16_t color);
void ILI9341_FillRect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color);
void ILI9341_WriteArea(uint16_t x,
uint16_t y,
uint16_t w,
uint16_t h,
const uint16_t *pixels);
void ILI9341_WriteArea_DMA(uint16_t x,
uint16_t y,
uint16_t w,
uint16_t h,
const uint16_t *pixels,
ILI9341_DmaDoneCallback done_cb,
void *user_data);
/*
* DMA 看门狗:建议从任务(线程)上下文周期性调用,例如放在
* StartDefaultTask 的 for(;;) 循环里、紧跟在 lv_timer_handler()
* 之后。绝不能从任何中断服务程序里调用。
*
* 新实现里 ILI9341_WriteArea_DMA 本身已经通过信号量+超时
* 做了同步等待和自恢复,正常情况下不需要这个看门狗介入;
* 它只是兜底:万一某次调用路径绕过了 WriteArea_DMA 内部的
* 超时保护(例如未来新增的调用方式),仍然能强制恢复 SPI
* 状态机,避免 lcd_dma_busy 卡死导致后续所有刷新被永久跳过。
*
* timeout_ms 建议 150~200ms(要大于 WriteArea_DMA 内部
* 100ms 的超时阈值,避免互相打架)。
*/
void ILI9341_DMA_Watchdog(uint32_t timeout_ms);
uint16_t ILI9341_Color565(uint8_t r, uint8_t g, uint8_t b);
#ifdef __cplusplus
}
#endif
#endif
+89
View File
@@ -0,0 +1,89 @@
#include "esc.h"
/*
* 当前 CubeMX TIM1 配置:
* Prescaler = 239
* Period = 19999
*
* 如果 TIM1 时钟是 240MHz
* 240MHz / (239 + 1) = 1MHz
* 所以 1 个计数 = 1us
*
* 因此 CCR = 1000 就是 1000us
*/
void ESC_Init(void)
{
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
/* 上电默认最低油门,防止电机突然启动 */
ESC_SetThrottleUs(ESC_MIN_US);
}
void ESC_SetThrottleUs(uint16_t us)
{
if (us < ESC_MIN_US)
{
us = ESC_MIN_US;
}
if (us > ESC_MAX_US)
{
us = ESC_MAX_US;
}
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, us);
}
void ESC_Stop(void)
{
ESC_SetThrottleUs(ESC_MIN_US);
}
/*
* 电调解锁:
* 一般 ESC 上电后需要先收到一段时间最低油门信号
*/
void ESC_Arm(void)
{
ESC_SetThrottleUs(ESC_MIN_US);
HAL_Delay(3000);
}
/*
* 低速测试:
* 第一次不要直接上 1500 或 2000,先用 1100~1200
*/
void ESC_TestLowSpeed(void)
{
ESC_SetThrottleUs(ESC_MIN_US);
HAL_Delay(2000);
ESC_SetThrottleUs(1100);
HAL_Delay(3000);
ESC_SetThrottleUs(1150);
HAL_Delay(3000);
ESC_SetThrottleUs(1200);
HAL_Delay(3000);
ESC_Stop();
HAL_Delay(2000);
}
/*
* 电调油门校准:
* 不确定电调是否需要校准时,不要随便调用。
* 常见流程:
* 1. 先输出最大油门
* 2. 给 ESC 上电
* 3. 听到提示音后切到最低油门
*/
void ESC_Calibrate(void)
{
ESC_SetThrottleUs(ESC_MAX_US);
HAL_Delay(5000);
ESC_SetThrottleUs(ESC_MIN_US);
HAL_Delay(5000);
}
+31
View File
@@ -0,0 +1,31 @@
#ifndef __ESC_H
#define __ESC_H
#ifdef __cplusplus
extern "C" {
#endif
#include "main.h"
#include "tim.h"
#include <stdint.h>
/* 常见 ESC PWM 范围,单位:us */
#define ESC_MIN_US 1100
#define ESC_MID_US 1500
#define ESC_MAX_US 3000
/* 建议初次测试不要超过这个值 */
#define ESC_TEST_LOW_US 1100
void ESC_Init(void);
void ESC_SetThrottleUs(uint16_t us);
void ESC_Stop(void);
void ESC_Arm(void);
void ESC_TestLowSpeed(void);
void ESC_Calibrate(void);
#ifdef __cplusplus
}
#endif
#endif
+119
View File
@@ -0,0 +1,119 @@
#include "uart4.h"
#include "stdio.h"
#include "string.h"
#include "stdarg.h"
/*
* 调试串口相关定义与缓冲区
*
* DEBUG_UART_HANDLE: 外部提供的 UART 句柄(在 CubeMX 产生的代码中为 huart4
* DEBUG_TX_BUF_SIZE: 发送缓冲区大小(单位:字节),用于格式化输出
*/
#define DEBUG_UART_HANDLE huart4
#define DEBUG_TX_BUF_SIZE 256
/* 发送缓冲区:用于存放格式化后的字符串 */
static char debug_tx_buf[DEBUG_TX_BUF_SIZE];
/* DMA 发送忙标志(若使用 DMA 发送,可用来避免并发访问) */
static volatile uint8_t dma_busy = 0;
/*
* 阻塞字符串打印(Debug_Print
* 说明:
* - 以阻塞方式调用 HAL_UART_Transmit 发送字符串数据,函数在发送完成或超时前会阻塞。
* - 适用场景:启动日志或对可靠性要求高、且发送频率低的调试信息。
* - 注意:不自动添加换行,需要用户在字符串末尾自己加上 "\r\n"(若需要)。
* 参数:
* - str:以 '\0' 结尾的字符串指针
*/
void Debug_Print(const char *str)
{
HAL_UART_Transmit(&DEBUG_UART_HANDLE,
(uint8_t *)str,
strlen(str),
100);
}
/*
* printf 风格的阻塞打印(Debug_Printf
*
* 说明:
* - 使用 vsnprintf 将可变参数格式化到内部缓冲区 debug_tx_buf,再以阻塞方式发送。
* - vsnprintf 的返回值 len 表示所需的缓冲区长度(不含终止符)。当 len >= DEBUG_TX_BUF_SIZE
* 时表示输出被截断。此处将发送的数据长度限定为 DEBUG_TX_BUF_SIZE(不发送终止符)。
* - 若格式化失败(len <= 0)则直接返回,不进行发送。
* - 发送超时时间为 100 ms,可根据实际需要调整。
*
* 注意事项:
* - debug_tx_buf 为静态缓冲区,若在中断或多任务环境中使用,请确保同步(或改用 DMA/队列)。
* - 如果输出非常频繁或较大,建议改为异步(DMA)发送或使用环形缓冲队列以避免阻塞。
*
* 参数:
* - format: printf 风格的格式字符串
*/
void Debug_Printf(const char *format, ...)
{
va_list args;
int len;
va_start(args, format);
/* 将格式化内容写入缓冲区,最多写入 DEBUG_TX_BUF_SIZE 字节(包含终止符) */
len = vsnprintf(debug_tx_buf, DEBUG_TX_BUF_SIZE, format, args);
va_end(args);
/* 格式化失败或没有输出内容,直接返回 */
if (len <= 0)
{
return;
}
/* 如果输出长度超过缓冲区容量,截断到缓冲区最大容量(不发送末尾的 '\0' */
if (len > DEBUG_TX_BUF_SIZE)
{
len = DEBUG_TX_BUF_SIZE;
}
/* 阻塞发送格式化后的数据 */
HAL_UART_Transmit(&DEBUG_UART_HANDLE,
(uint8_t *)debug_tx_buf,
len,
100);
}
/*
* DMA 打印:适合后面大量日志输出。
* 注意:TX DMA 建议 CubeMX 配 Normal,不要配 Circular。
*/
void Print_DMA(const char *str)
{
if (dma_busy)
{
return;
}
dma_busy = 1;
HAL_UART_Transmit_DMA(&DEBUG_UART_HANDLE,
(uint8_t *)str,
strlen(str));
}
uint8_t UART_IsBusy(void)
{
return dma_busy;
}
/*
* DMA 发送完成回调
* 注意:整个工程里 HAL_UART_TxCpltCallback 只能实现一次。
* 如果你其他地方也需要这个回调,要统一写在这里分发。
*/
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == UART4)
{
dma_busy = 0;
}
}
+22
View File
@@ -0,0 +1,22 @@
#ifndef __UART_DEBUG_H
#define __UART_DEBUG_H
#ifdef __cplusplus
extern "C" {
#endif
#include "main.h"
#include "usart.h"
#include <stdint.h>
void Debug_Print(const char *str);
void Debug_Printf(const char *format, ...);
void Print_DMA(const char *str);
uint8_t UART_IsBusy(void);
#ifdef __cplusplus
}
#endif
#endif