2025年1月

都是小端的形式; 以Window Paint.net生成的24位BMP文件为例:
位置|大小|内容
--|--|--
0x0| 2 | BM
0x2| 4 | 文件大小
0x6| 4 | 保留位, 一般全0
0xA| 4 | 像素数据起始位置, 一般为0x36
0xE| 4 | DIB(Bitmap Information Header)头长度(), 一般为0x28
0x12| 4 | 图片宽
0x16| 4 | 图片高
0x1A| 2 | color plane数量, 必须是1
0x1C| 2 | 色深, 24位就是0x18
0x1E| 4 | 压缩方法, 一般全0
0x22| 4 | 图片大小, 一般全0, 因为0x2已经说了文件大小, 减去头部0x36就知道了.
0x26| 4 | 横向分辨率, pixel/meter.
0x2A| 4 | 纵向分辨率, pixel/meter.
0x2E| 4 | color palatte数量, 一般全0
0x32| 4 | 重要颜色使用数量, 一般全0
0x36| n*3 | 颜色像素, 从左下角开始一行一行到右上角. 因为是小端排列,所以是BGR顺序, 每个一个字节. 如果一行不足4字节, 则补0.
像素区域一行补零的方式是: 如宽度为3px的画面, 一行像素为9个字节, 则一行后面补3个0byte, 到12个字节.

https://blog.csdn.net/qq_41126242/article/details/145050324

Nor Flash和Nand Flash

特性 Nor Flash Nand Flash
读写速度 高速,支持随机读写(类似于RAM) 较慢,按块顺序读写
写入速度 较慢 快速
擦除速度 较慢,擦除整个块(约5秒) 快速,擦除块(约4毫秒)
擦除次数 较少,一般为10000次左右 较多,可达100万次以上
可靠性 高,不易发生位反转 一般,易发生位反转,需使用ECC/DCC算法
容量 较小,通常在1MB到32MB之间 较大,通常在8GB到512GB之间
成本 较高 较低
接口 类似SRAM,有独立地址和数据总线 使用I/O总线串行访问
擦除方式 按字节擦除 按块擦除
应用领域 存储代码、关键数据、嵌入式系统等 存储大量数据,如固态硬盘、U盘等
执行方式 支持片上执行(XIP) 需要先将数据读入RAM再执行

硬件连线

Flash Pin MCU--FLash方向 作用
SCK M-->F 串行时钟, 确定Flash工作频率
CS M-->F 片选, 需要交互的时候打开(拉高),用完关闭(拉低)
MOSI M-->F MCU输出/串行输入, 有时候简称SI
MISO M<--F MCU输入/串行输出, 有时候简称SO

此外Flash还有VCC和GND自然不用说, 还有WP(写保护)和 HOLD/RESET(暂停/复位), 这两个一般就拉高处于可写、可用状态, 加起来就8个脚.
还有一种四线SPI的用法加快读的速度(Flash写比较慢也快不了), 叫做QSPI, 此时SI/SO/WP/HOLD都将作为双向IO口与MCU连接. 但多数情况单线就够了.

与Flash的交互通道: SPI的3个寄存器

SPI寄存器 MCU--Flash写入方向 作用
CR M-->F 控制寄存器1~2Byte, 控制Master模式, SPI使能能
DR M<->F 数据寄存器 1Byte, 双向的, 读写都通过这一个. 通常是一串数据包括 1B命令 + 3B地址 + nB数据.
SR M<--F 状态寄存器 1Byte, 读取Flash发来的状态, 主要是看Flash处理完上一条发过去的数据没有

Flash读/擦/写结构

Flash都是必须先擦再写的,否则就写不进去。英文里,为了与普通的写 Write区别, Flash的写叫做Program,所以普通的Write = Erase + Program.
Flash从小到大的结构单位依次是 Page < Sector < Block < Device. Page是Program的最小单位, Sector是Erase的=最小单位. 所以每次写操作是要先在内存中读取并合并至少一个Sector的新旧数据, 擦除并写入的.

最近要搞个新项目, 通过SPI驱动Flash, 芯片是杰理的, 杰理的文档和示例文件都相当于0, 配合的硬件嵌入式工程师说没搞过这块, 就给我连了线, 能读取flash ID就给我了, 也不想搞flash的读写. 他以前倒是搞过, 但是自己写的flash读写代码, 我一看没有1000行也大几百行了. 心里图快, 我实在是不想学习, 于是就想着怎么着去用现成的方案, 调用系统的现成函数, 结果这来来去去搞了一两天, 还是没弄成. 结果李博说flash读写很简单, 一下子就something just clicked for me, 真不如自己操作SPI就写了.
这里面第一个感悟, 就是应该与最原始的东西原汁原味对接, 以为是最麻烦但可能是最节约时间的方式, 原厂做出来也是为了方便用的不是. 算不算是第一性原理?
我发现无论是这次的硬件工程师,还是之前公司配合的硬件工程师, 都喜欢自己写代码和硬件对接. 这次是SPI操作flash,上次是I2C对接触摸芯片. 就是在MCU对接外设这一层级上, 供应链的公司做的都不好, MCU公司给的SDK难用, 缺乏文档, 外设公司的产品不标准, 用MCU公司的SDK接不起来, 出了BUG不能DEBUG, 不如自己看着外设的datasheet自己写代码接, 一次又一次地重复造轮子.这儿就是第二个感悟, MCU与外设对接, 无论是RTOS, SDK, 文档, 接口都缺乏统一标准, 徒增使用门槛, 这也许存在机会,也许是Zephyr的机会. 可惜的是不是国内公司发起的, 可喜的是国内公司在积极参与.

metaso给的回答:

Zephyr OS最初由风河公司(Wind River)开发,并于2016年被Linux基金会接管,成为其管理下的一个开源项目。在项目初期,英特尔、恩智浦半导体公司(NXP)、新思科技(Synopsys)和UbilibiOS Technology Limited等公司是该项目的主要支持者。此外,Linaro也作为白金会员加入了Zephyr项目,为嵌入式和物联网市场提供技术开发和测试的基础。

目前,Zephyr OS由Linux基金会托管,并由一个中立的项目团队维护,这个团队由来自不同公司的开发者和贡献者组成,包括英特尔、恩智浦、正点原子、乐鑫等公司。这些公司通过提供技术支持、代码贡献和资源投入,共同推动了Zephyr OS的发展。

对小孩的感悟: 有用,有趣.

由于打算在特定的杰理MCU中引入第三方的库zlib实现deflate/inflate压缩和解压, 发现比较好的方式是单独选择核心文件进行编译.
zlib官网 https://www.zlib.net/
当前最新的zlib源码 : https://www.zlib.net/zlib-1.3.1.tar.gz
解压缩后, 让cursor生成了一个核心代码的makefile替代原zlib目录下的makefile,使用杰理br25的编译器,自己根据杰理官方的项目中的makefile, 增加了complier flags

TOOL_DIR := C:/JL/pi32/bin
CC    := $(TOOL_DIR)/clang.exe
CXX   := $(TOOL_DIR)/clang.exe
LD    := $(TOOL_DIR)/pi32v2-lto-wrapper.exe
AR    := $(TOOL_DIR)/pi32v2-lto-ar.exe
MKDIR := mkdir_win -p
RM    := rm -rf

# Compiler flags
CFLAGS  = -O2 -Wall -DZ_SOLO \
    -target pi32v2 \
    -mcpu=r3 \
    -integrated-as \
    -flto \
    -Wuninitialized \
    -Wno-invalid-noreturn \
    -fno-common \
    -integrated-as \
    -Oz \
    -g \
    -flto \
    -fallow-pointer-null \
    -fprefer-gnu-section \
    -Wno-shift-negative-value \
    -Wundef \
    -fms-extensions \
    -w \

LDFLAGS =

# Source files
# SRCS = adler32.c compress.c crc32.c deflate.c gzclose.c gzlib.c gzread.c \
#        gzwrite.c infback.c inffast.c inflate.c inftrees.c trees.c uncompr.c zutil.c
SRCS = deflate.c inflate.c zutil.c
# Object files
OBJS = $(SRCS:.c=.o)

# Library name
LIBRARY = libz.a

# Default target
all: $(LIBRARY)

# Rule to build the library
$(LIBRARY): $(OBJS)
    $(AR) rcs $@ $(OBJS)

# Rule to compile source files
%.o: %.c
    $(CC) $(CFLAGS) -c $< -o $@

# Clean target
clean:
    $(RM) $(OBJS) $(LIBRARY)

.PHONY: all clean

zlib目录中运行make, 成功生成libz.a文件.

杰理的官方项目使用Code::Blocks作为编码IDE, 在Code::Blocks项目中添加libz.a: 菜单 > Project > Build Options > Linker Settings > Link Libraries中增加libz.a.
PS: 不放心还可以在other options中增加-LD:\d3\_git\c_proj\SDK_ble\apps\soundbox\zlib

注意, 上面只编译了核心的一些函数, 部分函数如compress/decompress没有. 要检查zlib中有哪些函数: c:\jl\pi32\bin\llvm-nm libz.a
显示如下:

deflate.o:
         U _dist_code
         U _length_code
         U _tr_align
         U _tr_flush_bits
         U _tr_flush_block
         U _tr_init
         U _tr_stored_block
         U adler32
-------- d configuration_table
         U crc32
-------- T deflate
-------- T deflateBound
-------- T deflateCopy
-------- T deflateEnd
-------- T deflateGetDictionary
-------- T deflateInit2_
-------- T deflateInit_
-------- T deflateParams
-------- T deflatePending
-------- T deflatePrime
-------- T deflateReset
-------- T deflateResetKeep
-------- T deflateSetDictionary
-------- T deflateSetHeader
-------- t deflateStateCheck
-------- T deflateTune
-------- D deflate_copyright
-------- t deflate_fast
-------- t deflate_slow
-------- t deflate_stored
-------- t fill_window
-------- t flush_pending
-------- t longest_match
-------- t putShortMSB
-------- t read_buf
-------- t slide_hash
         U z_errmsg
         U zmemcpy
         U zmemzero

inflate.o:
         U adler32
         U crc32
-------- d fixedtables.distfix
-------- d fixedtables.lenfix
-------- T inflate
-------- d inflate.order
-------- T inflateCodesUsed
-------- T inflateCopy
-------- T inflateEnd
-------- T inflateGetDictionary
-------- T inflateGetHeader
-------- T inflateInit2_
-------- T inflateInit_
-------- T inflateMark
-------- T inflatePrime
-------- T inflateReset
-------- T inflateReset2
-------- T inflateResetKeep
-------- T inflateSetDictionary
-------- t inflateStateCheck
-------- T inflateSync
-------- T inflateSyncPoint
-------- T inflateUndermine
-------- T inflateValidate
         U inflate_fast
         U inflate_table
-------- t syncsearch
-------- t updatewindow
         U zmemcpy

zutil.o:
-------- T zError
-------- D z_errmsg
-------- T zlibCompileFlags
-------- T zlibVersion
-------- T zmemcmp
-------- T zmemcpy
-------- T zmemzero

然后在项目的headers中增加zlib.h文件.
示例:


#define CHUNK 16384

int compress_data(const char *src, size_t src_len, unsigned char *dest, size_t *dest_len) {
    int ret;
    z_stream strm;

    strm.zfree = Z_NULL;
    strm.opaque = Z_NULL;

    ret = deflateInit(&strm, Z_DEFAULT_COMPRESSION);
    if (ret != Z_OK) return ret;

    strm.avail_in = src_len;
    strm.next_in = (unsigned char *)src;
    strm.avail_out = CHUNK;
    strm.next_out = dest;

    ret = deflate(&strm, Z_FINISH);
    if (ret != Z_STREAM_END) {
        deflateEnd(&strm);
        return ret;
    }

    *dest_len = CHUNK - strm.avail_out;

    deflateEnd(&strm);
    return Z_OK;
}

int decompress_data(const unsigned char *src, size_t src_len, char *dest, size_t *dest_len) {
    int ret;
    z_stream strm;

    strm.zfree = Z_NULL;
    strm.opaque = Z_NULL;

    ret = inflateInit(&strm);
    if (ret != Z_OK) return ret;

    strm.avail_in = src_len;
    strm.next_in = (unsigned char *)src;

    strm.avail_out = CHUNK;
    strm.next_out = (unsigned char *)dest;

    ret = inflate(&strm, Z_NO_FLUSH);

    if (ret != Z_STREAM_END) {
        inflateEnd(&strm);
        return ret;
    }

    *dest_len = CHUNK - strm.avail_out;

    inflateEnd(&strm);

    return Z_OK;
}

int test_zlib() {
    const char *original_data = "Hello, World! This is a test of the zlib compression library.";

    size_t original_length = strlen(original_data) + 1; // +1 for null terminator

    unsigned char compressed[CHUNK];

   // Compress the data
   size_t compressed_length;
   if(compress_data(original_data, original_length, compressed, &compressed_length) != Z_OK){
       printf("Failed to compress data\n");
       return 1;
   }

   printf("Original length: %zu\n", original_length);
   printf("Compressed length: %zu\n", compressed_length);

   // Decompress the data
   char decompressed[CHUNK];
   size_t decompressed_length;
   if(decompress_data(compressed, compressed_length, decompressed, &decompressed_length) != Z_OK){
       printf("Failed to decompress data\n");
       return 1;
   }

   printf("Decompressed length: %zu\n", decompressed_length);
   printf("Decompressed data: %s\n", decompressed);


   return 0;
}

在主函数中引用test_zlib()就可以编译了. 实测编译通过.