# bsp_qspi_flash: basic test

This commit is contained in:
Dmitry Akimov 2026-04-22 11:53:51 +03:00
parent e4571c2d3b
commit ab8bdeeb2f
3 changed files with 145 additions and 57 deletions

View file

@ -0,0 +1,26 @@
Нашёл и закрыл основную причину HardFault.
Что было не так
Ваша CodeQuickAccess/.ram_function секция не копировалась в ITCM при старте, потому что для startup_MIMXRT1052.S не были заданы макросы __STARTUP_INITIALIZE_RAMFUNCTION (и __STARTUP_CLEAR_BSS).
SysTick включался до bsp_qspi_init(), а в драйвере не было маскирования IRQ на время FlexSPI IP-операций, что опасно при XIP с той же флешки.
Что я изменил
Добавил startup-дефайны в firmware/test/CMakeLists.txt (line 20):
- `__STARTUP_INITIALIZE_RAMFUNCTION`
- `__STARTUP_CLEAR_BSS`
-
Переставил порядок инициализации в firmware/test/src/main.c (line 38):
bsp_qspi_init() теперь до bsp_tick_init().
Усилил XIP-safe поведение в bsp/qspi_flash/src/qspi_flash.c (line 205):
добавил qspi_irq_lock()/qspi_irq_unlock();
обернул критичные операции (init/read/write/erase) под IRQ lock;
публичные QSPI API размещены в CodeQuickAccess (ITCM).
Нашёл вероятный корень: в драйвере жёстко зашит watermark = 8 bytes, а в железе он может быть другим, из-за чего TX/RX FIFO-петли начинают висеть. Сейчас внесу фикс: динамический watermark из регистров + ожидание IPCMDDONE как в fsl_flexspi.
Да, это мой хвост после рефактора watermark-констант. Сейчас поправлю qspi_write_fifo на динамический watermark и сразу проверю сборку, чтобы не оставить таких разрывов.

View file

@ -103,12 +103,12 @@
/* ── Параметры FIFO ─────────────────────────────────────────────────────── */ /* ── Параметры FIFO ─────────────────────────────────────────────────────── */
/** @brief Байт на watermark-unit (TXWMRK=0 / RXWMRK=0 → 1 unit = 8 bytes). */
#define FIFO_WM_BYTES 8U
/** @brief 32-bit слов в одном watermark-unit. */
#define FIFO_WM_WORDS (FIFO_WM_BYTES / QSPI_RFDR_WORD_BYTES)
/** @brief Байт в одном слове RFDR/TFDR. */ /** @brief Байт в одном слове RFDR/TFDR. */
#define QSPI_RFDR_WORD_BYTES 4U #define QSPI_RFDR_WORD_BYTES 4U
/** @brief Байт в одном watermark-unit (по RM: 1 unit = 8 bytes). */
#define QSPI_WM_UNIT_BYTES 8U
/** @brief 32-bit слов в одном watermark-unit. */
#define QSPI_WM_UNIT_WORDS (QSPI_WM_UNIT_BYTES / QSPI_RFDR_WORD_BYTES)
/* ── Параметры SR-чтения ────────────────────────────────────────────────── */ /* ── Параметры SR-чтения ────────────────────────────────────────────────── */
@ -287,12 +287,13 @@ AT_QUICKACCESS_SECTION_CODE(static status_t qspi_check_error(uint32_t intr))
/* ── ITCM: RX FIFO ──────────────────────────────────────────────────────── */ /* ── ITCM: RX FIFO ──────────────────────────────────────────────────────── */
/** /**
* @brief Читает FIFO_WM_BYTES (8 байт) из RX FIFO в p_dst. * @brief Читает watermark-чанк из RX FIFO в p_dst.
* *
* Ожидает флага watermark-available, читает 2 слова, делает pop. * @param[in] p_dst Буфер назначения.
* @param[in] wm_words Количество 32-bit слов в текущем watermark.
*/ */
/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) */ /* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) */
AT_QUICKACCESS_SECTION_CODE(static status_t qspi_read_chunk(uint8_t *p_dst)) AT_QUICKACCESS_SECTION_CODE(static status_t qspi_read_chunk(uint8_t *p_dst, uint32_t wm_words))
{ {
uint32_t intr; uint32_t intr;
status_t err; status_t err;
@ -308,7 +309,7 @@ AT_QUICKACCESS_SECTION_CODE(static status_t qspi_read_chunk(uint8_t *p_dst))
} while ((intr & (uint32_t) kFLEXSPI_IpRxFifoWatermarkAvailableFlag) == 0U); } while ((intr & (uint32_t) kFLEXSPI_IpRxFifoWatermarkAvailableFlag) == 0U);
/* NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index) */ /* NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index) */
for (uint32_t w = 0U; w < FIFO_WM_WORDS; w++) for (uint32_t w = 0U; w < wm_words; w++)
{ {
/* NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index) */ /* NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index) */
const uint32_t WORD = QSPI_BASE->RFDR[w]; const uint32_t WORD = QSPI_BASE->RFDR[w];
@ -323,9 +324,9 @@ AT_QUICKACCESS_SECTION_CODE(static status_t qspi_read_chunk(uint8_t *p_dst))
} }
/** /**
* @brief Читает tail-байты (< FIFO_WM_BYTES) из RX FIFO. * @brief Читает tail-байты (< текущего watermark) из RX FIFO.
* *
* Ожидает через IPRXFSTS.FILL. Вызывать когда remain < FIFO_WM_BYTES. * Ожидает через IPRXFSTS.FILL.
*/ */
/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) */ /* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) */
AT_QUICKACCESS_SECTION_CODE(static status_t qspi_read_tail(uint8_t *p_dst, uint32_t remain)) AT_QUICKACCESS_SECTION_CODE(static status_t qspi_read_tail(uint8_t *p_dst, uint32_t remain))
@ -372,17 +373,22 @@ AT_QUICKACCESS_SECTION_CODE(static status_t qspi_read_tail(uint8_t *p_dst, uint3
/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) */ /* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) */
AT_QUICKACCESS_SECTION_CODE(static status_t qspi_read_fifo(uint8_t *p_buf, uint32_t len)) AT_QUICKACCESS_SECTION_CODE(static status_t qspi_read_fifo(uint8_t *p_buf, uint32_t len))
{ {
const uint32_t wm_units =
((QSPI_BASE->IPRXFCR & FLEXSPI_IPRXFCR_RXWMRK_MASK) >> FLEXSPI_IPRXFCR_RXWMRK_SHIFT) + 1U;
const uint32_t wm_words = wm_units * QSPI_WM_UNIT_WORDS;
const uint32_t wm_bytes = wm_words * QSPI_RFDR_WORD_BYTES;
uint32_t offset = 0U; uint32_t offset = 0U;
while ((len - offset) >= FIFO_WM_BYTES) while ((len - offset) >= wm_bytes)
{ {
/* NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ /* NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) */
const status_t ERR = qspi_read_chunk(p_buf + offset); const status_t ERR = qspi_read_chunk(p_buf + offset, wm_words);
if (ERR != kStatus_Success) if (ERR != kStatus_Success)
{ {
return ERR; return ERR;
} }
offset += FIFO_WM_BYTES; offset += wm_bytes;
} }
if (offset < len) if (offset < len)
@ -401,6 +407,10 @@ AT_QUICKACCESS_SECTION_CODE(static status_t qspi_write_fifo(const uint8_t *p_buf
{ {
const uint8_t *p = p_buf; const uint8_t *p = p_buf;
uint32_t remain = len; uint32_t remain = len;
const uint32_t wm_units =
((QSPI_BASE->IPTXFCR & FLEXSPI_IPTXFCR_TXWMRK_MASK) >> FLEXSPI_IPTXFCR_TXWMRK_SHIFT) + 1U;
const uint32_t wm_words = wm_units * QSPI_WM_UNIT_WORDS;
const uint32_t wm_bytes = wm_words * QSPI_RFDR_WORD_BYTES;
while (remain > 0U) while (remain > 0U)
{ {
@ -416,8 +426,12 @@ AT_QUICKACCESS_SECTION_CODE(static status_t qspi_write_fifo(const uint8_t *p_buf
} }
} while ((intr & (uint32_t) kFLEXSPI_IpTxFifoWatermarkEmptyFlag) == 0U); } while ((intr & (uint32_t) kFLEXSPI_IpTxFifoWatermarkEmptyFlag) == 0U);
const uint32_t CHUNK = (remain > FIFO_WM_BYTES) ? FIFO_WM_BYTES : remain; const uint32_t CHUNK = (remain > wm_bytes) ? wm_bytes : remain;
const uint32_t WR_WORDS = (CHUNK + QSPI_RFDR_WORD_BYTES - 1U) / QSPI_RFDR_WORD_BYTES; const uint32_t WR_WORDS = (CHUNK + QSPI_RFDR_WORD_BYTES - 1U) / QSPI_RFDR_WORD_BYTES;
if (WR_WORDS > wm_words)
{
return kStatus_FLEXSPI_IpCommandSequenceError;
}
for (uint32_t w = 0U; w < WR_WORDS; w++) for (uint32_t w = 0U; w < WR_WORDS; w++)
{ {

View file

@ -3,72 +3,120 @@
* @file main.c * @file main.c
* @brief firmware_test точка входа. * @brief firmware_test точка входа.
* *
* Bare-metal входной контроль платы. * Минимальный smoke-тест QSPI в XIP-конфигурации:
* Единственный канал хосттаргет: USB CDC ACM (J2). * 1) Инициализация QSPI.
* Протокол: JSON-lines через cli.c. * 2) Чтение JEDEC ID.
* * 3) Erase одного сектора в конце Flash.
* Последовательность старта: * 4) Запись одной страницы и чтение назад.
* 1. board_hw_init() тактирование, MPU, кэш, пины * 5) Сравнение буферов и LED-индикация PASS/FAIL.
* 2. bsp_tick_init() SysTick 1 мс
* 3. bsp_led_init() оба LED выключены
* 4. bsp_usb_cdc_init() PHY + стек + NVIC
* 5. Ожидание CDC ready LED_HEARTBEAT мигает
* 6. cli_init() сброс буфера
* 7. READY хост JSON сигнал готовности
* 8. Главный цикл poll + cli_process
*/ */
#include "board.h" #include "board.h"
#include "bsp/led.h" #include "bsp/led.h"
#include "bsp/qspi_flash.h" #include "bsp/qspi_flash.h"
#include "bsp/tick.h" #include "bsp/tick.h"
#include "bsp/usb_cdc.h"
#include "cli.h"
#include "protocol.h"
#include "test_runner.h"
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
static volatile uint32_t g_qspi_test_step = 0U;
static volatile uint32_t g_qspi_test_addr = 0U;
static volatile uint32_t g_qspi_test_mismatch_index = 0xFFFFFFFFUL;
static volatile bsp_qspi_jedec_t g_qspi_test_jedec = { 0U, 0U };
static bool bytes_equal(const uint8_t *p_lhs, const uint8_t *p_rhs, size_t size)
{
for (size_t i = 0U; i < size; i++)
{
if (p_lhs[i] != p_rhs[i])
{
g_qspi_test_mismatch_index = (uint32_t) i;
return false;
}
}
return true;
}
int main(void) int main(void)
{ {
const uint32_t CONNECT_BLINK_MS = 200U; const uint32_t ERROR_BLINK_MS = 80U;
const uint32_t ERROR_BLINK_MS = 250; const uint32_t PASS_BLINK_MS = 350U;
board_hw_init();
board_hw_init();
bsp_led_init(); bsp_led_init();
bsp_status_t status = bsp_qspi_init();
bsp_tick_init(); bsp_tick_init();
while (1) g_qspi_test_step = 1U;
if (bsp_qspi_init() != BSP_OK)
{ {
bsp_led_toggle(LED_HEARTBEAT); goto FAIL;
bsp_delay(ERROR_BLINK_MS);
}
#if 0
if (bsp_usb_cdc_init() != BSP_OK)
{
bsp_led_toggle(LED_HEARTBEAT);
bsp_delay(ERROR_BLINK_MS);
} }
/* Ожидать подключения хоста. LED_HEARTBEAT мигает — прошивка жива. */ g_qspi_test_step = 2U;
while (!bsp_usb_cdc_is_ready()) if (bsp_qspi_read_jedec_id((bsp_qspi_jedec_t *) &g_qspi_test_jedec) != BSP_OK)
{ {
bsp_usb_cdc_poll(); goto FAIL;
bsp_led_toggle(LED_HEARTBEAT);
bsp_delay(CONNECT_BLINK_MS);
} }
if (g_qspi_test_jedec.manufacturer_id != BSP_QSPI_MFR_WINBOND)
{
goto FAIL;
}
g_qspi_test_step = 3U;
const uint32_t flash_size = bsp_qspi_flash_size();
if (flash_size < BSP_QSPI_SECTOR_SIZE)
{
goto FAIL;
}
g_qspi_test_addr = flash_size - BSP_QSPI_SECTOR_SIZE;
uint8_t tx[BSP_QSPI_PAGE_SIZE];
uint8_t rx[BSP_QSPI_PAGE_SIZE];
for (uint32_t i = 0U; i < BSP_QSPI_PAGE_SIZE; i++)
{
tx[i] = (uint8_t) (0xA5U ^ i ^ g_qspi_test_jedec.manufacturer_id ^
(uint8_t) g_qspi_test_jedec.device_id);
rx[i] = 0U;
}
g_qspi_test_step = 4U;
if (bsp_qspi_erase_sector(g_qspi_test_addr) != BSP_OK)
{
goto FAIL;
}
g_qspi_test_step = 5U;
if (bsp_qspi_write_page(g_qspi_test_addr, tx) != BSP_OK)
{
goto FAIL;
}
g_qspi_test_step = 6U;
if (bsp_qspi_read(g_qspi_test_addr, rx, sizeof(rx)) != BSP_OK)
{
goto FAIL;
}
g_qspi_test_step = 7U;
if (!bytes_equal(tx, rx, sizeof(rx)))
{
goto FAIL;
}
g_qspi_test_step = 8U;
bsp_led_on(LED_APP); bsp_led_on(LED_APP);
cli_init();
test_runner_init();
protocol_send_session_start();
while (1) while (1)
{ {
bsp_usb_cdc_poll(); bsp_led_toggle(LED_HEARTBEAT);
cli_process(); bsp_delay(PASS_BLINK_MS);
test_runner_process(); }
FAIL:
g_qspi_test_step |= 0x80000000UL;
while (1)
{
bsp_led_toggle(LED_HEARTBEAT);
bsp_delay(ERROR_BLINK_MS);
} }
#endif
} }