DFU 流程
Software update handling 通过 READ_DFU 和 WRITE_DFU functions 完成。
必须使用以下 interrupt lines 推导 state:
- DFU_START_IRQ
- DFU_PACKET_IRQ
- DFU_STOP_IRQ
每当 software update triggered 时,DFU_START_IRQ 会变为 high。
然后 fixture 必须准备接收 firmware,并返回以下 response 之一:
- 0x0: DFU_ACCEPT
- 0x1 - 0xFF: DFU_ABORT
Fixture 使用 WRITE_DFU function response。Response timeout 为 20 seconds。写入 response 后会 disable DFU_START_IRQ flag。
接受 DFU 后,Vision Controller 开始发送 firmware packets。每个 packet 都有唯一 id,并向上计数。长度可变,最大 200 Byte。Default length 为 128 Byte。packetNr 使用 Big Endian,不是 Little Endian。
| data[0] | data[1] | data[2] | data[3] | ... | data[202] |
|---|---|---|---|---|---|
| packetNr | packetNr | data[0] | dmx[1] | ... | dmx[200] |
读取 packet 后,下一个 packet 会自动传输,不需要额外 response。
如果发生 error,可以通过写入大于 0 的 response(0x1 - 0xFF)提前 abort DFU。
如果 30 seconds 内没有读取 packet,DFU 会 abort。第一个 packet 到达前最多可能需要 30 seconds。请确保不要在该 timeout 之前 abort。
收到最后一个 packet 后,DFU_STOP_IRQ flag 会变为 high,用于表示所有 packets 已传输完成,DFU transmission 已结束。
此时 fixture 必须发送最后一个 response。Response 应为以下之一:
- 0x0: DFU_SUCCESS
- 0x1: DFU_ERROR_FLASH_ERASE
- 0x2: DFU_ERROR_NOT_IN_PROGRESS
- 0x3: DFU_ERROR_CORRUPTED_HEADER
- 0x4: DFU_ERROR_CORRUPTED_IMAGE
- 0x5: DFU_ERROR_INVALID_IMAGE_VERSION
- 0x6: DFU_ERROR_NO_PREAMBLE
- 0x7: DFU_ERROR_INVALID_FIXTURE_ID
- 0x8: DFU_ERROR_UNKNOWN
- 0x9: DFU_ERROR_SIGNATURE_NECESSARY_NOT_FOUND
- 0x0A: DFU_ERROR_PACKET_TIMEOUT
- 0x0B - 0xFF: Reserved

下面是一个 pseudo code example,用于在 application firmware 中接收 data,而不是像 example 那样使用 boot-loader 接收 data:
#define DFU_BOOT_DELAY_MS 1000
#define MAX_FIRMWARE_SIZE (256 * 1024u) // example
typedef enum
{
DFU_STATE_IDLE = 0,
DFU_STATE_RECEIVING,
DFU_STATE_ERROR,
DFU_STATE_BOOT_PENDING
} dfu_state_t;
static dfu_state_t dfuState = DFU_STATE_IDLE;
static uint32_t dfuDataCounter = 0;
static uint16_t dfuPacketCounter = 0;
static uint8_t firmwareData[MAX_FIRMWARE_SIZE]; // Just dummy, use flash to write here
static void Dfu_Reset(void)
{
dfuState = DFU_STATE_IDLE;
dfuDataCounter = 0;
dfuPacketCounter = 0;
}
static void Dfu_Abort(uint8_t errorCode)
{
vision_writeDfu(errorCode);
Dfu_Reset();
}
static bool Dfu_StoragePrepare(void)
{
// Erase temporary DFU flash area here.
// Return false if erase failed.
return true;
}
static bool Dfu_StorageWrite(uint32_t offset, const uint8_t *data, uint16_t length)
{
// Write data to temporary DFU flash area.
// Handle flash alignment here.
// Return false if flash write failed.
(void)offset;
(void)data;
(void)length;
return true;
}
static bool Dfu_CheckFirmwareDataValid(uint32_t imageSize)
{
if (imageSize == 0)
{
return false;
}
if (imageSize > MAX_FIRMWARE_SIZE)
{
return false;
}
// Recommended checks:
// - image header valid
// - target device / hardware ID valid
// - firmware size valid
// - CRC/hash valid
// - version valid
//
// Final signature/security validation should also be done by bootloader.
return true;
}
static void Dfu_StartBootloaderTimer(void)
{
// Start one-shot timer, for example 100 ms.
// After timeout call Dfu_BootloaderTimerElapsed().
}
static void Dfu_SetBootloaderPendingFlag(void)
{
// Store persistent bootloader request.
// Options:
// - backup register
// - retained RAM magic value
// - flash flag
//
// Bootloader must check this after reset.
}
static void Dfu_BootloaderTimerElapsed(void)
{
if (dfuState != DFU_STATE_BOOT_PENDING)
{
return;
}
Dfu_SetBootloaderPendingFlag();
__disable_irq();
// Optional:
// deinit peripherals
// flush logs
// stop radio/SPI/UART cleanly
NVIC_SystemReset();
}
// Callback Implementation SPI Library ---------------------------------------------------------------------
static void DfuFlagReceivedCallback(vision_dfu_flag_t flag)
{
if(flag == VISION_DFUFLAG_STOP)
{
if (dfuState != DFU_STATE_RECEIVING)
{
Dfu_Abort(DFU_ERROR_NOT_IN_PROGRESS);
return;
}
if (!Dfu_CheckFirmwareDataValid(dfuDataCounter))
{
Dfu_Abort(DFU_ERROR_CORRUPTED_IMAGE);
return;
}
dfuState = DFU_STATE_BOOT_PENDING;
// Tell sender that application-side receive/validation was okay.
vision_writeDfu(DFU_SUCCESS);
// Do not reset state here.
// Wait until the response had enough time to leave the device.
Dfu_StartBootloaderTimer();
return;
}
else if(flag == VISION_DFUFLAG_START)
{
Dfu_Reset();
if (!Dfu_StoragePrepare())
{
Dfu_Abort(DFU_ERROR_FLASH);
return;
}
dfuState = DFU_STATE_RECEIVING;
// State is valid before response is sent.
vision_writeDfu(DFU_SUCCESS);
return;
}
}
static void DfuDataReceivedCallback(uint16_t packetNr,const uint8_t* buf, uint16_t length)
{
if (dfuState != DFU_STATE_RECEIVING)
{
vision_writeDfu(DFU_ERROR_NOT_IN_PROGRESS);
return;
}
if (buf == NULL || length == 0)
{
Dfu_Abort(DFU_ERROR_INVALID_PACKET);
return;
}
if (packetNr != dfuPacketCounter)
{
Dfu_Abort(DFU_ERROR_WRONG_PACKETNR);
return;
}
if ((uint32_t)length > (MAX_FIRMWARE_SIZE - dfuDataCounter))
{
Dfu_Abort(DFU_ERROR_IMAGE_TOO_LARGE);
return;
}
if (!Dfu_StorageWrite(dfuDataCounter, buf, length))
{
Dfu_Abort(DFU_ERROR_FLASH);
return;
}
dfuDataCounter += length;
dfuPacketCounter++;
}