RL78系列BootLoader例程详解

RL78系列BootLoader例程详解

一、RL78 BootLoader基本原理

RL78系列MCU支持双Boot Cluster架构,这是实现BootLoader的关键特性:

  1. 双Boot Cluster:RL78有两个Boot Cluster(通常各4KB),可通过boot swap函数切换
  2. 自编程库:瑞萨提供Flash Self-Programming Library,支持运行时擦写Flash
  3. 内存映射:Boot Cluster 0地址为0x0000-0x0FFF,Boot Cluster 1地址为0x1000-0x1FFF
  4. OTA流程:通过boot swap实现应用程序和BootLoader的切换

二、工程结构与内存布局

2.1 BootLoader工程配置(CS+ for CCRL)

// bootloader.c - 主程序框架
#include "iodefine.h"
#include "flash_api.h"

// 通信协议定义
#define CMD_HANDSHAKE     0x6F
#define CMD_WRITE_MEM     0x02
#define CMD_ERASE_MEM     0x03
#define CMD_VERIFY_MEM    0x04
#define CMD_RUN_APP       0x1F

// 内存地址定义
#define BOOTLOADER_SIZE   0x1000      // 4KB
#define APP_START_ADDR    0x2000      // 应用程序起始地址
#define FLASH_PAGE_SIZE   0x200       // 512字节/页

// 全局变量
volatile uint8_t rx_buffer[256];
volatile uint8_t tx_buffer[256];
volatile uint16_t rx_index = 0;
volatile uint8_t cmd_received = 0;

// UART初始化
void UART_Init(uint32_t baudrate) {
    // RL78G13 UART配置示例
    SAU0EN = 1;                     // 使能SAU0
    SPS0 = 0x00;                    // 时钟选择
    SMR00 = 0x0020;                 // 8位数据,无校验
    SCR00 = 0x0087;                 // 发送接收使能
    SDR00 = (uint16_t)((F_CLK / baudrate) - 1); // 波特率设置
    SIR01 = 0x0000;                 // 中断控制
    SO0 |= 0x01;                    // 输出使能
    SS0 |= 0x01;                    // 开始UART0操作
}

// Flash自编程库初始化
void Flash_Init(void) {
    // 初始化Flash自编程库
    FLASH_Init();
    
    // 设置操作频率
    FLASH_SetFrequency(F_CLK);
    
    // 解锁Flash操作
    FLASH_Unlock();
}

// 接收中断处理
#pragma interrupt r_uart0_receive
void r_uart0_receive(void) {
    uint8_t data = RXD0;
    
    if (rx_index < sizeof(rx_buffer)) {
        rx_buffer[rx_index++] = data;
        
        // 简单协议:以0x0D 0x0A结束
        if (rx_index >= 2 && 
            rx_buffer[rx_index-2] == 0x0D && 
            rx_buffer[rx_index-1] == 0x0A) {
            cmd_received = 1;
        }
    }
}

// 发送数据
void UART_Send(uint8_t *data, uint16_t len) {
    for (uint16_t i = 0; i < len; i++) {
        while (!(SSR00 & 0x20));    // 等待发送缓冲区空
        TXD0 = data[i];
    }
}

// 发送ACK响应
void Send_ACK(uint8_t cmd) {
    uint8_t ack[4] = {0xAA, cmd, 0x55, 0x0D};
    UART_Send(ack, 4);
}

// 发送NAK响应
void Send_NAK(uint8_t cmd) {
    uint8_t nak[4] = {0x55, cmd, 0xAA, 0x0D};
    UART_Send(nak, 4);
}

2.2 链接脚本配置(bootloader.lsl)

// RL78G13 BootLoader链接脚本
MEMORY {
    // Boot Cluster 0 (4KB)
    BOOT_ROM : origin = 0x0000, length = 0x1000
    
    // RAM区域
    RAM     : origin = 0xFDE0, length = 0x0200
    SADDR   : origin = 0xFFE0, length = 0x0020
}

SECTIONS {
    // 向量表重映射
    .vectors 0x0000 : {
        KEEP(*(.vectors))
    } > BOOT_ROM
    
    // 代码段
    .text 0x00D8 : {    // BootLoader入口地址
        *(.text)
        *(.text.*)
    } > BOOT_ROM
    
    // 只读数据
    .rodata : {
        *(.rodata)
        *(.rodata.*)
    } > BOOT_ROM
    
    // 数据段
    .data : {
        *(.data)
        *(.data.*)
    } > RAM AT > BOOT_ROM
    
    // BSS段
    .bss : {
        *(.bss)
        *(.bss.*)
        *(COMMON)
    } > RAM
    
    // 堆栈设置
    .stack : {
        . = ALIGN(4);
        __stack_start = .;
        . += 0x100;     // 256字节堆栈
        __stack_end = .;
    } > RAM
}

三、BootLoader核心功能实现

3.1 主程序框架

// bootloader_main.c
#include "bootloader.h"

// 应用程序跳转函数
typedef void (*app_entry_t)(void);

// 应用程序信息结构
typedef struct {
    uint32_t app_start_addr;    // 应用程序起始地址
    uint32_t app_size;          // 应用程序大小
    uint32_t crc32;             // CRC校验值
    uint8_t  app_version[16];   // 版本信息
} app_info_t;

// 全局应用程序信息
app_info_t g_app_info = {
    .app_start_addr = APP_START_ADDR,
    .app_size = 0,
    .crc32 = 0,
    .app_version = "V1.0.0"
};

// 跳转到应用程序
void Jump_To_Application(void) {
    uint32_t app_addr = g_app_info.app_start_addr;
    app_entry_t app_entry;
    
    // 检查应用程序是否有效
    if (*(uint32_t *)app_addr == 0xFFFFFFFF) {
        // Flash为空,不跳转
        return;
    }
    
    // 设置堆栈指针
    __asm("movw sp, !0xFEF0");
    
    // 获取应用程序入口地址
    app_entry = (app_entry_t)(*(uint32_t *)(app_addr + 4));
    
    // 跳转到应用程序
    app_entry();
}

// 擦除Flash区域
uint8_t Erase_Flash(uint32_t start_addr, uint32_t size) {
    uint32_t end_addr = start_addr + size;
    uint32_t current_addr = start_addr;
    
    while (current_addr < end_addr) {
        // 擦除一页(512字节)
        if (FLASH_Erase(current_addr) != FLASH_SUCCESS) {
            return 0;  // 擦除失败
        }
        current_addr += FLASH_PAGE_SIZE;
    }
    
    return 1;  // 擦除成功
}

// 写入Flash数据
uint8_t Write_Flash(uint32_t addr, uint8_t *data, uint16_t len) {
    uint16_t i;
    
    for (i = 0; i < len; i += 4) {
        uint32_t word_data;
        uint16_t write_len = (len - i) >= 4 ? 4 : (len - i);
        
        // 组装32位数据
        word_data = 0;
        for (uint8_t j = 0; j < write_len; j++) {
            word_data |= (data[i + j] << (j * 8));
        }
        
        // 写入Flash
        if (FLASH_Write(addr + i, word_data) != FLASH_SUCCESS) {
            return 0;  // 写入失败
        }
    }
    
    return 1;  // 写入成功
}

// CRC32计算
uint32_t Calculate_CRC32(uint8_t *data, uint32_t len) {
    uint32_t crc = 0xFFFFFFFF;
    uint32_t i, j;
    
    for (i = 0; i < len; i++) {
        crc ^= data[i];
        for (j = 0; j < 8; j++) {
            if (crc & 1) {
                crc = (crc >> 1) ^ 0xEDB88320;
            } else {
                crc >>= 1;
            }
        }
    }
    
    return ~crc;
}

// 协议解析与处理
void Process_Command(void) {
    if (!cmd_received) return;
    
    uint8_t cmd = rx_buffer[0];
    uint16_t data_len = (rx_buffer[1] << 8) | rx_buffer[2];
    uint8_t *data_ptr = &rx_buffer[3];
    
    switch (cmd) {
        case CMD_HANDSHAKE: {
            // 握手命令
            Send_ACK(CMD_HANDSHAKE);
            break;
        }
        
        case CMD_ERASE_MEM: {
            // 擦除命令
            uint32_t start_addr = (data_ptr[0] << 24) | (data_ptr[1] << 16) | 
                                  (data_ptr[2] << 8) | data_ptr[3];
            uint32_t erase_size = (data_ptr[4] << 24) | (data_ptr[5] << 16) | 
                                  (data_ptr[6] << 8) | data_ptr[7];
            
            if (Erase_Flash(start_addr, erase_size)) {
                Send_ACK(CMD_ERASE_MEM);
            } else {
                Send_NAK(CMD_ERASE_MEM);
            }
            break;
        }
        
        case CMD_WRITE_MEM: {
            // 写入命令
            uint32_t start_addr = (data_ptr[0] << 24) | (data_ptr[1] << 16) | 
                                  (data_ptr[2] << 8) | data_ptr[3];
            uint16_t write_len = data_len - 4;
            
            if (Write_Flash(start_addr, &data_ptr[4], write_len)) {
                Send_ACK(CMD_WRITE_MEM);
            } else {
                Send_NAK(CMD_WRITE_MEM);
            }
            break;
        }
        
        case CMD_VERIFY_MEM: {
            // 验证命令
            uint32_t start_addr = (data_ptr[0] << 24) | (data_ptr[1] << 16) | 
                                  (data_ptr[2] << 8) | data_ptr[3];
            uint32_t verify_size = (data_ptr[4] << 24) | (data_ptr[5] << 16) | 
                                   (data_ptr[6] << 8) | data_ptr[7];
            uint32_t crc_received = (data_ptr[8] << 24) | (data_ptr[9] << 16) | 
                                    (data_ptr[10] << 8) | data_ptr[11];
            
            // 计算CRC
            uint32_t crc_calculated = Calculate_CRC32((uint8_t *)start_addr, verify_size);
            
            if (crc_calculated == crc_received) {
                Send_ACK(CMD_VERIFY_MEM);
            } else {
                Send_NAK(CMD_VERIFY_MEM);
            }
            break;
        }
        
        case CMD_RUN_APP: {
            // 运行应用程序命令
            Send_ACK(CMD_RUN_APP);
            
            // 延迟一段时间确保响应发送完成
            for (volatile uint32_t i = 0; i < 100000; i++);
            
            // 跳转到应用程序
            Jump_To_Application();
            break;
        }
        
        default:
            Send_NAK(cmd);
            break;
    }
    
    // 清空接收缓冲区
    rx_index = 0;
    cmd_received = 0;
}

// BootLoader主函数
void main(void) {
    // 初始化硬件
    System_Init();
    UART_Init(115200);
    Flash_Init();
    
    // 启用接收中断
    SIR01 = 0x0004;  // 使能接收中断
    EI();            // 全局中断使能
    
    // 主循环
    while (1) {
        // 检查是否有命令需要处理
        if (cmd_received) {
            Process_Command();
        }
        
        // 检查是否需要进入BootLoader模式
        if (Check_Update_Flag()) {
            // 进入固件更新模式
            Firmware_Update_Mode();
        } else {
            // 延迟后跳转到应用程序
            Delay_ms(100);
            Jump_To_Application();
        }
    }
}

3.2 应用程序工程配置

// application.c - 应用程序主程序
#include "iodefine.h"

// 应用程序向量表重映射
#pragma section @@VECT VECT
void (*const vector_table[])(void) = {
    // 异常向量
    (void (*)(void))0xFFFFFFFF,  // 复位向量(由BootLoader处理)
    (void (*)(void))0xFFFFFFFF,  // 非法指令
    (void (*)(void))0xFFFFFFFF,  // 非法地址
    
    // 中断向量
    (void (*)(void))Interrupt_Handler0,
    (void (*)(void))Interrupt_Handler1,
    // ... 其他中断向量
};

// 应用程序入口
#pragma entry _start
void _start(void) {
    // 初始化堆栈
    __asm("movw sp, !0xFEF0");
    
    // 初始化硬件
    System_Init();
    
    // 主循环
    while (1) {
        Application_Main();
    }
}

// 检查更新标志(通过特定引脚或Flash标志)
uint8_t Check_Update_Flag(void) {
    // 方法1:通过特定引脚电平
    if (P1 & 0x01) {  // P1.0为高电平时进入BootLoader
        return 1;
    }
    
    // 方法2:通过Flash中的标志位
    uint32_t flag_addr = 0x0000FFFC;  // Flash末尾4字节
    if (*(uint32_t *)flag_addr == 0x5A5A5A5A) {
        // 清除标志
        FLASH_Write(flag_addr, 0xFFFFFFFF);
        return 1;
    }
    
    return 0;
}

// 请求进入BootLoader模式
void Request_BootLoader(void) {
    // 设置更新标志
    uint32_t flag_addr = 0x0000FFFC;
    FLASH_Write(flag_addr, 0x5A5A5A5A);
    
    // 软件复位
    __asm("brk");
}

四、PC端工具实现(C#示例)

// RL78BootLoader.cs - PC端通信工具
using System;
using System.IO.Ports;
using System.Threading;

public class RL78BootLoader
{
    private SerialPort serialPort;
    private const int TIMEOUT = 5000; // 5秒超时
    
    // 命令定义
    private enum Command : byte
    {
        HANDSHAKE = 0x6F,
        WRITE_MEM = 0x02,
        ERASE_MEM = 0x03,
        VERIFY_MEM = 0x04,
        RUN_APP = 0x1F
    }
    
    // 初始化串口
    public bool Initialize(string portName, int baudRate)
    {
        try
        {
            serialPort = new SerialPort(portName, baudRate, Parity.None, 8, StopBits.One);
            serialPort.ReadTimeout = TIMEOUT;
            serialPort.WriteTimeout = TIMEOUT;
            serialPort.Open();
            return true;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"串口初始化失败: {ex.Message}");
            return false;
        }
    }
    
    // 发送握手命令
    public bool Handshake()
    {
        byte[] cmd = new byte[] { (byte)Command.HANDSHAKE, 0x00, 0x00, 0x0D, 0x0A };
        
        for (int i = 0; i < 3; i++) // 重试3次
        {
            serialPort.Write(cmd, 0, cmd.Length);
            
            // 等待ACK响应
            byte[] response = new byte[4];
            int bytesRead = serialPort.Read(response, 0, 4);
            
            if (bytesRead == 4 && 
                response[0] == 0xAA && 
                response[1] == (byte)Command.HANDSHAKE && 
                response[2] == 0x55)
            {
                Console.WriteLine("握手成功");
                return true;
            }
            
            Thread.Sleep(100);
        }
        
        Console.WriteLine("握手失败");
        return false;
    }
    
    // 擦除Flash
    public bool EraseFlash(uint startAddress, uint size)
    {
        byte[] cmd = new byte[12];
        cmd[0] = (byte)Command.ERASE_MEM;
        cmd[1] = 0x00; // 长度高字节
        cmd[2] = 0x08; // 长度低字节(8字节数据)
        
        // 起始地址(大端序)
        cmd[3] = (byte)((startAddress >> 24) & 0xFF);
        cmd[4] = (byte)((startAddress >> 16) & 0xFF);
        cmd[5] = (byte)((startAddress >> 8) & 0xFF);
        cmd[6] = (byte)(startAddress & 0xFF);
        
        // 擦除大小
        cmd[7] = (byte)((size >> 24) & 0xFF);
        cmd[8] = (byte)((size >> 16) & 0xFF);
        cmd[9] = (byte)((size >> 8) & 0xFF);
        cmd[10] = (byte)(size & 0xFF);
        
        cmd[11] = 0x0D; // 结束符
        cmd[12] = 0x0A;
        
        serialPort.Write(cmd, 0, cmd.Length);
        
        // 等待响应
        byte[] response = new byte[4];
        int bytesRead = serialPort.Read(response, 0, 4);
        
        return (bytesRead == 4 && 
                response[0] == 0xAA && 
                response[1] == (byte)Command.ERASE_MEM && 
                response[2] == 0x55);
    }
    
    // 写入Flash数据
    public bool WriteFlash(uint startAddress, byte[] data)
    {
        int chunkSize = 128; // 每次写入128字节
        int totalChunks = (data.Length + chunkSize - 1) / chunkSize;
        
        for (int chunk = 0; chunk < totalChunks; chunk++)
        {
            int offset = chunk * chunkSize;
            int currentSize = Math.Min(chunkSize, data.Length - offset);
            uint currentAddr = startAddress + (uint)offset;
            
            // 构建写入命令
            byte[] cmd = new byte[7 + currentSize];
            cmd[0] = (byte)Command.WRITE_MEM;
            cmd[1] = (byte)(((currentSize + 4) >> 8) & 0xFF); // 长度高字节
            cmd[2] = (byte)((currentSize + 4) & 0xFF);        // 长度低字节
            
            // 写入地址
            cmd[3] = (byte)((currentAddr >> 24) & 0xFF);
            cmd[4] = (byte)((currentAddr >> 16) & 0xFF);
            cmd[5] = (byte)((currentAddr >> 8) & 0xFF);
            cmd[6] = (byte)(currentAddr & 0xFF);
            
            // 写入数据
            Array.Copy(data, offset, cmd, 7, currentSize);
            
            // 添加结束符
            cmd[7 + currentSize] = 0x0D;
            cmd[8 + currentSize] = 0x0A;
            
            // 发送命令
            serialPort.Write(cmd, 0, cmd.Length);
            
            // 等待响应
            byte[] response = new byte[4];
            int bytesRead = serialPort.Read(response, 0, 4);
            
            if (!(bytesRead == 4 && 
                  response[0] == 0xAA && 
                  response[1] == (byte)Command.WRITE_MEM && 
                  response[2] == 0x55))
            {
                Console.WriteLine($"写入失败 at chunk {chunk}");
                return false;
            }
            
            // 进度显示
            Console.Write($"\r进度: {(chunk + 1) * 100 / totalChunks}%");
        }
        
        Console.WriteLine("\n写入完成");
        return true;
    }
    
    // 运行应用程序
    public bool RunApplication()
    {
        byte[] cmd = new byte[] { (byte)Command.RUN_APP, 0x00, 0x00, 0x0D, 0x0A };
        serialPort.Write(cmd, 0, cmd.Length);
        
        byte[] response = new byte[4];
        int bytesRead = serialPort.Read(response, 0, 4);
        
        return (bytesRead == 4 && 
                response[0] == 0xAA && 
                response[1] == (byte)Command.RUN_APP && 
                response[2] == 0x55);
    }
}

五、编译与调试配置

5.1 CS+ for CCRL工程设置

// BootLoader工程配置
[Compiler Options]
-optimize=4
-speed
-nologo
-goptimize
-debug

[Linker Options]
-form=abs
-nologo
-start=vectors,.text,.data,.bss
-rom=.vectors,.text,.rodata
-ram=.data,.bss,.stack

[Memory Settings]
ROM: 0x0000-0x0FFF  // Boot Cluster 0
RAM: 0xFDE0-0xFFDF

// 应用程序工程配置
[Compiler Options]
-optimize=4
-speed
-nologo
-goptimize
-debug

[Linker Options]
-form=abs
-nologo
-start=@@VECT,.text,.data,.bss
-rom=@@VECT,.text,.rodata
-ram=.data,.bss

[Memory Settings]
ROM: 0x2000-0xFFFF  // 应用程序区域
RAM: 0xFDE0-0xFFDF

5.2 关键注意事项

  1. 中断向量重映射:应用程序需要重映射中断向量表到自己的地址空间
  2. Flash操作限制:RL78的Flash自编程需要特定时钟频率和电压条件
  3. Boot Cluster切换:使用boot swap函数前需要确保两个Cluster都有有效程序
  4. 看门狗处理:在Flash操作期间需要禁用或定期喂狗
  5. 电源管理:确保在编程期间电源稳定

六、实际应用示例

6.1 完整OTA流程

// ota_manager.c - OTA管理模块
#include "ota_manager.h"

// OTA状态机
typedef enum {
    OTA_IDLE,
    OTA_CHECK_UPDATE,
    OTA_DOWNLOADING,
    OTA_VERIFYING,
    OTA_UPDATING,
    OTA_COMPLETE,
    OTA_ERROR
} ota_state_t;

// OTA管理器
typedef struct {
    ota_state_t state;
    uint32_t total_size;
    uint32_t received_size;
    uint32_t crc32;
    uint8_t  version[16];
} ota_manager_t;

static ota_manager_t ota_mgr;

// OTA初始化
void OTA_Init(void) {
    memset(&ota_mgr, 0, sizeof(ota_mgr));
    ota_mgr.state = OTA_IDLE;
}

// 检查OTA更新
uint8_t OTA_CheckUpdate(void) {
    // 1. 检查网络/串口是否有更新包
    // 2. 检查版本号
    // 3. 返回是否需要更新
    
    return Check_Update_Flag();
}

// 开始OTA更新
uint8_t OTA_StartUpdate(uint32_t total_size, uint8_t *version) {
    if (ota_mgr.state != OTA_IDLE) {
        return 0;
    }
    
    ota_mgr.state = OTA_DOWNLOADING;
    ota_mgr.total_size = total_size;
    ota_mgr.received_size = 0;
    memcpy(ota_mgr.version, version, 16);
    
    // 请求进入BootLoader模式
    Request_BootLoader();
    
    return 1;
}

// 处理OTA数据
uint8_t OTA_ProcessData(uint8_t *data, uint32_t len) {
    if (ota_mgr.state != OTA_DOWNLOADING) {
        return 0;
    }
    
    // 存储数据到临时缓冲区
    // 更新接收进度
    ota_mgr.received_size += len;
    
    // 计算CRC
    ota_mgr.crc32 = Calculate_CRC32(data, len);
    
    // 检查是否接收完成
    if (ota_mgr.received_size >= ota_mgr.total_size) {
        ota_mgr.state = OTA_VERIFYING;
    }
    
    return 1;
}

// 验证OTA包
uint8_t OTA_Verify(void) {
    if (ota_mgr.state != OTA_VERIFYING) {
        return 0;
    }
    
    // 验证CRC
    uint32_t expected_crc = Get_Expected_CRC();
    if (ota_mgr.crc32 != expected_crc) {
        ota_mgr.state = OTA_ERROR;
        return 0;
    }
    
    ota_mgr.state = OTA_UPDATING;
    return 1;
}

// 执行更新
uint8_t OTA_ExecuteUpdate(void) {
    if (ota_mgr.state != OTA_UPDATING) {
        return 0;
    }
    
    // 执行boot swap
    if (FLASH_BootSwap() != FLASH_SUCCESS) {
        ota_mgr.state = OTA_ERROR;
        return 0;
    }
    
    ota_mgr.state = OTA_COMPLETE;
    
    // 系统复位
    System_Reset();
    
    return 1;
}

参考代码 瑞萨RL78系列 BootLoader例程 www.youwenfan.com/contentcnu/57084.html

七、调试与测试

7.1 测试步骤

  1. 编译BootLoader:使用CS+ for CCRL编译生成.abs文件
  2. 转换格式:使用Renesas Flash Programmer转换为.hex或.mot文件
  3. 烧录BootLoader:通过E1/E2 Lite调试器烧录到RL78
  4. 编译应用程序:设置正确的内存映射,编译应用程序
  5. PC端测试:使用C#工具通过串口连接测试通信
  6. OTA测试:模拟完整OTA流程,验证功能

7.2 常见问题解决

  1. 无法进入BootLoader:检查启动引脚配置和Flash标志位
  2. 通信失败:检查波特率、数据位、停止位设置
  3. Flash写入失败:检查时钟频率和电压是否满足要求
  4. 应用程序无法运行:检查中断向量表重映射是否正确
  5. CRC校验失败:检查数据传输过程中是否有错误

专注于matlab/simulink,电子电路,编程