# bsp_qspi_flash: basic test
This commit is contained in:
parent
e4571c2d3b
commit
ab8bdeeb2f
3 changed files with 145 additions and 57 deletions
26
bsp/qspi_flash/REFACTORING.md
Normal file
26
bsp/qspi_flash/REFACTORING.md
Normal 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 и сразу проверю сборку, чтобы не оставить таких разрывов.
|
||||
|
|
@ -103,12 +103,12 @@
|
|||
|
||||
/* ── Параметры 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. */
|
||||
#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-чтения ────────────────────────────────────────────────── */
|
||||
|
||||
|
|
@ -287,12 +287,13 @@ AT_QUICKACCESS_SECTION_CODE(static status_t qspi_check_error(uint32_t intr))
|
|||
/* ── 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) */
|
||||
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;
|
||||
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);
|
||||
|
||||
/* 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) */
|
||||
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) */
|
||||
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) */
|
||||
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;
|
||||
|
||||
while ((len - offset) >= FIFO_WM_BYTES)
|
||||
while ((len - offset) >= wm_bytes)
|
||||
{
|
||||
/* 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)
|
||||
{
|
||||
return ERR;
|
||||
}
|
||||
offset += FIFO_WM_BYTES;
|
||||
offset += wm_bytes;
|
||||
}
|
||||
|
||||
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;
|
||||
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)
|
||||
{
|
||||
|
|
@ -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);
|
||||
|
||||
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;
|
||||
if (WR_WORDS > wm_words)
|
||||
{
|
||||
return kStatus_FLEXSPI_IpCommandSequenceError;
|
||||
}
|
||||
|
||||
for (uint32_t w = 0U; w < WR_WORDS; w++)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,72 +3,120 @@
|
|||
* @file main.c
|
||||
* @brief firmware_test — точка входа.
|
||||
*
|
||||
* Bare-metal входной контроль платы.
|
||||
* Единственный канал хост↔таргет: USB CDC ACM (J2).
|
||||
* Протокол: JSON-lines через cli.c.
|
||||
*
|
||||
* Последовательность старта:
|
||||
* 1. board_hw_init() — тактирование, MPU, кэш, пины
|
||||
* 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
|
||||
* Минимальный smoke-тест QSPI в XIP-конфигурации:
|
||||
* 1) Инициализация QSPI.
|
||||
* 2) Чтение JEDEC ID.
|
||||
* 3) Erase одного сектора в конце Flash.
|
||||
* 4) Запись одной страницы и чтение назад.
|
||||
* 5) Сравнение буферов и LED-индикация PASS/FAIL.
|
||||
*/
|
||||
#include "board.h"
|
||||
#include "bsp/led.h"
|
||||
#include "bsp/qspi_flash.h"
|
||||
#include "bsp/tick.h"
|
||||
#include "bsp/usb_cdc.h"
|
||||
#include "cli.h"
|
||||
#include "protocol.h"
|
||||
#include "test_runner.h"
|
||||
|
||||
#include <stdbool.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)
|
||||
{
|
||||
const uint32_t CONNECT_BLINK_MS = 200U;
|
||||
const uint32_t ERROR_BLINK_MS = 250;
|
||||
board_hw_init();
|
||||
const uint32_t ERROR_BLINK_MS = 80U;
|
||||
const uint32_t PASS_BLINK_MS = 350U;
|
||||
|
||||
board_hw_init();
|
||||
bsp_led_init();
|
||||
bsp_status_t status = bsp_qspi_init();
|
||||
bsp_tick_init();
|
||||
|
||||
while (1)
|
||||
g_qspi_test_step = 1U;
|
||||
if (bsp_qspi_init() != BSP_OK)
|
||||
{
|
||||
bsp_led_toggle(LED_HEARTBEAT);
|
||||
bsp_delay(ERROR_BLINK_MS);
|
||||
}
|
||||
#if 0
|
||||
if (bsp_usb_cdc_init() != BSP_OK)
|
||||
{
|
||||
bsp_led_toggle(LED_HEARTBEAT);
|
||||
bsp_delay(ERROR_BLINK_MS);
|
||||
goto FAIL;
|
||||
}
|
||||
|
||||
/* Ожидать подключения хоста. LED_HEARTBEAT мигает — прошивка жива. */
|
||||
while (!bsp_usb_cdc_is_ready())
|
||||
g_qspi_test_step = 2U;
|
||||
if (bsp_qspi_read_jedec_id((bsp_qspi_jedec_t *) &g_qspi_test_jedec) != BSP_OK)
|
||||
{
|
||||
bsp_usb_cdc_poll();
|
||||
bsp_led_toggle(LED_HEARTBEAT);
|
||||
bsp_delay(CONNECT_BLINK_MS);
|
||||
goto FAIL;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
cli_init();
|
||||
test_runner_init();
|
||||
protocol_send_session_start();
|
||||
while (1)
|
||||
{
|
||||
bsp_usb_cdc_poll();
|
||||
cli_process();
|
||||
test_runner_process();
|
||||
bsp_led_toggle(LED_HEARTBEAT);
|
||||
bsp_delay(PASS_BLINK_MS);
|
||||
}
|
||||
|
||||
FAIL:
|
||||
g_qspi_test_step |= 0x80000000UL;
|
||||
while (1)
|
||||
{
|
||||
bsp_led_toggle(LED_HEARTBEAT);
|
||||
bsp_delay(ERROR_BLINK_MS);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue