RL78系列BootLoader例程详解
一、RL78 BootLoader基本原理
RL78系列MCU支持双Boot Cluster架构,这是实现BootLoader的关键特性:
- 双Boot Cluster:RL78有两个Boot Cluster(通常各4KB),可通过
boot swap函数切换 - 自编程库:瑞萨提供Flash Self-Programming Library,支持运行时擦写Flash
- 内存映射:Boot Cluster 0地址为0x0000-0x0FFF,Boot Cluster 1地址为0x1000-0x1FFF
- 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 关键注意事项
- 中断向量重映射:应用程序需要重映射中断向量表到自己的地址空间
- Flash操作限制:RL78的Flash自编程需要特定时钟频率和电压条件
- Boot Cluster切换:使用
boot swap函数前需要确保两个Cluster都有有效程序 - 看门狗处理:在Flash操作期间需要禁用或定期喂狗
- 电源管理:确保在编程期间电源稳定
六、实际应用示例
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 测试步骤
- 编译BootLoader:使用CS+ for CCRL编译生成.abs文件
- 转换格式:使用Renesas Flash Programmer转换为.hex或.mot文件
- 烧录BootLoader:通过E1/E2 Lite调试器烧录到RL78
- 编译应用程序:设置正确的内存映射,编译应用程序
- PC端测试:使用C#工具通过串口连接测试通信
- OTA测试:模拟完整OTA流程,验证功能
7.2 常见问题解决
- 无法进入BootLoader:检查启动引脚配置和Flash标志位
- 通信失败:检查波特率、数据位、停止位设置
- Flash写入失败:检查时钟频率和电压是否满足要求
- 应用程序无法运行:检查中断向量表重映射是否正确
- CRC校验失败:检查数据传输过程中是否有错误