[教程] 使用瑞萨移植并验证软件算法-07 Base64篇

[复制链接]
12|1
暮雨之仁 发表于 2026-4-27 00:02 | 显示全部楼层 |阅读模式
适合串口传小固件 / 配置、Web 接**互、简单数据转文本存储的轻量级工具 ——Base64 编码 / 解码通用封装。支持任意长度二进制 / 文本数据,还加了可选填充控制(适配不同协议的 Base64 变体),在瑞萨 e²s IDE + RA6M5 上实测通过,代码量适中、无复杂数**算~

一、函数封装介绍
Base64 用 64 个可打印字符(A-Z、a-z、0-9、+、/)表示 3 字节二进制数据,每 3 字节拆成 4 个 6 位组,不足 3 字节时补 0 并加=填充。

函数放在 base64_utils.c 和 base64_utils.h 中,覆盖编码 / 解码、填充控制、长度预计算(避免缓冲区溢出)。

头文件(base64_utils.h)
  1. #ifndef BASE64_UTILS_H
  2. #define BASE64_UTILS_H

  3. #include <stdint.h>
  4. #include <stddef.h>
  5. #include <stdbool.h>

  6. // 预计算编码后长度(含可选填充)
  7. size_t base64_encode_len(size_t bin_len, bool use_padding);

  8. // 预计算解码后最大长度(含填充时自动减,不含填充时按输入长度估算)
  9. size_t base64_decode_max_len(size_t b64_len);

  10. // Base64编码(bin为输入二进制/文本,b64_out为输出缓冲区,use_padding控制是否加=)
  11. // 返回实际编码长度
  12. size_t base64_encode(const uint8_t *bin, size_t bin_len, uint8_t *b64_out, bool use_padding);

  13. // Base64解码(b64为输入Base64字符串,bin_out为输出缓冲区)
  14. // 返回实际解码长度,失败返回0
  15. size_t base64_decode(const uint8_t *b64, size_t b64_len, uint8_t *bin_out);

  16. #endif
实现文件(base64_utils.c)
  1. #include "base64_utils.h"

  2. // Base64标准编码表
  3. static const char b64_encode_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

  4. // Base64标准解码表(0xFF表示非法字符)
  5. static const uint8_t b64_decode_table[256] = {
  6.     0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
  7.     0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
  8.     0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x3E,0xFF,0xFF,0xFF,0x3F,
  9.     0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
  10.     0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,
  11.     0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0xFF,0xFF,0xFF,0xFF,0xFF,
  12.     0xFF,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,
  13.     0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,0x30,0x31,0x32,0x33,0xFF,0xFF,0xFF,0xFF,0xFF,
  14.     0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
  15.     0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
  16.     0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
  17.     0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
  18.     0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
  19.     0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
  20.     0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
  21.     0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF
  22. };

  23. size_t base64_encode_len(size_t bin_len, bool use_padding) {
  24.     size_t len = (bin_len + 2) / 3 * 4;
  25.     if (!use_padding) {
  26.         size_t rem = bin_len % 3;
  27.         if (rem == 1) len -= 2;
  28.         else if (rem == 2) len -= 1;
  29.     }
  30.     return len;
  31. }

  32. size_t base64_decode_max_len(size_t b64_len) {
  33.     return (b64_len + 3) / 4 * 3;
  34. }

  35. size_t base64_encode(const uint8_t *bin, size_t bin_len, uint8_t *b64_out, bool use_padding) {
  36.     size_t i = 0, j = 0;
  37.     uint32_t temp;

  38.     while (i + 3 <= bin_len) {
  39.         temp = (bin[i] << 16) | (bin[i+1] << 8) | bin[i+2];
  40.         b64_out[j++] = b64_encode_table[(temp >> 18) & 0x3F];
  41.         b64_out[j++] = b64_encode_table[(temp >> 12) & 0x3F];
  42.         b64_out[j++] = b64_encode_table[(temp >> 6) & 0x3F];
  43.         b64_out[j++] = b64_encode_table[temp & 0x3F];
  44.         i += 3;
  45.     }

  46.     if (i < bin_len) {
  47.         temp = 0;
  48.         for (size_t k = 0; k < bin_len - i; k++) {
  49.             temp |= (uint32_t)bin[i+k] << (16 - k*8);
  50.         }
  51.         b64_out[j++] = b64_encode_table[(temp >> 18) & 0x3F];
  52.         b64_out[j++] = b64_encode_table[(temp >> 12) & 0x3F];
  53.         if (bin_len - i == 2) {
  54.             b64_out[j++] = b64_encode_table[(temp >> 6) & 0x3F];
  55.             if (use_padding) b64_out[j++] = '=';
  56.         } else {
  57.             if (use_padding) {
  58.                 b64_out[j++] = '=';
  59.                 b64_out[j++] = '=';
  60.             }
  61.         }
  62.     }

  63.     return j;
  64. }

  65. size_t base64_decode(const uint8_t *b64, size_t b64_len, uint8_t *bin_out) {
  66.     size_t i = 0, j = 0;
  67.     uint32_t temp;
  68.     uint8_t group[4];
  69.     size_t group_len;

  70.     while (i < b64_len) {
  71.         group_len = 0;
  72.         for (size_t k = 0; k < 4 && i < b64_len; k++) {
  73.             uint8_t c = b64[i++];
  74.             if (c == '=') break;
  75.             uint8_t val = b64_decode_table[c];
  76.             if (val == 0xFF) return 0; // 非法字符
  77.             group[group_len++] = val;
  78.         }

  79.         if (group_len == 0) break;
  80.         if (group_len < 2) return 0; // 非法分组

  81.         temp = 0;
  82.         for (size_t k = 0; k < group_len; k++) {
  83.             temp |= (uint32_t)group[k] << (18 - k*6);
  84.         }

  85.         bin_out[j++] = (temp >> 16) & 0xFF;
  86.         if (group_len >= 3) bin_out[j++] = (temp >> 8) & 0xFF;
  87.         if (group_len == 4) bin_out[j++] = temp & 0xFF;
  88.     }

  89.     return j;
  90. }
二、使用方法
  • 预计算长度:编码前用 base64_encode_len() 算输出缓冲区大小,解码前用 base64_decode_max_len() 算最大输出(实际解码长度会更小);
  • 编码:传输入二进制 / 文本、长度、输出缓冲区、是否加=;
  • 解码:传输入 Base64 字符串、长度、输出缓冲区,返回 0 表示失败(非法字符 / 非法分组)。


三、移植方法
  • base64_utils.cbase64_utils.h 复制到 e²s 工程目录
  • 在 e²s 中右键工程 → Add Files,将 base64_utils.c 加入编译;
  • 在需要使用的文件中包含头文件:#include "base64_utils.h"
  • 按 “预计算长度 - 编码 / 解码” 流程调用函数即可。


四、调用函数代码验证
以下面这组测试数据为例:
6027969ee333818c85.png
验证代码(基于瑞萨 RA6M5 + e²s IDE)
[url=]复制[/url]


  1. #include "hal_data.h"
  2. #include "base64_utils.h"
  3. #include <stdio.h>
  4. #include <string.h>

  5. void uart_print(const char *str) {
  6.     R_SCI_UART_Write(&g_uart0_ctrl, (uint8_t *)str, strlen(str));
  7. }

  8. void base64_test(void) {
  9.     char buf[256];
  10.     uint8_t temp_b64[256];
  11.     uint8_t temp_bin[256];
  12.     size_t len;

  13.     // --- 第一组:短文本编码/解码(带填充) ---
  14.     uart_print("--- 第一组:短文本编码/解码(带填充) ---\r\n");
  15.     const char *text1 = "Hello Base64!";
  16.     size_t text1_len = strlen(text1);
  17.     // 编码
  18.     size_t b641_len = base64_encode_len(text1_len, true);
  19.     base64_encode((const uint8_t *)text1, text1_len, temp_b64, true);
  20.     temp_b64[b641_len] = '\0';
  21.     snprintf(buf, sizeof(buf), "编码结果:%s\r\n", temp_b64);
  22.     uart_print(buf);
  23.     // 解码
  24.     size_t bin1_max = base64_decode_max_len(b641_len);
  25.     size_t bin1_len = base64_decode(temp_b64, b641_len, temp_bin);
  26.     temp_bin[bin1_len] = '\0';
  27.     snprintf(buf, sizeof(buf), "解码结果:%s(长度:%zu)\r\n\r\n", temp_bin, bin1_len);
  28.     uart_print(buf);

  29.     // --- 第二组:二进制数组编码/解码(不带填充) ---
  30.     uart_print("--- 第二组:二进制数组编码/解码(不带填充) ---\r\n");
  31.     const uint8_t bin2[] = {0x01, 0x02, 0x03, 0x04};
  32.     size_t bin2_len = sizeof(bin2);
  33.     // 编码
  34.     size_t b642_len = base64_encode_len(bin2_len, false);
  35.     base64_encode(bin2, bin2_len, temp_b64, false);
  36.     temp_b64[b642_len] = '\0';
  37.     snprintf(buf, sizeof(buf), "编码结果:%s\r\n", temp_b64);
  38.     uart_print(buf);
  39.     // 解码
  40.     size_t bin2_max = base64_decode_max_len(b642_len);
  41.     size_t bin2_dec_len = base64_decode(temp_b64, b642_len, temp_bin);
  42.     snprintf(buf, sizeof(buf), "解码结果:");
  43.     uart_print(buf);
  44.     for (size_t i = 0; i < bin2_dec_len; i++) {
  45.         snprintf(buf, sizeof(buf), "%02X ", temp_bin[i]);
  46.         uart_print(buf);
  47.     }
  48.     snprintf(buf, sizeof(buf), "(长度:%zu)\r\n", bin2_dec_len);
  49.     uart_print(buf);
  50. }

  51. void hal_entry(void) {
  52.     R_SCI_UART_Open(&g_uart0_ctrl, &g_uart0_cfg);
  53.     base64_test();
  54.     while (1);
  55. }
五、结果对比
    我们通过串口助手验证数据与与其是否相符:

7470269ee3373ec4ea.png
    我们换一组数据继续验证:

7169669ee342fdf0b2.png
新验证代码(直接替换原 base64_test 函数即可)
  1. void base64_test(void) {
  2.     char buf[256];
  3.     uint8_t temp_b64[256];
  4.     uint8_t temp_bin[256];
  5.     size_t len;

  6.     // 1. 边界值:空数据编码
  7.     uart_print("--- 1. 边界值:空数据编码 ---\r\n");
  8.     len = base64_encode_len(0, true);
  9.     size_t encode_len = base64_encode(NULL, 0, temp_b64, true);
  10.     snprintf(buf, sizeof(buf), "编码长度:%zu,预期长度:%zu\r\n\r\n", encode_len, len);
  11.     uart_print(buf);

  12.     // 2. 单字节边界:0x00 带填充编码
  13.     uart_print("--- 2. 单字节0x00(带填充) ---\r\n");
  14.     const uint8_t byte1[] = {0x00};
  15.     len = base64_encode_len(sizeof(byte1), true);
  16.     encode_len = base64_encode(byte1, sizeof(byte1), temp_b64, true);
  17.     temp_b64[encode_len] = '\0';
  18.     snprintf(buf, sizeof(buf), "编码结果:%s,长度:%zu\r\n", temp_b64, encode_len);
  19.     uart_print(buf);
  20.     // 反向解码验证
  21.     size_t decode_len = base64_decode(temp_b64, encode_len, temp_bin);
  22.     snprintf(buf, sizeof(buf), "解码结果:0x%02X,长度:%zu\r\n\r\n", temp_bin[0], decode_len);
  23.     uart_print(buf);

  24.     // 3. 单字节边界:0x00 不带填充编码
  25.     uart_print("--- 3. 单字节0x00(不带填充) ---\r\n");
  26.     len = base64_encode_len(sizeof(byte1), false);
  27.     encode_len = base64_encode(byte1, sizeof(byte1), temp_b64, false);
  28.     temp_b64[encode_len] = '\0';
  29.     snprintf(buf, sizeof(buf), "编码结果:%s,长度:%zu\r\n\r\n", temp_b64, encode_len);
  30.     uart_print(buf);

  31.     // 4. 双字节边界:0x01,0x02 带填充编码
  32.     uart_print("--- 4. 双字节{0x01,0x02}(带填充) ---\r\n");
  33.     const uint8_t byte2[] = {0x01, 0x02};
  34.     len = base64_encode_len(sizeof(byte2), true);
  35.     encode_len = base64_encode(byte2, sizeof(byte2), temp_b64, true);
  36.     temp_b64[encode_len] = '\0';
  37.     snprintf(buf, sizeof(buf), "编码结果:%s,长度:%zu\r\n\r\n", temp_b64, encode_len);
  38.     uart_print(buf);

  39.     // 5. 固件版本号编码/解码
  40.     uart_print("--- 5. 固件版本号编码/解码 ---\r\n");
  41.     const char *fw_ver = "V1.2.3_20260426";
  42.     size_t fw_len = strlen(fw_ver);
  43.     // 编码
  44.     len = base64_encode_len(fw_len, true);
  45.     encode_len = base64_encode((const uint8_t *)fw_ver, fw_len, temp_b64, true);
  46.     temp_b64[encode_len] = '\0';
  47.     snprintf(buf, sizeof(buf), "编码结果:%s\r\n", temp_b64);
  48.     uart_print(buf);
  49.     // 解码
  50.     decode_len = base64_decode(temp_b64, encode_len, temp_bin);
  51.     temp_bin[decode_len] = '\0';
  52.     snprintf(buf, sizeof(buf), "解码结果:%s,长度:%zu\r\n\r\n", temp_bin, decode_len);
  53.     uart_print(buf);

  54.     // 6. 传感器二进制数据编码/解码(不带填充)
  55.     uart_print("--- 6. 传感器二进制数据(不带填充) ---\r\n");
  56.     const uint8_t sensor_data[] = {0x12,0x34,0x56,0x78,0x9A,0xBC,0xDE,0xF0};
  57.     size_t sensor_len = sizeof(sensor_data);
  58.     // 编码
  59.     len = base64_encode_len(sensor_len, false);
  60.     encode_len = base64_encode(sensor_data, sensor_len, temp_b64, false);
  61.     temp_b64[encode_len] = '\0';
  62.     snprintf(buf, sizeof(buf), "编码结果:%s\r\n", temp_b64);
  63.     uart_print(buf);
  64.     // 解码
  65.     decode_len = base64_decode(temp_b64, encode_len, temp_bin);
  66.     snprintf(buf, sizeof(buf), "解码结果:");
  67.     uart_print(buf);
  68.     for (size_t i = 0; i < decode_len; i++) {
  69.         snprintf(buf, sizeof(buf), "%02X ", temp_bin[i]);
  70.         uart_print(buf);
  71.     }
  72.     snprintf(buf, sizeof(buf), ",长度:%zu\r\n\r\n", decode_len);
  73.     uart_print(buf);

  74.     // 7. 中文UTF-8文本编码/解码
  75.     uart_print("--- 7. 中文UTF-8文本编码/解码 ---\r\n");
  76.     const char *cn_text = "21ic瑞萨RA论坛";
  77.     size_t cn_len = strlen(cn_text);
  78.     // 编码
  79.     len = base64_encode_len(cn_len, true);
  80.     encode_len = base64_encode((const uint8_t *)cn_text, cn_len, temp_b64, true);
  81.     temp_b64[encode_len] = '\0';
  82.     snprintf(buf, sizeof(buf), "编码结果:%s\r\n", temp_b64);
  83.     uart_print(buf);
  84.     // 解码
  85.     decode_len = base64_decode(temp_b64, encode_len, temp_bin);
  86.     temp_bin[decode_len] = '\0';
  87.     snprintf(buf, sizeof(buf), "解码结果:%s,长度:%zu\r\n\r\n", temp_bin, decode_len);
  88.     uart_print(buf);

  89.     // 8. 异常容错:非法字符解码
  90.     uart_print("--- 8. 异常容错:非法字符解码 ---\r\n");
  91.     const char *invalid_b64 = "SGVsbG8hIQ!!";
  92.     decode_len = base64_decode((const uint8_t *)invalid_b64, strlen(invalid_b64), temp_bin);
  93.     snprintf(buf, sizeof(buf), "解码返回值:%zu,预期:0(解码失败)\r\n", decode_len);
  94.     uart_print(buf);
  95. }


488469ee36cd087b1.png

注意事项
  • 缓冲区大小:编码 / 解码前必须预计算长度,避免缓冲区溢出;
  • 非法字符处理:解码时遇到非 Base64 标准字符(除=)会返回 0,需提前过滤输入;
  • 填充控制:不同协议的 Base64 变体可能要求不带填充(如 URL-safe Base64),可通过use_padding控制;
  • URL-safe 变体:本封装用的是标准+/,若需 URL-safe,可将编码表的+//换成-/_,解码表对应位置也需修改;
  • 资源占用:代码约 1.5KB Flash,解码表占 256 字节 RAM(可改为 Flash 存储以节省 RAM,适合 8 位机);
  • 大文件处理:本封装支持分段处理(每次处理一部分数据,拼接输出),无需一次性加载所有数据。

以上就是 Base64 编码 / 解码通用封装的验证,代码量适中、功能完整、适配性强,非常适合嵌入式设备的文本转二进制 / 二进制转文本场景。



等我变优秀 发表于 2026-4-28 21:46 | 显示全部楼层
长度预计算函数,这个真的是从根源上解决了嵌入式里最头疼的缓冲区溢出问题。之前用网上找的库,要么为了省事直接开个超大的缓冲区,白白浪费 RA 系列本就不算富余的 RAM;要么自己估算长度,稍微算错一点就越界。楼主的base64_encode_len和base64_decode_max_len,编码解码前先算好所需的缓冲区大小,一分不多一分不少,不仅安全,还把 RAM 占用压到了极致,就算是 RL78 这种只有几 KB RAM 的 8 位机,也能放心用。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

8

主题

15

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部
0