【OTA专题】15 实现App后台无感下载固件

目录

软件架构

线程分配:

OTA升级协议定义:​编辑

OTA升级流程图:

代码编写:

OTA状态机搭建:

OTA状态机:

SWC_OTA.h文件定义枚举

宏与变量

搭建ota状态机线程

按键扫描函数

软复位函数

W25Q64:下载(搬运)数据到外部flash线程搭建

资源与任务函数:

串口空闲中断

Ymodem.c接收函数解析

ymodem.h

[Ymodem_Receive() 接收Ymodem协议数据](#Ymodem_Receive() 接收Ymodem协议数据)

[Receive_Packet() 接收一包数据](#Receive_Packet() 接收一包数据)

[Receive_Bytes() 与 Send_Byte()](#Receive_Bytes() 与 Send_Byte())

ATC24C02功能搭建:

地址定义:

所遇的bug:

描述:

解决方法:


软件架构

线程分配:

  • 线程1:Default_taskHandle(应用线程)

  • 线程2:OTA_taskHandle(OTA状态管理线程)

  • 线程3:DownloadAppData_taskHandle(下载数据到w25q中)

OTA升级协议定义:

OTA升级流程图:

代码编写:

OTA状态机搭建:

OTA状态机:

SWC_OTA.h文件定义枚举
复制代码
#define REC_MAX_NUM 1030        // UART receive max num of bytes
                                // for trigger IDLE irq, the true REC_MAX_NUM is 1029

typedef enum {
    WAIT_REQ_DOWNLOAD,
    OTA_DOWNLOADING,
    WAIT_REQ_UPDATE,
    OTA_END,
}E_Ota_State_t;
宏与变量
复制代码
/* Define -----------------------------------------------*/
#define UPPER_MACHINE_UART    (&huart1)   // 与上位机通信的串口

/* Variable ---------------------------------------------*/
/* state machine thread -------------*/
static E_Ota_State_t s_g_OTA_State = WAIT_REQ_DOWNLOAD;
static uint8_t s_u8_OTA_Cmd[4] = { 0 }; // Using IDLE irq receive cmd from upper computer

/* Ymodem */
static int32_t s_g_file_size = 0;            // receive file size
uint8_t g_u8_YmodemRec_A[REC_MAX_NUM] = { 0 };      // receive data buffer
uint8_t g_u8_YmodemRec_B[REC_MAX_NUM] = { 0 };      // receive data buffer
extern int32_t packet_length;
搭建ota状态机线程
cpp 复制代码
/* OTA state machine */
QueueHandle_t Q_Ymodem_Rec_Len = NULL;  // Queue for Ymodem receive length
osThreadId_t ota_state_machine_thread_Handle;
const osThreadAttr_t ota_state_machine_thread_attributes = {
  .name = "ota_state_machine_thread",
  .stack_size = 128 * 4,
  .priority = (osPriority_t)osPriorityNormal1,
};

void ota_state_machine_thread(void* argument)
{
    /* Variable */
    uint8_t u8_rec_length = 0;
    uint8_t u8_Ackcmd[3] = { 0x44,0x55,0x66 };
    // create queue
    Q_Ymodem_Rec_Len = xQueueCreate(1, sizeof(uint16_t));

    uint8_t t_u8_OTAstate = 0x00;   // 0x00:没有APP更新
                                    // 0x11:APP数据下载过程中
                                    // 0x22:APP固件下载搬运完成,请求更新
    // 清除eeprom的OTA状态
    ee_WriteBytes(&t_u8_OTAstate, 0x00, 1); // 没有APP更新
    for (;;)
    {
        switch (s_g_OTA_State)
        {
        case WAIT_REQ_DOWNLOAD:
            // 阻塞式接收串口命令
            HAL_UARTEx_ReceiveToIdle_DMA(UPPER_MACHINE_UART, s_u8_OTA_Cmd, 4);
            xQueueReceive(Q_Ymodem_Rec_Len, &u8_rec_length, portMAX_DELAY);
            /* 校验数据长度 */
            if (3 == u8_rec_length)
            {
                /* 判断是否符合命令内容 */
                if (0x11 == s_u8_OTA_Cmd[0] && 0x22 == s_u8_OTA_Cmd[1] && 0x33 == s_u8_OTA_Cmd[2])
                {
                    /* 切换状态 */
                    s_g_OTA_State = OTA_DOWNLOADING;
                    // eeprom内写入新的OTA状态
                    t_u8_OTAstate = 0x11;
                    ee_WriteBytes(&t_u8_OTAstate, 0x00, 1); // APP数据下载过程中
                    /* 创建线程以及相关队列 */
                    // 创建线程
                    DownloadAppDataTaskHandle = osThreadNew(DownloadAppData_task, NULL, &DownloadAppDataTaskattributes);
                    // 创建队列
                    Q_AppDataBuffer = xQueueCreate(2, sizeof(uint8_t*));
                    // 创建互斥锁
                    Semaphore_ExtFlashState = xSemaphoreCreateMutex();
                }
                else
                {
                    for (uint8_t i = 0;i < 4;i++) {
                        /* 清掉数据 */
                        s_u8_OTA_Cmd[i] = 0;
                    }
                }
            }
            else
            {
                for (uint8_t i = 0;i < 4;i++) {
                    /* 清掉数据 */
                    s_u8_OTA_Cmd[i] = 0;
                }
            }
            break;
        case OTA_DOWNLOADING:
            /* 进入固件下载,并校验下载状态 */
            s_g_file_size = Ymodem_Receive(g_u8_YmodemRec_A, g_u8_YmodemRec_B);
            if (s_g_file_size > 0)
            {
                log_i("file_size = [%d]", s_g_file_size);
                /* 切换状态,发送应答告诉上位机数据下载完毕 */
                s_g_OTA_State = WAIT_REQ_UPDATE;
                HAL_UART_Transmit(UPPER_MACHINE_UART, u8_Ackcmd, 3, 1000);
                
                // 拿互斥锁,保证w25q已经写入完成
                xSemaphoreTake(Semaphore_ExtFlashState, portMAX_DELAY);
                xSemaphoreGive(Semaphore_ExtFlashState);
                /* 最后将数据缓冲区中剩余的数据写入 */
                W25Q64_WriteData_End();

                /* 清除一切生成的资源(队列、线程),后面用不到了 */
                // 删除线程
                vTaskDelete(DownloadAppDataTaskHandle);
                // 删除队列
                vQueueDelete(Q_AppDataBuffer);
                // 删除互斥量
                vSemaphoreDelete(Semaphore_ExtFlashState);

                // eeprom内写入新的OTA状态
                t_u8_OTAstate = 0x22;
                ee_WriteBytes(&t_u8_OTAstate, 0x00, 1); // APP固件下载完成,请求更新
                // eeprom内写入文件大小
                ee_WriteBytes((uint8_t*)(&s_g_file_size), 0x01, 4); // 写入4字节的文件大小

                
                #if 0   // test eeprom
                osDelay(1);
                uint8_t t_u8_readstate = 0;
                int32_t t_32_filesize = 0;
                if (1 == ee_ReadBytes(&t_u8_readstate, 0x00, 1)) {
                    //log_i("ee_ReadBytes success: OTAstate = [%x]", t_u8_readstate);
                    HAL_UART_Transmit(&huart1,&t_u8_readstate,1,1000);
                }
                if (1 == ee_ReadBytes((uint8_t*)(&t_32_filesize), 0x01, 4)) {
                    //log_i("ee_ReadBytes success: new filesize = [%d]", t_32_filesize);
                    HAL_UART_Transmit(&huart1,(uint8_t*)(&t_32_filesize),4,1000);
                }
                #endif

            }
            else
            {
                log_e("error: Ymodem_Receive failed! file_size = [%d]", s_g_file_size);
                s_g_file_size = 0;
                /* 切换状态,清除一切生成的资源(队列、线程) */
                s_g_OTA_State = WAIT_REQ_DOWNLOAD;
                // 删除线程
                vTaskDelete(DownloadAppDataTaskHandle);
                // 删除队列
                vQueueDelete(Q_AppDataBuffer);
                // 删除互斥量
                vSemaphoreDelete(Semaphore_ExtFlashState);
            }
            break;
        case WAIT_REQ_UPDATE:
            // 阻塞式接收串口命令
            HAL_UARTEx_ReceiveToIdle_DMA(UPPER_MACHINE_UART, s_u8_OTA_Cmd, 4);
            xQueueReceive(Q_Ymodem_Rec_Len, &u8_rec_length, portMAX_DELAY);
            /* 校验数据长度 */
            if (3 == u8_rec_length)
            {
                /* 判断是否符合命令内容 */
                if (0x77 == s_u8_OTA_Cmd[0] && 0x88 == s_u8_OTA_Cmd[1] && 0x99 == s_u8_OTA_Cmd[2])
                {
                    /* 切换状态 */
                    s_g_OTA_State = OTA_END;
                }
                else
                {
                    for (uint8_t i = 0;i < 4;i++) {
                        /* 清掉数据 */
                        s_u8_OTA_Cmd[i] = 0;
                    }
                }
            }
            else
            {
                for (uint8_t i = 0;i < 4;i++) {
                    /* 清掉数据 */
                    s_u8_OTA_Cmd[i] = 0;
                }
            }
            break;
        case OTA_END:
            if (1 == key_Scan())
            {
                /* 执行软复位 */
                SoftReset();
            }
            else
            {
                /* 切换状态,如果重启则会进入bootloader升级 */
                s_g_OTA_State = WAIT_REQ_DOWNLOAD;
            }
            break;
   
        default:
            break;
        }
    }
}
按键扫描函数
cpp 复制代码
/**
 * @brief   Scan key press
 *
 * 功能说明:检测按键20s内是否被按下
 *
 * @retval  1:  key has been pressed
 *          -1: time out, key has not been pressed
 */
static int8_t key_Scan()
{
    uint16_t Key_Scan_TimeCnt = 0;
    // 检测20s,20s内是否按下按键
    for (Key_Scan_TimeCnt = 0;Key_Scan_TimeCnt < 400;Key_Scan_TimeCnt++)
    {
        if (GPIO_PIN_RESET == HAL_GPIO_ReadPin(Key_GPIO_Port, Key_Pin))
        {
            osDelay(20);
            if (GPIO_PIN_RESET == HAL_GPIO_ReadPin(Key_GPIO_Port, Key_Pin))
            {
                /* 判断为按下 */
                return 1;
            }
        }
        osDelay(50);
    }
    /* 判断为超时 */
    return -1;
}
软复位函数
cpp 复制代码
/**
 * @brief   System SoftReset
 *
 * 功能说明:系统软复位
 *
 * @retval  None
 */
void SoftReset(void)
{
    // __set_FAULTMASK(1);
    NVIC_SystemReset();
}

W25Q64:下载(搬运)数据到外部flash线程搭建

需要注意,在使用之前需要先初始化W25Q64:

cpp 复制代码
#include "w25qxx_Handler.h"
// ...
int main(){
    // ...
    W25Q64_Init();
    // ...
}

资源与任务函数:

cpp 复制代码
/* Download data from data buffer to external flash */
QueueHandle_t Q_AppDataBuffer = NULL;
SemaphoreHandle_t Semaphore_ExtFlashState = NULL;
osThreadId_t DownloadAppDataTaskHandle;
const osThreadAttr_t DownloadAppDataTaskattributes = {
  .name = "DownloadAppData_Task",
  .stack_size = 128 * 4,
  .priority = (osPriority_t)osPriorityNormal1,
};

/**
 * @brief   DownloadAppData_task thread
 *
 * 功能说明:将接收到的APP数据搬运到外部flash中
 *
 * @param[in]   argument:任务参数
 * @retval  none
 */
void DownloadAppData_task(void* argument)
{
    uint8_t* pu8_buffer = NULL;
    int32_t* p32_size = NULL;
    // 第一包数据接收的肯定是size
    xQueueReceive(Q_AppDataBuffer, &p32_size, portMAX_DELAY);
    // 需要擦除掉flash size对应的所有扇区
    // TODO

    // 释放互斥量
    xSemaphoreGive(Semaphore_ExtFlashState);
    for (;;)
    {
        // 后续的数据包发送的队列消息就是buffer地址
        xQueueReceive(Q_AppDataBuffer, &pu8_buffer, portMAX_DELAY);
        // 通过互斥量锁住buffer
        xSemaphoreTake(Semaphore_ExtFlashState, portMAX_DELAY);
        if (pu8_buffer == NULL)
        {
            continue;
        }
        // 执行写入W25q的逻辑
        W25Q64_WriteData(pu8_buffer, (uint32_t)packet_length);
        xSemaphoreGive(Semaphore_ExtFlashState);
    }
}

需要注意的是,该线程在MCU启动后并不存在,而是在OTA状态机线程在串口中接收到上位机下载固件命令后,才创建该线程,同时开始接收固件数据,接收一包,写入一包,使用串口空闲中断来进行队列通知:

串口空闲中断

cpp 复制代码
/**
  * @brief  Reception Event Callback (Rx event notification called after use of advanced reception service).
  * @param  huart UART handle
  * @param  Size  Number of data available in application reception buffer (indicates a position in
  *               reception buffer until which, data are available)
  * @retval None
  */
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef* huart, uint16_t Size)
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    // 判断是串口1
    if (huart->Instance == USART1)
    {
        // 确定是串口空闲中断触发的回调函数,获取已经传输值
        if (1 == __HAL_UART_GET_FLAG(huart, UART_FLAG_IDLE))
        {
            // 通过队列将接收长度发送到 Q_Ymodem_Rec_Len 队列
            if (pdTRUE == xQueueSendFromISR(Q_Ymodem_Rec_Len, &Size, 0))
            {
                xHigherPriorityTaskWoken = pdTRUE;  // 发送完毕后我们进行手动切换任务
            }
            HAL_UART_DMAStop(huart);
        }
        // DMA传输完成后的操作
        if (REC_MAX_NUM == Size)  // DMA全满中断
        {
            // 使用空闲中断完成任务,不应该进入全满中断
            log_e("error: DMA_Cplt_Callback!");
        }

        // 触发任务切换
        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
    }
}

Ymodem.c接收函数解析

ymodem.h

cpp 复制代码
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef _YMODEM_H_
#define _YMODEM_H_

/* Includes ------------------------------------------------------------------*/
#include "stdint.h"
/* Exported types ------------------------------------------------------------*/
/* Exported constants --------------------------------------------------------*/
#define PACKET_SEQNO_INDEX      (1)
#define PACKET_SEQNO_COMP_INDEX (2)

#define PACKET_HEADER           (3)
#define PACKET_TRAILER          (2)
#define PACKET_OVERHEAD         (PACKET_HEADER + PACKET_TRAILER)
#define PACKET_SIZE             (128)
#define PACKET_1K_SIZE          (1024)

#define FILE_NAME_LENGTH        (256)
#define FILE_SIZE_LENGTH        (16)

#define SOH                     (0x01)  /* start of 128-byte data packet */
#define STX                     (0x02)  /* start of 1024-byte data packet */
#define EOT                     (0x04)  /* end of transmission 结束传输 */
#define ACK                     (0x06)  /* acknowledge 确认*/
#define NAK                     (0x15)  /* negative acknowledge 无确认 */
#define CA                      (0x18)  /* two of these in succession aborts transfer 其中两个相继中止传输 */
#define CRC16                   (0x43)  /* 'C' == 0x43, request 16-bit CRC 请求CRC */

#define ABORT1                  (0x41)  /* 'A' == 0x41, abort by user */
#define ABORT2                  (0x61)  /* 'a' == 0x61, abort by user */

#define NAK_TIMEOUT             (0x100000)//无确认超时时间
#define MAX_ERRORS              (3)//最大错误数量

/* User */
#define YMODEM_UART             (&huart1)   /* 收发Ymodem协议的串口 */

/* Exported macro ------------------------------------------------------------*/

/* Exported functions ------------------------------------------------------- */
int32_t Ymodem_Receive(uint8_t* buf1, uint8_t* buf2);
uint8_t Ymodem_Transmit (uint8_t *,const  uint8_t* , uint32_t );
uint16_t UpdateCRC16(uint16_t crcIn, uint8_t byte);
uint16_t Cal_CRC16(const uint8_t* data, uint32_t size);
uint8_t CalChecksum(const uint8_t* data, uint32_t size);
int32_t Ymodem_CheckResponse(uint8_t c);
void Ymodem_PrepareIntialPacket(uint8_t *data, const uint8_t* fileName, uint32_t *length);
void Ymodem_PreparePacket(uint8_t *SourceBuf, uint8_t *data, uint8_t pktNo, uint32_t sizeBlk);
void Ymodem_SendPacket(uint8_t *data, uint16_t length);

Ymodem_Receive() 接收Ymodem协议数据

cpp 复制代码
/**
  * @brief  Receive a file using the ymodem protocol
  * @param  buf: Address of the first byte
  * @retval The size of the file
  */
uint8_t file_size[FILE_SIZE_LENGTH], * file_ptr, * buf_ptr;
int32_t i, j, packet_length, session_done, file_done, packets_received, errors, session_begin, size = 0;
int32_t Ymodem_Receive(uint8_t* buf1, uint8_t* buf2)
{
    uint8_t* packet_data = buf1;
    // FlashDestination = BACKUP_APP_FLASH_ADDR;  // TODO

    for (session_done = 0, errors = 0, session_begin = 0; ;)//初始化变量,进入循环
    {
        for (packets_received = 0, file_done = 0; ;)
        {
        // 接收一整包数据到数据缓冲区
        switch (Receive_Packet(packet_data, &packet_length, 1000))
        {
        // 通过Receive_Packet的返回值判断包的状态
        case 0:
            errors = 0;
            switch (packet_length)  // 通过Receive_Packet()返回的包长来判断包的状态
            {
            /* Abort by sender */
            case - 1:
                Send_Byte(ACK);
                return 0;
            /* End of transmission */
            case 0:
                Send_Byte(ACK);
                file_done = 1;
                break;
            /* Normal packet */
            default:
                if ((packet_data[PACKET_SEQNO_INDEX] & 0xff) != (packets_received & 0xff))
                {
                    Send_Byte(NAK);
                }
                else
                {
                    if (packets_received == 0)  // 文件的第一包数据(文件的信息)
                    {
                        /* Filename packet */
                        if (packet_data[PACKET_HEADER] != 0)
                        {
                            /* Filename packet has valid data */
                            for (i = 0, file_ptr = packet_data + PACKET_HEADER; (*file_ptr != 0) && (i < FILE_NAME_LENGTH);)
                            {
                            file_name[i++] = *file_ptr++;
                            }
                            file_name[i++] = '\0';
                            for (i = 0, file_ptr ++; (*file_ptr != ' ') && (i < FILE_SIZE_LENGTH);)
                            {
                            file_size[i++] = *file_ptr++;
                            }
                            file_size[i++] = '\0';
                            Str2Int(file_size, &size);
                            // 通知任务,需要擦除某些w25q的扇区,根据size的大小决定
                            int32_t* p32_size = &size;
                            if (pdTRUE != xQueueSend(Q_AppDataBuffer, &p32_size, 0)) {
                                log_e("error: xQueueSend Q_AppDataBuffer failed! packets_received = [%d]", packets_received);
                            }
                            // 切换buffer
                            if (packet_data == buf1) {
                                packet_data = buf2;
                            }
                            else {
                                packet_data = buf1;
                            }
                            Send_Byte(ACK);
                            Send_Byte(CRC16);
                        }
                        /* Filename packet is empty, end session */
                        else
                        {
                            Send_Byte(ACK);
                            file_done = 1;
                            session_done = 1;
                            break;
                        }
                    }
                    /* Data packet */
                    else    // 文件的后续数据包(真正的数据包)
                    {
                        // 通知任务
                        uint8_t* data_ptr = packet_data + PACKET_HEADER;
                        if (pdTRUE != xQueueSend(Q_AppDataBuffer, &data_ptr, 0)) 
                        {
                            log_e("error: xQueueSend Q_AppDataBuffer failed! packets_received = [%d]", packets_received);
                        }
                        // 获取互斥量,防止下一个buffer的数据还没被w25q下载线程处理完
                        // 相当于进行一个任务间的同步
                        xSemaphoreTake(Semaphore_ExtFlashState, portMAX_DELAY);
                        xSemaphoreGive(Semaphore_ExtFlashState);
                        // 切换buffer
                        if (packet_data == buf1) {
                            packet_data = buf2;
                        }
                        else {
                            packet_data = buf1;
                        }
                        Send_Byte(ACK);
                    }
                    packets_received ++;
                    session_begin = 1;
                }
            }
            break;
        case 1:
            Send_Byte(CA);
            Send_Byte(CA);
            return -3;
        default:
            if (session_begin > 0)
            {
                errors++;
                // log_e("errors++");
            }
            if (errors > MAX_ERRORS)
            {
                Send_Byte(CA);
                Send_Byte(CA);
                log_e("errors > MAX_ERRORS");
                return 0;
            }
            Send_Byte(CRC16);
            break;
        }
        if (file_done != 0)
        {
            break;
        }
        }
        if (session_done != 0)
        {
            break;
        }
    }
    return (int32_t)size;       // 返回文件的长度
}

Receive_Packet() 接收一包数据

cpp 复制代码
/**
  * @brief  Receive a packet from sender
  * @param  data
  * @param  length  0: end of transmission
  *                 -1: abort by sender
  *                 >0: packet length
  * @param  timeout
  * @retval 0: normally return
  *        -1: timeout or packet error
  *         1: abort by user
  */
static int32_t Receive_Packet (uint8_t *data, int32_t *length, uint32_t timeout)
{
    uint16_t i, packet_size;
    *length = 0;
    // 接收一整包数据
    if (Receive_Bytes(data, REC_MAX_NUM, timeout) != 0)
    {
        return -1;
    }

    switch (*data)    // 检测第一个字节
    {
    case SOH:
        packet_size = PACKET_SIZE;
        break;
    case STX:
        packet_size = PACKET_1K_SIZE;
        break;
    case EOT:
        return 0;
    case CA:    // 如果第一个字节是CA,则检测下一个字节,如果也是CA则终止传输
        // if ((Receive_Byte(&c, timeout) == 0) && (c == CA))
        if (*(data + 1) == CA)
        {
            *length = -1;
            return 0;
        }
        else
        {
            log_e("Receive_Packet error");
            return -1;
            
        }
    case ABORT1:
    case ABORT2:
        return 1;
    default:
        log_e("Receive_Packet error");
        return -1;
    }
  
    if (data[PACKET_SEQNO_INDEX] != ((data[PACKET_SEQNO_COMP_INDEX] ^ 0xff) & 0xff))
    {
        return -1;
    }
    // 校验搬运长度和接收长度是否一致
    if (sg_u16_uart_rec_len != (packet_size + PACKET_OVERHEAD))
    {
        return -1;
    }
    *length = packet_size;    // 通过参数返回包长(128 or 1024)
    return 0;
}

Receive_Bytes() 与 Send_Byte()

cpp 复制代码
/**
  * @brief  Receive bytes from sender
  * @param  data_buffer: Character
  * @param  length: length of receive bytes
  * @param  timeout: Timeout
  * @retval 0: Byte received
  *         -1: Timeout
  */
static int32_t Receive_Bytes(uint8_t *data_buffer, uint16_t length, uint32_t timeout)
{
    // 接收指定长度的数据到缓冲区
    HAL_UARTEx_ReceiveToIdle_DMA(YMODEM_UART, data_buffer, length);
    // check the queue is valid
    if (Q_Ymodem_Rec_Len == NULL) {
        log_e("error: Queue Q_Ymodem_Rec_Len is NULL!");
        return -1;
    }
    // 等待空闲中断发送消息队列
    if (pdTRUE == xQueueReceive(Q_Ymodem_Rec_Len, &sg_u16_uart_rec_len, timeout))
    {
        return 0;
    }
    return -1;
}

/**
  * @brief  Send a byte
  * @param  c: Character
  * @retval 0: Byte sent
  */
static uint32_t Send_Byte (uint8_t c)
{
    HAL_UART_Transmit(YMODEM_UART, &c, 1, 10);
  return 0;
}

ATC24C02功能搭建:

使用前需要先在 main() 中确认应答:

地址定义:

先把硬件接好

AT24c MCU

VCC->3.3V

GND->GND

SCL->PB8

SDA->PB9

先添加头文件#include "AT24Cxx_Driver.h"

下载完成后发送相关信息

所遇的bug:

描述:

读取外部eeprom的相关地址的文件长度数据时,串口打印的数据全为0,但是数据的长度不可能为0

原因: AT24C02 是 I2C 接口的 EEPROM,其写入操作需要一定的时间(典型 5ms,最大 10 ms ,写入过程中无法响应读操作。如果在 ee_WriteBytes 后立即调用 ee_ReadBytes,此时 EEPROM 可能还在执行写入,读操作会失败,返回默认的 0。

解决方法:

加入10ms的延时

相关推荐
TEC_INO2 小时前
STM32_10:SPI
stm32·单片机·嵌入式硬件
polarislove02142 小时前
10.3[ADC]采样时间和转换时间-嵌入式铁头山羊STM32笔记
笔记·stm32·嵌入式硬件
创思通信2 小时前
STM32L151RCT6 BC20 采集温湿度DHT11 采集GPS定位 和ADC发送到最新版本ONENET物联网开放平台
stm32·嵌入式硬件·物联网
__万波__2 小时前
STM32建立完全空白的工程
stm32·单片机·嵌入式硬件
松涛和鸣2 小时前
51、51单片机
c语言·网络·单片机·嵌入式硬件·tcp/ip·51单片机
张海森-1688203 小时前
608_demo例子开红外及ir_cut是怎么做的呢
单片机
LongRunning3 小时前
【IDE】KEIL IAR GCC 编译信息
单片机
麒qiqi4 小时前
51 单片机入门详解:从基础概念到实战开发
单片机·嵌入式硬件
兆龙电子单片机设计4 小时前
【STM32项目开源】STM32单片机充电桩安全监测系统
stm32·单片机·物联网·开源·毕业设计