音频处理:MCU<-->ML28860<-->FLASH

Q:

使用ml28860音频芯片,该芯片可以通过内置命令自己访问外部flash,从flash中读取音频播放

flash需烧录特定bin文件,外部flash大小512K字节,是该音频IC规定大小

暂时无法在飞书文档外展示此内容

通过此软件生成bin文件

工程采用工厂模式的思想,提供port接口,方便其他的mcu调用

一、程序框架

暂时无法在飞书文档外展示此内容

二、程序源码

Q:程序中没有对连续播放编写,部分功能暂时没有用到,目前程序中使用的延时均为死等,如有完整的延时命令需添加delay函数

1.驱动

#ifndef __ML28860_AUDIO_H
#define __ML28860_AUDIO_H
#include "kf32a158sf_hli_dio.h"
#include "kf32a158sf_hli_spi.h"
#include <stdint.h>
#include <stdbool.h>

//==================== 配置 ====================
// ML28860
#define ML28860_PUP 0x00     // 上电
#define ML28860_AMODE 0x04   // 模拟模式设置
#define ML28860_AVOL 0x08    // 主音量
#define ML28860_FADE 0x0C    // 淡入淡出
#define ML28860_FDIRECT 0x10 // Flash直接访问
#define ML28860_WDTCL 0x14   // 看门狗清零
#define ML28860_PDWN 0x20    // 掉电
#define ML28860_FADR 0x30    // 短语设置(10bit)
#define ML28860_PLAY 0x40    // 播放(10bit)
#define ML28860_START 0x50   // 启动通道
#define ML28860_STOP 0x60    // 停止通道
#define ML28860_MUON 0x70    // 静音插入
#define ML28860_SLOOP 0x80   // 循环播放
#define ML28860_CLOOP 0x90   // 取消循环
#define ML28860_CVOL 0xA0    // 通道音量
#define ML28860_RDSTAT 0xB0  // 读状态
#define ML28860_RDVER 0xB4   // 读版本
#define ML28860_RDERR 0xB8   // 读错误
#define ML28860_OUTSTAT 0xC0 // 状态输出
#define ML28860_FADR2 0xC4   // 短语设置(12bit)
#define ML28860_PLAY2 0xC8   // 播放(12bit)
#define ML28860_SAFE 0xD0    // 安全/检测使能
#define ML28860_ERRCL 0xFF   // 错误清零
// SPI硬件配置
typedef struct
{
    HLI_Spi_ModuleType spi_handle; // SPI句柄
    uint8_t cs_pin;                // 片选引脚
    uint8_t reset_pin;             // 复位引脚
    uint8_t cbusy_pin;             // CBUSYB引脚(命令忙)
} ML28860_HWConfig;

// 通道定义
typedef enum
{
    CH0 = 0x01,
    CH1 = 0x02,
    CH2 = 0x04,
    CH3 = 0x08,
    CH_ALL = 0x0F
} ML28860_Channel;

//==================== API ====================
typedef struct
{
    /**
     * @brief PWRUp,SAFE,AMODE,mode configuration
     *
     * @param dev Point to this structure ML28860_Driver
     * @param cfg Point to this structure ML28860_HWConfig
     * @return true
     * @return false
     */
    bool (*init)(void *dev, ML28860_HWConfig *cfg);
    /**
     * @brief Stop the audio playback
     *
     * @param dev Point to this structure ML28860_Driver
     */
    void (*deinit)(void *dev);
    /**
     * @brief Specify and play channels and phrases
     *
     * @param dev Point to this structure ML28860_Driver
     * @param ch Specified playback channel
     * @param phrase se Select play phrase
     * @param repeat_on SLOOP
     * @return true
     * @return false
     */
    bool (*play)(void *dev, uint8_t ch, uint16_t phrase, uint8_t repeat_on);
    bool (*nextplay)(void *dev, uint8_t ch, uint16_t phrase);
    /**
     * @brief Stop looping playback
     *
     * @param dev Point to this structure ML28860_Driver
     * @param ch_no_en stop channel
     * @return true
     * @return false
     */
    bool (*cloop)(void *dev, uint8_t ch);
    /**
     * @brief  Stop playing command
     *
     * @param dev  Point to this structure ML28860_Driver
     * @param ch Specified playback channel
     * @return true
     * @return false
     */
    bool (*stop)(void *dev, uint8_t ch);
    /**
     * @brief Set the playback volume for the specified channel
     *
     * @param dev Point to this structure ML28860_Driver
     * @param ch channel
     * @param vol There are a total of 128 levels of adjustable volume.
     *            When using reset (reset b = "L") and entering the PDWN command, the set value will be initialized.
     *            The second byte of the CVOL command, CV1 and CV0, are configured on the high side of CV6 to CV2.
     *            0 CV1 CV0 CV6 CV5 CV4 CV3 CV2
     * @return true
     * @return false
     */
    bool (*set_ch_volume)(void *dev, uint8_t ch, uint8_t vol);
    /**
     * @brief The AVOL command is used to set the volume of the speaker amplifier.
     *        This command can be entered regardless of the status of the NCR signal.
     *        The initial value after the reset is lifted is -4.0dB.
     *        In addition, when the STOP command is entered, the setting value of the AVOL command will be retained,
     *        but it will be initialized in case of power failure.
     * @param dev Point to this structure ML28860_Driver
     * @param vol 0 0 AV5 AV4 AV3 AV2 0 0
     *       AV5-AV2 说明            AV5-AV2 说明
     *          F   +12.0dB          7       -8.0dB
                E   +10.0dB          6       -12.0dB
                D   +8.0dB           5       -18.0dB
                C   +6.0dB           4       -26.0dB
                B   +4.0dB           3       -34.0dB
                A   +2.0dB           2       禁止设置
                9   +0.0dB           1       禁止设置
                8   -4.0dB(初始值)  0       OFF
    * @return true
    * @return false
    */
    bool (*set_main_volume)(void *dev, uint8_t vol);
    /**
     * @brief Read the internal working status.
     *
     * @param dev Point to this structure ML28860_Driver
     * @return uint8_t status
     */
    uint8_t (*read_status)(void *dev);
    /**
     * @brief Read the status of misoperation detection and fault detection.
     *
     * @param dev
     * @return uint8_t
     */
    uint8_t (*read_error)(void *dev);
    /**
     * @brief Set the fade-in and fade-out function
     *
     * @param dev Point to this structure ML28860_Driver
     * @param en Whether to enable the fade-in and Fade-out function
     * @param fcon FCON2 FCON1 FCON0 说明
                    0 0 0 音量以 0dB×128/32768 步为单位变化
                    0 0 1 音量以 0dB×64/32768 步为单位变化
                    0 1 0 音量以 0dB×32/32768 步为单位变化
                    0 1 1 音量以 0dB×16/32768 步为单位变化
                    1 0 0 音量以 0dB×8/32768 步为单位变化
                    1 0 1 音量以 0dB×4/32768 步为单位变化
                    1 1 0 音量以 0dB×2/32768 步为单位变化
                    1 1 1 音量以 0dB×1/32768 步为单位变化
     * @return true
     * @return false
     */
    bool (*set_fade)(void *dev, uint8_t en, uint8_t fcon);
    /**
     * @brief Check whether the detection function is working
     *
     * @param dev Point to this structure ML28860_Driver
     * @param param OSCEN RSTEN WDTEN ROMEN SPDEN TSDEN DCDEN WCMEN
     * @return true
     * @return false
     */
    bool (*set_safe)(void *dev, uint8_t param);
    /**
     * @brief Select the internal working status output to the STATUS1 pin and STATUS2 pin.
     *
     * @param dev Point to this structure ML28860_Driver
     * @param param 0 PORT STA1 STA0 CH3 CH2 CH1 CH0
     * @return true
     * @return false
     */
    bool (*set_outstat)(void *dev, uint8_t param);
    /**
     * @brief Used to clear the error bits that the RDERR command can read
     *
     * @param dev Point to this structure ML28860_Driver
     * @return true
     * @return false
     */
    bool (*clean_err)(void *dev);
    /**
     * @brief Clear the watchdog timer counter
     *
     * @param dev Point to this structure ML28860_Driver
     * @return true
     * @return false
     */
    bool (*clean_wdt)(void *dev);
    /**
     * @brief Insert a mute between two phrases
     *
     * @param dev Point to this structure ML28860_Driver
     * @param ch channel
     * @param time Silent time
     * @return true
     * @return false
     */
    bool (*set_muon)(void *dev, uint8_t ch, uint8_t time);
    /**
     * @brief Control serial flash
     *
     * @param dev
     * @param mima  "0xFF" is not applicable
     * @return true
     * @return false
     */
    bool (*start_flash)(void *dev, uint8_t mima);
    bool (*out_stat)(void *dev, uint8_t parpm);
    uint8_t (*read_rom)(void *dev);

} ML28860_Ops;

//==================== 驱动实例 ====================
typedef struct
{
    ML28860_HWConfig hw;
    ML28860_Ops *ops;
    bool is_ready;
} ML28860_Driver;

//==================== 接口 ====================
extern ML28860_Driver ML28860_Control;
void ml28860_driver_destroy(ML28860_Driver *dev);

#endif
#include "Cdd_ML28860.h"
#include "Rte.h"
#include <string.h>
#include <stdlib.h>

//=================== port =====================
/**
 * @brief spi片选,与音频flash共享
 *
 * @param pin
 * @param low
 */
static void ml28860_cs(uint8_t pin, bool low)
{
    HLI_Dio_WritePin(pin, low);
}

/**
 * @brief 拉低驱动flash
 *
 * @param pin
 * @param low
 */
static void ml28860_eroff(uint8_t pin, bool low)
{
    (void)pin;
    if (low > 0)
        ML28860_EROFF_ENABLE;
    else
        ML28860_EROFF_DISABLE;
}

/**
 * @brief 音频复位io,拉低下电
 *
 * @param pin
 * @param low
 */
static void ml28860_reset(uint8_t pin, bool low)
{
    HLI_Dio_WritePin(pin, low);
}
/**
 * @brief 等待busy
 *
 * @param pin
 * @param timeout 超时时间,暂无
 * @return true
 * @return false
 */
static bool ml28860_wait_cbusy(uint8_t pin, uint32_t timeout)
{
    while (timeout--)
    {
        if (HLI_Dio_ReadPin(pin))
        {
            return true;
        }
    }
    return false;
}

static bool ml28860_spi_xfer(ML28860_HWConfig *spi_hdl, const uint8_t *tx, uint8_t *rx, uint16_t len)
{
    HLI_Spi_StatusType res = HLI_SPI_TRANSMISSION_COMPLETE;
    //    res = HLI_Spi_Exchange8Bit(spi_hdl->spi_handle, (const uint8_t *)tx, (const uint8_t *)rx, len, 0xFFFF);
    HLI_Spi_AsyncExchange8Bit(spi_hdl->spi_handle, (const uint8_t *)tx, (const uint8_t *)rx, len);
    if (res != HLI_SPI_TRANSMISSION_COMPLETE)
    {
        return false;
    }
    return true;
}

//========================================
static bool ml28860_send_cmd(ML28860_HWConfig *hw, uint8_t cmd, const uint8_t *param, uint8_t param_len)
{
    if (!ml28860_wait_cbusy(hw->cbusy_pin, 1000))
        return false;

    uint8_t len = param_len;
    ml28860_cs(hw->cs_pin, 0);
    bool ret = ml28860_spi_xfer(hw, &cmd, NULL, 1);

    if (ret && param_len > 0)
    {
        for (; len > 0; len--)
        {
            if (!ml28860_wait_cbusy(hw->cbusy_pin, 1000))
                return false;
            ret = ml28860_spi_xfer(hw, &param[param_len - len], NULL, sizeof(uint8_t));
        }
    }

    ml28860_cs(hw->cs_pin, 1);
    return ret;
}

static bool ml28860_read_cmd(ML28860_HWConfig *hw, uint8_t cmd, uint8_t *data)
{
    if (!ml28860_wait_cbusy(hw->cbusy_pin, 100))
        return false;

    uint8_t dummy = 0x00;
    ml28860_cs(hw->cs_pin, 0);
    bool ret = ml28860_spi_xfer(hw, &cmd, NULL, 1);
    if (!ml28860_wait_cbusy(hw->cbusy_pin, 100))
        return false;
    if (ret)
        ret = ml28860_spi_xfer(hw, &dummy, data, 1);
    ml28860_cs(hw->cs_pin, 1);

    return ret;
}

//========================================
static bool ml28860_init(void *dev, ML28860_HWConfig *cfg)
{
    ML28860_Driver *driver = (ML28860_Driver *)dev;
    if (!dev || !cfg)
        return false;

    memcpy(&driver->hw, cfg, sizeof(ML28860_HWConfig));
    ml28860_reset(cfg->reset_pin, 0);
    for (volatile int i = 0; i < 1000000; i++)
        ;
    ml28860_reset(cfg->reset_pin, 1);
    for (volatile int i = 0; i < 1000000; i++)
        ;

    if (!ml28860_send_cmd(cfg, ML28860_PUP, NULL, 0))
        return false;

    // if (ML28860_Control.ops->clean_wdt(&ML28860_Control))
    // {
    // }
    if (!ml28860_wait_cbusy(driver->hw.cbusy_pin, 1000000))
        return false;
    // 安全配置(使能检测)
    uint8_t safe = 0x00;
    driver->ops->set_safe((ML28860_Driver *)dev, safe);
    // 渐入渐出 disable
    driver->ops->set_fade((ML28860_Driver *)dev, 0, 0x00);
    // 设置 cvol
    //    if (!ML28860_Control.ops->set_ch_volume(&ML28860_Control, CH0, 0x0))
    //    {
    //    }
    // // 模拟模式(D类 使能功放)
    uint8_t amode_byte1 = ML28860_AMODE | (1 << 1) | 1;
    uint8_t amode_byte2 = 0x03;
    ml28860_send_cmd(cfg, amode_byte1, &amode_byte2, 1);
    if (!ml28860_wait_cbusy(driver->hw.cbusy_pin, 1000000))
        return false;
    driver->is_ready = true;
    return true;
}

static void ml28860_deinit(void *dev)
{
    ML28860_Driver *driver = (ML28860_Driver *)dev;
    if (!dev)
        return;

    ml28860_send_cmd(&driver->hw, ML28860_STOP, (uint8_t[]){CH_ALL}, 1);
    ml28860_send_cmd(&driver->hw, ML28860_PDWN, NULL, 0);
    driver->is_ready = false;
}

static bool ml28860_play(void *dev, uint8_t ch, uint16_t phrase, uint8_t repeat_on)
{
    ML28860_Driver *driver = (ML28860_Driver *)dev;
    if (!dev || !driver->is_ready)
        return false;
    if (!ml28860_wait_cbusy(driver->hw.cbusy_pin, 1000000))
        return false;
    uint8_t ret = 0, state = 0;
    uint8_t cmd = ML28860_PLAY | ((ch & 0x03) << 0) | ((phrase & 0x300) >> 6);
    uint8_t param = phrase & 0xFF;
    ret = ml28860_send_cmd(&driver->hw, cmd, &param, 1);
    if (ret == false)
    {
        return (false);
    }
    if (repeat_on != 0)
    { /*repeat ON?*/
        do
        {
            state = driver->ops->read_status((ML28860_Driver *)dev);

        } while ((state & ((uint8_t)((0x01) << (ch & 0x03)))) == 0);

        return ml28860_send_cmd(&driver->hw, (uint8_t)(ML28860_SLOOP | ((uint8_t)((0x01) << (ch & 0x03)))), NULL, 0);
    }

    // 先发送 FADR 命令设置短语地址
    //    uint8_t fadr_cmd = ML28860_FADR | ((phrase & 0x300) >> 8);
    //    uint8_t fadr_param = phrase & 0xFF;
    //    ret = ml28860_send_cmd(&driver->hw, fadr_cmd, &fadr_param, 1);
    //    if (ret == false)
    //    {
    //        return false;
    //    }
    //
    //  uint8_t stch = ML28860_START | CH_ALL;
    //  ml28860_send_cmd(&driver->hw, stch, NULL, 0);

    return ret;
}
/**
 * @brief
 *
 * @param dev
 * @param ch
 * @param phrase
 * @return true
 * @return false
 */
static bool ml28860_Nextplay(void *dev, uint8_t ch, uint16_t phrase)
{
    ML28860_Driver *driver = (ML28860_Driver *)dev;
    if (!dev || !driver->is_ready)
        return false;
    uint8_t ret = 0, state = 0;
    state = driver->ops->read_status((ML28860_Driver *)dev);
    if ((state & ((uint8_t)((0x01) << (ch & 0x03)))) == 0)
    {
        return false; /*不是相同的通道 */
    }

    uint8_t cmd = ML28860_PLAY | ((ch & 0x03) << 0) | ((phrase >> 8) & 0x03);
    uint8_t param = phrase & 0xFF;
    return ml28860_send_cmd(&driver->hw, cmd, &param, 1);
}

static bool ml28860_Cloop(void *dev, uint8_t ch)
{
    ML28860_Driver *driver = (ML28860_Driver *)dev;
    uint8_t cmd = ML28860_CLOOP | (ch & 0x0f);
    return ml28860_send_cmd(&driver->hw, ML28860_CLOOP, NULL, 0);
}
static bool ml28860_stop(void *dev, uint8_t ch)
{
    ML28860_Driver *driver = (ML28860_Driver *)dev;
    uint8_t cmd = ML28860_STOP | (ch & 0x0f);
    return ml28860_send_cmd(&driver->hw, cmd, NULL, 0);
}

static bool ml28860_set_ch_vol(void *dev, uint8_t ch, uint8_t vol)
{
    ML28860_Driver *driver = (ML28860_Driver *)dev;
    uint8_t cmd = ML28860_CVOL | ch;
    return ml28860_send_cmd(&driver->hw, cmd, &vol, 1);
}

static bool ml28860_set_main_vol(void *dev, uint8_t vol)
{
    ML28860_Driver *driver = (ML28860_Driver *)dev;
    uint8_t param = (vol & 0x0F) << 2;
    return ml28860_send_cmd(&driver->hw, ML28860_AVOL, &param, 1);
}

static uint8_t ml28860_read_status(void *dev)
{
    ML28860_Driver *driver = (ML28860_Driver *)dev;
    uint8_t stat = 0;
    ml28860_read_cmd(&driver->hw, ML28860_RDSTAT, &stat);
    return stat;
}
static uint8_t ml28860_read_romRDVER(void *dev)
{
    ML28860_Driver *driver = (ML28860_Driver *)dev;
    uint8_t stat = 0;
    ml28860_read_cmd(&driver->hw, ML28860_RDVER, &stat);
    return stat;
}

static uint8_t ml28860_read_error(void *dev)
{
    ML28860_Driver *driver = (ML28860_Driver *)dev;
    uint8_t err = 0;
    ml28860_read_cmd(&driver->hw, ML28860_RDERR, &err);
    return err;
}

static bool ml28860_Volume_gradient(void *dev, uint8_t en, uint8_t fcon)
{
    ML28860_Driver *driver = (ML28860_Driver *)dev;
    uint8_t param = (en & 0x01) | ((fcon << 1) & 0x0E);
    return ml28860_send_cmd(&driver->hw, ML28860_FADE, &param, 1);
}

static bool ml28860_SAFE(void *dev, uint8_t param)
{
    ML28860_Driver *driver = (ML28860_Driver *)dev;
    return ml28860_send_cmd(&driver->hw, ML28860_SAFE, &param, 1);
}

static bool ml28860_OutStat(void *dev, uint8_t param)
{
    ML28860_Driver *driver = (ML28860_Driver *)dev;
    return ml28860_send_cmd(&driver->hw, ML28860_OUTSTAT, &param, 1);
}

static bool ml28860_err_clean(void *dev)
{
    ML28860_Driver *driver = (ML28860_Driver *)dev;
    return ml28860_send_cmd(&driver->hw, ML28860_ERRCL, NULL, 0);
}

static bool ml28860_clean_wdt(void *dev)
{
    ML28860_Driver *driver = (ML28860_Driver *)dev;
    return ml28860_send_cmd(&driver->hw, ML28860_WDTCL, NULL, 0);
}

static bool ml28860_set_muon(void *dev, uint8_t ch, uint8_t time)
{
    ML28860_Driver *driver = (ML28860_Driver *)dev;
    uint8_t param = ML28860_MUON | (ch & 0x0f);
    return ml28860_send_cmd(&driver->hw, param, &time, 1);
}

static bool ml28860_start_flash(void *dev, uint8_t mima)
{
    ML28860_Driver *driver = (ML28860_Driver *)dev;
    ml28860_eroff(DIO_CHANNEL_D_14, 0);
    return ml28860_send_cmd(&driver->hw, ML28860_FDIRECT, &mima, 1);
}

static bool ml28860_outStat(void *dev, uint8_t parpm)
{
    ML28860_Driver *driver = (ML28860_Driver *)dev;
    return ml28860_send_cmd(&driver->hw, ML28860_OUTSTAT, &parpm, 1);
}
// 绑定操作接口
static ML28860_Ops ml28860_ops = {
    .init = ml28860_init,
    .deinit = ml28860_deinit,
    .play = ml28860_play,
    .stop = ml28860_stop,
    .set_ch_volume = ml28860_set_ch_vol,
    .set_main_volume = ml28860_set_main_vol,
    .read_status = ml28860_read_status,
    .read_error = ml28860_read_error,
    .set_fade = ml28860_Volume_gradient,
    .set_safe = ml28860_SAFE,
    .clean_err = ml28860_err_clean,
    .clean_wdt = ml28860_clean_wdt,
    .cloop = ml28860_Cloop,
    .nextplay = ml28860_Nextplay,
    .set_muon = ml28860_set_muon,
    .start_flash = ml28860_start_flash,
    .out_stat = ml28860_outStat,
    .read_rom = ml28860_read_romRDVER,
    //
};

ML28860_Driver ML28860_Control = {
    .hw = {
        .spi_handle = HLI_SPI_MODULE_0,
        .cbusy_pin = DIO_CHANNEL_H_4,
        .cs_pin = DIO_CHANNEL_B_4,
        .reset_pin = DIO_CHANNEL_D_15,
    },
    .ops = &ml28860_ops,
    .is_ready = false,
    //
};
//========================================

void ml28860_driver_destroy(ML28860_Driver *dev)
{
    if (!dev)
        return;
    if (dev->ops && dev->is_ready)
        dev->ops->deinit(dev);
}

不同flash之间差别不大,ID号会不同

#ifndef __MX25V4035_H
#define __MX25V4035_H

#include "kf32a158sf_hli_dio.h"
#include "kf32a158sf_hli_spi.h"
#include <stdint.h>
#include <stdbool.h>

#define MX25V4035_FLASH_SIZE (512 * 1024) // 512KB
#define MX25V4035_PAGE_SIZE 256           // 页大小 256字节
#define MX25V4035_SECTOR_SIZE (4 * 1024)  // 扇区大小 4KB
#define MX25V4035_BLOCK_SIZE (64 * 1024)  // 块大小 64KB

#define MX25V4035_CMD_READ_ID 0x90
#define MX25V4035_CMD_JEDEC_ID 0x9F
#define MX25V4035_CMD_READ_STATUS 0x05
#define MX25V4035_CMD_WRITE_ENABLE 0x06
#define MX25V4035_CMD_WRITE_DISABLE 0x04
#define MX25V4035_CMD_READ_DATA 0x03
#define MX25V4035_CMD_FAST_READ 0x0B
#define MX25V4035_CMD_PAGE_PROGRAM 0x02
#define MX25V4035_CMD_SECTOR_ERASE 0x20
#define MX25V4035_CMD_BLOCK_ERASE 0xD8
#define MX25V4035_CMD_CHIP_ERASE 0xC7
#define MX25V4035_CMD_DEEP_POWER_DOWN 0xB9
#define MX25V4035_CMD_RELEASE_DP 0xAB

#define MX25V4035_STATUS_WIP (1 << 0)
#define MX25V4035_STATUS_WEL (1 << 1)

typedef struct
{
    HLI_Spi_ModuleType spi_handle;
    uint8_t cs_pin;
} MX25V4035_Config;

typedef struct
{
    /**
     * @brief Initialize Flash device
     *
     * @param dev Point to this structure MX25V4035_Driver
     * @param cfg Point to this structure MX25V4035_Config
     * @return true Success
     * @return false Failed
     */
    bool (*init)(void *dev, MX25V4035_Config *cfg);
    /**
     * @brief Deinitialize Flash device
     *
     * @param dev Point to this structure MX25V4035_Driver
     */
    void (*deinit)(void *dev);
    /**
     * @brief Read Flash manufacturer and device ID
     *
     * @param dev Point to this structure MX25V4035_Driver
     * @param manufacturer_id Point to store manufacturer ID (0xC2 for MXIC)
     * @param device_id Point to store device ID
     * @return true Success
     * @return false Failed
     */
    bool (*read_id)(void *dev, uint8_t *manufacturer_id, uint8_t *device_id);
    /**
     * @brief Read data from Flash
     *
     * @param dev Point to this structure MX25V4035_Driver
     * @param addr Read start address (0 - 512KB)
     * @param data Point to store read data
     * @param len Data length to read
     * @return true Success
     * @return false Failed
     */
    bool (*read)(void *dev, uint32_t addr, uint8_t *data, uint32_t len);
    /**
     * @brief Page program (write) data to Flash
     * @note Data length must not exceed 256 bytes and address must be aligned to page boundary
     *
     * @param dev Point to this structure MX25V4035_Driver
     * @param addr Program start address (must be 256-byte aligned)
     * @param data Point to data to write
     * @param len Data length (1-256 bytes)
     * @return true Success
     * @return false Failed
     */
    bool (*page_program)(void *dev, uint32_t addr, uint8_t *data, uint16_t len);
    /**
     * @brief Erase one sector (4KB)
     *
     * @param dev Point to this structure MX25V4035_Driver
     * @param addr Sector address (must be 4KB aligned)
     * @return true Success
     * @return false Failed
     */
    bool (*sector_erase)(void *dev, uint32_t addr);
    /**
     * @brief Erase one block (64KB)
     *
     * @param dev Point to this structure MX25V4035_Driver
     * @param addr Block address (must be 64KB aligned)
     * @return true Success
     * @return false Failed
     */
    bool (*block_erase)(void *dev, uint32_t addr);
    /**
     * @brief Erase entire Flash chip
     * @note This operation takes long time (several seconds)
     *
     * @param dev Point to this structure MX25V4035_Driver
     * @return true Success
     * @return false Failed
     */
    bool (*chip_erase)(void *dev);
    /**
     * @brief Read status register
     *
     * @param dev Point to this structure MX25V4035_Driver
     * @param status Point to store status register value
     * @return true Success
     * @return false Failed
     */
    bool (*read_status)(void *dev, uint8_t *status);
    /**
     * @brief Wait for Flash busy to complete
     *
     * @param dev Point to this structure MX25V4035_Driver
     * @param timeout Timeout in milliseconds
     * @return true Flash ready
     * @return false Timeout
     */
    bool (*wait_busy)(void *dev, uint32_t timeout);
} MX25V4035_Ops;

typedef struct
{
    MX25V4035_Config hw;
    MX25V4035_Ops *ops;
    bool is_ready;
} MX25V4035_Driver;

extern MX25V4035_Driver g_driver;
void mx25v4035_driver_destroy(MX25V4035_Driver *dev);
MX25V4035_Driver *mx25v4035_driver_create(void);

#endif
#include "Cdd_MX25V4035.h"
#include <string.h>
#include <stdlib.h>

/**
 * @brief spi 片选
 *
 * @param pin io
 * @param low 电平
 */
static void mx25v4035_cs(uint8_t pin, bool low)
{
    HLI_Dio_WritePin(pin, low);
}
/**
 * @brief spi收发
 *
 * @param driver spi枚举
 * @param tx   发送的数据
 * @return uint8_t  返回的数据
 */
static uint8_t mx25v4035_spi_xfer(HLI_Spi_ModuleType driver, uint8_t tx)
{
    uint8_t rxdata = 0;
    HLI_Spi_StatusType res = HLI_SPI_TRANSMISSION_COMPLETE;
    res = HLI_Spi_Exchange8Bit(driver, (const uint8_t *)&tx, (const uint8_t *)&rxdata, 1, 0xFFFF);
    if (res != HLI_SPI_TRANSMISSION_COMPLETE)
    {
        return 0;
    }
    return rxdata;
}
/**
 * @brief 延时
 *
 * @param ms
 */
static void mx25v4035_delay_ms(uint32_t ms)
{
    (void)ms;
}
/**
 * @brief 写使能
 *
 * @param spi_hdl 指向MX25V4035_Driver
 * @param cs_pin 片选引脚
 * @return true
 * @return false
 */
static bool mx25v4035_write_enable(void *spi_hdl, uint8_t cs_pin)
{
    MX25V4035_Driver *driver = (MX25V4035_Driver *)spi_hdl;
    if (!spi_hdl)
        return false;

    mx25v4035_cs(cs_pin, 0);
    mx25v4035_spi_xfer(driver->hw.spi_handle, MX25V4035_CMD_WRITE_ENABLE);
    mx25v4035_cs(cs_pin, 1);
    return true;
}

static bool mx25v4035_wait_busy(void *dev, uint32_t timeout)
{
    MX25V4035_Driver *driver = (MX25V4035_Driver *)dev;
    if (!dev)
        return false;

    uint8_t status = 0;
    uint32_t elapsed = 0;

    while (elapsed < timeout)
    {
        if (!driver->ops->read_status(dev, &status))
            return false;
        if (!(status & MX25V4035_STATUS_WIP))
            return true;
        mx25v4035_delay_ms(1);
        elapsed++;
    }

    return false;
}

static bool mx25v4035_read_id(void *dev, uint8_t *manufacturer_id, uint8_t *device_id)
{
    MX25V4035_Driver *driver = (MX25V4035_Driver *)dev;
    if (!dev)
        return false;

    mx25v4035_cs(driver->hw.cs_pin, 0);
    mx25v4035_spi_xfer(driver->hw.spi_handle, MX25V4035_CMD_READ_ID);
    mx25v4035_spi_xfer(driver->hw.spi_handle, 0x00);               // Address byte 1
    mx25v4035_spi_xfer(driver->hw.spi_handle, 0x00);               // Address byte 2
    mx25v4035_spi_xfer(driver->hw.spi_handle, 0x00);               // Address byte 3
    uint8_t mid = mx25v4035_spi_xfer(driver->hw.spi_handle, 0xFF); // Manufacturer ID (0xC2 for MXIC)
    uint8_t did = mx25v4035_spi_xfer(driver->hw.spi_handle, 0xFF); // Device ID
    mx25v4035_cs(driver->hw.cs_pin, 1);

    *manufacturer_id = mid;
    *device_id = did;

    return true;
}

static bool mx25v4035_read_status(void *dev, uint8_t *status)
{
    MX25V4035_Driver *driver = (MX25V4035_Driver *)dev;
    if (!dev || !status)
        return false;

    mx25v4035_cs(driver->hw.cs_pin, 0);
    mx25v4035_spi_xfer(driver->hw.spi_handle, MX25V4035_CMD_READ_STATUS);
    *status = mx25v4035_spi_xfer(driver->hw.spi_handle, 0xFF);
    mx25v4035_cs(driver->hw.cs_pin, 1);

    return true;
}

static bool mx25v4035_read(void *dev, uint32_t addr, uint8_t *data, uint32_t len)
{
    MX25V4035_Driver *driver = (MX25V4035_Driver *)dev;
    if (!dev || !data || len == 0)
        return false;

    mx25v4035_cs(driver->hw.cs_pin, 0);
    mx25v4035_spi_xfer(driver->hw.spi_handle, MX25V4035_CMD_READ_DATA);
    mx25v4035_spi_xfer(driver->hw.spi_handle, (addr >> 16) & 0xFF);
    mx25v4035_spi_xfer(driver->hw.spi_handle, (addr >> 8) & 0xFF);
    mx25v4035_spi_xfer(driver->hw.spi_handle, addr & 0xFF);

    for (uint32_t i = 0; i < len; i++)
    {
        data[i] = mx25v4035_spi_xfer(driver->hw.spi_handle, 0xFF);
    }

    mx25v4035_cs(driver->hw.cs_pin, 1);

    return true;
}

static bool mx25v4035_page_program(void *dev, uint32_t addr, uint8_t *data, uint16_t len)
{
    MX25V4035_Driver *driver = (MX25V4035_Driver *)dev;
    if (!dev || !data || len == 0 || len > MX25V4035_PAGE_SIZE)
        return false;

    if (!mx25v4035_write_enable(driver, driver->hw.cs_pin))
        return false;

    mx25v4035_cs(driver->hw.cs_pin, 0);
    mx25v4035_spi_xfer(driver->hw.spi_handle, MX25V4035_CMD_PAGE_PROGRAM);
    mx25v4035_spi_xfer(driver->hw.spi_handle, (addr >> 16) & 0xFF);
    mx25v4035_spi_xfer(driver->hw.spi_handle, (addr >> 8) & 0xFF);
    mx25v4035_spi_xfer(driver->hw.spi_handle, addr & 0xFF);

    for (uint16_t i = 0; i < len; i++)
    {
        mx25v4035_spi_xfer(driver->hw.spi_handle, data[i]);
    }

    mx25v4035_cs(driver->hw.cs_pin, 1);

    return mx25v4035_wait_busy(dev, 1000);
}

static bool mx25v4035_sector_erase(void *dev, uint32_t addr)
{
    MX25V4035_Driver *driver = (MX25V4035_Driver *)dev;
    if (!dev)
        return false;

    if (!mx25v4035_write_enable(driver, driver->hw.cs_pin))
        return false;

    mx25v4035_cs(driver->hw.cs_pin, 0);
    mx25v4035_spi_xfer(driver->hw.spi_handle, MX25V4035_CMD_SECTOR_ERASE);
    mx25v4035_spi_xfer(driver->hw.spi_handle, (addr >> 16) & 0xFF);
    mx25v4035_spi_xfer(driver->hw.spi_handle, (addr >> 8) & 0xFF);
    mx25v4035_spi_xfer(driver->hw.spi_handle, addr & 0xFF);
    mx25v4035_cs(driver->hw.cs_pin, 1);

    return mx25v4035_wait_busy(dev, 5000000);
}

static bool mx25v4035_block_erase(void *dev, uint32_t addr)
{
    MX25V4035_Driver *driver = (MX25V4035_Driver *)dev;
    if (!dev)
        return false;

    if (!mx25v4035_write_enable(driver, driver->hw.cs_pin))
        return false;

    mx25v4035_cs(driver->hw.cs_pin, 0);
    mx25v4035_spi_xfer(driver->hw.spi_handle, MX25V4035_CMD_BLOCK_ERASE);
    mx25v4035_spi_xfer(driver->hw.spi_handle, (addr >> 16) & 0xFF);
    mx25v4035_spi_xfer(driver->hw.spi_handle, (addr >> 8) & 0xFF);
    mx25v4035_spi_xfer(driver->hw.spi_handle, addr & 0xFF);
    mx25v4035_cs(driver->hw.cs_pin, 1);

    return mx25v4035_wait_busy(dev, 3000);
}

static bool mx25v4035_chip_erase(void *dev)
{
    MX25V4035_Driver *driver = (MX25V4035_Driver *)dev;
    if (!dev)
        return false;

    if (!mx25v4035_write_enable(driver, driver->hw.cs_pin))
        return false;

    mx25v4035_cs(driver->hw.cs_pin, 0);
    mx25v4035_spi_xfer(driver->hw.spi_handle, MX25V4035_CMD_CHIP_ERASE);
    mx25v4035_cs(driver->hw.cs_pin, 1);

    return mx25v4035_wait_busy(dev, 10000);
}

static bool mx25v4035_init(void *dev, MX25V4035_Config *cfg)
{
    MX25V4035_Driver *driver = (MX25V4035_Driver *)dev;
    if (!dev)
        return false;

    // memcpy(&driver->hw, cfg, sizeof(MX25V4035_Config));

    uint8_t mid, did;
    if (!driver->ops->read_id(driver, &mid, &did))
        return false;

    if (mid != 0xC2) // C2
        return false;

    driver->is_ready = true;
    return true;
}

static void mx25v4035_deinit(void *dev)
{
    MX25V4035_Driver *driver = (MX25V4035_Driver *)dev;
    if (!dev)
        return;

    driver->is_ready = false;
}

static bool mx25v4035_write(void *dev, uint32_t addr, uint8_t *data, uint32_t len)
{
    MX25V4035_Driver *driver = (MX25V4035_Driver *)dev;
    if (!dev || !driver->is_ready || !data || len == 0)
        return false;

    uint32_t page_size = MX25V4035_PAGE_SIZE;
    uint32_t page_offset = addr % page_size;
    uint32_t write_len = 0;

    while (len > 0)
    {
        write_len = page_size - page_offset;
        if (write_len > len)
            write_len = len;

        if (!driver->ops->page_program(dev, addr, data, (uint16_t)write_len))
            return false;

        addr += write_len;
        data += write_len;
        len -= write_len;
        page_offset = 0;
    }

    return true;
}

static bool mx25v4035_erase(void *dev, uint32_t addr, uint32_t len)
{
    MX25V4035_Driver *driver = (MX25V4035_Driver *)dev;
    if (!dev || !driver->is_ready)
        return false;

    uint32_t sector_size = MX25V4035_SECTOR_SIZE;
    uint32_t block_size = MX25V4035_BLOCK_SIZE;

    if (len >= block_size && (addr % block_size) == 0)
    {
        uint32_t block_count = len / block_size;
        for (uint32_t i = 0; i < block_count; i++)
        {
            if (!driver->ops->block_erase(dev, addr + i * block_size))
                return false;
        }
        len -= block_count * block_size;
        addr += block_count * block_size;
    }

    if (len >= sector_size && (addr % sector_size) == 0)
    {
        uint32_t sector_count = len / sector_size;
        for (uint32_t i = 0; i < sector_count; i++)
        {
            if (!driver->ops->sector_erase(dev, addr + i * sector_size))
                return false;
        }
    }

    return true;
}

static MX25V4035_Ops mx25v4035_ops = {
    .init = mx25v4035_init,
    .deinit = mx25v4035_deinit,
    .read_id = mx25v4035_read_id,
    .read = mx25v4035_read,
    .page_program = mx25v4035_page_program,
    .sector_erase = mx25v4035_sector_erase,
    .block_erase = mx25v4035_block_erase,
    .chip_erase = mx25v4035_chip_erase,
    .read_status = mx25v4035_read_status,
    .wait_busy = mx25v4035_wait_busy,
};
MX25V4035_Driver g_driver = {
    .ops = &mx25v4035_ops,
    .hw = {
        .spi_handle = HLI_SPI_MODULE_0,
        .cs_pin = DIO_CHANNEL_B_4,
    },
    .is_ready = false,
};

MX25V4035_Driver *mx25v4035_driver_create(void)
{
    return &g_driver;
}
/**
 * @brief deinit 控制
 *
 * @param dev 指向MX25V4035_Driver
 */
void mx25v4035_driver_destroy(MX25V4035_Driver *dev)
{
    if (!dev)
        return;
    if (dev->ops && dev->is_ready)
        dev->ops->deinit(dev);
}

2.串行写入flash


#ifndef __CAN_FLASH_WRITER_H
#define __CAN_FLASH_WRITER_H

#include <stdint.h>
#include <stdbool.h>

// ==================== 配置宏 ====================
#ifndef FLASH_TOTAL_SIZE
#define FLASH_TOTAL_SIZE (512 * 1024) // Flash总容量 512KB
#endif

#ifndef FLASH_PAGE_SIZE
#define FLASH_PAGE_SIZE 256 // 页大小 256字节
#endif

#ifndef FLASH_SECTOR_SIZE
#define FLASH_SECTOR_SIZE (4 * 1024) // 扇区大小 4KB
#endif

// CAN ID定义
#define CAN_TX_ID 0x101 // MCU发送ID
#define CAN_RX_ID 0x100 // MCU接收ID

// ==================== 类型定义 ====================

typedef enum
{
    STATE_IDLE = 0,  // 空闲
    STATE_RECEIVING, // 接收数据中
    STATE_WRITING,   // 写入Flash中
    STATE_ERASING,   // 擦除中
} TransferState;

// ==================== 接口函数 ====================

/**
 * @brief 初始化CAN Flash写入模块
 * @note 会调用bsp_can_init()和bsp_flash_init()
 */
void can_flash_writer_init(void);

/**
 * @brief 轮询处理
 * @note 建议调用周期: 1-10ms
 */
void can_flash_writer(uint32_t id, uint8_t *data, uint8_t dlc);

/**
 * @brief 获取当前传输状态
 * @return 当前状态
 */
TransferState can_flash_writer_get_state(void);

/**
 * @brief 获取传输进度
 * @param total 输出总包数 (可为NULL)
 * @param received 输出已接收包数 (可为NULL)
 */
void can_flash_writer_get_progress(uint32_t *total, uint32_t *received);

/**
 * @brief 中止当前传输
 */
void can_flash_writer_abort(void);

// ==================== 底层BSP接口 (弱函数,用户可重写) ====================

/**
 * @brief CAN初始化
 * @note 用户需要实现CAN外设初始化,配置过滤器接收0x100
 */
void bsp_can_init(void);

/**
 * @brief 发送CAN帧
 * @param id CAN ID
 * @param data 数据指针 (8字节)
 * @param dlc 数据长度 (0-8)
 * @return true 成功, false 失败
 */
bool bsp_can_send(uint32_t id, uint8_t *data, uint8_t dlc);

/**
 * @brief 检查是否有CAN数据接收
 * @param id 输出接收到的CAN ID
 * @param data 输出数据缓冲区
 * @param dlc 输出数据长度
 * @return true 有数据, false 无数据
 */
bool bsp_can_receive(uint32_t *id, uint8_t *data, uint8_t *dlc);

/**
 * @brief SPI Flash初始化
 * @note 用户需要实现SPI和Flash初始化
 */
void bsp_flash_init(void);

/**
 * @brief Flash擦除扇区
 * @param addr 扇区地址 (4KB对齐)
 * @return true 成功, false 失败
 */
bool bsp_flash_erase_sector(uint32_t addr);

/**
 * @brief Flash页写入 (256字节)
 * @param addr 页地址 (256字节对齐)
 * @param data 数据指针
 * @param len 数据长度 (<=256)
 * @return true 成功, false 失败
 */
bool bsp_flash_page_write(uint32_t addr, uint8_t *data, uint16_t len);

/**
 * @brief Flash读取
 * @param addr 读取地址
 * @param data 数据缓冲区
 * @param len 读取长度
 * @return true 成功, false 失败
 */
bool bsp_flash_read(uint32_t addr, uint8_t *data, uint32_t len);

/**
 * @brief Flash忙检测
 * @return true 忙, false 空闲
 */
bool bsp_flash_is_busy(void);

/**
 * @brief 毫秒延时
 * @param ms 毫秒数
 */
void bsp_delay_ms(uint32_t ms);

/**
 * @brief 获取系统tick (用于超时判断)
 * @return tick值
 */
uint32_t bsp_get_tick(void);

#endif /* __CAN_FLASH_WRITER_H */
/**
 * 命令格式:
 * - CMD_START (0x01): [CMD(1), file_size(4), 保留(3)]
 * - CMD_DATA  (0x02): [CMD(1), seq(1), data(6)]
 * - CMD_END   (0x03): [CMD, 0x00, crc16(2), 0x00(3)]
 * - CMD_ERASE (0x04): [CMD, 0x00, addr(4), pages(2)]
 */

#include "can_flash_writer.h"
#include "Rte.h"
#include <string.h>

// ========================================
#define CAN_DATA_PER_PKT 6 // 每包实际数据字节数

// 缓冲区配置
#define WRITE_BUFFER_SIZE FLASH_PAGE_SIZE // 写入缓冲区大小1页

// ==================== 命令定义 ====================
typedef enum
{
    CMD_START = 0x01, // 开始传输
    CMD_DATA = 0x02,  // 数据包
    CMD_END = 0x03,   // 结束传输
    CMD_ERASE = 0x04, // 擦除Flash
} CanCommand;

typedef enum
{
    RESP_OK = 0x00,   // 成功
    RESP_BUSY = 0x01, // 忙
    RESP_ERR = 0x02,  // 错误
} CanResponse;

// ==================== 数据结构 ====================
typedef struct
{
    uint32_t file_size;        // 文件总大小
    uint32_t total_packets;    // 总包数
    uint32_t received_packets; // 已接收包数
    uint32_t flash_addr;       // 当前Flash写入地址
    uint16_t buffer_offset;    // 缓冲区当前偏移
    TransferState state;       // 当前状态
    bool is_valid;             // 传输是否有效
} TransferContext;

// ==================== 全局变量 ====================
static HLI_Canfd_MessageType AudioFlashPdu;
static TransferContext g_ctx;
static uint8_t g_write_buffer[WRITE_BUFFER_SIZE]; // 写入缓冲区
static uint8_t g_can_tx_buf[8];                   // CAN发送缓冲区
static MX25V4035_Driver *g_flash;

/**
 * @brief CAN初始化
 *        ecuM初始化
 */
void bsp_can_init(void)
{
    // CAN外设初始化
    // 配置过滤器接收0x100
}

/**
 * @brief 发送CAN帧
 */
bool bsp_can_send(uint32_t id, uint8_t *data, uint8_t dlc)
{
    uint8_t can_tx_status = 0;

    AudioFlashPdu.ID = id;
    AudioFlashPdu.DataPtr = data;
    AudioFlashPdu.DataLength = dlc;
    AudioFlashPdu.IDType = HLI_CANFD_DATA_STD;
    AudioFlashPdu.FrameType = HLI_CANFD_FRAME_CLASSICAL;
    AudioFlashPdu.IsRemote = 0;

    can_tx_status = HLI_Canfd_Write(HLI_CANFD7, 0, &AudioFlashPdu);
    if (can_tx_status != HLI_CANFD_OK)
    {
        can_tx_status = HLI_Canfd_Write(HLI_CANFD7, 0, &AudioFlashPdu);
        if (can_tx_status != HLI_CANFD_OK)
            return false;
        else
            return true;
    }
    else
        return true;
    /*正常不会到这*/
    return false;
}

/**
 * @brief 检查是否有CAN数据接收
 *        中断接收
 */
bool bsp_can_receive(uint32_t *id, uint8_t *data, uint8_t *dlc)
{
    (void)id;
    (void)data;
    (void)dlc;
    return false;
}

/**
 * @brief SPI Flash初始化
 */
void bsp_flash_init(void)
{
    g_flash = mx25v4035_driver_create();
    if (g_flash == NULL)
        return;

    if (!ML28860_Control.ops->start_flash(&ML28860_Control, 0xFF))
    {
        ML28860_Control.is_ready = false;
    }
    if (!g_flash->ops->init(g_flash, NULL))
    {
        g_flash->is_ready = false;
    }
}

/**
 * @brief Flash擦除扇区
 *
 * @param addr 擦除地址
 * @return true
 * @return false
 */
bool bsp_flash_erase_sector(uint32_t addr)
{
    if (g_flash == NULL || !g_flash->is_ready)
        return false;
    return g_flash->ops->sector_erase(g_flash, addr);
}

/**
 * @brief Flash页写入
 *
 * @param addr dizhi
 * @param data data
 * @param len
 * @return true
 * @return false
 */
bool bsp_flash_page_write(uint32_t addr, uint8_t *data, uint16_t len)
{
    if (g_flash == NULL || !g_flash->is_ready)
        return false;
    return g_flash->ops->page_program(g_flash, addr, data, len);
}

/**
 * @brief Flash读取
 *
 * @param addr
 * @param data
 * @param len
 * @return true
 * @return false
 */
bool bsp_flash_read(uint32_t addr, uint8_t *data, uint32_t len)
{
    if (g_flash == NULL || !g_flash->is_ready)
        return false;
    return g_flash->ops->read(g_flash, addr, data, len);
}

/**
 * @brief Flash忙检测
 *
 * @return true
 * @return false
 */
bool bsp_flash_is_busy(void)
{
    if (g_flash == NULL || !g_flash->is_ready)
        return false;
    uint8_t status = 0;
    if (g_flash->ops->read_status(g_flash, &status))
        return (status & MX25V4035_STATUS_WIP) != 0;
    return false;
}

/**
 * @brief 毫秒延时
 *
 * @param ms
 */
void bsp_delay_ms(uint32_t ms)
{
    (void)ms;
}

/**
 * @brief 获取系统tick
 *
 * @return uint32_t
 */
uint32_t bsp_get_tick(void)
{
    return 10;
}

// ==================== 内部函数 ====================

/**
 * @brief 发送响应帧
 *
 * @param resp_code
 * @param extra_info
 */
static void send_response(uint8_t resp_code, uint8_t extra_info)
{
    memset(g_can_tx_buf, 0, 8);
    g_can_tx_buf[0] = resp_code;
    g_can_tx_buf[1] = extra_info;
    bsp_can_send(CAN_TX_ID, g_can_tx_buf, 8);
}

/**
 * @brief 将缓冲区数据写入Flash
 * @return true 成功, false 失败
 */
static bool flush_buffer_to_flash(void)
{
    if (g_ctx.buffer_offset == 0)
    {
        return true; // 缓冲区为空
    }

    // 等待Flash空闲
    uint32_t timeout = bsp_get_tick(); //   +1000; // 1秒超时
    while (bsp_flash_is_busy())
    {
        timeout--;
        // if (bsp_get_tick() > timeout)
        if (!timeout)
        {
            return false;
        }
    }

    // 写入Flash (按实际缓冲区大小)
    uint16_t write_len = g_ctx.buffer_offset;
    if (!bsp_flash_page_write(g_ctx.flash_addr, g_write_buffer, write_len))
    {
        return false;
    }

    // 更新地址和偏移
    g_ctx.flash_addr += write_len;
    g_ctx.buffer_offset = 0;

    return true;
}

/**
 * @brief 处理开始传输命令
 * @note 格式: [CMD(1), file_size(4), 保留(3)] = 8字节
 */
static void handle_start_cmd(uint8_t *data)
{
    if (g_ctx.state != STATE_IDLE)
    {
        send_response(RESP_BUSY, 0);
        return;
    }

    // 解析参数 [CMD(1), file_size(4), 保留(3)]
    g_ctx.file_size = ((uint32_t)data[1] << 24) |
                      ((uint32_t)data[2] << 16) |
                      ((uint32_t)data[3] << 8) |
                      data[4];

    // 根据文件大小计算总包数 (每包6字节,向上取整)
    g_ctx.total_packets = (g_ctx.file_size + CAN_DATA_PER_PKT - 1) / CAN_DATA_PER_PKT;

    // 检查参数有效性
    if (g_ctx.file_size > FLASH_TOTAL_SIZE)
    {
        send_response(RESP_ERR, 0x01); // 文件过大
        return;
    }

    // 初始化传输上下文
    g_ctx.received_packets = 0;
    g_ctx.flash_addr = 0;
    g_ctx.buffer_offset = 0;
    g_ctx.is_valid = true;
    memset(g_write_buffer, 0xFF, WRITE_BUFFER_SIZE);

    g_ctx.state = STATE_RECEIVING;

    send_response(RESP_OK, 0);
}

/**
 * @brief 处理数据包命令
 */
static void handle_data_cmd(uint8_t *data)
{
    if (g_ctx.state != STATE_RECEIVING)
    {
        send_response(RESP_ERR, 0x02); // 状态错误
        return;
    }

    if (!g_ctx.is_valid)
    {
        send_response(RESP_ERR, 0x03); // 传输无效
        return;
    }

    // uint8_t seq = data[1];

    // 计算本包实际数据长度
    uint32_t remaining = g_ctx.file_size -
                         (g_ctx.received_packets * CAN_DATA_PER_PKT);
    uint8_t data_len = (remaining < CAN_DATA_PER_PKT) ? (uint8_t)remaining : CAN_DATA_PER_PKT;

    // 拷贝数据到缓冲区
    for (uint8_t i = 0; i < data_len; i++)
    {
        g_write_buffer[g_ctx.buffer_offset++] = data[2 + i];

        // 缓冲区满,写入Flash
        if (g_ctx.buffer_offset >= FLASH_PAGE_SIZE)
        {
            g_ctx.state = STATE_WRITING;

            if (!flush_buffer_to_flash())
            {
                g_ctx.is_valid = false;
                g_ctx.state = STATE_IDLE;
                send_response(RESP_ERR, 0x04); // 写入失败
                return;
            }

            g_ctx.state = STATE_RECEIVING;
        }
    }

    g_ctx.received_packets++;

    // 每包都响应,确保数据传输可靠
    send_response(RESP_OK, 0);
}

/**
 * @brief 处理结束传输命令
 */
static void handle_end_cmd(uint8_t *data)
{
    (void)data;

    if (g_ctx.state != STATE_RECEIVING && g_ctx.state != STATE_IDLE)
    {
        send_response(RESP_ERR, 0x02);
        return;
    }

    // 刷新剩余数据到Flash
    if (g_ctx.buffer_offset > 0)
    {
        g_ctx.state = STATE_WRITING;

        if (!flush_buffer_to_flash())
        {
            g_ctx.is_valid = false;
            g_ctx.state = STATE_IDLE;
            send_response(RESP_ERR, 0x04);
            return;
        }
    }

    // 传输完成
    g_ctx.state = STATE_IDLE;
    g_ctx.is_valid = false;

    send_response(RESP_OK, 0);
}

/**
 * @brief 处理擦除命令
 */
static void handle_erase_cmd(uint8_t *data)
{
    if (g_ctx.state != STATE_IDLE)
    {
        send_response(RESP_BUSY, 0);
        return;
    }

    // 解析参数 [CMD, 0x00, addr(4), pages(2)]
    uint32_t addr = ((uint32_t)data[2] << 24) |
                    ((uint32_t)data[3] << 16) |
                    ((uint32_t)data[4] << 8) |
                    data[5];
    uint16_t pages = ((uint16_t)data[6] << 8) | data[7];

    // 检查地址对齐 (4KB扇区对齐)
    if (addr & (FLASH_SECTOR_SIZE - 1))
    {
        send_response(RESP_ERR, 0x05); // 地址未对齐
        return;
    }

    // 检查范围
    if (addr + (pages * FLASH_SECTOR_SIZE) > FLASH_TOTAL_SIZE)
    {
        send_response(RESP_ERR, 0x06); // 超出范围
        return;
    }

    g_ctx.state = STATE_ERASING;

    // 执行擦除
    uint32_t erase_addr = addr;
    for (uint16_t i = 0; i < pages; i++)
    {
        if (!bsp_flash_erase_sector(erase_addr))
        {
            g_ctx.state = STATE_IDLE;
            send_response(RESP_ERR, 0x07); // 擦除失败
            return;
        }
        erase_addr += FLASH_SECTOR_SIZE;

        // 喂狗
        // bsp_watchdog_refresh();
    }

    g_ctx.state = STATE_IDLE;
    send_response(RESP_OK, 0);
}

/**
 * @brief 处理CAN接收数据
 */
static void process_can_frame(uint32_t id, uint8_t *data, uint8_t dlc)
{
    if (id != CAN_RX_ID || dlc < 1)
    {
        return;
    }

    uint8_t cmd = data[0];
    /*驱动串行flash */
//    ML28860_Control.ops->start_flash(&ML28860_Control, 0xFF);

    switch (cmd)
    {
    case CMD_START:
        handle_start_cmd(data);
        break;

    case CMD_DATA:
        handle_data_cmd(data);
        break;

    case CMD_END:
        handle_end_cmd(data);
        break;

    case CMD_ERASE:
        handle_erase_cmd(data);
        break;

    default:
        send_response(RESP_ERR, 0xFF); // 未知命令
        break;
    }
}

/**
 * @brief 初始化CAN Flash写入模块
 */
void can_flash_writer_init(void)
{
    // 初始化上下文
    memset(&g_ctx, 0, sizeof(g_ctx));
    g_ctx.state = STATE_IDLE;

    // 初始化缓冲区
    memset(g_write_buffer, 0xFF, WRITE_BUFFER_SIZE);

    // 初始化底层
    bsp_can_init();
    bsp_flash_init();
}

/**
 * @brief 轮询处理
 */
void can_flash_writer(uint32_t id, uint8_t *data, uint8_t dlc)
{

    // 检查并处理接收到的CAN帧
    // if (bsp_can_receive(&rx_id, rx_data, &rx_dlc))
    // {
    //     process_can_frame(rx_id, rx_data, rx_dlc);
    // }

    process_can_frame(id, data, dlc);
}

/**
 * @brief 获取当前传输状态
 */
TransferState can_flash_writer_get_state(void)
{
    return g_ctx.state;
}

/**
 * @brief 获取传输进度
 * @param total 输出总包数
 * @param received 输出已接收包数
 */
void can_flash_writer_get_progress(uint32_t *total, uint32_t *received)
{
    if (total)
        *total = g_ctx.total_packets;
    if (received)
        *received = g_ctx.received_packets;
}

/**
 * @brief 中止当前传输
 */
void can_flash_writer_abort(void)
{
    g_ctx.state = STATE_IDLE;
    g_ctx.is_valid = false;
    g_ctx.buffer_offset = 0;
}

py脚本

import sys
import os
import time
import struct
from pathlib import Path
from typing import Optional, Callable

from PySide6.QtWidgets import (
    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
    QLabel, QPushButton, QLineEdit, QProgressBar, QTextEdit,
    QFileDialog, QComboBox, QSpinBox, QGroupBox, QMessageBox
)
from PySide6.QtCore import Qt, QThread, Signal, QTimer
from PySide6.QtGui import QFont

try:
    import can
    CAN_AVAILABLE = True
except ImportError:
    CAN_AVAILABLE = False

# ==================== CAN协议定义 ====================
class CanProtocol:
    """CAN通信协议定义"""
    
    # CAN ID
    TX_ID = 0x100  # 上位机 -> MCU
    RX_ID = 0x101  # MCU -> 上位机
    
    # 命令类型
    CMD_START = 0x01   # 开始传输 [CMD(1), file_size(4), 保留(3)]
    CMD_DATA = 0x02    # 数据包 [CMD, seq, data(6)]
    CMD_END = 0x03     # 结束传输 [CMD, 0x00, crc16(2), 0x00(3)]
    CMD_ERASE = 0x04   # 擦除Flash [CMD, 0x00, addr(4), pages(2)]
    
    # 响应类型
    RESP_OK = 0x00     # 成功
    RESP_BUSY = 0x01   # 忙
    RESP_ERR = 0x02    # 错误
    
    # 数据包大小
    DATA_PER_PACKET = 6  # 每包实际数据字节数
    PACKET_SIZE = 8      # CAN帧总字节数

# ==================== CAN工作线程 ====================
class CanWorker(QThread):
    """CAN通信工作线程"""
    
    # 信号定义
    progress_updated = Signal(int, int)  # 当前包数, 总包数
    status_changed = Signal(str)         # 状态消息
    finished_signal = Signal(bool, str)  # 成功/失败, 消息
    log_message = Signal(str)            # 日志消息
    
    def __init__(self, parent=None):
        super().__init__(parent)
        self.bus = None
        self.channel = "PCAN_USBBUS1"
        self.baudrate = 500000
        self.file_path: Optional[str] = None
        self.is_running = False
        self._stop_requested = False
        
    def initialize(self, channel: str, baudrate: int) -> bool:
        """初始化CAN设备"""
        if not CAN_AVAILABLE:
            self.log_message.emit("错误: python-can库不可用")
            return False
            
        try:
            # 波特率映射
            baudrate_map = {
                "125K": 125000,
                "250K": 250000,
                "500K": 500000,
                "1M": 1000000,
            }
            
            self.baudrate = baudrate_map.get(baudrate, 500000)
            self.channel = channel
            
            # 创建CAN总线
            self.bus = can.interface.Bus(
                channel=self.channel,
                bustype='pcan',
                bitrate=self.baudrate
            )
            
            # 设置过滤器只接收MCU响应
            self.bus.set_filters([{"can_id": CanProtocol.RX_ID, "can_mask": 0x7FF}])
            
            self.log_message.emit(f"CAN初始化成功: {channel} @ {baudrate}")
            return True
            
        except Exception as e:
            self.log_message.emit(f"CAN初始化失败: {str(e)}")
            return False
        
    def uninitialize(self):
        """释放CAN设备"""
        if self.bus:
            self.bus.shutdown()
            self.bus = None
            self.log_message.emit("CAN已释放")
            
    def set_file(self, file_path: str):
        """设置要传输的文件"""
        self.file_path = file_path
        
    def stop(self):
        """请求停止传输"""
        self._stop_requested = True
        self.status_changed.emit("正在停止...")
        
    def send_frame(self, data: bytes) -> bool:
        """发送CAN帧"""
        if not self.bus:
            return False
            
        try:
            msg = can.Message(
                arbitration_id=CanProtocol.TX_ID,
                data=list(data),
                is_extended_id=False
            )
            self.bus.send(msg)
            return True
        except Exception:
            return False
            # return True
        
    def wait_response(self, timeout_ms: int = 1000) -> Optional[bytes]:
        """等待MCU响应"""
        if not self.bus:
            return None
            
        try:
            msg = self.bus.recv(timeout=timeout_ms / 1000.0)
            if msg and msg.arbitration_id == CanProtocol.RX_ID:
                return bytes(msg.data)
        except Exception:
            pass
        return None
        
    def run(self):
        """主工作循环"""
        if not self.file_path or not os.path.exists(self.file_path):
            self.finished_signal.emit(False, "文件不存在")
            return
            
        self.is_running = True
        self._stop_requested = False
        
        try:
            # 读取文件
            with open(self.file_path, 'rb') as f:
                file_data = f.read()
                
            file_size = len(file_data)
            self.log_message.emit(f"文件大小: {file_size} 字节")
            
            if file_size > 512 * 1024:
                self.finished_signal.emit(False, "文件超过512KB限制")
                return
                
            # 计算包数
            total_packets = (file_size + CanProtocol.DATA_PER_PACKET - 1) // CanProtocol.DATA_PER_PACKET
            self.log_message.emit(f"总包数: {total_packets}")
            
            # 1. 发送开始命令
            self.status_changed.emit("发送开始命令...")
            # 格式: CMD(1) + file_size(4) + 保留(3) = 8字节
            start_cmd = struct.pack('>BI',
                CanProtocol.CMD_START,  # CMD
                file_size               # 文件大小 (4字节)
            )
            start_cmd = start_cmd.ljust(8, b'\x00')  # 填充到8字节
            
            if not self.send_frame(start_cmd):
                self.finished_signal.emit(False, "发送开始命令失败")
                return
                
            # 等待MCU确认
            resp = self.wait_response(2000)
            if not resp or resp[0] != CanProtocol.RESP_OK:
                self.finished_signal.emit(False, "MCU未响应开始命令")
                return
                
            self.log_message.emit("MCU已就绪,开始传输数据...")
            
            # 2. 发送数据包
            for pkt_idx in range(total_packets):
                if self._stop_requested:
                    self.finished_signal.emit(False, "用户取消")
                    return
                    
                # 计算数据偏移和长度
                offset = pkt_idx * CanProtocol.DATA_PER_PACKET
                data_len = min(CanProtocol.DATA_PER_PACKET, file_size - offset)
                data_chunk = file_data[offset:offset + data_len]
                
                # 构建数据包 (填充到8字节)
                # 序列号: 低8位是当前包序号,用于基本同步检查
                seq = (pkt_idx + 1) & 0xFF  # 包号从1开始,避免0
                packet = bytes([CanProtocol.CMD_DATA, seq]) + data_chunk
                packet = packet.ljust(8, b'\x00')
                
                # 发送
                retry_count = 0
                while retry_count < 3:
                    if self.send_frame(packet):
                        break
                    retry_count += 1
                    time.sleep(0.001)
                    
                if retry_count >= 3:
                    self.finished_signal.emit(False, f"发送第{pkt_idx+1}包失败")
                    return
                    
                # 每包都等待响应,确保数据传输可靠
                resp = self.wait_response(500)
                if resp and resp[0] != CanProtocol.RESP_OK:
                    self.finished_signal.emit(False, f"第{pkt_idx+1}包MCU响应错误")
                    return
                        
                # 更新进度
                self.progress_updated.emit(pkt_idx + 1, total_packets)
                
            # 3. 发送结束命令
            self.status_changed.emit("发送结束命令...")
            end_cmd = bytes([CanProtocol.CMD_END, 0, 0, 0, 0, 0, 0, 0])
            
            if not self.send_frame(end_cmd):
                self.finished_signal.emit(False, "发送结束命令失败")
                return
                
            resp = self.wait_response(5000)
            if not resp or resp[0] != CanProtocol.RESP_OK:
                self.finished_signal.emit(False, "MCU结束响应异常")
                return
                
            self.finished_signal.emit(True, f"传输完成,共{total_packets}包")
            
        except Exception as e:
            self.finished_signal.emit(False, f"异常: {str(e)}")
        finally:
            self.is_running = False
            
    def erase_flash(self, addr: int = 0, pages: int = 128) -> bool:
        """擦除Flash"""
        self.status_changed.emit("发送擦除命令...")
        
        erase_cmd = struct.pack('>BBIH',
            CanProtocol.CMD_ERASE,
            0,
            addr,
            pages
        )
        
        if not self.send_frame(erase_cmd):
            return False
            
        resp = self.wait_response(10000)  # 擦除需要时间
        return resp is not None and resp[0] == CanProtocol.RESP_OK
        # return True

# ==================== 主窗口 ====================
class MainWindow(QMainWindow):
    """主窗口"""
    
    def __init__(self):
        super().__init__()
        self.worker = None
        self._signals_connected = False  # 标记信号是否已连接
        self.setup_ui()
        self.apply_styles()
        
    def setup_ui(self):
        """设置UI"""
        self.setWindowTitle("ML28860 音频数据上传工具 - python-can")
        self.setMinimumSize(700, 500)
        
        # 中央部件
        central = QWidget()
        self.setCentralWidget(central)
        layout = QVBoxLayout(central)
        layout.setSpacing(15)
        layout.setContentsMargins(20, 20, 20, 20)
        
        # ===== 文件选择区域 =====
        file_group = QGroupBox("文件选择")
        file_layout = QHBoxLayout(file_group)
        
        self.file_edit = QLineEdit()
        self.file_edit.setPlaceholderText("选择要上传的bin文件...")
        self.file_edit.setReadOnly(True)
        
        self.btn_browse = QPushButton("浏览...")
        self.btn_browse.clicked.connect(self.browse_file)
        
        file_layout.addWidget(self.file_edit)
        file_layout.addWidget(self.btn_browse)
        
        layout.addWidget(file_group)
        
        # ===== CAN配置区域 =====
        can_group = QGroupBox("CAN配置")
        can_layout = QHBoxLayout(can_group)
        
        can_layout.addWidget(QLabel("通道:"))
        self.combo_channel = QComboBox()
        self.combo_channel.addItems(["PCAN_USBBUS1", "PCAN_USBBUS2", "PCAN_USBBUS3", "PCAN_USBBUS4"])
        can_layout.addWidget(self.combo_channel)
        
        can_layout.addWidget(QLabel("波特率:"))
        self.combo_baudrate = QComboBox()
        self.combo_baudrate.addItems(["125K", "250K", "500K", "1M"])
        self.combo_baudrate.setCurrentText("500K")
        can_layout.addWidget(self.combo_baudrate)
        
        can_layout.addStretch()
        
        self.btn_connect = QPushButton("连接")
        self.btn_connect.clicked.connect(self.toggle_connection)
        can_layout.addWidget(self.btn_connect)
        
        layout.addWidget(can_group)
        
        # ===== 操作按钮区域 =====
        btn_layout = QHBoxLayout()
        
        self.btn_erase = QPushButton("擦除Flash")
        self.btn_erase.setEnabled(False)
        self.btn_erase.clicked.connect(self.erase_flash)
        btn_layout.addWidget(self.btn_erase)
        
        self.btn_upload = QPushButton("开始上传")
        self.btn_upload.setEnabled(False)
        self.btn_upload.clicked.connect(self.start_upload)
        btn_layout.addWidget(self.btn_upload)
        
        self.btn_stop = QPushButton("停止")
        self.btn_stop.setEnabled(False)
        self.btn_stop.clicked.connect(self.stop_upload)
        btn_layout.addWidget(self.btn_stop)
        
        btn_layout.addStretch()
        
        layout.addLayout(btn_layout)
        
        # ===== 进度区域 =====
        self.progress_bar = QProgressBar()
        self.progress_bar.setTextVisible(True)
        self.progress_bar.setFormat("%p% (%v/%m)")
        layout.addWidget(self.progress_bar)
        
        self.status_label = QLabel("就绪")
        self.status_label.setAlignment(Qt.AlignCenter)
        layout.addWidget(self.status_label)
        
        # ===== 日志区域 =====
        log_group = QGroupBox("日志")
        log_layout = QVBoxLayout(log_group)
        
        self.log_edit = QTextEdit()
        self.log_edit.setReadOnly(True)
        self.log_edit.setFont(QFont("Consolas", 9))
        log_layout.addWidget(self.log_edit)
        
        self.btn_clear = QPushButton("清空日志")
        self.btn_clear.clicked.connect(self.log_edit.clear)
        log_layout.addWidget(self.btn_clear)
        
        layout.addWidget(log_group, stretch=1)
        
        # 初始化状态
        self.is_connected = False
        self.update_ui_state()
        
    def apply_styles(self):
        """应用样式"""
        self.setStyleSheet("""
            QMainWindow {
                background-color: #f5f5f5;
            }
            QGroupBox {
                font-weight: bold;
                border: 1px solid #cccccc;
                border-radius: 5px;
                margin-top: 10px;
                padding-top: 10px;
            }
            QGroupBox::title {
                subcontrol-origin: margin;
                left: 10px;
                padding: 0 5px;
            }
            QPushButton {
                padding: 8px 20px;
                background-color: #0078d4;
                color: white;
                border: none;
                border-radius: 4px;
                font-weight: bold;
            }
            QPushButton:hover {
                background-color: #106ebe;
            }
            QPushButton:disabled {
                background-color: #cccccc;
            }
            QPushButton#danger {
                background-color: #d83b01;
            }
            QLineEdit {
                padding: 6px;
                border: 1px solid #cccccc;
                border-radius: 4px;
            }
            QProgressBar {
                border: 1px solid #cccccc;
                border-radius: 4px;
                text-align: center;
                height: 25px;
            }
            QProgressBar::chunk {
                background-color: #0078d4;
            }
            QTextEdit {
                border: 1px solid #cccccc;
                border-radius: 4px;
                background-color: #1e1e1e;
                color: #d4d4d4;
            }
        """)
        
    def browse_file(self):
        """浏览文件"""
        file_path, _ = QFileDialog.getOpenFileName(
            self,
            "选择bin文件",
            "",
            "Bin文件 (*.bin);;所有文件 (*.*)"
        )
        if file_path:
            self.file_edit.setText(file_path)
            self.log(f"已选择文件: {file_path}")
            self.update_ui_state()
            
    def toggle_connection(self):
        """切换连接状态"""
        if not self.is_connected:
            # 连接
            self.worker = CanWorker()
            self.worker.log_message.connect(self.log)

            if self.worker.initialize(
                self.combo_channel.currentText(),
                self.combo_baudrate.currentText()
            ):
                self.is_connected = True
                self.btn_connect.setText("断开")
                self.log("CAN已连接")
            else:
                self.worker = None
                QMessageBox.critical(self, "错误", "CAN连接失败")
        else:
            # 断开
            if self.worker:
                if self.worker.is_running:
                    self.worker.stop()
                    self.worker.wait(2000)
                self.worker.uninitialize()
                self.worker = None
            self.is_connected = False
            self._signals_connected = False  # 重置信号连接标记
            self.btn_connect.setText("连接")
            self.log("CAN已断开")

        self.update_ui_state()
        
    def update_ui_state(self):
        """更新UI状态"""
        has_file = bool(self.file_edit.text())
        
        self.combo_channel.setEnabled(not self.is_connected)
        self.combo_baudrate.setEnabled(not self.is_connected)
        self.btn_erase.setEnabled(self.is_connected)
        self.btn_upload.setEnabled(self.is_connected and has_file and 
                                   (not self.worker or not self.worker.is_running))
        self.btn_stop.setEnabled(self.worker is not None and self.worker.is_running)
        
    def erase_flash(self):
        """擦除Flash"""
        if not self.worker:
            return
            
        reply = QMessageBox.question(
            self,
            "确认",
            "确定要擦除外部Flash吗?",
            QMessageBox.Yes | QMessageBox.No
        )
        
        if reply == QMessageBox.Yes:
            self.status_label.setText("正在擦除Flash...")
            if self.worker.erase_flash():
                self.log("Flash擦除完成")
                QMessageBox.information(self, "完成", "Flash擦除成功")
            else:
                self.log("Flash擦除失败")
                QMessageBox.critical(self, "错误", "Flash擦除失败")
            self.status_label.setText("就绪")
            
    def start_upload(self):
        """开始上传"""
        if not self.worker:
            return

        # 检查是否正在运行
        if self.worker.is_running:
            self.log("传输已在进行中")
            return

        self.worker.set_file(self.file_edit.text())

        # 只在第一次连接信号,避免重复连接
        if not self._signals_connected:
            self.worker.progress_updated.connect(self.on_progress)
            self.worker.status_changed.connect(self.on_status)
            self.worker.finished_signal.connect(self.on_finished)
            self.worker.log_message.connect(self.log)
            self._signals_connected = True

        self.progress_bar.setMaximum(100)
        self.progress_bar.setValue(0)
        self.worker.start()
        self.update_ui_state()
        
    def stop_upload(self):
        """停止上传"""
        if self.worker and self.worker.is_running:
            self.worker.stop()
            
    def on_progress(self, current: int, total: int):
        """进度更新"""
        self.progress_bar.setMaximum(total)
        self.progress_bar.setValue(current)
        
    def on_status(self, msg: str):
        """状态更新"""
        self.status_label.setText(msg)
        
    def on_finished(self, success: bool, msg: str):
        """完成回调"""
        self.update_ui_state()
        self.status_label.setText("就绪" if success else "失败")
        
        if success:
            self.log(f"✓ {msg}")
            QMessageBox.information(self, "完成", msg)
        else:
            self.log(f"✗ {msg}")
            QMessageBox.critical(self, "错误", msg)
            
    def log(self, msg: str):
        """添加日志"""
        timestamp = time.strftime("%H:%M:%S")
        self.log_edit.append(f"[{timestamp}] {msg}")
        
    def closeEvent(self, event):
        """关闭事件"""
        if self.worker:
            if self.worker.is_running:
                self.worker.stop()
                self.worker.wait(2000)
            self.worker.uninitialize()
            self.worker = None
        event.accept()

    def __del__(self):
        """析构函数"""
        if hasattr(self, 'worker') and self.worker:
            if self.worker.is_running:
                self.worker.stop()
                self.worker.wait(1000)
            self.worker.uninitialize()

def main():
    app = QApplication(sys.argv)
    app.setStyle('Fusion')
    
    window = MainWindow()
    window.show()
    
    sys.exit(app.exec())

if __name__ == "__main__":
    main()

Q:程序目前均可使用,脚本因测试使用,遂功能简单,程序使用包包回传,所以烧录事件慢,若修改注意有512K限制

三、资料

暂时无法在飞书文档外展示此内容

暂时无法在飞书文档外展示此内容

暂时无法在飞书文档外展示此内容

Q&A:

1.

目前需注意初始化中这里设置的是D类功放,设置D类运放需打开AMODE命令的位1(7-0)

必须置位,才可使用D类运放,

D类功放使用信号位数字信号,类似PWM波

AB类功放 为模拟信号,需线路中添加电容

2.SpeechLSIUtility3使用

需注意这些地方,左侧勾选,音频IC才会知道

右边为设置生成的bin文件大小,需大于音频文件大小,小于flash容量

点击option选项

需注意是否使用的是音频IC内部RC时钟,以及D类运放,在此页面也可设置

串行访问密码,默认0xFF

posted on 2026-03-27 13:24  li5920o  阅读(2)  评论(0)    收藏  举报

导航