音频处理: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, ¶m[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, ¶m, 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, ¶m, 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, ¶m, 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, ¶m, 1);
}
static bool ml28860_SAFE(void *dev, uint8_t param)
{
ML28860_Driver *driver = (ML28860_Driver *)dev;
return ml28860_send_cmd(&driver->hw, ML28860_SAFE, ¶m, 1);
}
static bool ml28860_OutStat(void *dev, uint8_t param)
{
ML28860_Driver *driver = (ML28860_Driver *)dev;
return ml28860_send_cmd(&driver->hw, ML28860_OUTSTAT, ¶m, 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
浙公网安备 33010602011771号